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
package/dist/chat.js
ADDED
|
@@ -0,0 +1,666 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (c) Reflectt AI
|
|
3
|
+
import { promises as fs } from 'fs';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import { eventBus } from './events.js';
|
|
6
|
+
import { DATA_DIR, LEGACY_DATA_DIR } from './config.js';
|
|
7
|
+
import { CHANNEL_DEFINITIONS, DEFAULT_CHAT_CHANNELS } from './channels.js';
|
|
8
|
+
import { getDb, importJsonlIfNeeded, safeJsonParse, safeJsonStringify } from './db.js';
|
|
9
|
+
import { suppressionLedger } from './suppression-ledger.js';
|
|
10
|
+
// OpenClaw integration pending — chat works standalone for now
|
|
11
|
+
const MESSAGES_FILE = join(DATA_DIR, 'messages.jsonl');
|
|
12
|
+
const LEGACY_MESSAGES_FILE = join(LEGACY_DATA_DIR, 'messages.jsonl');
|
|
13
|
+
// All reads go directly to SQLite — no in-memory message cache.
|
|
14
|
+
// JSONL file is kept as an append-only audit trail.
|
|
15
|
+
function importMessages(db, records) {
|
|
16
|
+
const byId = new Map();
|
|
17
|
+
for (const record of records) {
|
|
18
|
+
if (!record || typeof record !== 'object')
|
|
19
|
+
continue;
|
|
20
|
+
const message = record;
|
|
21
|
+
if (typeof message.id !== 'string' || message.id.length === 0)
|
|
22
|
+
continue;
|
|
23
|
+
const deleteUpdate = Boolean(message.metadata?.deleteUpdate);
|
|
24
|
+
if (deleteUpdate) {
|
|
25
|
+
byId.delete(message.id);
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (typeof message.from !== 'string' || typeof message.content !== 'string')
|
|
29
|
+
continue;
|
|
30
|
+
byId.set(message.id, {
|
|
31
|
+
id: message.id,
|
|
32
|
+
from: message.from,
|
|
33
|
+
to: message.to,
|
|
34
|
+
content: message.content,
|
|
35
|
+
timestamp: Number(message.timestamp) || Date.now(),
|
|
36
|
+
channel: message.channel || 'general',
|
|
37
|
+
reactions: message.reactions || {},
|
|
38
|
+
threadId: message.threadId,
|
|
39
|
+
metadata: message.metadata,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
if (byId.size === 0)
|
|
43
|
+
return 0;
|
|
44
|
+
const upsert = db.prepare(`
|
|
45
|
+
INSERT OR REPLACE INTO chat_messages (
|
|
46
|
+
id, "from", "to", content, timestamp, channel, reactions, thread_id, metadata, attachments
|
|
47
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
48
|
+
`);
|
|
49
|
+
const insertMany = db.transaction((messages) => {
|
|
50
|
+
for (const msg of messages) {
|
|
51
|
+
upsert.run(msg.id, msg.from, msg.to ?? null, msg.content, msg.timestamp, msg.channel || 'general', safeJsonStringify(msg.reactions), msg.threadId ?? null, safeJsonStringify(msg.metadata), msg.attachments?.length ? safeJsonStringify(msg.attachments) : null);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
const messages = Array.from(byId.values());
|
|
55
|
+
insertMany(messages);
|
|
56
|
+
return messages.length;
|
|
57
|
+
}
|
|
58
|
+
/** Convert a SQLite row to an AgentMessage */
|
|
59
|
+
function rowToMessage(row) {
|
|
60
|
+
return {
|
|
61
|
+
id: row.id,
|
|
62
|
+
from: row.from,
|
|
63
|
+
to: row.to ?? undefined,
|
|
64
|
+
content: row.content,
|
|
65
|
+
timestamp: row.timestamp,
|
|
66
|
+
channel: row.channel || 'general',
|
|
67
|
+
reactions: safeJsonParse(row.reactions) || {},
|
|
68
|
+
threadId: row.thread_id ?? undefined,
|
|
69
|
+
metadata: safeJsonParse(row.metadata),
|
|
70
|
+
...(row.attachments ? { attachments: safeJsonParse(row.attachments) ?? undefined } : {}),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
class ChatManager {
|
|
74
|
+
rooms = new Map();
|
|
75
|
+
subscribers = new Set();
|
|
76
|
+
initialized = false;
|
|
77
|
+
constructor() {
|
|
78
|
+
// OpenClaw listener disabled for now — chat works standalone
|
|
79
|
+
// TODO: re-enable when OpenClaw connection is configured
|
|
80
|
+
// openclawClient.on('message', ...)
|
|
81
|
+
// Create default rooms/channels
|
|
82
|
+
for (const channel of CHANNEL_DEFINITIONS) {
|
|
83
|
+
this.createRoom(channel.id, channel.name);
|
|
84
|
+
}
|
|
85
|
+
// Run one-time JSONL → SQLite migration
|
|
86
|
+
this.migrateJsonl().catch(err => {
|
|
87
|
+
console.error('[Chat] Failed to migrate JSONL:', err);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
async migrateJsonl() {
|
|
91
|
+
try {
|
|
92
|
+
await fs.mkdir(DATA_DIR, { recursive: true });
|
|
93
|
+
const db = getDb();
|
|
94
|
+
importJsonlIfNeeded(db, MESSAGES_FILE, 'chat_messages', importMessages);
|
|
95
|
+
importJsonlIfNeeded(db, LEGACY_MESSAGES_FILE, 'chat_messages', importMessages);
|
|
96
|
+
// All queries now go directly to SQLite — no in-memory array.
|
|
97
|
+
const countRow = db.prepare('SELECT COUNT(*) as count FROM chat_messages').get();
|
|
98
|
+
console.log(`[Chat] SQLite has ${countRow.count} messages (all queries go to DB, no in-memory cache)`);
|
|
99
|
+
}
|
|
100
|
+
finally {
|
|
101
|
+
this.initialized = true;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
writeMessageToDb(message) {
|
|
105
|
+
const db = getDb();
|
|
106
|
+
const meta = (message.metadata || {});
|
|
107
|
+
const dedupKey = typeof meta.dedup_key === 'string' ? meta.dedup_key : null;
|
|
108
|
+
const upsert = db.prepare(`
|
|
109
|
+
INSERT OR REPLACE INTO chat_messages (
|
|
110
|
+
id, "from", "to", content, timestamp, channel, reactions, thread_id, metadata, dedup_key, attachments
|
|
111
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
112
|
+
`);
|
|
113
|
+
upsert.run(message.id, message.from, message.to ?? null, message.content, message.timestamp, message.channel || 'general', safeJsonStringify(message.reactions), message.threadId ?? null, safeJsonStringify(message.metadata), dedupKey, message.attachments?.length ? safeJsonStringify(message.attachments) : null);
|
|
114
|
+
}
|
|
115
|
+
deleteMessageFromDb(messageId) {
|
|
116
|
+
const db = getDb();
|
|
117
|
+
db.prepare('DELETE FROM chat_messages WHERE id = ?').run(messageId);
|
|
118
|
+
}
|
|
119
|
+
async appendAuditRecord(record) {
|
|
120
|
+
try {
|
|
121
|
+
await fs.mkdir(DATA_DIR, { recursive: true });
|
|
122
|
+
await fs.appendFile(MESSAGES_FILE, JSON.stringify(record) + '\n', 'utf-8');
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
console.error('[Chat] Failed to append audit record:', err);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
async persistMessage(message) {
|
|
129
|
+
try {
|
|
130
|
+
this.writeMessageToDb(message);
|
|
131
|
+
await this.appendAuditRecord(message);
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
console.error('[Chat] Failed to persist message:', err);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
createRoom(id, name) {
|
|
138
|
+
const room = {
|
|
139
|
+
id,
|
|
140
|
+
name,
|
|
141
|
+
participants: [],
|
|
142
|
+
createdAt: Date.now(),
|
|
143
|
+
};
|
|
144
|
+
this.rooms.set(id, room);
|
|
145
|
+
return room;
|
|
146
|
+
}
|
|
147
|
+
getRoom(id) {
|
|
148
|
+
return this.rooms.get(id);
|
|
149
|
+
}
|
|
150
|
+
listRooms() {
|
|
151
|
+
return Array.from(this.rooms.values());
|
|
152
|
+
}
|
|
153
|
+
// ── Noise Budget State ──
|
|
154
|
+
// Per-channel message counts in rolling windows
|
|
155
|
+
channelBudgetCounters = new Map();
|
|
156
|
+
// Duplicate suppression: hash → timestamp of last send
|
|
157
|
+
recentMessageHashes = new Map();
|
|
158
|
+
// Suppression stats tracking
|
|
159
|
+
suppressionStats = { total: 0, byCategory: new Map(), since: Date.now() };
|
|
160
|
+
// Digest batching: queued system reminders per channel
|
|
161
|
+
digestQueue = new Map();
|
|
162
|
+
digestTimers = new Map();
|
|
163
|
+
// Configurable limits
|
|
164
|
+
static CHANNEL_BUDGET_WINDOW_MS = 60 * 60 * 1000; // 1 hour
|
|
165
|
+
static CHANNEL_BUDGET_MAX = {
|
|
166
|
+
general: 30, // max 30 messages per hour in #general
|
|
167
|
+
shipping: 20,
|
|
168
|
+
reviews: 20,
|
|
169
|
+
blockers: 15,
|
|
170
|
+
_default: 40,
|
|
171
|
+
};
|
|
172
|
+
static DEDUP_WINDOW_MS = 5 * 60 * 1000; // 5 minute dedup window (non-system)
|
|
173
|
+
static SYSTEM_DEDUP_WINDOW_MS = 30 * 60 * 1000; // 30 minute dedup window (system messages)
|
|
174
|
+
static DIGEST_BATCH_DELAY_MS = 30 * 1000; // 30s digest batching window
|
|
175
|
+
static SYSTEM_REMINDER_PATTERNS = [
|
|
176
|
+
/^⚠️ Working contract warning/,
|
|
177
|
+
/^⚠️ \[Product Enforcement\]/,
|
|
178
|
+
/^🔄.*Auto-requeued/,
|
|
179
|
+
/^⚠️ SLA breach/,
|
|
180
|
+
/^⚠️ Ready-queue floor/,
|
|
181
|
+
/^🪞 Reflection nudge/,
|
|
182
|
+
/^\[SHIP\]/,
|
|
183
|
+
/^🚨 CRITICAL/,
|
|
184
|
+
/^✅ Auto-closed/,
|
|
185
|
+
];
|
|
186
|
+
/**
|
|
187
|
+
* Check if message is within per-channel budget
|
|
188
|
+
*/
|
|
189
|
+
checkChannelBudget(channel) {
|
|
190
|
+
const now = Date.now();
|
|
191
|
+
const counter = this.channelBudgetCounters.get(channel);
|
|
192
|
+
const maxBudget = ChatManager.CHANNEL_BUDGET_MAX[channel] ?? ChatManager.CHANNEL_BUDGET_MAX._default;
|
|
193
|
+
if (!counter || now - counter.windowStart > ChatManager.CHANNEL_BUDGET_WINDOW_MS) {
|
|
194
|
+
// New window
|
|
195
|
+
this.channelBudgetCounters.set(channel, { count: 1, windowStart: now });
|
|
196
|
+
return { allowed: true };
|
|
197
|
+
}
|
|
198
|
+
if (counter.count >= maxBudget) {
|
|
199
|
+
return { allowed: false, reason: `Channel #${channel} budget exceeded (${counter.count}/${maxBudget} per hour)` };
|
|
200
|
+
}
|
|
201
|
+
counter.count++;
|
|
202
|
+
return { allowed: true };
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Check for duplicate messages within suppression window.
|
|
206
|
+
* System messages use a 30-minute window; others use 5 minutes.
|
|
207
|
+
* Callers can pass an explicit dedup_key in metadata to control dedup grouping.
|
|
208
|
+
*/
|
|
209
|
+
checkDuplicate(from, channel, content, dedupKey) {
|
|
210
|
+
const now = Date.now();
|
|
211
|
+
const isSystem = from === 'system';
|
|
212
|
+
const windowMs = isSystem ? ChatManager.SYSTEM_DEDUP_WINDOW_MS : ChatManager.DEDUP_WINDOW_MS;
|
|
213
|
+
// Clean expired entries (use the longer window to avoid premature cleanup)
|
|
214
|
+
const maxWindow = ChatManager.SYSTEM_DEDUP_WINDOW_MS;
|
|
215
|
+
for (const [key, ts] of this.recentMessageHashes) {
|
|
216
|
+
if (now - ts > maxWindow)
|
|
217
|
+
this.recentMessageHashes.delete(key);
|
|
218
|
+
}
|
|
219
|
+
// Hash: explicit dedup_key if provided, otherwise from + channel + normalized content
|
|
220
|
+
let hash;
|
|
221
|
+
if (dedupKey) {
|
|
222
|
+
hash = `dedup:${channel}:${dedupKey}`;
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
// Normalize: strip timestamps, task IDs, agent-specific names, counts
|
|
226
|
+
const normalized = content
|
|
227
|
+
.replace(/\d{10,}/g, '') // strip epoch timestamps
|
|
228
|
+
.replace(/task-\S+/g, 'TASK') // normalize task IDs
|
|
229
|
+
.replace(/@\w+/g, '@AGENT') // normalize @mentions
|
|
230
|
+
.replace(/\d+\/\d+/g, 'N/M') // normalize counts like "0/2"
|
|
231
|
+
.replace(/\(need \d+ more\)/g, '') // normalize "need N more"
|
|
232
|
+
.trim().slice(0, 200);
|
|
233
|
+
hash = `${from}:${channel}:${normalized}`;
|
|
234
|
+
}
|
|
235
|
+
const lastSent = this.recentMessageHashes.get(hash);
|
|
236
|
+
if (lastSent && (now - lastSent) < windowMs) {
|
|
237
|
+
// Track suppression stats
|
|
238
|
+
this.suppressionStats.total++;
|
|
239
|
+
const category = isSystem ? 'system' : from;
|
|
240
|
+
this.suppressionStats.byCategory.set(category, (this.suppressionStats.byCategory.get(category) || 0) + 1);
|
|
241
|
+
return true; // duplicate
|
|
242
|
+
}
|
|
243
|
+
this.recentMessageHashes.set(hash, now);
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Get suppression statistics
|
|
248
|
+
*/
|
|
249
|
+
getSuppressionStats() {
|
|
250
|
+
return {
|
|
251
|
+
total: this.suppressionStats.total,
|
|
252
|
+
byCategory: Object.fromEntries(this.suppressionStats.byCategory),
|
|
253
|
+
since: this.suppressionStats.since,
|
|
254
|
+
activeHashes: this.recentMessageHashes.size,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Check if message is a system reminder eligible for digest batching
|
|
259
|
+
*/
|
|
260
|
+
isSystemReminder(content) {
|
|
261
|
+
return ChatManager.SYSTEM_REMINDER_PATTERNS.some(p => p.test(content));
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Queue a system reminder for digest batching
|
|
265
|
+
* Returns true if queued (caller should skip sending), false if digest should flush now
|
|
266
|
+
*/
|
|
267
|
+
queueForDigest(channel, from, content) {
|
|
268
|
+
const queue = this.digestQueue.get(channel) || [];
|
|
269
|
+
queue.push({ content, from, queuedAt: Date.now() });
|
|
270
|
+
this.digestQueue.set(channel, queue);
|
|
271
|
+
// If timer already running, just add to queue
|
|
272
|
+
if (this.digestTimers.has(channel))
|
|
273
|
+
return true;
|
|
274
|
+
// Start digest timer
|
|
275
|
+
const timer = setTimeout(() => {
|
|
276
|
+
this.flushDigest(channel);
|
|
277
|
+
this.digestTimers.delete(channel);
|
|
278
|
+
}, ChatManager.DIGEST_BATCH_DELAY_MS);
|
|
279
|
+
timer.unref();
|
|
280
|
+
this.digestTimers.set(channel, timer);
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Flush queued digest messages into a single batched message
|
|
285
|
+
*/
|
|
286
|
+
flushDigest(channel) {
|
|
287
|
+
const queue = this.digestQueue.get(channel);
|
|
288
|
+
if (!queue || queue.length === 0)
|
|
289
|
+
return;
|
|
290
|
+
this.digestQueue.delete(channel);
|
|
291
|
+
if (queue.length === 1) {
|
|
292
|
+
// Single message — send as-is
|
|
293
|
+
void this.sendMessage({ from: queue[0].from, channel, content: queue[0].content });
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
// Batch multiple reminders into a single digest
|
|
297
|
+
const summary = `📋 **System digest** (${queue.length} reminders):\n` +
|
|
298
|
+
queue.map((q, i) => `${i + 1}. ${q.content.split('\n')[0].slice(0, 120)}`).join('\n');
|
|
299
|
+
void this.sendMessage({
|
|
300
|
+
from: 'system',
|
|
301
|
+
channel,
|
|
302
|
+
content: summary,
|
|
303
|
+
metadata: { digest: true, batchedCount: queue.length },
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
async sendMessage(message) {
|
|
307
|
+
const channel = message.channel || 'general';
|
|
308
|
+
// ── Noise Budget Checks ──
|
|
309
|
+
// Skip budget checks for direct messages (has `to` field) and metadata.bypass_budget
|
|
310
|
+
const bypassBudget = message.metadata?.bypass_budget === true || message.to;
|
|
311
|
+
if (!bypassBudget) {
|
|
312
|
+
// 1. Duplicate suppression (supports explicit dedup_key from metadata)
|
|
313
|
+
const dedupKey = message.metadata?.dedup_key;
|
|
314
|
+
if (this.checkDuplicate(message.from, channel, message.content, dedupKey)) {
|
|
315
|
+
console.log(`[Chat/NoiseBudget] Suppressed duplicate from ${message.from} in #${channel}`);
|
|
316
|
+
// Return a synthetic message so callers don't break
|
|
317
|
+
return {
|
|
318
|
+
...message,
|
|
319
|
+
id: `msg-${Date.now()}-suppressed`,
|
|
320
|
+
timestamp: Date.now(),
|
|
321
|
+
channel,
|
|
322
|
+
reactions: {},
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
// 2. System reminder digest batching
|
|
326
|
+
if (this.isSystemReminder(message.content)) {
|
|
327
|
+
if (this.queueForDigest(channel, message.from, message.content)) {
|
|
328
|
+
console.log(`[Chat/NoiseBudget] Queued system reminder for digest in #${channel}`);
|
|
329
|
+
return {
|
|
330
|
+
...message,
|
|
331
|
+
id: `msg-${Date.now()}-queued`,
|
|
332
|
+
timestamp: Date.now(),
|
|
333
|
+
channel,
|
|
334
|
+
reactions: {},
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
// 3. Per-channel budget — DISABLED
|
|
339
|
+
// Was silently dropping messages after 30/hr with a fake success response.
|
|
340
|
+
// Dedup + watchdog are sufficient noise controls.
|
|
341
|
+
// const budget = this.checkChannelBudget(channel)
|
|
342
|
+
// if (!budget.allowed) { ... }
|
|
343
|
+
}
|
|
344
|
+
// ── Persistent suppression ledger (dedup_key) ──
|
|
345
|
+
// For system/automated messages, compute a dedup_key and check the persistent ledger
|
|
346
|
+
const msgMeta = (message.metadata || {});
|
|
347
|
+
const category = typeof msgMeta.category === 'string' ? msgMeta.category : 'general';
|
|
348
|
+
const isSystemSource = message.from === 'system'
|
|
349
|
+
|| typeof msgMeta.category === 'string'
|
|
350
|
+
|| msgMeta.digest === true;
|
|
351
|
+
let dedupKey;
|
|
352
|
+
if (isSystemSource && !bypassBudget) {
|
|
353
|
+
const check = suppressionLedger.check({
|
|
354
|
+
category,
|
|
355
|
+
channel,
|
|
356
|
+
from: message.from,
|
|
357
|
+
content: message.content,
|
|
358
|
+
});
|
|
359
|
+
dedupKey = check.dedup_key;
|
|
360
|
+
if (check.isDuplicate) {
|
|
361
|
+
console.log(`[Chat/SuppressionLedger] Suppressed duplicate (key=${dedupKey}) from ${message.from} in #${channel}`);
|
|
362
|
+
return {
|
|
363
|
+
...message,
|
|
364
|
+
id: `msg-${Date.now()}-ledger-suppressed`,
|
|
365
|
+
timestamp: Date.now(),
|
|
366
|
+
channel,
|
|
367
|
+
reactions: {},
|
|
368
|
+
metadata: { ...msgMeta, dedup_key: dedupKey, suppressed: true },
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
const fullMessage = {
|
|
373
|
+
...message,
|
|
374
|
+
id: `msg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
375
|
+
timestamp: Date.now(),
|
|
376
|
+
channel,
|
|
377
|
+
reactions: message.reactions || {},
|
|
378
|
+
threadId: message.threadId,
|
|
379
|
+
metadata: dedupKey ? { ...msgMeta, dedup_key: dedupKey } : message.metadata,
|
|
380
|
+
};
|
|
381
|
+
// Persist to SQLite + JSONL audit
|
|
382
|
+
await this.persistMessage(fullMessage);
|
|
383
|
+
// Notify local subscribers
|
|
384
|
+
this.notifySubscribers(fullMessage);
|
|
385
|
+
// Emit event to event bus
|
|
386
|
+
eventBus.emitMessagePosted(fullMessage);
|
|
387
|
+
// Route to agent inboxes (auto-routing)
|
|
388
|
+
this.routeToInboxes(fullMessage);
|
|
389
|
+
// TODO: Send via OpenClaw when connected
|
|
390
|
+
return fullMessage;
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Route message to agent inboxes
|
|
394
|
+
* This is called automatically when a message is posted
|
|
395
|
+
*/
|
|
396
|
+
routeToInboxes(message) {
|
|
397
|
+
// Import here to avoid circular dependency at module level
|
|
398
|
+
import('./inbox.js')
|
|
399
|
+
.then(({ inboxManager }) => {
|
|
400
|
+
const agents = this.getKnownAgents();
|
|
401
|
+
inboxManager.routeMessage(message, agents);
|
|
402
|
+
})
|
|
403
|
+
.catch(err => {
|
|
404
|
+
console.error('[Chat] Failed to route message to inboxes:', err);
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Get list of all known agents from SQLite.
|
|
409
|
+
*/
|
|
410
|
+
getKnownAgents() {
|
|
411
|
+
const db = getDb();
|
|
412
|
+
const rows = db.prepare(`
|
|
413
|
+
SELECT DISTINCT "from" as agent FROM chat_messages
|
|
414
|
+
UNION
|
|
415
|
+
SELECT DISTINCT "to" as agent FROM chat_messages WHERE "to" IS NOT NULL
|
|
416
|
+
`).all();
|
|
417
|
+
return rows.map(r => r.agent).filter((v) => Boolean(v));
|
|
418
|
+
}
|
|
419
|
+
handleIncomingMessage(message) {
|
|
420
|
+
// Persist to DB (INSERT OR REPLACE handles dedup)
|
|
421
|
+
this.writeMessageToDb(message);
|
|
422
|
+
this.notifySubscribers(message);
|
|
423
|
+
}
|
|
424
|
+
hydrateRows(rows) {
|
|
425
|
+
return rows.map((row) => ({
|
|
426
|
+
id: row.id,
|
|
427
|
+
from: row.from,
|
|
428
|
+
to: row.to ?? undefined,
|
|
429
|
+
content: row.content,
|
|
430
|
+
timestamp: row.timestamp,
|
|
431
|
+
channel: row.channel || 'general',
|
|
432
|
+
reactions: safeJsonParse(row.reactions) || {},
|
|
433
|
+
threadId: row.thread_id ?? undefined,
|
|
434
|
+
metadata: safeJsonParse(row.metadata),
|
|
435
|
+
}));
|
|
436
|
+
}
|
|
437
|
+
getMessages(options) {
|
|
438
|
+
const db = getDb();
|
|
439
|
+
const conditions = [];
|
|
440
|
+
const params = [];
|
|
441
|
+
if (options?.from) {
|
|
442
|
+
conditions.push('"from" = ?');
|
|
443
|
+
params.push(options.from);
|
|
444
|
+
}
|
|
445
|
+
if (options?.excludeFrom) {
|
|
446
|
+
conditions.push('"from" != ?');
|
|
447
|
+
params.push(options.excludeFrom);
|
|
448
|
+
}
|
|
449
|
+
if (options?.to) {
|
|
450
|
+
conditions.push('"to" = ?');
|
|
451
|
+
params.push(options.to);
|
|
452
|
+
}
|
|
453
|
+
if (options?.channel) {
|
|
454
|
+
conditions.push('channel = ?');
|
|
455
|
+
params.push(options.channel);
|
|
456
|
+
}
|
|
457
|
+
if (options?.since) {
|
|
458
|
+
conditions.push('timestamp >= ?');
|
|
459
|
+
params.push(options.since);
|
|
460
|
+
}
|
|
461
|
+
if (options?.before) {
|
|
462
|
+
conditions.push('timestamp < ?');
|
|
463
|
+
params.push(options.before);
|
|
464
|
+
}
|
|
465
|
+
if (options?.after) {
|
|
466
|
+
conditions.push('timestamp > ?');
|
|
467
|
+
params.push(options.after);
|
|
468
|
+
}
|
|
469
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
|
|
470
|
+
const limit = options?.limit !== undefined ? options.limit : 20;
|
|
471
|
+
// Fetch newest-first for efficiency, then return ascending.
|
|
472
|
+
const sql = limit > 0
|
|
473
|
+
? `SELECT * FROM (SELECT * FROM chat_messages ${where} ORDER BY timestamp DESC LIMIT ?) ORDER BY timestamp ASC`
|
|
474
|
+
: `SELECT * FROM chat_messages ${where} ORDER BY timestamp ASC`;
|
|
475
|
+
if (limit > 0)
|
|
476
|
+
params.push(limit);
|
|
477
|
+
const rows = db.prepare(sql).all(...params);
|
|
478
|
+
const messages = rows.map(rowToMessage);
|
|
479
|
+
return this.addReplyCounts(messages);
|
|
480
|
+
}
|
|
481
|
+
addReplyCounts(messages) {
|
|
482
|
+
if (messages.length === 0)
|
|
483
|
+
return messages;
|
|
484
|
+
const db = getDb();
|
|
485
|
+
const ids = messages.map(m => m.id);
|
|
486
|
+
// Build `IN (?, ?, ...)` safely.
|
|
487
|
+
const placeholders = ids.map(() => '?').join(',');
|
|
488
|
+
const rows = db.prepare(`
|
|
489
|
+
SELECT thread_id as id, COUNT(*) as c
|
|
490
|
+
FROM chat_messages
|
|
491
|
+
WHERE thread_id IN (${placeholders})
|
|
492
|
+
GROUP BY thread_id
|
|
493
|
+
`).all(...ids);
|
|
494
|
+
const map = new Map(rows.map(r => [r.id, r.c]));
|
|
495
|
+
return messages.map(m => ({ ...m, replyCount: map.get(m.id) ?? 0 }));
|
|
496
|
+
}
|
|
497
|
+
getThread(messageId) {
|
|
498
|
+
const db = getDb();
|
|
499
|
+
const parentRow = db.prepare('SELECT * FROM chat_messages WHERE id = ?').get(messageId);
|
|
500
|
+
if (!parentRow)
|
|
501
|
+
return null;
|
|
502
|
+
const parent = rowToMessage(parentRow);
|
|
503
|
+
const replyRows = db.prepare('SELECT * FROM chat_messages WHERE thread_id = ? ORDER BY timestamp ASC').all(messageId);
|
|
504
|
+
const replies = replyRows.map(rowToMessage);
|
|
505
|
+
return this.addReplyCounts([parent, ...replies]);
|
|
506
|
+
}
|
|
507
|
+
subscribe(callback) {
|
|
508
|
+
this.subscribers.add(callback);
|
|
509
|
+
return () => this.subscribers.delete(callback);
|
|
510
|
+
}
|
|
511
|
+
notifySubscribers(message) {
|
|
512
|
+
this.subscribers.forEach(callback => {
|
|
513
|
+
try {
|
|
514
|
+
callback(message);
|
|
515
|
+
}
|
|
516
|
+
catch (err) {
|
|
517
|
+
console.error('[Chat] Subscriber error:', err);
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
getChannels() {
|
|
522
|
+
const db = getDb();
|
|
523
|
+
const rows = db.prepare(`
|
|
524
|
+
SELECT COALESCE(channel, 'general') as channel, COUNT(*) as count, MAX(timestamp) as lastActivity
|
|
525
|
+
FROM chat_messages
|
|
526
|
+
GROUP BY COALESCE(channel, 'general')
|
|
527
|
+
ORDER BY lastActivity DESC
|
|
528
|
+
`).all();
|
|
529
|
+
// Include default channels even if empty
|
|
530
|
+
const channelMap = new Map();
|
|
531
|
+
DEFAULT_CHAT_CHANNELS.forEach(ch => channelMap.set(ch, { count: 0, lastActivity: 0 }));
|
|
532
|
+
for (const row of rows) {
|
|
533
|
+
channelMap.set(row.channel, { count: row.count, lastActivity: row.lastActivity });
|
|
534
|
+
}
|
|
535
|
+
return Array.from(channelMap.entries())
|
|
536
|
+
.map(([channel, data]) => ({ channel, ...data }))
|
|
537
|
+
.sort((a, b) => b.lastActivity - a.lastActivity);
|
|
538
|
+
}
|
|
539
|
+
async addReaction(messageId, emoji, from) {
|
|
540
|
+
const db = getDb();
|
|
541
|
+
const row = db.prepare('SELECT * FROM chat_messages WHERE id = ?').get(messageId);
|
|
542
|
+
if (!row)
|
|
543
|
+
return null;
|
|
544
|
+
const message = rowToMessage(row);
|
|
545
|
+
// Initialize reactions if needed
|
|
546
|
+
if (!message.reactions)
|
|
547
|
+
message.reactions = {};
|
|
548
|
+
if (!message.reactions[emoji])
|
|
549
|
+
message.reactions[emoji] = [];
|
|
550
|
+
const agents = message.reactions[emoji];
|
|
551
|
+
const index = agents.indexOf(from);
|
|
552
|
+
if (index >= 0) {
|
|
553
|
+
agents.splice(index, 1);
|
|
554
|
+
if (agents.length === 0)
|
|
555
|
+
delete message.reactions[emoji];
|
|
556
|
+
}
|
|
557
|
+
else {
|
|
558
|
+
agents.push(from);
|
|
559
|
+
}
|
|
560
|
+
this.writeMessageToDb(message);
|
|
561
|
+
await this.appendAuditRecord(message);
|
|
562
|
+
this.notifySubscribers(message);
|
|
563
|
+
eventBus.emit({
|
|
564
|
+
id: `evt-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
565
|
+
type: 'message_posted',
|
|
566
|
+
timestamp: Date.now(),
|
|
567
|
+
data: { ...message, reactionUpdate: true },
|
|
568
|
+
});
|
|
569
|
+
return message;
|
|
570
|
+
}
|
|
571
|
+
getReactions(messageId) {
|
|
572
|
+
const db = getDb();
|
|
573
|
+
const row = db.prepare('SELECT reactions FROM chat_messages WHERE id = ?').get(messageId);
|
|
574
|
+
if (!row)
|
|
575
|
+
return null;
|
|
576
|
+
return safeJsonParse(row.reactions) || {};
|
|
577
|
+
}
|
|
578
|
+
async editMessage(messageId, editor, content) {
|
|
579
|
+
const trimmed = content.trim();
|
|
580
|
+
if (!trimmed)
|
|
581
|
+
return { ok: false, error: 'invalid_content' };
|
|
582
|
+
const db = getDb();
|
|
583
|
+
const row = db.prepare('SELECT * FROM chat_messages WHERE id = ?').get(messageId);
|
|
584
|
+
if (!row) {
|
|
585
|
+
return { ok: false, error: 'not_found' };
|
|
586
|
+
}
|
|
587
|
+
const message = rowToMessage(row);
|
|
588
|
+
if (message.from !== editor) {
|
|
589
|
+
return { ok: false, error: 'forbidden' };
|
|
590
|
+
}
|
|
591
|
+
message.content = trimmed;
|
|
592
|
+
message.metadata = {
|
|
593
|
+
...(message.metadata || {}),
|
|
594
|
+
editedAt: Date.now(),
|
|
595
|
+
editedBy: editor,
|
|
596
|
+
};
|
|
597
|
+
this.writeMessageToDb(message);
|
|
598
|
+
await this.appendAuditRecord(message);
|
|
599
|
+
// No in-memory cache to update.
|
|
600
|
+
this.notifySubscribers(message);
|
|
601
|
+
eventBus.emit({
|
|
602
|
+
id: `evt-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
603
|
+
type: 'message_posted',
|
|
604
|
+
timestamp: Date.now(),
|
|
605
|
+
data: { ...message, editUpdate: true },
|
|
606
|
+
});
|
|
607
|
+
return { ok: true, message };
|
|
608
|
+
}
|
|
609
|
+
async deleteMessage(messageId, actor) {
|
|
610
|
+
const db = getDb();
|
|
611
|
+
const row = db.prepare('SELECT * FROM chat_messages WHERE id = ?').get(messageId);
|
|
612
|
+
if (!row) {
|
|
613
|
+
return { ok: false, error: 'not_found' };
|
|
614
|
+
}
|
|
615
|
+
const message = rowToMessage(row);
|
|
616
|
+
if (message.from !== actor) {
|
|
617
|
+
return { ok: false, error: 'forbidden' };
|
|
618
|
+
}
|
|
619
|
+
this.deleteMessageFromDb(messageId);
|
|
620
|
+
await this.appendAuditRecord({
|
|
621
|
+
id: messageId,
|
|
622
|
+
from: actor,
|
|
623
|
+
content: '',
|
|
624
|
+
timestamp: Date.now(),
|
|
625
|
+
channel: message.channel || 'general',
|
|
626
|
+
metadata: {
|
|
627
|
+
deleteUpdate: true,
|
|
628
|
+
deletedBy: actor,
|
|
629
|
+
},
|
|
630
|
+
});
|
|
631
|
+
eventBus.emit({
|
|
632
|
+
id: `evt-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
633
|
+
type: 'message_posted',
|
|
634
|
+
timestamp: Date.now(),
|
|
635
|
+
data: { id: messageId, deleteUpdate: true },
|
|
636
|
+
});
|
|
637
|
+
return { ok: true };
|
|
638
|
+
}
|
|
639
|
+
search(query, options) {
|
|
640
|
+
const db = getDb();
|
|
641
|
+
const likePattern = `%${query}%`;
|
|
642
|
+
const limit = options?.limit ?? 50;
|
|
643
|
+
const rows = db.prepare(`
|
|
644
|
+
SELECT * FROM chat_messages
|
|
645
|
+
WHERE content LIKE ? COLLATE NOCASE
|
|
646
|
+
OR "from" LIKE ? COLLATE NOCASE
|
|
647
|
+
OR channel LIKE ? COLLATE NOCASE
|
|
648
|
+
ORDER BY timestamp DESC
|
|
649
|
+
LIMIT ?
|
|
650
|
+
`).all(likePattern, likePattern, likePattern, limit);
|
|
651
|
+
// Return in chronological order
|
|
652
|
+
return rows.map(rowToMessage).reverse();
|
|
653
|
+
}
|
|
654
|
+
getStats() {
|
|
655
|
+
const db = getDb();
|
|
656
|
+
const totalRow = db.prepare('SELECT COUNT(*) as count FROM chat_messages').get();
|
|
657
|
+
return {
|
|
658
|
+
totalMessages: totalRow?.count ?? 0,
|
|
659
|
+
rooms: this.rooms.size,
|
|
660
|
+
subscribers: this.subscribers.size,
|
|
661
|
+
initialized: this.initialized,
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
export const chatManager = new ChatManager();
|
|
666
|
+
//# sourceMappingURL=chat.js.map
|