mixdog 0.7.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/.claude-plugin/marketplace.json +31 -0
- package/.claude-plugin/plugin.json +20 -0
- package/.gitattributes +34 -0
- package/.mcp.json +14 -0
- package/ARCHITECTURE.md +77 -0
- package/CHANGELOG.md +7 -0
- package/CONTRIBUTING.md +45 -0
- package/DATA-FLOW.md +79 -0
- package/LICENSE +21 -0
- package/README.md +389 -0
- package/SECURITY.md +138 -0
- package/UNINSTALL.md +112 -0
- package/agents/maintenance.md +5 -0
- package/agents/memory-classification.md +30 -0
- package/agents/scheduler-task.md +18 -0
- package/agents/webhook-handler.md +27 -0
- package/agents/worker.md +24 -0
- package/bin/bridge +133 -0
- package/bin/statusline-launcher.mjs +78 -0
- package/bin/statusline-lib.mjs +550 -0
- package/bin/statusline.mjs +607 -0
- package/bun.lock +802 -0
- package/commands/config.md +16 -0
- package/commands/doctor.md +13 -0
- package/commands/setup.md +17 -0
- package/defaults/cycle3-review-prompt.md +90 -0
- package/defaults/hidden-roles.json +65 -0
- package/defaults/memory-chunk-prompt.md +63 -0
- package/defaults/memory-promote-prompt.md +135 -0
- package/defaults/mixdog-config.template.json +27 -0
- package/defaults/user-workflow.json +8 -0
- package/defaults/user-workflow.md +12 -0
- package/hooks/hooks.json +73 -0
- package/hooks/lib/active-instance.cjs +77 -0
- package/hooks/lib/permission-evaluator.cjs +411 -0
- package/hooks/lib/permission-route.cjs +63 -0
- package/hooks/lib/permission-rules.cjs +170 -0
- package/hooks/lib/settings-loader.cjs +116 -0
- package/hooks/post-tool-use.cjs +84 -0
- package/hooks/pre-mcp-sandbox.cjs +158 -0
- package/hooks/pre-tool-subagent.cjs +253 -0
- package/hooks/session-start.cjs +1372 -0
- package/hooks/turn-timer.cjs +82 -0
- package/lib/claude-md-writer.cjs +386 -0
- package/lib/config-cjs.cjs +61 -0
- package/lib/hook-pipe-path.cjs +10 -0
- package/lib/keychain-cjs.cjs +263 -0
- package/lib/plugin-paths.cjs +61 -0
- package/lib/rules-builder.cjs +241 -0
- package/lib/text-utils.cjs +61 -0
- package/native/README.md +117 -0
- package/native/prebuilt/linux-aarch64/mixdog-shim +0 -0
- package/native/prebuilt/linux-x86_64/mixdog-shim +0 -0
- package/native/prebuilt/macos-aarch64/mixdog-shim +0 -0
- package/native/prebuilt/macos-x86_64/mixdog-shim +0 -0
- package/native/prebuilt/windows-x86_64/mixdog-shim.exe +0 -0
- package/package.json +107 -0
- package/prompts/code-review.txt +16 -0
- package/prompts/security-audit.txt +17 -0
- package/rules/bridge/00-common.md +39 -0
- package/rules/bridge/20-skip-protocol.md +18 -0
- package/rules/bridge/30-explorer.md +33 -0
- package/rules/bridge/40-cycle1-agent.md +52 -0
- package/rules/bridge/41-cycle2-agent.md +62 -0
- package/rules/bridge/42-cycle3-agent.md +44 -0
- package/rules/lead/00-tool-lead.md +61 -0
- package/rules/lead/01-general.md +23 -0
- package/rules/lead/02-channels.md +49 -0
- package/rules/lead/03-team.md +27 -0
- package/rules/lead/04-workflow.md +20 -0
- package/rules/shared/00-language.md +14 -0
- package/rules/shared/01-tool.md +138 -0
- package/scripts/bootstrap.mjs +184 -0
- package/scripts/bridge-unify-smoke.mjs +308 -0
- 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/builtin-utils-smoke.mjs +392 -0
- package/scripts/check-json.mjs +45 -0
- package/scripts/check-syntax-changed.mjs +102 -0
- package/scripts/check-syntax.mjs +58 -0
- package/scripts/code-graph-batch.test.mjs +33 -0
- package/scripts/config-preserve-smoke.mjs +180 -0
- package/scripts/doctor.mjs +484 -0
- package/scripts/edit-normalize-fuzz.mjs +130 -0
- package/scripts/edit-normalize-smoke.mjs +401 -0
- package/scripts/edit-operation-smoke.mjs +369 -0
- package/scripts/edit2-smoke.mjs +63 -0
- package/scripts/fuzzy-e2e.mjs +28 -0
- package/scripts/fuzzy-smoke.mjs +26 -0
- package/scripts/generate-runtime-manifest.mjs +166 -0
- package/scripts/guard-smoke.mjs +66 -0
- package/scripts/hidden-role-schema-smoke.mjs +162 -0
- package/scripts/hook-routing-smoke.mjs +29 -0
- package/scripts/inject-input.ps1 +204 -0
- package/scripts/io-complex-smoke.mjs +667 -0
- package/scripts/io-explore-bench.mjs +424 -0
- package/scripts/io-guardrails-smoke.mjs +205 -0
- package/scripts/io-mini-bench-baseline.json +11 -0
- package/scripts/io-mini-bench.mjs +216 -0
- package/scripts/io-route-harness.mjs +933 -0
- package/scripts/io-telemetry-report.mjs +691 -0
- package/scripts/mutation-bench.mjs +564 -0
- package/scripts/mutation-io-smoke.mjs +1081 -0
- package/scripts/native-patch-bridge-smoke.mjs +288 -0
- package/scripts/native-patch-smoke.mjs +304 -0
- package/scripts/patch-interior-context-smoke.mjs +49 -0
- package/scripts/patch-newline-utf8-smoke.mjs +157 -0
- package/scripts/perf-hook-smoke.mjs +71 -0
- package/scripts/permission-eval-smoke.mjs +426 -0
- package/scripts/prep-patch.mjs +53 -0
- package/scripts/prep-shim.mjs +96 -0
- package/scripts/provider-cache-smoke.mjs +687 -0
- package/scripts/report-runtime-health.mjs +132 -0
- package/scripts/run-mcp.mjs +1547 -0
- package/scripts/salvage-v4a-shatter.test.mjs +58 -0
- package/scripts/scoped-cache-io-smoke.mjs +103 -0
- package/scripts/shell-policy-round3-smoke.mjs +46 -0
- package/scripts/smoke-runtime-negative.ps1 +100 -0
- package/scripts/smoke-runtime-negative.sh +95 -0
- package/scripts/stall-policy-smoke.mjs +50 -0
- package/scripts/start-memory-worker.mjs +23 -0
- package/scripts/statusline-launcher-smoke.mjs +82 -0
- package/scripts/stress-atomic-write.mjs +1028 -0
- package/scripts/test-config-rmw-restore.mjs +122 -0
- package/scripts/test-fault-inject.mjs +164 -0
- package/scripts/test-large-file.mjs +174 -0
- package/scripts/tool-edge-smoke.mjs +209 -0
- package/scripts/uninstall.mjs +201 -0
- package/scripts/webhook-selfheal-smoke.mjs +29 -0
- package/scripts/write-overwrite-guard-smoke.mjs +56 -0
- package/server-main.mjs +3055 -0
- package/server.mjs +468 -0
- package/setup/config-merge.mjs +254 -0
- package/setup/install.mjs +120 -0
- package/setup/launch-core.mjs +507 -0
- package/setup/launch.mjs +101 -0
- package/setup/setup-server.mjs +3206 -0
- package/setup/setup.html +3693 -0
- package/skills/retro-skill-proposer/SKILL.md +92 -0
- package/skills/schedule-add/SKILL.md +77 -0
- package/skills/setup/SKILL.md +346 -0
- package/skills/webhook-add/SKILL.md +81 -0
- package/src/agent/bridge-stall-watchdog.mjs +337 -0
- package/src/agent/index.mjs +2138 -0
- package/src/agent/orchestrator/activity-bus.mjs +38 -0
- package/src/agent/orchestrator/ai-wrapped-dispatch.mjs +1010 -0
- package/src/agent/orchestrator/bridge-retry.mjs +220 -0
- package/src/agent/orchestrator/bridge-trace.mjs +583 -0
- package/src/agent/orchestrator/cache-mtime.mjs +58 -0
- package/src/agent/orchestrator/config.mjs +358 -0
- package/src/agent/orchestrator/context/collect.mjs +651 -0
- package/src/agent/orchestrator/dispatch-persist.mjs +549 -0
- package/src/agent/orchestrator/drain-registry.mjs +50 -0
- package/src/agent/orchestrator/explore-validator.mjs +8 -0
- package/src/agent/orchestrator/internal-roles.mjs +118 -0
- package/src/agent/orchestrator/internal-tools.mjs +88 -0
- package/src/agent/orchestrator/jobs.mjs +116 -0
- package/src/agent/orchestrator/mcp/client.mjs +364 -0
- package/src/agent/orchestrator/providers/anthropic-betas.mjs +21 -0
- package/src/agent/orchestrator/providers/anthropic-oauth.mjs +1745 -0
- package/src/agent/orchestrator/providers/anthropic.mjs +437 -0
- package/src/agent/orchestrator/providers/gemini.mjs +1175 -0
- package/src/agent/orchestrator/providers/grok-oauth.mjs +782 -0
- package/src/agent/orchestrator/providers/model-catalog.mjs +241 -0
- package/src/agent/orchestrator/providers/openai-compat.mjs +1467 -0
- package/src/agent/orchestrator/providers/openai-oauth-ws.mjs +1890 -0
- package/src/agent/orchestrator/providers/openai-oauth.mjs +1307 -0
- package/src/agent/orchestrator/providers/openai-ws.mjs +104 -0
- package/src/agent/orchestrator/providers/registry.mjs +192 -0
- package/src/agent/orchestrator/providers/retry-classifier.mjs +325 -0
- package/src/agent/orchestrator/session/abort-lookup.mjs +13 -0
- package/src/agent/orchestrator/session/cache/post-edit-marks.mjs +42 -0
- package/src/agent/orchestrator/session/cache/prefetch-cache.mjs +142 -0
- package/src/agent/orchestrator/session/cache/read-cache.mjs +319 -0
- package/src/agent/orchestrator/session/cache/scoped-cache-outcome.mjs +11 -0
- package/src/agent/orchestrator/session/cache/scoped-cache.mjs +361 -0
- package/src/agent/orchestrator/session/cache/util.mjs +49 -0
- package/src/agent/orchestrator/session/loop.mjs +1478 -0
- package/src/agent/orchestrator/session/manager.mjs +1975 -0
- package/src/agent/orchestrator/session/read-dedup.mjs +6 -0
- package/src/agent/orchestrator/session/result-classification.mjs +65 -0
- package/src/agent/orchestrator/session/save-session-worker.mjs +18 -0
- package/src/agent/orchestrator/session/store.mjs +624 -0
- package/src/agent/orchestrator/session/stream-watchdog.mjs +130 -0
- package/src/agent/orchestrator/session/tool-result-offload.mjs +166 -0
- package/src/agent/orchestrator/session/trim.mjs +491 -0
- package/src/agent/orchestrator/smart-bridge/CACHE-SHARD.md +115 -0
- package/src/agent/orchestrator/smart-bridge/bridge-llm.mjs +327 -0
- package/src/agent/orchestrator/smart-bridge/cache-obs.mjs +150 -0
- package/src/agent/orchestrator/smart-bridge/cache-strategy.mjs +228 -0
- package/src/agent/orchestrator/smart-bridge/index.mjs +215 -0
- package/src/agent/orchestrator/smart-bridge/profiles.mjs +37 -0
- package/src/agent/orchestrator/smart-bridge/registry.mjs +348 -0
- package/src/agent/orchestrator/smart-bridge/session-builder.mjs +116 -0
- package/src/agent/orchestrator/stall-policy.mjs +195 -0
- package/src/agent/orchestrator/tool-loop-guard.mjs +75 -0
- package/src/agent/orchestrator/tools/bash-policy-scan.mjs +77 -0
- package/src/agent/orchestrator/tools/bash-session.mjs +721 -0
- package/src/agent/orchestrator/tools/builtin/advisory-lock.mjs +171 -0
- package/src/agent/orchestrator/tools/builtin/arg-guard.mjs +455 -0
- package/src/agent/orchestrator/tools/builtin/atomic-write.mjs +236 -0
- package/src/agent/orchestrator/tools/builtin/bash-tool.mjs +480 -0
- package/src/agent/orchestrator/tools/builtin/binary-file.mjs +76 -0
- package/src/agent/orchestrator/tools/builtin/builtin-tools.mjs +256 -0
- package/src/agent/orchestrator/tools/builtin/cache-layers.mjs +386 -0
- package/src/agent/orchestrator/tools/builtin/cwd-utils.mjs +37 -0
- package/src/agent/orchestrator/tools/builtin/device-paths.mjs +154 -0
- package/src/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +292 -0
- package/src/agent/orchestrator/tools/builtin/diff-utils.mjs +109 -0
- package/src/agent/orchestrator/tools/builtin/edit-base-guard.mjs +58 -0
- package/src/agent/orchestrator/tools/builtin/edit-byte-plan.mjs +240 -0
- package/src/agent/orchestrator/tools/builtin/edit-byte-utils.mjs +113 -0
- package/src/agent/orchestrator/tools/builtin/edit-commit.mjs +74 -0
- package/src/agent/orchestrator/tools/builtin/edit-context-utils.mjs +242 -0
- package/src/agent/orchestrator/tools/builtin/edit-diagnostics.mjs +211 -0
- package/src/agent/orchestrator/tools/builtin/edit-engine.mjs +1364 -0
- package/src/agent/orchestrator/tools/builtin/edit-failure-context.mjs +126 -0
- package/src/agent/orchestrator/tools/builtin/edit-hint.mjs +141 -0
- package/src/agent/orchestrator/tools/builtin/edit-match-utils.mjs +194 -0
- package/src/agent/orchestrator/tools/builtin/edit-partial-write.mjs +60 -0
- package/src/agent/orchestrator/tools/builtin/edit-stale-refresh.mjs +168 -0
- package/src/agent/orchestrator/tools/builtin/edit-tool.mjs +173 -0
- package/src/agent/orchestrator/tools/builtin/edit-utf8-guard.mjs +48 -0
- package/src/agent/orchestrator/tools/builtin/fs-reachability.mjs +48 -0
- package/src/agent/orchestrator/tools/builtin/fuzzy-match.mjs +99 -0
- package/src/agent/orchestrator/tools/builtin/glob-walk.mjs +170 -0
- package/src/agent/orchestrator/tools/builtin/grep-formatting.mjs +113 -0
- package/src/agent/orchestrator/tools/builtin/hash-utils.mjs +6 -0
- package/src/agent/orchestrator/tools/builtin/list-formatting.mjs +7 -0
- package/src/agent/orchestrator/tools/builtin/list-tool.mjs +593 -0
- package/src/agent/orchestrator/tools/builtin/native-edit-runner.mjs +89 -0
- package/src/agent/orchestrator/tools/builtin/notebook-edit-tool.mjs +300 -0
- package/src/agent/orchestrator/tools/builtin/open-config-tool.mjs +26 -0
- package/src/agent/orchestrator/tools/builtin/path-diagnostics.mjs +152 -0
- package/src/agent/orchestrator/tools/builtin/path-locks.mjs +35 -0
- package/src/agent/orchestrator/tools/builtin/path-utils.mjs +201 -0
- package/src/agent/orchestrator/tools/builtin/read-args.mjs +103 -0
- package/src/agent/orchestrator/tools/builtin/read-batch.mjs +172 -0
- package/src/agent/orchestrator/tools/builtin/read-constants.mjs +40 -0
- package/src/agent/orchestrator/tools/builtin/read-formatting.mjs +118 -0
- package/src/agent/orchestrator/tools/builtin/read-image-resize.mjs +189 -0
- package/src/agent/orchestrator/tools/builtin/read-image.mjs +88 -0
- package/src/agent/orchestrator/tools/builtin/read-lines.mjs +12 -0
- package/src/agent/orchestrator/tools/builtin/read-mode-tool.mjs +455 -0
- package/src/agent/orchestrator/tools/builtin/read-open.mjs +190 -0
- package/src/agent/orchestrator/tools/builtin/read-range-index.mjs +271 -0
- package/src/agent/orchestrator/tools/builtin/read-ranges.mjs +26 -0
- package/src/agent/orchestrator/tools/builtin/read-single-tool.mjs +728 -0
- package/src/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +173 -0
- package/src/agent/orchestrator/tools/builtin/read-special-files.mjs +268 -0
- package/src/agent/orchestrator/tools/builtin/read-streaming.mjs +602 -0
- package/src/agent/orchestrator/tools/builtin/read-tool.mjs +530 -0
- package/src/agent/orchestrator/tools/builtin/read-windows.mjs +107 -0
- package/src/agent/orchestrator/tools/builtin/rename-tool.mjs +196 -0
- package/src/agent/orchestrator/tools/builtin/rg-runner.mjs +422 -0
- package/src/agent/orchestrator/tools/builtin/search-builders.mjs +158 -0
- package/src/agent/orchestrator/tools/builtin/search-tool.mjs +869 -0
- package/src/agent/orchestrator/tools/builtin/shell-analysis.mjs +653 -0
- package/src/agent/orchestrator/tools/builtin/shell-jobs.mjs +936 -0
- package/src/agent/orchestrator/tools/builtin/shell-output.mjs +36 -0
- package/src/agent/orchestrator/tools/builtin/shell-runtime.mjs +214 -0
- package/src/agent/orchestrator/tools/builtin/snapshot-helpers.mjs +143 -0
- package/src/agent/orchestrator/tools/builtin/snapshot-store.mjs +206 -0
- package/src/agent/orchestrator/tools/builtin/snapshot-validation.mjs +98 -0
- package/src/agent/orchestrator/tools/builtin/text-stats.mjs +69 -0
- package/src/agent/orchestrator/tools/builtin/windows-roots.mjs +23 -0
- package/src/agent/orchestrator/tools/builtin/write-tool.mjs +401 -0
- package/src/agent/orchestrator/tools/builtin.mjs +500 -0
- package/src/agent/orchestrator/tools/code-graph-prewarm-worker.mjs +39 -0
- package/src/agent/orchestrator/tools/code-graph-tool-defs.mjs +24 -0
- package/src/agent/orchestrator/tools/code-graph.mjs +4095 -0
- package/src/agent/orchestrator/tools/cwd-tool.mjs +298 -0
- package/src/agent/orchestrator/tools/destructive-warning.mjs +323 -0
- package/src/agent/orchestrator/tools/edit-normalize.mjs +603 -0
- package/src/agent/orchestrator/tools/env-scrub.mjs +100 -0
- package/src/agent/orchestrator/tools/graph-binary-fetcher.mjs +144 -0
- package/src/agent/orchestrator/tools/graph-manifest.json +26 -0
- package/src/agent/orchestrator/tools/host-input.mjs +204 -0
- package/src/agent/orchestrator/tools/mutation-content-cache.mjs +67 -0
- package/src/agent/orchestrator/tools/mutation-planner.mjs +75 -0
- package/src/agent/orchestrator/tools/next-call-utils.mjs +48 -0
- package/src/agent/orchestrator/tools/patch-binary-fetcher.mjs +133 -0
- package/src/agent/orchestrator/tools/patch-manifest.json +26 -0
- package/src/agent/orchestrator/tools/patch-tool-defs.mjs +20 -0
- package/src/agent/orchestrator/tools/patch.mjs +2754 -0
- package/src/agent/orchestrator/tools/progress-message.mjs +118 -0
- package/src/agent/orchestrator/tools/result-compression.mjs +279 -0
- package/src/agent/orchestrator/tools/shell-command.mjs +865 -0
- package/src/agent/orchestrator/tools/shell-exec-policy.mjs +89 -0
- package/src/agent/orchestrator/tools/shell-policy-danger-target.mjs +27 -0
- package/src/agent/orchestrator/tools/shell-policy-imports.mjs +7 -0
- package/src/agent/orchestrator/tools/shell-policy.mjs +345 -0
- package/src/agent/orchestrator/tools/shell-snapshot.mjs +313 -0
- package/src/agent/orchestrator/workflow-store.mjs +93 -0
- package/src/agent/tool-defs.mjs +103 -0
- package/src/channels/backends/discord.mjs +784 -0
- package/src/channels/data/voice-runtime-manifest.json +138 -0
- package/src/channels/index.mjs +3229 -0
- package/src/channels/lib/cli-worker-host.mjs +12 -0
- package/src/channels/lib/config-lock.mjs +13 -0
- package/src/channels/lib/config.mjs +292 -0
- package/src/channels/lib/drop-trace.mjs +71 -0
- package/src/channels/lib/event-pipeline.mjs +81 -0
- package/src/channels/lib/event-queue.mjs +345 -0
- package/src/channels/lib/executor.mjs +168 -0
- package/src/channels/lib/format.mjs +188 -0
- package/src/channels/lib/holidays.mjs +138 -0
- package/src/channels/lib/hook-pipe-server.mjs +802 -0
- package/src/channels/lib/interaction-workflows.mjs +184 -0
- package/src/channels/lib/memory-client.mjs +149 -0
- package/src/channels/lib/output-forwarder.mjs +765 -0
- package/src/channels/lib/runtime-paths.mjs +479 -0
- package/src/channels/lib/scheduler.mjs +723 -0
- package/src/channels/lib/session-control.mjs +36 -0
- package/src/channels/lib/session-discovery.mjs +103 -0
- package/src/channels/lib/settings.mjs +11 -0
- package/src/channels/lib/state-file.mjs +68 -0
- package/src/channels/lib/status-snapshot.mjs +219 -0
- package/src/channels/lib/tool-format.mjs +140 -0
- package/src/channels/lib/transcript-discovery.mjs +195 -0
- package/src/channels/lib/voice-runtime-fetcher.mjs +734 -0
- package/src/channels/lib/webhook.mjs +1179 -0
- package/src/channels/lib/whisper-server.mjs +477 -0
- package/src/channels/tool-defs.mjs +170 -0
- package/src/daemon/host.mjs +118 -0
- package/src/daemon/mcp-transport.mjs +47 -0
- package/src/daemon/session.mjs +100 -0
- package/src/daemon/thin-client.mjs +71 -0
- package/src/daemon/transport.mjs +163 -0
- package/src/memory/data/runtime-manifest.json +40 -0
- package/src/memory/index.mjs +3305 -0
- package/src/memory/lib/agent-ipc.mjs +93 -0
- package/src/memory/lib/bridge-trace-queries.mjs +120 -0
- package/src/memory/lib/core-memory-store.mjs +330 -0
- package/src/memory/lib/embedding-provider.mjs +269 -0
- package/src/memory/lib/embedding-worker.mjs +323 -0
- package/src/memory/lib/llm-worker-host.mjs +17 -0
- package/src/memory/lib/memory-cycle.mjs +11 -0
- package/src/memory/lib/memory-cycle1.mjs +641 -0
- package/src/memory/lib/memory-cycle2.mjs +1284 -0
- package/src/memory/lib/memory-cycle3.mjs +540 -0
- package/src/memory/lib/memory-embed.mjs +299 -0
- package/src/memory/lib/memory-extraction.mjs +5 -0
- package/src/memory/lib/memory-maintenance-store.mjs +32 -0
- package/src/memory/lib/memory-ops-policy.mjs +190 -0
- package/src/memory/lib/memory-recall-id-patch.mjs +15 -0
- package/src/memory/lib/memory-recall-read-query.mjs +7 -0
- package/src/memory/lib/memory-recall-scope-filter.mjs +63 -0
- package/src/memory/lib/memory-recall-store.mjs +621 -0
- package/src/memory/lib/memory-retrievers.mjs +112 -0
- package/src/memory/lib/memory-score.mjs +71 -0
- package/src/memory/lib/memory-text-utils.mjs +58 -0
- package/src/memory/lib/memory.mjs +412 -0
- package/src/memory/lib/model-profile.mjs +85 -0
- package/src/memory/lib/pg/adapter.mjs +308 -0
- package/src/memory/lib/pg/process.mjs +360 -0
- package/src/memory/lib/pg/supervisor.mjs +396 -0
- package/src/memory/lib/project-id-resolver.mjs +86 -0
- package/src/memory/lib/runtime-fetcher.mjs +442 -0
- package/src/memory/lib/trace-store.mjs +728 -0
- package/src/memory/tool-defs.mjs +79 -0
- package/src/search/index.mjs +1173 -0
- package/src/search/lib/backends/anthropic-oauth.mjs +98 -0
- package/src/search/lib/backends/exa.mjs +50 -0
- package/src/search/lib/backends/firecrawl.mjs +61 -0
- package/src/search/lib/backends/gemini-api.mjs +83 -0
- package/src/search/lib/backends/grok-oauth.mjs +86 -0
- package/src/search/lib/backends/index.mjs +150 -0
- package/src/search/lib/backends/openai-api.mjs +144 -0
- package/src/search/lib/backends/openai-oauth.mjs +98 -0
- package/src/search/lib/backends/openai-web-search.mjs +76 -0
- package/src/search/lib/backends/tavily.mjs +55 -0
- package/src/search/lib/backends/xai-api.mjs +113 -0
- package/src/search/lib/cache.mjs +131 -0
- package/src/search/lib/config.mjs +192 -0
- package/src/search/lib/formatter.mjs +115 -0
- package/src/search/lib/provider-usage.mjs +67 -0
- package/src/search/lib/providers.mjs +47 -0
- package/src/search/lib/search-intent.mjs +109 -0
- package/src/search/lib/setup-handler.mjs +261 -0
- package/src/search/lib/state.mjs +201 -0
- package/src/search/lib/web-tools.mjs +1207 -0
- package/src/search/tool-defs.mjs +83 -0
- package/src/setup/defender-exclusion.mjs +183 -0
- package/src/shared/abort-controller.mjs +15 -0
- package/src/shared/atomic-file.mjs +420 -0
- package/src/shared/config.mjs +350 -0
- package/src/shared/daemon-recycle.mjs +108 -0
- package/src/shared/disable-claude-builtins.mjs +88 -0
- package/src/shared/err-text.mjs +12 -0
- package/src/shared/llm/cost.mjs +66 -0
- package/src/shared/llm/http-agent.mjs +123 -0
- package/src/shared/llm/index.mjs +41 -0
- package/src/shared/llm/pid-cleanup.mjs +27 -0
- package/src/shared/llm/usage-log.mjs +47 -0
- package/src/shared/plugin-paths.mjs +58 -0
- package/src/shared/schedules-store.mjs +70 -0
- package/src/shared/seed.mjs +119 -0
- package/src/shared/user-cwd.mjs +213 -0
- package/src/shared/user-data-guard.mjs +238 -0
- package/src/status/aggregator.mjs +584 -0
- package/src/status/server.mjs +413 -0
- package/tools.json +1653 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { readFileSync, statSync } from 'fs';
|
|
2
|
+
import { hashText } from './hash-utils.mjs';
|
|
3
|
+
import { mergeReadRanges } from './read-ranges.mjs';
|
|
4
|
+
import {
|
|
5
|
+
normaliseRangeHashEntry,
|
|
6
|
+
snapshotCoversFullFile,
|
|
7
|
+
statMatchesSnapshot,
|
|
8
|
+
decodeRawBufferForSnapshotCheck,
|
|
9
|
+
} from './snapshot-helpers.mjs';
|
|
10
|
+
import {
|
|
11
|
+
rawContentCacheGet,
|
|
12
|
+
rawContentCacheSet,
|
|
13
|
+
} from './cache-layers.mjs';
|
|
14
|
+
import {
|
|
15
|
+
readFilesForScope,
|
|
16
|
+
readScopeKey,
|
|
17
|
+
scheduleScopePersist,
|
|
18
|
+
} from './snapshot-store.mjs';
|
|
19
|
+
import {
|
|
20
|
+
isSnapshotStale as isSnapshotStaleImpl,
|
|
21
|
+
readContentIfSnapshotHashMatches as readContentIfSnapshotHashMatchesImpl,
|
|
22
|
+
} from './snapshot-validation.mjs';
|
|
23
|
+
|
|
24
|
+
export function readTextForSnapshotCheck(fullPath, cache = null, st = null) {
|
|
25
|
+
let statForRawCache = st;
|
|
26
|
+
const getCachedRaw = () => {
|
|
27
|
+
try {
|
|
28
|
+
if (!statForRawCache) statForRawCache = statSync(fullPath);
|
|
29
|
+
return rawContentCacheGet(fullPath, statForRawCache);
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
if (cache && typeof cache.readTextSync === 'function') {
|
|
35
|
+
const entry = typeof cache.getEntry === 'function' ? cache.getEntry(fullPath) : null;
|
|
36
|
+
if (typeof entry?.content === 'string') return entry.content;
|
|
37
|
+
if (!Buffer.isBuffer(entry?.rawBuf) && typeof cache.seedBuffer === 'function') {
|
|
38
|
+
const cachedRaw = getCachedRaw();
|
|
39
|
+
if (cachedRaw) cache.seedBuffer(fullPath, cachedRaw);
|
|
40
|
+
}
|
|
41
|
+
return cache.readTextSync(fullPath);
|
|
42
|
+
}
|
|
43
|
+
if (cache && typeof cache.content === 'string' && Buffer.isBuffer(cache.rawBuf)) {
|
|
44
|
+
return cache.content;
|
|
45
|
+
}
|
|
46
|
+
const cachedRaw = getCachedRaw();
|
|
47
|
+
const rawBuf = cachedRaw || readFileSync(fullPath);
|
|
48
|
+
const content = decodeRawBufferForSnapshotCheck(rawBuf);
|
|
49
|
+
if (cache) {
|
|
50
|
+
cache.rawBuf = rawBuf;
|
|
51
|
+
cache.content = content;
|
|
52
|
+
}
|
|
53
|
+
if (!cachedRaw && statForRawCache) rawContentCacheSet(fullPath, statForRawCache, rawBuf);
|
|
54
|
+
return content;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function recordReadSnapshot(fullPath, st, scope = null, meta = {}) {
|
|
58
|
+
const readFiles = readFilesForScope(scope);
|
|
59
|
+
let mtimeMs;
|
|
60
|
+
let ctimeMs;
|
|
61
|
+
let size;
|
|
62
|
+
try {
|
|
63
|
+
if (st && typeof st.mtimeMs === 'number') {
|
|
64
|
+
mtimeMs = st.mtimeMs;
|
|
65
|
+
ctimeMs = st.ctimeMs;
|
|
66
|
+
size = st.size;
|
|
67
|
+
} else {
|
|
68
|
+
const fresh = statSync(fullPath);
|
|
69
|
+
mtimeMs = fresh.mtimeMs;
|
|
70
|
+
ctimeMs = fresh.ctimeMs;
|
|
71
|
+
size = fresh.size;
|
|
72
|
+
}
|
|
73
|
+
} catch {
|
|
74
|
+
mtimeMs = Date.now();
|
|
75
|
+
ctimeMs = mtimeMs;
|
|
76
|
+
size = 0;
|
|
77
|
+
}
|
|
78
|
+
const incomingRanges = Array.isArray(meta.ranges)
|
|
79
|
+
? meta.ranges
|
|
80
|
+
: [{ startLine: 1, endLine: Infinity }];
|
|
81
|
+
const replaceExisting = meta.replaceExisting === true;
|
|
82
|
+
const existing = replaceExisting ? null : readFiles.get(fullPath);
|
|
83
|
+
const sameFile = existing
|
|
84
|
+
&& statMatchesSnapshot({ mtimeMs, ctimeMs, size }, existing)
|
|
85
|
+
&& Array.isArray(existing.ranges);
|
|
86
|
+
const merged = sameFile
|
|
87
|
+
? mergeReadRanges([...existing.ranges, ...incomingRanges])
|
|
88
|
+
: mergeReadRanges(incomingRanges);
|
|
89
|
+
// fileLineCount is omitted here so it can ONLY be set via the explicit
|
|
90
|
+
// guard below (which excludes source==='read_batch_sliced'); otherwise a
|
|
91
|
+
// caller passing fileLineCount with a batch source would leak it through
|
|
92
|
+
// restMeta and bypass the fail-closed batch path.
|
|
93
|
+
const { ranges: _omitRanges, rangeHash: _omitRangeHash, rangeHashes: _omitRangeHashes, replaceExisting: _omitReplaceExisting, fileLineCount: _omitFileLineCount, ...restMeta } = meta;
|
|
94
|
+
const next = { ...restMeta, mtimeMs, ctimeMs, size, ranges: merged };
|
|
95
|
+
if (!next.contentHash && sameFile && existing.contentHash) {
|
|
96
|
+
next.contentHash = existing.contentHash;
|
|
97
|
+
}
|
|
98
|
+
// Provenance: a snapshot is "grep-only" while EVERY contributing read was a
|
|
99
|
+
// single-file grep (match lines only, never the whole file). Any real
|
|
100
|
+
// read/edit/write clears it permanently. NOTE: grepOnly does not itself gate
|
|
101
|
+
// edits or overwrites — the write-overwrite gate keys on full-file PROOF
|
|
102
|
+
// (contentHash + full coverage), which a single-file grep snapshot acquires
|
|
103
|
+
// via the auto-hash below, so grep satisfies BOTH the edit gate and the
|
|
104
|
+
// write-overwrite gate; grepOnly only tailors the code-10 message when proof
|
|
105
|
+
// is absent. Sticky across merges in both orders: read→grep keeps it false
|
|
106
|
+
// (existing.grepOnly === false wins), and grep→read rebuilds it false
|
|
107
|
+
// because a read uses replaceExisting.
|
|
108
|
+
const incomingIsGrep = meta.source === 'grep';
|
|
109
|
+
next.grepOnly = incomingIsGrep && (sameFile ? existing.grepOnly === true : true);
|
|
110
|
+
const rangeHashRows = [];
|
|
111
|
+
if (sameFile && Array.isArray(existing.rangeHashes)) {
|
|
112
|
+
for (const row of existing.rangeHashes) {
|
|
113
|
+
const nextRow = normaliseRangeHashEntry(row);
|
|
114
|
+
if (nextRow) rangeHashRows.push(nextRow);
|
|
115
|
+
}
|
|
116
|
+
} else if (sameFile && existing.rangeHash && Array.isArray(existing.ranges) && existing.ranges.length === 1) {
|
|
117
|
+
const nextRow = normaliseRangeHashEntry({ ...existing.ranges[0], hash: existing.rangeHash });
|
|
118
|
+
if (nextRow) rangeHashRows.push(nextRow);
|
|
119
|
+
}
|
|
120
|
+
if (meta.rangeHash && Array.isArray(meta.ranges) && meta.ranges.length === 1) {
|
|
121
|
+
const nextRow = normaliseRangeHashEntry({ ...meta.ranges[0], hash: meta.rangeHash });
|
|
122
|
+
if (nextRow) rangeHashRows.push(nextRow);
|
|
123
|
+
}
|
|
124
|
+
if (Array.isArray(meta.rangeHashes)) {
|
|
125
|
+
for (const row of meta.rangeHashes) {
|
|
126
|
+
const nextRow = normaliseRangeHashEntry(row);
|
|
127
|
+
if (nextRow) rangeHashRows.push(nextRow);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (!next.contentHash && snapshotCoversFullFile(next)) {
|
|
131
|
+
try {
|
|
132
|
+
const content = decodeRawBufferForSnapshotCheck(readFileSync(fullPath));
|
|
133
|
+
next.contentHash = hashText(content);
|
|
134
|
+
} catch {}
|
|
135
|
+
}
|
|
136
|
+
if (!next.contentHash && !snapshotCoversFullFile(next) && rangeHashRows.length > 0) {
|
|
137
|
+
const seen = new Set();
|
|
138
|
+
next.rangeHashes = rangeHashRows.filter((row) => {
|
|
139
|
+
const key = `${row.startLine}:${row.endLine}:${row.hash}`;
|
|
140
|
+
if (seen.has(key)) return false;
|
|
141
|
+
seen.add(key);
|
|
142
|
+
return true;
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
const batchSliced = meta.source === 'read_batch_sliced';
|
|
146
|
+
if (!batchSliced && Number.isFinite(meta.fileLineCount) && meta.fileLineCount >= 0) {
|
|
147
|
+
next.fileLineCount = Math.trunc(meta.fileLineCount);
|
|
148
|
+
} else if (!batchSliced && sameFile && Number.isFinite(existing?.fileLineCount) && existing.fileLineCount >= 0) {
|
|
149
|
+
next.fileLineCount = Math.trunc(existing.fileLineCount);
|
|
150
|
+
}
|
|
151
|
+
readFiles.set(fullPath, next);
|
|
152
|
+
scheduleScopePersist(readScopeKey(scope));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function getReadSnapshot(fullPath, scope = null) {
|
|
156
|
+
return readFilesForScope(scope).get(fullPath);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function isSnapshotStale(stat, snapshot, fullPath = '', readCache = null) {
|
|
160
|
+
return isSnapshotStaleImpl(stat, snapshot, {
|
|
161
|
+
fullPath,
|
|
162
|
+
readCache,
|
|
163
|
+
readTextForSnapshotCheck,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function readContentIfSnapshotHashMatches(fullPath, snapshot, readCache = null, st = null) {
|
|
168
|
+
return readContentIfSnapshotHashMatchesImpl(fullPath, snapshot, {
|
|
169
|
+
readCache,
|
|
170
|
+
st,
|
|
171
|
+
readTextForSnapshotCheck,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { readFile, stat } from 'fs/promises';
|
|
2
|
+
import { open } from 'fs/promises';
|
|
3
|
+
import { createRequire } from 'module';
|
|
4
|
+
import { READ_MAX_SIZE_BYTES } from './read-constants.mjs';
|
|
5
|
+
import { imageBlocksFromBuffer } from './read-image-resize.mjs';
|
|
6
|
+
|
|
7
|
+
const requireCjs = createRequire(import.meta.url);
|
|
8
|
+
const DEFAULT_READ_MAX_OUTPUT_BYTES = 100 * 1024;
|
|
9
|
+
|
|
10
|
+
// PDFs at or under this size are emitted as an Anthropic base64 document
|
|
11
|
+
// block (the model reads the rendered PDF directly). Mirrors CC's
|
|
12
|
+
// PDF_TARGET_RAW_SIZE (20MB raw → ~27MB base64, under the 32MB request cap).
|
|
13
|
+
// Larger PDFs fall back to pdf-parse TEXT extraction.
|
|
14
|
+
const PDF_DOCUMENT_MAX_BYTES = 20 * 1024 * 1024;
|
|
15
|
+
|
|
16
|
+
// %PDF- magic bytes (0x25 0x50 0x44 0x46 0x2D). A document block must only be
|
|
17
|
+
// emitted for a real PDF — sending a non-PDF blob as application/pdf would
|
|
18
|
+
// poison the conversation history (the API/model rejects the malformed block).
|
|
19
|
+
const PDF_MAGIC = Buffer.from('%PDF-', 'latin1');
|
|
20
|
+
|
|
21
|
+
// Per-output text ceiling inside a notebook. A single cell output larger than
|
|
22
|
+
// this is replaced with a jq hint (port of CC's large-notebook guidance) so a
|
|
23
|
+
// runaway stdout / data dump can't blow the read budget.
|
|
24
|
+
const IPYNB_OUTPUT_MAX_CHARS = 10_000;
|
|
25
|
+
|
|
26
|
+
// Read the leading bytes and confirm the %PDF- magic header. Returns false on
|
|
27
|
+
// any IO error (caller treats a non-PDF as text-fallback).
|
|
28
|
+
async function fileStartsWithPdfMagic(fullPath) {
|
|
29
|
+
let fh;
|
|
30
|
+
try {
|
|
31
|
+
fh = await open(fullPath, 'r');
|
|
32
|
+
const head = Buffer.alloc(PDF_MAGIC.length);
|
|
33
|
+
const { bytesRead } = await fh.read(head, 0, PDF_MAGIC.length, 0);
|
|
34
|
+
return bytesRead === PDF_MAGIC.length && head.equals(PDF_MAGIC);
|
|
35
|
+
} catch {
|
|
36
|
+
return false;
|
|
37
|
+
} finally {
|
|
38
|
+
if (fh) { try { await fh.close(); } catch {} }
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Validate / parse a pages arg ("N" or "N-M", 1-based, span <=20). Returns
|
|
43
|
+
// { filter } on success, { error } (a string) on rejection, or { filter: null }
|
|
44
|
+
// when no pages arg was supplied.
|
|
45
|
+
function parsePagesArg(pagesArg) {
|
|
46
|
+
if (pagesArg == null || pagesArg === '') return { filter: null };
|
|
47
|
+
if (typeof pagesArg !== 'string') {
|
|
48
|
+
return { error: `Error: pages must be a string like "1-5"; got ${typeof pagesArg}` };
|
|
49
|
+
}
|
|
50
|
+
const trimmed = pagesArg.trim();
|
|
51
|
+
const m = trimmed.match(/^(\d+)(?:-(\d+))?$/);
|
|
52
|
+
if (!m) {
|
|
53
|
+
return { error: `Error: pages "${trimmed}" not in "N" or "N-M" form (1-based positive integers)` };
|
|
54
|
+
}
|
|
55
|
+
const from = parseInt(m[1], 10);
|
|
56
|
+
const to = m[2] ? parseInt(m[2], 10) : from;
|
|
57
|
+
if (from < 1 || to < 1) {
|
|
58
|
+
return { error: `Error: pages "${trimmed}" out of range — page numbers are 1-based` };
|
|
59
|
+
}
|
|
60
|
+
if (to < from) {
|
|
61
|
+
return { error: `Error: pages "${trimmed}" inverted — end (${to}) precedes start (${from})` };
|
|
62
|
+
}
|
|
63
|
+
if (to - from + 1 > 20) {
|
|
64
|
+
return { error: `Error: pages "${trimmed}" spans ${to - from + 1} pages; max 20 per request — narrow the range` };
|
|
65
|
+
}
|
|
66
|
+
return { filter: { from, to } };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// pdf-parse TEXT extraction. Fallback path for PDFs over the document-block
|
|
70
|
+
// size cap, and the always-path when a page range is requested (a base64
|
|
71
|
+
// document block can't be page-filtered, so a narrowed read keeps using text).
|
|
72
|
+
async function extractPdfTextBody(fullPath, pageFilter, maxOutputBytes) {
|
|
73
|
+
const pdfParse = requireCjs('pdf-parse');
|
|
74
|
+
const buf = await readFile(fullPath);
|
|
75
|
+
const pageTexts = [];
|
|
76
|
+
const data = await pdfParse(buf, {
|
|
77
|
+
pagerender: (pageData) => {
|
|
78
|
+
// pdf-parse exposes either `pageNumber` (1-based, pdf.js) or
|
|
79
|
+
// `_pageIndex` (0-based, internal); preferring pageIndex+1 over
|
|
80
|
+
// pageNumber dropped/duplicated pages when pdf.js renumbered with
|
|
81
|
+
// annotations or oddball page trees. Use pageNumber first.
|
|
82
|
+
const pageNum = (typeof pageData.pageNumber === 'number')
|
|
83
|
+
? pageData.pageNumber
|
|
84
|
+
: ((pageData._pageIndex ?? pageData.pageIndex ?? 0) + 1);
|
|
85
|
+
if (pageFilter && (pageNum < pageFilter.from || pageNum > pageFilter.to)) return Promise.resolve('');
|
|
86
|
+
return pageData.getTextContent().then((tc) => {
|
|
87
|
+
const text = tc.items.map((i) => i.str).join(' ');
|
|
88
|
+
pageTexts.push({ page: pageNum, text });
|
|
89
|
+
return text;
|
|
90
|
+
});
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
let out = pageFilter
|
|
94
|
+
? pageTexts.map((p) => `--- Page ${p.page} ---\n${p.text}`).join('\n\n')
|
|
95
|
+
: (data.text || '');
|
|
96
|
+
if (out.length > maxOutputBytes) {
|
|
97
|
+
out = out.slice(0, maxOutputBytes) + `\n\n... [PDF output truncated at ${Math.round(maxOutputBytes / 1024)} KB; use pages param to narrow]`;
|
|
98
|
+
}
|
|
99
|
+
return out || '(no text content extracted from PDF)';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export async function extractPdfText(fullPath, pagesArg, { maxOutputBytes = DEFAULT_READ_MAX_OUTPUT_BYTES, textOnly = false } = {}) {
|
|
103
|
+
try {
|
|
104
|
+
let pdfStat;
|
|
105
|
+
try { pdfStat = await stat(fullPath); } catch (e) {
|
|
106
|
+
return `Error: pdf stat failed — ${e instanceof Error ? e.message : String(e)}`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const pages = parsePagesArg(pagesArg);
|
|
110
|
+
if (pages.error) return pages.error;
|
|
111
|
+
|
|
112
|
+
// Document-block path: whole-PDF read, no page range, within the size
|
|
113
|
+
// cap, and confirmed %PDF- magic. Emits a base64 document block the
|
|
114
|
+
// model reads natively (figures, layout, tables) instead of lossy
|
|
115
|
+
// pdf-parse text. The magic-byte guard prevents a non-PDF (mislabelled
|
|
116
|
+
// extension) from poisoning history as a malformed document block.
|
|
117
|
+
// textOnly (batch context) skips the block and always emits text.
|
|
118
|
+
if (!textOnly && !pages.filter && pdfStat.size <= PDF_DOCUMENT_MAX_BYTES) {
|
|
119
|
+
if (await fileStartsWithPdfMagic(fullPath)) {
|
|
120
|
+
const buf = await readFile(fullPath);
|
|
121
|
+
return {
|
|
122
|
+
content: [{
|
|
123
|
+
type: 'document',
|
|
124
|
+
source: {
|
|
125
|
+
type: 'base64',
|
|
126
|
+
media_type: 'application/pdf',
|
|
127
|
+
data: buf.toString('base64'),
|
|
128
|
+
},
|
|
129
|
+
}],
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
// Not a real PDF — fall through to pdf-parse, which surfaces a
|
|
133
|
+
// clean parse error rather than emitting a bad document block.
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// TEXT fallback: >20MB PDFs, page-filtered reads, or non-magic files.
|
|
137
|
+
return await extractPdfTextBody(fullPath, pages.filter, maxOutputBytes);
|
|
138
|
+
} catch (err) {
|
|
139
|
+
return `Error: pdf-parse failed — ${err instanceof Error ? err.message : String(err)}`;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export async function extractIpynbText(fullPath, { maxOutputBytes = DEFAULT_READ_MAX_OUTPUT_BYTES, hasRangeArgs = false, textOnly = false } = {}) {
|
|
144
|
+
// Range args (offset/limit/line) don't map cleanly onto a Jupyter
|
|
145
|
+
// notebook: cells aren't line-addressable in the source JSON, so applying
|
|
146
|
+
// offset:200 against rendered code+markdown would slice mid-cell without
|
|
147
|
+
// matching the line numbers a follow-up `edit` would target. Refuse the
|
|
148
|
+
// range up front and direct the caller to the underlying .ipynb JSON.
|
|
149
|
+
if (hasRangeArgs) {
|
|
150
|
+
return 'Error: range args (offset/limit/line) are not supported for .ipynb extraction — cells are not line-addressable; rename to .json or call read on a converted file for line-level access';
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
let nbStat;
|
|
154
|
+
try { nbStat = await stat(fullPath); } catch (e) {
|
|
155
|
+
return `Error: ipynb stat failed — ${e instanceof Error ? e.message : String(e)}`;
|
|
156
|
+
}
|
|
157
|
+
if (nbStat.size > READ_MAX_SIZE_BYTES) {
|
|
158
|
+
return `Error: notebook too large (size: ${nbStat.size}B, cap: ${READ_MAX_SIZE_BYTES}B) — use a cell range to narrow`;
|
|
159
|
+
}
|
|
160
|
+
const raw = await readFile(fullPath, 'utf-8');
|
|
161
|
+
const nb = JSON.parse(raw);
|
|
162
|
+
const cells = Array.isArray(nb.cells) ? nb.cells : [];
|
|
163
|
+
|
|
164
|
+
// Accumulate rendered cells as text, flushing to a text block whenever
|
|
165
|
+
// an image output is hit so block ORDER matches notebook order.
|
|
166
|
+
const blocks = [];
|
|
167
|
+
const textParts = [];
|
|
168
|
+
let textLen = 0;
|
|
169
|
+
const flushText = () => {
|
|
170
|
+
if (textParts.length === 0) return;
|
|
171
|
+
blocks.push({ type: 'text', text: textParts.join('\n\n') });
|
|
172
|
+
textParts.length = 0;
|
|
173
|
+
};
|
|
174
|
+
const pushText = (s) => { textParts.push(s); textLen += s.length; };
|
|
175
|
+
|
|
176
|
+
let cellIndex = -1;
|
|
177
|
+
for (const cell of cells) {
|
|
178
|
+
cellIndex += 1;
|
|
179
|
+
const src = Array.isArray(cell.source) ? cell.source.join('') : (cell.source || '');
|
|
180
|
+
if (cell.cell_type === 'markdown') {
|
|
181
|
+
pushText(src);
|
|
182
|
+
} else if (cell.cell_type === 'code') {
|
|
183
|
+
let block = src;
|
|
184
|
+
const outputs = Array.isArray(cell.outputs) ? cell.outputs : [];
|
|
185
|
+
// Collect image outputs to emit AFTER this cell's code block.
|
|
186
|
+
const pendingImages = [];
|
|
187
|
+
for (const out of outputs) {
|
|
188
|
+
const data = out.data || {};
|
|
189
|
+
if (data['text/plain'] || out.text) {
|
|
190
|
+
const rawTxt = data['text/plain']
|
|
191
|
+
? (Array.isArray(data['text/plain']) ? data['text/plain'].join('') : data['text/plain'])
|
|
192
|
+
: (Array.isArray(out.text) ? out.text.join('') : out.text);
|
|
193
|
+
// Port CC behaviour: a single huge output is replaced
|
|
194
|
+
// with a jq hint rather than dumped inline.
|
|
195
|
+
if (typeof rawTxt === 'string' && rawTxt.length > IPYNB_OUTPUT_MAX_CHARS) {
|
|
196
|
+
block += `\n# Output: [large output omitted — ${rawTxt.length} chars; inspect with: cat "${fullPath}" | jq '.cells[${cellIndex}].outputs']`;
|
|
197
|
+
} else {
|
|
198
|
+
block += '\n# Output:\n' + rawTxt;
|
|
199
|
+
}
|
|
200
|
+
} else if (data['image/png'] || data['image/jpeg']) {
|
|
201
|
+
const isPng = !!data['image/png'];
|
|
202
|
+
const b64 = isPng ? data['image/png'] : data['image/jpeg'];
|
|
203
|
+
const b64str = Array.isArray(b64) ? b64.join('') : b64;
|
|
204
|
+
pendingImages.push({ mimeType: isPng ? 'image/png' : 'image/jpeg', b64: b64str });
|
|
205
|
+
block += `\n# Output: [image output — cell ${cellIndex}]`;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
pushText('```python\n' + block + '\n```');
|
|
209
|
+
// Embed each image output as a real image block (resized via the
|
|
210
|
+
// shared helper). On fallback (sharp absent / decode failure) the
|
|
211
|
+
// image is left as the text placeholder already in `block`.
|
|
212
|
+
// textOnly (batch context) skips embedding entirely — the text
|
|
213
|
+
// placeholder in `block` is the only representation.
|
|
214
|
+
if (!textOnly && pendingImages.length > 0) {
|
|
215
|
+
flushText();
|
|
216
|
+
for (const img of pendingImages) {
|
|
217
|
+
let buf;
|
|
218
|
+
try { buf = Buffer.from(img.b64, 'base64'); } catch { buf = null; }
|
|
219
|
+
if (!buf || buf.length === 0) continue;
|
|
220
|
+
const built = await imageBlocksFromBuffer(buf, img.mimeType, { sourcePath: `${fullPath} [cell ${cellIndex}]` });
|
|
221
|
+
if (built) {
|
|
222
|
+
if (built.textBlock) blocks.push(built.textBlock);
|
|
223
|
+
blocks.push(built.imageBlock);
|
|
224
|
+
}
|
|
225
|
+
// built === null → sharp unavailable; placeholder text already emitted.
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
flushText();
|
|
231
|
+
|
|
232
|
+
// Output-byte cap on the combined TEXT. Image blocks are not counted
|
|
233
|
+
// (they're already size-bounded by the resize helper).
|
|
234
|
+
if (textLen > maxOutputBytes) {
|
|
235
|
+
// Trim trailing text blocks until under the cap, then mark the cut.
|
|
236
|
+
let running = 0;
|
|
237
|
+
const capped = [];
|
|
238
|
+
let truncated = false;
|
|
239
|
+
for (const b of blocks) {
|
|
240
|
+
if (b.type !== 'text') { capped.push(b); continue; }
|
|
241
|
+
if (running >= maxOutputBytes) { truncated = true; continue; }
|
|
242
|
+
if (running + b.text.length > maxOutputBytes) {
|
|
243
|
+
capped.push({ type: 'text', text: b.text.slice(0, maxOutputBytes - running) });
|
|
244
|
+
running = maxOutputBytes;
|
|
245
|
+
truncated = true;
|
|
246
|
+
} else {
|
|
247
|
+
capped.push(b);
|
|
248
|
+
running += b.text.length;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
if (truncated) capped.push({ type: 'text', text: `\n\n... [notebook output truncated at ${Math.round(maxOutputBytes / 1024)} KB]` });
|
|
252
|
+
blocks.length = 0;
|
|
253
|
+
blocks.push(...capped);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const hasImageBlock = blocks.some((b) => b.type === 'image');
|
|
257
|
+
if (blocks.length === 0) return '(empty notebook)';
|
|
258
|
+
// No embedded images → return the joined TEXT string (backwards
|
|
259
|
+
// compatible: works in batch reads and the loop's text path). Images
|
|
260
|
+
// present → return a content-block ARRAY so the model can see them.
|
|
261
|
+
if (!hasImageBlock) {
|
|
262
|
+
return blocks.map((b) => b.text).join('\n\n') || '(empty notebook)';
|
|
263
|
+
}
|
|
264
|
+
return { content: blocks };
|
|
265
|
+
} catch (err) {
|
|
266
|
+
return `Error: ipynb parse failed — ${err instanceof Error ? err.message : String(err)}`;
|
|
267
|
+
}
|
|
268
|
+
}
|