@otto-code/server 0.5.0
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/.env.example +31 -0
- package/README.md +111 -0
- package/dist/scripts/dev-runner.js +37 -0
- package/dist/scripts/mcp-stdio-socket-bridge-cli.mjs +62 -0
- package/dist/scripts/supervisor-entrypoint.js +141 -0
- package/dist/scripts/supervisor-log-config.js +31 -0
- package/dist/scripts/supervisor.js +252 -0
- package/dist/server/executable-resolution/executable-resolution.d.ts +10 -0
- package/dist/server/executable-resolution/executable-resolution.js +118 -0
- package/dist/server/executable-resolution/windows.d.ts +18 -0
- package/dist/server/executable-resolution/windows.js +65 -0
- package/dist/server/server/agent/activity-curator.d.ts +28 -0
- package/dist/server/server/agent/activity-curator.js +227 -0
- package/dist/server/server/agent/agent-archive.d.ts +11 -0
- package/dist/server/server/agent/agent-archive.js +16 -0
- package/dist/server/server/agent/agent-loading.d.ts +14 -0
- package/dist/server/server/agent/agent-loading.js +54 -0
- package/dist/server/server/agent/agent-manager.d.ts +470 -0
- package/dist/server/server/agent/agent-manager.js +3049 -0
- package/dist/server/server/agent/agent-personalities.d.ts +59 -0
- package/dist/server/server/agent/agent-personalities.js +103 -0
- package/dist/server/server/agent/agent-projections.d.ts +35 -0
- package/dist/server/server/agent/agent-projections.js +488 -0
- package/dist/server/server/agent/agent-prompt.d.ts +76 -0
- package/dist/server/server/agent/agent-prompt.js +230 -0
- package/dist/server/server/agent/agent-response-loop.d.ts +72 -0
- package/dist/server/server/agent/agent-response-loop.js +333 -0
- package/dist/server/server/agent/agent-sdk-types.d.ts +736 -0
- package/dist/server/server/agent/agent-sdk-types.js +22 -0
- package/dist/server/server/agent/agent-storage.d.ts +147 -0
- package/dist/server/server/agent/agent-storage.js +326 -0
- package/dist/server/server/agent/agent-stream-coalescer.d.ts +41 -0
- package/dist/server/server/agent/agent-stream-coalescer.js +166 -0
- package/dist/server/server/agent/agent-timeline-store-types.d.ts +50 -0
- package/dist/server/server/agent/agent-timeline-store-types.js +2 -0
- package/dist/server/server/agent/agent-timeline-store.d.ts +27 -0
- package/dist/server/server/agent/agent-timeline-store.js +230 -0
- package/dist/server/server/agent/audio-utils.d.ts +3 -0
- package/dist/server/server/agent/audio-utils.js +19 -0
- package/dist/server/server/agent/create-agent/create.d.ts +91 -0
- package/dist/server/server/agent/create-agent/create.js +370 -0
- package/dist/server/server/agent/create-agent-lifecycle-dispatch.d.ts +54 -0
- package/dist/server/server/agent/create-agent-lifecycle-dispatch.js +152 -0
- package/dist/server/server/agent/create-agent-mode.d.ts +13 -0
- package/dist/server/server/agent/create-agent-mode.js +62 -0
- package/dist/server/server/agent/create-agent-title.d.ts +10 -0
- package/dist/server/server/agent/create-agent-title.js +34 -0
- package/dist/server/server/agent/dictation-debug.d.ts +13 -0
- package/dist/server/server/agent/dictation-debug.js +50 -0
- package/dist/server/server/agent/effort-levels.d.ts +2 -0
- package/dist/server/server/agent/effort-levels.js +7 -0
- package/dist/server/server/agent/foreground-run-state.d.ts +50 -0
- package/dist/server/server/agent/foreground-run-state.js +160 -0
- package/dist/server/server/agent/import-sessions.d.ts +47 -0
- package/dist/server/server/agent/import-sessions.js +145 -0
- package/dist/server/server/agent/lifecycle-command.d.ts +73 -0
- package/dist/server/server/agent/lifecycle-command.js +92 -0
- package/dist/server/server/agent/mcp-server.d.ts +5 -0
- package/dist/server/server/agent/mcp-server.js +66 -0
- package/dist/server/server/agent/mcp-shared.d.ts +192 -0
- package/dist/server/server/agent/mcp-shared.js +201 -0
- package/dist/server/server/agent/pcm16-resampler.d.ts +14 -0
- package/dist/server/server/agent/pcm16-resampler.js +63 -0
- package/dist/server/server/agent/permission-response.d.ts +15 -0
- package/dist/server/server/agent/permission-response.js +14 -0
- package/dist/server/server/agent/personality-stats-store.d.ts +23 -0
- package/dist/server/server/agent/personality-stats-store.js +65 -0
- package/dist/server/server/agent/prompt-attachments.d.ts +7 -0
- package/dist/server/server/agent/prompt-attachments.js +117 -0
- package/dist/server/server/agent/provider-history-timestamps.d.ts +2 -0
- package/dist/server/server/agent/provider-history-timestamps.js +16 -0
- package/dist/server/server/agent/provider-launch-config.d.ts +43 -0
- package/dist/server/server/agent/provider-launch-config.js +167 -0
- package/dist/server/server/agent/provider-notices.d.ts +3 -0
- package/dist/server/server/agent/provider-notices.js +5 -0
- package/dist/server/server/agent/provider-registry.d.ts +41 -0
- package/dist/server/server/agent/provider-registry.js +532 -0
- package/dist/server/server/agent/provider-session-import.d.ts +10 -0
- package/dist/server/server/agent/provider-session-import.js +49 -0
- package/dist/server/server/agent/provider-snapshot-manager.d.ts +142 -0
- package/dist/server/server/agent/provider-snapshot-manager.js +690 -0
- package/dist/server/server/agent/providers/acp-agent.d.ts +353 -0
- package/dist/server/server/agent/providers/acp-agent.js +2439 -0
- package/dist/server/server/agent/providers/claude/agent.d.ts +66 -0
- package/dist/server/server/agent/providers/claude/agent.js +4535 -0
- package/dist/server/server/agent/providers/claude/feature-definitions.d.ts +8 -0
- package/dist/server/server/agent/providers/claude/feature-definitions.js +24 -0
- package/dist/server/server/agent/providers/claude/model-manifest.d.ts +90 -0
- package/dist/server/server/agent/providers/claude/model-manifest.js +205 -0
- package/dist/server/server/agent/providers/claude/models.d.ts +11 -0
- package/dist/server/server/agent/providers/claude/models.js +97 -0
- package/dist/server/server/agent/providers/claude/partial-json.d.ts +5 -0
- package/dist/server/server/agent/providers/claude/partial-json.js +306 -0
- package/dist/server/server/agent/providers/claude/project-dir.d.ts +6 -0
- package/dist/server/server/agent/providers/claude/project-dir.js +57 -0
- package/dist/server/server/agent/providers/claude/query.d.ts +17 -0
- package/dist/server/server/agent/providers/claude/query.js +86 -0
- package/dist/server/server/agent/providers/claude/rewind.d.ts +30 -0
- package/dist/server/server/agent/providers/claude/rewind.js +30 -0
- package/dist/server/server/agent/providers/claude/sidechain-tracker.d.ts +22 -0
- package/dist/server/server/agent/providers/claude/sidechain-tracker.js +232 -0
- package/dist/server/server/agent/providers/claude/task-notification-tool-call.d.ts +35 -0
- package/dist/server/server/agent/providers/claude/task-notification-tool-call.js +272 -0
- package/dist/server/server/agent/providers/claude/test-rewind-claude-sdk.d.ts +24 -0
- package/dist/server/server/agent/providers/claude/test-rewind-claude-sdk.js +23 -0
- package/dist/server/server/agent/providers/claude/tool-call-detail-parser.d.ts +3 -0
- package/dist/server/server/agent/providers/claude/tool-call-detail-parser.js +130 -0
- package/dist/server/server/agent/providers/claude/tool-call-mapper.d.ts +16 -0
- package/dist/server/server/agent/providers/claude/tool-call-mapper.js +129 -0
- package/dist/server/server/agent/providers/codex/app-server-transport.d.ts +90 -0
- package/dist/server/server/agent/providers/codex/app-server-transport.js +262 -0
- package/dist/server/server/agent/providers/codex/rewind.d.ts +21 -0
- package/dist/server/server/agent/providers/codex/rewind.js +46 -0
- package/dist/server/server/agent/providers/codex/test-utils/fake-app-server.d.ts +36 -0
- package/dist/server/server/agent/providers/codex/test-utils/fake-app-server.js +217 -0
- package/dist/server/server/agent/providers/codex/tool-call-detail-parser.d.ts +12 -0
- package/dist/server/server/agent/providers/codex/tool-call-detail-parser.js +104 -0
- package/dist/server/server/agent/providers/codex/tool-call-mapper.d.ts +24 -0
- package/dist/server/server/agent/providers/codex/tool-call-mapper.js +830 -0
- package/dist/server/server/agent/providers/codex-app-server-agent.d.ts +308 -0
- package/dist/server/server/agent/providers/codex-app-server-agent.js +4515 -0
- package/dist/server/server/agent/providers/codex-feature-definitions.d.ts +11 -0
- package/dist/server/server/agent/providers/codex-feature-definitions.js +45 -0
- package/dist/server/server/agent/providers/copilot-acp-agent.d.ts +26 -0
- package/dist/server/server/agent/providers/copilot-acp-agent.js +188 -0
- package/dist/server/server/agent/providers/cursor-acp-agent.d.ts +15 -0
- package/dist/server/server/agent/providers/cursor-acp-agent.js +18 -0
- package/dist/server/server/agent/providers/diagnostic-utils.d.ts +36 -0
- package/dist/server/server/agent/providers/diagnostic-utils.js +355 -0
- package/dist/server/server/agent/providers/generic-acp-agent.d.ts +42 -0
- package/dist/server/server/agent/providers/generic-acp-agent.js +160 -0
- package/dist/server/server/agent/providers/kiro-acp-agent.d.ts +17 -0
- package/dist/server/server/agent/providers/kiro-acp-agent.js +78 -0
- package/dist/server/server/agent/providers/mock-load-test-agent.d.ts +85 -0
- package/dist/server/server/agent/providers/mock-load-test-agent.js +1010 -0
- package/dist/server/server/agent/providers/mock-slow-provider.d.ts +14 -0
- package/dist/server/server/agent/providers/mock-slow-provider.js +39 -0
- package/dist/server/server/agent/providers/openai-compat-agent.d.ts +422 -0
- package/dist/server/server/agent/providers/openai-compat-agent.js +2536 -0
- package/dist/server/server/agent/providers/openai-compat-feature-definitions.d.ts +40 -0
- package/dist/server/server/agent/providers/openai-compat-feature-definitions.js +82 -0
- package/dist/server/server/agent/providers/openai-compat-mcp.d.ts +87 -0
- package/dist/server/server/agent/providers/openai-compat-mcp.js +353 -0
- package/dist/server/server/agent/providers/openai-compat-otto-tool-permissions.d.ts +29 -0
- package/dist/server/server/agent/providers/openai-compat-otto-tool-permissions.js +96 -0
- package/dist/server/server/agent/providers/openai-compat-tools.d.ts +69 -0
- package/dist/server/server/agent/providers/openai-compat-tools.js +1224 -0
- package/dist/server/server/agent/providers/opencode/paths.d.ts +2 -0
- package/dist/server/server/agent/providers/opencode/paths.js +7 -0
- package/dist/server/server/agent/providers/opencode/rewind.d.ts +18 -0
- package/dist/server/server/agent/providers/opencode/rewind.js +14 -0
- package/dist/server/server/agent/providers/opencode/server-manager.d.ts +91 -0
- package/dist/server/server/agent/providers/opencode/server-manager.js +425 -0
- package/dist/server/server/agent/providers/opencode/test-server-manager.d.ts +25 -0
- package/dist/server/server/agent/providers/opencode/test-server-manager.js +39 -0
- package/dist/server/server/agent/providers/opencode/test-utils/test-opencode-harness.d.ts +92 -0
- package/dist/server/server/agent/providers/opencode/test-utils/test-opencode-harness.js +260 -0
- package/dist/server/server/agent/providers/opencode/tool-call-detail-parser.d.ts +3 -0
- package/dist/server/server/agent/providers/opencode/tool-call-detail-parser.js +256 -0
- package/dist/server/server/agent/providers/opencode/tool-call-mapper.d.ts +13 -0
- package/dist/server/server/agent/providers/opencode/tool-call-mapper.js +53 -0
- package/dist/server/server/agent/providers/opencode-agent.d.ts +286 -0
- package/dist/server/server/agent/providers/opencode-agent.js +2830 -0
- package/dist/server/server/agent/providers/pi/agent.d.ts +132 -0
- package/dist/server/server/agent/providers/pi/agent.js +1649 -0
- package/dist/server/server/agent/providers/pi/cli-runtime.d.ts +21 -0
- package/dist/server/server/agent/providers/pi/cli-runtime.js +261 -0
- package/dist/server/server/agent/providers/pi/history-mapper.d.ts +9 -0
- package/dist/server/server/agent/providers/pi/history-mapper.js +140 -0
- package/dist/server/server/agent/providers/pi/rewind.d.ts +8 -0
- package/dist/server/server/agent/providers/pi/rewind.js +8 -0
- package/dist/server/server/agent/providers/pi/rpc-types.d.ts +208 -0
- package/dist/server/server/agent/providers/pi/rpc-types.js +2 -0
- package/dist/server/server/agent/providers/pi/runtime.d.ts +57 -0
- package/dist/server/server/agent/providers/pi/runtime.js +56 -0
- package/dist/server/server/agent/providers/pi/session-descriptor.d.ts +16 -0
- package/dist/server/server/agent/providers/pi/session-descriptor.js +346 -0
- package/dist/server/server/agent/providers/pi/test-utils/fake-pi.d.ts +83 -0
- package/dist/server/server/agent/providers/pi/test-utils/fake-pi.js +184 -0
- package/dist/server/server/agent/providers/pi/tool-call-mapper.d.ts +112 -0
- package/dist/server/server/agent/providers/pi/tool-call-mapper.js +284 -0
- package/dist/server/server/agent/providers/provider-image-output.d.ts +25 -0
- package/dist/server/server/agent/providers/provider-image-output.js +111 -0
- package/dist/server/server/agent/providers/provider-runner.d.ts +27 -0
- package/dist/server/server/agent/providers/provider-runner.js +81 -0
- package/dist/server/server/agent/providers/test-utils/session-stream-adapter.d.ts +3 -0
- package/dist/server/server/agent/providers/test-utils/session-stream-adapter.js +60 -0
- package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts +884 -0
- package/dist/server/server/agent/providers/tool-call-detail-primitives.js +778 -0
- package/dist/server/server/agent/providers/tool-call-mapper-utils.d.ts +25 -0
- package/dist/server/server/agent/providers/tool-call-mapper-utils.js +208 -0
- package/dist/server/server/agent/providers/trae-acp-agent.d.ts +15 -0
- package/dist/server/server/agent/providers/trae-acp-agent.js +18 -0
- package/dist/server/server/agent/recordings-debug.d.ts +3 -0
- package/dist/server/server/agent/recordings-debug.js +19 -0
- package/dist/server/server/agent/rewind/rewind.d.ts +10 -0
- package/dist/server/server/agent/rewind/rewind.js +29 -0
- package/dist/server/server/agent/rewind/test-rewind-session.d.ts +48 -0
- package/dist/server/server/agent/rewind/test-rewind-session.js +88 -0
- package/dist/server/server/agent/runtime-mcp-config.d.ts +14 -0
- package/dist/server/server/agent/runtime-mcp-config.js +53 -0
- package/dist/server/server/agent/structured-generation-providers.d.ts +29 -0
- package/dist/server/server/agent/structured-generation-providers.js +236 -0
- package/dist/server/server/agent/stt-debug.d.ts +10 -0
- package/dist/server/server/agent/stt-debug.js +33 -0
- package/dist/server/server/agent/stt-manager.d.ts +38 -0
- package/dist/server/server/agent/stt-manager.js +246 -0
- package/dist/server/server/agent/system-prompt.d.ts +2 -0
- package/dist/server/server/agent/system-prompt.js +8 -0
- package/dist/server/server/agent/timeline-append.d.ts +10 -0
- package/dist/server/server/agent/timeline-append.js +27 -0
- package/dist/server/server/agent/timeline-projection.d.ts +71 -0
- package/dist/server/server/agent/timeline-projection.js +373 -0
- package/dist/server/server/agent/tools/otto-tools.d.ts +80 -0
- package/dist/server/server/agent/tools/otto-tools.js +2912 -0
- package/dist/server/server/agent/tools/types.d.ts +36 -0
- package/dist/server/server/agent/tools/types.js +2 -0
- package/dist/server/server/agent/tts-debug.d.ts +8 -0
- package/dist/server/server/agent/tts-debug.js +24 -0
- package/dist/server/server/agent/tts-manager.d.ts +41 -0
- package/dist/server/server/agent/tts-manager.js +375 -0
- package/dist/server/server/agent-attention-policy.d.ts +29 -0
- package/dist/server/server/agent-attention-policy.js +36 -0
- package/dist/server/server/artifact/artifact-prompt.d.ts +2 -0
- package/dist/server/server/artifact/artifact-prompt.js +15 -0
- package/dist/server/server/artifact/artifact-service.d.ts +91 -0
- package/dist/server/server/artifact/artifact-service.js +419 -0
- package/dist/server/server/artifact/artifact-store.d.ts +24 -0
- package/dist/server/server/artifact/artifact-store.js +168 -0
- package/dist/server/server/artifact/artifact-watcher.d.ts +34 -0
- package/dist/server/server/artifact/artifact-watcher.js +146 -0
- package/dist/server/server/artifact/html-validator.d.ts +26 -0
- package/dist/server/server/artifact/html-validator.js +92 -0
- package/dist/server/server/atomic-file.d.ts +5 -0
- package/dist/server/server/atomic-file.js +19 -0
- package/dist/server/server/auth.d.ts +38 -0
- package/dist/server/server/auth.js +143 -0
- package/dist/server/server/auto-archive-on-merge/archive-if-safe.d.ts +40 -0
- package/dist/server/server/auto-archive-on-merge/archive-if-safe.js +92 -0
- package/dist/server/server/auto-archive-on-merge/index.d.ts +8 -0
- package/dist/server/server/auto-archive-on-merge/index.js +15 -0
- package/dist/server/server/bootstrap.d.ts +127 -0
- package/dist/server/server/bootstrap.js +1331 -0
- package/dist/server/server/browser-tools/broker.d.ts +45 -0
- package/dist/server/server/browser-tools/broker.js +390 -0
- package/dist/server/server/browser-tools/errors.d.ts +22 -0
- package/dist/server/server/browser-tools/errors.js +27 -0
- package/dist/server/server/browser-tools/policy.d.ts +10 -0
- package/dist/server/server/browser-tools/policy.js +16 -0
- package/dist/server/server/browser-tools/tools.d.ts +24 -0
- package/dist/server/server/browser-tools/tools.js +967 -0
- package/dist/server/server/chat/chat-mentions.d.ts +47 -0
- package/dist/server/server/chat/chat-mentions.js +113 -0
- package/dist/server/server/chat/chat-service.d.ts +78 -0
- package/dist/server/server/chat/chat-service.js +337 -0
- package/dist/server/server/checkout/status-projection.d.ts +19 -0
- package/dist/server/server/checkout/status-projection.js +117 -0
- package/dist/server/server/checkout-diff-manager.d.ts +41 -0
- package/dist/server/server/checkout-diff-manager.js +189 -0
- package/dist/server/server/checkout-git-utils.d.ts +11 -0
- package/dist/server/server/checkout-git-utils.js +34 -0
- package/dist/server/server/client-message-id.d.ts +3 -0
- package/dist/server/server/client-message-id.js +12 -0
- package/dist/server/server/config.d.ts +22 -0
- package/dist/server/server/config.js +330 -0
- package/dist/server/server/connection-offer.d.ts +20 -0
- package/dist/server/server/connection-offer.js +59 -0
- package/dist/server/server/daemon-config-store.d.ts +44 -0
- package/dist/server/server/daemon-config-store.js +530 -0
- package/dist/server/server/daemon-keypair.d.ts +8 -0
- package/dist/server/server/daemon-keypair.js +42 -0
- package/dist/server/server/daemon-version.d.ts +5 -0
- package/dist/server/server/daemon-version.js +22 -0
- package/dist/server/server/daemon-worker.d.ts +2 -0
- package/dist/server/server/daemon-worker.js +251 -0
- package/dist/server/server/dictation/dictation-stream-manager.d.ts +87 -0
- package/dist/server/server/dictation/dictation-stream-manager.js +572 -0
- package/dist/server/server/exports.d.ts +24 -0
- package/dist/server/server/exports.js +24 -0
- package/dist/server/server/file-download/token-store.d.ts +24 -0
- package/dist/server/server/file-download/token-store.js +40 -0
- package/dist/server/server/file-explorer/code-index.d.ts +28 -0
- package/dist/server/server/file-explorer/code-index.js +127 -0
- package/dist/server/server/file-explorer/file-search.d.ts +72 -0
- package/dist/server/server/file-explorer/file-search.js +297 -0
- package/dist/server/server/file-explorer/file-watcher.d.ts +40 -0
- package/dist/server/server/file-explorer/file-watcher.js +0 -0
- package/dist/server/server/file-explorer/gitignore.d.ts +39 -0
- package/dist/server/server/file-explorer/gitignore.js +191 -0
- package/dist/server/server/file-explorer/service.d.ts +103 -0
- package/dist/server/server/file-explorer/service.js +420 -0
- package/dist/server/server/file-upload/index.d.ts +27 -0
- package/dist/server/server/file-upload/index.js +158 -0
- package/dist/server/server/hostnames.d.ts +13 -0
- package/dist/server/server/hostnames.js +94 -0
- package/dist/server/server/json-utils.d.ts +11 -0
- package/dist/server/server/json-utils.js +45 -0
- package/dist/server/server/lifecycle-reasons.d.ts +4 -0
- package/dist/server/server/lifecycle-reasons.js +6 -0
- package/dist/server/server/logger.d.ts +29 -0
- package/dist/server/server/logger.js +125 -0
- package/dist/server/server/loop-service.d.ts +234 -0
- package/dist/server/server/loop-service.js +804 -0
- package/dist/server/server/managed-processes/managed-processes.d.ts +76 -0
- package/dist/server/server/managed-processes/managed-processes.js +326 -0
- package/dist/server/server/messages.d.ts +9 -0
- package/dist/server/server/messages.js +29 -0
- package/dist/server/server/migrations/backfill-workspace-id.migration.d.ts +9 -0
- package/dist/server/server/migrations/backfill-workspace-id.migration.js +64 -0
- package/dist/server/server/otto-env.d.ts +13 -0
- package/dist/server/server/otto-env.js +53 -0
- package/dist/server/server/otto-home.d.ts +2 -0
- package/dist/server/server/otto-home.js +19 -0
- package/dist/server/server/otto-worktree-service.d.ts +52 -0
- package/dist/server/server/otto-worktree-service.js +294 -0
- package/dist/server/server/package-version.d.ts +19 -0
- package/dist/server/server/package-version.js +60 -0
- package/dist/server/server/pagination/cursor.d.ts +16 -0
- package/dist/server/server/pagination/cursor.js +62 -0
- package/dist/server/server/pagination/sortable-pager.d.ts +24 -0
- package/dist/server/server/pagination/sortable-pager.js +68 -0
- package/dist/server/server/pairing-offer.d.ts +18 -0
- package/dist/server/server/pairing-offer.js +47 -0
- package/dist/server/server/pairing-qr.d.ts +7 -0
- package/dist/server/server/pairing-qr.js +45 -0
- package/dist/server/server/path-utils.d.ts +5 -0
- package/dist/server/server/path-utils.js +34 -0
- package/dist/server/server/persisted-config.d.ts +417 -0
- package/dist/server/server/persisted-config.js +471 -0
- package/dist/server/server/persistence-hooks.d.ts +31 -0
- package/dist/server/server/persistence-hooks.js +93 -0
- package/dist/server/server/pid-lock.d.ts +32 -0
- package/dist/server/server/pid-lock.js +170 -0
- package/dist/server/server/preview/dev-server-manager.d.ts +108 -0
- package/dist/server/server/preview/dev-server-manager.js +426 -0
- package/dist/server/server/preview/launch-config.d.ts +37 -0
- package/dist/server/server/preview/launch-config.js +63 -0
- package/dist/server/server/preview/preview-tools.d.ts +30 -0
- package/dist/server/server/preview/preview-tools.js +249 -0
- package/dist/server/server/private-files.d.ts +6 -0
- package/dist/server/server/private-files.js +37 -0
- package/dist/server/server/process-diagnostics.d.ts +17 -0
- package/dist/server/server/process-diagnostics.js +22 -0
- package/dist/server/server/push/notifications.d.ts +9 -0
- package/dist/server/server/push/notifications.js +15 -0
- package/dist/server/server/push/push-service.d.ts +20 -0
- package/dist/server/server/push/push-service.js +66 -0
- package/dist/server/server/push/token-store.d.ts +18 -0
- package/dist/server/server/push/token-store.js +68 -0
- package/dist/server/server/relay-transport.d.ts +31 -0
- package/dist/server/server/relay-transport.js +514 -0
- package/dist/server/server/resolve-workspace-id-for-path.d.ts +3 -0
- package/dist/server/server/resolve-workspace-id-for-path.js +41 -0
- package/dist/server/server/resolve-worktree-creation-intent.d.ts +32 -0
- package/dist/server/server/resolve-worktree-creation-intent.js +121 -0
- package/dist/server/server/schedule/cron.d.ts +4 -0
- package/dist/server/server/schedule/cron.js +81 -0
- package/dist/server/server/schedule/service.d.ts +89 -0
- package/dist/server/server/schedule/service.js +882 -0
- package/dist/server/server/schedule/store.d.ts +27 -0
- package/dist/server/server/schedule/store.js +190 -0
- package/dist/server/server/script-health-monitor.d.ts +39 -0
- package/dist/server/server/script-health-monitor.js +159 -0
- package/dist/server/server/script-proxy.d.ts +3 -0
- package/dist/server/server/script-proxy.js +2 -0
- package/dist/server/server/script-route-branch-handler.d.ts +10 -0
- package/dist/server/server/script-route-branch-handler.js +11 -0
- package/dist/server/server/script-status-projection.d.ts +36 -0
- package/dist/server/server/script-status-projection.js +200 -0
- package/dist/server/server/server-id.d.ts +17 -0
- package/dist/server/server/server-id.js +68 -0
- package/dist/server/server/service-proxy.d.ts +237 -0
- package/dist/server/server/service-proxy.js +714 -0
- package/dist/server/server/session/agent-config/agent-config-session.d.ts +59 -0
- package/dist/server/server/session/agent-config/agent-config-session.js +110 -0
- package/dist/server/server/session/agent-updates/agent-updates-service.d.ts +65 -0
- package/dist/server/server/session/agent-updates/agent-updates-service.js +232 -0
- package/dist/server/server/session/artifact/artifact-session.d.ts +45 -0
- package/dist/server/server/session/artifact/artifact-session.js +293 -0
- package/dist/server/server/session/chat/chat-schedule-loop-session.d.ts +120 -0
- package/dist/server/server/session/chat/chat-schedule-loop-session.js +489 -0
- package/dist/server/server/session/checkout/checkout-session.d.ts +146 -0
- package/dist/server/server/session/checkout/checkout-session.js +1000 -0
- package/dist/server/server/session/checkout/git-metadata-generator.d.ts +53 -0
- package/dist/server/server/session/checkout/git-metadata-generator.js +159 -0
- package/dist/server/server/session/daemon/daemon-self-update-session-controller.d.ts +32 -0
- package/dist/server/server/session/daemon/daemon-self-update-session-controller.js +88 -0
- package/dist/server/server/session/daemon/daemon-self-updater.d.ts +32 -0
- package/dist/server/server/session/daemon/daemon-self-updater.js +56 -0
- package/dist/server/server/session/daemon/daemon-session.d.ts +76 -0
- package/dist/server/server/session/daemon/daemon-session.js +148 -0
- package/dist/server/server/session/daemon/diagnostics.d.ts +41 -0
- package/dist/server/server/session/daemon/diagnostics.js +431 -0
- package/dist/server/server/session/daemon/install-origin.d.ts +7 -0
- package/dist/server/server/session/daemon/install-origin.js +64 -0
- package/dist/server/server/session/daemon/npm-global-cli.d.ts +29 -0
- package/dist/server/server/session/daemon/npm-global-cli.js +98 -0
- package/dist/server/server/session/files/workspace-files-session.d.ts +60 -0
- package/dist/server/server/session/files/workspace-files-session.js +518 -0
- package/dist/server/server/session/git-mutation/git-mutation-service.d.ts +34 -0
- package/dist/server/server/session/git-mutation/git-mutation-service.js +71 -0
- package/dist/server/server/session/project-config/project-config-session.d.ts +34 -0
- package/dist/server/server/session/project-config/project-config-session.js +125 -0
- package/dist/server/server/session/provider/provider-catalog-session.d.ts +74 -0
- package/dist/server/server/session/provider/provider-catalog-session.js +343 -0
- package/dist/server/server/session/voice/voice-session.d.ts +167 -0
- package/dist/server/server/session/voice/voice-session.js +912 -0
- package/dist/server/server/session/voice/voice-turn-controller.d.ts +38 -0
- package/dist/server/server/session/voice/voice-turn-controller.js +393 -0
- package/dist/server/server/session/workspace-git-observer/workspace-git-observer-service.d.ts +36 -0
- package/dist/server/server/session/workspace-git-observer/workspace-git-observer-service.js +134 -0
- package/dist/server/server/session/workspace-provisioning/workspace-provisioning-service.d.ts +34 -0
- package/dist/server/server/session/workspace-provisioning/workspace-provisioning-service.js +190 -0
- package/dist/server/server/session/workspace-scripts/workspace-scripts-service.d.ts +41 -0
- package/dist/server/server/session/workspace-scripts/workspace-scripts-service.js +100 -0
- package/dist/server/server/session.d.ts +450 -0
- package/dist/server/server/session.js +4763 -0
- package/dist/server/server/speech/audio.d.ts +10 -0
- package/dist/server/server/speech/audio.js +101 -0
- package/dist/server/server/speech/provider-resolver.d.ts +3 -0
- package/dist/server/server/speech/provider-resolver.js +7 -0
- package/dist/server/server/speech/providers/local/config.d.ts +30 -0
- package/dist/server/server/speech/providers/local/config.js +133 -0
- package/dist/server/server/speech/providers/local/models.d.ts +13 -0
- package/dist/server/server/speech/providers/local/models.js +21 -0
- package/dist/server/server/speech/providers/local/runtime.d.ts +31 -0
- package/dist/server/server/speech/providers/local/runtime.js +135 -0
- package/dist/server/server/speech/providers/local/sherpa/assets/silero_vad.onnx +0 -0
- package/dist/server/server/speech/providers/local/sherpa/model-catalog.d.ts +109 -0
- package/dist/server/server/speech/providers/local/sherpa/model-catalog.js +129 -0
- package/dist/server/server/speech/providers/local/sherpa/model-downloader.d.ts +16 -0
- package/dist/server/server/speech/providers/local/sherpa/model-downloader.js +195 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-offline-recognizer.d.ts +49 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-offline-recognizer.js +73 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.d.ts +9 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.js +107 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-realtime-session.d.ts +28 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-realtime-session.js +137 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-stt.d.ts +21 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-stt.js +133 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-runtime-env.d.ts +18 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-runtime-env.js +84 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-tts.d.ts +24 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-tts.js +145 -0
- package/dist/server/server/speech/providers/local/sherpa/silero-vad-provider.d.ts +19 -0
- package/dist/server/server/speech/providers/local/sherpa/silero-vad-provider.js +49 -0
- package/dist/server/server/speech/providers/local/sherpa/silero-vad-session.d.ts +38 -0
- package/dist/server/server/speech/providers/local/sherpa/silero-vad-session.js +176 -0
- package/dist/server/server/speech/providers/local/sherpa/tts-voices.d.ts +10 -0
- package/dist/server/server/speech/providers/local/sherpa/tts-voices.js +86 -0
- package/dist/server/server/speech/providers/local/worker-bytes.d.ts +4 -0
- package/dist/server/server/speech/providers/local/worker-bytes.js +9 -0
- package/dist/server/server/speech/providers/local/worker-client.d.ts +93 -0
- package/dist/server/server/speech/providers/local/worker-client.js +591 -0
- package/dist/server/server/speech/providers/local/worker-process.d.ts +2 -0
- package/dist/server/server/speech/providers/local/worker-process.js +270 -0
- package/dist/server/server/speech/providers/local/worker-protocol.d.ts +96 -0
- package/dist/server/server/speech/providers/local/worker-protocol.js +2 -0
- package/dist/server/server/speech/providers/openai/config.d.ts +19 -0
- package/dist/server/server/speech/providers/openai/config.js +160 -0
- package/dist/server/server/speech/providers/openai/runtime.d.ts +29 -0
- package/dist/server/server/speech/providers/openai/runtime.js +102 -0
- package/dist/server/server/speech/providers/openai/stt.d.ts +23 -0
- package/dist/server/server/speech/providers/openai/stt.js +209 -0
- package/dist/server/server/speech/providers/openai/tts.d.ts +20 -0
- package/dist/server/server/speech/providers/openai/tts.js +70 -0
- package/dist/server/server/speech/speech-config-resolver.d.ts +11 -0
- package/dist/server/server/speech/speech-config-resolver.js +132 -0
- package/dist/server/server/speech/speech-provider.d.ts +71 -0
- package/dist/server/server/speech/speech-provider.js +2 -0
- package/dist/server/server/speech/speech-runtime.d.ts +67 -0
- package/dist/server/server/speech/speech-runtime.js +615 -0
- package/dist/server/server/speech/speech-settings-options.d.ts +14 -0
- package/dist/server/server/speech/speech-settings-options.js +73 -0
- package/dist/server/server/speech/speech-types.d.ts +22 -0
- package/dist/server/server/speech/speech-types.js +8 -0
- package/dist/server/server/speech/turn-detection-provider.d.ts +23 -0
- package/dist/server/server/speech/turn-detection-provider.js +2 -0
- package/dist/server/server/utils/diff-highlighter.d.ts +57 -0
- package/dist/server/server/utils/diff-highlighter.js +279 -0
- package/dist/server/server/voice-config.d.ts +15 -0
- package/dist/server/server/voice-config.js +54 -0
- package/dist/server/server/voice-permission-policy.d.ts +4 -0
- package/dist/server/server/voice-permission-policy.js +13 -0
- package/dist/server/server/voice-types.d.ts +12 -0
- package/dist/server/server/voice-types.js +2 -0
- package/dist/server/server/web-ui.d.ts +10 -0
- package/dist/server/server/web-ui.js +205 -0
- package/dist/server/server/websocket/runtime-metrics.d.ts +94 -0
- package/dist/server/server/websocket/runtime-metrics.js +148 -0
- package/dist/server/server/websocket-server.d.ts +187 -0
- package/dist/server/server/websocket-server.js +1766 -0
- package/dist/server/server/workspace-archive-service.d.ts +65 -0
- package/dist/server/server/workspace-archive-service.js +229 -0
- package/dist/server/server/workspace-auto-name.d.ts +55 -0
- package/dist/server/server/workspace-auto-name.js +128 -0
- package/dist/server/server/workspace-bootstrap-dedupe.d.ts +34 -0
- package/dist/server/server/workspace-bootstrap-dedupe.js +23 -0
- package/dist/server/server/workspace-directory.d.ts +93 -0
- package/dist/server/server/workspace-directory.js +422 -0
- package/dist/server/server/workspace-git-metadata.d.ts +24 -0
- package/dist/server/server/workspace-git-metadata.js +55 -0
- package/dist/server/server/workspace-git-service.d.ts +270 -0
- package/dist/server/server/workspace-git-service.js +1423 -0
- package/dist/server/server/workspace-reconciliation-service.d.ts +57 -0
- package/dist/server/server/workspace-reconciliation-service.js +278 -0
- package/dist/server/server/workspace-registry-bootstrap.d.ts +13 -0
- package/dist/server/server/workspace-registry-bootstrap.js +121 -0
- package/dist/server/server/workspace-registry-model.d.ts +55 -0
- package/dist/server/server/workspace-registry-model.js +190 -0
- package/dist/server/server/workspace-registry.d.ts +117 -0
- package/dist/server/server/workspace-registry.js +194 -0
- package/dist/server/server/workspace-script-runtime-store.d.ts +28 -0
- package/dist/server/server/workspace-script-runtime-store.js +78 -0
- package/dist/server/server/workspace-service-env.d.ts +18 -0
- package/dist/server/server/workspace-service-env.js +85 -0
- package/dist/server/server/workspace-service-port-registry.d.ts +19 -0
- package/dist/server/server/workspace-service-port-registry.js +59 -0
- package/dist/server/server/worktree/commands.d.ts +55 -0
- package/dist/server/server/worktree/commands.js +107 -0
- package/dist/server/server/worktree-bootstrap.d.ts +76 -0
- package/dist/server/server/worktree-bootstrap.js +720 -0
- package/dist/server/server/worktree-branch-name-generator.d.ts +35 -0
- package/dist/server/server/worktree-branch-name-generator.js +91 -0
- package/dist/server/server/worktree-core.d.ts +31 -0
- package/dist/server/server/worktree-core.js +105 -0
- package/dist/server/server/worktree-errors.d.ts +12 -0
- package/dist/server/server/worktree-errors.js +28 -0
- package/dist/server/server/worktree-session.d.ts +152 -0
- package/dist/server/server/worktree-session.js +466 -0
- package/dist/server/services/git-hosting/bitbucket-cloud-service.d.ts +24 -0
- package/dist/server/services/git-hosting/bitbucket-cloud-service.js +754 -0
- package/dist/server/services/git-hosting/github-hosting-service.d.ts +4 -0
- package/dist/server/services/git-hosting/github-hosting-service.js +11 -0
- package/dist/server/services/git-hosting/hosting-http-client.d.ts +20 -0
- package/dist/server/services/git-hosting/hosting-http-client.js +131 -0
- package/dist/server/services/git-hosting/request-cache.d.ts +20 -0
- package/dist/server/services/git-hosting/request-cache.js +68 -0
- package/dist/server/services/git-hosting/resolver.d.ts +32 -0
- package/dist/server/services/git-hosting/resolver.js +152 -0
- package/dist/server/services/git-hosting/router.d.ts +4 -0
- package/dist/server/services/git-hosting/router.js +105 -0
- package/dist/server/services/git-hosting/status-poller.d.ts +39 -0
- package/dist/server/services/git-hosting/status-poller.js +149 -0
- package/dist/server/services/git-hosting/types.d.ts +53 -0
- package/dist/server/services/git-hosting/types.js +75 -0
- package/dist/server/services/github-service.d.ts +339 -0
- package/dist/server/services/github-service.js +2109 -0
- package/dist/server/services/quota-fetcher/manifest.d.ts +4 -0
- package/dist/server/services/quota-fetcher/manifest.js +52 -0
- package/dist/server/services/quota-fetcher/provider.d.ts +17 -0
- package/dist/server/services/quota-fetcher/provider.js +2 -0
- package/dist/server/services/quota-fetcher/providers/claude.d.ts +26 -0
- package/dist/server/services/quota-fetcher/providers/claude.js +217 -0
- package/dist/server/services/quota-fetcher/providers/codex.d.ts +23 -0
- package/dist/server/services/quota-fetcher/providers/codex.js +211 -0
- package/dist/server/services/quota-fetcher/providers/copilot.d.ts +17 -0
- package/dist/server/services/quota-fetcher/providers/copilot.js +75 -0
- package/dist/server/services/quota-fetcher/providers/cursor.d.ts +17 -0
- package/dist/server/services/quota-fetcher/providers/cursor.js +123 -0
- package/dist/server/services/quota-fetcher/providers/grok.d.ts +18 -0
- package/dist/server/services/quota-fetcher/providers/grok.js +89 -0
- package/dist/server/services/quota-fetcher/providers/kimi.d.ts +20 -0
- package/dist/server/services/quota-fetcher/providers/kimi.js +89 -0
- package/dist/server/services/quota-fetcher/providers/minimax.d.ts +29 -0
- package/dist/server/services/quota-fetcher/providers/minimax.js +227 -0
- package/dist/server/services/quota-fetcher/providers/zai.d.ts +17 -0
- package/dist/server/services/quota-fetcher/providers/zai.js +58 -0
- package/dist/server/services/quota-fetcher/service.d.ts +28 -0
- package/dist/server/services/quota-fetcher/service.js +58 -0
- package/dist/server/services/quota-fetcher/usage.d.ts +22 -0
- package/dist/server/services/quota-fetcher/usage.js +49 -0
- package/dist/server/terminal/activity/terminal-activity-tracker.d.ts +20 -0
- package/dist/server/terminal/activity/terminal-activity-tracker.js +59 -0
- package/dist/server/terminal/agent-hooks/agent-hook-installer.d.ts +62 -0
- package/dist/server/terminal/agent-hooks/agent-hook-installer.js +117 -0
- package/dist/server/terminal/agent-hooks/claude/claude-settings.d.ts +7 -0
- package/dist/server/terminal/agent-hooks/claude/claude-settings.js +88 -0
- package/dist/server/terminal/agent-hooks/claude/claude.d.ts +4 -0
- package/dist/server/terminal/agent-hooks/claude/claude.js +47 -0
- package/dist/server/terminal/agent-hooks/codex/codex-settings.d.ts +7 -0
- package/dist/server/terminal/agent-hooks/codex/codex-settings.js +99 -0
- package/dist/server/terminal/agent-hooks/codex/codex.d.ts +4 -0
- package/dist/server/terminal/agent-hooks/codex/codex.js +30 -0
- package/dist/server/terminal/agent-hooks/opencode/opencode-plugin.d.ts +4 -0
- package/dist/server/terminal/agent-hooks/opencode/opencode-plugin.js +46 -0
- package/dist/server/terminal/agent-hooks/opencode/opencode.d.ts +3 -0
- package/dist/server/terminal/agent-hooks/opencode/opencode.js +23 -0
- package/dist/server/terminal/agent-hooks/provider-registry.d.ts +24 -0
- package/dist/server/terminal/agent-hooks/provider-registry.js +36 -0
- package/dist/server/terminal/agent-hooks/terminal-agent-hook-setting.d.ts +10 -0
- package/dist/server/terminal/agent-hooks/terminal-agent-hook-setting.js +26 -0
- package/dist/server/terminal/shell-integration/zsh/.zshenv +17 -0
- package/dist/server/terminal/shell-integration/zsh/otto-integration.zsh +32 -0
- package/dist/server/terminal/terminal-capture.d.ts +12 -0
- package/dist/server/terminal/terminal-capture.js +43 -0
- package/dist/server/terminal/terminal-manager-factory.d.ts +6 -0
- package/dist/server/terminal/terminal-manager-factory.js +5 -0
- package/dist/server/terminal/terminal-manager.d.ts +78 -0
- package/dist/server/terminal/terminal-manager.js +319 -0
- package/dist/server/terminal/terminal-output-coalescer.d.ts +34 -0
- package/dist/server/terminal/terminal-output-coalescer.js +73 -0
- package/dist/server/terminal/terminal-restore.d.ts +18 -0
- package/dist/server/terminal/terminal-restore.js +52 -0
- package/dist/server/terminal/terminal-session-controller.d.ts +85 -0
- package/dist/server/terminal/terminal-session-controller.js +819 -0
- package/dist/server/terminal/terminal-ts-loader.mjs +20 -0
- package/dist/server/terminal/terminal-worker-process.d.ts +2 -0
- package/dist/server/terminal/terminal-worker-process.js +304 -0
- package/dist/server/terminal/terminal-worker-protocol.d.ts +119 -0
- package/dist/server/terminal/terminal-worker-protocol.js +2 -0
- package/dist/server/terminal/terminal.d.ts +151 -0
- package/dist/server/terminal/terminal.js +1155 -0
- package/dist/server/terminal/worker-terminal-manager.d.ts +20 -0
- package/dist/server/terminal/worker-terminal-manager.js +688 -0
- package/dist/server/utils/build-metadata-prompt.d.ts +19 -0
- package/dist/server/utils/build-metadata-prompt.js +29 -0
- package/dist/server/utils/checkout-git.d.ts +253 -0
- package/dist/server/utils/checkout-git.js +2241 -0
- package/dist/server/utils/directory-suggestions.d.ts +26 -0
- package/dist/server/utils/directory-suggestions.js +830 -0
- package/dist/server/utils/git-rev-parse-path.d.ts +3 -0
- package/dist/server/utils/git-rev-parse-path.js +21 -0
- package/dist/server/utils/github-remote.d.ts +9 -0
- package/dist/server/utils/github-remote.js +62 -0
- package/dist/server/utils/otto-config-file.d.ts +30 -0
- package/dist/server/utils/otto-config-file.js +90 -0
- package/dist/server/utils/path.d.ts +18 -0
- package/dist/server/utils/path.js +123 -0
- package/dist/server/utils/project-icon.d.ts +39 -0
- package/dist/server/utils/project-icon.js +364 -0
- package/dist/server/utils/promise-timeout.d.ts +9 -0
- package/dist/server/utils/promise-timeout.js +25 -0
- package/dist/server/utils/run-git-command.d.ts +37 -0
- package/dist/server/utils/run-git-command.js +232 -0
- package/dist/server/utils/spawn.d.ts +25 -0
- package/dist/server/utils/spawn.js +59 -0
- package/dist/server/utils/string-command-shell.d.ts +13 -0
- package/dist/server/utils/string-command-shell.js +37 -0
- package/dist/server/utils/tree-kill.d.ts +18 -0
- package/dist/server/utils/tree-kill.js +77 -0
- package/dist/server/utils/windows-command.d.ts +15 -0
- package/dist/server/utils/windows-command.js +41 -0
- package/dist/server/utils/worktree-metadata.d.ts +39 -0
- package/dist/server/utils/worktree-metadata.js +174 -0
- package/dist/server/utils/worktree.d.ts +205 -0
- package/dist/server/utils/worktree.js +1120 -0
- package/dist/server/web-ui/_expo/static/css/xterm-3bb1704bf6cb0876640973dc0244b4cb.css +1 -0
- package/dist/server/web-ui/_expo/static/css/xterm-3bb1704bf6cb0876640973dc0244b4cb.css.br +0 -0
- package/dist/server/web-ui/_expo/static/css/xterm-3bb1704bf6cb0876640973dc0244b4cb.css.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-a25c6055b348629689f75618db2c83e1.js +1 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-a25c6055b348629689f75618db2c83e1.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-a25c6055b348629689f75618db2c83e1.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-7dc26ea1611eb9d7c2971a6745e5bf5c.js +1 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-7dc26ea1611eb9d7c2971a6745e5bf5c.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-7dc26ea1611eb9d7c2971a6745e5bf5c.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/index-ea91f496cee180fa5bf308c8cc177646.js +3199 -0
- package/dist/server/web-ui/_expo/static/js/web/index-ea91f496cee180fa5bf308c8cc177646.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/index-ea91f496cee180fa5bf308c8cc177646.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-b6b853a967343babf305512fe8c5958c.js +1 -0
- package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-b6b853a967343babf305512fe8c5958c.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-b6b853a967343babf305512fe8c5958c.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-17223e158406802adc686d02d946573d.js +3 -0
- package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-17223e158406802adc686d02d946573d.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-17223e158406802adc686d02d946573d.js.gz +0 -0
- package/dist/server/web-ui/apple-touch-icon.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@expo-google-fonts/inter/400Regular/Inter_400Regular.51b6ad87261f18b6433ec52871ddfabc.ttf +0 -0
- package/dist/server/web-ui/assets/__node_modules/@expo-google-fonts/jetbrains-mono/400Regular/JetBrainsMono_400Regular.a0147b5ab9e4946e81879aef45313def.ttf +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/back-icon-mask.0a328cd9c1afd0afe8e3b1ec5165b1b4.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/back-icon.35ba0eaec5a4f5ed12ca16fabeae451d.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@2x.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@3x.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@4x.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@2x.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@3x.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@4x.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/search-icon.286d67d3f74808a60a78d3ebf1a5fb57.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/expo-router/assets/arrow_down.017bc6ba3fc25503e5eb5e53826d48a8.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/expo-router/assets/error.d1ea1496f9057eb392d5bbf3732a61b7.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/expo-router/assets/file.19eeb73b9593a38f8e9f418337fc7d10.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/expo-router/assets/forward.d8b800c443b8972542883e0b9de2bdc6.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/expo-router/assets/pkg.ab19f4cbc543357183a20571f68380a3.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/expo-router/assets/sitemap.412dd9275b6b48ad28f5e3d81bb1f626.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/expo-router/assets/unmatched.20e71bdf79e3a97bf55fd9e164041578.png +0 -0
- package/dist/server/web-ui/assets/assets/images/editor-apps/antigravity.6e91a685c33435e0b466a56db86cf141.png +0 -0
- package/dist/server/web-ui/assets/assets/images/editor-apps/clion.baa1f4eb572e962a6117bdbee95749e4.png +0 -0
- package/dist/server/web-ui/assets/assets/images/editor-apps/cursor.c31d6bce4fe9aadc3fe59962f4c4fcf3.png +0 -0
- package/dist/server/web-ui/assets/assets/images/editor-apps/finder.7f68fc2c475621a672e1be09309d5567.png +0 -0
- package/dist/server/web-ui/assets/assets/images/editor-apps/goland.0796ab2b9be2637fd1cdcde891fad3ca.png +0 -0
- package/dist/server/web-ui/assets/assets/images/editor-apps/intellij-idea.33bc9ff9c0815d8ad43f618c8c244401.png +0 -0
- package/dist/server/web-ui/assets/assets/images/editor-apps/phpstorm.c60b33bc7a8309143d232e14b76554c5.png +0 -0
- package/dist/server/web-ui/assets/assets/images/editor-apps/pycharm.88f25ac484989c45c25323e0adccfa9f.png +0 -0
- package/dist/server/web-ui/assets/assets/images/editor-apps/rider.1a1091dc3a0ede0461f0aa38a54f080a.png +0 -0
- package/dist/server/web-ui/assets/assets/images/editor-apps/rubymine.0e5b66fd7d43f8cbac0f05bba97c9a21.png +0 -0
- package/dist/server/web-ui/assets/assets/images/editor-apps/rustrover.3590ef05482369f41d3484c90272570d.png +0 -0
- package/dist/server/web-ui/assets/assets/images/editor-apps/vscode.832bdb4c685d930f1c864c793703600b.png +0 -0
- package/dist/server/web-ui/assets/assets/images/editor-apps/webstorm.aa5dc2cd8c20cc0a155c4c5c5ab3c5f5.png +0 -0
- package/dist/server/web-ui/assets/assets/images/editor-apps/zed.f3a670b7f9aa226da4fe53fb86f1abbd.png +0 -0
- package/dist/server/web-ui/assets/assets/images/favicon-dark-attention.3b2c331d11122d8f743c73a5878593be.png +0 -0
- package/dist/server/web-ui/assets/assets/images/favicon-dark-running.6077c2ab71021b31c82a42e0b42ad837.png +0 -0
- package/dist/server/web-ui/assets/assets/images/favicon-dark.12984efe6d435a13794a74cd2f858dcd.png +0 -0
- package/dist/server/web-ui/assets/assets/images/favicon-light-attention.3b2c331d11122d8f743c73a5878593be.png +0 -0
- package/dist/server/web-ui/assets/assets/images/favicon-light-running.6077c2ab71021b31c82a42e0b42ad837.png +0 -0
- package/dist/server/web-ui/assets/assets/images/favicon-light.12984efe6d435a13794a74cd2f858dcd.png +0 -0
- package/dist/server/web-ui/assets/assets/images/notification-icon.2394c3b427c0f5ee2bb84864d8fb0f45.png +0 -0
- package/dist/server/web-ui/favicon.ico +0 -0
- package/dist/server/web-ui/index.html +147 -0
- package/dist/server/web-ui/index.html.br +0 -0
- package/dist/server/web-ui/index.html.gz +0 -0
- package/dist/server/web-ui/manifest.json +27 -0
- package/dist/server/web-ui/manifest.json.br +0 -0
- package/dist/server/web-ui/manifest.json.gz +0 -0
- package/dist/server/web-ui/metadata.json +1 -0
- package/dist/server/web-ui/metadata.json.br +1 -0
- package/dist/server/web-ui/metadata.json.gz +0 -0
- package/dist/server/web-ui/pwa-icon-192.png +0 -0
- package/dist/server/web-ui/pwa-icon-512.png +0 -0
- package/dist/server/web-ui/robots.txt +2 -0
- package/dist/src/executable-resolution/executable-resolution.js +118 -0
- package/dist/src/executable-resolution/windows.js +65 -0
- package/dist/src/server/agent/provider-launch-config.js +167 -0
- package/dist/src/server/otto-env.js +53 -0
- package/dist/src/server/otto-home.js +19 -0
- package/dist/src/server/persisted-config.js +471 -0
- package/dist/src/server/pid-lock.js +170 -0
- package/dist/src/server/private-files.js +37 -0
- package/dist/src/server/speech/providers/local/sherpa/sherpa-runtime-env.js +84 -0
- package/dist/src/terminal/shell-integration/zsh/.zshenv +17 -0
- package/dist/src/terminal/shell-integration/zsh/otto-integration.zsh +32 -0
- package/dist/src/utils/spawn.js +59 -0
- package/dist/src/utils/windows-command.js +41 -0
- package/package.json +118 -0
|
@@ -0,0 +1,1224 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { once } from "node:events";
|
|
3
|
+
import * as fs from "node:fs/promises";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import * as dns from "node:dns";
|
|
6
|
+
import * as net from "node:net";
|
|
7
|
+
import { Agent, fetch as undiciFetch } from "undici";
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// SSRF Protection — prevent web_fetch from reaching internal networks
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
/**
|
|
12
|
+
* IP ranges that must never be reachable from web_fetch.
|
|
13
|
+
* Covers: loopback, link-local, private (RFC 1918), carrier-grade,
|
|
14
|
+
* unique-local, multicast, reserved, and cloud metadata endpoints.
|
|
15
|
+
*/
|
|
16
|
+
const BLOCKED_IP_RANGES = [
|
|
17
|
+
// Loopback: 127.0.0.0/8
|
|
18
|
+
["127.0.0.0", "127.255.255.255"],
|
|
19
|
+
// Link-local: 169.254.0.0/16
|
|
20
|
+
["169.254.0.0", "169.254.255.255"],
|
|
21
|
+
// Private: 10.0.0.0/8
|
|
22
|
+
["10.0.0.0", "10.255.255.255"],
|
|
23
|
+
// Private: 172.16.0.0/12
|
|
24
|
+
["172.16.0.0", "172.31.255.255"],
|
|
25
|
+
// Private: 192.168.0.0/16
|
|
26
|
+
["192.168.0.0", "192.168.255.255"],
|
|
27
|
+
// Carrier-grade NAT: 100.64.0.0/10
|
|
28
|
+
["100.64.0.0", "100.127.255.255"],
|
|
29
|
+
// Documentation: 192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24
|
|
30
|
+
// (not strictly needed but defensive)
|
|
31
|
+
["192.0.2.0", "192.0.2.255"],
|
|
32
|
+
["198.51.100.0", "198.51.100.255"],
|
|
33
|
+
["203.0.113.0", "203.0.113.255"],
|
|
34
|
+
// Benchmarking: 198.18.0.0/15
|
|
35
|
+
["198.18.0.0", "198.19.255.255"],
|
|
36
|
+
// Multicast: 224.0.0.0/4
|
|
37
|
+
["224.0.0.0", "239.255.255.255"],
|
|
38
|
+
// Reserved + broadcast: 240.0.0.0/4
|
|
39
|
+
["240.0.0.0", "255.255.255.255"],
|
|
40
|
+
// Any local: 0.0.0.0/8
|
|
41
|
+
["0.0.0.0", "0.255.255.255"],
|
|
42
|
+
];
|
|
43
|
+
/** Hostnames that are always allowed (DuckDuckGo API endpoints). */
|
|
44
|
+
const ALLOWED_HOSTS = new Set(["api.duckduckgo.com", "html.duckduckgo.com"]);
|
|
45
|
+
/**
|
|
46
|
+
* Convert an IPv4 address string to an unsigned 32-bit integer for range
|
|
47
|
+
* comparison. Throws on anything that is not a dotted quad.
|
|
48
|
+
*/
|
|
49
|
+
function ipv4ToNumber(ip) {
|
|
50
|
+
const parts = ip.split(".");
|
|
51
|
+
if (parts.length !== 4) {
|
|
52
|
+
throw new Error(`Not an IPv4 address: ${ip}`);
|
|
53
|
+
}
|
|
54
|
+
let value = 0;
|
|
55
|
+
for (const part of parts) {
|
|
56
|
+
const octet = Number.parseInt(part, 10);
|
|
57
|
+
if (!Number.isInteger(octet) || octet < 0 || octet > 255) {
|
|
58
|
+
throw new Error(`Not an IPv4 address: ${ip}`);
|
|
59
|
+
}
|
|
60
|
+
value = (value * 256 + octet) >>> 0;
|
|
61
|
+
}
|
|
62
|
+
return value;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Extract the IPv4 address embedded in an IPv4-mapped IPv6 address
|
|
66
|
+
* (::ffff:a.b.c.d, including the hex form ::ffff:aabb:ccdd). Returns null for
|
|
67
|
+
* anything else. Mapped addresses must be screened with the IPv4 rules — a
|
|
68
|
+
* socket to ::ffff:127.0.0.1 reaches loopback.
|
|
69
|
+
*/
|
|
70
|
+
function extractMappedIpv4(lowerIp) {
|
|
71
|
+
const dotted = lowerIp.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/u);
|
|
72
|
+
if (dotted) {
|
|
73
|
+
return dotted[1];
|
|
74
|
+
}
|
|
75
|
+
const hex = lowerIp.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/u);
|
|
76
|
+
if (hex) {
|
|
77
|
+
const high = Number.parseInt(hex[1], 16);
|
|
78
|
+
const low = Number.parseInt(hex[2], 16);
|
|
79
|
+
return `${high >> 8}.${high & 0xff}.${low >> 8}.${low & 0xff}`;
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Check whether an IP address falls within any blocked range.
|
|
85
|
+
* Returns true if the IP is dangerous and should be blocked.
|
|
86
|
+
*/
|
|
87
|
+
export function isBlockedIp(ip) {
|
|
88
|
+
// IPv6: block loopback (::1), unique-local (fc00::/7), multicast (ff00::/8),
|
|
89
|
+
// link-local (fe80::/10, over-broadly as fe*), and IPv4-mapped addresses
|
|
90
|
+
// whose embedded IPv4 is blocked.
|
|
91
|
+
if (ip.includes(":")) {
|
|
92
|
+
const lower = ip.toLowerCase();
|
|
93
|
+
const mapped = extractMappedIpv4(lower);
|
|
94
|
+
if (mapped !== null) {
|
|
95
|
+
return isBlockedIp(mapped);
|
|
96
|
+
}
|
|
97
|
+
return (lower === "::1" ||
|
|
98
|
+
lower === "::" ||
|
|
99
|
+
lower.startsWith("fc") ||
|
|
100
|
+
lower.startsWith("fd") ||
|
|
101
|
+
lower.startsWith("fe") ||
|
|
102
|
+
lower.startsWith("ff"));
|
|
103
|
+
}
|
|
104
|
+
// IPv4: check against blocked ranges
|
|
105
|
+
try {
|
|
106
|
+
const num = ipv4ToNumber(ip);
|
|
107
|
+
for (const [start, end] of BLOCKED_IP_RANGES) {
|
|
108
|
+
if (num >= ipv4ToNumber(start) && num <= ipv4ToNumber(end)) {
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// Unparseable IP — block it defensively
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Resolve a hostname and validate that none of its addresses are blocked.
|
|
121
|
+
* Returns the resolved addresses so the connection can be pinned to exactly
|
|
122
|
+
* what was validated — validating and then letting fetch re-resolve would be
|
|
123
|
+
* bypassable via low-TTL DNS rebinding. Throws if the host is blocked or
|
|
124
|
+
* resolution fails.
|
|
125
|
+
*/
|
|
126
|
+
async function validateHostname(host) {
|
|
127
|
+
// Allowlist check — skip DNS validation for trusted hosts
|
|
128
|
+
if (ALLOWED_HOSTS.has(host)) {
|
|
129
|
+
return { addresses: [] };
|
|
130
|
+
}
|
|
131
|
+
// Reject well-known internal hostname patterns without DNS lookup.
|
|
132
|
+
if (host === "localhost" ||
|
|
133
|
+
host.endsWith(".localhost") ||
|
|
134
|
+
host.endsWith(".local") ||
|
|
135
|
+
host.endsWith(".internal")) {
|
|
136
|
+
throw new Error(`Blocked: hostname '${host}' targets an internal network`);
|
|
137
|
+
}
|
|
138
|
+
// IP literals validate directly — no DNS involved. URL.hostname keeps the
|
|
139
|
+
// brackets around IPv6 literals.
|
|
140
|
+
const literal = host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
|
|
141
|
+
const literalFamily = net.isIP(literal);
|
|
142
|
+
if (literalFamily !== 0) {
|
|
143
|
+
if (isBlockedIp(literal)) {
|
|
144
|
+
throw new Error(`Blocked: '${literal}' is in a restricted network range`);
|
|
145
|
+
}
|
|
146
|
+
return { addresses: [{ address: literal, family: literalFamily }] };
|
|
147
|
+
}
|
|
148
|
+
// getaddrinfo (not dns.resolve*) so hosts-file entries are validated the
|
|
149
|
+
// same way the socket would resolve them, across both address families.
|
|
150
|
+
let results;
|
|
151
|
+
try {
|
|
152
|
+
results = await dns.promises.lookup(host, { all: true });
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
throw new Error(`DNS resolution failed for '${host}'`);
|
|
156
|
+
}
|
|
157
|
+
if (results.length === 0) {
|
|
158
|
+
throw new Error(`DNS resolution failed for '${host}'`);
|
|
159
|
+
}
|
|
160
|
+
for (const { address } of results) {
|
|
161
|
+
if (isBlockedIp(address)) {
|
|
162
|
+
throw new Error(`Blocked: '${host}' resolved to ${address} which is in a restricted network range`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return { addresses: results };
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Validate that a URL is safe to fetch, checking protocol, hostname, and
|
|
169
|
+
* resolved IPs. Returns the validated addresses for connection pinning.
|
|
170
|
+
* Throws a descriptive error if the URL is blocked.
|
|
171
|
+
*/
|
|
172
|
+
async function validateUrlForFetch(urlString) {
|
|
173
|
+
const parsedUrl = new URL(urlString);
|
|
174
|
+
// Only allow http/https
|
|
175
|
+
if (!["http:", "https:"].includes(parsedUrl.protocol)) {
|
|
176
|
+
throw new Error(`Blocked: only http:// and https:// URLs are allowed`);
|
|
177
|
+
}
|
|
178
|
+
return validateHostname(parsedUrl.hostname);
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Dispatcher whose socket lookup answers from the pre-validated address set
|
|
182
|
+
* instead of re-querying DNS — the connection can only reach an IP that
|
|
183
|
+
* passed isBlockedIp. TLS keeps the hostname as servername, so certificate
|
|
184
|
+
* validation is unaffected. An empty address set (allowlisted hosts) falls
|
|
185
|
+
* back to the default resolver.
|
|
186
|
+
*/
|
|
187
|
+
function buildPinnedDispatcher(target) {
|
|
188
|
+
if (target.addresses.length === 0) {
|
|
189
|
+
return new Agent();
|
|
190
|
+
}
|
|
191
|
+
const pinned = target.addresses;
|
|
192
|
+
const lookup = (_hostname, options, callback) => {
|
|
193
|
+
if (options.all) {
|
|
194
|
+
callback(null, pinned);
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
const first = pinned[0];
|
|
198
|
+
callback(null, first.address, first.family);
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
return new Agent({ connect: { lookup: lookup } });
|
|
202
|
+
}
|
|
203
|
+
const MAX_READ_BYTES = 256 * 1024;
|
|
204
|
+
const MAX_TOOL_OUTPUT_CHARS = 30000;
|
|
205
|
+
const MAX_COMMAND_OUTPUT_CHARS = 16000;
|
|
206
|
+
const MAX_GREP_MATCHES = 100;
|
|
207
|
+
const MAX_GREP_FILE_BYTES = 1024 * 1024;
|
|
208
|
+
const DEFAULT_COMMAND_TIMEOUT_MS = 120000;
|
|
209
|
+
const MAX_COMMAND_TIMEOUT_MS = 600000;
|
|
210
|
+
/** Stop buffering command output past this point — only the head is ever shown. */
|
|
211
|
+
const MAX_COMMAND_BUFFER_BYTES = 1024 * 1024;
|
|
212
|
+
const SKIPPED_DIRECTORIES = new Set([
|
|
213
|
+
".git",
|
|
214
|
+
"node_modules",
|
|
215
|
+
"dist",
|
|
216
|
+
"build",
|
|
217
|
+
".expo",
|
|
218
|
+
".next",
|
|
219
|
+
".dev",
|
|
220
|
+
]);
|
|
221
|
+
// --- Web tool constants ---
|
|
222
|
+
const DDG_API_URL = "https://api.duckduckgo.com";
|
|
223
|
+
const DDG_HTML_URL = "https://html.duckduckgo.com/html/";
|
|
224
|
+
const MAX_WEB_FETCH_BYTES = 1000000;
|
|
225
|
+
const MAX_WEB_FETCH_OUTPUT = 15000;
|
|
226
|
+
const WEB_SEARCH_TIMEOUT_MS = 15000;
|
|
227
|
+
const WEB_FETCH_TIMEOUT_MS = 30000;
|
|
228
|
+
const MAX_WEB_SEARCH_RESULTS = 15;
|
|
229
|
+
const MAX_WEB_FETCH_REDIRECTS = 5;
|
|
230
|
+
export const COMPAT_TOOL_SPECS = [
|
|
231
|
+
{
|
|
232
|
+
name: "read_file",
|
|
233
|
+
kind: "read",
|
|
234
|
+
description: "Read a file. Returns the file content, truncated when very large. Use offset/limit (line numbers) to page through big files.",
|
|
235
|
+
parameters: {
|
|
236
|
+
type: "object",
|
|
237
|
+
properties: {
|
|
238
|
+
path: { type: "string", description: "File path, absolute or relative to the workspace" },
|
|
239
|
+
offset: { type: "number", description: "1-based line to start reading from" },
|
|
240
|
+
limit: { type: "number", description: "Maximum number of lines to return" },
|
|
241
|
+
},
|
|
242
|
+
required: ["path"],
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
name: "list_dir",
|
|
247
|
+
kind: "read",
|
|
248
|
+
description: "List the entries of a directory. Directories are suffixed with '/'. Defaults to the workspace root.",
|
|
249
|
+
parameters: {
|
|
250
|
+
type: "object",
|
|
251
|
+
properties: {
|
|
252
|
+
path: { type: "string", description: "Directory path, defaults to the workspace root" },
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
name: "grep_search",
|
|
258
|
+
kind: "read",
|
|
259
|
+
description: "Search file contents recursively with a regular expression. Returns matching lines as path:line:text. Skips binary files and dependency folders.",
|
|
260
|
+
parameters: {
|
|
261
|
+
type: "object",
|
|
262
|
+
properties: {
|
|
263
|
+
pattern: { type: "string", description: "JavaScript regular expression to search for" },
|
|
264
|
+
path: { type: "string", description: "Directory to search, defaults to workspace root" },
|
|
265
|
+
max_results: { type: "number", description: "Maximum matches to return (default 100)" },
|
|
266
|
+
},
|
|
267
|
+
required: ["pattern"],
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
name: "web_search",
|
|
272
|
+
kind: "read",
|
|
273
|
+
description: "Search the web using DuckDuckGo. Returns a list of results with titles, URLs and snippets. " +
|
|
274
|
+
"Use this to find current information, documentation, or anything not available locally.",
|
|
275
|
+
parameters: {
|
|
276
|
+
type: "object",
|
|
277
|
+
properties: {
|
|
278
|
+
query: { type: "string", description: "The search query" },
|
|
279
|
+
},
|
|
280
|
+
required: ["query"],
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
name: "web_fetch",
|
|
285
|
+
kind: "network",
|
|
286
|
+
description: "Fetch the content of a web page and return it as readable text. " +
|
|
287
|
+
"Use this to read the full content of a specific URL found via web_search or provided directly.",
|
|
288
|
+
parameters: {
|
|
289
|
+
type: "object",
|
|
290
|
+
properties: {
|
|
291
|
+
url: { type: "string", description: "The URL to fetch" },
|
|
292
|
+
max_length: {
|
|
293
|
+
type: "number",
|
|
294
|
+
description: "Maximum characters to return (default 15000, capped at 30000)",
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
required: ["url"],
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
name: "write_file",
|
|
302
|
+
kind: "edit",
|
|
303
|
+
description: "Create or overwrite a file with the given content. Parent directories are created automatically.",
|
|
304
|
+
parameters: {
|
|
305
|
+
type: "object",
|
|
306
|
+
properties: {
|
|
307
|
+
path: { type: "string", description: "File path, absolute or relative to the workspace" },
|
|
308
|
+
content: { type: "string", description: "Full file content to write" },
|
|
309
|
+
},
|
|
310
|
+
required: ["path", "content"],
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
name: "edit_file",
|
|
315
|
+
kind: "edit",
|
|
316
|
+
description: "Replace an exact string in a file. old_string must match exactly once unless replace_all is true. Include enough surrounding context to make it unique.",
|
|
317
|
+
parameters: {
|
|
318
|
+
type: "object",
|
|
319
|
+
properties: {
|
|
320
|
+
path: { type: "string", description: "File path, absolute or relative to the workspace" },
|
|
321
|
+
old_string: { type: "string", description: "Exact text to replace" },
|
|
322
|
+
new_string: { type: "string", description: "Replacement text" },
|
|
323
|
+
replace_all: { type: "boolean", description: "Replace every occurrence (default false)" },
|
|
324
|
+
},
|
|
325
|
+
required: ["path", "old_string", "new_string"],
|
|
326
|
+
},
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
name: "run_command",
|
|
330
|
+
kind: "execute",
|
|
331
|
+
description: "Run a shell command in the workspace directory and return its combined output and exit code. Long output is truncated. " +
|
|
332
|
+
"Do not start long-running dev servers here — they block the shell and cannot be verified; when the preview tools are available, use preview_start instead.",
|
|
333
|
+
parameters: {
|
|
334
|
+
type: "object",
|
|
335
|
+
properties: {
|
|
336
|
+
command: { type: "string", description: "The shell command to run" },
|
|
337
|
+
timeout_seconds: {
|
|
338
|
+
type: "number",
|
|
339
|
+
description: "Maximum runtime in seconds (default 120, max 600)",
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
required: ["command"],
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
];
|
|
346
|
+
export function buildOpenAIToolsPayload(specs) {
|
|
347
|
+
return specs.map((spec) => ({
|
|
348
|
+
type: "function",
|
|
349
|
+
function: {
|
|
350
|
+
name: spec.name,
|
|
351
|
+
description: spec.description,
|
|
352
|
+
parameters: spec.parameters,
|
|
353
|
+
},
|
|
354
|
+
}));
|
|
355
|
+
}
|
|
356
|
+
export function findCompatToolSpec(name) {
|
|
357
|
+
return COMPAT_TOOL_SPECS.find((spec) => spec.name === name);
|
|
358
|
+
}
|
|
359
|
+
export async function executeCompatTool(input) {
|
|
360
|
+
switch (input.name) {
|
|
361
|
+
case "read_file":
|
|
362
|
+
return readFileTool(input);
|
|
363
|
+
case "list_dir":
|
|
364
|
+
return listDirTool(input);
|
|
365
|
+
case "grep_search":
|
|
366
|
+
return grepSearchTool(input);
|
|
367
|
+
case "web_search":
|
|
368
|
+
return webSearchTool(input);
|
|
369
|
+
case "web_fetch":
|
|
370
|
+
return webFetchTool(input);
|
|
371
|
+
case "write_file":
|
|
372
|
+
return writeFileTool(input);
|
|
373
|
+
case "edit_file":
|
|
374
|
+
return editFileTool(input);
|
|
375
|
+
case "run_command":
|
|
376
|
+
return runCommandTool(input);
|
|
377
|
+
default:
|
|
378
|
+
return {
|
|
379
|
+
output: `Unknown tool: ${input.name}`,
|
|
380
|
+
detail: { type: "unknown", input: input.arguments, output: null },
|
|
381
|
+
isError: true,
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Build the preview detail shown in the timeline (and permission prompt)
|
|
387
|
+
* before the tool has produced output.
|
|
388
|
+
*/
|
|
389
|
+
export function buildCompatToolPreviewDetail(name, args, cwd) {
|
|
390
|
+
switch (name) {
|
|
391
|
+
case "read_file":
|
|
392
|
+
return { type: "read", filePath: resolveToolPath(cwd, readString(args, "path") ?? "") };
|
|
393
|
+
case "list_dir":
|
|
394
|
+
return {
|
|
395
|
+
type: "plain_text",
|
|
396
|
+
label: `List ${readString(args, "path") ?? "."}`,
|
|
397
|
+
icon: "eye",
|
|
398
|
+
};
|
|
399
|
+
case "grep_search":
|
|
400
|
+
return { type: "search", query: readString(args, "pattern") ?? "", toolName: "grep" };
|
|
401
|
+
case "web_search":
|
|
402
|
+
return { type: "search", query: readString(args, "query") ?? "", toolName: "web_search" };
|
|
403
|
+
case "web_fetch":
|
|
404
|
+
return { type: "fetch", url: readString(args, "url") ?? "" };
|
|
405
|
+
case "write_file":
|
|
406
|
+
return {
|
|
407
|
+
type: "write",
|
|
408
|
+
filePath: resolveToolPath(cwd, readString(args, "path") ?? ""),
|
|
409
|
+
content: readString(args, "content") ?? "",
|
|
410
|
+
};
|
|
411
|
+
case "edit_file":
|
|
412
|
+
return {
|
|
413
|
+
type: "edit",
|
|
414
|
+
filePath: resolveToolPath(cwd, readString(args, "path") ?? ""),
|
|
415
|
+
oldString: readString(args, "old_string") ?? "",
|
|
416
|
+
newString: readString(args, "new_string") ?? "",
|
|
417
|
+
};
|
|
418
|
+
case "run_command":
|
|
419
|
+
return { type: "shell", command: readString(args, "command") ?? "", cwd };
|
|
420
|
+
default:
|
|
421
|
+
return { type: "unknown", input: args, output: null };
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
function readString(args, key) {
|
|
425
|
+
const value = args[key];
|
|
426
|
+
return typeof value === "string" ? value : undefined;
|
|
427
|
+
}
|
|
428
|
+
function readNumber(args, key) {
|
|
429
|
+
const value = args[key];
|
|
430
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
431
|
+
}
|
|
432
|
+
function resolveToolPath(cwd, target) {
|
|
433
|
+
return path.resolve(cwd, target);
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* True when the target path stays inside the workspace subtree. Used to scope
|
|
437
|
+
* acceptEdits auto-approval: edits outside the cwd (~/.bashrc, Otto's own
|
|
438
|
+
* config) still prompt. Purely lexical — symlinks are not chased — so it
|
|
439
|
+
* gates prompting, it is not a sandbox.
|
|
440
|
+
*/
|
|
441
|
+
export function isPathInsideWorkspace(cwd, target) {
|
|
442
|
+
const relative = path.relative(cwd, path.resolve(cwd, target));
|
|
443
|
+
return (relative === "" ||
|
|
444
|
+
(!path.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path.sep}`)));
|
|
445
|
+
}
|
|
446
|
+
function truncateOutput(text, maxChars) {
|
|
447
|
+
if (text.length <= maxChars) {
|
|
448
|
+
return { text, truncated: false };
|
|
449
|
+
}
|
|
450
|
+
return { text: text.slice(0, maxChars), truncated: true };
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Cap text fed back to the model as a tool result. Shared with MCP tool
|
|
454
|
+
* results, which are untrusted external content and must not blow the context.
|
|
455
|
+
*/
|
|
456
|
+
export function capToolOutput(text) {
|
|
457
|
+
const { text: capped, truncated } = truncateOutput(text, MAX_TOOL_OUTPUT_CHARS);
|
|
458
|
+
return truncated ? `${capped}\n[truncated]` : capped;
|
|
459
|
+
}
|
|
460
|
+
function errorOutcome(message, detail) {
|
|
461
|
+
return { output: message, detail, isError: true };
|
|
462
|
+
}
|
|
463
|
+
async function readFileTool(input) {
|
|
464
|
+
const relPath = readString(input.arguments, "path");
|
|
465
|
+
if (!relPath) {
|
|
466
|
+
return errorOutcome("read_file requires a 'path' argument", {
|
|
467
|
+
type: "unknown",
|
|
468
|
+
input: input.arguments,
|
|
469
|
+
output: null,
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
const filePath = resolveToolPath(input.cwd, relPath);
|
|
473
|
+
const detailBase = { type: "read", filePath };
|
|
474
|
+
let raw;
|
|
475
|
+
try {
|
|
476
|
+
const stat = await fs.stat(filePath);
|
|
477
|
+
if (stat.isDirectory()) {
|
|
478
|
+
return errorOutcome(`${filePath} is a directory — use list_dir instead`, detailBase);
|
|
479
|
+
}
|
|
480
|
+
if (stat.size > MAX_READ_BYTES) {
|
|
481
|
+
const handle = await fs.open(filePath, "r");
|
|
482
|
+
try {
|
|
483
|
+
const buffer = Buffer.alloc(MAX_READ_BYTES);
|
|
484
|
+
const { bytesRead } = await handle.read(buffer, 0, MAX_READ_BYTES, 0);
|
|
485
|
+
raw = `${buffer.subarray(0, bytesRead).toString("utf8")}\n[truncated: file is ${stat.size} bytes]`;
|
|
486
|
+
}
|
|
487
|
+
finally {
|
|
488
|
+
await handle.close();
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
else {
|
|
492
|
+
raw = await fs.readFile(filePath, "utf8");
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
catch (error) {
|
|
496
|
+
return errorOutcome(describeFsError(error), detailBase);
|
|
497
|
+
}
|
|
498
|
+
const offset = readNumber(input.arguments, "offset");
|
|
499
|
+
const limit = readNumber(input.arguments, "limit");
|
|
500
|
+
let output = raw;
|
|
501
|
+
if (offset !== undefined || limit !== undefined) {
|
|
502
|
+
const lines = raw.split("\n");
|
|
503
|
+
const start = Math.max((offset ?? 1) - 1, 0);
|
|
504
|
+
const end = limit !== undefined ? start + limit : lines.length;
|
|
505
|
+
output = lines.slice(start, end).join("\n");
|
|
506
|
+
}
|
|
507
|
+
const { text, truncated } = truncateOutput(output, MAX_TOOL_OUTPUT_CHARS);
|
|
508
|
+
return {
|
|
509
|
+
output: truncated ? `${text}\n[truncated]` : text,
|
|
510
|
+
detail: {
|
|
511
|
+
...detailBase,
|
|
512
|
+
...(offset !== undefined ? { offset } : {}),
|
|
513
|
+
...(limit !== undefined ? { limit } : {}),
|
|
514
|
+
},
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
async function listDirTool(input) {
|
|
518
|
+
const relPath = readString(input.arguments, "path") ?? ".";
|
|
519
|
+
const dirPath = resolveToolPath(input.cwd, relPath);
|
|
520
|
+
const detail = { type: "plain_text", label: `List ${relPath}`, icon: "eye" };
|
|
521
|
+
try {
|
|
522
|
+
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
523
|
+
const names = entries
|
|
524
|
+
.map((entry) => (entry.isDirectory() ? `${entry.name}/` : entry.name))
|
|
525
|
+
.sort((a, b) => a.localeCompare(b));
|
|
526
|
+
const listing = names.length > 0 ? names.join("\n") : "(empty directory)";
|
|
527
|
+
return { output: listing, detail: { ...detail, text: listing } };
|
|
528
|
+
}
|
|
529
|
+
catch (error) {
|
|
530
|
+
return errorOutcome(describeFsError(error), detail);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
async function grepSearchTool(input) {
|
|
534
|
+
const pattern = readString(input.arguments, "pattern");
|
|
535
|
+
if (!pattern) {
|
|
536
|
+
return errorOutcome("grep_search requires a 'pattern' argument", {
|
|
537
|
+
type: "search",
|
|
538
|
+
query: "",
|
|
539
|
+
toolName: "grep",
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
const detailBase = { type: "search", query: pattern, toolName: "grep" };
|
|
543
|
+
let regex;
|
|
544
|
+
try {
|
|
545
|
+
regex = new RegExp(pattern);
|
|
546
|
+
}
|
|
547
|
+
catch (error) {
|
|
548
|
+
return errorOutcome(`Invalid regular expression: ${error instanceof Error ? error.message : String(error)}`, detailBase);
|
|
549
|
+
}
|
|
550
|
+
const rootPath = resolveToolPath(input.cwd, readString(input.arguments, "path") ?? ".");
|
|
551
|
+
const maxResults = Math.min(readNumber(input.arguments, "max_results") ?? MAX_GREP_MATCHES, MAX_GREP_MATCHES);
|
|
552
|
+
const matches = [];
|
|
553
|
+
const filesWithMatches = new Set();
|
|
554
|
+
try {
|
|
555
|
+
await walkForGrep({
|
|
556
|
+
dirPath: rootPath,
|
|
557
|
+
rootPath,
|
|
558
|
+
regex,
|
|
559
|
+
maxResults,
|
|
560
|
+
matches,
|
|
561
|
+
filesWithMatches,
|
|
562
|
+
signal: input.signal,
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
catch (error) {
|
|
566
|
+
return errorOutcome(describeFsError(error), detailBase);
|
|
567
|
+
}
|
|
568
|
+
const truncated = matches.length >= maxResults;
|
|
569
|
+
const content = matches.length > 0 ? matches.join("\n") : `No matches for /${pattern}/ under ${rootPath}`;
|
|
570
|
+
return {
|
|
571
|
+
output: truncated ? `${content}\n[truncated at ${maxResults} matches]` : content,
|
|
572
|
+
detail: {
|
|
573
|
+
...detailBase,
|
|
574
|
+
content,
|
|
575
|
+
numMatches: matches.length,
|
|
576
|
+
numFiles: filesWithMatches.size,
|
|
577
|
+
truncated,
|
|
578
|
+
},
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
async function walkForGrep(context) {
|
|
582
|
+
if (context.matches.length >= context.maxResults || context.signal?.aborted) {
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
const entries = await fs.readdir(context.dirPath, { withFileTypes: true });
|
|
586
|
+
for (const entry of entries) {
|
|
587
|
+
if (context.matches.length >= context.maxResults || context.signal?.aborted) {
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
const entryPath = path.join(context.dirPath, entry.name);
|
|
591
|
+
if (entry.isDirectory()) {
|
|
592
|
+
if (!SKIPPED_DIRECTORIES.has(entry.name)) {
|
|
593
|
+
await walkForGrep({ ...context, dirPath: entryPath });
|
|
594
|
+
}
|
|
595
|
+
continue;
|
|
596
|
+
}
|
|
597
|
+
if (!entry.isFile()) {
|
|
598
|
+
continue;
|
|
599
|
+
}
|
|
600
|
+
await grepFile(context, entryPath);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
async function grepFile(context, filePath) {
|
|
604
|
+
let stat;
|
|
605
|
+
try {
|
|
606
|
+
stat = await fs.stat(filePath);
|
|
607
|
+
}
|
|
608
|
+
catch {
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
if (stat.size > MAX_GREP_FILE_BYTES) {
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
let content;
|
|
615
|
+
try {
|
|
616
|
+
content = await fs.readFile(filePath, "utf8");
|
|
617
|
+
}
|
|
618
|
+
catch {
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
if (content.includes("")) {
|
|
622
|
+
return; // binary
|
|
623
|
+
}
|
|
624
|
+
const relPath = path.relative(context.rootPath, filePath) || filePath;
|
|
625
|
+
const lines = content.split("\n");
|
|
626
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
627
|
+
if (context.matches.length >= context.maxResults) {
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
const line = lines[index] ?? "";
|
|
631
|
+
if (context.regex.test(line)) {
|
|
632
|
+
context.filesWithMatches.add(filePath);
|
|
633
|
+
context.matches.push(`${relPath}:${index + 1}:${line.slice(0, 400)}`);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
async function writeFileTool(input) {
|
|
638
|
+
const relPath = readString(input.arguments, "path");
|
|
639
|
+
const content = readString(input.arguments, "content");
|
|
640
|
+
if (!relPath || content === undefined) {
|
|
641
|
+
return errorOutcome("write_file requires 'path' and 'content' arguments", {
|
|
642
|
+
type: "unknown",
|
|
643
|
+
input: input.arguments,
|
|
644
|
+
output: null,
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
const filePath = resolveToolPath(input.cwd, relPath);
|
|
648
|
+
const detail = { type: "write", filePath, content };
|
|
649
|
+
try {
|
|
650
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
651
|
+
await fs.writeFile(filePath, content, "utf8");
|
|
652
|
+
}
|
|
653
|
+
catch (error) {
|
|
654
|
+
return errorOutcome(describeFsError(error), detail);
|
|
655
|
+
}
|
|
656
|
+
return { output: `Wrote ${Buffer.byteLength(content, "utf8")} bytes to ${filePath}`, detail };
|
|
657
|
+
}
|
|
658
|
+
async function editFileTool(input) {
|
|
659
|
+
const relPath = readString(input.arguments, "path");
|
|
660
|
+
const oldString = readString(input.arguments, "old_string");
|
|
661
|
+
const newString = readString(input.arguments, "new_string");
|
|
662
|
+
if (!relPath || oldString === undefined || newString === undefined) {
|
|
663
|
+
return errorOutcome("edit_file requires 'path', 'old_string' and 'new_string' arguments", {
|
|
664
|
+
type: "unknown",
|
|
665
|
+
input: input.arguments,
|
|
666
|
+
output: null,
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
const filePath = resolveToolPath(input.cwd, relPath);
|
|
670
|
+
const detail = { type: "edit", filePath, oldString, newString };
|
|
671
|
+
let content;
|
|
672
|
+
try {
|
|
673
|
+
content = await fs.readFile(filePath, "utf8");
|
|
674
|
+
}
|
|
675
|
+
catch (error) {
|
|
676
|
+
return errorOutcome(describeFsError(error), detail);
|
|
677
|
+
}
|
|
678
|
+
const occurrences = content.split(oldString).length - 1;
|
|
679
|
+
if (occurrences === 0) {
|
|
680
|
+
return errorOutcome(`old_string not found in ${filePath}`, detail);
|
|
681
|
+
}
|
|
682
|
+
const replaceAll = input.arguments["replace_all"] === true;
|
|
683
|
+
if (occurrences > 1 && !replaceAll) {
|
|
684
|
+
return errorOutcome(`old_string matches ${occurrences} times in ${filePath} — add more context or set replace_all`, detail);
|
|
685
|
+
}
|
|
686
|
+
// split/join, never String.replace: with a string search value, replace()
|
|
687
|
+
// interprets $&, $`, $' and $$ in the replacement and silently splices
|
|
688
|
+
// surrounding file content into the write.
|
|
689
|
+
const updated = content.split(oldString).join(newString);
|
|
690
|
+
try {
|
|
691
|
+
await fs.writeFile(filePath, updated, "utf8");
|
|
692
|
+
}
|
|
693
|
+
catch (error) {
|
|
694
|
+
return errorOutcome(describeFsError(error), detail);
|
|
695
|
+
}
|
|
696
|
+
const replacements = replaceAll ? occurrences : 1;
|
|
697
|
+
return { output: `Replaced ${replacements} occurrence(s) in ${filePath}`, detail };
|
|
698
|
+
}
|
|
699
|
+
async function runCommandTool(input) {
|
|
700
|
+
const command = readString(input.arguments, "command");
|
|
701
|
+
if (!command) {
|
|
702
|
+
return errorOutcome("run_command requires a 'command' argument", {
|
|
703
|
+
type: "shell",
|
|
704
|
+
command: "",
|
|
705
|
+
cwd: input.cwd,
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
const timeoutSeconds = readNumber(input.arguments, "timeout_seconds");
|
|
709
|
+
const timeoutMs = Math.min((timeoutSeconds ?? DEFAULT_COMMAND_TIMEOUT_MS / 1000) * 1000, MAX_COMMAND_TIMEOUT_MS);
|
|
710
|
+
const result = await runShellCommand(command, input.cwd, timeoutMs, input.signal);
|
|
711
|
+
const { text, truncated } = truncateOutput(result.output, MAX_COMMAND_OUTPUT_CHARS);
|
|
712
|
+
const outputText = truncated ? `${text}\n[output truncated]` : text;
|
|
713
|
+
const statusLine = describeShellStatus(result, timeoutMs);
|
|
714
|
+
return {
|
|
715
|
+
output: outputText ? `${outputText}\n${statusLine}` : statusLine,
|
|
716
|
+
detail: {
|
|
717
|
+
type: "shell",
|
|
718
|
+
command,
|
|
719
|
+
cwd: input.cwd,
|
|
720
|
+
output: outputText,
|
|
721
|
+
exitCode: result.exitCode,
|
|
722
|
+
},
|
|
723
|
+
isError: result.exitCode !== 0,
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
function describeShellStatus(result, timeoutMs) {
|
|
727
|
+
if (result.exitCode !== null) {
|
|
728
|
+
return `[exit code ${result.exitCode}]`;
|
|
729
|
+
}
|
|
730
|
+
if (result.timedOut) {
|
|
731
|
+
return `[command timed out after ${Math.round(timeoutMs / 1000)}s]`;
|
|
732
|
+
}
|
|
733
|
+
return "[command was interrupted]";
|
|
734
|
+
}
|
|
735
|
+
async function runShellCommand(command, cwd, timeoutMs, signal) {
|
|
736
|
+
// stdin closed from the start — a command that reads stdin should see EOF
|
|
737
|
+
// immediately instead of hanging until the timeout kills it.
|
|
738
|
+
const child = spawn(command, {
|
|
739
|
+
shell: true,
|
|
740
|
+
cwd,
|
|
741
|
+
windowsHide: true,
|
|
742
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
743
|
+
});
|
|
744
|
+
let output = "";
|
|
745
|
+
let timedOut = false;
|
|
746
|
+
const kill = () => {
|
|
747
|
+
// shell:true spawns an intermediate shell; child.kill only reaches the
|
|
748
|
+
// shell on Windows, so use taskkill to take the whole tree down.
|
|
749
|
+
if (process.platform === "win32" && child.pid) {
|
|
750
|
+
spawn("taskkill", ["/T", "/F", "/PID", String(child.pid)], { windowsHide: true });
|
|
751
|
+
}
|
|
752
|
+
else {
|
|
753
|
+
child.kill("SIGKILL");
|
|
754
|
+
}
|
|
755
|
+
};
|
|
756
|
+
const timer = setTimeout(() => {
|
|
757
|
+
timedOut = true;
|
|
758
|
+
kill();
|
|
759
|
+
}, timeoutMs);
|
|
760
|
+
const onAbort = () => {
|
|
761
|
+
kill();
|
|
762
|
+
};
|
|
763
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
764
|
+
const append = (chunk) => {
|
|
765
|
+
if (output.length < MAX_COMMAND_BUFFER_BYTES) {
|
|
766
|
+
output += chunk.toString("utf8");
|
|
767
|
+
}
|
|
768
|
+
};
|
|
769
|
+
child.stdout?.on("data", append);
|
|
770
|
+
child.stderr?.on("data", append);
|
|
771
|
+
let exitCode = null;
|
|
772
|
+
try {
|
|
773
|
+
// events.once rejects when the child emits "error" (spawn failure) —
|
|
774
|
+
// the one case where "close" never arrives.
|
|
775
|
+
const [code] = (await once(child, "close"));
|
|
776
|
+
exitCode = code;
|
|
777
|
+
}
|
|
778
|
+
catch (error) {
|
|
779
|
+
output += `\n${error instanceof Error ? error.message : String(error)}`;
|
|
780
|
+
}
|
|
781
|
+
finally {
|
|
782
|
+
clearTimeout(timer);
|
|
783
|
+
signal?.removeEventListener("abort", onAbort);
|
|
784
|
+
}
|
|
785
|
+
return {
|
|
786
|
+
output,
|
|
787
|
+
exitCode: timedOut || signal?.aborted ? null : exitCode,
|
|
788
|
+
timedOut,
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
// ---------------------------------------------------------------------------
|
|
792
|
+
// DuckDuckGo web search & fetch helpers
|
|
793
|
+
// ---------------------------------------------------------------------------
|
|
794
|
+
/** Make an HTTP request with an optional timeout and abort signal. */
|
|
795
|
+
async function fetchWithTimeout(url, init, timeoutMs = 15000) {
|
|
796
|
+
const controller = new AbortController();
|
|
797
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
798
|
+
try {
|
|
799
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
800
|
+
}
|
|
801
|
+
finally {
|
|
802
|
+
clearTimeout(timeout);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
/** Simple HTML entity decoder for common entities. */
|
|
806
|
+
function decodeHtmlEntities(s) {
|
|
807
|
+
return s
|
|
808
|
+
.replace(/&/g, "&")
|
|
809
|
+
.replace(/</g, "<")
|
|
810
|
+
.replace(/>/g, ">")
|
|
811
|
+
.replace(/"/g, '"')
|
|
812
|
+
.replace(/'/g, "'")
|
|
813
|
+
.replace(/&#(\d+);/g, (_, d) => String.fromCodePoint(Number.parseInt(d, 10)))
|
|
814
|
+
.replace(/&#x([0-9a-fA-F]+);/g, (_, h) => String.fromCodePoint(Number.parseInt(h, 16)));
|
|
815
|
+
}
|
|
816
|
+
/** Strip HTML tags and collapse whitespace. */
|
|
817
|
+
function stripHtml(html) {
|
|
818
|
+
return html
|
|
819
|
+
.replace(/<!--[\s\S]*?-->/g, "")
|
|
820
|
+
.replace(/<[^>]+>/g, "")
|
|
821
|
+
.replace(/\r\n/g, "\n")
|
|
822
|
+
.replace(/\r/g, "\n")
|
|
823
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
824
|
+
.replace(/\s+/g, " ")
|
|
825
|
+
.trim();
|
|
826
|
+
}
|
|
827
|
+
async function ddgInstantAnswerSearch(query) {
|
|
828
|
+
const url = new URL(`${DDG_API_URL}/`);
|
|
829
|
+
url.searchParams.set("q", query);
|
|
830
|
+
url.searchParams.set("format", "json");
|
|
831
|
+
url.searchParams.set("no_redirect", "1");
|
|
832
|
+
const response = await fetchWithTimeout(url.toString(), {
|
|
833
|
+
headers: {
|
|
834
|
+
Accept: "application/json",
|
|
835
|
+
"User-Agent": "Mozilla/5.0 (compatible; Otto/1.0; +https://github.com/otto-ai/otto-code)",
|
|
836
|
+
},
|
|
837
|
+
}, WEB_SEARCH_TIMEOUT_MS);
|
|
838
|
+
if (!response.ok) {
|
|
839
|
+
throw new Error(`DuckDuckGo API returned ${response.status} ${response.statusText}`);
|
|
840
|
+
}
|
|
841
|
+
const text = await response.text();
|
|
842
|
+
if (!text.trim()) {
|
|
843
|
+
return {};
|
|
844
|
+
}
|
|
845
|
+
try {
|
|
846
|
+
return JSON.parse(text);
|
|
847
|
+
}
|
|
848
|
+
catch {
|
|
849
|
+
return {};
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
async function ddgHtmlSearch(query) {
|
|
853
|
+
const url = new URL(DDG_HTML_URL);
|
|
854
|
+
url.searchParams.set("q", query);
|
|
855
|
+
const response = await fetchWithTimeout(url.toString(), {
|
|
856
|
+
headers: {
|
|
857
|
+
Accept: "text/html",
|
|
858
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
859
|
+
},
|
|
860
|
+
}, WEB_SEARCH_TIMEOUT_MS);
|
|
861
|
+
if (!response.ok) {
|
|
862
|
+
throw new Error(`DuckDuckGo HTML search returned ${response.status} ${response.statusText}`);
|
|
863
|
+
}
|
|
864
|
+
const html = await response.text();
|
|
865
|
+
return parseDdgHtmlResults(html);
|
|
866
|
+
}
|
|
867
|
+
export function parseDdgHtmlResults(html) {
|
|
868
|
+
const results = [];
|
|
869
|
+
// DDG's container markup changes across revisions (<li class="result">,
|
|
870
|
+
// <div class="result web-result">, …) and attribute order isn't stable, so
|
|
871
|
+
// key off the stable classes instead: the title anchor carries result__a
|
|
872
|
+
// and the snippet element (between one title anchor and the next) carries
|
|
873
|
+
// result__snippet.
|
|
874
|
+
const anchorRegex = /<a\b[^>]*\bclass="[^"]*\bresult__a\b[^"]*"[^>]*>([\s\S]*?)<\/a>/gi;
|
|
875
|
+
const snippetRegex = /<([a-z]+)\b[^>]*\bclass="[^"]*\bresult__snippet\b[^"]*"[^>]*>([\s\S]*?)<\/\1>/i;
|
|
876
|
+
const anchors = [];
|
|
877
|
+
let match;
|
|
878
|
+
while ((match = anchorRegex.exec(html)) !== null && anchors.length < MAX_WEB_SEARCH_RESULTS) {
|
|
879
|
+
anchors.push({
|
|
880
|
+
tag: match[0],
|
|
881
|
+
inner: match[1],
|
|
882
|
+
start: match.index,
|
|
883
|
+
end: anchorRegex.lastIndex,
|
|
884
|
+
});
|
|
885
|
+
}
|
|
886
|
+
for (const [index, anchor] of anchors.entries()) {
|
|
887
|
+
const title = stripHtml(decodeHtmlEntities(anchor.inner)).trim();
|
|
888
|
+
// DDG wraps results in /l/?uddg=<encoded> redirect links; recover the
|
|
889
|
+
// destination (query params intact — stripping them breaks links that
|
|
890
|
+
// need them) and fall back to the raw href.
|
|
891
|
+
const hrefMatch = anchor.tag.match(/\bhref="([^"]+)"/i);
|
|
892
|
+
let url = hrefMatch ? decodeHtmlEntities(hrefMatch[1]) : "";
|
|
893
|
+
const uddgMatch = url.match(/[?&]uddg=([^&]+)/);
|
|
894
|
+
if (uddgMatch) {
|
|
895
|
+
try {
|
|
896
|
+
url = decodeURIComponent(uddgMatch[1]);
|
|
897
|
+
}
|
|
898
|
+
catch {
|
|
899
|
+
// Malformed encoding — keep the redirect URL, it still resolves.
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
const segment = html.slice(anchor.end, anchors[index + 1]?.start ?? html.length);
|
|
903
|
+
const snippetMatch = segment.match(snippetRegex);
|
|
904
|
+
const snippet = snippetMatch ? stripHtml(decodeHtmlEntities(snippetMatch[2])).trim() : "";
|
|
905
|
+
if (title || url) {
|
|
906
|
+
results.push({ title, url, snippet });
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
return results;
|
|
910
|
+
}
|
|
911
|
+
async function webSearchTool(input) {
|
|
912
|
+
const query = readString(input.arguments, "query");
|
|
913
|
+
if (!query) {
|
|
914
|
+
return errorOutcome("web_search requires a 'query' argument", {
|
|
915
|
+
type: "search",
|
|
916
|
+
query: "",
|
|
917
|
+
toolName: "web_search",
|
|
918
|
+
});
|
|
919
|
+
}
|
|
920
|
+
const detailBase = {
|
|
921
|
+
type: "search",
|
|
922
|
+
query,
|
|
923
|
+
toolName: "web_search",
|
|
924
|
+
};
|
|
925
|
+
try {
|
|
926
|
+
// Try the Instant Answer API first (fast, structured JSON)
|
|
927
|
+
const instantResults = await ddgInstantAnswerSearch(query);
|
|
928
|
+
const webResults = [];
|
|
929
|
+
const annotations = [];
|
|
930
|
+
// Extract results from the Instant Answer API. Results and annotations
|
|
931
|
+
// are joined by index later, so an annotation slot (possibly empty) is
|
|
932
|
+
// pushed for every kept result — filling them under different conditions
|
|
933
|
+
// attaches snippets to the wrong result.
|
|
934
|
+
if (instantResults.Results && instantResults.Results.length > 0) {
|
|
935
|
+
for (const r of instantResults.Results) {
|
|
936
|
+
const url = r.AbstractURL || r.URL || "";
|
|
937
|
+
if (!url) {
|
|
938
|
+
continue;
|
|
939
|
+
}
|
|
940
|
+
webResults.push({ title: r.Heading || "", url });
|
|
941
|
+
annotations.push(r.Text ?? "");
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
// If we got good results from the API, return them
|
|
945
|
+
if (webResults.length > 0) {
|
|
946
|
+
const lines = webResults.map((r, i) => {
|
|
947
|
+
const snippet = annotations[i] || "";
|
|
948
|
+
return `${i + 1}. ${r.title}\n URL: ${r.url}${snippet ? "\n " + snippet : ""}`;
|
|
949
|
+
});
|
|
950
|
+
const output = `Web search results for "${query}":\n\n${lines.join("\n\n")}`;
|
|
951
|
+
return {
|
|
952
|
+
output,
|
|
953
|
+
detail: {
|
|
954
|
+
...detailBase,
|
|
955
|
+
webResults,
|
|
956
|
+
...(annotations.some((snippet) => snippet.length > 0) ? { annotations } : {}),
|
|
957
|
+
},
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
// Fallback: scrape DuckDuckGo HTML results
|
|
961
|
+
const htmlResults = await ddgHtmlSearch(query);
|
|
962
|
+
if (htmlResults.length > 0) {
|
|
963
|
+
const webResultsFromHtml = htmlResults.map((r) => ({
|
|
964
|
+
title: r.title,
|
|
965
|
+
url: r.url,
|
|
966
|
+
}));
|
|
967
|
+
const lines = htmlResults.map((r, i) => `${i + 1}. ${r.title}\n URL: ${r.url}${r.snippet ? "\n " + r.snippet : ""}`);
|
|
968
|
+
const output = `Web search results for "${query}":\n\n${lines.join("\n\n")}`;
|
|
969
|
+
return {
|
|
970
|
+
output,
|
|
971
|
+
detail: {
|
|
972
|
+
...detailBase,
|
|
973
|
+
webResults: webResultsFromHtml,
|
|
974
|
+
},
|
|
975
|
+
};
|
|
976
|
+
}
|
|
977
|
+
return {
|
|
978
|
+
output: `No results found for "${query}"`,
|
|
979
|
+
detail: detailBase,
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
catch (error) {
|
|
983
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
984
|
+
return errorOutcome(`web_search failed: ${message}`, detailBase);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
/**
|
|
988
|
+
* Fetch a URL, following redirects manually so every hop is re-validated
|
|
989
|
+
* against the SSRF blocklist — a public URL must not be able to redirect into
|
|
990
|
+
* an internal/metadata address. Each hop's connection is pinned to the
|
|
991
|
+
* addresses that passed validation (see buildPinnedDispatcher), so a low-TTL
|
|
992
|
+
* DNS-rebinding server cannot answer validation with a public IP and the
|
|
993
|
+
* connection with a private one. Throws on blocked hosts or excessive
|
|
994
|
+
* redirects.
|
|
995
|
+
*/
|
|
996
|
+
async function fetchWithSsrfGuard(urlString) {
|
|
997
|
+
let currentUrl = urlString;
|
|
998
|
+
for (let hop = 0; hop <= MAX_WEB_FETCH_REDIRECTS; hop++) {
|
|
999
|
+
const target = await validateUrlForFetch(currentUrl);
|
|
1000
|
+
const dispatcher = buildPinnedDispatcher(target);
|
|
1001
|
+
const dispose = async () => {
|
|
1002
|
+
await dispatcher.close().catch(() => undefined);
|
|
1003
|
+
};
|
|
1004
|
+
let response;
|
|
1005
|
+
try {
|
|
1006
|
+
// undici's own fetch, not the global one: the pinned dispatcher must
|
|
1007
|
+
// come from the same undici build that executes the request.
|
|
1008
|
+
response = await undiciFetch(currentUrl, {
|
|
1009
|
+
headers: {
|
|
1010
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
1011
|
+
Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
1012
|
+
"Accept-Language": "en-US,en;q=0.9",
|
|
1013
|
+
},
|
|
1014
|
+
redirect: "manual",
|
|
1015
|
+
signal: AbortSignal.timeout(WEB_FETCH_TIMEOUT_MS),
|
|
1016
|
+
dispatcher,
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
catch (error) {
|
|
1020
|
+
await dispose();
|
|
1021
|
+
throw error;
|
|
1022
|
+
}
|
|
1023
|
+
if (response.status >= 300 && response.status < 400) {
|
|
1024
|
+
const location = response.headers.get("location");
|
|
1025
|
+
if (!location) {
|
|
1026
|
+
return { response, dispose };
|
|
1027
|
+
}
|
|
1028
|
+
await response.body?.cancel().catch(() => undefined);
|
|
1029
|
+
await dispose();
|
|
1030
|
+
if (hop === MAX_WEB_FETCH_REDIRECTS) {
|
|
1031
|
+
throw new Error(`Too many redirects (>${MAX_WEB_FETCH_REDIRECTS})`);
|
|
1032
|
+
}
|
|
1033
|
+
currentUrl = new URL(location, currentUrl).toString();
|
|
1034
|
+
continue;
|
|
1035
|
+
}
|
|
1036
|
+
return { response, dispose };
|
|
1037
|
+
}
|
|
1038
|
+
throw new Error("No response received");
|
|
1039
|
+
}
|
|
1040
|
+
/**
|
|
1041
|
+
* Read a response body up to maxBytes. Stops reading and cancels the stream
|
|
1042
|
+
* the moment the cap is crossed — the whole point is that a hostile server
|
|
1043
|
+
* cannot stream an unbounded body into daemon memory before a size check.
|
|
1044
|
+
*/
|
|
1045
|
+
async function readBodyWithCap(response, maxBytes) {
|
|
1046
|
+
if (!response.body) {
|
|
1047
|
+
return { body: Buffer.alloc(0), exceededCap: false };
|
|
1048
|
+
}
|
|
1049
|
+
const reader = response.body.getReader();
|
|
1050
|
+
const chunks = [];
|
|
1051
|
+
let total = 0;
|
|
1052
|
+
try {
|
|
1053
|
+
for (;;) {
|
|
1054
|
+
const { done, value } = await reader.read();
|
|
1055
|
+
if (done) {
|
|
1056
|
+
break;
|
|
1057
|
+
}
|
|
1058
|
+
const chunk = Buffer.from(value);
|
|
1059
|
+
total += chunk.byteLength;
|
|
1060
|
+
if (total > maxBytes) {
|
|
1061
|
+
await reader.cancel().catch(() => undefined);
|
|
1062
|
+
return { body: Buffer.concat(chunks), exceededCap: true };
|
|
1063
|
+
}
|
|
1064
|
+
chunks.push(chunk);
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
finally {
|
|
1068
|
+
reader.releaseLock();
|
|
1069
|
+
}
|
|
1070
|
+
return { body: Buffer.concat(chunks), exceededCap: false };
|
|
1071
|
+
}
|
|
1072
|
+
async function webFetchTool(input) {
|
|
1073
|
+
const urlString = readString(input.arguments, "url");
|
|
1074
|
+
if (!urlString) {
|
|
1075
|
+
return errorOutcome("web_fetch requires a 'url' argument", {
|
|
1076
|
+
type: "fetch",
|
|
1077
|
+
url: "",
|
|
1078
|
+
});
|
|
1079
|
+
}
|
|
1080
|
+
// Validate URL shape up front for a friendly error message; protocol and
|
|
1081
|
+
// SSRF checks happen per-hop inside fetchWithSsrfGuard.
|
|
1082
|
+
if (!URL.canParse(urlString)) {
|
|
1083
|
+
return errorOutcome(`web_fetch failed: Invalid URL "${urlString}"`, {
|
|
1084
|
+
type: "fetch",
|
|
1085
|
+
url: urlString,
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1088
|
+
// max_length is model-controlled — clamp it so a tool result can never
|
|
1089
|
+
// exceed the shared per-result context budget.
|
|
1090
|
+
const requestedLength = readNumber(input.arguments, "max_length");
|
|
1091
|
+
const maxLength = Math.min(Math.max(requestedLength ?? MAX_WEB_FETCH_OUTPUT, 1), MAX_TOOL_OUTPUT_CHARS);
|
|
1092
|
+
const detailBase = {
|
|
1093
|
+
type: "fetch",
|
|
1094
|
+
url: urlString,
|
|
1095
|
+
};
|
|
1096
|
+
try {
|
|
1097
|
+
const { response, dispose } = await fetchWithSsrfGuard(urlString);
|
|
1098
|
+
let bodyResult;
|
|
1099
|
+
try {
|
|
1100
|
+
// Reject oversized responses up front when the server declares a
|
|
1101
|
+
// length; the streamed cap below covers servers that lie or omit it.
|
|
1102
|
+
const declaredLength = Number.parseInt(response.headers.get("content-length") ?? "", 10);
|
|
1103
|
+
if (Number.isFinite(declaredLength) && declaredLength > MAX_WEB_FETCH_BYTES) {
|
|
1104
|
+
await response.body?.cancel().catch(() => undefined);
|
|
1105
|
+
return errorOutcome(`web_fetch failed: Response too large (${declaredLength} bytes, max ${MAX_WEB_FETCH_BYTES})`, detailBase);
|
|
1106
|
+
}
|
|
1107
|
+
bodyResult = await readBodyWithCap(response, MAX_WEB_FETCH_BYTES);
|
|
1108
|
+
}
|
|
1109
|
+
finally {
|
|
1110
|
+
await dispose();
|
|
1111
|
+
}
|
|
1112
|
+
if (bodyResult.exceededCap) {
|
|
1113
|
+
return errorOutcome(`web_fetch failed: Response too large (>${MAX_WEB_FETCH_BYTES} bytes)`, detailBase);
|
|
1114
|
+
}
|
|
1115
|
+
const statusText = response.status >= 400 ? response.statusText : "OK";
|
|
1116
|
+
const bytes = bodyResult.body.byteLength;
|
|
1117
|
+
const encoding = normalizeBufferEncoding(extractCharset(response.headers.get("content-type") ?? ""));
|
|
1118
|
+
const text = bodyResult.body.toString(encoding);
|
|
1119
|
+
const isHtml = response.headers.get("content-type")?.includes("text/html") ?? false;
|
|
1120
|
+
let content;
|
|
1121
|
+
if (isHtml) {
|
|
1122
|
+
content = extractReadableTextFromHtml(text);
|
|
1123
|
+
}
|
|
1124
|
+
else {
|
|
1125
|
+
// Try to parse as JSON for pretty-printing
|
|
1126
|
+
try {
|
|
1127
|
+
const json = JSON.parse(text);
|
|
1128
|
+
content = JSON.stringify(json, null, 2);
|
|
1129
|
+
}
|
|
1130
|
+
catch {
|
|
1131
|
+
content = text;
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
const truncated = content.length > maxLength;
|
|
1135
|
+
const result = truncated ? content.slice(0, maxLength) : content;
|
|
1136
|
+
const output = `Fetched ${urlString} (${response.status} ${statusText}, ${bytes} bytes)\n\n${result}${truncated ? "\n\n[truncated]" : ""}`;
|
|
1137
|
+
return {
|
|
1138
|
+
output: capToolOutput(output),
|
|
1139
|
+
detail: {
|
|
1140
|
+
...detailBase,
|
|
1141
|
+
result: result.slice(0, 500) + (truncated ? "..." : ""),
|
|
1142
|
+
code: response.status,
|
|
1143
|
+
codeText: statusText,
|
|
1144
|
+
bytes,
|
|
1145
|
+
},
|
|
1146
|
+
};
|
|
1147
|
+
}
|
|
1148
|
+
catch (error) {
|
|
1149
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1150
|
+
return errorOutcome(`web_fetch failed: ${message}`, detailBase);
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
/** Extract a charset from a Content-Type header value. */
|
|
1154
|
+
function extractCharset(contentType) {
|
|
1155
|
+
const match = contentType.match(/charset=([^\s;]+)/i);
|
|
1156
|
+
return match ? match[1] : null;
|
|
1157
|
+
}
|
|
1158
|
+
/**
|
|
1159
|
+
* Map a charset label to a Node Buffer encoding, falling back to utf-8 for
|
|
1160
|
+
* anything Node's Buffer can't decode (e.g. windows-1252). Buffer.toString
|
|
1161
|
+
* throws on unknown encodings, so this keeps web_fetch from failing on pages
|
|
1162
|
+
* that declare an exotic charset.
|
|
1163
|
+
*/
|
|
1164
|
+
function normalizeBufferEncoding(charset) {
|
|
1165
|
+
switch (charset?.toLowerCase().trim()) {
|
|
1166
|
+
case "utf-8":
|
|
1167
|
+
case "utf8":
|
|
1168
|
+
return "utf-8";
|
|
1169
|
+
case "iso-8859-1":
|
|
1170
|
+
case "latin1":
|
|
1171
|
+
case "us-ascii":
|
|
1172
|
+
case "ascii":
|
|
1173
|
+
return "latin1";
|
|
1174
|
+
case "utf-16le":
|
|
1175
|
+
case "utf-16":
|
|
1176
|
+
case "ucs-2":
|
|
1177
|
+
return "utf16le";
|
|
1178
|
+
default:
|
|
1179
|
+
return "utf-8";
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
/**
|
|
1183
|
+
* Extract readable text from an HTML document.
|
|
1184
|
+
* Strips scripts, styles, nav, footer, etc. and returns the main content.
|
|
1185
|
+
*/
|
|
1186
|
+
function extractReadableTextFromHtml(html) {
|
|
1187
|
+
let text = html;
|
|
1188
|
+
// Remove script and style contents
|
|
1189
|
+
text = text.replace(/<script[\s\S]*?<\/script>/gi, "");
|
|
1190
|
+
text = text.replace(/<style[\s\S]*?<\/style>/gi, "");
|
|
1191
|
+
text = text.replace(/<noscript[\s\S]*?<\/noscript>/gi, "");
|
|
1192
|
+
// Remove comments
|
|
1193
|
+
text = text.replace(/<!--[\s\S]*?-->/g, "");
|
|
1194
|
+
// Try to extract <body> content
|
|
1195
|
+
const bodyMatch = text.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
|
|
1196
|
+
if (bodyMatch) {
|
|
1197
|
+
text = bodyMatch[1];
|
|
1198
|
+
}
|
|
1199
|
+
// Try to extract <article> or <main> content (preferred for readability)
|
|
1200
|
+
const articleMatch = text.match(/<(?:article|main)[^>]*>([\s\S]*?)<\/(?:article|main)>/i);
|
|
1201
|
+
if (articleMatch) {
|
|
1202
|
+
text = articleMatch[1];
|
|
1203
|
+
}
|
|
1204
|
+
// Add newlines for block elements
|
|
1205
|
+
text = text.replace(/<\/(?:p|div|h[1-6]|li|br|tr|blockquote|pre)>/gi, "\n");
|
|
1206
|
+
text = text.replace(/<li/gi, "\n- ");
|
|
1207
|
+
text = text.replace(/<br\s*\/?>/gi, "\n");
|
|
1208
|
+
// Remove remaining tags
|
|
1209
|
+
text = text.replace(/<[^>]+>/g, "");
|
|
1210
|
+
// Decode entities
|
|
1211
|
+
text = decodeHtmlEntities(text);
|
|
1212
|
+
// Collapse whitespace
|
|
1213
|
+
text = text
|
|
1214
|
+
.replace(/\r\n/g, "\n")
|
|
1215
|
+
.replace(/\r/g, "\n")
|
|
1216
|
+
.replace(/[ \t]+/g, " ")
|
|
1217
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
1218
|
+
.trim();
|
|
1219
|
+
return text;
|
|
1220
|
+
}
|
|
1221
|
+
function describeFsError(error) {
|
|
1222
|
+
return error instanceof Error ? error.message : String(error);
|
|
1223
|
+
}
|
|
1224
|
+
//# sourceMappingURL=openai-compat-tools.js.map
|