mixdog 0.9.3 → 0.9.5
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/README.md +112 -38
- package/package.json +10 -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/r5-orchestrated-task.json +7 -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-r13-clientmeta.json +464 -0
- package/scripts/bench/round-r14-betafeatures.json +466 -0
- package/scripts/bench/round-r15-fulldefault.json +462 -0
- package/scripts/bench/round-r16-sessionid.json +466 -0
- package/scripts/bench/round-r17-wirebytes.json +456 -0
- package/scripts/bench/round-r18-prewarm.json +468 -0
- package/scripts/bench/round-r19-clean.json +472 -0
- package/scripts/bench/round-r20-prewarm-clean.json +475 -0
- package/scripts/bench/round-r21-delta-retry.json +473 -0
- package/scripts/bench/round-r22-full-probe.json +693 -0
- package/scripts/bench/round-r23-itemprobe.json +701 -0
- package/scripts/bench/round-r24-shapefix.json +677 -0
- package/scripts/bench/round-r25-serial.json +464 -0
- package/scripts/bench/round-r26-parallel3.json +671 -0
- package/scripts/bench/round-r27-parallel10.json +894 -0
- package/scripts/bench/round-r28-parallel10-stagger.json +882 -0
- package/scripts/bench/round-r29-parallel10-stagger166.json +886 -0
- package/scripts/bench/round-r30-instid.json +253 -0
- package/scripts/bench/round-r31-upgradeprobe.json +256 -0
- package/scripts/bench/round-r32-vs-codex-lead.json +254 -0
- package/scripts/bench/round-r33-vs-codex-codex.json +115 -0
- package/scripts/bench/round-r34-orchestrated.json +120 -0
- package/scripts/bench/round-r35-orchestrated-codex.json +61 -0
- package/scripts/bench/round-r36-orchestrated-capped.json +128 -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 +251 -32
- package/scripts/freevar-smoke.mjs +95 -0
- package/scripts/internal-comms-bench.mjs +3 -4
- package/scripts/internal-comms-smoke.mjs +10 -9
- package/scripts/model-catalog-audit.mjs +209 -0
- package/scripts/model-list-sanitize-test.mjs +37 -0
- 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-bench.mjs +76 -13
- package/scripts/recall-quality-cases.json +12 -0
- package/scripts/recall-usecase-cases.json +18 -0
- package/scripts/session-bench.mjs +152 -6
- package/scripts/tool-smoke.mjs +25 -65
- 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 +9 -8
- package/src/agents/maintainer/AGENT.md +4 -0
- package/src/agents/reviewer/AGENT.md +2 -1
- package/src/agents/scheduler-task/AGENT.md +2 -3
- package/src/agents/webhook-handler/AGENT.md +2 -3
- package/src/agents/worker/AGENT.md +10 -7
- package/src/app.mjs +12 -1
- package/src/headless-role.mjs +7 -1
- package/src/lib/rules-builder.cjs +4 -0
- package/src/mixdog-session-runtime.mjs +647 -2056
- package/src/output-styles/default.md +30 -9
- package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
- package/src/output-styles/minimal.md +8 -6
- package/src/output-styles/simple.md +21 -7
- package/src/rules/agent/00-common.md +6 -3
- package/src/rules/agent/30-explorer.md +16 -5
- package/src/rules/lead/01-general.md +5 -5
- package/src/rules/lead/lead-brief.md +15 -0
- package/src/rules/lead/lead-tool.md +6 -15
- package/src/rules/shared/01-tool.md +17 -21
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +8 -3
- 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 +105 -11
- package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +356 -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 +740 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +349 -2131
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +143 -1002
- package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +229 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +465 -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/providers/provider-catalog-cache.mjs +80 -0
- 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 +275 -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 +278 -402
- 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/stall-policy.mjs +20 -1
- 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 +35 -44
- 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 +27 -318
- package/src/runtime/channels/backends/telegram.mjs +8 -12
- package/src/runtime/channels/index.mjs +247 -701
- package/src/runtime/channels/lib/backend-dispatch.mjs +46 -0
- package/src/runtime/channels/lib/config.mjs +37 -149
- package/src/runtime/channels/lib/event-pipeline.mjs +22 -5
- package/src/runtime/channels/lib/event-queue.mjs +78 -13
- package/src/runtime/channels/lib/inbound-routing.mjs +74 -0
- package/src/runtime/channels/lib/interaction-workflows.mjs +5 -113
- 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/scheduler.mjs +27 -113
- 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 +313 -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 +43 -616
- package/src/runtime/channels/tool-defs.mjs +11 -130
- package/src/runtime/memory/index.mjs +210 -1948
- package/src/runtime/memory/lib/core-memory-store.mjs +5 -1
- 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/ko-morph.mjs +195 -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 +69 -12
- package/src/runtime/memory/lib/memory-text-utils.mjs +46 -0
- package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
- package/src/runtime/memory/lib/query-handlers.mjs +802 -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/config.mjs +14 -4
- package/src/runtime/shared/launcher-control.mjs +2 -2
- package/src/runtime/shared/markdown-frontmatter.mjs +19 -0
- package/src/runtime/shared/schedules-store.mjs +13 -3
- package/src/runtime/shared/tool-execution-contract.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 +119 -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 +205 -0
- package/src/session-runtime/quick-search-models.mjs +47 -0
- package/src/session-runtime/session-hooks.mjs +93 -0
- package/src/session-runtime/settings-api.mjs +352 -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/session-runtime/workflow.mjs +1 -1
- 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 +138 -669
- package/src/standalone/channel-admin.mjs +102 -90
- package/src/standalone/channel-worker.mjs +4 -7
- package/src/standalone/explore-tool.mjs +64 -14
- 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 +1059 -8110
- package/src/tui/app/app-format.mjs +213 -0
- package/src/tui/app/channel-pickers.mjs +508 -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 +356 -0
- package/src/tui/app/model-options.mjs +334 -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 +489 -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 +677 -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 +512 -0
- package/src/tui/app/use-transcript-window.mjs +607 -0
- package/src/tui/components/ConfirmBar.jsx +10 -7
- package/src/tui/components/Picker.jsx +64 -15
- package/src/tui/components/PromptInput.jsx +33 -102
- package/src/tui/components/SlashCommandPalette.jsx +8 -1
- package/src/tui/components/StatusLine.jsx +69 -15
- 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 +13553 -12384
- 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 +170 -574
- package/src/tui/figures.mjs +5 -0
- package/src/tui/index.jsx +65 -1
- 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 +77 -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 +16 -18
- package/src/workflows/sequential/WORKFLOW.md +16 -18
- package/vendor/ink/build/display-width.js +19 -3
- package/vendor/ink/build/ink.js +112 -7
- 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/channels/lib/holidays.mjs +0 -138
- 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
|
@@ -1,578 +1,36 @@
|
|
|
1
1
|
import * as http from "http";
|
|
2
|
-
import * as crypto from "crypto";
|
|
3
2
|
import { join } from "path";
|
|
4
3
|
import { spawn, spawnSync } from "child_process";
|
|
5
|
-
import {
|
|
6
|
-
import { getWebhookAuthtoken } from "../../shared/config.mjs";
|
|
7
|
-
import { appendFileSync, readFileSync, readdirSync, mkdirSync, writeFileSync, unlinkSync, existsSync, renameSync, watch as fsWatch } from "fs";
|
|
8
|
-
import { appendFile } from "fs/promises";
|
|
4
|
+
import { existsSync, readFileSync } from "fs";
|
|
9
5
|
import { randomUUID } from "crypto";
|
|
6
|
+
import { getWebhookAuthtoken } from "../../shared/config.mjs";
|
|
10
7
|
import { readMarkdownDocument } from "../../shared/markdown-frontmatter.mjs";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
process.stderr.write(`mixdog webhook: ${msg}
|
|
38
|
-
`);
|
|
39
|
-
} catch {
|
|
40
|
-
}
|
|
41
|
-
webhookLogBuffer.push(line);
|
|
42
|
-
if (!webhookLogTimer) {
|
|
43
|
-
webhookLogTimer = setTimeout(flushWebhookLog, 1000);
|
|
44
|
-
webhookLogTimer.unref?.();
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
const SIGNATURE_HEADERS = {
|
|
48
|
-
github: { header: "x-hub-signature-256", prefix: "sha256=" },
|
|
49
|
-
sentry: { header: "sentry-hook-signature", prefix: "" },
|
|
50
|
-
stripe: { header: "stripe-signature", prefix: "" },
|
|
51
|
-
generic: { header: "x-signature-256", prefix: "sha256=" }
|
|
52
|
-
};
|
|
53
|
-
function extractSignature(headers, parser) {
|
|
54
|
-
if (parser) {
|
|
55
|
-
const mapping = SIGNATURE_HEADERS[parser];
|
|
56
|
-
if (mapping) {
|
|
57
|
-
const raw = headers[mapping.header];
|
|
58
|
-
if (raw) return mapping.prefix ? raw.replace(mapping.prefix, "") : raw;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
for (const mapping of Object.values(SIGNATURE_HEADERS)) {
|
|
62
|
-
const raw = headers[mapping.header];
|
|
63
|
-
if (raw) return mapping.prefix ? raw.replace(mapping.prefix, "") : raw;
|
|
64
|
-
}
|
|
65
|
-
return null;
|
|
66
|
-
}
|
|
67
|
-
// Stripe's documented replay tolerance. A captured signature older (or more
|
|
68
|
-
// than this skew newer) than the window is rejected even if the HMAC matches.
|
|
69
|
-
const STRIPE_TOLERANCE_MS = 5 * 60 * 1000;
|
|
70
|
-
function verifySignature(secret, rawBody, signatureValue, parser) {
|
|
71
|
-
if (parser === "stripe") {
|
|
72
|
-
// Stripe signs `${t}.${payload}`, not the body alone, and the t= field
|
|
73
|
-
// must be validated against the clock: without it a captured (t, v1) pair
|
|
74
|
-
// replays forever. Require BOTH fields, check freshness, then verify the
|
|
75
|
-
// HMAC over the timestamped payload.
|
|
76
|
-
const vMatch = signatureValue.match(/v1=([a-f0-9]+)/);
|
|
77
|
-
const tMatch = signatureValue.match(/t=(\d+)/);
|
|
78
|
-
if (!vMatch || !tMatch) return false;
|
|
79
|
-
const ts = Number(tMatch[1]);
|
|
80
|
-
if (!Number.isFinite(ts) || Math.abs(Date.now() - ts * 1000) > STRIPE_TOLERANCE_MS) return false;
|
|
81
|
-
const expected = crypto.createHmac("sha256", secret).update(`${tMatch[1]}.${rawBody}`).digest("hex");
|
|
82
|
-
// timingSafeEqual throws on length mismatch / malformed hex; wrap so a
|
|
83
|
-
// crafted signature header can't crash the request handler.
|
|
84
|
-
try {
|
|
85
|
-
const a = Buffer.from(vMatch[1], "hex");
|
|
86
|
-
const b = Buffer.from(expected, "hex");
|
|
87
|
-
if (a.length !== b.length) return false;
|
|
88
|
-
return crypto.timingSafeEqual(a, b);
|
|
89
|
-
} catch {
|
|
90
|
-
return false;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
const expected = crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
|
|
94
|
-
try {
|
|
95
|
-
const a = Buffer.from(signatureValue, "hex");
|
|
96
|
-
const b = Buffer.from(expected, "hex");
|
|
97
|
-
if (a.length !== b.length) return false;
|
|
98
|
-
return crypto.timingSafeEqual(a, b);
|
|
99
|
-
} catch {
|
|
100
|
-
return false;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// ── Endpoint config loader ─────────────────────────────────────────────
|
|
105
|
-
// Reads DATA_DIR/webhooks/<name>/WEBHOOK.md (written by setup-server.mjs
|
|
106
|
-
// via POST /webhooks). The frontmatter IS the config object; the markdown
|
|
107
|
-
// body is the instructions/prompt. Cached in-memory, invalidated by
|
|
108
|
-
// fs.watch on the webhooks directory. Returns the frontmatter object
|
|
109
|
-
// { secret, parser, channel, model, role, enabled } where routing is by
|
|
110
|
-
// `channel` presence and `role` names a user-workflow entry when set.
|
|
111
|
-
const _endpointCache = new Map();
|
|
112
|
-
let _endpointWatcher = null;
|
|
113
|
-
function _endpointConfigPath(name) {
|
|
114
|
-
return join(WEBHOOKS_DIR, name, "WEBHOOK.md");
|
|
115
|
-
}
|
|
116
|
-
// Per-endpoint HMAC secret is stored in a side file (WEBHOOKS_DIR/<name>/secret),
|
|
117
|
-
// not in WEBHOOK.md frontmatter — frontmatter is a lossy `key: value` format
|
|
118
|
-
// (unquote strips surrounding quotes) and would corrupt user secrets on the
|
|
119
|
-
// save->rewrite round-trip. Read fresh on each verify (no cache): the signing
|
|
120
|
-
// path is not hot and a stale secret would silently reject valid deliveries.
|
|
121
|
-
function _readEndpointSecret(name) {
|
|
122
|
-
try {
|
|
123
|
-
const s = readFileSync(join(WEBHOOKS_DIR, name, "secret"), "utf8").trim();
|
|
124
|
-
return s || null;
|
|
125
|
-
} catch {
|
|
126
|
-
return null;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
function _ensureEndpointWatcher() {
|
|
130
|
-
if (_endpointWatcher) return;
|
|
131
|
-
try {
|
|
132
|
-
if (!existsSync(WEBHOOKS_DIR)) return;
|
|
133
|
-
_endpointWatcher = fsWatch(WEBHOOKS_DIR, { recursive: true }, (_event, filename) => {
|
|
134
|
-
if (!filename) { _endpointCache.clear(); return; }
|
|
135
|
-
// filename is like "<endpoint>/WEBHOOK.md" or "<endpoint>"
|
|
136
|
-
const parts = String(filename).split(/[\\/]/);
|
|
137
|
-
const endpointName = parts[0];
|
|
138
|
-
if (endpointName) _endpointCache.delete(endpointName);
|
|
139
|
-
else _endpointCache.clear();
|
|
140
|
-
});
|
|
141
|
-
_endpointWatcher.on("error", () => { _endpointWatcher = null; _endpointCache.clear(); });
|
|
142
|
-
} catch {
|
|
143
|
-
// Watch failures are non-fatal; cache simply stays until process restart.
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
function _closeEndpointWatcher() {
|
|
147
|
-
if (!_endpointWatcher) return;
|
|
148
|
-
try { _endpointWatcher.close(); } catch {}
|
|
149
|
-
_endpointWatcher = null;
|
|
150
|
-
_endpointCache.clear();
|
|
151
|
-
}
|
|
152
|
-
function loadEndpointConfig(name) {
|
|
153
|
-
if (!name) return null;
|
|
154
|
-
// A cached entry is only authoritative while the fs.watch handle is
|
|
155
|
-
// armed — otherwise a later mkdir+write of WEBHOOKS_DIR/<name>/
|
|
156
|
-
// WEBHOOK.md has no way to invalidate the cache and a stale `null`
|
|
157
|
-
// (e.g. captured before WEBHOOKS_DIR existed) would pin forever.
|
|
158
|
-
if (_endpointCache.has(name) && _endpointWatcher) return _endpointCache.get(name);
|
|
159
|
-
_ensureEndpointWatcher();
|
|
160
|
-
const p = _endpointConfigPath(name);
|
|
161
|
-
if (!existsSync(p)) {
|
|
162
|
-
// Only cache the missing-config state when the watcher is live, so
|
|
163
|
-
// a later create can invalidate it. Otherwise leave the slot empty
|
|
164
|
-
// and re-read on the next call.
|
|
165
|
-
if (_endpointWatcher) _endpointCache.set(name, null);
|
|
166
|
-
return null;
|
|
167
|
-
}
|
|
168
|
-
try {
|
|
169
|
-
// Frontmatter is the config object; `enabled` arrives as a string and
|
|
170
|
-
// is cast so `endpoint?.enabled === false` gates match a written
|
|
171
|
-
// `enabled: false`.
|
|
172
|
-
const { frontmatter } = readMarkdownDocument(readFileSync(p, "utf8"));
|
|
173
|
-
const cfg = { ...frontmatter };
|
|
174
|
-
if (Object.prototype.hasOwnProperty.call(cfg, "enabled")) {
|
|
175
|
-
cfg.enabled = cfg.enabled !== "false" && cfg.enabled !== false;
|
|
176
|
-
}
|
|
177
|
-
_endpointCache.set(name, cfg);
|
|
178
|
-
return cfg;
|
|
179
|
-
} catch {
|
|
180
|
-
if (_endpointWatcher) _endpointCache.set(name, null);
|
|
181
|
-
return null;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// ── Delivery tracking ─────────────────────────────────────────────────
|
|
186
|
-
// Per-endpoint append-only log at WEBHOOKS_DIR/<name>/deliveries.jsonl.
|
|
187
|
-
// Each POST writes at least two lines: {status:"pending"|"processing"}
|
|
188
|
-
// then {status:"done"|"failed"|"dedup"}. Earlier fields (payloadPreview,
|
|
189
|
-
// headersSummary) are kept on the first line only; later status updates
|
|
190
|
-
// reference the same `id` and are merged latest-wins at read time.
|
|
191
|
-
const DELIVERY_INDEX_MAX_IDS = 2000;
|
|
192
|
-
const DELIVERY_LOG_MAX_LINES = 10_000;
|
|
193
|
-
/** @type {Map<string, Map<string, object>>} */
|
|
194
|
-
const _deliveryIndexByEndpoint = new Map();
|
|
195
|
-
/** @type {Set<string>} */
|
|
196
|
-
const _deliveryIndexWarmed = new Set();
|
|
197
|
-
/** @type {Map<string, number>} */
|
|
198
|
-
const _deliveryLogLineCountByEndpoint = new Map();
|
|
199
|
-
/** @type {Map<string, number>} distinct-id count at last warm/compaction; drives the redundancy-based compaction trigger. */
|
|
200
|
-
const _deliveryKeptCountByEndpoint = new Map();
|
|
201
|
-
function _deliveriesPath(name) {
|
|
202
|
-
return join(WEBHOOKS_DIR, name, "deliveries.jsonl");
|
|
203
|
-
}
|
|
204
|
-
function _mergeDeliveryRows(prior, entry) {
|
|
205
|
-
return prior ? { ...prior, ...entry } : entry;
|
|
206
|
-
}
|
|
207
|
-
function _isBlockingDeliveryStatus(status) {
|
|
208
|
-
return status === "received" || status === "processing" || status === "done";
|
|
209
|
-
}
|
|
210
|
-
function _deliveryIndexFor(name) {
|
|
211
|
-
let map = _deliveryIndexByEndpoint.get(name);
|
|
212
|
-
if (!map) {
|
|
213
|
-
map = new Map();
|
|
214
|
-
_deliveryIndexByEndpoint.set(name, map);
|
|
215
|
-
}
|
|
216
|
-
return map;
|
|
217
|
-
}
|
|
218
|
-
// Bound retained ids so successful ("done") deliveries cannot accumulate
|
|
219
|
-
// forever in RAM or on disk. In-flight claims (received/processing) are
|
|
220
|
-
// ALWAYS kept — dropping one would let a duplicate dispatch through. The
|
|
221
|
-
// remaining DELIVERY_INDEX_MAX_IDS budget goes to the newest "done" rows
|
|
222
|
-
// (dedup of recent retries) first, then newest terminal rows for history.
|
|
223
|
-
// Older "done" rows age out; a sender re-delivering an id that stale is
|
|
224
|
-
// treated as new — acceptable beyond any realistic retry window.
|
|
225
|
-
function _retainedDeliveryIds(entries) {
|
|
226
|
-
const inflight = [];
|
|
227
|
-
const done = [];
|
|
228
|
-
const other = [];
|
|
229
|
-
for (const e of entries) {
|
|
230
|
-
if (e.status === "received" || e.status === "processing") inflight.push(e);
|
|
231
|
-
else if (e.status === "done") done.push(e);
|
|
232
|
-
else other.push(e);
|
|
233
|
-
}
|
|
234
|
-
const keep = new Set(inflight.map((e) => e.id));
|
|
235
|
-
const byTsDesc = (a, b) => String(b.ts || "").localeCompare(String(a.ts || ""));
|
|
236
|
-
done.sort(byTsDesc);
|
|
237
|
-
other.sort(byTsDesc);
|
|
238
|
-
for (const e of [...done, ...other]) {
|
|
239
|
-
if (keep.size >= DELIVERY_INDEX_MAX_IDS) break;
|
|
240
|
-
keep.add(e.id);
|
|
241
|
-
}
|
|
242
|
-
return keep;
|
|
243
|
-
}
|
|
244
|
-
function _pruneDeliveryIndexMap(byId) {
|
|
245
|
-
if (byId.size <= DELIVERY_INDEX_MAX_IDS) return;
|
|
246
|
-
const keep = _retainedDeliveryIds([...byId.values()]);
|
|
247
|
-
for (const id of byId.keys()) {
|
|
248
|
-
if (!keep.has(id)) byId.delete(id);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
function _deliveryLogLineCount(name) {
|
|
252
|
-
return _deliveryLogLineCountByEndpoint.get(name) ?? 0;
|
|
253
|
-
}
|
|
254
|
-
function _setDeliveryLogLineCount(name, n) {
|
|
255
|
-
_deliveryLogLineCountByEndpoint.set(name, Math.max(0, n));
|
|
256
|
-
}
|
|
257
|
-
function _bumpDeliveryLogLineCount(name, delta = 1) {
|
|
258
|
-
_setDeliveryLogLineCount(name, _deliveryLogLineCount(name) + delta);
|
|
259
|
-
}
|
|
260
|
-
function _readDeliveriesFileMerged(name) {
|
|
261
|
-
const p = _deliveriesPath(name);
|
|
262
|
-
const byId = new Map();
|
|
263
|
-
let lineCount = 0;
|
|
264
|
-
if (!existsSync(p)) return { byId, lineCount };
|
|
265
|
-
try {
|
|
266
|
-
const raw = readFileSync(p, "utf8");
|
|
267
|
-
for (const line of raw.split("\n")) {
|
|
268
|
-
if (!line) continue;
|
|
269
|
-
lineCount++;
|
|
270
|
-
try {
|
|
271
|
-
const entry = JSON.parse(line);
|
|
272
|
-
if (!entry?.id) continue;
|
|
273
|
-
byId.set(entry.id, _mergeDeliveryRows(byId.get(entry.id), entry));
|
|
274
|
-
} catch {}
|
|
275
|
-
}
|
|
276
|
-
} catch {}
|
|
277
|
-
return { byId, lineCount };
|
|
278
|
-
}
|
|
279
|
-
function _ingestDeliveriesFileIntoIndex(name) {
|
|
280
|
-
const { byId: merged, lineCount } = _readDeliveriesFileMerged(name);
|
|
281
|
-
const byId = _deliveryIndexFor(name);
|
|
282
|
-
byId.clear();
|
|
283
|
-
for (const [id, row] of merged) byId.set(id, row);
|
|
284
|
-
_pruneDeliveryIndexMap(byId);
|
|
285
|
-
_setDeliveryLogLineCount(name, lineCount);
|
|
286
|
-
// Track the RETAINED (post-prune) distinct count, not the raw file count:
|
|
287
|
-
// a pre-existing oversized log then trips the compaction trigger promptly
|
|
288
|
-
// instead of inflating the threshold until it grows even larger.
|
|
289
|
-
_deliveryKeptCountByEndpoint.set(name, byId.size);
|
|
290
|
-
}
|
|
291
|
-
function _ensureDeliveryIndex(name) {
|
|
292
|
-
if (_deliveryIndexWarmed.has(name)) return;
|
|
293
|
-
_deliveryIndexWarmed.add(name);
|
|
294
|
-
_ingestDeliveriesFileIntoIndex(name);
|
|
295
|
-
}
|
|
296
|
-
function _applyDeliveryEntryToIndex(name, entry) {
|
|
297
|
-
if (!entry?.id) return;
|
|
298
|
-
const byId = _deliveryIndexFor(name);
|
|
299
|
-
byId.set(entry.id, _mergeDeliveryRows(byId.get(entry.id), entry));
|
|
300
|
-
_pruneDeliveryIndexMap(byId);
|
|
301
|
-
}
|
|
302
|
-
function _compactDeliveriesLogIfNeeded(name) {
|
|
303
|
-
// Redundancy-based trigger: compact only when the log holds meaningfully more
|
|
304
|
-
// lines than distinct ids (i.e. there are status-update rows to collapse).
|
|
305
|
-
// The threshold scales with the distinct-id count so an endpoint with many
|
|
306
|
-
// legitimate blocking ids does NOT re-compact on every append (which would
|
|
307
|
-
// re-read the whole log permanently once distinct > DELIVERY_LOG_MAX_LINES).
|
|
308
|
-
const kept = _deliveryKeptCountByEndpoint.get(name) ?? _deliveryIndexFor(name).size;
|
|
309
|
-
const threshold = Math.max(DELIVERY_LOG_MAX_LINES, kept * 2);
|
|
310
|
-
if (_deliveryLogLineCount(name) <= threshold) return;
|
|
311
|
-
const { byId: merged } = _readDeliveriesFileMerged(name);
|
|
312
|
-
const rows = [...merged.values()];
|
|
313
|
-
const keepIds = _retainedDeliveryIds(rows);
|
|
314
|
-
const keep = new Map();
|
|
315
|
-
for (const e of rows) {
|
|
316
|
-
if (keepIds.has(e.id)) keep.set(e.id, e);
|
|
317
|
-
}
|
|
318
|
-
const lines = [...keep.values()]
|
|
319
|
-
.sort((a, b) => String(a.ts || "").localeCompare(String(b.ts || "")))
|
|
320
|
-
.map((e) => JSON.stringify(e) + "\n")
|
|
321
|
-
.join("");
|
|
322
|
-
const p = _deliveriesPath(name);
|
|
323
|
-
const tmp = `${p}.compact-${process.pid}-${Date.now()}.tmp`;
|
|
324
|
-
try {
|
|
325
|
-
writeFileSync(tmp, lines);
|
|
326
|
-
renameSync(tmp, p);
|
|
327
|
-
// Refresh index + counters by RE-READING the post-rename file (not the
|
|
328
|
-
// pre-rename `keep` snapshot). The webhook daemon is the single writer and
|
|
329
|
-
// append+compact run synchronously in one process, so no append can
|
|
330
|
-
// interleave between the fresh read and the rename; re-reading keeps the
|
|
331
|
-
// warmed state exactly matching on-disk content.
|
|
332
|
-
_ingestDeliveriesFileIntoIndex(name);
|
|
333
|
-
} catch (err) {
|
|
334
|
-
logWebhook(`${name}: deliveries compact failed: ${err?.message ?? err}`);
|
|
335
|
-
try {
|
|
336
|
-
if (existsSync(tmp)) unlinkSync(tmp);
|
|
337
|
-
} catch {}
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
function appendDelivery(name, entry) {
|
|
341
|
-
try {
|
|
342
|
-
const dir = join(WEBHOOKS_DIR, name);
|
|
343
|
-
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
344
|
-
const full = { ts: new Date().toISOString(), ...entry };
|
|
345
|
-
const line = JSON.stringify(full) + "\n";
|
|
346
|
-
void appendFile(_deliveriesPath(name), line).catch((err) => {
|
|
347
|
-
logWebhook(`${name}: deliveries append failed: ${err?.message ?? err}`);
|
|
348
|
-
});
|
|
349
|
-
const wasWarmed = _deliveryIndexWarmed.has(name);
|
|
350
|
-
_ensureDeliveryIndex(name);
|
|
351
|
-
if (wasWarmed) _bumpDeliveryLogLineCount(name, 1);
|
|
352
|
-
_applyDeliveryEntryToIndex(name, full);
|
|
353
|
-
_compactDeliveriesLogIfNeeded(name);
|
|
354
|
-
return true;
|
|
355
|
-
} catch (err) {
|
|
356
|
-
logWebhook(`${name}: deliveries append failed: ${err?.message ?? err}`);
|
|
357
|
-
return false;
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
function readDeliveries(name) {
|
|
361
|
-
_ensureDeliveryIndex(name);
|
|
362
|
-
const byId = _deliveryIndexByEndpoint.get(name);
|
|
363
|
-
return byId ? [...byId.values()] : [];
|
|
364
|
-
}
|
|
365
|
-
// Dedup gate against a still-active claim or a successful prior delivery.
|
|
366
|
-
// Only rows with status "received" (non-terminal claim) or "done"
|
|
367
|
-
// (successful delivery) block a retry; terminal "failed" / "quiet-skip"
|
|
368
|
-
// rows are NOT considered duplicates so a sender can legitimately
|
|
369
|
-
// redeliver the same id after a recoverable failure. Without this
|
|
370
|
-
// scoping, every prior row would permanently dedup the id and stop
|
|
371
|
-
// legit redelivery.
|
|
372
|
-
function deliveryExists(name, id) {
|
|
373
|
-
// "processing" must also dedup: a delegate dispatch in flight (up to
|
|
374
|
-
// DISPATCH_TIMEOUT_MS = 10 min) would otherwise be duplicated by a
|
|
375
|
-
// retried delivery of the same id while the first handler is still
|
|
376
|
-
// running. Block on any non-terminal status.
|
|
377
|
-
_ensureDeliveryIndex(name);
|
|
378
|
-
const entry = _deliveryIndexFor(name).get(id);
|
|
379
|
-
return Boolean(entry && _isBlockingDeliveryStatus(entry.status));
|
|
380
|
-
}
|
|
381
|
-
function extractDeliveryId(headers) {
|
|
382
|
-
return headers["x-github-delivery"]
|
|
383
|
-
|| headers["x-delivery-id"]
|
|
384
|
-
|| headers["x-request-id"]
|
|
385
|
-
|| null;
|
|
386
|
-
}
|
|
387
|
-
function buildHeadersSummary(headers) {
|
|
388
|
-
const summary = {};
|
|
389
|
-
if (headers["x-github-event"]) summary.event_type = headers["x-github-event"];
|
|
390
|
-
if (headers["x-github-delivery"]) summary.delivery_id = headers["x-github-delivery"];
|
|
391
|
-
summary.signature_present = Boolean(
|
|
392
|
-
headers["x-hub-signature-256"] || headers["x-signature-256"]
|
|
393
|
-
|| headers["stripe-signature"] || headers["sentry-hook-signature"]
|
|
394
|
-
);
|
|
395
|
-
if (headers["content-type"]) summary.content_type = headers["content-type"];
|
|
396
|
-
return summary;
|
|
397
|
-
}
|
|
398
|
-
// Public read helper — used by setup-server API to list deliveries across endpoints.
|
|
399
|
-
function listAllDeliveries({ endpoint = null, status = null, limit = 100 } = {}) {
|
|
400
|
-
const out = [];
|
|
401
|
-
if (!existsSync(WEBHOOKS_DIR)) return out;
|
|
402
|
-
const names = endpoint
|
|
403
|
-
? [endpoint]
|
|
404
|
-
: readdirSync(WEBHOOKS_DIR, { withFileTypes: true })
|
|
405
|
-
.filter((d) => d.isDirectory())
|
|
406
|
-
.map((d) => d.name);
|
|
407
|
-
for (const name of names) {
|
|
408
|
-
for (const entry of readDeliveries(name)) {
|
|
409
|
-
if (status && entry.status !== status) continue;
|
|
410
|
-
out.push({ endpoint: name, ...entry });
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
out.sort((a, b) => String(b.ts || "").localeCompare(String(a.ts || "")));
|
|
414
|
-
return out.slice(0, limit);
|
|
415
|
-
}
|
|
416
|
-
export { listAllDeliveries };
|
|
417
|
-
function _readNgrokBinFromRegistry() {
|
|
418
|
-
if (process.platform !== "win32") return null;
|
|
419
|
-
try {
|
|
420
|
-
const r = spawnSync("reg", ["query", "HKCU\\Environment", "/v", "NGROK_BIN"], {
|
|
421
|
-
encoding: "utf8", windowsHide: true, stdio: ["ignore", "pipe", "ignore"],
|
|
422
|
-
});
|
|
423
|
-
if (r.status === 0 && r.stdout) {
|
|
424
|
-
const m = r.stdout.match(/NGROK_BIN\s+REG_(?:EXPAND_)?SZ\s+(.+?)\r?\n/);
|
|
425
|
-
if (m && m[1]) return m[1].trim();
|
|
426
|
-
}
|
|
427
|
-
} catch { /* missing reg.exe is non-fatal */ }
|
|
428
|
-
return null;
|
|
429
|
-
}
|
|
430
|
-
function resolveNgrokBin() {
|
|
431
|
-
// Invariant on Windows: BOTH process.env.NGROK_BIN AND HKCU\Environment\NGROK_BIN
|
|
432
|
-
// are candidate sources. process.env is the shell-start snapshot; registry
|
|
433
|
-
// is the live user definition. Each candidate is tried in order and the
|
|
434
|
-
// first that resolves to an existing file wins. This recovers two distinct
|
|
435
|
-
// post-setx cases without a host-agent restart:
|
|
436
|
-
// (a) env unset, registry set — fresh install + setx after process start
|
|
437
|
-
// (b) env set to stale old path, registry set to new — user moved or
|
|
438
|
-
// re-installed ngrok and setx'd the new path; the old env value would
|
|
439
|
-
// otherwise dead-end at existsSync=false.
|
|
440
|
-
// POSIX has no registry; process.env is the sole candidate.
|
|
441
|
-
const candidates = [];
|
|
442
|
-
if (process.env.NGROK_BIN) candidates.push(process.env.NGROK_BIN);
|
|
443
|
-
const fromReg = _readNgrokBinFromRegistry();
|
|
444
|
-
if (fromReg && !candidates.includes(fromReg)) candidates.push(fromReg);
|
|
445
|
-
for (const p of candidates) {
|
|
446
|
-
if (existsSync(p)) return p;
|
|
447
|
-
}
|
|
448
|
-
if (candidates.length > 0) {
|
|
449
|
-
throw new Error(`NGROK_BIN candidates (${candidates.join(", ")}) do not exist on disk. Set NGROK_BIN to the correct ngrok binary path.`);
|
|
450
|
-
}
|
|
451
|
-
throw new Error('NGROK_BIN env var is not set. Set NGROK_BIN to the path of the ngrok binary (e.g. NGROK_BIN=/usr/local/bin/ngrok).');
|
|
452
|
-
}
|
|
453
|
-
const NGROK_META_FILE = join(DATA_DIR, "ngrok-meta.json");
|
|
454
|
-
const NGROK_OLD_PID_FILE = join(DATA_DIR, "ngrok.pid");
|
|
455
|
-
const NGROK_MAX_AGE_MS = 24 * 60 * 60 * 1e3; // 24 hours
|
|
456
|
-
|
|
457
|
-
function normalizeDomain(d) {
|
|
458
|
-
if (!d) return '';
|
|
459
|
-
const url = new URL(d.includes('://') ? d : 'https://' + d);
|
|
460
|
-
if (!url.hostname) throw new Error(`[webhook] invalid host: ${d}`);
|
|
461
|
-
return url.hostname.toLowerCase();
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
function readNgrokMeta() {
|
|
465
|
-
try { return JSON.parse(readFileSync(NGROK_META_FILE, 'utf8')) } catch {}
|
|
466
|
-
// Migration: read old pid file if meta doesn't exist
|
|
467
|
-
try {
|
|
468
|
-
const pid = parseInt(readFileSync(NGROK_OLD_PID_FILE, 'utf8').trim());
|
|
469
|
-
if (pid > 0) {
|
|
470
|
-
logWebhook(`migrating ngrok.pid (PID ${pid}) to ngrok-meta.json`);
|
|
471
|
-
const meta = { pid, domain: '', port: 0, startedAt: new Date().toISOString() };
|
|
472
|
-
writeNgrokMeta(meta);
|
|
473
|
-
try { unlinkSync(NGROK_OLD_PID_FILE) } catch {}
|
|
474
|
-
return meta;
|
|
475
|
-
}
|
|
476
|
-
} catch {}
|
|
477
|
-
return null;
|
|
478
|
-
}
|
|
479
|
-
function writeNgrokMeta(meta) {
|
|
480
|
-
try { writeFileSync(NGROK_META_FILE, JSON.stringify(meta, null, 2)) } catch {}
|
|
481
|
-
}
|
|
482
|
-
function clearNgrokMeta() {
|
|
483
|
-
try { unlinkSync(NGROK_META_FILE) } catch {}
|
|
484
|
-
}
|
|
485
|
-
// Recycled-PID guard: a stale ngrok-meta.json may name a PID that ngrok
|
|
486
|
-
// long ago freed and the OS reassigned to an unrelated process (commonly
|
|
487
|
-
// another mixdog server). Verify the PID is actually an ngrok process
|
|
488
|
-
// before sending a kill signal, so a live peer's server is never taken
|
|
489
|
-
// down. Returns false (skip kill) when the check is inconclusive.
|
|
490
|
-
function isLikelyNgrok(pid) {
|
|
491
|
-
if (!pid || pid <= 0) return false;
|
|
492
|
-
try {
|
|
493
|
-
if (process.platform === "win32") {
|
|
494
|
-
const r = spawnSync("tasklist", ["/FI", `PID eq ${pid}`, "/FO", "CSV", "/NH"], { encoding: "utf8", timeout: 5000, windowsHide: true });
|
|
495
|
-
return /ngrok/i.test(r.stdout || "");
|
|
496
|
-
}
|
|
497
|
-
const r = spawnSync("ps", ["-p", String(pid), "-o", "comm="], { encoding: "utf8", timeout: 5000, windowsHide: true });
|
|
498
|
-
return /ngrok/i.test(r.stdout || "");
|
|
499
|
-
} catch { return false; }
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
function isProcessAlive(pid) {
|
|
503
|
-
if (!pid || pid <= 0) return false;
|
|
504
|
-
try { process.kill(pid, 0); return true; } catch { return false; }
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
// Strict PID extraction: the first non-empty output line must be decimal-only.
|
|
508
|
-
// `123junk` / any non-numeric noise → null, so a malformed shell result can
|
|
509
|
-
// never be coerced into a real PID we might later signal or kill.
|
|
510
|
-
function parseStrictPidLine(out) {
|
|
511
|
-
const line = String(out || "").split(/\r?\n/).map((s) => s.trim()).find((s) => s.length > 0);
|
|
512
|
-
if (!line || !/^\d+$/.test(line)) return null;
|
|
513
|
-
const n = parseInt(line, 10);
|
|
514
|
-
return Number.isFinite(n) && n > 0 ? n : null;
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
function resolvePortOwnerPid(port) {
|
|
518
|
-
// Coerce + range-validate the port BEFORE any spawn so it can never inject
|
|
519
|
-
// into a command, and use spawnSync argv (no shell) for defense in depth.
|
|
520
|
-
// Invalid port → null (treated as "no owner", never an exec).
|
|
521
|
-
const p = Number(port);
|
|
522
|
-
if (!Number.isInteger(p) || p < 1 || p > 65535) return null;
|
|
523
|
-
try {
|
|
524
|
-
if (process.platform === "win32") {
|
|
525
|
-
const r = spawnSync(
|
|
526
|
-
"powershell",
|
|
527
|
-
["-NoProfile", "-Command", `(Get-NetTCPConnection -LocalPort ${p} -State Listen -ErrorAction SilentlyContinue | Select-Object -First 1).OwningProcess`],
|
|
528
|
-
{ encoding: "utf8", timeout: 3000, windowsHide: true },
|
|
529
|
-
);
|
|
530
|
-
return r.status === 0 ? parseStrictPidLine(r.stdout) : null;
|
|
531
|
-
}
|
|
532
|
-
const r = spawnSync("lsof", ["-ti", `:${p}`, "-sTCP:LISTEN"], { encoding: "utf8", timeout: 3000, windowsHide: true });
|
|
533
|
-
return r.status === 0 ? parseStrictPidLine(r.stdout) : null;
|
|
534
|
-
} catch {
|
|
535
|
-
return null;
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
async function handleWebhookPortInUse(basePort, expectedDomain) {
|
|
540
|
-
const ownerPid = resolvePortOwnerPid(basePort);
|
|
541
|
-
const ownerAlive = ownerPid != null && isProcessAlive(ownerPid);
|
|
542
|
-
const ownerIsNgrok = ownerAlive && isLikelyNgrok(ownerPid);
|
|
543
|
-
logWebhook(
|
|
544
|
-
`port ${basePort} EADDRINUSE — not reclaiming external PID ${ownerPid ?? "unknown"} (alive=${ownerAlive}, ngrok=${ownerIsNgrok}); trying next port`,
|
|
545
|
-
);
|
|
546
|
-
return { ok: false, bump: true, ownerPid };
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
function checkNgrokHealth(expectedDomain, expectedPort = null) {
|
|
550
|
-
try {
|
|
551
|
-
return new Promise((resolve) => {
|
|
552
|
-
const req = http.get("http://localhost:4040/api/tunnels", { timeout: 2000 }, (res) => {
|
|
553
|
-
let data = "";
|
|
554
|
-
res.on("data", chunk => data += chunk);
|
|
555
|
-
res.on("end", () => {
|
|
556
|
-
try {
|
|
557
|
-
const tunnels = JSON.parse(data).tunnels || [];
|
|
558
|
-
const expected = normalizeDomain(expectedDomain);
|
|
559
|
-
const match = tunnels.some(t => {
|
|
560
|
-
if (normalizeDomain(t.public_url) !== expected) return false;
|
|
561
|
-
if (!expectedPort) return true;
|
|
562
|
-
const addr = String(t.config?.addr || '');
|
|
563
|
-
return addr === `http://localhost:${expectedPort}`
|
|
564
|
-
|| addr === `https://localhost:${expectedPort}`
|
|
565
|
-
|| addr.endsWith(`:${expectedPort}`);
|
|
566
|
-
});
|
|
567
|
-
resolve(match);
|
|
568
|
-
} catch { resolve(false); }
|
|
569
|
-
});
|
|
570
|
-
});
|
|
571
|
-
req.on("error", () => resolve(false));
|
|
572
|
-
req.on("timeout", () => { req.destroy(); resolve(false); });
|
|
573
|
-
});
|
|
574
|
-
} catch { return Promise.resolve(false); }
|
|
575
|
-
}
|
|
8
|
+
import { logWebhook } from "./webhook/log.mjs";
|
|
9
|
+
import { SIGNATURE_HEADERS, extractSignature, STRIPE_TOLERANCE_MS, verifySignature } from "./webhook/signature.mjs";
|
|
10
|
+
import {
|
|
11
|
+
WEBHOOKS_DIR,
|
|
12
|
+
_readEndpointSecret,
|
|
13
|
+
_closeEndpointWatcher,
|
|
14
|
+
loadEndpointConfig,
|
|
15
|
+
appendDelivery,
|
|
16
|
+
deliveryExists,
|
|
17
|
+
extractDeliveryId,
|
|
18
|
+
buildHeadersSummary,
|
|
19
|
+
} from "./webhook/deliveries.mjs";
|
|
20
|
+
import {
|
|
21
|
+
NGROK_MAX_AGE_MS,
|
|
22
|
+
resolveNgrokBin,
|
|
23
|
+
normalizeDomain,
|
|
24
|
+
readNgrokMeta,
|
|
25
|
+
writeNgrokMeta,
|
|
26
|
+
clearNgrokMeta,
|
|
27
|
+
isLikelyNgrok,
|
|
28
|
+
isProcessAlive,
|
|
29
|
+
parseStrictPidLine,
|
|
30
|
+
resolvePortOwnerPid,
|
|
31
|
+
handleWebhookPortInUse,
|
|
32
|
+
checkNgrokHealth,
|
|
33
|
+
} from "./webhook/ngrok.mjs";
|
|
576
34
|
|
|
577
35
|
class WebhookServer {
|
|
578
36
|
config;
|
|
@@ -583,19 +41,8 @@ class WebhookServer {
|
|
|
583
41
|
listenInFlight = false;
|
|
584
42
|
noSecretWarned = false;
|
|
585
43
|
ngrokProcess = null;
|
|
586
|
-
|
|
587
|
-
// ctor accepts the TOP-LEVEL normalized config slice as the second arg:
|
|
588
|
-
// new WebhookServer(config.webhook, { quiet: config.quiet })
|
|
589
|
-
constructor(config, topLevel) {
|
|
44
|
+
constructor(config) {
|
|
590
45
|
this.config = config;
|
|
591
|
-
this._applyTopLevel(topLevel);
|
|
592
|
-
}
|
|
593
|
-
_applyTopLevel(src) {
|
|
594
|
-
this.quiet = null;
|
|
595
|
-
if (!src || typeof src !== "object") return;
|
|
596
|
-
if (src.quiet && typeof src.quiet === "object") {
|
|
597
|
-
this.quiet = src.quiet;
|
|
598
|
-
}
|
|
599
46
|
}
|
|
600
47
|
setEventPipeline(pipeline) {
|
|
601
48
|
this.eventPipeline = pipeline;
|
|
@@ -782,7 +229,6 @@ class WebhookServer {
|
|
|
782
229
|
const parsed = body ? JSON.parse(body) : {};
|
|
783
230
|
const eventType = headers["x-github-event"] || null;
|
|
784
231
|
const eventAction = parsed?.action || null;
|
|
785
|
-
if (this._maybeQuietSkip(name, eventType, eventAction, deliveryId, res)) return;
|
|
786
232
|
// Invariant: skip self-generated GitHub issue_comment events. All
|
|
787
233
|
// mixdog-authored issue comments are prefixed with "[mixdog "
|
|
788
234
|
// (e.g. "[mixdog reviewer] ..."), so a comment.body starting with
|
|
@@ -882,32 +328,6 @@ class WebhookServer {
|
|
|
882
328
|
}
|
|
883
329
|
return true;
|
|
884
330
|
}
|
|
885
|
-
// Quiet-hours skip: drop (do not queue) when webhook opt-in is on
|
|
886
|
-
// and current time falls inside the shared quiet window. Returns
|
|
887
|
-
// true when the request was answered with a 202 quiet-skip.
|
|
888
|
-
_maybeQuietSkip(name, eventType, eventAction, deliveryId, res) {
|
|
889
|
-
const webhookRespect = this.config?.respectQuiet === true;
|
|
890
|
-
const quietCfg = this.quiet
|
|
891
|
-
? { schedule: this.quiet.schedule ?? null, holidays: this.quiet.holidays ?? false }
|
|
892
|
-
: null;
|
|
893
|
-
if (webhookRespect && quietCfg && isInQuietWindow(quietCfg, new Date())) {
|
|
894
|
-
logWebhook(`${name}: quiet-skip event=${eventType || "<none>"} action=${eventAction || "<none>"} (id=${deliveryId})`);
|
|
895
|
-
// Terminal delivery row: the `received` claim at the top of
|
|
896
|
-
// handleRequest must be resolved by a terminal status. Without
|
|
897
|
-
// it, deliveryExists() keeps the non-terminal row visible and
|
|
898
|
-
// dedupes every future retry of the same id forever.
|
|
899
|
-
appendDelivery(name, {
|
|
900
|
-
id: deliveryId,
|
|
901
|
-
status: "quiet-skip",
|
|
902
|
-
event: eventType,
|
|
903
|
-
action: eventAction,
|
|
904
|
-
});
|
|
905
|
-
res.writeHead(202, { "Content-Type": "application/json" });
|
|
906
|
-
res.end(JSON.stringify({ status: "quiet-skip", event: eventType, action: eventAction, id: deliveryId }));
|
|
907
|
-
return true;
|
|
908
|
-
}
|
|
909
|
-
return false;
|
|
910
|
-
}
|
|
911
331
|
_listenWithRetry() {
|
|
912
332
|
if (!this.server || this.listenInFlight) return;
|
|
913
333
|
this.listenInFlight = true;
|
|
@@ -1149,10 +569,8 @@ class WebhookServer {
|
|
|
1149
569
|
logWebhook("stopped (ngrok left running for reuse)");
|
|
1150
570
|
return closed;
|
|
1151
571
|
}
|
|
1152
|
-
// reloadConfig(webhookCfg,
|
|
1153
|
-
|
|
1154
|
-
// slice (typically `{ quiet }`).
|
|
1155
|
-
async reloadConfig(config, topLevel, options = {}) {
|
|
572
|
+
// reloadConfig(webhookCfg, options?)
|
|
573
|
+
async reloadConfig(config, options = {}) {
|
|
1156
574
|
// Await server.close() before re-listen: server.close() is async and
|
|
1157
575
|
// releases the bound port only after the close callback fires. Calling
|
|
1158
576
|
// start() before that drains races the port and surfaces EADDRINUSE
|
|
@@ -1160,7 +578,6 @@ class WebhookServer {
|
|
|
1160
578
|
// holds the port.
|
|
1161
579
|
await this.stop();
|
|
1162
580
|
this.config = config;
|
|
1163
|
-
this._applyTopLevel(topLevel);
|
|
1164
581
|
if (options.autoStart !== false && config.enabled) this.start();
|
|
1165
582
|
}
|
|
1166
583
|
// ── Webhook handler ───────────────────────────────────────────────
|
|
@@ -1174,14 +591,17 @@ class WebhookServer {
|
|
|
1174
591
|
// The signature gate (below) fails closed on any WEBHOOK.md
|
|
1175
592
|
// endpoint lacking a secret, so dropping `mode` does not weaken auth.
|
|
1176
593
|
// `instructions` carries the markdown body (the prompt) so callers read
|
|
1177
|
-
// one file instead of a config.json + instructions.md pair.
|
|
1178
|
-
|
|
594
|
+
// one file instead of a config.json + instructions.md pair. Frontmatter
|
|
595
|
+
// `name`/`description` (SKILL.md convention) are display-only: dir name
|
|
596
|
+
// stays the canonical identity/URL routing key, so `name` is ignored here.
|
|
597
|
+
const handler = { channel: null, role: "webhook-handler", model: null, description: "", instructions: "" };
|
|
1179
598
|
if (existsSync(mdPath)) {
|
|
1180
599
|
try {
|
|
1181
600
|
const { frontmatter, body } = readMarkdownDocument(readFileSync(mdPath, "utf8"));
|
|
1182
601
|
if (frontmatter.channel) handler.channel = frontmatter.channel;
|
|
1183
602
|
if (typeof frontmatter.role === "string" && frontmatter.role) handler.role = frontmatter.role;
|
|
1184
603
|
if (typeof frontmatter.model === "string" && frontmatter.model) handler.model = frontmatter.model;
|
|
604
|
+
handler.description = String(frontmatter.description || "");
|
|
1185
605
|
handler.instructions = String(body || "").trim();
|
|
1186
606
|
} catch {
|
|
1187
607
|
}
|
|
@@ -1313,4 +733,11 @@ ${payload}
|
|
|
1313
733
|
}
|
|
1314
734
|
export {
|
|
1315
735
|
WebhookServer,
|
|
736
|
+
// Exported for scripts/webhook-smoke.mjs unit coverage. No behavior
|
|
737
|
+
// change — these were previously module-private.
|
|
738
|
+
extractSignature,
|
|
739
|
+
verifySignature,
|
|
740
|
+
loadEndpointConfig,
|
|
741
|
+
SIGNATURE_HEADERS,
|
|
742
|
+
STRIPE_TOLERANCE_MS,
|
|
1316
743
|
};
|