mixdog 0.9.3 → 0.9.4
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/package.json +7 -3
- package/scripts/bench/lead-review-tasks-r3.json +20 -0
- package/scripts/bench/lead-review-tasks.json +20 -0
- package/scripts/bench/r4-mixed-tasks.json +20 -0
- package/scripts/bench/review-tasks.json +20 -0
- package/scripts/bench/round-codex.json +114 -0
- package/scripts/bench/round-mixdog-lead-r3.json +269 -0
- package/scripts/bench/round-mixdog-lead.json +269 -0
- package/scripts/bench/round-mixdog.json +126 -0
- package/scripts/bench/round-r10-bigsample.json +679 -0
- package/scripts/bench/round-r11-codexalign.json +257 -0
- package/scripts/bench/round-r4-codex.json +114 -0
- package/scripts/bench/round-r4-mixed.json +225 -0
- package/scripts/bench/round-r5-gpt-lead.json +259 -0
- package/scripts/bench/round-r6-codex.json +114 -0
- package/scripts/bench/round-r6-solo.json +257 -0
- package/scripts/bench/round-r7-full.json +254 -0
- package/scripts/bench/round-r8-fulldefault.json +255 -0
- package/scripts/bench-run.mjs +215 -29
- package/scripts/freevar-smoke.mjs +95 -0
- package/scripts/internal-comms-bench.mjs +1 -0
- package/scripts/internal-comms-smoke.mjs +10 -9
- package/scripts/mouse-probe.mjs +45 -0
- package/scripts/output-style-bench.mjs +13 -6
- package/scripts/output-style-smoke.mjs +4 -4
- package/scripts/provider-toolcall-test.mjs +7 -3
- package/scripts/recall-usecase-cases.json +18 -0
- package/scripts/recall-usecase-probe.json +6 -0
- package/scripts/session-bench.mjs +152 -6
- package/scripts/tool-smoke.mjs +23 -63
- package/scripts/tui-render-smoke.mjs +90 -0
- package/scripts/webhook-smoke.mjs +208 -0
- package/src/agents/debugger/AGENT.md +4 -1
- package/src/agents/heavy-worker/AGENT.md +6 -5
- package/src/agents/maintainer/AGENT.md +4 -0
- package/src/agents/reviewer/AGENT.md +2 -1
- package/src/agents/worker/AGENT.md +8 -4
- package/src/lib/rules-builder.cjs +4 -0
- package/src/mixdog-session-runtime.mjs +632 -2042
- package/src/output-styles/default.md +34 -9
- package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
- package/src/output-styles/minimal.md +4 -1
- package/src/output-styles/simple.md +22 -7
- package/src/rules/agent/00-common.md +2 -0
- package/src/rules/lead/lead-brief.md +12 -0
- package/src/rules/lead/lead-tool.md +0 -11
- package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +25 -0
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +100 -23
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +6 -15
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +362 -0
- package/src/runtime/agent/orchestrator/agent-trace-io.mjs +410 -0
- package/src/runtime/agent/orchestrator/agent-trace.mjs +16 -735
- package/src/runtime/agent/orchestrator/config.mjs +69 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +62 -20
- package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +209 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +489 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +81 -1281
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +32 -3
- package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +81 -0
- package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +248 -0
- package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +303 -0
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +505 -0
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +43 -1013
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +17 -3
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +86 -11
- package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +348 -0
- package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +58 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +368 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +760 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +40 -1143
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +732 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +297 -2123
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +130 -1002
- package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +227 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +436 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1105 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/compact/budget.mjs +288 -0
- package/src/runtime/agent/orchestrator/session/compact/constants.mjs +85 -0
- package/src/runtime/agent/orchestrator/session/compact/engine.mjs +749 -0
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +82 -0
- package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +315 -0
- package/src/runtime/agent/orchestrator/session/compact/summary.mjs +643 -0
- package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +326 -0
- package/src/runtime/agent/orchestrator/session/compact.mjs +40 -2282
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +14 -2
- package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -3
- package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +182 -0
- package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
- package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
- package/src/runtime/agent/orchestrator/session/loop.mjs +251 -397
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
- package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +7 -4
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +12 -0
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +406 -0
- package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +80 -0
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
- package/src/runtime/agent/orchestrator/session/manager.mjs +166 -1087
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +189 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +74 -179
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +70 -20
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +22 -2
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +32 -41
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +29 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +8 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +126 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +81 -92
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +161 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +108 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +28 -265
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +0 -6
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +57 -3
- package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +10 -122
- package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
- package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +6 -3
- package/src/runtime/agent/orchestrator/tools/patch/constants.mjs +9 -0
- package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +171 -0
- package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +471 -0
- package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +436 -0
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +342 -0
- package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +359 -0
- package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +340 -0
- package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +643 -0
- package/src/runtime/agent/orchestrator/tools/patch.mjs +36 -2959
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +0 -21
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +9 -72
- package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
- package/src/runtime/agent/orchestrator/tools/shell-state.mjs +154 -0
- package/src/runtime/channels/backends/discord-access.mjs +32 -0
- package/src/runtime/channels/backends/discord-attachments.mjs +65 -0
- package/src/runtime/channels/backends/discord-gateway.mjs +233 -0
- package/src/runtime/channels/backends/discord.mjs +12 -292
- package/src/runtime/channels/index.mjs +229 -663
- package/src/runtime/channels/lib/backend-dispatch.mjs +44 -0
- package/src/runtime/channels/lib/event-pipeline.mjs +18 -1
- package/src/runtime/channels/lib/event-queue.mjs +63 -4
- package/src/runtime/channels/lib/inbound-routing.mjs +111 -0
- package/src/runtime/channels/lib/output-forwarder.mjs +1 -1
- package/src/runtime/channels/lib/owner-heartbeat.mjs +75 -0
- package/src/runtime/channels/lib/parent-bridge.mjs +88 -0
- package/src/runtime/channels/lib/runtime-paths.mjs +14 -4
- package/src/runtime/channels/lib/session-discovery.mjs +56 -4
- package/src/runtime/channels/lib/tool-dispatch.mjs +158 -0
- package/src/runtime/channels/lib/tool-format.mjs +1 -1
- package/src/runtime/channels/lib/transcript-discovery.mjs +4 -4
- package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -3
- package/src/runtime/channels/lib/voice-transcription.mjs +179 -0
- package/src/runtime/channels/lib/webhook/deliveries.mjs +312 -0
- package/src/runtime/channels/lib/webhook/log.mjs +42 -0
- package/src/runtime/channels/lib/webhook/ngrok.mjs +181 -0
- package/src/runtime/channels/lib/webhook/signature.mjs +60 -0
- package/src/runtime/channels/lib/webhook.mjs +36 -570
- package/src/runtime/channels/tool-defs.mjs +11 -130
- package/src/runtime/memory/index.mjs +201 -1948
- package/src/runtime/memory/lib/cycle-llm-adapters.mjs +58 -0
- package/src/runtime/memory/lib/cycle-scheduler.mjs +497 -0
- package/src/runtime/memory/lib/embedding-warmup.mjs +58 -0
- package/src/runtime/memory/lib/memory-config-flags.mjs +91 -0
- package/src/runtime/memory/lib/memory-cycle.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle2-gate.mjs +515 -0
- package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +324 -0
- package/src/runtime/memory/lib/memory-cycle2-shared.mjs +18 -0
- package/src/runtime/memory/lib/memory-cycle2.mjs +24 -842
- package/src/runtime/memory/lib/memory-embed.mjs +149 -0
- package/src/runtime/memory/lib/memory-process-lock.mjs +162 -0
- package/src/runtime/memory/lib/memory-recall-store.mjs +22 -2
- package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
- package/src/runtime/memory/lib/query-handlers.mjs +780 -0
- package/src/runtime/memory/lib/recall-format.mjs +55 -0
- package/src/runtime/memory/lib/runtime-fetcher.mjs +8 -3
- package/src/runtime/memory/lib/transcript-ingest.mjs +425 -0
- package/src/runtime/memory/tool-defs.mjs +5 -13
- package/src/runtime/search/lib/http-fetch.mjs +274 -0
- package/src/runtime/search/lib/ssrf-guard.mjs +333 -0
- package/src/runtime/search/lib/web-tools.mjs +24 -602
- package/src/runtime/shared/atomic-file.mjs +26 -1
- package/src/runtime/shared/launcher-control.mjs +2 -2
- package/src/runtime/shared/tool-primitives.mjs +308 -0
- package/src/runtime/shared/tool-result-summary.mjs +515 -0
- package/src/runtime/shared/tool-surface.mjs +80 -898
- package/src/runtime/shared/transcript-writer.mjs +23 -0
- package/src/runtime/shared/update-checker.mjs +7 -4
- package/src/session-runtime/config-helpers.mjs +84 -2
- package/src/session-runtime/config-lifecycle.mjs +232 -0
- package/src/session-runtime/cwd-plugins.mjs +226 -0
- package/src/session-runtime/mcp-glue.mjs +177 -0
- package/src/session-runtime/model-recency.mjs +111 -0
- package/src/session-runtime/native-search.mjs +247 -0
- package/src/session-runtime/output-styles.mjs +11 -9
- package/src/session-runtime/prewarm.mjs +142 -0
- package/src/session-runtime/provider-models.mjs +278 -0
- package/src/session-runtime/provider-usage.mjs +120 -0
- package/src/session-runtime/quick-model-rows.mjs +170 -0
- package/src/session-runtime/quick-search-models.mjs +46 -0
- package/src/session-runtime/session-hooks.mjs +93 -0
- package/src/session-runtime/settings-api.mjs +319 -0
- package/src/session-runtime/tool-catalog.mjs +29 -29
- package/src/session-runtime/tool-defs.mjs +84 -0
- package/src/session-runtime/warmup-schedulers.mjs +201 -0
- package/src/standalone/agent-tool/helpers.mjs +237 -0
- package/src/standalone/agent-tool/notify.mjs +107 -0
- package/src/standalone/agent-tool/provider-init.mjs +143 -0
- package/src/standalone/agent-tool/render.mjs +152 -0
- package/src/standalone/agent-tool/tool-def.mjs +55 -0
- package/src/standalone/agent-tool.mjs +110 -671
- package/src/standalone/channel-worker.mjs +4 -7
- package/src/standalone/explore-tool.mjs +30 -9
- package/src/standalone/hook-bus/config.mjs +207 -0
- package/src/standalone/hook-bus/constants.mjs +90 -0
- package/src/standalone/hook-bus/handlers.mjs +481 -0
- package/src/standalone/hook-bus/payload.mjs +31 -0
- package/src/standalone/hook-bus/rules.mjs +77 -0
- package/src/standalone/hook-bus.mjs +77 -870
- package/src/standalone/memory-runtime-proxy.mjs +7 -0
- package/src/standalone/opencode-go-login.mjs +5 -1
- package/src/standalone/provider-admin.mjs +1 -16
- package/src/standalone/usage-dashboard.mjs +3 -1
- package/src/tui/App.jsx +945 -8094
- package/src/tui/app/app-format.mjs +206 -0
- package/src/tui/app/channel-pickers.mjs +510 -0
- package/src/tui/app/clipboard.mjs +67 -0
- package/src/tui/app/core-memory-picker.mjs +210 -0
- package/src/tui/app/extension-pickers.mjs +506 -0
- package/src/tui/app/input-parsers.mjs +193 -0
- package/src/tui/app/maintenance-pickers.mjs +324 -0
- package/src/tui/app/model-options.mjs +330 -0
- package/src/tui/app/model-picker.mjs +365 -0
- package/src/tui/app/onboarding-steps.mjs +400 -0
- package/src/tui/app/project-picker.mjs +247 -0
- package/src/tui/app/provider-setup-picker.mjs +580 -0
- package/src/tui/app/resume-picker.mjs +55 -0
- package/src/tui/app/route-pickers.mjs +419 -0
- package/src/tui/app/settings-picker.mjs +490 -0
- package/src/tui/app/slash-commands.mjs +101 -0
- package/src/tui/app/slash-dispatch.mjs +427 -0
- package/src/tui/app/text-layout.mjs +46 -0
- package/src/tui/app/theme-effort-pickers.mjs +154 -0
- package/src/tui/app/transcript-window.mjs +671 -0
- package/src/tui/app/use-mouse-input.mjs +460 -0
- package/src/tui/app/use-prompt-handlers.mjs +310 -0
- package/src/tui/app/use-transcript-scroll.mjs +510 -0
- package/src/tui/app/use-transcript-window.mjs +589 -0
- package/src/tui/components/ConfirmBar.jsx +1 -1
- package/src/tui/components/Picker.jsx +32 -4
- package/src/tui/components/PromptInput.jsx +23 -101
- package/src/tui/components/SlashCommandPalette.jsx +8 -1
- package/src/tui/components/StatusLine.jsx +63 -12
- package/src/tui/components/TextEntryPanel.jsx +11 -0
- package/src/tui/components/ToolExecution.jsx +52 -594
- package/src/tui/components/TranscriptItem.jsx +105 -0
- package/src/tui/components/UsagePanel.jsx +18 -4
- package/src/tui/components/prompt-input/edit-helpers.mjs +72 -0
- package/src/tui/components/prompt-input/voice-indicator.mjs +39 -0
- package/src/tui/components/tool-execution/ResultBody.jsx +56 -0
- package/src/tui/components/tool-execution/surface-detail.mjs +405 -0
- package/src/tui/components/tool-execution/text-format.mjs +161 -0
- package/src/tui/display-width.mjs +20 -3
- package/src/tui/dist/index.mjs +19652 -18630
- package/src/tui/engine/agent-job-feed.mjs +133 -0
- package/src/tui/engine/notification-plan.mjs +76 -0
- package/src/tui/engine/render-timing.mjs +17 -0
- package/src/tui/engine/tool-approval.mjs +94 -0
- package/src/tui/engine/tool-card-results.mjs +234 -0
- package/src/tui/engine/tool-result-status.mjs +135 -0
- package/src/tui/engine.mjs +122 -562
- package/src/tui/figures.mjs +5 -0
- package/src/tui/index.jsx +105 -0
- package/src/tui/input-editing.mjs +2 -2
- package/src/tui/markdown/format-token.mjs +4 -1
- package/src/tui/statusline-ansi-bridge.mjs +11 -3
- package/src/tui/theme.mjs +6 -0
- package/src/ui/statusline-agents.mjs +213 -0
- package/src/ui/statusline-format.mjs +146 -0
- package/src/ui/statusline-segments.mjs +148 -0
- package/src/ui/statusline.mjs +67 -501
- package/src/ui/tool-card.mjs +0 -1
- package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
- package/src/workflows/default/WORKFLOW.md +1 -1
- package/src/workflows/sequential/WORKFLOW.md +1 -1
- package/vendor/ink/build/display-width.js +19 -3
- package/vendor/ink/build/ink.js +103 -6
- package/vendor/ink/build/log-update.js +17 -3
- package/vendor/ink/build/wrap-text.js +125 -0
- package/scripts/_test-folder-dialog.mjs +0 -30
- package/scripts/fix-brief-fn.mjs +0 -35
- package/scripts/fix-format-tool-surface.mjs +0 -24
- package/scripts/fix-tool-exec-visible.mjs +0 -42
- package/scripts/patch-agent-brief.mjs +0 -48
- package/scripts/patch-app.mjs +0 -21
- package/scripts/patch-app2.mjs +0 -18
- package/scripts/patch-dist-brief.mjs +0 -96
- package/scripts/patch-tool-exec.mjs +0 -70
- package/src/examples/schedules/SCHEDULE.example.md +0 -32
- package/src/examples/webhooks/WEBHOOK.example.md +0 -40
- package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +0 -107
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +0 -143
- package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -285
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +0 -162
- package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
- package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
- package/src/runtime/shared/task-notification-envelope.test.mjs +0 -107
- package/src/runtime/shared/tool-execution-contract.test.mjs +0 -183
- package/src/standalone/agent-task-status.test.mjs +0 -76
- package/src/tui/components/tool-output-format.test.mjs +0 -399
- package/src/tui/display-width.test.mjs +0 -35
- package/src/tui/engine-runtime-notification.test.mjs +0 -115
- package/src/tui/engine-tool-result-text.test.mjs +0 -75
- package/src/tui/input-editing.selection.test.mjs +0 -75
- package/src/tui/markdown/format-token.test.mjs +0 -354
- package/src/tui/markdown/render-ansi.test.mjs +0 -108
- package/src/tui/markdown/stream-fence.test.mjs +0 -26
- package/src/tui/markdown/streaming-markdown.test.mjs +0 -70
- package/src/tui/paste-fix.test.mjs +0 -119
- package/src/tui/prompt-history-store.test.mjs +0 -52
- package/src/tui/statusline-ansi-bridge.test.mjs +0 -159
- package/src/tui/transcript-tool-failures.test.mjs +0 -111
- package/src/ui/markdown.test.mjs +0 -70
- package/src/ui/statusline-context-label.test.mjs +0 -15
- package/src/vendor/statusline/bin/statusline-lib.mjs +0 -186
- package/src/vendor/statusline/bin/statusline-route.test.mjs +0 -80
|
@@ -73,6 +73,23 @@ export function parsePeriod(period, hasQuery) {
|
|
|
73
73
|
endMs: Date.parse(rangeMatch[2] + 'T23:59:59.999'),
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
|
+
// Time-of-day windows: 'HH:MM~HH:MM' (today) or 'YYYY-MM-DD HH:MM~HH:MM'
|
|
77
|
+
// (specific day). Covers "this afternoon 12:00-14:00" style recall that the
|
|
78
|
+
// day-granular date/range forms cannot express. End is inclusive to the
|
|
79
|
+
// minute (:59.999). An end at or before start returns null (invalid window)
|
|
80
|
+
// rather than guessing an overnight wrap.
|
|
81
|
+
const todMatch = period.match(/^(?:(\d{4}-\d{2}-\d{2})[ T])?(\d{1,2}):(\d{2})~(\d{1,2}):(\d{2})$/)
|
|
82
|
+
if (todMatch) {
|
|
83
|
+
const [, day, h1, m1, h2, m2] = todMatch
|
|
84
|
+
const base = day ? new Date(day + 'T00:00:00') : new Date()
|
|
85
|
+
if (Number.isNaN(base.getTime())) return null
|
|
86
|
+
const sh = Number(h1), sm = Number(m1), eh = Number(h2), em = Number(m2)
|
|
87
|
+
if (sh > 23 || eh > 23 || sm > 59 || em > 59) return null
|
|
88
|
+
const start = new Date(base); start.setHours(sh, sm, 0, 0)
|
|
89
|
+
const end = new Date(base); end.setHours(eh, em, 59, 999)
|
|
90
|
+
if (end.getTime() <= start.getTime()) return null
|
|
91
|
+
return { startMs: start.getTime(), endMs: end.getTime(), exact: true }
|
|
92
|
+
}
|
|
76
93
|
const dateMatch = period.match(/^(\d{4}-\d{2}-\d{2})$/)
|
|
77
94
|
if (dateMatch) {
|
|
78
95
|
return {
|
|
@@ -181,3 +198,41 @@ export function renderEntryLines(rows) {
|
|
|
181
198
|
if (_capped) lines.push(`[recall truncated — showing first ${RECALL_LINE_CAP} lines; narrow the query (limit/period/projectScope) for the rest]`)
|
|
182
199
|
return lines.join('\n')
|
|
183
200
|
}
|
|
201
|
+
|
|
202
|
+
// Compact session label for group headers: keep short ids verbatim, shorten
|
|
203
|
+
// long ones to a recognizable tail (ids are typically unique in the suffix —
|
|
204
|
+
// timestamp/counter — not the prefix).
|
|
205
|
+
function shortSessionLabel(sid) {
|
|
206
|
+
const s = String(sid || '').trim()
|
|
207
|
+
if (s.length <= 20) return s
|
|
208
|
+
return `…${s.slice(-16)}`
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Session-grouped rendering for the GLOBAL query-less browse ("what did we
|
|
212
|
+
// work on recently") when the window spans multiple sessions. A flat ts-desc
|
|
213
|
+
// list interleaves sessions into one indistinguishable stream and lets one
|
|
214
|
+
// chatty session crowd the page; grouping keeps each work-unit readable.
|
|
215
|
+
// Group order = newest activity first (rows arrive ts-desc, first-seen wins).
|
|
216
|
+
// The caller's own session (currentSessionId hint) is marked "(current)".
|
|
217
|
+
// Single-session (or session-less) result sets fall through to the flat
|
|
218
|
+
// renderer — no headers when grouping adds nothing.
|
|
219
|
+
export function renderSessionGroupedLines(rows, { currentSessionId } = {}) {
|
|
220
|
+
if (!rows || rows.length === 0) return '(no results)'
|
|
221
|
+
const groups = new Map()
|
|
222
|
+
for (const r of rows) {
|
|
223
|
+
const sid = String(r?.session_id || '').trim()
|
|
224
|
+
const key = sid || '(no session)'
|
|
225
|
+
if (!groups.has(key)) groups.set(key, [])
|
|
226
|
+
groups.get(key).push(r)
|
|
227
|
+
}
|
|
228
|
+
if (groups.size <= 1) return renderEntryLines(rows)
|
|
229
|
+
const current = String(currentSessionId || '').trim()
|
|
230
|
+
const parts = []
|
|
231
|
+
for (const [sid, groupRows] of groups) {
|
|
232
|
+
const mark = current && sid === current ? ' (current)' : ''
|
|
233
|
+
const label = sid === '(no session)' ? sid : `session ${shortSessionLabel(sid)}`
|
|
234
|
+
parts.push(`## ${label}${mark}`)
|
|
235
|
+
parts.push(renderEntryLines(groupRows))
|
|
236
|
+
}
|
|
237
|
+
return parts.join('\n')
|
|
238
|
+
}
|
|
@@ -75,13 +75,18 @@ function isUsableAsset(asset) {
|
|
|
75
75
|
// ---------------------------------------------------------------------------
|
|
76
76
|
|
|
77
77
|
async function loadManifest(dataDir) {
|
|
78
|
+
// Bundled manifest FIRST: it always matches the installed mixdog version.
|
|
79
|
+
// A cached <dataDir>/runtime/manifest.json from an older install must never
|
|
80
|
+
// shadow it — stale caches caused sha256 mismatches on already-installed
|
|
81
|
+
// machines after an upgrade. The cache is only a fallback for the rare case
|
|
82
|
+
// where the bundled file is missing (e.g. a stripped checkout).
|
|
83
|
+
if (existsSync(BUNDLED_MANIFEST_PATH)) {
|
|
84
|
+
return JSON.parse(readFileSync(BUNDLED_MANIFEST_PATH, 'utf8'))
|
|
85
|
+
}
|
|
78
86
|
const runtimeManifestPath = join(dataDir, 'runtime', 'manifest.json')
|
|
79
87
|
if (existsSync(runtimeManifestPath)) {
|
|
80
88
|
try { return JSON.parse(readFileSync(runtimeManifestPath, 'utf8')) } catch {}
|
|
81
89
|
}
|
|
82
|
-
if (existsSync(BUNDLED_MANIFEST_PATH)) {
|
|
83
|
-
return JSON.parse(readFileSync(BUNDLED_MANIFEST_PATH, 'utf8'))
|
|
84
|
-
}
|
|
85
90
|
const res = await fetch(MANIFEST_URL, { signal: AbortSignal.timeout(30_000) })
|
|
86
91
|
if (!res.ok) throw new Error(`[runtime-fetcher] manifest fetch failed: ${res.status} ${res.statusText}`)
|
|
87
92
|
const manifest = await res.json()
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
// Transcript ingest cluster, extracted from index.mjs (pass 2).
|
|
2
|
+
//
|
|
3
|
+
// Owns: transcript offset load/persist, JSONL tail ingestion, cwd extraction,
|
|
4
|
+
// timestamp coercion, and the cross-platform transcript watcher. Everything
|
|
5
|
+
// that touched module-level state in index.mjs (db handle, mainConfig-derived
|
|
6
|
+
// data dir, the _transcriptOffsets / _ingestTranscriptTails maps, the persist
|
|
7
|
+
// tail promise) is now closed over inside createTranscriptIngest via injected
|
|
8
|
+
// accessors, so index.mjs keeps ownership of the live db/config lifecycle and
|
|
9
|
+
// this module stays a pure factory with no import-time side effects.
|
|
10
|
+
import fs from 'node:fs'
|
|
11
|
+
import path from 'node:path'
|
|
12
|
+
|
|
13
|
+
// Pure: coerce a transcript timestamp (seconds, ms, or ISO string) to ms.
|
|
14
|
+
export function parseTsToMs(value) {
|
|
15
|
+
if (typeof value === 'number' && Number.isFinite(value)) return value < 1e12 ? value * 1000 : value
|
|
16
|
+
const parsed = Date.parse(String(value))
|
|
17
|
+
return Number.isFinite(parsed) ? parsed : Date.now()
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Pure: extract cwd from the transcript file's JSONL rows. Mixdog embeds the
|
|
21
|
+
// session cwd as a top-level `cwd` field on every message row, so scanning the
|
|
22
|
+
// first few lines is reliable on all platforms without slug-decoding ambiguity.
|
|
23
|
+
// Returns undefined when no cwd is found or the extracted path does not exist
|
|
24
|
+
// on disk (falls back to COMMON).
|
|
25
|
+
export function cwdFromTranscriptPath(fp) {
|
|
26
|
+
let fd
|
|
27
|
+
try {
|
|
28
|
+
fd = fs.openSync(fp, 'r')
|
|
29
|
+
const buf = Buffer.alloc(Math.min(fs.fstatSync(fd).size, 100 * 1024))
|
|
30
|
+
fs.readSync(fd, buf, 0, buf.length, 0)
|
|
31
|
+
fs.closeSync(fd)
|
|
32
|
+
fd = undefined
|
|
33
|
+
const lines = buf.toString('utf8').split('\n')
|
|
34
|
+
for (let i = 0; i < Math.min(lines.length, 5); i++) {
|
|
35
|
+
const line = lines[i].trim()
|
|
36
|
+
if (!line) continue
|
|
37
|
+
try {
|
|
38
|
+
const obj = JSON.parse(line)
|
|
39
|
+
if (typeof obj.cwd === 'string' && obj.cwd) {
|
|
40
|
+
const candidate = obj.cwd
|
|
41
|
+
try { if (fs.statSync(candidate).isDirectory()) return candidate } catch {}
|
|
42
|
+
}
|
|
43
|
+
} catch {}
|
|
44
|
+
}
|
|
45
|
+
} catch {} finally {
|
|
46
|
+
if (fd != null) { try { fs.closeSync(fd) } catch {} }
|
|
47
|
+
}
|
|
48
|
+
return undefined
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Factory. All live-state coupling is injected:
|
|
52
|
+
// getDb() -> current pg-shim db handle (or null pre-init)
|
|
53
|
+
// loadMeta() -> Promise<string> raw transcript-offsets meta value
|
|
54
|
+
// persistMeta(json) -> Promise<void> writes serialized offsets to meta
|
|
55
|
+
// projectsRoot() -> string, mixdogHome()/projects
|
|
56
|
+
// resolveProjectId(cwd) -> project id | null
|
|
57
|
+
// firstTextContent / cleanMemoryText -> shared ingest text helpers
|
|
58
|
+
// log(msg) -> stderr logger
|
|
59
|
+
//
|
|
60
|
+
// Returns the same surface index.mjs previously exposed as module functions,
|
|
61
|
+
// plus resetOffsets() for stop()'s teardown.
|
|
62
|
+
export function createTranscriptIngest({
|
|
63
|
+
getDb,
|
|
64
|
+
loadMeta,
|
|
65
|
+
persistMeta,
|
|
66
|
+
projectsRoot,
|
|
67
|
+
resolveProjectId,
|
|
68
|
+
firstTextContent,
|
|
69
|
+
cleanMemoryText,
|
|
70
|
+
log = () => {},
|
|
71
|
+
}) {
|
|
72
|
+
let _transcriptOffsets = new Map()
|
|
73
|
+
/** @type {Map<string, Promise<unknown>>} */
|
|
74
|
+
const _ingestTranscriptTails = new Map()
|
|
75
|
+
let _transcriptOffsetsPersistTail = Promise.resolve()
|
|
76
|
+
|
|
77
|
+
async function loadTranscriptOffsets() {
|
|
78
|
+
try {
|
|
79
|
+
const raw = await loadMeta()
|
|
80
|
+
const obj = JSON.parse(raw)
|
|
81
|
+
_transcriptOffsets = new Map(Object.entries(obj))
|
|
82
|
+
} catch {
|
|
83
|
+
_transcriptOffsets = new Map()
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function persistTranscriptOffsets() {
|
|
88
|
+
const run = _transcriptOffsetsPersistTail.catch(() => {}).then(async () => {
|
|
89
|
+
try {
|
|
90
|
+
const obj = Object.fromEntries(_transcriptOffsets)
|
|
91
|
+
await persistMeta(JSON.stringify(obj))
|
|
92
|
+
} catch (e) {
|
|
93
|
+
log(`[memory] persist transcript offsets failed: ${e.message}\n`)
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
_transcriptOffsetsPersistTail = run.catch(() => {})
|
|
97
|
+
return run
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function runTranscriptIngestSerialized(transcriptPath, fn) {
|
|
101
|
+
const key = path.resolve(transcriptPath)
|
|
102
|
+
const prev = _ingestTranscriptTails.get(key) ?? Promise.resolve()
|
|
103
|
+
const run = prev.catch(() => {}).then(fn)
|
|
104
|
+
_ingestTranscriptTails.set(key, run.catch(() => {}))
|
|
105
|
+
return run
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function snapshotTranscriptOffset(transcriptPath) {
|
|
109
|
+
const stored = _transcriptOffsets.get(transcriptPath)
|
|
110
|
+
if (!stored) return { bytes: 0, lineIndex: 0 }
|
|
111
|
+
return { bytes: Number(stored.bytes) || 0, lineIndex: Number(stored.lineIndex) || 0 }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function ingestTranscriptFileImpl(transcriptPath, { cwd } = {}) {
|
|
115
|
+
const db = getDb()
|
|
116
|
+
let stat
|
|
117
|
+
try { stat = await fs.promises.stat(transcriptPath) } catch { return 0 }
|
|
118
|
+
const sessionUuid = path.basename(transcriptPath, '.jsonl')
|
|
119
|
+
const prev = snapshotTranscriptOffset(transcriptPath)
|
|
120
|
+
if (stat.size < prev.bytes) {
|
|
121
|
+
prev.bytes = 0
|
|
122
|
+
prev.lineIndex = 0
|
|
123
|
+
}
|
|
124
|
+
if (stat.size <= prev.bytes) return 0
|
|
125
|
+
|
|
126
|
+
const fh = await fs.promises.open(transcriptPath, 'r')
|
|
127
|
+
const buf = Buffer.alloc(stat.size - prev.bytes)
|
|
128
|
+
try {
|
|
129
|
+
await fh.read(buf, 0, buf.length, prev.bytes)
|
|
130
|
+
} finally {
|
|
131
|
+
await fh.close()
|
|
132
|
+
}
|
|
133
|
+
const text = buf.toString('utf8')
|
|
134
|
+
|
|
135
|
+
const resolvedCwd = typeof cwd === 'string' && cwd ? cwd : cwdFromTranscriptPath(transcriptPath)
|
|
136
|
+
// No cwd resolved -> classify as COMMON (project_id NULL). Falling back to
|
|
137
|
+
// process.cwd() would misclassify rows under the service/plugin cwd.
|
|
138
|
+
const projectId = resolvedCwd ? resolveProjectId(resolvedCwd) : null
|
|
139
|
+
|
|
140
|
+
let count = 0
|
|
141
|
+
let index = prev.lineIndex
|
|
142
|
+
// Track the byte boundary of the LAST line we fully consumed (parsed +
|
|
143
|
+
// either inserted or intentionally skipped). On parse failure or
|
|
144
|
+
// transient insert error we stop and leave the boundary untouched so the
|
|
145
|
+
// next sweep retries from the same position. This prevents malformed
|
|
146
|
+
// trailing JSONL (mid-write partial lines) and DB hiccups from being
|
|
147
|
+
// silently consumed forever.
|
|
148
|
+
let lastGoodBytes = prev.bytes
|
|
149
|
+
let lastGoodLineIndex = prev.lineIndex
|
|
150
|
+
let cursor = 0
|
|
151
|
+
while (cursor < text.length) {
|
|
152
|
+
const nl = text.indexOf('\n', cursor)
|
|
153
|
+
// No trailing newline -> partial line still being written; stop here
|
|
154
|
+
// without advancing so the rest is re-read once the writer flushes.
|
|
155
|
+
if (nl === -1) break
|
|
156
|
+
const rawLine = text.slice(cursor, nl)
|
|
157
|
+
const consumedBytes = Buffer.byteLength(rawLine, 'utf8') + 1
|
|
158
|
+
cursor = nl + 1
|
|
159
|
+
const line = rawLine.replace(/\r$/, '')
|
|
160
|
+
if (!line) {
|
|
161
|
+
lastGoodBytes += consumedBytes
|
|
162
|
+
continue
|
|
163
|
+
}
|
|
164
|
+
index += 1
|
|
165
|
+
let parsed
|
|
166
|
+
try { parsed = JSON.parse(line) } catch {
|
|
167
|
+
// Malformed line: do not advance past it; retry on next sweep.
|
|
168
|
+
index -= 1
|
|
169
|
+
break
|
|
170
|
+
}
|
|
171
|
+
// Transcript lines carry the role either as message.role (legacy) or
|
|
172
|
+
// as the top-level `type` field ({"type":"assistant","message":{...}}
|
|
173
|
+
// — the current session-runtime writer). Reading only message.role
|
|
174
|
+
// silently skipped EVERY line of current-format transcripts, so the
|
|
175
|
+
// background watcher ingested 0 rows forever.
|
|
176
|
+
const role = parsed.message?.role
|
|
177
|
+
?? ((parsed.type === 'user' || parsed.type === 'assistant') ? parsed.type : undefined)
|
|
178
|
+
if (role !== 'user' && role !== 'assistant') {
|
|
179
|
+
lastGoodBytes += consumedBytes
|
|
180
|
+
lastGoodLineIndex = index
|
|
181
|
+
continue
|
|
182
|
+
}
|
|
183
|
+
const content = firstTextContent(parsed.message?.content)
|
|
184
|
+
if (!content || !content.trim()) {
|
|
185
|
+
lastGoodBytes += consumedBytes
|
|
186
|
+
lastGoodLineIndex = index
|
|
187
|
+
continue
|
|
188
|
+
}
|
|
189
|
+
const cleaned = cleanMemoryText(content)
|
|
190
|
+
if (!cleaned) {
|
|
191
|
+
lastGoodBytes += consumedBytes
|
|
192
|
+
lastGoodLineIndex = index
|
|
193
|
+
continue
|
|
194
|
+
}
|
|
195
|
+
const tsMs = parseTsToMs(parsed.timestamp ?? parsed.ts ?? Date.now())
|
|
196
|
+
const sourceRef = `transcript:${sessionUuid}#${index}`
|
|
197
|
+
try {
|
|
198
|
+
const result = await db.query(
|
|
199
|
+
`INSERT INTO entries(ts, role, content, source_ref, session_id, source_turn, project_id)
|
|
200
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
201
|
+
ON CONFLICT DO NOTHING`,
|
|
202
|
+
[tsMs, role, cleaned, sourceRef, sessionUuid, index, projectId]
|
|
203
|
+
)
|
|
204
|
+
if (Number(result.rowCount ?? result.affectedRows ?? 0) > 0) count += 1
|
|
205
|
+
lastGoodBytes += consumedBytes
|
|
206
|
+
lastGoodLineIndex = index
|
|
207
|
+
} catch (e) {
|
|
208
|
+
log(`[transcript-watch] insert error (${sourceRef}): ${e.message}\n`)
|
|
209
|
+
// Transient insert failure: leave the boundary before this line so
|
|
210
|
+
// the next sweep retries it. Roll back the line counter too.
|
|
211
|
+
index -= 1
|
|
212
|
+
break
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
_transcriptOffsets.set(transcriptPath, {
|
|
216
|
+
bytes: lastGoodBytes,
|
|
217
|
+
lineIndex: lastGoodLineIndex,
|
|
218
|
+
})
|
|
219
|
+
await persistTranscriptOffsets()
|
|
220
|
+
return count
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async function ingestTranscriptFile(transcriptPath, options = {}) {
|
|
224
|
+
return runTranscriptIngestSerialized(transcriptPath, () => ingestTranscriptFileImpl(transcriptPath, options))
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function getOffset(fp) {
|
|
228
|
+
return _transcriptOffsets.get(fp)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function resetOffsets() {
|
|
232
|
+
_transcriptOffsets = new Map()
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function initTranscriptWatcher() {
|
|
236
|
+
const root = projectsRoot()
|
|
237
|
+
const SAFETY_POLL_MS = 5 * 60_000
|
|
238
|
+
const DEBOUNCE_MS = 500
|
|
239
|
+
const watchedFiles = new Map()
|
|
240
|
+
const pendingByFile = new Map()
|
|
241
|
+
const watchers = []
|
|
242
|
+
const intervals = []
|
|
243
|
+
const polledFiles = new Set()
|
|
244
|
+
let safetySweepTimeout = null
|
|
245
|
+
|
|
246
|
+
function isWatchable(relOrBase) {
|
|
247
|
+
const base = path.basename(relOrBase)
|
|
248
|
+
if (!base.endsWith('.jsonl') || base.startsWith('agent-')) return false
|
|
249
|
+
if (relOrBase.includes('tmp') || relOrBase.includes('cache') || relOrBase.includes('plugins')) return false
|
|
250
|
+
return true
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async function ingestOne(fp) {
|
|
254
|
+
try {
|
|
255
|
+
if (!fs.existsSync(fp)) return
|
|
256
|
+
const stat = fs.statSync(fp)
|
|
257
|
+
const mtime = stat.mtimeMs
|
|
258
|
+
const prev = watchedFiles.get(fp)
|
|
259
|
+
if (prev && prev >= mtime) return
|
|
260
|
+
const n = await ingestTranscriptFile(fp, { cwd: cwdFromTranscriptPath(fp) })
|
|
261
|
+
// Only mark this mtime as 'consumed' once the persisted offset has
|
|
262
|
+
// fully advanced past the observed file size. On a transient insert
|
|
263
|
+
// error (or a malformed trailing line) ingestTranscriptFile leaves
|
|
264
|
+
// the persisted offset before the failed line for retry; caching
|
|
265
|
+
// the new mtime unconditionally would suppress the next sweep until
|
|
266
|
+
// the file mutated again, losing the retry. Leave the cache
|
|
267
|
+
// untouched on partial advance so the next sweep re-ingests.
|
|
268
|
+
const off = _transcriptOffsets.get(fp)
|
|
269
|
+
if (off && off.bytes >= stat.size) {
|
|
270
|
+
watchedFiles.set(fp, mtime)
|
|
271
|
+
}
|
|
272
|
+
if (n > 0) {
|
|
273
|
+
log(`[transcript-watch] ingested ${n} entries from ${path.basename(fp)}\n`)
|
|
274
|
+
}
|
|
275
|
+
} catch (e) {
|
|
276
|
+
log(`[transcript-watch] ingest error: ${e.message}\n`)
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function scheduleIngest(fp) {
|
|
281
|
+
const existing = pendingByFile.get(fp)
|
|
282
|
+
if (existing) clearTimeout(existing)
|
|
283
|
+
const timer = setTimeout(() => {
|
|
284
|
+
pendingByFile.delete(fp)
|
|
285
|
+
ingestOne(fp)
|
|
286
|
+
}, DEBOUNCE_MS)
|
|
287
|
+
pendingByFile.set(fp, timer)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
async function discoverActiveTranscripts() {
|
|
291
|
+
let topLevel
|
|
292
|
+
try { topLevel = await fs.promises.readdir(root) }
|
|
293
|
+
catch { return [] }
|
|
294
|
+
const files = []
|
|
295
|
+
for (const d of topLevel) {
|
|
296
|
+
if (d.includes('tmp') || d.includes('cache') || d.includes('plugins')) continue
|
|
297
|
+
const full = path.join(root, d)
|
|
298
|
+
let inner
|
|
299
|
+
try { inner = await fs.promises.readdir(full) } catch { continue }
|
|
300
|
+
for (const f of inner) {
|
|
301
|
+
if (!f.endsWith('.jsonl') || f.startsWith('agent-')) continue
|
|
302
|
+
const fp = path.join(full, f)
|
|
303
|
+
try {
|
|
304
|
+
const stat = await fs.promises.stat(fp)
|
|
305
|
+
files.push({ path: fp, mtime: stat.mtimeMs })
|
|
306
|
+
} catch {}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
const cutoff = Date.now() - 30 * 60_000
|
|
310
|
+
return files.filter(f => f.mtime > cutoff)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
async function safetySweep() {
|
|
314
|
+
try {
|
|
315
|
+
const active = await discoverActiveTranscripts()
|
|
316
|
+
for (const { path: fp } of active) ingestOne(fp)
|
|
317
|
+
} catch (e) {
|
|
318
|
+
log(`[transcript-watch] safety sweep error: ${e.message}\n`)
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
safetySweepTimeout = setTimeout(safetySweep, 3_000)
|
|
323
|
+
|
|
324
|
+
// fs.watch({recursive}) is only reliable on win32.
|
|
325
|
+
// darwin: recursive option unreliable — use flat watch per-entry (glob dirs at start).
|
|
326
|
+
// linux/WSL: recursive not supported — use fs.watchFile polling per file found via
|
|
327
|
+
// the safety sweep, or fall back entirely to safety sweep.
|
|
328
|
+
if (process.platform === 'win32') {
|
|
329
|
+
try {
|
|
330
|
+
const watcher = fs.watch(root, { recursive: true, persistent: true }, (_event, filename) => {
|
|
331
|
+
if (!filename) return
|
|
332
|
+
if (!isWatchable(filename)) return
|
|
333
|
+
const fp = path.join(root, filename)
|
|
334
|
+
scheduleIngest(fp)
|
|
335
|
+
})
|
|
336
|
+
watcher.on('error', (err) => {
|
|
337
|
+
log(`[transcript-watch] fs.watch error: ${err.message}\n`)
|
|
338
|
+
})
|
|
339
|
+
watchers.push(watcher)
|
|
340
|
+
log(`[transcript-watch] fs.watch(recursive) active on ${root}\n`)
|
|
341
|
+
} catch (e) {
|
|
342
|
+
log(`[transcript-watch] fs.watch setup failed: ${e.message} — relying on safety sweep only\n`)
|
|
343
|
+
}
|
|
344
|
+
intervals.push(setInterval(safetySweep, SAFETY_POLL_MS))
|
|
345
|
+
} else if (process.platform === 'darwin') {
|
|
346
|
+
// Flat watch: register a non-recursive watcher on each immediate subdirectory.
|
|
347
|
+
// New subdirs are picked up on the next safety sweep cycle.
|
|
348
|
+
try {
|
|
349
|
+
const registerFlat = (dir) => {
|
|
350
|
+
try {
|
|
351
|
+
const w = fs.watch(dir, { persistent: true }, (_event, filename) => {
|
|
352
|
+
if (!filename) return
|
|
353
|
+
const fp = path.join(dir, filename)
|
|
354
|
+
if (!isWatchable(fp)) return
|
|
355
|
+
scheduleIngest(fp)
|
|
356
|
+
})
|
|
357
|
+
w.on('error', () => { /* ignore individual dir errors */ })
|
|
358
|
+
watchers.push(w)
|
|
359
|
+
} catch { /* dir may not exist yet */ }
|
|
360
|
+
}
|
|
361
|
+
registerFlat(root)
|
|
362
|
+
try {
|
|
363
|
+
for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
|
|
364
|
+
if (entry.isDirectory()) registerFlat(path.join(root, entry.name))
|
|
365
|
+
}
|
|
366
|
+
} catch { /* best effort */ }
|
|
367
|
+
log(`[transcript-watch] flat fs.watch active on ${root} (darwin)\n`)
|
|
368
|
+
} catch (e) {
|
|
369
|
+
log(`[transcript-watch] flat watch setup failed: ${e.message} — relying on safety sweep only\n`)
|
|
370
|
+
}
|
|
371
|
+
intervals.push(setInterval(safetySweep, SAFETY_POLL_MS))
|
|
372
|
+
} else {
|
|
373
|
+
// linux/WSL: fs.watch recursive is unsupported. Use fs.watchFile polling for
|
|
374
|
+
// individual files surfaced by the safety sweep, in addition to the sweep itself.
|
|
375
|
+
log(`[transcript-watch] linux/WSL — using safety sweep + fs.watchFile polling (no recursive watch)\n`)
|
|
376
|
+
// Wrap by reassigning the closure-captured reference is not possible here;
|
|
377
|
+
// instead, register watchFile inside the safety sweep callback by intercepting
|
|
378
|
+
// active file list after each sweep. The interval already calls safetySweep
|
|
379
|
+
// which calls ingestOne; watchFile additions happen as a side-effect of the sweep.
|
|
380
|
+
const _patchedSweep = async () => {
|
|
381
|
+
try {
|
|
382
|
+
const active = await discoverActiveTranscripts()
|
|
383
|
+
for (const { path: fp } of active) {
|
|
384
|
+
if (!polledFiles.has(fp)) {
|
|
385
|
+
polledFiles.add(fp)
|
|
386
|
+
fs.watchFile(fp, { persistent: false, interval: 2000 }, () => {
|
|
387
|
+
if (isWatchable(fp)) scheduleIngest(fp)
|
|
388
|
+
})
|
|
389
|
+
}
|
|
390
|
+
ingestOne(fp)
|
|
391
|
+
}
|
|
392
|
+
} catch (e) {
|
|
393
|
+
log(`[transcript-watch] linux sweep error: ${e.message}\n`)
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
// Replace the safety sweep interval with the patched version.
|
|
397
|
+
intervals.push(setInterval(_patchedSweep, SAFETY_POLL_MS))
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return {
|
|
401
|
+
stop() {
|
|
402
|
+
if (safetySweepTimeout) { clearTimeout(safetySweepTimeout); safetySweepTimeout = null }
|
|
403
|
+
for (const t of pendingByFile.values()) { try { clearTimeout(t) } catch {} }
|
|
404
|
+
pendingByFile.clear()
|
|
405
|
+
for (const i of intervals) { try { clearInterval(i) } catch {} }
|
|
406
|
+
intervals.length = 0
|
|
407
|
+
for (const w of watchers) { try { w.close() } catch {} }
|
|
408
|
+
watchers.length = 0
|
|
409
|
+
for (const fp of polledFiles) { try { fs.unwatchFile(fp) } catch {} }
|
|
410
|
+
polledFiles.clear()
|
|
411
|
+
},
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return {
|
|
416
|
+
loadTranscriptOffsets,
|
|
417
|
+
persistTranscriptOffsets,
|
|
418
|
+
ingestTranscriptFile,
|
|
419
|
+
cwdFromTranscriptPath,
|
|
420
|
+
parseTsToMs,
|
|
421
|
+
initTranscriptWatcher,
|
|
422
|
+
getOffset,
|
|
423
|
+
resetOffsets,
|
|
424
|
+
}
|
|
425
|
+
}
|
|
@@ -8,27 +8,19 @@ export const TOOL_DEFS = [
|
|
|
8
8
|
name: 'memory',
|
|
9
9
|
title: 'Memory Cycle',
|
|
10
10
|
annotations: { title: 'Memory Cycle', readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
11
|
-
description: '
|
|
11
|
+
description: 'Core-memory mutation and status. Use recall for retrieval. When the user explicitly asks to remember something (e.g. "remember this", a stated preference, or a decision), persist it immediately with action:"core" op:"add" — do not wait for a cycle.',
|
|
12
12
|
inputSchema: {
|
|
13
13
|
type: 'object',
|
|
14
14
|
properties: {
|
|
15
|
-
action: { type: 'string', enum: ['core','
|
|
15
|
+
action: { type: 'string', enum: ['core','status'], description: 'Operation.' },
|
|
16
16
|
op: { type: 'string', enum: ['add','edit','delete','list','candidates','promote','dismiss'], description: 'Mutation op. candidates/promote/dismiss drive core-memory proposal approval.' },
|
|
17
17
|
id: { type: 'number', description: 'Exact memory id.' },
|
|
18
18
|
element: { type: 'string', description: 'Memory key/title.' },
|
|
19
19
|
summary: { type: 'string', description: 'Memory content.' },
|
|
20
20
|
category: { type: 'string', enum: ['rule','constraint','decision','fact','goal','preference','task','issue'], description: 'Category.' },
|
|
21
21
|
status: { type: 'string', enum: ['pending','active','archived'], description: 'Lifecycle status.' },
|
|
22
|
-
maxDays: { type: 'number', description: 'Prune age days.' },
|
|
23
|
-
window: { type: 'string', description: 'Window, e.g. 24h/7d/30d.' },
|
|
24
22
|
limit: { type: 'number', description: 'Max rows/items.' },
|
|
25
|
-
concurrency: { type: 'number', description: 'Worker concurrency.' },
|
|
26
|
-
sessionId: { type: 'string', description: 'Scoped session id.' },
|
|
27
|
-
session_id: { type: 'string', description: 'Alias for sessionId.' },
|
|
28
|
-
window_size: { type: 'number', description: 'cycle1 rows/window.' },
|
|
29
|
-
rows_per_session: { type: 'number', description: 'cycle1 rows/session.' },
|
|
30
23
|
confirm: { type: 'string', description: 'Exact confirmation phrase for destructive actions.' },
|
|
31
|
-
cycle3Mode: { type: 'string', enum: ['proposal','conservative'], description: 'cycle3 mode.' },
|
|
32
24
|
project_id: { type: 'string', description: 'Core pool: common, slug, or *.' },
|
|
33
25
|
},
|
|
34
26
|
required: ['action'],
|
|
@@ -38,13 +30,13 @@ export const TOOL_DEFS = [
|
|
|
38
30
|
name: 'recall',
|
|
39
31
|
title: 'Recall',
|
|
40
32
|
annotations: { title: 'Recall', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
41
|
-
description: 'Retrieve stored memory/session history
|
|
33
|
+
description: 'Retrieve stored memory/session history; when in doubt, recall first. Query is topic/semantic search (hybrid embedding+keyword), not regex. Common patterns — previous conversation: period:"last" (all history newest-first, grouped per session); current session: sessionId with no query; recent window: period:"3h"/"30m"/"24h"; specific timeframe: period:"2026-07-03" / "2026-07-01~2026-07-03" / "12:00~14:00" (today) / "2026-07-03 12:00~14:00"; topic: query alone or query+period; exact follow-up: id from a prior result.',
|
|
42
34
|
inputSchema: {
|
|
43
35
|
type: 'object',
|
|
44
36
|
properties: {
|
|
45
37
|
query: { anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' }, minItems: 1 }], description: 'Search text, or array for independent fan-out queries.' },
|
|
46
38
|
id: { anyOf: [{ type: 'number' }, { type: 'array', items: { type: 'number' }, minItems: 1 }], description: 'Exact #id(s) from recall. Do not invent ids.' },
|
|
47
|
-
period: { type: 'string', description: "last (
|
|
39
|
+
period: { type: 'string', description: "last (all history newest-first), Nm/Nh/Nd (rolling), today/yesterday/this_week/last_week, all, YYYY-MM-DD, YYYY-MM-DD~YYYY-MM-DD, HH:MM~HH:MM (today), or 'YYYY-MM-DD HH:MM~HH:MM'." },
|
|
48
40
|
limit: { type: 'number', description: 'Max entries.' },
|
|
49
41
|
offset: { type: 'number', description: 'Skip entries.' },
|
|
50
42
|
sort: { type: 'string', enum: ['importance', 'date'], description: 'importance or date.' },
|
|
@@ -71,7 +63,7 @@ export const TOOL_DEFS = [
|
|
|
71
63
|
type: 'object',
|
|
72
64
|
properties: {
|
|
73
65
|
query: { type: 'string', description: 'Search text.' },
|
|
74
|
-
period: { type: 'string', description:
|
|
66
|
+
period: { type: 'string', description: "last, Nm/Nh/Nd, today/yesterday/this_week/last_week, all, YYYY-MM-DD, date range, HH:MM~HH:MM (today), or 'YYYY-MM-DD HH:MM~HH:MM'." },
|
|
75
67
|
sort: { type: 'string', enum: ['date', 'importance'], description: 'date or importance.' },
|
|
76
68
|
category: { anyOf: [{ type: 'string', enum: ['rule','constraint','decision','fact','goal','preference','task','issue'] }, { type: 'array', items: { type: 'string', enum: ['rule','constraint','decision','fact','goal','preference','task','issue'] }, minItems: 1 }], description: 'Category filter.' },
|
|
77
69
|
limit: { type: 'number', default: 30, description: 'Max entries.' },
|