reflectt-node 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +178 -0
- package/README.md +188 -0
- package/dist/activationEvents.d.ts +110 -0
- package/dist/activationEvents.d.ts.map +1 -0
- package/dist/activationEvents.js +378 -0
- package/dist/activationEvents.js.map +1 -0
- package/dist/activity-signal.d.ts +30 -0
- package/dist/activity-signal.d.ts.map +1 -0
- package/dist/activity-signal.js +93 -0
- package/dist/activity-signal.js.map +1 -0
- package/dist/alert-integrity.d.ts +100 -0
- package/dist/alert-integrity.d.ts.map +1 -0
- package/dist/alert-integrity.js +333 -0
- package/dist/alert-integrity.js.map +1 -0
- package/dist/alert-preflight.d.ts +40 -0
- package/dist/alert-preflight.d.ts.map +1 -0
- package/dist/alert-preflight.js +235 -0
- package/dist/alert-preflight.js.map +1 -0
- package/dist/analytics.d.ts +131 -0
- package/dist/analytics.d.ts.map +1 -0
- package/dist/analytics.js +371 -0
- package/dist/analytics.js.map +1 -0
- package/dist/artifact-mirror.d.ts +26 -0
- package/dist/artifact-mirror.d.ts.map +1 -0
- package/dist/artifact-mirror.js +170 -0
- package/dist/artifact-mirror.js.map +1 -0
- package/dist/artifact-resolver.d.ts +48 -0
- package/dist/artifact-resolver.d.ts.map +1 -0
- package/dist/artifact-resolver.js +164 -0
- package/dist/artifact-resolver.js.map +1 -0
- package/dist/assignment.d.ts +116 -0
- package/dist/assignment.d.ts.map +1 -0
- package/dist/assignment.js +475 -0
- package/dist/assignment.js.map +1 -0
- package/dist/auditLedger.d.ts +50 -0
- package/dist/auditLedger.d.ts.map +1 -0
- package/dist/auditLedger.js +136 -0
- package/dist/auditLedger.js.map +1 -0
- package/dist/boardHealthWorker.d.ts +134 -0
- package/dist/boardHealthWorker.d.ts.map +1 -0
- package/dist/boardHealthWorker.js +882 -0
- package/dist/boardHealthWorker.js.map +1 -0
- package/dist/bootstrap-team.d.ts +42 -0
- package/dist/bootstrap-team.d.ts.map +1 -0
- package/dist/bootstrap-team.js +111 -0
- package/dist/bootstrap-team.js.map +1 -0
- package/dist/buildInfo.d.ts +17 -0
- package/dist/buildInfo.d.ts.map +1 -0
- package/dist/buildInfo.js +56 -0
- package/dist/buildInfo.js.map +1 -0
- package/dist/calendar-events.d.ts +133 -0
- package/dist/calendar-events.d.ts.map +1 -0
- package/dist/calendar-events.js +615 -0
- package/dist/calendar-events.js.map +1 -0
- package/dist/calendar-ical.d.ts +41 -0
- package/dist/calendar-ical.d.ts.map +1 -0
- package/dist/calendar-ical.js +413 -0
- package/dist/calendar-ical.js.map +1 -0
- package/dist/calendar-reminder-engine.d.ts +10 -0
- package/dist/calendar-reminder-engine.d.ts.map +1 -0
- package/dist/calendar-reminder-engine.js +143 -0
- package/dist/calendar-reminder-engine.js.map +1 -0
- package/dist/calendar.d.ts +75 -0
- package/dist/calendar.d.ts.map +1 -0
- package/dist/calendar.js +391 -0
- package/dist/calendar.js.map +1 -0
- package/dist/canvas-multiplexer.d.ts +44 -0
- package/dist/canvas-multiplexer.d.ts.map +1 -0
- package/dist/canvas-multiplexer.js +150 -0
- package/dist/canvas-multiplexer.js.map +1 -0
- package/dist/canvas-slots.d.ts +83 -0
- package/dist/canvas-slots.d.ts.map +1 -0
- package/dist/canvas-slots.js +144 -0
- package/dist/canvas-slots.js.map +1 -0
- package/dist/canvas-types.d.ts +56 -0
- package/dist/canvas-types.d.ts.map +1 -0
- package/dist/canvas-types.js +54 -0
- package/dist/canvas-types.js.map +1 -0
- package/dist/cf-keepalive.d.ts +40 -0
- package/dist/cf-keepalive.d.ts.map +1 -0
- package/dist/cf-keepalive.js +153 -0
- package/dist/cf-keepalive.js.map +1 -0
- package/dist/changeFeed.d.ts +38 -0
- package/dist/changeFeed.d.ts.map +1 -0
- package/dist/changeFeed.js +324 -0
- package/dist/changeFeed.js.map +1 -0
- package/dist/channels.d.ts +28 -0
- package/dist/channels.d.ts.map +1 -0
- package/dist/channels.js +23 -0
- package/dist/channels.js.map +1 -0
- package/dist/chat-approval-detector.d.ts +47 -0
- package/dist/chat-approval-detector.d.ts.map +1 -0
- package/dist/chat-approval-detector.js +224 -0
- package/dist/chat-approval-detector.js.map +1 -0
- package/dist/chat.d.ts +119 -0
- package/dist/chat.d.ts.map +1 -0
- package/dist/chat.js +666 -0
- package/dist/chat.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +1142 -0
- package/dist/cli.js.map +1 -0
- package/dist/cloud.d.ts +45 -0
- package/dist/cloud.d.ts.map +1 -0
- package/dist/cloud.js +962 -0
- package/dist/cloud.js.map +1 -0
- package/dist/config.d.ts +17 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +33 -0
- package/dist/config.js.map +1 -0
- package/dist/connectivity.d.ts +59 -0
- package/dist/connectivity.d.ts.map +1 -0
- package/dist/connectivity.js +173 -0
- package/dist/connectivity.js.map +1 -0
- package/dist/contacts.d.ts +59 -0
- package/dist/contacts.d.ts.map +1 -0
- package/dist/contacts.js +183 -0
- package/dist/contacts.js.map +1 -0
- package/dist/content.d.ts +130 -0
- package/dist/content.d.ts.map +1 -0
- package/dist/content.js +186 -0
- package/dist/content.js.map +1 -0
- package/dist/context-budget.d.ts +87 -0
- package/dist/context-budget.d.ts.map +1 -0
- package/dist/context-budget.js +459 -0
- package/dist/context-budget.js.map +1 -0
- package/dist/continuity-loop.d.ts +55 -0
- package/dist/continuity-loop.d.ts.map +1 -0
- package/dist/continuity-loop.js +267 -0
- package/dist/continuity-loop.js.map +1 -0
- package/dist/dashboard.d.ts +6 -0
- package/dist/dashboard.d.ts.map +1 -0
- package/dist/dashboard.js +2348 -0
- package/dist/dashboard.js.map +1 -0
- package/dist/db.d.ts +44 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +648 -0
- package/dist/db.js.map +1 -0
- package/dist/doctor.d.ts +30 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +159 -0
- package/dist/doctor.js.map +1 -0
- package/dist/duplicateClosureGuard.d.ts +31 -0
- package/dist/duplicateClosureGuard.d.ts.map +1 -0
- package/dist/duplicateClosureGuard.js +83 -0
- package/dist/duplicateClosureGuard.js.map +1 -0
- package/dist/embeddings.d.ts +13 -0
- package/dist/embeddings.d.ts.map +1 -0
- package/dist/embeddings.js +78 -0
- package/dist/embeddings.js.map +1 -0
- package/dist/escalation.d.ts +80 -0
- package/dist/escalation.d.ts.map +1 -0
- package/dist/escalation.js +213 -0
- package/dist/escalation.js.map +1 -0
- package/dist/events.d.ts +130 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +382 -0
- package/dist/events.js.map +1 -0
- package/dist/executionSweeper.d.ts +97 -0
- package/dist/executionSweeper.d.ts.map +1 -0
- package/dist/executionSweeper.js +875 -0
- package/dist/executionSweeper.js.map +1 -0
- package/dist/experiments.d.ts +47 -0
- package/dist/experiments.d.ts.map +1 -0
- package/dist/experiments.js +133 -0
- package/dist/experiments.js.map +1 -0
- package/dist/feedback.d.ts +179 -0
- package/dist/feedback.d.ts.map +1 -0
- package/dist/feedback.js +397 -0
- package/dist/feedback.js.map +1 -0
- package/dist/files.d.ts +52 -0
- package/dist/files.d.ts.map +1 -0
- package/dist/files.js +172 -0
- package/dist/files.js.map +1 -0
- package/dist/format-duration.d.ts +19 -0
- package/dist/format-duration.d.ts.map +1 -0
- package/dist/format-duration.js +33 -0
- package/dist/format-duration.js.map +1 -0
- package/dist/github-actor-auth.d.ts +20 -0
- package/dist/github-actor-auth.d.ts.map +1 -0
- package/dist/github-actor-auth.js +54 -0
- package/dist/github-actor-auth.js.map +1 -0
- package/dist/github-ci.d.ts +16 -0
- package/dist/github-ci.d.ts.map +1 -0
- package/dist/github-ci.js +37 -0
- package/dist/github-ci.js.map +1 -0
- package/dist/github-identity.d.ts +30 -0
- package/dist/github-identity.d.ts.map +1 -0
- package/dist/github-identity.js +96 -0
- package/dist/github-identity.js.map +1 -0
- package/dist/github-reviews.d.ts +24 -0
- package/dist/github-reviews.d.ts.map +1 -0
- package/dist/github-reviews.js +56 -0
- package/dist/github-reviews.js.map +1 -0
- package/dist/health.d.ts +391 -0
- package/dist/health.d.ts.map +1 -0
- package/dist/health.js +1841 -0
- package/dist/health.js.map +1 -0
- package/dist/host-keepalive.d.ts +22 -0
- package/dist/host-keepalive.d.ts.map +1 -0
- package/dist/host-keepalive.js +126 -0
- package/dist/host-keepalive.js.map +1 -0
- package/dist/host-registry.d.ts +43 -0
- package/dist/host-registry.d.ts.map +1 -0
- package/dist/host-registry.js +93 -0
- package/dist/host-registry.js.map +1 -0
- package/dist/inbox.d.ts +87 -0
- package/dist/inbox.d.ts.map +1 -0
- package/dist/inbox.js +410 -0
- package/dist/inbox.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +306 -0
- package/dist/index.js.map +1 -0
- package/dist/insight-mutation.d.ts +32 -0
- package/dist/insight-mutation.d.ts.map +1 -0
- package/dist/insight-mutation.js +160 -0
- package/dist/insight-mutation.js.map +1 -0
- package/dist/insight-promotion.d.ts +89 -0
- package/dist/insight-promotion.d.ts.map +1 -0
- package/dist/insight-promotion.js +278 -0
- package/dist/insight-promotion.js.map +1 -0
- package/dist/insight-task-bridge.d.ts +77 -0
- package/dist/insight-task-bridge.d.ts.map +1 -0
- package/dist/insight-task-bridge.js +556 -0
- package/dist/insight-task-bridge.js.map +1 -0
- package/dist/insights.d.ts +222 -0
- package/dist/insights.d.ts.map +1 -0
- package/dist/insights.js +871 -0
- package/dist/insights.js.map +1 -0
- package/dist/intake-pipeline.d.ts +74 -0
- package/dist/intake-pipeline.d.ts.map +1 -0
- package/dist/intake-pipeline.js +199 -0
- package/dist/intake-pipeline.js.map +1 -0
- package/dist/intensity.d.ts +31 -0
- package/dist/intensity.d.ts.map +1 -0
- package/dist/intensity.js +94 -0
- package/dist/intensity.js.map +1 -0
- package/dist/knowledge-auto-index.d.ts +37 -0
- package/dist/knowledge-auto-index.d.ts.map +1 -0
- package/dist/knowledge-auto-index.js +149 -0
- package/dist/knowledge-auto-index.js.map +1 -0
- package/dist/knowledge-docs.d.ts +45 -0
- package/dist/knowledge-docs.d.ts.map +1 -0
- package/dist/knowledge-docs.js +188 -0
- package/dist/knowledge-docs.js.map +1 -0
- package/dist/lane-config.d.ts +25 -0
- package/dist/lane-config.d.ts.map +1 -0
- package/dist/lane-config.js +105 -0
- package/dist/lane-config.js.map +1 -0
- package/dist/lineage.d.ts +86 -0
- package/dist/lineage.d.ts.map +1 -0
- package/dist/lineage.js +303 -0
- package/dist/lineage.js.map +1 -0
- package/dist/logStore.d.ts +25 -0
- package/dist/logStore.d.ts.map +1 -0
- package/dist/logStore.js +83 -0
- package/dist/logStore.js.map +1 -0
- package/dist/manage.d.ts +12 -0
- package/dist/manage.d.ts.map +1 -0
- package/dist/manage.js +253 -0
- package/dist/manage.js.map +1 -0
- package/dist/mcp.d.ts +5 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +604 -0
- package/dist/mcp.js.map +1 -0
- package/dist/memory.d.ts +47 -0
- package/dist/memory.d.ts.map +1 -0
- package/dist/memory.js +149 -0
- package/dist/memory.js.map +1 -0
- package/dist/mention-ack.d.ts +80 -0
- package/dist/mention-ack.d.ts.map +1 -0
- package/dist/mention-ack.js +175 -0
- package/dist/mention-ack.js.map +1 -0
- package/dist/messageRouter.d.ts +60 -0
- package/dist/messageRouter.d.ts.map +1 -0
- package/dist/messageRouter.js +309 -0
- package/dist/messageRouter.js.map +1 -0
- package/dist/mutationAlert.d.ts +44 -0
- package/dist/mutationAlert.d.ts.map +1 -0
- package/dist/mutationAlert.js +174 -0
- package/dist/mutationAlert.js.map +1 -0
- package/dist/noise-budget.d.ts +136 -0
- package/dist/noise-budget.d.ts.map +1 -0
- package/dist/noise-budget.js +340 -0
- package/dist/noise-budget.js.map +1 -0
- package/dist/notifications.d.ts +67 -0
- package/dist/notifications.d.ts.map +1 -0
- package/dist/notifications.js +253 -0
- package/dist/notifications.js.map +1 -0
- package/dist/openclaw.d.ts +34 -0
- package/dist/openclaw.d.ts.map +1 -0
- package/dist/openclaw.js +208 -0
- package/dist/openclaw.js.map +1 -0
- package/dist/pause-controls.d.ts +31 -0
- package/dist/pause-controls.d.ts.map +1 -0
- package/dist/pause-controls.js +130 -0
- package/dist/pause-controls.js.map +1 -0
- package/dist/pidlock.d.ts +25 -0
- package/dist/pidlock.d.ts.map +1 -0
- package/dist/pidlock.js +179 -0
- package/dist/pidlock.js.map +1 -0
- package/dist/policy.d.ts +139 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +264 -0
- package/dist/policy.js.map +1 -0
- package/dist/polls.d.ts +47 -0
- package/dist/polls.d.ts.map +1 -0
- package/dist/polls.js +162 -0
- package/dist/polls.js.map +1 -0
- package/dist/portability.d.ts +55 -0
- package/dist/portability.d.ts.map +1 -0
- package/dist/portability.js +292 -0
- package/dist/portability.js.map +1 -0
- package/dist/pr-integrity.d.ts +45 -0
- package/dist/pr-integrity.d.ts.map +1 -0
- package/dist/pr-integrity.js +124 -0
- package/dist/pr-integrity.js.map +1 -0
- package/dist/prAutoMerge.d.ts +62 -0
- package/dist/prAutoMerge.d.ts.map +1 -0
- package/dist/prAutoMerge.js +493 -0
- package/dist/prAutoMerge.js.map +1 -0
- package/dist/preflight.d.ts +66 -0
- package/dist/preflight.d.ts.map +1 -0
- package/dist/preflight.js +864 -0
- package/dist/preflight.js.map +1 -0
- package/dist/presence.d.ts +98 -0
- package/dist/presence.d.ts.map +1 -0
- package/dist/presence.js +347 -0
- package/dist/presence.js.map +1 -0
- package/dist/provisioning.d.ts +101 -0
- package/dist/provisioning.d.ts.map +1 -0
- package/dist/provisioning.js +430 -0
- package/dist/provisioning.js.map +1 -0
- package/dist/reflection-automation.d.ts +59 -0
- package/dist/reflection-automation.d.ts.map +1 -0
- package/dist/reflection-automation.js +350 -0
- package/dist/reflection-automation.js.map +1 -0
- package/dist/reflections.d.ts +65 -0
- package/dist/reflections.d.ts.map +1 -0
- package/dist/reflections.js +306 -0
- package/dist/reflections.js.map +1 -0
- package/dist/release.d.ts +67 -0
- package/dist/release.d.ts.map +1 -0
- package/dist/release.js +275 -0
- package/dist/release.js.map +1 -0
- package/dist/request-tracker.d.ts +36 -0
- package/dist/request-tracker.d.ts.map +1 -0
- package/dist/request-tracker.js +109 -0
- package/dist/request-tracker.js.map +1 -0
- package/dist/research.d.ts +75 -0
- package/dist/research.d.ts.map +1 -0
- package/dist/research.js +171 -0
- package/dist/research.js.map +1 -0
- package/dist/routing-approvals.d.ts +73 -0
- package/dist/routing-approvals.d.ts.map +1 -0
- package/dist/routing-approvals.js +88 -0
- package/dist/routing-approvals.js.map +1 -0
- package/dist/routing-override.d.ts +94 -0
- package/dist/routing-override.d.ts.map +1 -0
- package/dist/routing-override.js +290 -0
- package/dist/routing-override.js.map +1 -0
- package/dist/scope-routing.d.ts +18 -0
- package/dist/scope-routing.d.ts.map +1 -0
- package/dist/scope-routing.js +29 -0
- package/dist/scope-routing.js.map +1 -0
- package/dist/secrets.d.ts +77 -0
- package/dist/secrets.d.ts.map +1 -0
- package/dist/secrets.js +287 -0
- package/dist/secrets.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +10887 -0
- package/dist/server.js.map +1 -0
- package/dist/service-probe.d.ts +53 -0
- package/dist/service-probe.d.ts.map +1 -0
- package/dist/service-probe.js +225 -0
- package/dist/service-probe.js.map +1 -0
- package/dist/shared-workspace-api.d.ts +73 -0
- package/dist/shared-workspace-api.d.ts.map +1 -0
- package/dist/shared-workspace-api.js +281 -0
- package/dist/shared-workspace-api.js.map +1 -0
- package/dist/shipped-heartbeat.d.ts +91 -0
- package/dist/shipped-heartbeat.d.ts.map +1 -0
- package/dist/shipped-heartbeat.js +272 -0
- package/dist/shipped-heartbeat.js.map +1 -0
- package/dist/starter-team.d.ts +23 -0
- package/dist/starter-team.d.ts.map +1 -0
- package/dist/starter-team.js +88 -0
- package/dist/starter-team.js.map +1 -0
- package/dist/suppression-ledger.d.ts +73 -0
- package/dist/suppression-ledger.d.ts.map +1 -0
- package/dist/suppression-ledger.js +125 -0
- package/dist/suppression-ledger.js.map +1 -0
- package/dist/system-loop-state.d.ts +4 -0
- package/dist/system-loop-state.d.ts.map +1 -0
- package/dist/system-loop-state.js +40 -0
- package/dist/system-loop-state.js.map +1 -0
- package/dist/taskCommentIngest.d.ts +43 -0
- package/dist/taskCommentIngest.d.ts.map +1 -0
- package/dist/taskCommentIngest.js +59 -0
- package/dist/taskCommentIngest.js.map +1 -0
- package/dist/taskPrecheck.d.ts +20 -0
- package/dist/taskPrecheck.d.ts.map +1 -0
- package/dist/taskPrecheck.js +329 -0
- package/dist/taskPrecheck.js.map +1 -0
- package/dist/taskStateSync.d.ts +8 -0
- package/dist/taskStateSync.d.ts.map +1 -0
- package/dist/taskStateSync.js +79 -0
- package/dist/taskStateSync.js.map +1 -0
- package/dist/tasks.d.ts +140 -0
- package/dist/tasks.d.ts.map +1 -0
- package/dist/tasks.js +1281 -0
- package/dist/tasks.js.map +1 -0
- package/dist/team-config.d.ts +24 -0
- package/dist/team-config.d.ts.map +1 -0
- package/dist/team-config.js +221 -0
- package/dist/team-config.js.map +1 -0
- package/dist/team-doctor.d.ts +22 -0
- package/dist/team-doctor.d.ts.map +1 -0
- package/dist/team-doctor.js +270 -0
- package/dist/team-doctor.js.map +1 -0
- package/dist/team-pulse.d.ts +52 -0
- package/dist/team-pulse.d.ts.map +1 -0
- package/dist/team-pulse.js +176 -0
- package/dist/team-pulse.js.map +1 -0
- package/dist/telemetry.d.ts +74 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +256 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/test-task-filter.d.ts +21 -0
- package/dist/test-task-filter.d.ts.map +1 -0
- package/dist/test-task-filter.js +48 -0
- package/dist/test-task-filter.js.map +1 -0
- package/dist/types.d.ts +126 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +4 -0
- package/dist/types.js.map +1 -0
- package/dist/usage-tracking.d.ts +101 -0
- package/dist/usage-tracking.d.ts.map +1 -0
- package/dist/usage-tracking.js +325 -0
- package/dist/usage-tracking.js.map +1 -0
- package/dist/vector-store.d.ts +87 -0
- package/dist/vector-store.d.ts.map +1 -0
- package/dist/vector-store.js +247 -0
- package/dist/vector-store.js.map +1 -0
- package/dist/watchdog/idleNudgeLane.d.ts +22 -0
- package/dist/watchdog/idleNudgeLane.d.ts.map +1 -0
- package/dist/watchdog/idleNudgeLane.js +98 -0
- package/dist/watchdog/idleNudgeLane.js.map +1 -0
- package/dist/webhooks.d.ts +103 -0
- package/dist/webhooks.d.ts.map +1 -0
- package/dist/webhooks.js +398 -0
- package/dist/webhooks.js.map +1 -0
- package/dist/working-contract.d.ts +42 -0
- package/dist/working-contract.d.ts.map +1 -0
- package/dist/working-contract.js +228 -0
- package/dist/working-contract.js.map +1 -0
- package/dist/ws-heartbeat.d.ts +66 -0
- package/dist/ws-heartbeat.d.ts.map +1 -0
- package/dist/ws-heartbeat.js +174 -0
- package/dist/ws-heartbeat.js.map +1 -0
- package/package.json +87 -0
- package/plugins/reflectt-channel/README.md +96 -0
- package/plugins/reflectt-channel/index.ts +789 -0
- package/plugins/reflectt-channel/openclaw.plugin.json +23 -0
- package/plugins/reflectt-channel/package.json +23 -0
- package/plugins/reflectt-channel/src/channel.ts +433 -0
- package/plugins/reflectt-channel/src/types.ts +29 -0
- package/public/avatars/echo.png +0 -0
- package/public/avatars/harmony.png +0 -0
- package/public/avatars/kai.png +0 -0
- package/public/avatars/link.png +0 -0
- package/public/avatars/pixel.png +0 -0
- package/public/avatars/rhythm.png +0 -0
- package/public/avatars/ryan.png +0 -0
- package/public/avatars/sage.png +0 -0
- package/public/avatars/scout.png +0 -0
- package/public/avatars/spark.png +0 -0
- package/public/dashboard-animations.css +381 -0
- package/public/dashboard.js +3479 -0
- package/public/docs.md +1062 -0
- package/public/file-upload-mock.html +1097 -0
- package/public/og-card.png +0 -0
- package/public/ui-kit.html +318 -0
- package/public/widget/feedback.js +194 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
interface ProbeConfig {
|
|
2
|
+
/** Base URL of the reflectt-node service */
|
|
3
|
+
baseUrl: string;
|
|
4
|
+
/** Probe interval in seconds */
|
|
5
|
+
intervalSec: number;
|
|
6
|
+
/** Timeout per endpoint check in ms */
|
|
7
|
+
timeoutMs: number;
|
|
8
|
+
/** Consecutive failures before restart */
|
|
9
|
+
maxRetries: number;
|
|
10
|
+
/** Backoff multiplier for restart attempts */
|
|
11
|
+
backoffMultiplier: number;
|
|
12
|
+
/** Maximum restarts before giving up (per hour) */
|
|
13
|
+
maxRestartsPerHour: number;
|
|
14
|
+
/** Dry run: log but don't restart */
|
|
15
|
+
dryRun: boolean;
|
|
16
|
+
/** Log file path */
|
|
17
|
+
logPath: string;
|
|
18
|
+
/** LaunchAgent label for restart */
|
|
19
|
+
launchAgentLabel: string;
|
|
20
|
+
}
|
|
21
|
+
declare const DEFAULT_CONFIG: ProbeConfig;
|
|
22
|
+
interface EndpointCheck {
|
|
23
|
+
name: string;
|
|
24
|
+
path: string;
|
|
25
|
+
/** Validate the response body */
|
|
26
|
+
validate: (body: unknown) => boolean;
|
|
27
|
+
/** If true, failure of this endpoint alone triggers restart */
|
|
28
|
+
critical: boolean;
|
|
29
|
+
}
|
|
30
|
+
declare const ENDPOINTS: EndpointCheck[];
|
|
31
|
+
interface ProbeState {
|
|
32
|
+
consecutiveFailures: number;
|
|
33
|
+
restartTimestamps: number[];
|
|
34
|
+
lastCheckAt: number | null;
|
|
35
|
+
lastSuccessAt: number | null;
|
|
36
|
+
totalChecks: number;
|
|
37
|
+
totalFailures: number;
|
|
38
|
+
totalRestarts: number;
|
|
39
|
+
}
|
|
40
|
+
declare const state: ProbeState;
|
|
41
|
+
declare function checkEndpoint(config: ProbeConfig, endpoint: EndpointCheck): Promise<{
|
|
42
|
+
ok: boolean;
|
|
43
|
+
latencyMs: number;
|
|
44
|
+
error?: string;
|
|
45
|
+
}>;
|
|
46
|
+
declare function canRestart(config: ProbeConfig): {
|
|
47
|
+
allowed: boolean;
|
|
48
|
+
reason?: string;
|
|
49
|
+
};
|
|
50
|
+
declare function triggerRestart(config: ProbeConfig, reason: string): boolean;
|
|
51
|
+
declare function runProbe(config: ProbeConfig): Promise<void>;
|
|
52
|
+
export { checkEndpoint, runProbe, triggerRestart, canRestart, state as _probeState, ENDPOINTS, DEFAULT_CONFIG, type ProbeConfig, type ProbeState, type EndpointCheck, };
|
|
53
|
+
//# sourceMappingURL=service-probe.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-probe.d.ts","sourceRoot":"","sources":["../src/service-probe.ts"],"names":[],"mappings":"AAcA,UAAU,WAAW;IACnB,4CAA4C;IAC5C,OAAO,EAAE,MAAM,CAAA;IACf,gCAAgC;IAChC,WAAW,EAAE,MAAM,CAAA;IACnB,uCAAuC;IACvC,SAAS,EAAE,MAAM,CAAA;IACjB,0CAA0C;IAC1C,UAAU,EAAE,MAAM,CAAA;IAClB,8CAA8C;IAC9C,iBAAiB,EAAE,MAAM,CAAA;IACzB,mDAAmD;IACnD,kBAAkB,EAAE,MAAM,CAAA;IAC1B,qCAAqC;IACrC,MAAM,EAAE,OAAO,CAAA;IACf,oBAAoB;IACpB,OAAO,EAAE,MAAM,CAAA;IACf,oCAAoC;IACpC,gBAAgB,EAAE,MAAM,CAAA;CACzB;AAED,QAAA,MAAM,cAAc,EAAE,WAUrB,CAAA;AAID,UAAU,aAAa;IACrB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,iCAAiC;IACjC,QAAQ,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,CAAA;IACpC,+DAA+D;IAC/D,QAAQ,EAAE,OAAO,CAAA;CAClB;AAED,QAAA,MAAM,SAAS,EAAE,aAAa,EA4B7B,CAAA;AAID,UAAU,UAAU;IAClB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,iBAAiB,EAAE,MAAM,EAAE,CAAA;IAC3B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,WAAW,EAAE,MAAM,CAAA;IACnB,aAAa,EAAE,MAAM,CAAA;IACrB,aAAa,EAAE,MAAM,CAAA;CACtB;AAED,QAAA,MAAM,KAAK,EAAE,UAQZ,CAAA;AAiBD,iBAAe,aAAa,CAAC,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA0BtI;AAID,iBAAS,UAAU,CAAC,MAAM,EAAE,WAAW,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAY9E;AAED,iBAAS,cAAc,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAgCpE;AAID,iBAAe,QAAQ,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAwC1D;AA0DD,OAAO,EACL,aAAa,EACb,QAAQ,EACR,cAAc,EACd,UAAU,EACV,KAAK,IAAI,WAAW,EACpB,SAAS,EACT,cAAc,EACd,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,aAAa,GACnB,CAAA"}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Synthetic health probe for reflectt-node service.
|
|
3
|
+
//
|
|
4
|
+
// Periodically validates critical endpoints and triggers auto-restart
|
|
5
|
+
// on sustained failure. Runs as a standalone process (not inside the server).
|
|
6
|
+
//
|
|
7
|
+
// Usage: node dist/service-probe.js [--interval 30] [--max-retries 3] [--dry-run]
|
|
8
|
+
import { execSync } from 'node:child_process';
|
|
9
|
+
import { appendFile, mkdir } from 'node:fs/promises';
|
|
10
|
+
import { dirname } from 'node:path';
|
|
11
|
+
const DEFAULT_CONFIG = {
|
|
12
|
+
baseUrl: 'http://127.0.0.1:4445',
|
|
13
|
+
intervalSec: 30,
|
|
14
|
+
timeoutMs: 5000,
|
|
15
|
+
maxRetries: 3,
|
|
16
|
+
backoffMultiplier: 2,
|
|
17
|
+
maxRestartsPerHour: 5,
|
|
18
|
+
dryRun: false,
|
|
19
|
+
logPath: 'logs/service-probe.log',
|
|
20
|
+
launchAgentLabel: 'com.reflectt.node',
|
|
21
|
+
};
|
|
22
|
+
const ENDPOINTS = [
|
|
23
|
+
{
|
|
24
|
+
name: 'health',
|
|
25
|
+
path: '/health',
|
|
26
|
+
validate: (body) => {
|
|
27
|
+
const b = body;
|
|
28
|
+
return b?.status === 'ok';
|
|
29
|
+
},
|
|
30
|
+
critical: true,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: 'tasks-list',
|
|
34
|
+
path: '/tasks?limit=1',
|
|
35
|
+
validate: (body) => {
|
|
36
|
+
const b = body;
|
|
37
|
+
return b?.success === true || Array.isArray(b?.tasks);
|
|
38
|
+
},
|
|
39
|
+
critical: true,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'noise-budget',
|
|
43
|
+
path: '/chat/noise-budget',
|
|
44
|
+
validate: (body) => {
|
|
45
|
+
const b = body;
|
|
46
|
+
return b?.success === true;
|
|
47
|
+
},
|
|
48
|
+
critical: false,
|
|
49
|
+
},
|
|
50
|
+
];
|
|
51
|
+
const state = {
|
|
52
|
+
consecutiveFailures: 0,
|
|
53
|
+
restartTimestamps: [],
|
|
54
|
+
lastCheckAt: null,
|
|
55
|
+
lastSuccessAt: null,
|
|
56
|
+
totalChecks: 0,
|
|
57
|
+
totalFailures: 0,
|
|
58
|
+
totalRestarts: 0,
|
|
59
|
+
};
|
|
60
|
+
// ── Logging ──
|
|
61
|
+
async function log(level, message, extra) {
|
|
62
|
+
const ts = new Date().toISOString();
|
|
63
|
+
const line = JSON.stringify({ ts, level, message, ...extra });
|
|
64
|
+
console.log(`[${level}] ${message}`);
|
|
65
|
+
try {
|
|
66
|
+
await mkdir(dirname(DEFAULT_CONFIG.logPath), { recursive: true });
|
|
67
|
+
await appendFile(DEFAULT_CONFIG.logPath, line + '\n');
|
|
68
|
+
}
|
|
69
|
+
catch { /* ignore log write failures */ }
|
|
70
|
+
}
|
|
71
|
+
// ── HTTP Check ──
|
|
72
|
+
async function checkEndpoint(config, endpoint) {
|
|
73
|
+
const url = `${config.baseUrl}${endpoint.path}`;
|
|
74
|
+
const start = Date.now();
|
|
75
|
+
try {
|
|
76
|
+
const controller = new AbortController();
|
|
77
|
+
const timer = setTimeout(() => controller.abort(), config.timeoutMs);
|
|
78
|
+
const res = await fetch(url, { signal: controller.signal });
|
|
79
|
+
clearTimeout(timer);
|
|
80
|
+
const latencyMs = Date.now() - start;
|
|
81
|
+
if (!res.ok) {
|
|
82
|
+
return { ok: false, latencyMs, error: `HTTP ${res.status}` };
|
|
83
|
+
}
|
|
84
|
+
const body = await res.json();
|
|
85
|
+
const valid = endpoint.validate(body);
|
|
86
|
+
return { ok: valid, latencyMs, error: valid ? undefined : 'validation failed' };
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
const latencyMs = Date.now() - start;
|
|
90
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
91
|
+
return { ok: false, latencyMs, error };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// ── Restart Logic ──
|
|
95
|
+
function canRestart(config) {
|
|
96
|
+
const now = Date.now();
|
|
97
|
+
const hourAgo = now - 3600_000;
|
|
98
|
+
// Prune old timestamps
|
|
99
|
+
state.restartTimestamps = state.restartTimestamps.filter(ts => ts > hourAgo);
|
|
100
|
+
if (state.restartTimestamps.length >= config.maxRestartsPerHour) {
|
|
101
|
+
return { allowed: false, reason: `Max restarts/hour (${config.maxRestartsPerHour}) reached` };
|
|
102
|
+
}
|
|
103
|
+
return { allowed: true };
|
|
104
|
+
}
|
|
105
|
+
function triggerRestart(config, reason) {
|
|
106
|
+
const { allowed, reason: denyReason } = canRestart(config);
|
|
107
|
+
if (!allowed) {
|
|
108
|
+
log('ERROR', `Restart denied: ${denyReason}`, { reason, restartCount: state.totalRestarts });
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
if (config.dryRun) {
|
|
112
|
+
log('WARN', `[DRY-RUN] Would restart: ${reason}`);
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
log('ALERT', `Restarting service: ${reason}`, {
|
|
117
|
+
consecutiveFailures: state.consecutiveFailures,
|
|
118
|
+
restartCount: state.totalRestarts + 1,
|
|
119
|
+
});
|
|
120
|
+
const uid = execSync('id -u').toString().trim();
|
|
121
|
+
execSync(`launchctl kickstart -k gui/${uid}/${config.launchAgentLabel}`, { timeout: 10000 });
|
|
122
|
+
state.restartTimestamps.push(Date.now());
|
|
123
|
+
state.totalRestarts++;
|
|
124
|
+
state.consecutiveFailures = 0;
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
129
|
+
log('ERROR', `Restart failed: ${error}`, { reason });
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// ── Probe Cycle ──
|
|
134
|
+
async function runProbe(config) {
|
|
135
|
+
state.totalChecks++;
|
|
136
|
+
state.lastCheckAt = Date.now();
|
|
137
|
+
const results = [];
|
|
138
|
+
for (const endpoint of ENDPOINTS) {
|
|
139
|
+
const result = await checkEndpoint(config, endpoint);
|
|
140
|
+
results.push({ endpoint: endpoint.name, ...result, critical: endpoint.critical });
|
|
141
|
+
}
|
|
142
|
+
const criticalFailures = results.filter(r => !r.ok && r.critical);
|
|
143
|
+
const allOk = results.every(r => r.ok);
|
|
144
|
+
if (allOk) {
|
|
145
|
+
state.consecutiveFailures = 0;
|
|
146
|
+
state.lastSuccessAt = Date.now();
|
|
147
|
+
// Only log every 10th success to reduce noise
|
|
148
|
+
if (state.totalChecks % 10 === 0) {
|
|
149
|
+
await log('INFO', 'Probe OK', {
|
|
150
|
+
latencies: Object.fromEntries(results.map(r => [r.endpoint, r.latencyMs])),
|
|
151
|
+
totalChecks: state.totalChecks,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
state.consecutiveFailures++;
|
|
157
|
+
state.totalFailures++;
|
|
158
|
+
await log('WARN', `Probe failed (${state.consecutiveFailures}/${config.maxRetries})`, {
|
|
159
|
+
failures: results.filter(r => !r.ok).map(r => ({ endpoint: r.endpoint, error: r.error, latencyMs: r.latencyMs })),
|
|
160
|
+
critical: criticalFailures.length > 0,
|
|
161
|
+
});
|
|
162
|
+
if (criticalFailures.length > 0 && state.consecutiveFailures >= config.maxRetries) {
|
|
163
|
+
const failedNames = criticalFailures.map(f => f.endpoint).join(', ');
|
|
164
|
+
const errors = criticalFailures.map(f => `${f.endpoint}: ${f.error}`).join('; ');
|
|
165
|
+
triggerRestart(config, `${state.consecutiveFailures} consecutive critical failures on [${failedNames}]: ${errors}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// ── Main ──
|
|
170
|
+
function parseArgs() {
|
|
171
|
+
const args = process.argv.slice(2);
|
|
172
|
+
const config = {};
|
|
173
|
+
for (let i = 0; i < args.length; i++) {
|
|
174
|
+
switch (args[i]) {
|
|
175
|
+
case '--interval':
|
|
176
|
+
config.intervalSec = Number(args[++i]);
|
|
177
|
+
break;
|
|
178
|
+
case '--max-retries':
|
|
179
|
+
config.maxRetries = Number(args[++i]);
|
|
180
|
+
break;
|
|
181
|
+
case '--timeout':
|
|
182
|
+
config.timeoutMs = Number(args[++i]);
|
|
183
|
+
break;
|
|
184
|
+
case '--dry-run':
|
|
185
|
+
config.dryRun = true;
|
|
186
|
+
break;
|
|
187
|
+
case '--base-url':
|
|
188
|
+
config.baseUrl = args[++i];
|
|
189
|
+
break;
|
|
190
|
+
case '--log':
|
|
191
|
+
config.logPath = args[++i];
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return config;
|
|
196
|
+
}
|
|
197
|
+
async function main() {
|
|
198
|
+
const overrides = parseArgs();
|
|
199
|
+
const config = { ...DEFAULT_CONFIG, ...overrides };
|
|
200
|
+
await log('INFO', 'Service probe starting', {
|
|
201
|
+
interval: config.intervalSec,
|
|
202
|
+
maxRetries: config.maxRetries,
|
|
203
|
+
timeout: config.timeoutMs,
|
|
204
|
+
dryRun: config.dryRun,
|
|
205
|
+
endpoints: ENDPOINTS.map(e => e.name),
|
|
206
|
+
});
|
|
207
|
+
// Run immediately, then on interval
|
|
208
|
+
await runProbe(config);
|
|
209
|
+
setInterval(() => {
|
|
210
|
+
runProbe(config).catch(err => {
|
|
211
|
+
console.error('[ServiceProbe] Unexpected error:', err);
|
|
212
|
+
});
|
|
213
|
+
}, config.intervalSec * 1000);
|
|
214
|
+
}
|
|
215
|
+
// ── Exported for testing ──
|
|
216
|
+
export { checkEndpoint, runProbe, triggerRestart, canRestart, state as _probeState, ENDPOINTS, DEFAULT_CONFIG, };
|
|
217
|
+
// Run if executed directly
|
|
218
|
+
const isMain = process.argv[1]?.endsWith('service-probe.js') || process.argv[1]?.endsWith('service-probe.ts');
|
|
219
|
+
if (isMain) {
|
|
220
|
+
main().catch(err => {
|
|
221
|
+
console.error('Fatal:', err);
|
|
222
|
+
process.exit(1);
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
//# sourceMappingURL=service-probe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-probe.js","sourceRoot":"","sources":["../src/service-probe.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
export declare const ALLOWED_EXTENSIONS: Set<string>;
|
|
2
|
+
export interface SharedFileEntry {
|
|
3
|
+
name: string;
|
|
4
|
+
path: string;
|
|
5
|
+
type: 'file' | 'directory';
|
|
6
|
+
size?: number;
|
|
7
|
+
extension?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface SharedFileContent {
|
|
10
|
+
path: string;
|
|
11
|
+
content: string;
|
|
12
|
+
size: number;
|
|
13
|
+
truncated: boolean;
|
|
14
|
+
source: 'shared-workspace';
|
|
15
|
+
}
|
|
16
|
+
export interface SharedListResult {
|
|
17
|
+
success: boolean;
|
|
18
|
+
root: string;
|
|
19
|
+
path: string;
|
|
20
|
+
entries: SharedFileEntry[];
|
|
21
|
+
error?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface SharedReadResult {
|
|
24
|
+
success: boolean;
|
|
25
|
+
file?: SharedFileContent;
|
|
26
|
+
error?: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Validate a relative path against security invariants.
|
|
30
|
+
* Returns the resolved absolute path or throws.
|
|
31
|
+
*
|
|
32
|
+
* This is a synchronous pre-check. For full security (symlink containment),
|
|
33
|
+
* use validatePathWithRealpath() which also resolves symlinks.
|
|
34
|
+
*/
|
|
35
|
+
export declare function validatePath(relPath: string): string;
|
|
36
|
+
/**
|
|
37
|
+
* Validate path AND resolve symlinks for full containment check.
|
|
38
|
+
* Prevents symlink escape attacks (e.g., a symlinked dir inside process/ pointing outside root).
|
|
39
|
+
*
|
|
40
|
+
* On macOS, realpath handles APFS case-insensitivity and /var→/private/var canonicalization.
|
|
41
|
+
*/
|
|
42
|
+
export declare function validatePathWithRealpath(relPath: string): Promise<string>;
|
|
43
|
+
/**
|
|
44
|
+
* Validate file extension against allowlist.
|
|
45
|
+
*/
|
|
46
|
+
export declare function validateExtension(filePath: string): void;
|
|
47
|
+
/**
|
|
48
|
+
* List files in a shared workspace directory.
|
|
49
|
+
* Uses lstat to detect symlinks; verifies symlink targets stay within root.
|
|
50
|
+
*/
|
|
51
|
+
export declare function listSharedFiles(relPath?: string, limit?: number): Promise<SharedListResult>;
|
|
52
|
+
/**
|
|
53
|
+
* Read a file from the shared workspace.
|
|
54
|
+
* Returns content (truncated to MAX_FILE_SIZE) or preview (first N chars).
|
|
55
|
+
* Uses realpath containment to defeat symlink escape attacks.
|
|
56
|
+
*/
|
|
57
|
+
export declare function readSharedFile(relPath: string, opts?: {
|
|
58
|
+
preview?: boolean;
|
|
59
|
+
maxChars?: number;
|
|
60
|
+
}): Promise<SharedReadResult>;
|
|
61
|
+
/**
|
|
62
|
+
* Resolve a task artifact path: try workspace root first, then shared workspace fallback.
|
|
63
|
+
* Uses realpath containment for shared workspace paths.
|
|
64
|
+
* Returns metadata about accessibility.
|
|
65
|
+
*/
|
|
66
|
+
export declare function resolveTaskArtifact(artifactPath: string, workspaceRoot: string): Promise<{
|
|
67
|
+
type: 'file' | 'directory' | 'missing';
|
|
68
|
+
accessible: boolean;
|
|
69
|
+
source: 'workspace' | 'shared-workspace' | null;
|
|
70
|
+
resolvedPath: string | null;
|
|
71
|
+
preview?: string;
|
|
72
|
+
}>;
|
|
73
|
+
//# sourceMappingURL=shared-workspace-api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared-workspace-api.d.ts","sourceRoot":"","sources":["../src/shared-workspace-api.ts"],"names":[],"mappings":"AAoBA,eAAO,MAAM,kBAAkB,aAA6D,CAAA;AAK5F,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,GAAG,WAAW,CAAA;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,OAAO,CAAA;IAClB,MAAM,EAAE,kBAAkB,CAAA;CAC3B;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,eAAe,EAAE,CAAA;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAA;IAChB,IAAI,CAAC,EAAE,iBAAiB,CAAA;IACxB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAgCpD;AAED;;;;;GAKG;AACH,wBAAsB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA6B/E;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAKxD;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,OAAO,GAAE,MAAmB,EAAE,KAAK,GAAE,MAAY,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA2ElH;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAC9C,OAAO,CAAC,gBAAgB,CAAC,CA+B3B;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,YAAY,EAAE,MAAM,EACpB,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC;IACT,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAAA;IACtC,UAAU,EAAE,OAAO,CAAA;IACnB,MAAM,EAAE,WAAW,GAAG,kBAAkB,GAAG,IAAI,CAAA;IAC/C,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB,CAAC,CAwED"}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Shared workspace read API — safe, read-only access to shared artifacts.
|
|
3
|
+
//
|
|
4
|
+
// Security invariants:
|
|
5
|
+
// - Only repo-relative paths (no absolute, no drive letters)
|
|
6
|
+
// - Normalize + reject any '..' segments
|
|
7
|
+
// - Allowlist prefixes: process/ (extensible)
|
|
8
|
+
// - realpath containment: resolved real path must be under shared root (defeats symlink escape)
|
|
9
|
+
// - Extension allowlist: .md, .txt, .json, .log, .yml, .yaml
|
|
10
|
+
// - Size cap: 400KB
|
|
11
|
+
// - Listing uses lstat to detect symlinks; symlinks pointing outside root are skipped
|
|
12
|
+
//
|
|
13
|
+
// Note: we do NOT try to model host-credential scoped access here. Access is gated
|
|
14
|
+
// at the API route layer (localhost only for reflectt-node).
|
|
15
|
+
import { promises as fs } from 'fs';
|
|
16
|
+
import { resolve, extname, normalize, relative, isAbsolute } from 'path';
|
|
17
|
+
import { SHARED_WORKSPACE } from './artifact-mirror.js';
|
|
18
|
+
const ALLOWED_PREFIXES = ['process/'];
|
|
19
|
+
export const ALLOWED_EXTENSIONS = new Set(['.md', '.txt', '.json', '.log', '.yml', '.yaml']);
|
|
20
|
+
const MAX_FILE_SIZE = 400 * 1024; // 400KB
|
|
21
|
+
const MAX_PREVIEW_CHARS = 2000;
|
|
22
|
+
const MAX_LIST_ENTRIES = 500;
|
|
23
|
+
/**
|
|
24
|
+
* Validate a relative path against security invariants.
|
|
25
|
+
* Returns the resolved absolute path or throws.
|
|
26
|
+
*
|
|
27
|
+
* This is a synchronous pre-check. For full security (symlink containment),
|
|
28
|
+
* use validatePathWithRealpath() which also resolves symlinks.
|
|
29
|
+
*/
|
|
30
|
+
export function validatePath(relPath) {
|
|
31
|
+
if (!relPath || typeof relPath !== 'string') {
|
|
32
|
+
throw new Error('Path is required');
|
|
33
|
+
}
|
|
34
|
+
// Reject absolute paths / drive letters
|
|
35
|
+
if (isAbsolute(relPath) || /^[A-Za-z]:/.test(relPath)) {
|
|
36
|
+
throw new Error('Absolute paths are not allowed');
|
|
37
|
+
}
|
|
38
|
+
// Reject .. segments (before normalization to prevent bypass)
|
|
39
|
+
if (relPath.includes('..')) {
|
|
40
|
+
throw new Error('Path traversal (..) is not allowed');
|
|
41
|
+
}
|
|
42
|
+
// Check prefix allowlist
|
|
43
|
+
const normalized = normalize(relPath).replace(/\\/g, '/');
|
|
44
|
+
if (!ALLOWED_PREFIXES.some(prefix => normalized.startsWith(prefix))) {
|
|
45
|
+
throw new Error(`Path must start with one of: ${ALLOWED_PREFIXES.join(', ')}`);
|
|
46
|
+
}
|
|
47
|
+
// Resolve against shared workspace root
|
|
48
|
+
const sharedRoot = SHARED_WORKSPACE();
|
|
49
|
+
const resolved = resolve(sharedRoot, relPath);
|
|
50
|
+
// Containment check via path.relative (not string prefix — avoids /root vs /root-evil confusion)
|
|
51
|
+
const rel = relative(sharedRoot, resolved);
|
|
52
|
+
if (rel.startsWith('..') || isAbsolute(rel)) {
|
|
53
|
+
throw new Error('Path escapes shared workspace root');
|
|
54
|
+
}
|
|
55
|
+
return resolved;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Validate path AND resolve symlinks for full containment check.
|
|
59
|
+
* Prevents symlink escape attacks (e.g., a symlinked dir inside process/ pointing outside root).
|
|
60
|
+
*
|
|
61
|
+
* On macOS, realpath handles APFS case-insensitivity and /var→/private/var canonicalization.
|
|
62
|
+
*/
|
|
63
|
+
export async function validatePathWithRealpath(relPath) {
|
|
64
|
+
// Run synchronous checks first
|
|
65
|
+
const resolved = validatePath(relPath);
|
|
66
|
+
// Get real paths (resolves symlinks)
|
|
67
|
+
const sharedRoot = SHARED_WORKSPACE();
|
|
68
|
+
let rootReal;
|
|
69
|
+
try {
|
|
70
|
+
rootReal = await fs.realpath(sharedRoot);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
throw new Error('Shared workspace root does not exist or is inaccessible');
|
|
74
|
+
}
|
|
75
|
+
let candidateReal;
|
|
76
|
+
try {
|
|
77
|
+
candidateReal = await fs.realpath(resolved);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// File doesn't exist yet — the synchronous check already validated structure
|
|
81
|
+
throw new Error('Path does not exist');
|
|
82
|
+
}
|
|
83
|
+
// Containment via path.relative on real paths
|
|
84
|
+
const rel = relative(rootReal, candidateReal);
|
|
85
|
+
if (rel === '')
|
|
86
|
+
return candidateReal; // candidate IS root (for listing)
|
|
87
|
+
if (rel.startsWith('..') || isAbsolute(rel)) {
|
|
88
|
+
throw new Error('Path escapes shared workspace root (symlink escape detected)');
|
|
89
|
+
}
|
|
90
|
+
return candidateReal;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Validate file extension against allowlist.
|
|
94
|
+
*/
|
|
95
|
+
export function validateExtension(filePath) {
|
|
96
|
+
const ext = extname(filePath).toLowerCase();
|
|
97
|
+
if (!ALLOWED_EXTENSIONS.has(ext)) {
|
|
98
|
+
throw new Error(`File extension '${ext}' is not allowed. Allowed: ${[...ALLOWED_EXTENSIONS].join(', ')}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* List files in a shared workspace directory.
|
|
103
|
+
* Uses lstat to detect symlinks; verifies symlink targets stay within root.
|
|
104
|
+
*/
|
|
105
|
+
export async function listSharedFiles(relPath = 'process/', limit = 200) {
|
|
106
|
+
try {
|
|
107
|
+
const realResolved = await validatePathWithRealpath(relPath);
|
|
108
|
+
const sharedRoot = SHARED_WORKSPACE();
|
|
109
|
+
let rootReal;
|
|
110
|
+
try {
|
|
111
|
+
rootReal = await fs.realpath(sharedRoot);
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return { success: false, root: sharedRoot, path: relPath, entries: [], error: 'Shared workspace root inaccessible' };
|
|
115
|
+
}
|
|
116
|
+
const stat = await fs.lstat(realResolved);
|
|
117
|
+
if (!stat.isDirectory()) {
|
|
118
|
+
return { success: false, root: sharedRoot, path: relPath, entries: [], error: 'Path is not a directory' };
|
|
119
|
+
}
|
|
120
|
+
const rawEntries = await fs.readdir(realResolved, { withFileTypes: true });
|
|
121
|
+
const entries = [];
|
|
122
|
+
const effectiveLimit = Math.min(limit, MAX_LIST_ENTRIES);
|
|
123
|
+
for (const entry of rawEntries) {
|
|
124
|
+
if (entries.length >= effectiveLimit)
|
|
125
|
+
break;
|
|
126
|
+
const entryFullPath = resolve(realResolved, entry.name);
|
|
127
|
+
const entryRelPath = `${relPath.replace(/\/$/, '')}/${entry.name}`;
|
|
128
|
+
// Use lstat to detect symlinks
|
|
129
|
+
let entryStat;
|
|
130
|
+
try {
|
|
131
|
+
entryStat = await fs.lstat(entryFullPath);
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
continue; // skip inaccessible
|
|
135
|
+
}
|
|
136
|
+
// For symlinks: resolve and check containment
|
|
137
|
+
if (entryStat.isSymbolicLink()) {
|
|
138
|
+
try {
|
|
139
|
+
const realTarget = await fs.realpath(entryFullPath);
|
|
140
|
+
const rel = relative(rootReal, realTarget);
|
|
141
|
+
if (rel.startsWith('..') || isAbsolute(rel)) {
|
|
142
|
+
continue; // symlink escapes root — skip silently
|
|
143
|
+
}
|
|
144
|
+
entryStat = await fs.stat(realTarget);
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
continue; // broken symlink — skip
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (entryStat.isDirectory()) {
|
|
151
|
+
entries.push({ name: entry.name, path: entryRelPath, type: 'directory' });
|
|
152
|
+
}
|
|
153
|
+
else if (entryStat.isFile()) {
|
|
154
|
+
const ext = extname(entry.name).toLowerCase();
|
|
155
|
+
if (!ALLOWED_EXTENSIONS.has(ext))
|
|
156
|
+
continue; // skip disallowed extensions
|
|
157
|
+
entries.push({
|
|
158
|
+
name: entry.name,
|
|
159
|
+
path: entryRelPath,
|
|
160
|
+
type: 'file',
|
|
161
|
+
size: entryStat.size,
|
|
162
|
+
extension: ext,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// Sort: directories first, then by name
|
|
167
|
+
entries.sort((a, b) => {
|
|
168
|
+
if (a.type !== b.type)
|
|
169
|
+
return a.type === 'directory' ? -1 : 1;
|
|
170
|
+
return a.name.localeCompare(b.name);
|
|
171
|
+
});
|
|
172
|
+
return { success: true, root: sharedRoot, path: relPath, entries };
|
|
173
|
+
}
|
|
174
|
+
catch (err) {
|
|
175
|
+
return { success: false, root: SHARED_WORKSPACE(), path: relPath, entries: [], error: err.message };
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Read a file from the shared workspace.
|
|
180
|
+
* Returns content (truncated to MAX_FILE_SIZE) or preview (first N chars).
|
|
181
|
+
* Uses realpath containment to defeat symlink escape attacks.
|
|
182
|
+
*/
|
|
183
|
+
export async function readSharedFile(relPath, opts) {
|
|
184
|
+
try {
|
|
185
|
+
const realResolved = await validatePathWithRealpath(relPath);
|
|
186
|
+
validateExtension(realResolved);
|
|
187
|
+
const stat = await fs.stat(realResolved);
|
|
188
|
+
if (!stat.isFile()) {
|
|
189
|
+
return { success: false, error: 'Path is not a file' };
|
|
190
|
+
}
|
|
191
|
+
if (stat.size > MAX_FILE_SIZE) {
|
|
192
|
+
return { success: false, error: `File exceeds size limit (${stat.size} bytes > ${MAX_FILE_SIZE} bytes)` };
|
|
193
|
+
}
|
|
194
|
+
const raw = await fs.readFile(realResolved, 'utf-8');
|
|
195
|
+
const maxChars = opts?.preview ? (opts.maxChars || MAX_PREVIEW_CHARS) : raw.length;
|
|
196
|
+
const content = raw.slice(0, maxChars);
|
|
197
|
+
return {
|
|
198
|
+
success: true,
|
|
199
|
+
file: {
|
|
200
|
+
path: relPath,
|
|
201
|
+
content,
|
|
202
|
+
size: stat.size,
|
|
203
|
+
truncated: content.length < raw.length,
|
|
204
|
+
source: 'shared-workspace',
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
catch (err) {
|
|
209
|
+
return { success: false, error: err.message };
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Resolve a task artifact path: try workspace root first, then shared workspace fallback.
|
|
214
|
+
* Uses realpath containment for shared workspace paths.
|
|
215
|
+
* Returns metadata about accessibility.
|
|
216
|
+
*/
|
|
217
|
+
export async function resolveTaskArtifact(artifactPath, workspaceRoot) {
|
|
218
|
+
if (!artifactPath) {
|
|
219
|
+
return { type: 'missing', accessible: false, source: null, resolvedPath: null };
|
|
220
|
+
}
|
|
221
|
+
const trimmed = String(artifactPath).trim();
|
|
222
|
+
const normalizedPath = trimmed.startsWith('shared/') ? trimmed.slice('shared/'.length) : trimmed;
|
|
223
|
+
// Reject obviously unsafe paths early
|
|
224
|
+
if (isAbsolute(normalizedPath) || normalizedPath.includes('..') || normalizedPath.includes('\0')) {
|
|
225
|
+
return { type: 'missing', accessible: false, source: null, resolvedPath: null };
|
|
226
|
+
}
|
|
227
|
+
// Try workspace root first (repo-local)
|
|
228
|
+
const wsPath = resolve(workspaceRoot, normalizedPath);
|
|
229
|
+
// Containment check for workspace root too
|
|
230
|
+
const wsRel = relative(workspaceRoot, wsPath);
|
|
231
|
+
if (!wsRel.startsWith('..') && !isAbsolute(wsRel)) {
|
|
232
|
+
const wsStat = await fs.stat(wsPath).catch(() => null);
|
|
233
|
+
if (wsStat) {
|
|
234
|
+
const type = wsStat.isDirectory() ? 'directory' : 'file';
|
|
235
|
+
let preview;
|
|
236
|
+
if (type === 'file' && wsStat.size <= MAX_FILE_SIZE) {
|
|
237
|
+
const ext = extname(wsPath).toLowerCase();
|
|
238
|
+
if (ALLOWED_EXTENSIONS.has(ext)) {
|
|
239
|
+
const raw = await fs.readFile(wsPath, 'utf-8').catch(() => '');
|
|
240
|
+
preview = raw.slice(0, MAX_PREVIEW_CHARS);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return { type, accessible: true, source: 'workspace', resolvedPath: wsPath, preview };
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
// Fallback to shared workspace — locked down (prefix + extension allowlist)
|
|
247
|
+
// V1 safety: only allow shared fallback for process/* artifacts.
|
|
248
|
+
if (!normalizedPath.startsWith('process/')) {
|
|
249
|
+
return { type: 'missing', accessible: false, source: null, resolvedPath: null };
|
|
250
|
+
}
|
|
251
|
+
// Use the hardened shared-workspace validator (prefix allowlist + traversal + realpath containment).
|
|
252
|
+
let sharedReal;
|
|
253
|
+
try {
|
|
254
|
+
sharedReal = await validatePathWithRealpath(normalizedPath);
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
return { type: 'missing', accessible: false, source: null, resolvedPath: null };
|
|
258
|
+
}
|
|
259
|
+
const sharedStat = await fs.stat(sharedReal).catch(() => null);
|
|
260
|
+
if (sharedStat) {
|
|
261
|
+
const type = sharedStat.isDirectory() ? 'directory' : 'file';
|
|
262
|
+
// For files, enforce extension allowlist before exposing as accessible.
|
|
263
|
+
if (type === 'file') {
|
|
264
|
+
const ext = extname(sharedReal).toLowerCase();
|
|
265
|
+
if (!ALLOWED_EXTENSIONS.has(ext)) {
|
|
266
|
+
return { type: 'missing', accessible: false, source: null, resolvedPath: null };
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
let preview;
|
|
270
|
+
if (type === 'file' && sharedStat.size <= MAX_FILE_SIZE) {
|
|
271
|
+
const ext = extname(sharedReal).toLowerCase();
|
|
272
|
+
if (ALLOWED_EXTENSIONS.has(ext)) {
|
|
273
|
+
const raw = await fs.readFile(sharedReal, 'utf-8').catch(() => '');
|
|
274
|
+
preview = raw.slice(0, MAX_PREVIEW_CHARS);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return { type, accessible: true, source: 'shared-workspace', resolvedPath: sharedReal, preview };
|
|
278
|
+
}
|
|
279
|
+
return { type: 'missing', accessible: false, source: null, resolvedPath: null };
|
|
280
|
+
}
|
|
281
|
+
//# sourceMappingURL=shared-workspace-api.js.map
|