mixdog 0.8.1 → 0.9.1
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 +47 -23
- package/package.json +34 -28
- package/scripts/_test-folder-dialog.mjs +30 -0
- package/scripts/agent-parallel-smoke.mjs +388 -0
- package/scripts/agent-tag-reuse-smoke.mjs +183 -0
- package/scripts/background-task-meta-smoke.mjs +38 -0
- package/scripts/boot-smoke.mjs +52 -9
- package/scripts/build-runtime-linux.sh +348 -0
- package/scripts/build-runtime-macos.sh +217 -0
- package/scripts/build-runtime-windows.ps1 +242 -0
- package/scripts/compact-active-turn-test.mjs +68 -0
- package/scripts/compact-smoke.mjs +859 -129
- package/scripts/compact-trigger-migration-smoke.mjs +187 -0
- package/scripts/fix-brief-fn.mjs +35 -0
- package/scripts/fix-format-tool-surface.mjs +24 -0
- package/scripts/fix-tool-exec-visible.mjs +42 -0
- package/scripts/generate-runtime-manifest.mjs +166 -0
- package/scripts/hook-bus-test.mjs +330 -0
- package/scripts/lead-workflow-smoke.mjs +33 -39
- package/scripts/live-worker-smoke.mjs +43 -37
- package/scripts/llm-trace-summary.mjs +315 -0
- package/scripts/memory-meta-concurrency-test.mjs +20 -0
- package/scripts/output-style-smoke.mjs +56 -15
- package/scripts/parent-abort-link-test.mjs +44 -0
- package/scripts/patch-agent-brief.mjs +48 -0
- package/scripts/patch-app.mjs +21 -0
- package/scripts/patch-app2.mjs +18 -0
- package/scripts/patch-dist-brief.mjs +96 -0
- package/scripts/patch-tool-exec.mjs +70 -0
- package/scripts/pretool-ask-runtime-test.mjs +54 -0
- package/scripts/provider-toolcall-test.mjs +376 -0
- package/scripts/reactive-compact-persist-smoke.mjs +124 -0
- package/scripts/sanitize-tool-pairs-test.mjs +260 -0
- package/scripts/session-context-bench.mjs +205 -33
- package/scripts/session-ingest-smoke.mjs +177 -0
- package/scripts/set-effort-config-test.mjs +41 -0
- package/scripts/smoke-runtime-negative.ps1 +106 -0
- package/scripts/smoke-runtime-negative.sh +97 -0
- package/scripts/smoke.mjs +25 -0
- package/scripts/tool-result-hook-test.mjs +48 -0
- package/scripts/tool-smoke.mjs +1222 -90
- package/scripts/toolcall-args-test.mjs +150 -0
- package/scripts/tui-background-failure-smoke.mjs +73 -0
- package/scripts/usage-metrics-epoch-smoke.mjs +114 -0
- package/src/agents/debugger/AGENT.md +8 -0
- package/src/agents/explore/AGENT.md +4 -0
- package/src/agents/heavy-worker/AGENT.md +9 -3
- package/src/agents/maintainer/AGENT.md +4 -0
- package/src/agents/reviewer/AGENT.md +8 -0
- package/src/agents/scheduler-task/AGENT.md +12 -0
- package/src/agents/scheduler-task/agent.json +6 -0
- package/src/agents/webhook-handler/AGENT.md +12 -0
- package/src/agents/webhook-handler/agent.json +6 -0
- package/src/agents/worker/AGENT.md +9 -3
- package/src/app.mjs +77 -3
- package/src/defaults/hidden-roles.json +17 -12
- package/src/headless-role.mjs +117 -0
- package/src/help.mjs +30 -0
- package/src/hooks/lib/permission-evaluator.cjs +11 -475
- package/src/lib/keychain-cjs.cjs +9 -1
- package/src/lib/mixdog-debug.cjs +0 -29
- package/src/lib/plugin-paths.cjs +1 -7
- package/src/lib/rules-builder.cjs +242 -98
- package/src/lib/text-utils.cjs +1 -1
- package/src/mixdog-session-runtime.mjs +2211 -446
- package/src/output-styles/default.md +12 -28
- package/src/output-styles/extreme-simple.md +9 -6
- package/src/output-styles/simple.md +22 -9
- package/src/repl.mjs +108 -59
- package/src/rules/agent/00-common.md +15 -0
- package/src/rules/{bridge → agent}/20-skip-protocol.md +1 -2
- package/src/rules/agent/30-explorer.md +22 -0
- package/src/rules/{bridge → agent}/40-cycle1-agent.md +7 -0
- package/src/rules/{bridge → agent}/41-cycle2-agent.md +7 -0
- package/src/rules/{bridge → agent}/42-cycle3-agent.md +7 -0
- package/src/rules/lead/01-general.md +9 -5
- package/src/rules/lead/04-workflow.md +51 -12
- package/src/rules/lead/lead-tool.md +6 -0
- package/src/rules/shared/01-tool.md +12 -1
- package/src/runtime/agent/orchestrator/activity-bus.mjs +7 -18
- package/src/runtime/agent/orchestrator/agent-owner.mjs +11 -0
- package/src/runtime/agent/orchestrator/{smart-bridge/bridge-llm.mjs → agent-runtime/agent-dispatch.mjs} +138 -111
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +94 -0
- package/src/runtime/agent/orchestrator/{smart-bridge → agent-runtime}/cache-strategy.mjs +32 -23
- package/src/runtime/agent/orchestrator/{smart-bridge → agent-runtime}/session-builder.mjs +33 -27
- package/src/runtime/agent/orchestrator/{bridge-trace.mjs → agent-trace.mjs} +132 -81
- package/src/runtime/agent/orchestrator/cache-mtime.mjs +0 -21
- package/src/runtime/agent/orchestrator/config.mjs +174 -55
- package/src/runtime/agent/orchestrator/context/collect.mjs +195 -487
- package/src/runtime/agent/orchestrator/dispatch-persist.mjs +1 -1
- package/src/runtime/agent/orchestrator/internal-roles.mjs +77 -29
- package/src/runtime/agent/orchestrator/internal-tools.mjs +5 -6
- package/src/runtime/agent/orchestrator/mcp/client.mjs +15 -9
- package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +380 -268
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +131 -93
- package/src/runtime/agent/orchestrator/providers/api-usage.mjs +236 -4
- package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +49 -0
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +58 -13
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +67 -149
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +132 -2
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +45 -0
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +61 -116
- package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +25 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +79 -255
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +203 -71
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +512 -312
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +343 -518
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +6 -6
- package/src/runtime/agent/orchestrator/providers/registry.mjs +88 -51
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +289 -11
- package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +41 -8
- package/src/runtime/agent/orchestrator/session/compact.mjs +1173 -267
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +199 -36
- package/src/runtime/agent/orchestrator/session/loop.mjs +840 -612
- package/src/runtime/agent/orchestrator/session/manager.mjs +1574 -459
- package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +107 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +67 -29
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +2 -2
- package/src/runtime/agent/orchestrator/stall-policy.mjs +31 -16
- package/src/runtime/agent/orchestrator/tool-loop-guard.mjs +3 -219
- package/src/runtime/agent/orchestrator/tools/bash-session.mjs +35 -8
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +19 -8
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +10 -53
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +60 -37
- package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +21 -2
- package/src/runtime/agent/orchestrator/tools/builtin/device-paths.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -7
- package/src/runtime/agent/orchestrator/tools/builtin/glob-walk.mjs +1 -3
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +36 -12
- package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +2 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +5 -12
- package/src/runtime/agent/orchestrator/tools/builtin/read-image-resize.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +4 -36
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +2 -40
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +148 -27
- package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +43 -75
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +90 -20
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +59 -5
- package/src/runtime/agent/orchestrator/tools/code-graph-state.mjs +86 -0
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +11 -11
- package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4106 -4019
- package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +33 -4
- package/src/runtime/agent/orchestrator/tools/patch.mjs +90 -6
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +6 -4
- package/src/runtime/agent/orchestrator/tools/result-compression.mjs +4 -4
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +8 -1
- package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +4 -4
- package/src/runtime/channels/index.mjs +149 -51
- package/src/runtime/channels/lib/cli-worker-host.mjs +1 -8
- package/src/runtime/channels/lib/config.mjs +0 -1
- package/src/runtime/channels/lib/drop-trace.mjs +1 -1
- package/src/runtime/channels/lib/executor.mjs +0 -3
- package/src/runtime/channels/lib/memory-client.mjs +0 -38
- package/src/runtime/channels/lib/output-forwarder.mjs +1 -8
- package/src/runtime/channels/lib/runtime-paths.mjs +0 -6
- package/src/runtime/channels/lib/scheduler.mjs +18 -14
- package/src/runtime/channels/lib/session-discovery.mjs +3 -6
- package/src/runtime/channels/lib/tool-format.mjs +0 -1
- package/src/runtime/channels/lib/transcript-discovery.mjs +4 -12
- package/src/runtime/channels/lib/webhook.mjs +1 -1
- package/src/runtime/channels/tool-defs.mjs +29 -29
- package/src/runtime/lib/keychain-cjs.cjs +0 -1
- package/src/runtime/memory/data/runtime-manifest.json +6 -7
- package/src/runtime/memory/index.mjs +519 -118
- package/src/runtime/memory/lib/agent-ipc.mjs +29 -12
- package/src/runtime/memory/lib/core-memory-store.mjs +2 -2
- package/src/runtime/memory/lib/embedding-model-config.mjs +55 -0
- package/src/runtime/memory/lib/embedding-provider.mjs +31 -4
- package/src/runtime/memory/lib/embedding-worker.mjs +19 -10
- package/src/runtime/memory/lib/llm-worker-host.mjs +0 -4
- package/src/runtime/memory/lib/memory-cycle1.mjs +28 -7
- package/src/runtime/memory/lib/memory-cycle2.mjs +4 -5
- package/src/runtime/memory/lib/memory-cycle3.mjs +2 -2
- package/src/runtime/memory/lib/memory-ops-policy.mjs +2 -2
- package/src/runtime/memory/lib/memory-session-merge.mjs +38 -0
- package/src/runtime/memory/lib/memory.mjs +88 -9
- package/src/runtime/memory/lib/model-profile.mjs +1 -1
- package/src/runtime/memory/lib/pg/adapter.mjs +1 -1
- package/src/runtime/memory/lib/pg/supervisor.mjs +12 -0
- package/src/runtime/memory/lib/runtime-fetcher.mjs +80 -21
- package/src/runtime/memory/lib/session-ingest.mjs +196 -0
- package/src/runtime/memory/lib/trace-store.mjs +96 -51
- package/src/runtime/memory/tool-defs.mjs +46 -37
- package/src/runtime/search/index.mjs +104 -473
- package/src/runtime/search/lib/config.mjs +0 -4
- package/src/runtime/search/lib/state.mjs +1 -15
- package/src/runtime/search/lib/web-tools.mjs +44 -25
- package/src/runtime/search/tool-defs.mjs +16 -23
- package/src/runtime/shared/abort-controller.mjs +1 -1
- package/src/runtime/shared/atomic-file.mjs +4 -3
- package/src/runtime/shared/background-tasks.mjs +122 -11
- package/src/runtime/shared/child-spawn-gate.mjs +139 -0
- package/src/runtime/shared/config.mjs +7 -4
- package/src/runtime/shared/err-text.mjs +131 -4
- package/src/runtime/shared/llm/cost.mjs +2 -2
- package/src/runtime/shared/llm/http-agent.mjs +23 -7
- package/src/runtime/shared/llm/index.mjs +34 -11
- package/src/runtime/shared/llm/usage-log.mjs +4 -4
- package/src/runtime/shared/markdown-frontmatter.mjs +56 -0
- package/src/runtime/shared/singleton-owner.mjs +104 -0
- package/src/runtime/shared/tool-execution-contract.mjs +199 -20
- package/src/runtime/shared/tool-execution-contract.test.mjs +183 -0
- package/src/runtime/shared/tool-surface.mjs +624 -95
- package/src/runtime/shared/user-data-guard.mjs +0 -2
- package/src/standalone/agent-task-status.mjs +203 -0
- package/src/standalone/agent-task-status.test.mjs +76 -0
- package/src/standalone/agent-tool.mjs +1913 -0
- package/src/standalone/channel-worker.mjs +370 -14
- package/src/standalone/explore-tool.mjs +165 -70
- package/src/standalone/folder-dialog.mjs +314 -0
- package/src/standalone/hook-bus.mjs +898 -22
- package/src/standalone/memory-runtime-proxy.mjs +320 -0
- package/src/standalone/projects.mjs +226 -0
- package/src/standalone/provider-admin.mjs +41 -24
- package/src/standalone/seeds.mjs +1 -78
- package/src/standalone/usage-dashboard.mjs +96 -8
- package/src/tui/App.jsx +4800 -2140
- package/src/tui/components/AnsiText.jsx +39 -28
- package/src/tui/components/ContextPanel.jsx +14 -4
- package/src/tui/components/Markdown.jsx +43 -77
- package/src/tui/components/MarkdownTable.jsx +9 -184
- package/src/tui/components/Message.jsx +28 -11
- package/src/tui/components/Picker.jsx +95 -56
- package/src/tui/components/PromptInput.jsx +428 -239
- package/src/tui/components/QueuedCommands.jsx +1 -1
- package/src/tui/components/SlashCommandPalette.jsx +27 -21
- package/src/tui/components/Spinner.jsx +67 -38
- package/src/tui/components/StatusLine.jsx +606 -38
- package/src/tui/components/TextEntryPanel.jsx +128 -9
- package/src/tui/components/ToolExecution.jsx +597 -362
- package/src/tui/components/TurnDone.jsx +3 -3
- package/src/tui/components/UsagePanel.jsx +3 -5
- package/src/tui/components/tool-output-format.mjs +499 -0
- package/src/tui/components/tool-output-format.test.mjs +312 -0
- package/src/tui/dist/index.mjs +9192 -2399
- package/src/tui/engine-runtime-notification.test.mjs +115 -0
- package/src/tui/engine-tool-result-text.test.mjs +75 -0
- package/src/tui/engine.mjs +1455 -279
- package/src/tui/figures.mjs +21 -40
- package/src/tui/index.jsx +75 -31
- package/src/tui/input-editing.mjs +25 -0
- package/src/tui/markdown/format-token.mjs +671 -69
- package/src/tui/markdown/format-token.test.mjs +312 -0
- package/src/tui/markdown/render-ansi.mjs +94 -0
- package/src/tui/markdown/render-ansi.test.mjs +108 -0
- package/src/tui/markdown/stream-fence.mjs +34 -0
- package/src/tui/markdown/stream-fence.test.mjs +26 -0
- package/src/tui/markdown/table-layout.mjs +250 -0
- package/src/tui/paste-attachments.mjs +0 -7
- package/src/tui/spinner-verbs.mjs +1 -2
- package/src/tui/statusline-ansi-bridge.mjs +172 -0
- package/src/tui/statusline-ansi-bridge.test.mjs +159 -0
- package/src/tui/theme.mjs +756 -24
- package/src/tui/time-format.mjs +1 -1
- package/src/tui/transcript-tool-failures.mjs +67 -0
- package/src/tui/transcript-tool-failures.test.mjs +111 -0
- package/src/ui/ansi.mjs +1 -2
- package/src/ui/markdown.mjs +85 -26
- package/src/ui/markdown.test.mjs +70 -0
- package/src/ui/model-display.mjs +121 -0
- package/src/ui/session-stats.mjs +44 -0
- package/src/ui/statusline-context-label.test.mjs +15 -0
- package/src/ui/statusline.mjs +386 -178
- package/src/ui/tool-card.mjs +2 -13
- package/src/vendor/statusline/bin/statusline-lib.mjs +1 -620
- package/src/vendor/statusline/bin/statusline-route.mjs +169 -37
- package/src/vendor/statusline/bin/statusline-route.test.mjs +80 -0
- package/src/vendor/statusline/scripts/lib/gateway-settings.mjs +3 -3
- package/src/vendor/statusline/src/gateway/claude-current.mjs +1 -1
- package/src/vendor/statusline/src/gateway/route-meta.mjs +44 -6
- package/src/vendor/statusline/src/gateway/session-routes.mjs +1 -1
- package/src/workflows/default/WORKFLOW.md +12 -5
- package/src/workflows/default/workflow.json +0 -1
- package/src/workflows/solo/WORKFLOW.md +15 -0
- package/src/workflows/solo/workflow.json +7 -0
- package/vendor/ink/build/ink.js +54 -8
- package/vendor/ink/build/output.js +6 -1
- package/src/agents/scheduler-task.md +0 -3
- package/src/agents/web-researcher/AGENT.md +0 -3
- package/src/agents/web-researcher/agent.json +0 -6
- package/src/agents/webhook-handler.md +0 -3
- package/src/hooks/lib/permission-rules.cjs +0 -170
- package/src/hooks/lib/settings-loader.cjs +0 -112
- package/src/lib/hook-pipe-path.cjs +0 -10
- package/src/rules/bridge/00-common.md +0 -5
- package/src/rules/bridge/30-explorer.md +0 -4
- package/src/rules/lead/00-tool-lead.md +0 -5
- package/src/rules/shared/00-language.md +0 -3
- package/src/runtime/agent/orchestrator/tools/builtin/native-edit-runner.mjs +0 -110
- package/src/runtime/agent/orchestrator/tools/mutation-content-cache.mjs +0 -67
- package/src/runtime/channels/lib/hook-pipe-server.mjs +0 -671
- package/src/runtime/memory/lib/bridge-trace-queries.mjs +0 -120
- package/src/runtime/shared/llm/pid-cleanup.mjs +0 -27
- package/src/standalone/bridge-tool.mjs +0 -1414
- package/src/tui/runtime/shared/process-shutdown.mjs +0 -1
package/vendor/ink/build/ink.js
CHANGED
|
@@ -181,6 +181,11 @@ export default class Ink {
|
|
|
181
181
|
// cells ({ x1, y1, x2, y2 } inclusive, normalized) or null. The App sets it
|
|
182
182
|
// via setSelection(); onRender forwards it to the renderer for highlighting.
|
|
183
183
|
selectionRect = null;
|
|
184
|
+
// [mixdog fork] Coalesce drag-selection repaints when a frame is pending.
|
|
185
|
+
isRendering = false;
|
|
186
|
+
selectionRepaintQueued = false;
|
|
187
|
+
selectionRepaintFlushPending = false;
|
|
188
|
+
selectionRepaintEpoch = 0;
|
|
184
189
|
// [mixdog fork] text under the current selection rect, refreshed every render
|
|
185
190
|
// from the output grid. Read back via getSelectionText() on drag-release.
|
|
186
191
|
selectedText = null;
|
|
@@ -318,9 +323,9 @@ export default class Ink {
|
|
|
318
323
|
this.log.setCursorPosition(position);
|
|
319
324
|
};
|
|
320
325
|
// [mixdog fork] Update the mouse drag-selection rectangle and repaint so the
|
|
321
|
-
// inverse highlight tracks the drag
|
|
326
|
+
// inverse highlight tracks the drag. Called by the App's mouse handler.
|
|
322
327
|
// A no-op-equal update is skipped to avoid redundant frames during motion.
|
|
323
|
-
setSelection = (rect) => {
|
|
328
|
+
setSelection = (rect, options = {}) => {
|
|
324
329
|
const a = this.selectionRect;
|
|
325
330
|
const same = a === rect ||
|
|
326
331
|
(a && rect &&
|
|
@@ -333,16 +338,47 @@ export default class Ink {
|
|
|
333
338
|
a.clipY2 === rect.clipY2 &&
|
|
334
339
|
a.captureText === rect.captureText);
|
|
335
340
|
if (same) {
|
|
336
|
-
|
|
341
|
+
if (!options.immediate) {
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
this.selectionRect = rect ?? null;
|
|
337
347
|
}
|
|
338
|
-
this.selectionRect = rect ?? null;
|
|
339
348
|
if (!this.isUnmounted) {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
349
|
+
if (options.immediate) {
|
|
350
|
+
this.selectionRepaintEpoch++;
|
|
351
|
+
this.selectionRepaintFlushPending = false;
|
|
352
|
+
this.rootNode.onImmediateRender();
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
this.scheduleSelectionRepaint();
|
|
344
356
|
}
|
|
345
357
|
};
|
|
358
|
+
scheduleSelectionRepaint = () => {
|
|
359
|
+
if (this.hasPendingThrottledRender) {
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
if (this.isRendering) {
|
|
363
|
+
this.selectionRepaintQueued = true;
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
if (this.selectionRepaintFlushPending) {
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
this.selectionRepaintFlushPending = true;
|
|
370
|
+
const epoch = ++this.selectionRepaintEpoch;
|
|
371
|
+
queueMicrotask(() => {
|
|
372
|
+
this.selectionRepaintFlushPending = false;
|
|
373
|
+
if (epoch !== this.selectionRepaintEpoch || this.isUnmounted) {
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
if (this.hasPendingThrottledRender) {
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
this.rootNode.onImmediateRender();
|
|
380
|
+
});
|
|
381
|
+
};
|
|
346
382
|
// [mixdog fork] Given a 0-based cell (x, y), return the inclusive rect of the
|
|
347
383
|
// word (maximal run of non-whitespace cells) on that single row, or null if
|
|
348
384
|
// the cell is whitespace/empty or out of range. Reuses the cached cell-value
|
|
@@ -401,6 +437,8 @@ export default class Ink {
|
|
|
401
437
|
this.nextRenderCommit.resolve();
|
|
402
438
|
this.nextRenderCommit = undefined;
|
|
403
439
|
}
|
|
440
|
+
this.isRendering = true;
|
|
441
|
+
try {
|
|
404
442
|
const startTime = performance.now();
|
|
405
443
|
const { output, outputHeight, staticOutput, cursor, selectedText, plainRows } = render(this.rootNode, this.isScreenReaderEnabled, this.selectionRect);
|
|
406
444
|
// [mixdog fork] Cache the text under the current selection rect so the App
|
|
@@ -488,6 +526,14 @@ export default class Ink {
|
|
|
488
526
|
this.fullStaticOutput += staticOutput;
|
|
489
527
|
}
|
|
490
528
|
this.renderInteractiveFrame(output, outputHeight, hasStaticOutput ? staticOutput : '');
|
|
529
|
+
}
|
|
530
|
+
finally {
|
|
531
|
+
this.isRendering = false;
|
|
532
|
+
if (this.selectionRepaintQueued) {
|
|
533
|
+
this.selectionRepaintQueued = false;
|
|
534
|
+
this.scheduleSelectionRepaint();
|
|
535
|
+
}
|
|
536
|
+
}
|
|
491
537
|
};
|
|
492
538
|
render(node) {
|
|
493
539
|
const tree = (React.createElement(AccessibilityContext.Provider, { value: { isScreenReaderEnabled: this.isScreenReaderEnabled } },
|
|
@@ -308,7 +308,12 @@ export default class Output {
|
|
|
308
308
|
// [mixdog fork] Snapshot per-row, column-indexed cell values so the App
|
|
309
309
|
// can compute word boundaries (double-click select) without retaining
|
|
310
310
|
// this Output instance, which is created fresh per render and discarded.
|
|
311
|
-
|
|
311
|
+
// ALWAYS build this, even while a selection rect is active: gating it on
|
|
312
|
+
// `!sel` froze the snapshot at the frame the first selection appeared, so
|
|
313
|
+
// every later double-click read STALE cell rows (wrong/empty words) until
|
|
314
|
+
// the selection cleared. Applying the selection above only rewrites cell
|
|
315
|
+
// `styles`, never `value`, so reading `value` here stays correct.
|
|
316
|
+
const plainRows = output.map((row) => (row || []).map((cell) => (cell?.value ?? '')));
|
|
312
317
|
const generatedOutput = output
|
|
313
318
|
.map(line => {
|
|
314
319
|
// See https://github.com/vadimdemedes/ink/pull/564#issuecomment-1637022742
|
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
/**
|
|
3
|
-
* permission-rules.cjs
|
|
4
|
-
* Pattern matcher and priority evaluator. Priority order and Tool(content)
|
|
5
|
-
* parsing mirror Claude Code native logic; specifier matching uses a local
|
|
6
|
-
* path/command glob (native delegates per-tool content matching to each tool).
|
|
7
|
-
*
|
|
8
|
-
* Priority (highest → lowest): deny > ask > allow > mode default
|
|
9
|
-
*
|
|
10
|
-
* Pattern forms:
|
|
11
|
-
* mcp__* → toolName starts with "mcp__"
|
|
12
|
-
* ToolName → exact toolName match (no parens)
|
|
13
|
-
* ToolName(*) → toolName matches AND specifier glob matches any
|
|
14
|
-
* ToolName(specifier) → toolName matches AND specifier glob matches
|
|
15
|
-
* toolInput.path / .command / .file
|
|
16
|
-
* Glob chars: * (any chars within segment), ** (any segments)
|
|
17
|
-
*
|
|
18
|
-
* No external deps; no eval/Function.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
// Read-only tool set (mixdog tool names).
|
|
22
|
-
const READ_ONLY_TOOLS = new Set([
|
|
23
|
-
'read', 'list', 'glob', 'grep', 'find_symbol', 'find_references', 'find_callers', 'explore',
|
|
24
|
-
'recall', 'search', 'fetch', 'web_fetch',
|
|
25
|
-
'schedule_status', 'list_models', 'job_wait', 'download_attachment',
|
|
26
|
-
]);
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Find the index of the first/last UNescaped occurrence of a char.
|
|
30
|
-
* A char is escaped when preceded by an odd number of backslashes.
|
|
31
|
-
* Mirrors native permissionRuleParser.findFirst/LastUnescapedChar.
|
|
32
|
-
*/
|
|
33
|
-
function findFirstUnescapedChar(str, char) {
|
|
34
|
-
for (let i = 0; i < str.length; i++) {
|
|
35
|
-
if (str[i] !== char) continue;
|
|
36
|
-
let bs = 0, j = i - 1;
|
|
37
|
-
while (j >= 0 && str[j] === '\\') { bs++; j--; }
|
|
38
|
-
if (bs % 2 === 0) return i;
|
|
39
|
-
}
|
|
40
|
-
return -1;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function findLastUnescapedChar(str, char) {
|
|
44
|
-
for (let i = str.length - 1; i >= 0; i--) {
|
|
45
|
-
if (str[i] !== char) continue;
|
|
46
|
-
let bs = 0, j = i - 1;
|
|
47
|
-
while (j >= 0 && str[j] === '\\') { bs++; j--; }
|
|
48
|
-
if (bs % 2 === 0) return i;
|
|
49
|
-
}
|
|
50
|
-
return -1;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** Reverse escapeRuleContent: \( -> (, \) -> ), \\ -> \ (order matters). */
|
|
54
|
-
function unescapeRuleContent(content) {
|
|
55
|
-
return content
|
|
56
|
-
.replace(/\\\(/g, '(')
|
|
57
|
-
.replace(/\\\)/g, ')')
|
|
58
|
-
.replace(/\\\\/g, '\\');
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Convert a glob pattern string to a RegExp.
|
|
63
|
-
* Supports: * (non-slash wildcard), ** (any), ? (single char).
|
|
64
|
-
* Safe: no eval.
|
|
65
|
-
*/
|
|
66
|
-
function globToRegex(glob) {
|
|
67
|
-
let src = '';
|
|
68
|
-
let i = 0;
|
|
69
|
-
while (i < glob.length) {
|
|
70
|
-
const ch = glob[i];
|
|
71
|
-
if (ch === '*' && glob[i + 1] === '*') {
|
|
72
|
-
src += '.*';
|
|
73
|
-
i += 2;
|
|
74
|
-
if (glob[i] === '/') i++; // consume trailing slash
|
|
75
|
-
} else if (ch === '*') {
|
|
76
|
-
src += '[^/\\\\]*';
|
|
77
|
-
i++;
|
|
78
|
-
} else if (ch === '?') {
|
|
79
|
-
src += '[^/\\\\]';
|
|
80
|
-
i++;
|
|
81
|
-
} else {
|
|
82
|
-
// Escape regex metacharacters
|
|
83
|
-
src += ch.replace(/[.+^${}()|[\]\\]/g, '\\$&');
|
|
84
|
-
i++;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
return new RegExp('^' + src + '$', 'i');
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Match a single pattern entry against (toolName, toolInput).
|
|
92
|
-
* Returns true if the pattern applies.
|
|
93
|
-
*/
|
|
94
|
-
function matchesPattern(pattern, toolName, toolInput) {
|
|
95
|
-
if (typeof pattern !== 'string') return false;
|
|
96
|
-
|
|
97
|
-
// Detect Tool(specifier) form. Escape-aware, mirroring native
|
|
98
|
-
// permissionRuleValueFromString: only UNescaped parens delimit the
|
|
99
|
-
// specifier, and the close paren must be the final char.
|
|
100
|
-
const parenIdx = findFirstUnescapedChar(pattern, '(');
|
|
101
|
-
const closeIdx = findLastUnescapedChar(pattern, ')');
|
|
102
|
-
if (parenIdx > 0 && closeIdx === pattern.length - 1 && closeIdx > parenIdx) {
|
|
103
|
-
const namePart = pattern.slice(0, parenIdx);
|
|
104
|
-
const rawSpecifier = pattern.slice(parenIdx + 1, closeIdx);
|
|
105
|
-
|
|
106
|
-
if (toolName !== namePart) return false;
|
|
107
|
-
|
|
108
|
-
// Empty "()" or wildcard "(*)" specifier → tool-wide rule, match any
|
|
109
|
-
if (rawSpecifier === '' || rawSpecifier === '*') return true;
|
|
110
|
-
|
|
111
|
-
// Unescape \( \) before glob matching
|
|
112
|
-
const specifier = unescapeRuleContent(rawSpecifier);
|
|
113
|
-
// Match specifier against known path-bearing fields
|
|
114
|
-
const specRe = globToRegex(specifier);
|
|
115
|
-
const inp = toolInput || {};
|
|
116
|
-
const candidates = [...new Set([
|
|
117
|
-
inp.path, inp.command, inp.file, inp.file_path, inp.base_path, inp.cwd,
|
|
118
|
-
...(Array.isArray(inp.path) ? inp.path.map(p => (p && typeof p === 'object' ? p.path : p)) : []),
|
|
119
|
-
...(Array.isArray(inp.reads) ? inp.reads.map(r => r?.path) : []),
|
|
120
|
-
...(Array.isArray(inp.edits) ? inp.edits.flatMap(e => [e?.path, e?.file_path]) : []),
|
|
121
|
-
...(Array.isArray(inp.writes) ? inp.writes.flatMap(w => [w?.path, w?.file_path]) : []),
|
|
122
|
-
].filter(v => typeof v === 'string'))];
|
|
123
|
-
|
|
124
|
-
return candidates.some(c => specRe.test(c));
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Prefix wildcard: e.g. "mcp__*"
|
|
128
|
-
if (pattern.endsWith('*')) {
|
|
129
|
-
const prefix = pattern.slice(0, -1);
|
|
130
|
-
return toolName.startsWith(prefix);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Glob on toolName itself (may contain * or **)
|
|
134
|
-
if (pattern.includes('*') || pattern.includes('?')) {
|
|
135
|
-
return globToRegex(pattern).test(toolName);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Exact match
|
|
139
|
-
return toolName === pattern;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Evaluate priority rules against a tool call.
|
|
144
|
-
*
|
|
145
|
-
* @param {string} toolName
|
|
146
|
-
* @param {object} toolInput
|
|
147
|
-
* @param {string[]} allowList
|
|
148
|
-
* @param {string[]} denyList
|
|
149
|
-
* @param {string[]} askList
|
|
150
|
-
* @returns {'deny'|'ask'|'allow'|null} null = no list matched (use mode default)
|
|
151
|
-
*/
|
|
152
|
-
function evaluateRules(toolName, toolInput, allowList, denyList, askList) {
|
|
153
|
-
if (denyList.some(p => matchesPattern(p, toolName, toolInput))) return 'deny';
|
|
154
|
-
if (askList.some(p => matchesPattern(p, toolName, toolInput))) return 'ask';
|
|
155
|
-
if (allowList.some(p => matchesPattern(p, toolName, toolInput))) return 'allow';
|
|
156
|
-
return null;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Determine whether a tool is considered read-only.
|
|
161
|
-
* For mcp__ tools: strip the mcp__ prefix chain and check the short tool name.
|
|
162
|
-
*/
|
|
163
|
-
function isReadOnlyTool(toolName) {
|
|
164
|
-
// Strip mcp__plugin_...__ prefix (one or more double-underscore segments)
|
|
165
|
-
const parts = toolName.split('__');
|
|
166
|
-
const shortName = parts[parts.length - 1];
|
|
167
|
-
return READ_ONLY_TOOLS.has(shortName);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
module.exports = { matchesPattern, evaluateRules, isReadOnlyTool, READ_ONLY_TOOLS };
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
/**
|
|
3
|
-
* settings-loader.cjs
|
|
4
|
-
* Loads and merges Mixdog settings from three tiers (lowest → highest):
|
|
5
|
-
* 1. User global: $MIXDOG_CONFIG_DIR/settings.json (or ~/.mixdog/settings.json)
|
|
6
|
-
* 2. Project: $MIXDOG_PROJECT_DIR/.mixdog/settings.json (or cwd/.mixdog/settings.json)
|
|
7
|
-
* 3. Project local: $MIXDOG_PROJECT_DIR/.mixdog/settings.local.json
|
|
8
|
-
*
|
|
9
|
-
* Only `permissions` sub-tree is returned; the rest is ignored.
|
|
10
|
-
* Pure fs/path — no external deps.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
const fs = require('fs');
|
|
14
|
-
const path = require('path');
|
|
15
|
-
const os = require('os');
|
|
16
|
-
|
|
17
|
-
// ── In-process mtime-driven cache ────────────────────────────────────────────
|
|
18
|
-
// Keyed by resolved file path. Each entry: { mtime: number|null, data: any }.
|
|
19
|
-
// Invalidated per-tier independently when mtime changes.
|
|
20
|
-
// TTL: never-expire by default (mtime-driven only).
|
|
21
|
-
const _fileCache = new Map();
|
|
22
|
-
|
|
23
|
-
function readJsonCached(filePath) {
|
|
24
|
-
let entry = _fileCache.get(filePath);
|
|
25
|
-
let mtime = null;
|
|
26
|
-
try { mtime = fs.statSync(filePath).mtimeMs; } catch { /* file absent */ }
|
|
27
|
-
if (entry && entry.mtime === mtime) return entry.data;
|
|
28
|
-
let data = null;
|
|
29
|
-
if (mtime !== null) {
|
|
30
|
-
try { data = JSON.parse(fs.readFileSync(filePath, 'utf8')); } catch { data = null; }
|
|
31
|
-
}
|
|
32
|
-
_fileCache.set(filePath, { mtime, data });
|
|
33
|
-
return data;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/** Clear the entire settings cache (for test isolation). */
|
|
37
|
-
function clearSettingsCache() {
|
|
38
|
-
_fileCache.clear();
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Merge two permissions objects (base then overlay).
|
|
43
|
-
* Arrays are concatenated and de-duped; scalar fields are overwritten.
|
|
44
|
-
*/
|
|
45
|
-
function mergePermissions(base, overlay) {
|
|
46
|
-
if (!overlay) return base || {};
|
|
47
|
-
if (!base) return overlay || {};
|
|
48
|
-
|
|
49
|
-
const merged = Object.assign({}, base);
|
|
50
|
-
|
|
51
|
-
for (const key of ['allow', 'deny', 'ask']) {
|
|
52
|
-
const b = Array.isArray(base[key]) ? base[key] : [];
|
|
53
|
-
const o = Array.isArray(overlay[key]) ? overlay[key] : [];
|
|
54
|
-
const combined = [...b, ...o];
|
|
55
|
-
if (combined.length) merged[key] = [...new Set(combined)];
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// scalar: overlay wins
|
|
59
|
-
if (overlay.defaultMode !== undefined) merged.defaultMode = overlay.defaultMode;
|
|
60
|
-
|
|
61
|
-
return merged;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Load and merge settings from all three tiers.
|
|
66
|
-
* Returns `{ allow: string[], deny: string[], ask: string[], defaultMode: string }`.
|
|
67
|
-
*/
|
|
68
|
-
function loadPermissions(projectDir) {
|
|
69
|
-
const userConfigDir = process.env.MIXDOG_CONFIG_DIR ||
|
|
70
|
-
path.join(os.homedir(), '.mixdog');
|
|
71
|
-
const projDir = projectDir ||
|
|
72
|
-
process.env.MIXDOG_PROJECT_DIR ||
|
|
73
|
-
process.cwd();
|
|
74
|
-
|
|
75
|
-
const userSettings = readJsonCached(path.join(userConfigDir, 'settings.json'));
|
|
76
|
-
const projectSettings = readJsonCached(path.join(projDir, '.mixdog', 'settings.json'));
|
|
77
|
-
const localSettings = readJsonCached(path.join(projDir, '.mixdog', 'settings.local.json'));
|
|
78
|
-
|
|
79
|
-
// Accept permissions from `settings.permissions` (canonical) OR top-level
|
|
80
|
-
// `allow`/`deny`/`ask`/`defaultMode` fields (common user shorthand).
|
|
81
|
-
function extractPerms(s) {
|
|
82
|
-
if (!s) return {};
|
|
83
|
-
const nested = (s.permissions && typeof s.permissions === 'object') ? s.permissions : {};
|
|
84
|
-
const topLevel = {};
|
|
85
|
-
for (const key of ['allow', 'deny', 'ask']) {
|
|
86
|
-
if (Array.isArray(s[key])) topLevel[key] = s[key];
|
|
87
|
-
}
|
|
88
|
-
if (typeof s.defaultMode === 'string') topLevel.defaultMode = s.defaultMode;
|
|
89
|
-
return mergePermissions(nested, topLevel);
|
|
90
|
-
}
|
|
91
|
-
const userPerms = extractPerms(userSettings);
|
|
92
|
-
const projectPerms = extractPerms(projectSettings);
|
|
93
|
-
const localPerms = extractPerms(localSettings);
|
|
94
|
-
|
|
95
|
-
let merged = mergePermissions({}, userPerms);
|
|
96
|
-
merged = mergePermissions(merged, projectPerms);
|
|
97
|
-
merged = mergePermissions(merged, localPerms);
|
|
98
|
-
|
|
99
|
-
const VALID_MODES = new Set(['default', 'acceptEdits', 'plan', 'dontAsk', 'bypassPermissions', 'auto']);
|
|
100
|
-
const rawMode = merged.defaultMode;
|
|
101
|
-
const resolvedMode = (typeof rawMode === 'string' && VALID_MODES.has(rawMode))
|
|
102
|
-
? rawMode
|
|
103
|
-
: 'default';
|
|
104
|
-
return {
|
|
105
|
-
allow: Array.isArray(merged.allow) ? merged.allow : [],
|
|
106
|
-
deny: Array.isArray(merged.deny) ? merged.deny : [],
|
|
107
|
-
ask: Array.isArray(merged.ask) ? merged.ask : [],
|
|
108
|
-
defaultMode: resolvedMode,
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
module.exports = { loadPermissions, clearSettingsCache };
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// Single source for the hook IPC path. Centralizes an existing platform
|
|
4
|
-
// contract (not new heuristic). Rust mirror native/mixdog-shim/src/main.rs:34-43
|
|
5
|
-
// must stay in sync.
|
|
6
|
-
module.exports = function hookPipePath() {
|
|
7
|
-
return process.platform === 'win32'
|
|
8
|
-
? '\\\\.\\pipe\\mixdog-hooks'
|
|
9
|
-
: require('path').join(process.env.XDG_RUNTIME_DIR || '/tmp', 'mixdog-hooks.sock');
|
|
10
|
-
};
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
# Lead Tool Use
|
|
2
|
-
|
|
3
|
-
- Lead owns repo-local shell work: git, build, test, run, and verification commands.
|
|
4
|
-
- Use `shell` directly for approved git/build/test/run work; do not delegate those commands to bridge agents.
|
|
5
|
-
- Use bridge agents for scoped implementation, research, review, and debugging, not for git commit/push/stash or Ship.
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import { existsSync } from 'fs';
|
|
2
|
-
import { dirname, join, resolve } from 'path';
|
|
3
|
-
import { performance } from 'perf_hooks';
|
|
4
|
-
import { fileURLToPath } from 'url';
|
|
5
|
-
import { snapshotCoversFullFile } from './snapshot-helpers.mjs';
|
|
6
|
-
import { getPluginData } from '../../config.mjs';
|
|
7
|
-
import { findCachedPatchBinary } from '../patch-binary-fetcher.mjs';
|
|
8
|
-
import { runServerEdit } from '../patch.mjs';
|
|
9
|
-
|
|
10
|
-
const PLUGIN_ROOT = process.env.MIXDOG_ROOT
|
|
11
|
-
|| resolve(dirname(fileURLToPath(import.meta.url)), '../../../../..');
|
|
12
|
-
const NATIVE_EDIT_DEFAULT_BIN = join(
|
|
13
|
-
PLUGIN_ROOT,
|
|
14
|
-
'native/mixdog-patch/target/release',
|
|
15
|
-
process.platform === 'win32' ? 'mixdog-patch.exe' : 'mixdog-patch',
|
|
16
|
-
);
|
|
17
|
-
|
|
18
|
-
export function nativeEditMode() {
|
|
19
|
-
return String(process.env.MIXDOG_EDIT_NATIVE || 'auto').toLowerCase();
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function nativeEditBinCandidate() {
|
|
23
|
-
const override = process.env.MIXDOG_EDIT_NATIVE_BIN || process.env.MIXDOG_PATCH_NATIVE_BIN;
|
|
24
|
-
if (override) return { path: override, kind: 'override' };
|
|
25
|
-
if (existsSync(NATIVE_EDIT_DEFAULT_BIN)) return { path: NATIVE_EDIT_DEFAULT_BIN, kind: 'local' };
|
|
26
|
-
const cached = findCachedPatchBinary(getPluginData());
|
|
27
|
-
if (cached) return { path: cached, kind: 'cached' };
|
|
28
|
-
return { path: NATIVE_EDIT_DEFAULT_BIN, kind: 'missing' };
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function nativeEditBinPath() {
|
|
32
|
-
return nativeEditBinCandidate().path;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function nativeEditShouldAttempt({ editSnapshot, oldStr, newStr, preloadedContent, preloadedRawBuf }) {
|
|
36
|
-
const mode = nativeEditMode();
|
|
37
|
-
if (/^(0|false|no|off|js|legacy)$/i.test(mode)) return false;
|
|
38
|
-
const forcedNative = /^(1|true|yes|on|native)$/i.test(mode);
|
|
39
|
-
const candidate = nativeEditBinCandidate();
|
|
40
|
-
if (!existsSync(candidate.path)) return false;
|
|
41
|
-
// Cached release prebuilds are guaranteed valid for apply_patch, but older
|
|
42
|
-
// manifests (currently v0.6.5 in clean CI) predate the EDIT server protocol.
|
|
43
|
-
// In auto mode, native edit is only an acceleration, so require either a
|
|
44
|
-
// local cargo build or an explicit override. If a user forces native mode,
|
|
45
|
-
// still try the cached binary and surface any protocol failure.
|
|
46
|
-
if (candidate.kind === 'cached' && !forcedNative) return false;
|
|
47
|
-
if (!snapshotCoversFullFile(editSnapshot)) return false;
|
|
48
|
-
if (preloadedContent !== null || preloadedRawBuf !== null) return false;
|
|
49
|
-
if (typeof oldStr !== 'string' || oldStr.length === 0 || typeof newStr !== 'string') return false;
|
|
50
|
-
if (forcedNative) return true;
|
|
51
|
-
// auto: the persistent server removed per-call spawn cost, so route edits to
|
|
52
|
-
// native edit2 by default (B3). Same-size edits keep the JS in-place partial
|
|
53
|
-
// write, which rewrites bytes in place instead of the whole file.
|
|
54
|
-
const oldBytes = Buffer.byteLength(oldStr, 'utf-8');
|
|
55
|
-
const newBytes = Buffer.byteLength(newStr, 'utf-8');
|
|
56
|
-
if (oldBytes === newBytes) return false;
|
|
57
|
-
return true;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export async function runNativeExactEdit({ fullPath, oldStr, newStr, replaceAll, signal = null }) {
|
|
61
|
-
const forcedNative = /^(1|true|yes|on|native)$/i.test(nativeEditMode());
|
|
62
|
-
if (signal?.aborted) {
|
|
63
|
-
return { ok: false, fallback: false, error: signal.reason?.message || signal.reason || 'native edit aborted' };
|
|
64
|
-
}
|
|
65
|
-
const oldBuf = Buffer.from(oldStr, 'utf-8');
|
|
66
|
-
const newBuf = Buffer.from(newStr, 'utf-8');
|
|
67
|
-
const started = performance.now();
|
|
68
|
-
try {
|
|
69
|
-
// PARITY GUARD: the native engine MATCHES via the curly-quote fold
|
|
70
|
-
// tier but applies new_string verbatim, silently downgrading the
|
|
71
|
-
// file's typographic quotes (JS slow path preserves them via
|
|
72
|
-
// preserveQuoteTypography). When old_string carries quote-family
|
|
73
|
-
// chars — the only inputs that can land on the curly tier — probe
|
|
74
|
-
// with a dry run (persistent server, ~ms) and defer curly-tier
|
|
75
|
-
// matches to the JS editor.
|
|
76
|
-
if (/["'‘’“”]/.test(oldStr)) {
|
|
77
|
-
const probe = await runServerEdit({ fullPath, oldBuf, newBuf, replaceAll, dryRun: true, signal });
|
|
78
|
-
if (probe?.tier === 'curly') {
|
|
79
|
-
return { ok: false, fallback: true, error: 'curly-quote fold match — deferred to JS editor for typography preservation' };
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
const res = await runServerEdit({ fullPath, oldBuf, newBuf, replaceAll, signal });
|
|
83
|
-
return {
|
|
84
|
-
ok: true,
|
|
85
|
-
replacements: res.replacements,
|
|
86
|
-
readMs: res.readMs,
|
|
87
|
-
applyMs: res.applyMs,
|
|
88
|
-
writeMs: res.writeMs,
|
|
89
|
-
totalMs: res.totalMs,
|
|
90
|
-
roundtripMs: res.roundtripMs ?? (performance.now() - started),
|
|
91
|
-
stage: res.tier,
|
|
92
|
-
contentHash: res.contentHash,
|
|
93
|
-
};
|
|
94
|
-
} catch (err) {
|
|
95
|
-
if (err?.name === 'AbortError') {
|
|
96
|
-
return { ok: false, fallback: false, error: err.message };
|
|
97
|
-
}
|
|
98
|
-
const msg = String(err?.message || err);
|
|
99
|
-
// Tier misses and not-found map to a JS fallback; transport/spawn errors
|
|
100
|
-
// also fall back so a server hiccup never blocks an edit. Older cached
|
|
101
|
-
// mixdog-patch binaries (for example the v0.6.5 release prebuilds used
|
|
102
|
-
// by clean CI before a local cargo build exists) support APPLY but not
|
|
103
|
-
// the EDIT server protocol, and answer EDIT with the APPLY parser's
|
|
104
|
-
// "bad header" error. In auto mode that means "native edit unavailable",
|
|
105
|
-
// not "the edit is invalid", so fall through to the JS editor. When the
|
|
106
|
-
// user explicitly forces native mode, keep surfacing the native failure.
|
|
107
|
-
const fallback = !forcedNative && /old_string (?:not found|found \d+ times)|not valid UTF-8|no exact match|not found|server|bad header|bad edit header/i.test(msg);
|
|
108
|
-
return { ok: false, fallback, error: msg };
|
|
109
|
-
}
|
|
110
|
-
}
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import * as nodeBuffer from 'node:buffer';
|
|
2
|
-
import { readFileSync } from 'node:fs';
|
|
3
|
-
import { readFile as readFileAsync } from 'node:fs/promises';
|
|
4
|
-
import { decodeRawBufferForSnapshotCheck } from './builtin/snapshot-helpers.mjs';
|
|
5
|
-
|
|
6
|
-
function keyForPath(fullPath) {
|
|
7
|
-
const text = String(fullPath || '');
|
|
8
|
-
return process.platform === 'win32' ? text.toLowerCase() : text;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function isValidUtf8Buffer(buf) {
|
|
12
|
-
if (typeof nodeBuffer.isUtf8 === 'function') return nodeBuffer.isUtf8(buf);
|
|
13
|
-
if (typeof Buffer.isUtf8 === 'function') return Buffer.isUtf8(buf);
|
|
14
|
-
return Buffer.from(buf.toString('utf-8'), 'utf-8').equals(buf);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function createMutationContentCache() {
|
|
18
|
-
const entries = new Map();
|
|
19
|
-
|
|
20
|
-
function getOrCreate(fullPath) {
|
|
21
|
-
const key = keyForPath(fullPath);
|
|
22
|
-
let entry = entries.get(key);
|
|
23
|
-
if (!entry) {
|
|
24
|
-
entry = { fullPath, rawBuf: null, content: null };
|
|
25
|
-
entries.set(key, entry);
|
|
26
|
-
}
|
|
27
|
-
return entry;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function seedBuffer(fullPath, rawBuf) {
|
|
31
|
-
const entry = getOrCreate(fullPath);
|
|
32
|
-
entry.rawBuf = rawBuf;
|
|
33
|
-
entry.content = null;
|
|
34
|
-
return entry;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function readBufferSync(fullPath) {
|
|
38
|
-
const entry = getOrCreate(fullPath);
|
|
39
|
-
if (!Buffer.isBuffer(entry.rawBuf)) entry.rawBuf = readFileSync(fullPath);
|
|
40
|
-
return entry.rawBuf;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
async function readBuffer(fullPath) {
|
|
44
|
-
const entry = getOrCreate(fullPath);
|
|
45
|
-
if (!Buffer.isBuffer(entry.rawBuf)) entry.rawBuf = await readFileAsync(fullPath);
|
|
46
|
-
return entry.rawBuf;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function readTextSync(fullPath) {
|
|
50
|
-
const entry = getOrCreate(fullPath);
|
|
51
|
-
if (typeof entry.content !== 'string') {
|
|
52
|
-
entry.content = decodeRawBufferForSnapshotCheck(readBufferSync(fullPath));
|
|
53
|
-
}
|
|
54
|
-
return entry.content;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function getEntry(fullPath) {
|
|
58
|
-
return entries.get(keyForPath(fullPath)) || null;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function clear(fullPath = null) {
|
|
62
|
-
if (fullPath) entries.delete(keyForPath(fullPath));
|
|
63
|
-
else entries.clear();
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return { seedBuffer, readBuffer, readBufferSync, readTextSync, getEntry, clear };
|
|
67
|
-
}
|