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
|
@@ -36,9 +36,6 @@ export function formatToolStartProgress(name, args = {}) {
|
|
|
36
36
|
? 'listing background tasks'
|
|
37
37
|
: `${action === 'wait' ? 'waiting for' : action} task ${_t(a.task_id)}`;
|
|
38
38
|
}
|
|
39
|
-
case 'diagnostics':
|
|
40
|
-
return `running diagnostics${a.path ? ` for ${_t(a.path)}` : ''}`;
|
|
41
|
-
|
|
42
39
|
// ── builtin / code_graph: search & navigation ────────────────────
|
|
43
40
|
case 'grep':
|
|
44
41
|
return Array.isArray(a.pattern) ? `searching for ${_plural(a.pattern.length, 'pattern')}` : `searching for ${_t(a.pattern)}`;
|
|
@@ -82,26 +79,8 @@ export function formatToolStartProgress(name, args = {}) {
|
|
|
82
79
|
// ── channels module ──────────────────────────────────────────────
|
|
83
80
|
case 'reply':
|
|
84
81
|
return 'replying';
|
|
85
|
-
case 'react':
|
|
86
|
-
return 'reacting';
|
|
87
|
-
case 'edit_message':
|
|
88
|
-
return 'editing message';
|
|
89
|
-
case 'download_attachment':
|
|
90
|
-
return 'downloading attachment';
|
|
91
82
|
case 'fetch':
|
|
92
83
|
return 'fetching messages';
|
|
93
|
-
case 'schedule_status':
|
|
94
|
-
return 'checking schedules';
|
|
95
|
-
case 'trigger_schedule':
|
|
96
|
-
return a.name ? `triggering ${_t(a.name)}` : 'triggering schedule';
|
|
97
|
-
case 'schedule_control':
|
|
98
|
-
return 'scheduling';
|
|
99
|
-
case 'activate_channel_bridge':
|
|
100
|
-
return 'activating channel';
|
|
101
|
-
case 'reload_config':
|
|
102
|
-
return 'reloading config';
|
|
103
|
-
case 'inject_command':
|
|
104
|
-
return 'injecting command';
|
|
105
84
|
|
|
106
85
|
// ── host_input / cwd ─────────────────────────────────────────────
|
|
107
86
|
case 'inject_input':
|
|
@@ -38,6 +38,15 @@ import { startChildGuardian } from '../../../shared/child-guardian.mjs';
|
|
|
38
38
|
// a static cycle — safe because neither binding is touched at module-eval
|
|
39
39
|
// time, only when the respective functions actually run.
|
|
40
40
|
import { adoptForegroundShellJob } from './builtin/shell-jobs.mjs';
|
|
41
|
+
import {
|
|
42
|
+
_maybeEncodePowerShellCommand,
|
|
43
|
+
extractPowerShellCommandInner,
|
|
44
|
+
} from './shell-powershell.mjs';
|
|
45
|
+
|
|
46
|
+
export {
|
|
47
|
+
_maybeEncodePowerShellCommand,
|
|
48
|
+
extractPowerShellCommandInner,
|
|
49
|
+
} from './shell-powershell.mjs';
|
|
41
50
|
|
|
42
51
|
// Inline cap. Output above this size is spilled to disk and the caller
|
|
43
52
|
// renders a path marker instead of pasting the tail. Matches the
|
|
@@ -415,78 +424,6 @@ export class ExecResult {
|
|
|
415
424
|
}
|
|
416
425
|
}
|
|
417
426
|
|
|
418
|
-
// On Windows, nested `powershell -Command "<inline>"` invocations can be
|
|
419
|
-
// mangled by an outer shell quoting layer before powershell.exe sees
|
|
420
|
-
// automatic variables (`$_`, `$args`, `$($_.Line)`, etc.). Rewrite
|
|
421
|
-
// `powershell -Command "<inline>"` / `pwsh -Command "<inline>"` to
|
|
422
|
-
// `-EncodedCommand <utf16le-base64>` so the payload stays opaque to the
|
|
423
|
-
// outer shell. Other shells / non-Windows are no-op pass-through.
|
|
424
|
-
// Match -Command "<body>" where the body may contain escaped quotes
|
|
425
|
-
// (`\"` or `""`). Stops at the first unescaped closing quote so nested
|
|
426
|
-
// patterns like `"... \"inner\" ..."` survive intact. Common PowerShell
|
|
427
|
-
// flags (NoProfile, NonInteractive, WindowStyle, ExecutionPolicy, Sta,
|
|
428
|
-
// Mta, NoLogo, NoExit) are recognised so they don't break the match.
|
|
429
|
-
// Single-quoted -Command '<body>' is also covered.
|
|
430
|
-
const _POWERSHELL_FLAGS_RE = /\s+-(?:NoProfile|NonInteractive|WindowStyle\s+\S+|ExecutionPolicy\s+\S+|Sta|Mta|NoLogo|NoExit)/.source;
|
|
431
|
-
const _POWERSHELL_DOUBLE_RE = new RegExp(
|
|
432
|
-
'\\b(powershell(?:\\.exe)?|pwsh(?:\\.exe)?)((?:' + _POWERSHELL_FLAGS_RE + ')*)\\s+(?:-Command|-c)\\s+"((?:[^"\\\\]|\\\\.|"")+?)"(?=\\s|$|;|&&|\\|\\|)',
|
|
433
|
-
'gi',
|
|
434
|
-
);
|
|
435
|
-
const _POWERSHELL_SINGLE_RE = new RegExp(
|
|
436
|
-
"\\b(powershell(?:\\.exe)?|pwsh(?:\\.exe)?)((?:" + _POWERSHELL_FLAGS_RE + ")*)\\s+(?:-Command|-c)\\s+'((?:[^'\\\\]|\\\\.|'')+?)'(?=\\s|$|;|&&|\\|\\|)",
|
|
437
|
-
'gi',
|
|
438
|
-
);
|
|
439
|
-
|
|
440
|
-
export function _maybeEncodePowerShellCommand(command) {
|
|
441
|
-
if (process.platform !== 'win32') return command;
|
|
442
|
-
if (typeof command !== 'string' || command.length === 0) return command;
|
|
443
|
-
const replaceFn = (match, exe, flags, body) => {
|
|
444
|
-
try {
|
|
445
|
-
// Unescape doubled-up quotes the caller used to embed " / ' inside
|
|
446
|
-
// the -Command literal. We're handing the body to powershell as
|
|
447
|
-
// base64 so the outer-shell escaping is no longer needed.
|
|
448
|
-
// Unescape both PowerShell-style doubled quotes (`""` / `''`) AND
|
|
449
|
-
// bash-style backslash-escaped quotes (`\"` / `\'`) since POSIX
|
|
450
|
-
// outer-shell wrappers commonly use backslash form. Without
|
|
451
|
-
// backslash unescape, `pwsh -Command "Get-Process \"foo\""` would
|
|
452
|
-
// base64-encode the literal backslash, breaking inside PowerShell.
|
|
453
|
-
const unescaped = body
|
|
454
|
-
.replace(/""/g, '"')
|
|
455
|
-
.replace(/''/g, "'")
|
|
456
|
-
.replace(/\\"/g, '"')
|
|
457
|
-
.replace(/\\'/g, "'");
|
|
458
|
-
const encoded = Buffer.from(unescaped, 'utf16le').toString('base64');
|
|
459
|
-
const trimmedFlags = (flags || '').replace(/\s+/g, ' ').trim();
|
|
460
|
-
return `${exe}${trimmedFlags ? ' ' + trimmedFlags : ''} -EncodedCommand ${encoded}`;
|
|
461
|
-
} catch {
|
|
462
|
-
return match;
|
|
463
|
-
}
|
|
464
|
-
};
|
|
465
|
-
return command.replace(_POWERSHELL_DOUBLE_RE, replaceFn).replace(_POWERSHELL_SINGLE_RE, replaceFn);
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
function _unescapePowerShellCommandBody(body) {
|
|
469
|
-
return String(body || '')
|
|
470
|
-
.replace(/""/g, '"')
|
|
471
|
-
.replace(/''/g, "'")
|
|
472
|
-
.replace(/\\"/g, '"')
|
|
473
|
-
.replace(/\\'/g, "'");
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
// Extract inline `powershell -Command "…"` bodies for policy scan parity
|
|
477
|
-
// with hard-block normalization (encoded payloads use decodePowerShellEncodedCommand).
|
|
478
|
-
export function extractPowerShellCommandInner(command) {
|
|
479
|
-
if (typeof command !== 'string' || command.length === 0) return [];
|
|
480
|
-
const out = [];
|
|
481
|
-
const push = (body) => {
|
|
482
|
-
const unescaped = _unescapePowerShellCommandBody(body);
|
|
483
|
-
if (unescaped.trim()) out.push(unescaped);
|
|
484
|
-
};
|
|
485
|
-
for (const m of command.matchAll(_POWERSHELL_DOUBLE_RE)) push(m[3]);
|
|
486
|
-
for (const m of command.matchAll(_POWERSHELL_SINGLE_RE)) push(m[3]);
|
|
487
|
-
return out;
|
|
488
|
-
}
|
|
489
|
-
|
|
490
427
|
// One-shot async shell runner. abortSignal optional (session-scoped abort
|
|
491
428
|
// from getAbortSignalForSession in builtin.mjs). Timeout implemented via
|
|
492
429
|
// treeKill so forked grandchildren also come down. Output streams capture
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// PowerShell inline-command normalization + policy-scan extraction. Extracted
|
|
3
|
+
// verbatim from shell-command.mjs (behavior-preserving). shell-command.mjs
|
|
4
|
+
// re-exports _maybeEncodePowerShellCommand / extractPowerShellCommandInner so
|
|
5
|
+
// the module's public export surface is unchanged.
|
|
6
|
+
//
|
|
7
|
+
// On Windows, nested `powershell -Command "<inline>"` invocations can be
|
|
8
|
+
// mangled by an outer shell quoting layer before powershell.exe sees
|
|
9
|
+
// automatic variables (`$_`, `$args`, `$($_.Line)`, etc.). Rewrite
|
|
10
|
+
// `powershell -Command "<inline>"` / `pwsh -Command "<inline>"` to
|
|
11
|
+
// `-EncodedCommand <utf16le-base64>` so the payload stays opaque to the
|
|
12
|
+
// outer shell. Other shells / non-Windows are no-op pass-through.
|
|
13
|
+
// Match -Command "<body>" where the body may contain escaped quotes
|
|
14
|
+
// (`\"` or `""`). Stops at the first unescaped closing quote so nested
|
|
15
|
+
// patterns like `"... \"inner\" ..."` survive intact. Common PowerShell
|
|
16
|
+
// flags (NoProfile, NonInteractive, WindowStyle, ExecutionPolicy, Sta,
|
|
17
|
+
// Mta, NoLogo, NoExit) are recognised so they don't break the match.
|
|
18
|
+
// Single-quoted -Command '<body>' is also covered.
|
|
19
|
+
const _POWERSHELL_FLAGS_RE = /\s+-(?:NoProfile|NonInteractive|WindowStyle\s+\S+|ExecutionPolicy\s+\S+|Sta|Mta|NoLogo|NoExit)/.source;
|
|
20
|
+
const _POWERSHELL_DOUBLE_RE = new RegExp(
|
|
21
|
+
'\\b(powershell(?:\\.exe)?|pwsh(?:\\.exe)?)((?:' + _POWERSHELL_FLAGS_RE + ')*)\\s+(?:-Command|-c)\\s+"((?:[^"\\\\]|\\\\.|"")+?)"(?=\\s|$|;|&&|\\|\\|)',
|
|
22
|
+
'gi',
|
|
23
|
+
);
|
|
24
|
+
const _POWERSHELL_SINGLE_RE = new RegExp(
|
|
25
|
+
"\\b(powershell(?:\\.exe)?|pwsh(?:\\.exe)?)((?:" + _POWERSHELL_FLAGS_RE + ")*)\\s+(?:-Command|-c)\\s+'((?:[^'\\\\]|\\\\.|'')+?)'(?=\\s|$|;|&&|\\|\\|)",
|
|
26
|
+
'gi',
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
export function _maybeEncodePowerShellCommand(command) {
|
|
30
|
+
if (process.platform !== 'win32') return command;
|
|
31
|
+
if (typeof command !== 'string' || command.length === 0) return command;
|
|
32
|
+
const replaceFn = (match, exe, flags, body) => {
|
|
33
|
+
try {
|
|
34
|
+
// Unescape doubled-up quotes the caller used to embed " / ' inside
|
|
35
|
+
// the -Command literal. We're handing the body to powershell as
|
|
36
|
+
// base64 so the outer-shell escaping is no longer needed.
|
|
37
|
+
// Unescape both PowerShell-style doubled quotes (`""` / `''`) AND
|
|
38
|
+
// bash-style backslash-escaped quotes (`\"` / `\'`) since POSIX
|
|
39
|
+
// outer-shell wrappers commonly use backslash form. Without
|
|
40
|
+
// backslash unescape, `pwsh -Command "Get-Process \"foo\""` would
|
|
41
|
+
// base64-encode the literal backslash, breaking inside PowerShell.
|
|
42
|
+
const unescaped = body
|
|
43
|
+
.replace(/""/g, '"')
|
|
44
|
+
.replace(/''/g, "'")
|
|
45
|
+
.replace(/\\"/g, '"')
|
|
46
|
+
.replace(/\\'/g, "'");
|
|
47
|
+
const encoded = Buffer.from(unescaped, 'utf16le').toString('base64');
|
|
48
|
+
const trimmedFlags = (flags || '').replace(/\s+/g, ' ').trim();
|
|
49
|
+
return `${exe}${trimmedFlags ? ' ' + trimmedFlags : ''} -EncodedCommand ${encoded}`;
|
|
50
|
+
} catch {
|
|
51
|
+
return match;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
return command.replace(_POWERSHELL_DOUBLE_RE, replaceFn).replace(_POWERSHELL_SINGLE_RE, replaceFn);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function _unescapePowerShellCommandBody(body) {
|
|
58
|
+
return String(body || '')
|
|
59
|
+
.replace(/""/g, '"')
|
|
60
|
+
.replace(/''/g, "'")
|
|
61
|
+
.replace(/\\"/g, '"')
|
|
62
|
+
.replace(/\\'/g, "'");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Extract inline `powershell -Command "…"` bodies for policy scan parity
|
|
66
|
+
// with hard-block normalization (encoded payloads use decodePowerShellEncodedCommand).
|
|
67
|
+
export function extractPowerShellCommandInner(command) {
|
|
68
|
+
if (typeof command !== 'string' || command.length === 0) return [];
|
|
69
|
+
const out = [];
|
|
70
|
+
const push = (body) => {
|
|
71
|
+
const unescaped = _unescapePowerShellCommandBody(body);
|
|
72
|
+
if (unescaped.trim()) out.push(unescaped);
|
|
73
|
+
};
|
|
74
|
+
for (const m of command.matchAll(_POWERSHELL_DOUBLE_RE)) push(m[3]);
|
|
75
|
+
for (const m of command.matchAll(_POWERSHELL_SINGLE_RE)) push(m[3]);
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Claude Code-style shell state carry-over for the one-shot shell tool.
|
|
3
|
+
//
|
|
4
|
+
// No live shell process is kept. Instead, after each sync command we chain a
|
|
5
|
+
// trailing cwd probe that writes the final working directory to a per-session
|
|
6
|
+
// state file. The next shell call (without an explicit cwd arg) reads that file
|
|
7
|
+
// and starts there — mimicking a persistent terminal's cwd without a PTY.
|
|
8
|
+
//
|
|
9
|
+
// Style mirrors shell-snapshot.mjs: files live under getPluginData(), one file
|
|
10
|
+
// per session key, with a stale-file sweep on module init.
|
|
11
|
+
|
|
12
|
+
import { existsSync, mkdirSync, readFileSync, statSync, utimesSync } from 'node:fs';
|
|
13
|
+
import { readdir as readdirAsync, stat as statAsync, unlink as unlinkAsync } from 'node:fs/promises';
|
|
14
|
+
import { join } from 'node:path';
|
|
15
|
+
import { createHash } from 'node:crypto';
|
|
16
|
+
import { getPluginData } from '../config.mjs';
|
|
17
|
+
|
|
18
|
+
function _stateDir() {
|
|
19
|
+
return join(getPluginData(), 'shell-state');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// One file per session key. Hash the key so arbitrary session ids (which may
|
|
23
|
+
// contain path-hostile chars) map to a safe, collision-resistant filename.
|
|
24
|
+
function _keyFor(sessionKey) {
|
|
25
|
+
const raw = String(sessionKey == null || sessionKey === '' ? '__default__' : sessionKey);
|
|
26
|
+
return createHash('sha1').update(raw).digest('hex').slice(0, 16);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function _stateFileFor(sessionKey) {
|
|
30
|
+
return join(_stateDir(), `cwd-${_keyFor(sessionKey)}.txt`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Stale-file sweep on module init. Mirrors shell-snapshot's _sweepStaleSnapshots:
|
|
34
|
+
// state files older than the cutoff belong to sessions that are long gone.
|
|
35
|
+
const STATE_STALE_MS = 24 * 60 * 60 * 1000;
|
|
36
|
+
async function _sweepStaleState() {
|
|
37
|
+
const dir = _stateDir();
|
|
38
|
+
if (!existsSync(dir)) return;
|
|
39
|
+
const cutoff = Date.now() - STATE_STALE_MS;
|
|
40
|
+
let names;
|
|
41
|
+
try { names = await readdirAsync(dir); } catch { return; }
|
|
42
|
+
await Promise.all(names.map(async (name) => {
|
|
43
|
+
if (!name.startsWith('cwd-') || !name.endsWith('.txt')) return;
|
|
44
|
+
const p = join(dir, name);
|
|
45
|
+
try {
|
|
46
|
+
const st = await statAsync(p);
|
|
47
|
+
if (st.mtimeMs < cutoff) {
|
|
48
|
+
// Tolerate the stat→unlink race: a concurrent write may have
|
|
49
|
+
// refreshed mtime, or another sweeper may have already removed
|
|
50
|
+
// it. ENOENT and any transient error are non-fatal.
|
|
51
|
+
try { await unlinkAsync(p); } catch (err) { if (err && err.code !== 'ENOENT') { /* swallow */ } }
|
|
52
|
+
}
|
|
53
|
+
} catch {}
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
56
|
+
_sweepStaleState();
|
|
57
|
+
|
|
58
|
+
// Resolve the effective cwd for a shell call. Explicit cwd always wins (the
|
|
59
|
+
// caller resolved/validated it already). Otherwise, if a stored cwd exists and
|
|
60
|
+
// still points at a live directory, use it; else fall back to defaultCwd.
|
|
61
|
+
export function resolveSessionCwd(sessionKey, explicitCwd, defaultCwd) {
|
|
62
|
+
if (explicitCwd) return explicitCwd;
|
|
63
|
+
// No nonblank session key → no carry-over (mirrors stateFilePath). Without a
|
|
64
|
+
// real key every caller would share the `__default__` file and read each
|
|
65
|
+
// other's cwd, so skip the store entirely and use the default.
|
|
66
|
+
if (sessionKey == null || String(sessionKey).trim() === '') return defaultCwd;
|
|
67
|
+
try {
|
|
68
|
+
const file = _stateFileFor(sessionKey);
|
|
69
|
+
if (!existsSync(file)) return defaultCwd;
|
|
70
|
+
const stored = readFileSync(file, 'utf-8').trim();
|
|
71
|
+
if (!stored) return defaultCwd;
|
|
72
|
+
const st = statSync(stored);
|
|
73
|
+
if (st.isDirectory()) return stored;
|
|
74
|
+
} catch {}
|
|
75
|
+
return defaultCwd;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Path to the state file for this session (created lazily). Returned so the
|
|
79
|
+
// caller can embed it in the probe chain. Returns null if the dir can't be made
|
|
80
|
+
// OR when there is no nonblank session key — without a real key all callers
|
|
81
|
+
// would collapse onto a single shared `__default__` file and cross-contaminate
|
|
82
|
+
// each other's cwd, so we skip carry-over entirely in that case.
|
|
83
|
+
export function stateFilePath(sessionKey) {
|
|
84
|
+
if (sessionKey == null || String(sessionKey).trim() === '') return null;
|
|
85
|
+
const dir = _stateDir();
|
|
86
|
+
try { mkdirSync(dir, { recursive: true }); } catch { return null; }
|
|
87
|
+
const file = _stateFileFor(sessionKey);
|
|
88
|
+
// Refresh mtime on every write so an old-but-live session file is not
|
|
89
|
+
// reaped by the stale sweep mid-session. `touch`-equivalent: bump atime/
|
|
90
|
+
// mtime to now if the file already exists; a fresh file gets its mtime on
|
|
91
|
+
// first write by the probe redirection.
|
|
92
|
+
try {
|
|
93
|
+
if (existsSync(file)) {
|
|
94
|
+
const now = new Date();
|
|
95
|
+
utimesSync(file, now, now);
|
|
96
|
+
}
|
|
97
|
+
} catch {}
|
|
98
|
+
return file;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// PowerShell single-quote escape ('' escapes a literal quote inside '...').
|
|
102
|
+
function _psQuote(s) {
|
|
103
|
+
return `'${String(s).replace(/'/g, "''")}'`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// POSIX single-quote escape.
|
|
107
|
+
function _shQuote(s) {
|
|
108
|
+
return `'${String(s).replace(/'/g, "'\\''")}'`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Append a trailing cwd probe so the final working directory is persisted after
|
|
112
|
+
// the user command runs. The probe is joined with `;` (statement separator, not
|
|
113
|
+
// `&&`) so it runs regardless of the command's exit status, and it does not
|
|
114
|
+
// touch the exit code the runtime reports: the runtime reads the child's exit
|
|
115
|
+
// code, and neither `Out-File` nor a redirection changes `$?`/`$LASTEXITCODE`
|
|
116
|
+
// semantics observed by the *process* exit — the shell's exit status is the
|
|
117
|
+
// last statement's, but see wrapWithCwdProbe callers which capture the code
|
|
118
|
+
// BEFORE the probe. For PowerShell we snapshot $LASTEXITCODE/$? and restore the
|
|
119
|
+
// process exit via `exit` so the probe cannot mask a non-zero command.
|
|
120
|
+
export function wrapPowerShellWithCwdProbe(command, stateFile) {
|
|
121
|
+
if (!stateFile) return command;
|
|
122
|
+
const qFile = _psQuote(stateFile);
|
|
123
|
+
// $?-driven, bash-like exit semantics. $LASTEXITCODE persists the last
|
|
124
|
+
// *native* process's code indefinitely, so a prologue reset cannot cover
|
|
125
|
+
// intra-command staleness (`cmd /c exit 5; Write-Output ok` leaves the
|
|
126
|
+
// trailing cmdlet succeeding but $LASTEXITCODE still 5). Instead we mirror
|
|
127
|
+
// bash: the exit code is the LAST statement's success. $? immediately after
|
|
128
|
+
// the command reflects that statement — false only when it failed, and for
|
|
129
|
+
// a failing native call $LASTEXITCODE holds its real code (case3 → 7).
|
|
130
|
+
//
|
|
131
|
+
// The `if (-not $?)` check MUST be the first statement after the command;
|
|
132
|
+
// any intervening statement resets $?. Terminating errors (`throw`) skip to
|
|
133
|
+
// catch → $__ec = 1. The cwd probe runs in finally regardless, and we exit
|
|
134
|
+
// with $__ec so the probe never masks the observed code.
|
|
135
|
+
return '$global:LASTEXITCODE = 0\n'
|
|
136
|
+
+ '$__ec = 0\n'
|
|
137
|
+
+ 'try {\n'
|
|
138
|
+
+ `${command}\n`
|
|
139
|
+
+ ' if (-not $?) { $__ec = if ($LASTEXITCODE) { $LASTEXITCODE } else { 1 } }\n'
|
|
140
|
+
+ '} catch { $__ec = 1 }\n'
|
|
141
|
+
+ `finally { (Get-Location).Path | Out-File -Encoding utf8 ${qFile} }\n`
|
|
142
|
+
+ 'exit $__ec';
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function wrapBashWithCwdProbe(command, stateFile) {
|
|
146
|
+
if (!stateFile) return command;
|
|
147
|
+
const qFile = _shQuote(stateFile);
|
|
148
|
+
// Capture $? before the probe, run the probe unconditionally (`;`), then
|
|
149
|
+
// re-exit with the saved status so `pwd`/redirection cannot mask it.
|
|
150
|
+
return `${command}\n`
|
|
151
|
+
+ '__mixdog_ec=$?;\n'
|
|
152
|
+
+ `pwd -P >| ${qFile} 2>/dev/null || true;\n`
|
|
153
|
+
+ 'exit $__mixdog_ec';
|
|
154
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Access-config helpers extracted from discord.mjs. Pure functions with no
|
|
2
|
+
// dependency on the backend instance; behavior is identical to the originals.
|
|
3
|
+
|
|
4
|
+
function defaultAccess() {
|
|
5
|
+
return {
|
|
6
|
+
dmPolicy: "allowlist",
|
|
7
|
+
allowFrom: [],
|
|
8
|
+
channels: {}
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
function normalizeAccess(parsed) {
|
|
12
|
+
const defaults = defaultAccess();
|
|
13
|
+
return {
|
|
14
|
+
// Legacy "pairing" policy was removed (its approval flow was never
|
|
15
|
+
// completable); normalize it to "allowlist" on load.
|
|
16
|
+
dmPolicy: parsed?.dmPolicy === "pairing" ? "allowlist" : (parsed?.dmPolicy ?? defaults.dmPolicy),
|
|
17
|
+
allowFrom: parsed?.allowFrom ?? defaults.allowFrom,
|
|
18
|
+
channels: parsed?.channels ?? defaults.channels,
|
|
19
|
+
mentionPatterns: parsed?.mentionPatterns,
|
|
20
|
+
// Setup UI historically saved a boolean toggle; runtime needs an emoji
|
|
21
|
+
// string for msg.react(). true → default emoji, non-string → off.
|
|
22
|
+
ackReaction: parsed?.ackReaction === true
|
|
23
|
+
? "✅"
|
|
24
|
+
: (typeof parsed?.ackReaction === "string" && parsed.ackReaction ? parsed.ackReaction : undefined),
|
|
25
|
+
replyToMode: parsed?.replyToMode,
|
|
26
|
+
textChunkLimit: parsed?.textChunkLimit,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function safeAttName(att) {
|
|
30
|
+
return (att.name ?? att.id).replace(/[\[\]\r\n;]/g, "_");
|
|
31
|
+
}
|
|
32
|
+
export { defaultAccess, normalizeAccess, safeAttName };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Attachment download + streaming size-guard extracted from discord.mjs.
|
|
2
|
+
// Standalone function; takes inboxDir explicitly instead of `this`.
|
|
3
|
+
import { writeFileSync, mkdirSync, realpathSync } from "fs";
|
|
4
|
+
import { join, sep } from "path";
|
|
5
|
+
|
|
6
|
+
const MAX_ATTACHMENT_BYTES = 25 * 1024 * 1024;
|
|
7
|
+
|
|
8
|
+
async function downloadSingleAttachment(att, inboxDir) {
|
|
9
|
+
if (att.size > MAX_ATTACHMENT_BYTES) {
|
|
10
|
+
throw new Error(
|
|
11
|
+
`attachment too large: ${(att.size / 1024 / 1024).toFixed(1)}MB, max ${MAX_ATTACHMENT_BYTES / 1024 / 1024}MB`
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
const res = await fetch(att.url, { signal: AbortSignal.timeout(180_000) });
|
|
15
|
+
if (!res.ok) {
|
|
16
|
+
throw new Error(`attachment download failed: HTTP ${res.status}`);
|
|
17
|
+
}
|
|
18
|
+
if (!res.body) {
|
|
19
|
+
throw new Error(`attachment download returned empty body: ${att.name ?? att.id}`);
|
|
20
|
+
}
|
|
21
|
+
// Stream the response so an oversized payload is aborted before the
|
|
22
|
+
// full body lands in memory. Buffering via arrayBuffer() first would
|
|
23
|
+
// already exceed MAX_ATTACHMENT_BYTES by the time we checked length.
|
|
24
|
+
const reader = res.body.getReader();
|
|
25
|
+
const chunks = [];
|
|
26
|
+
let received = 0;
|
|
27
|
+
try {
|
|
28
|
+
while (true) {
|
|
29
|
+
const { done, value } = await reader.read();
|
|
30
|
+
if (done) break;
|
|
31
|
+
if (!value) continue;
|
|
32
|
+
received += value.byteLength;
|
|
33
|
+
if (received > MAX_ATTACHMENT_BYTES) {
|
|
34
|
+
try { await reader.cancel(); } catch {}
|
|
35
|
+
throw new Error(
|
|
36
|
+
`attachment payload too large: exceeded ${MAX_ATTACHMENT_BYTES / 1024 / 1024}MB while streaming (${att.name ?? att.id})`
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
chunks.push(value);
|
|
40
|
+
}
|
|
41
|
+
} finally {
|
|
42
|
+
try { reader.releaseLock(); } catch {}
|
|
43
|
+
}
|
|
44
|
+
if (received === 0) {
|
|
45
|
+
throw new Error(`attachment download returned empty buffer: ${att.name ?? att.id}`);
|
|
46
|
+
}
|
|
47
|
+
const buf = Buffer.concat(chunks.map((c) => Buffer.from(c.buffer, c.byteOffset, c.byteLength)), received);
|
|
48
|
+
if (att.size > 0 && buf.length !== att.size) {
|
|
49
|
+
process.stderr.write(`mixdog discord: attachment size mismatch: expected ${att.size} got ${buf.length} (${att.name ?? att.id})\n`);
|
|
50
|
+
}
|
|
51
|
+
const name = att.name ?? `${att.id}`;
|
|
52
|
+
const rawExt = name.includes(".") ? name.slice(name.lastIndexOf(".") + 1) : "bin";
|
|
53
|
+
const ext = rawExt.replace(/[^a-zA-Z0-9]/g, "") || "bin";
|
|
54
|
+
const safeId = String(att.id).replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
55
|
+
mkdirSync(inboxDir, { recursive: true });
|
|
56
|
+
const candidate = join(inboxDir, `${Date.now()}-${safeId}.${ext}`);
|
|
57
|
+
const resolvedInbox = realpathSync(inboxDir);
|
|
58
|
+
if (!candidate.startsWith(resolvedInbox + sep)) {
|
|
59
|
+
throw new Error(`attachment path traversal rejected: ${candidate}`);
|
|
60
|
+
}
|
|
61
|
+
writeFileSync(candidate, buf);
|
|
62
|
+
return candidate;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export { MAX_ATTACHMENT_BYTES, downloadSingleAttachment };
|