@gajae-code/coding-agent 0.18.11 → 0.19.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/CHANGELOG.md +760 -0
- package/dist/types/async/job-manager.d.ts +7 -3
- package/dist/types/async/result-formatting.d.ts +2 -0
- package/dist/types/autoresearch/git.d.ts +1 -1
- package/dist/types/capability/fs.d.ts +2 -2
- package/dist/types/capability/rule.d.ts +7 -2
- package/dist/types/cli/accounts-cli.d.ts +2 -2
- package/dist/types/cli/append-system-prompt.d.ts +6 -0
- package/dist/types/cli/args.d.ts +7 -2
- package/dist/types/cli/customize-doctor-precedence.d.ts +16 -0
- package/dist/types/cli/customize-doctor.d.ts +4 -3
- package/dist/types/cli/doctor/install-repairs.d.ts +4 -1
- package/dist/types/cli/install-activation.d.ts +10 -4
- package/dist/types/cli/public-command-errors.d.ts +2 -0
- package/dist/types/cli/root-flags.d.ts +0 -12
- package/dist/types/cli/update-cli.d.ts +21 -3
- package/dist/types/cli-main.d.ts +0 -6
- package/dist/types/cli-ordinary.d.ts +2 -2
- package/dist/types/commands/accounts.d.ts +2 -1
- package/dist/types/commands/launch.d.ts +0 -6
- package/dist/types/commands/migrate.d.ts +1 -1
- package/dist/types/commands/sdk.d.ts +13 -0
- package/dist/types/commit/changelog/index.d.ts +1 -0
- package/dist/types/commit/map-reduce/index.d.ts +4 -4
- package/dist/types/config/autorouting-tier-map.d.ts +53 -1
- package/dist/types/config/config-hot-reload-trust.d.ts +12 -0
- package/dist/types/config/config-hot-reload.d.ts +44 -0
- package/dist/types/config/file-lock.d.ts +52 -3
- package/dist/types/config/model-discovery-manager.d.ts +2 -0
- package/dist/types/config/model-profile-activation.d.ts +79 -9
- package/dist/types/config/model-registry.d.ts +72 -8
- package/dist/types/config/model-resolver.d.ts +7 -4
- package/dist/types/config/models-config-schema.d.ts +10 -0
- package/dist/types/config/settings-schema.d.ts +57 -4
- package/dist/types/config/settings.d.ts +33 -1
- package/dist/types/coordinator-mcp/server.d.ts +4 -0
- package/dist/types/customization/import.d.ts +2 -0
- package/dist/types/customization/types.d.ts +1 -1
- package/dist/types/dap/config.d.ts +8 -4
- package/dist/types/dap/session.d.ts +11 -2
- package/dist/types/debug/system-info.d.ts +6 -1
- package/dist/types/discovery/helpers.d.ts +7 -2
- package/dist/types/discovery/mcp-compat.d.ts +3 -1
- package/dist/types/edit/index.d.ts +5 -0
- package/dist/types/edit/modes/patch.d.ts +14 -0
- package/dist/types/edit/modes/replace.d.ts +3 -0
- package/dist/types/eval/py/runtime.d.ts +9 -0
- package/dist/types/exec/bash-executor.d.ts +12 -0
- package/dist/types/export/ttsr.d.ts +19 -0
- package/dist/types/extensibility/extensions/types.d.ts +5 -1
- package/dist/types/extensibility/slash-commands.d.ts +1 -0
- package/dist/types/gjc-runtime/managed-owner-admission.d.ts +3 -0
- package/dist/types/gjc-runtime/ralplan-current-final.d.ts +54 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +5 -0
- package/dist/types/gjc-runtime/session-state-lock.d.ts +2 -0
- package/dist/types/gjc-runtime/state-runtime.d.ts +0 -2
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +24 -0
- package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +6 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +1 -0
- package/dist/types/harness-control-plane/receipt-spool.d.ts +4 -0
- package/dist/types/hindsight/content.d.ts +5 -6
- package/dist/types/internal-urls/types.d.ts +2 -0
- package/dist/types/lsp/clients/swiftlint-client.d.ts +0 -4
- package/dist/types/main.d.ts +23 -18
- package/dist/types/migrate/action-planner.d.ts +10 -1
- package/dist/types/migrate/adapters/codex.d.ts +2 -1
- package/dist/types/migrate/adapters/index.d.ts +42 -1
- package/dist/types/migrate/executor.d.ts +3 -1
- package/dist/types/migrate/hook-module.d.ts +23 -0
- package/dist/types/migrate/report.d.ts +2 -1
- package/dist/types/migrate/types.d.ts +82 -3
- package/dist/types/modes/components/agent-dashboard.d.ts +1 -1
- package/dist/types/modes/components/model-selector.d.ts +43 -0
- package/dist/types/modes/components/settings-selector.d.ts +6 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +2 -0
- package/dist/types/modes/prompt-suggestion-controller.d.ts +1 -1
- package/dist/types/modes/theme/theme.d.ts +1 -0
- package/dist/types/modes/utils/host-status.d.ts +15 -11
- package/dist/types/runtime-mcp/content-limits.d.ts +4 -0
- package/dist/types/runtime-mcp/manager.d.ts +7 -0
- package/dist/types/runtime-mcp/plugin-mcp-name-filter.d.ts +22 -0
- package/dist/types/runtime-mcp/project-host-values.d.ts +11 -5
- package/dist/types/runtime-mcp/tool-bridge.d.ts +4 -0
- package/dist/types/sdk/broker/broker-exit.d.ts +3 -3
- package/dist/types/sdk/broker/broker.d.ts +29 -2
- package/dist/types/sdk/broker/daemon-entry.d.ts +11 -1
- package/dist/types/sdk/broker/doctor-restart.d.ts +1 -1
- package/dist/types/sdk/broker/ensure.d.ts +141 -9
- package/dist/types/sdk/broker/hop.d.ts +45 -0
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +18 -10
- package/dist/types/sdk/broker/lifecycle.d.ts +22 -0
- package/dist/types/sdk/broker/lock-host-identity.d.ts +5 -0
- package/dist/types/sdk/broker/session-index.d.ts +6 -1
- package/dist/types/sdk/broker/transport.d.ts +2 -0
- package/dist/types/sdk/bus/chat-command-policy.d.ts +2 -1
- package/dist/types/sdk/bus/index.d.ts +44 -0
- package/dist/types/sdk/bus/reconciliation-store.d.ts +10 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +9 -1
- package/dist/types/sdk/bus/telegram-daemon.d.ts +2 -0
- package/dist/types/sdk/bus/threaded-render.d.ts +2 -1
- package/dist/types/sdk/cli/session-cli.d.ts +0 -1
- package/dist/types/sdk/host/control/operations.d.ts +5 -1
- package/dist/types/sdk/host/events.d.ts +4 -0
- package/dist/types/sdk/host/host.d.ts +2 -1
- package/dist/types/sdk/host/prompt-image-upload.d.ts +43 -0
- package/dist/types/sdk/host/session-runtime.d.ts +40 -8
- package/dist/types/sdk/host/surface-policy.d.ts +2 -1
- package/dist/types/sdk/lifecycle/service.d.ts +12 -0
- package/dist/types/sdk/prompt-deadline-flush.d.ts +42 -4
- package/dist/types/sdk/prompt-deadline-manager.d.ts +2 -0
- package/dist/types/sdk/prompt-status.d.ts +11 -4
- package/dist/types/sdk/protocol/adapter-validation.d.ts +10 -4
- package/dist/types/sdk/reconciliation-extensions.d.ts +1 -1
- package/dist/types/sdk/session-list.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +107 -9
- package/dist/types/session/internal/managed-session-scope.d.ts +2 -2
- package/dist/types/session/internal/managed-session-storage.d.ts +1 -1
- package/dist/types/session/internal/native-publish-outcome.d.ts +1 -1
- package/dist/types/session/messages.d.ts +5 -1
- package/dist/types/session/session-manager.d.ts +29 -3
- package/dist/types/session/startup-auth-config.d.ts +24 -2
- package/dist/types/session/streaming-output.d.ts +11 -0
- package/dist/types/session/terminal-abort.d.ts +3 -1
- package/dist/types/session-import/service.d.ts +1 -1
- package/dist/types/skill-state/workflow-mutation-guard.d.ts +20 -4
- package/dist/types/slash-commands/helpers/mcp.d.ts +5 -0
- package/dist/types/slash-commands/helpers/provider-auth-status.d.ts +8 -0
- package/dist/types/ssh/ssh-executor.d.ts +8 -1
- package/dist/types/system-prompt.d.ts +3 -2
- package/dist/types/task/executor.d.ts +19 -2
- package/dist/types/task/role-body.d.ts +11 -0
- package/dist/types/tools/ast-edit-write-path.d.ts +10 -0
- package/dist/types/tools/atomic-file-write.d.ts +9 -7
- package/dist/types/tools/awaiting-input.d.ts +28 -0
- package/dist/types/tools/bash-allowed-prefixes.d.ts +17 -0
- package/dist/types/tools/bash-skill-urls.d.ts +4 -0
- package/dist/types/tools/bash.d.ts +7 -0
- package/dist/types/tools/descriptors.d.ts +15 -2
- package/dist/types/tools/fetch.d.ts +7 -1
- package/dist/types/tools/index.d.ts +2 -0
- package/dist/types/tools/irc.d.ts +5 -3
- package/dist/types/tools/python.d.ts +1 -1
- package/dist/types/tools/report-issue.d.ts +41 -0
- package/dist/types/tools/subagent.d.ts +23 -0
- package/dist/types/tools/tmux-self-injection-guard.d.ts +2 -0
- package/dist/types/tools/ultragoal-ask-guard.d.ts +1 -1
- package/dist/types/tools/write.d.ts +2 -0
- package/dist/types/utils/file-mentions.d.ts +5 -0
- package/dist/types/utils/image-limits.d.ts +7 -0
- package/dist/types/utils/image-loading.d.ts +0 -1
- package/dist/types/utils/open.d.ts +0 -1
- package/dist/types/utils/pasted-image-loading.d.ts +1 -5
- package/dist/types/utils/prompt-suggestion.d.ts +2 -2
- package/dist/types/utils/shell-snapshot.d.ts +2 -0
- package/dist/types/utils/title-generator.d.ts +13 -3
- package/dist/types/web/scrapers/types.d.ts +8 -1
- package/dist/types/web/search/providers/codex.d.ts +6 -7
- package/dist/types/web/search/providers/insane.d.ts +7 -1
- package/dist/types/web/search/providers/searxng.d.ts +5 -0
- package/package.json +10 -9
- package/scripts/acp-conformance-agent.ts +12 -2
- package/scripts/build-binary.ts +2 -1
- package/scripts/capture-subagent-compact-panel-showcase.ts +105 -0
- package/scripts/generate-docs-index.ts +8 -1
- package/scripts/generate-sdk-operation-inventory.ts +38 -4
- package/src/async/job-manager.ts +356 -56
- package/src/async/result-formatting.ts +40 -0
- package/src/autoresearch/git.ts +9 -2
- package/src/autoresearch/prompts/prompt-setup.md +1 -1
- package/src/capability/fs.ts +58 -22
- package/src/capability/index.ts +2 -2
- package/src/capability/rule.ts +11 -2
- package/src/cli/accounts-cli.ts +176 -8
- package/src/cli/append-system-prompt.ts +12 -0
- package/src/cli/args.ts +11 -7
- package/src/cli/auth-broker-cli.ts +21 -11
- package/src/cli/customize-doctor-precedence.ts +31 -0
- package/src/cli/customize-doctor.ts +49 -39
- package/src/cli/doctor/install-repairs.ts +35 -5
- package/src/cli/fast-help.ts +1 -3
- package/src/cli/install-activation.ts +47 -4
- package/src/cli/migrate-cli.ts +19 -6
- package/src/cli/public-command-errors.ts +6 -0
- package/src/cli/root-flags.ts +2 -4
- package/src/cli/stats-cli.ts +9 -5
- package/src/cli/update-cli.ts +471 -13
- package/src/cli-ordinary.ts +7 -2
- package/src/commands/accounts.ts +10 -5
- package/src/commands/harness.ts +25 -15
- package/src/commands/migrate.ts +3 -3
- package/src/commands/sdk.ts +277 -50
- package/src/commands/ultragoal.ts +16 -8
- package/src/commit/agentic/agent.ts +6 -6
- package/src/commit/analysis/conventional.ts +14 -5
- package/src/commit/changelog/index.ts +39 -1
- package/src/commit/map-reduce/index.ts +7 -7
- package/src/commit/message.ts +10 -3
- package/src/commit/pipeline.ts +12 -12
- package/src/config/autorouting-tier-map.ts +50 -1
- package/src/config/config-file.ts +106 -3
- package/src/config/config-hot-reload-trust.ts +148 -0
- package/src/config/config-hot-reload.ts +742 -0
- package/src/config/file-lock.ts +1615 -328
- package/src/config/keybindings.ts +154 -7
- package/src/config/model-bindings-applier.ts +85 -47
- package/src/config/model-discovery-manager.ts +10 -0
- package/src/config/model-profile-activation.ts +889 -148
- package/src/config/model-registry.ts +1706 -205
- package/src/config/model-resolver.ts +52 -22
- package/src/config/models-config-schema.ts +6 -0
- package/src/config/settings-schema.ts +73 -4
- package/src/config/settings.ts +460 -24
- package/src/coordinator-mcp/policy.ts +26 -1
- package/src/coordinator-mcp/server.ts +37 -2
- package/src/crash/sanitize.ts +7 -1
- package/src/customization/import.ts +279 -8
- package/src/customization/inventory.ts +24 -8
- package/src/customization/types.ts +1 -0
- package/src/dap/client.ts +3 -2
- package/src/dap/config.ts +31 -11
- package/src/dap/defaults.json +1 -1
- package/src/dap/session.ts +161 -36
- package/src/debug/crash-diagnostics.ts +89 -6
- package/src/debug/report-bundle.ts +90 -2
- package/src/debug/system-info.ts +8 -3
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/shared/base-url.ts +21 -7
- package/src/discovery/agents-md.ts +1 -1
- package/src/discovery/builtin.ts +4 -3
- package/src/discovery/helpers.ts +25 -16
- package/src/discovery/mcp-compat.ts +120 -17
- package/src/edit/apply-patch/index.ts +12 -1
- package/src/edit/index.ts +204 -22
- package/src/edit/modes/patch.ts +214 -8
- package/src/edit/modes/replace.ts +136 -0
- package/src/eval/js/context-manager.ts +29 -3
- package/src/eval/js/worker-entry.ts +51 -18
- package/src/eval/py/executor.ts +14 -4
- package/src/eval/py/runner.py +144 -5
- package/src/eval/py/runtime.ts +44 -4
- package/src/exec/bash-executor.ts +81 -4
- package/src/exec/bash-shell-guardian.ts +4 -1
- package/src/exec/bash-shell-supervisor.ts +8 -4
- package/src/exec/isolated-shell.ts +5 -1
- package/src/exec/non-interactive-env.ts +0 -1
- package/src/export/html/index.ts +86 -6
- package/src/export/ttsr.ts +176 -1
- package/src/extensibility/custom-tools/wrapper.ts +3 -1
- package/src/extensibility/extensions/loader.ts +10 -1
- package/src/extensibility/extensions/ouroboros-ooo-bridge.ts +63 -1
- package/src/extensibility/extensions/types.ts +5 -1
- package/src/extensibility/hooks/loader.ts +10 -2
- package/src/extensibility/plugins/marketplace/fetcher.ts +2 -2
- package/src/extensibility/plugins/marketplace/manager.ts +36 -10
- package/src/extensibility/slash-commands.ts +5 -0
- package/src/gjc-runtime/gc-runtime.ts +37 -7
- package/src/gjc-runtime/launch-tmux.ts +30 -15
- package/src/gjc-runtime/launch-worktree.test.ts +148 -0
- package/src/gjc-runtime/launch-worktree.ts +101 -14
- package/src/gjc-runtime/managed-owner-admission.ts +46 -2
- package/src/gjc-runtime/ralplan-current-final.ts +294 -0
- package/src/gjc-runtime/ralplan-runtime.ts +95 -1
- package/src/gjc-runtime/session-resolution.ts +38 -6
- package/src/gjc-runtime/session-state-lock.ts +59 -14
- package/src/gjc-runtime/state-runtime.ts +23 -19
- package/src/gjc-runtime/state-writer.ts +109 -5
- package/src/gjc-runtime/tmux-sessions.ts +93 -13
- package/src/gjc-runtime/ultragoal-change-set.ts +90 -11
- package/src/gjc-runtime/ultragoal-guard.ts +9 -1
- package/src/gjc-runtime/ultragoal-runtime.ts +15 -5
- package/src/harness-control-plane/finalize.ts +11 -0
- package/src/harness-control-plane/receipt-spool.ts +105 -28
- package/src/harness-control-plane/storage.ts +124 -6
- package/src/hindsight/content.ts +116 -16
- package/src/hindsight/mental-models.ts +22 -10
- package/src/hindsight/state.ts +7 -4
- package/src/hooks/native-skill-hook.ts +17 -12
- package/src/internal-urls/artifact-protocol.ts +3 -1
- package/src/internal-urls/docs-index.generated.ts +23 -22
- package/src/internal-urls/local-protocol.ts +167 -8
- package/src/internal-urls/types.ts +2 -0
- package/src/lsp/client.ts +2 -1
- package/src/lsp/clients/biome-client.ts +2 -0
- package/src/lsp/clients/swiftlint-client.ts +2 -0
- package/src/lsp/index.ts +29 -0
- package/src/lsp/workspace-path.ts +23 -0
- package/src/main.ts +330 -92
- package/src/memories/index.ts +25 -2
- package/src/migrate/action-planner.ts +574 -2
- package/src/migrate/adapters/claude-code.ts +162 -5
- package/src/migrate/adapters/codex.ts +77 -7
- package/src/migrate/adapters/index.ts +258 -5
- package/src/migrate/adapters/opencode.ts +10 -1
- package/src/migrate/executor.ts +222 -7
- package/src/migrate/hook-module.ts +193 -0
- package/src/migrate/report.ts +63 -2
- package/src/migrate/types.ts +84 -4
- package/src/modes/DESIGN.md +51 -0
- package/src/modes/acp/acp-agent.ts +10 -1
- package/src/modes/acp/permission-mode.ts +18 -1
- package/src/modes/components/agent-dashboard.ts +20 -4
- package/src/modes/components/assistant-message.ts +55 -1
- package/src/modes/components/customization/customization-dashboard.ts +7 -0
- package/src/modes/components/customization/import-wizard.ts +29 -3
- package/src/modes/components/model-selector.ts +157 -103
- package/src/modes/components/settings-selector.ts +103 -37
- package/src/modes/components/welcome.ts +3 -1
- package/src/modes/controllers/command-controller.ts +300 -9
- package/src/modes/controllers/event-controller.ts +1 -1
- package/src/modes/controllers/extension-ui-controller.ts +31 -1
- package/src/modes/controllers/goal-mode-controller.ts +79 -10
- package/src/modes/controllers/input-controller.ts +2 -0
- package/src/modes/controllers/plan-mode-controller.ts +66 -11
- package/src/modes/controllers/runtime-mcp-command-controller.ts +7 -6
- package/src/modes/controllers/selector-controller.ts +76 -7
- package/src/modes/interactive-mode.ts +123 -4
- package/src/modes/prompt-suggestion-controller.ts +1 -1
- package/src/modes/runtime-init.ts +5 -1
- package/src/modes/theme/theme.ts +15 -0
- package/src/modes/utils/host-status.ts +21 -10
- package/src/prompts/agent-fragments/browser-aside.md +2 -2
- package/src/prompts/system/irc-incoming.md +3 -1
- package/src/prompts/tools/debug.md +1 -1
- package/src/prompts/tools/irc.md +1 -1
- package/src/prompts/tools/read.md +1 -1
- package/src/prompts/tools/report-issue.md +13 -0
- package/src/prompts/tools/task.md +1 -1
- package/src/runtime-credential-selector.ts +2 -2
- package/src/runtime-mcp/content-limits.ts +7 -0
- package/src/runtime-mcp/manager-pool.test.ts +5 -4
- package/src/runtime-mcp/manager.ts +40 -3
- package/src/runtime-mcp/oauth-flow.ts +32 -6
- package/src/runtime-mcp/plugin-mcp-name-filter.ts +62 -0
- package/src/runtime-mcp/project-host-values.ts +12 -8
- package/src/runtime-mcp/render.ts +16 -1
- package/src/runtime-mcp/tool-bridge.ts +194 -21
- package/src/runtime-mcp/transports/stdio.ts +2 -1
- package/src/sdk/broker/broker-exit.ts +10 -5
- package/src/sdk/broker/broker.ts +484 -85
- package/src/sdk/broker/daemon-entry.ts +338 -116
- package/src/sdk/broker/doctor-restart.ts +2 -1
- package/src/sdk/broker/ensure.ts +1214 -268
- package/src/sdk/broker/hop.ts +286 -0
- package/src/sdk/broker/lifecycle-ledger.ts +38 -11
- package/src/sdk/broker/lifecycle.ts +102 -6
- package/src/sdk/broker/lock-host-identity.ts +29 -0
- package/src/sdk/broker/session-index.ts +91 -16
- package/src/sdk/broker/transport.ts +17 -1
- package/src/sdk/bus/chat-command-policy.ts +16 -3
- package/src/sdk/bus/chat-daemon-control.ts +2 -2
- package/src/sdk/bus/index.ts +460 -35
- package/src/sdk/bus/reconciliation-store.ts +201 -8
- package/src/sdk/bus/session-control.ts +2 -0
- package/src/sdk/bus/slack-daemon.ts +105 -9
- package/src/sdk/bus/telegram-daemon-contract.ts +9 -1
- package/src/sdk/bus/telegram-daemon.ts +10 -2
- package/src/sdk/bus/threaded-render.ts +20 -1
- package/src/sdk/cli/session-cli.ts +23 -6
- package/src/sdk/host/control/dispatch.ts +13 -1
- package/src/sdk/host/control/operations.ts +10 -1
- package/src/sdk/host/events.ts +67 -4
- package/src/sdk/host/host.ts +8 -2
- package/src/sdk/host/prompt-image-upload.ts +438 -0
- package/src/sdk/host/sdk-surface-parity.test.ts +15 -0
- package/src/sdk/host/session-runtime.test.ts +1737 -129
- package/src/sdk/host/session-runtime.ts +684 -83
- package/src/sdk/host/surface-policy.ts +4 -0
- package/src/sdk/host/websocket-transport.lifecycle.test.ts +90 -2
- package/src/sdk/host/websocket-transport.ts +19 -7
- package/src/sdk/lifecycle/service.ts +15 -1
- package/src/sdk/prompt-deadline-flush.ts +78 -5
- package/src/sdk/prompt-deadline-manager.ts +68 -14
- package/src/sdk/prompt-status.ts +6 -4
- package/src/sdk/protocol/adapter-validation.ts +81 -12
- package/src/sdk/protocol/operation-inventory.generated.json +261 -2
- package/src/sdk/protocol/operation-registry.ts +33 -3
- package/src/sdk/reconciliation-extensions.ts +1 -0
- package/src/sdk/session-list.ts +1 -0
- package/src/sdk/session.ts +237 -97
- package/src/session/agent-session.ts +2726 -479
- package/src/session/history-storage.ts +182 -12
- package/src/session/internal/managed-session-scope.ts +173 -57
- package/src/session/internal/managed-session-storage.ts +85 -12
- package/src/session/internal/native-publish-outcome.ts +5 -1
- package/src/session/messages.ts +47 -6
- package/src/session/session-manager.ts +326 -19
- package/src/session/startup-auth-config.ts +116 -8
- package/src/session/streaming-output.ts +262 -4
- package/src/session/terminal-abort.ts +14 -2
- package/src/session-import/redact.ts +53 -2
- package/src/session-import/service.ts +1 -0
- package/src/skill-state/workflow-mutation-guard.ts +2145 -206
- package/src/slash-commands/builtin-registry.ts +21 -6
- package/src/slash-commands/helpers/credential-switch.ts +10 -5
- package/src/slash-commands/helpers/mcp.ts +11 -3
- package/src/slash-commands/helpers/provider-auth-status.ts +456 -0
- package/src/ssh/ssh-executor.ts +9 -2
- package/src/stt/recorder.ts +26 -9
- package/src/system-prompt.ts +61 -16
- package/src/task/executor.ts +115 -20
- package/src/task/index.ts +389 -175
- package/src/task/role-body.ts +84 -0
- package/src/task/token-log-mode.test.ts +162 -0
- package/src/task/token-log.ts +91 -2
- package/src/tools/archive-reader.ts +650 -37
- package/src/tools/ask.ts +1 -0
- package/src/tools/ast-edit-write-path.ts +64 -0
- package/src/tools/ast-edit.ts +18 -1
- package/src/tools/ast-grep.ts +7 -2
- package/src/tools/atomic-file-write.ts +46 -15
- package/src/tools/auto-generated-guard.ts +15 -3
- package/src/tools/awaiting-input.ts +114 -0
- package/src/tools/bash-allowed-prefixes.ts +214 -6
- package/src/tools/bash-interactive.ts +14 -2
- package/src/tools/bash-skill-urls.ts +58 -5
- package/src/tools/bash.ts +113 -26
- package/src/tools/browser/tab-worker.ts +39 -13
- package/src/tools/browser.ts +17 -7
- package/src/tools/cron.ts +10 -1
- package/src/tools/debug.ts +16 -8
- package/src/tools/descriptors.test.ts +44 -0
- package/src/tools/descriptors.ts +50 -6
- package/src/tools/eval.ts +15 -0
- package/src/tools/fetch.ts +93 -4
- package/src/tools/gh.ts +27 -12
- package/src/tools/image-gen.ts +20 -6
- package/src/tools/index.ts +2 -0
- package/src/tools/irc.ts +5 -3
- package/src/tools/monitor.ts +9 -1
- package/src/tools/output-meta.ts +74 -5
- package/src/tools/python.ts +1 -1
- package/src/tools/read.ts +26 -37
- package/src/tools/recipe/runner.ts +23 -12
- package/src/tools/render-utils.ts +2 -2
- package/src/tools/report-issue.ts +184 -0
- package/src/tools/search.ts +3 -2
- package/src/tools/ssh.ts +274 -8
- package/src/tools/subagent-render.ts +114 -12
- package/src/tools/subagent.ts +118 -5
- package/src/tools/tmux-self-injection-guard.ts +3301 -122
- package/src/tools/todo-write.ts +41 -3
- package/src/tools/tool-catalog.generated.ts +32 -4
- package/src/tools/ultragoal-ask-guard.ts +13 -1
- package/src/tools/write.ts +42 -3
- package/src/tui/tree-list.ts +15 -1
- package/src/utils/commit-message-generator.ts +12 -4
- package/src/utils/external-editor.ts +21 -7
- package/src/utils/file-mentions.ts +75 -7
- package/src/utils/git.ts +38 -7
- package/src/utils/image-limits.ts +8 -0
- package/src/utils/image-loading.ts +1 -1
- package/src/utils/open.ts +73 -4
- package/src/utils/pasted-image-loading.ts +9 -8
- package/src/utils/prompt-suggestion.ts +5 -4
- package/src/utils/shell-snapshot.ts +9 -1
- package/src/utils/title-generator.ts +72 -22
- package/src/web/scrapers/index.ts +3 -1
- package/src/web/scrapers/types.ts +8 -0
- package/src/web/scrapers/youtube.ts +3 -2
- package/src/web/search/index.ts +2 -1
- package/src/web/search/providers/codex.ts +29 -32
- package/src/web/search/providers/insane.ts +91 -28
- package/src/web/search/providers/searxng.ts +62 -10
|
@@ -1,5 +1,26 @@
|
|
|
1
|
+
import { gunzipSync } from "node:zlib";
|
|
1
2
|
import { ToolError } from "./tool-errors";
|
|
2
3
|
|
|
4
|
+
const MAX_ARCHIVE_BYTES = 256 * 1024 * 1024;
|
|
5
|
+
const MAX_ARCHIVE_ENTRY_BYTES = 16 * 1024 * 1024;
|
|
6
|
+
const MAX_ARCHIVE_TOTAL_BYTES = 64 * 1024 * 1024;
|
|
7
|
+
const MAX_ARCHIVE_ENTRY_COUNT = 8192;
|
|
8
|
+
const MAX_ARCHIVE_PATH_DEPTH = 48;
|
|
9
|
+
// Round each 512-byte header plus up to 511 bytes of data padding to 1 KiB per entry.
|
|
10
|
+
const MAX_TAR_CONTAINER_BYTES = MAX_ARCHIVE_TOTAL_BYTES + MAX_ARCHIVE_ENTRY_COUNT * 1024 + 1024;
|
|
11
|
+
const MAX_GZIP_LAYERS = 24;
|
|
12
|
+
const ZIP_LOCAL_FILE_HEADER_SIGNATURE = 0x04034b50;
|
|
13
|
+
const ZIP_CENTRAL_DIRECTORY_SIGNATURE = 0x02014b50;
|
|
14
|
+
const ZIP_END_OF_CENTRAL_DIRECTORY_SIGNATURE = 0x06054b50;
|
|
15
|
+
const ZIP64_END_OF_CENTRAL_DIRECTORY_SIGNATURE = 0x06064b50;
|
|
16
|
+
const ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIGNATURE = 0x07064b50;
|
|
17
|
+
const ZIP_DATA_DESCRIPTOR_SIGNATURE = 0x08074b50;
|
|
18
|
+
const ZIP_CENTRAL_DIRECTORY_DIGITAL_SIGNATURE = 0x05054b50;
|
|
19
|
+
const ZIP_UINT16_MAX = 0xffff;
|
|
20
|
+
const ZIP_UINT32_MAX = 0xffff_ffff;
|
|
21
|
+
// fflate inflates a pushed chunk before invoking ondata, so keep each input chunk small.
|
|
22
|
+
const ZIP_INFLATE_CHUNK_BYTES = 512;
|
|
23
|
+
|
|
3
24
|
let fflateModulePromise: Promise<typeof import("fflate")> | undefined;
|
|
4
25
|
async function loadFflate(): Promise<typeof import("fflate")> {
|
|
5
26
|
if (!fflateModulePromise) fflateModulePromise = import("fflate");
|
|
@@ -75,6 +96,43 @@ function isArchiveDirectoryName(rawPath: string): boolean {
|
|
|
75
96
|
return rawPath.endsWith("/") || rawPath.endsWith("\\");
|
|
76
97
|
}
|
|
77
98
|
|
|
99
|
+
function archiveEntryDepth(rawPath: string): number {
|
|
100
|
+
const normalized = normalizeArchiveEntryPath(rawPath);
|
|
101
|
+
if (!normalized) return 0;
|
|
102
|
+
return normalized.split("/").length;
|
|
103
|
+
}
|
|
104
|
+
function concatBytes(chunks: Uint8Array[], total: number): Uint8Array {
|
|
105
|
+
const out = new Uint8Array(total);
|
|
106
|
+
let offset = 0;
|
|
107
|
+
for (const chunk of chunks) {
|
|
108
|
+
out.set(chunk, offset);
|
|
109
|
+
offset += chunk.byteLength;
|
|
110
|
+
}
|
|
111
|
+
return out;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function archiveLimitError(kind: "entry" | "total" | "count" | "depth", detail = ""): ToolError {
|
|
115
|
+
const preview = detail.length > 120 ? `${detail.slice(0, 119)}…` : detail;
|
|
116
|
+
switch (kind) {
|
|
117
|
+
case "count":
|
|
118
|
+
return new ToolError(
|
|
119
|
+
`Archive has too many entries (limit ${MAX_ARCHIVE_ENTRY_COUNT}). Extract a subset with a dedicated tool.`,
|
|
120
|
+
);
|
|
121
|
+
case "total":
|
|
122
|
+
return new ToolError(
|
|
123
|
+
`Archive expands past the total limit (${MAX_ARCHIVE_TOTAL_BYTES} bytes). Extract a subset with a dedicated tool.`,
|
|
124
|
+
);
|
|
125
|
+
case "depth":
|
|
126
|
+
return new ToolError(
|
|
127
|
+
`Archive entry '${preview}' is nested too deeply (limit ${MAX_ARCHIVE_PATH_DEPTH}). Extract a subset with a dedicated tool.`,
|
|
128
|
+
);
|
|
129
|
+
case "entry":
|
|
130
|
+
return new ToolError(
|
|
131
|
+
`Archive entry '${preview}' expands past the per-entry limit (${MAX_ARCHIVE_ENTRY_BYTES} bytes). Extract a subset with a dedicated tool.`,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
78
136
|
function upsertArchiveEntry(map: Map<string, ArchiveIndexEntry>, entry: ArchiveIndexEntry): void {
|
|
79
137
|
const existing = map.get(entry.path);
|
|
80
138
|
if (!existing) {
|
|
@@ -123,70 +181,626 @@ function getArchiveFormatFromPath(filePath: string): ArchiveFormat | undefined {
|
|
|
123
181
|
return undefined;
|
|
124
182
|
}
|
|
125
183
|
|
|
126
|
-
|
|
127
|
-
let archive: Bun.Archive;
|
|
184
|
+
function gunzipBounded(bytes: Uint8Array, maxOutputLength: number): Uint8Array {
|
|
128
185
|
try {
|
|
129
|
-
|
|
186
|
+
return gunzipSync(bytes, { maxOutputLength });
|
|
130
187
|
} catch (error) {
|
|
188
|
+
const code = typeof error === "object" && error !== null && "code" in error ? String(error.code) : "";
|
|
189
|
+
if (code === "ERR_BUFFER_TOO_LARGE") throw archiveLimitError("total");
|
|
131
190
|
throw new ToolError(error instanceof Error ? error.message : String(error));
|
|
132
191
|
}
|
|
192
|
+
}
|
|
133
193
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
194
|
+
function isGzipBytes(bytes: Uint8Array): boolean {
|
|
195
|
+
return bytes.byteLength >= 3 && bytes[0] === 0x1f && bytes[1] === 0x8b;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function inflateToTar(bytes: Uint8Array, labeledGzip: boolean): Uint8Array {
|
|
199
|
+
let payload = bytes;
|
|
200
|
+
let layers = 0;
|
|
201
|
+
let totalInflatedBytes = 0;
|
|
202
|
+
let sawGzip = labeledGzip || isGzipBytes(payload);
|
|
203
|
+
while (sawGzip) {
|
|
204
|
+
layers += 1;
|
|
205
|
+
if (layers > MAX_GZIP_LAYERS) throw archiveLimitError("total");
|
|
206
|
+
const remaining = MAX_TAR_CONTAINER_BYTES - totalInflatedBytes;
|
|
207
|
+
if (remaining <= 0) throw archiveLimitError("total");
|
|
208
|
+
payload = gunzipBounded(payload, remaining);
|
|
209
|
+
totalInflatedBytes += payload.byteLength;
|
|
210
|
+
sawGzip = isGzipBytes(payload);
|
|
211
|
+
}
|
|
212
|
+
return payload;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function tarText(header: Uint8Array, start: number, length: number): string {
|
|
216
|
+
let end = start;
|
|
217
|
+
const stop = start + length;
|
|
218
|
+
while (end < stop && header[end] !== 0) end += 1;
|
|
219
|
+
return new TextDecoder().decode(header.subarray(start, end));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function tarOctal(header: Uint8Array, start: number, length: number): number {
|
|
223
|
+
const text = tarText(header, start, length).trim();
|
|
224
|
+
if (!text) return 0;
|
|
225
|
+
if (!/^[0-7]+$/.test(text)) return Number.NaN;
|
|
226
|
+
return Number.parseInt(text, 8);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function tarEntryName(header: Uint8Array): string {
|
|
230
|
+
const name = tarText(header, 0, 100);
|
|
231
|
+
const prefix = tarText(header, 257, 5) === "ustar" ? tarText(header, 345, 155) : "";
|
|
232
|
+
return prefix ? `${prefix}/${name}` : name;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
type PaxOverrides = { path?: string | null; size?: number | null };
|
|
236
|
+
|
|
237
|
+
function paxOverrides(data: Uint8Array, global = false): PaxOverrides {
|
|
238
|
+
const out: PaxOverrides = {};
|
|
239
|
+
const raw = Buffer.from(data).toString("latin1");
|
|
240
|
+
for (let index = 0; index < raw.length; ) {
|
|
241
|
+
const space = raw.indexOf(" ", index);
|
|
242
|
+
const token = space < 0 ? "" : raw.slice(index, space);
|
|
243
|
+
const length = /^\d+$/.test(token) ? Number(token) : 0;
|
|
244
|
+
const end = index + length;
|
|
245
|
+
const eq = space < 0 ? -1 : raw.indexOf("=", space);
|
|
246
|
+
if (length < 1 || !Number.isSafeInteger(length) || end > raw.length)
|
|
247
|
+
throw new ToolError("Unsupported tar pax header");
|
|
248
|
+
if (space >= end || eq < 0 || eq >= end || raw[end - 1] !== "\n")
|
|
249
|
+
throw new ToolError("Unsupported tar pax header");
|
|
250
|
+
const key = raw.slice(space + 1, eq);
|
|
251
|
+
const value = new TextDecoder().decode(data.subarray(eq + 1, end - 1));
|
|
252
|
+
if (key === "path") out.path = global && value === "" ? null : value;
|
|
253
|
+
else if (key === "size" && global && value === "") out.size = null;
|
|
254
|
+
else if (key === "size" && /^\d+$/.test(value) && Number.isSafeInteger(Number(value))) out.size = Number(value);
|
|
255
|
+
else if (key === "size") throw new ToolError("Unsupported tar size encoding");
|
|
256
|
+
index = end;
|
|
139
257
|
}
|
|
258
|
+
return out;
|
|
259
|
+
}
|
|
140
260
|
|
|
261
|
+
async function readTarEntries(bytes: Uint8Array, labeledGzip: boolean): Promise<ArchiveIndexEntry[]> {
|
|
262
|
+
const payload = inflateToTar(bytes, labeledGzip);
|
|
263
|
+
if (payload.byteLength > MAX_TAR_CONTAINER_BYTES) throw archiveLimitError("total");
|
|
141
264
|
const entries: ArchiveIndexEntry[] = [];
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
265
|
+
let entryCount = 0;
|
|
266
|
+
let memberBytes = 0;
|
|
267
|
+
let offset = 0;
|
|
268
|
+
let longName: string | undefined;
|
|
269
|
+
let pendingPax: PaxOverrides | undefined;
|
|
270
|
+
const globalPax: { path?: string; size?: number } = {};
|
|
271
|
+
while (offset + 512 <= payload.byteLength) {
|
|
272
|
+
const header = payload.subarray(offset, offset + 512);
|
|
273
|
+
if (header.every(byte => byte === 0)) break;
|
|
274
|
+
const headerSize = tarOctal(header, 124, 12);
|
|
275
|
+
if (!Number.isSafeInteger(headerSize) || headerSize < 0) throw new ToolError("Unsupported tar size encoding");
|
|
276
|
+
const typeFlag = header[156] ?? 0;
|
|
277
|
+
const headerName = tarEntryName(header);
|
|
278
|
+
let size = headerSize;
|
|
279
|
+
let rawName = typeFlag === 76 ? headerName : (longName ?? headerName);
|
|
280
|
+
if (typeFlag !== 120 && typeFlag !== 103 && typeFlag !== 76) {
|
|
281
|
+
rawName = pendingPax?.path ?? globalPax.path ?? rawName;
|
|
282
|
+
size = pendingPax?.size ?? globalPax.size ?? size;
|
|
283
|
+
pendingPax = undefined;
|
|
284
|
+
}
|
|
285
|
+
entryCount += 1;
|
|
286
|
+
if (entryCount > MAX_ARCHIVE_ENTRY_COUNT) throw archiveLimitError("count");
|
|
287
|
+
if (typeFlag < 100 && archiveEntryDepth(rawName) > MAX_ARCHIVE_PATH_DEPTH)
|
|
288
|
+
throw archiveLimitError("depth", rawName);
|
|
289
|
+
if (size > MAX_ARCHIVE_TOTAL_BYTES) throw archiveLimitError("total");
|
|
290
|
+
if (size > MAX_ARCHIVE_ENTRY_BYTES) throw archiveLimitError("entry", rawName);
|
|
291
|
+
const isRegular = typeFlag === 0 || typeFlag === 48;
|
|
292
|
+
if (isRegular) {
|
|
293
|
+
memberBytes += size;
|
|
294
|
+
if (memberBytes > MAX_ARCHIVE_TOTAL_BYTES) throw archiveLimitError("total");
|
|
295
|
+
}
|
|
296
|
+
const dataEnd = offset + 512 + size;
|
|
297
|
+
if (dataEnd > payload.byteLength) throw new ToolError("Truncated tar entry");
|
|
298
|
+
const data = payload.subarray(offset + 512, dataEnd);
|
|
299
|
+
offset += 512 + Math.ceil(size / 512) * 512;
|
|
300
|
+
if (typeFlag === 120 || typeFlag === 103) {
|
|
301
|
+
if (typeFlag === 103) {
|
|
302
|
+
const overrides = paxOverrides(data, true);
|
|
303
|
+
if (overrides.path === null) delete globalPax.path;
|
|
304
|
+
else if (overrides.path !== undefined) globalPax.path = overrides.path;
|
|
305
|
+
if (overrides.size === null) delete globalPax.size;
|
|
306
|
+
else if (overrides.size !== undefined) globalPax.size = overrides.size;
|
|
307
|
+
} else pendingPax = paxOverrides(data);
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
if (typeFlag === 76) {
|
|
311
|
+
longName = new TextDecoder().decode(data).replace(/\0+$/, "");
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
longName = undefined;
|
|
315
|
+
const normalizedPath = normalizeArchiveEntryPath(rawName);
|
|
316
|
+
if (!normalizedPath || !isRegular) continue;
|
|
317
|
+
const mtime = tarOctal(header, 136, 12);
|
|
318
|
+
const mtimeMs = Number.isFinite(mtime) && mtime > 0 ? mtime * 1000 : undefined;
|
|
146
319
|
entries.push({
|
|
147
320
|
path: normalizedPath,
|
|
148
321
|
isDirectory: false,
|
|
149
|
-
size
|
|
322
|
+
size,
|
|
150
323
|
mtimeMs,
|
|
151
|
-
storage: {
|
|
324
|
+
storage: {
|
|
325
|
+
type: "tar",
|
|
326
|
+
file: new File([data], normalizedPath, { lastModified: mtimeMs ?? 0 }),
|
|
327
|
+
},
|
|
152
328
|
});
|
|
153
329
|
}
|
|
154
|
-
|
|
155
330
|
return entries;
|
|
156
331
|
}
|
|
157
332
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
333
|
+
interface ZipDirectoryEntry {
|
|
334
|
+
name: string;
|
|
335
|
+
nameBytes: Uint8Array;
|
|
336
|
+
flags: number;
|
|
337
|
+
compression: number;
|
|
338
|
+
crc32: number;
|
|
339
|
+
compressedSize: number;
|
|
340
|
+
uncompressedSize: number;
|
|
341
|
+
localOffset: number;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
interface Zip64ExtraNeeds {
|
|
345
|
+
uncompressedSize: boolean;
|
|
346
|
+
compressedSize: boolean;
|
|
347
|
+
localOffset: boolean;
|
|
348
|
+
diskNumber: boolean;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
interface Zip64ExtraValues {
|
|
352
|
+
uncompressedSize?: number;
|
|
353
|
+
compressedSize?: number;
|
|
354
|
+
localOffset?: number;
|
|
355
|
+
diskNumber?: number;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function invalidZipArchive(): ToolError {
|
|
359
|
+
return new ToolError("Invalid ZIP archive");
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function requireZipRange(view: DataView, offset: number, length: number, end = view.byteLength): void {
|
|
363
|
+
if (
|
|
364
|
+
!Number.isSafeInteger(offset) ||
|
|
365
|
+
!Number.isSafeInteger(length) ||
|
|
366
|
+
offset < 0 ||
|
|
367
|
+
length < 0 ||
|
|
368
|
+
offset + length > end
|
|
369
|
+
) {
|
|
370
|
+
throw invalidZipArchive();
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function zipUint64(view: DataView, offset: number, end: number): number {
|
|
375
|
+
requireZipRange(view, offset, 8, end);
|
|
376
|
+
const value = view.getBigUint64(offset, true);
|
|
377
|
+
if (value > BigInt(Number.MAX_SAFE_INTEGER)) throw invalidZipArchive();
|
|
378
|
+
return Number(value);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function parseZip64Extra(view: DataView, start: number, length: number, needs: Zip64ExtraNeeds): Zip64ExtraValues {
|
|
382
|
+
const end = start + length;
|
|
383
|
+
requireZipRange(view, start, length);
|
|
384
|
+
let zip64Start: number | undefined;
|
|
385
|
+
let zip64End = 0;
|
|
386
|
+
for (let offset = start; offset < end; ) {
|
|
387
|
+
requireZipRange(view, offset, 4, end);
|
|
388
|
+
const id = view.getUint16(offset, true);
|
|
389
|
+
const fieldLength = view.getUint16(offset + 2, true);
|
|
390
|
+
const fieldStart = offset + 4;
|
|
391
|
+
requireZipRange(view, fieldStart, fieldLength, end);
|
|
392
|
+
if (id === 0x0001) {
|
|
393
|
+
if (zip64Start !== undefined) throw invalidZipArchive();
|
|
394
|
+
zip64Start = fieldStart;
|
|
395
|
+
zip64End = fieldStart + fieldLength;
|
|
165
396
|
}
|
|
166
|
-
|
|
397
|
+
offset = fieldStart + fieldLength;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const needsZip64 = Object.values(needs).some(Boolean);
|
|
401
|
+
if (zip64Start === undefined) {
|
|
402
|
+
if (needsZip64) throw invalidZipArchive();
|
|
403
|
+
return {};
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const values: Zip64ExtraValues = {};
|
|
407
|
+
let offset = zip64Start;
|
|
408
|
+
if (needs.uncompressedSize) {
|
|
409
|
+
values.uncompressedSize = zipUint64(view, offset, zip64End);
|
|
410
|
+
offset += 8;
|
|
411
|
+
}
|
|
412
|
+
if (needs.compressedSize) {
|
|
413
|
+
values.compressedSize = zipUint64(view, offset, zip64End);
|
|
414
|
+
offset += 8;
|
|
415
|
+
}
|
|
416
|
+
if (needs.localOffset) {
|
|
417
|
+
values.localOffset = zipUint64(view, offset, zip64End);
|
|
418
|
+
offset += 8;
|
|
419
|
+
}
|
|
420
|
+
if (needs.diskNumber) {
|
|
421
|
+
requireZipRange(view, offset, 4, zip64End);
|
|
422
|
+
values.diskNumber = view.getUint32(offset, true);
|
|
423
|
+
}
|
|
424
|
+
return values;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function decodeZipName(bytes: Uint8Array, flags: number): string {
|
|
428
|
+
if ((flags & 0x0800) !== 0) return new TextDecoder().decode(bytes);
|
|
429
|
+
let name = "";
|
|
430
|
+
for (let offset = 0; offset < bytes.byteLength; offset += 0x4000) {
|
|
431
|
+
name += String.fromCharCode(...bytes.subarray(offset, offset + 0x4000));
|
|
432
|
+
}
|
|
433
|
+
return name;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function zipDescriptorEnd(
|
|
437
|
+
view: DataView,
|
|
438
|
+
start: number,
|
|
439
|
+
end: number,
|
|
440
|
+
entry: ZipDirectoryEntry,
|
|
441
|
+
zip64Sizes: boolean,
|
|
442
|
+
): number {
|
|
443
|
+
const matches = (hasSignature: boolean): number | undefined => {
|
|
444
|
+
let offset = start;
|
|
445
|
+
if (hasSignature) {
|
|
446
|
+
if (offset + 4 > end || view.getUint32(offset, true) !== ZIP_DATA_DESCRIPTOR_SIGNATURE) return undefined;
|
|
447
|
+
offset += 4;
|
|
448
|
+
}
|
|
449
|
+
const sizeLength = zip64Sizes ? 8 : 4;
|
|
450
|
+
if (offset + 4 + sizeLength * 2 > end) return undefined;
|
|
451
|
+
if (view.getUint32(offset, true) !== entry.crc32) return undefined;
|
|
452
|
+
const compressedSize = zip64Sizes
|
|
453
|
+
? view.getBigUint64(offset + 4, true)
|
|
454
|
+
: BigInt(view.getUint32(offset + 4, true));
|
|
455
|
+
const uncompressedSize = zip64Sizes
|
|
456
|
+
? view.getBigUint64(offset + 4 + sizeLength, true)
|
|
457
|
+
: BigInt(view.getUint32(offset + 4 + sizeLength, true));
|
|
458
|
+
if (compressedSize !== BigInt(entry.compressedSize) || uncompressedSize !== BigInt(entry.uncompressedSize)) {
|
|
459
|
+
return undefined;
|
|
460
|
+
}
|
|
461
|
+
return offset + 4 + sizeLength * 2;
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
const withSignature = matches(true);
|
|
465
|
+
const withoutSignature = matches(false);
|
|
466
|
+
const descriptorEnd = withSignature ?? withoutSignature;
|
|
467
|
+
if (descriptorEnd === undefined) throw invalidZipArchive();
|
|
468
|
+
return descriptorEnd;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function validateZipLocalEntry(
|
|
472
|
+
view: DataView,
|
|
473
|
+
bytes: Uint8Array,
|
|
474
|
+
entry: ZipDirectoryEntry,
|
|
475
|
+
offsetCorrection: number,
|
|
476
|
+
centralDirectoryStart: number,
|
|
477
|
+
): { entry: ZipDirectoryEntry; endOffset: number } {
|
|
478
|
+
const localOffset = entry.localOffset + offsetCorrection;
|
|
479
|
+
requireZipRange(view, localOffset, 30, centralDirectoryStart);
|
|
480
|
+
if (view.getUint32(localOffset, true) !== ZIP_LOCAL_FILE_HEADER_SIGNATURE) throw invalidZipArchive();
|
|
481
|
+
const flags = view.getUint16(localOffset + 6, true);
|
|
482
|
+
const compression = view.getUint16(localOffset + 8, true);
|
|
483
|
+
const crc32 = view.getUint32(localOffset + 14, true);
|
|
484
|
+
const compressedSize32 = view.getUint32(localOffset + 18, true);
|
|
485
|
+
const uncompressedSize32 = view.getUint32(localOffset + 22, true);
|
|
486
|
+
const nameLength = view.getUint16(localOffset + 26, true);
|
|
487
|
+
const extraLength = view.getUint16(localOffset + 28, true);
|
|
488
|
+
const nameStart = localOffset + 30;
|
|
489
|
+
const extraStart = nameStart + nameLength;
|
|
490
|
+
requireZipRange(view, nameStart, nameLength + extraLength, centralDirectoryStart);
|
|
491
|
+
const localName = bytes.subarray(nameStart, extraStart);
|
|
492
|
+
if (
|
|
493
|
+
flags !== entry.flags ||
|
|
494
|
+
compression !== entry.compression ||
|
|
495
|
+
localName.byteLength !== entry.nameBytes.byteLength ||
|
|
496
|
+
localName.some((byte, index) => byte !== entry.nameBytes[index])
|
|
497
|
+
) {
|
|
498
|
+
throw invalidZipArchive();
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
const zip64Sizes = compressedSize32 === ZIP_UINT32_MAX || uncompressedSize32 === ZIP_UINT32_MAX;
|
|
502
|
+
const zip64 = parseZip64Extra(view, extraStart, extraLength, {
|
|
503
|
+
uncompressedSize: uncompressedSize32 === ZIP_UINT32_MAX,
|
|
504
|
+
compressedSize: compressedSize32 === ZIP_UINT32_MAX,
|
|
505
|
+
localOffset: false,
|
|
506
|
+
diskNumber: false,
|
|
167
507
|
});
|
|
508
|
+
const compressedSize = compressedSize32 === ZIP_UINT32_MAX ? zip64.compressedSize : compressedSize32;
|
|
509
|
+
const uncompressedSize = uncompressedSize32 === ZIP_UINT32_MAX ? zip64.uncompressedSize : uncompressedSize32;
|
|
510
|
+
if (compressedSize === undefined || uncompressedSize === undefined) throw invalidZipArchive();
|
|
511
|
+
|
|
512
|
+
const hasDescriptor = (flags & 0x0008) !== 0;
|
|
513
|
+
if (hasDescriptor) {
|
|
514
|
+
if (crc32 !== 0 && crc32 !== entry.crc32) throw invalidZipArchive();
|
|
515
|
+
if (compressedSize !== 0 && compressedSize !== entry.compressedSize) throw invalidZipArchive();
|
|
516
|
+
if (uncompressedSize !== 0 && uncompressedSize !== entry.uncompressedSize) throw invalidZipArchive();
|
|
517
|
+
} else if (
|
|
518
|
+
crc32 !== entry.crc32 ||
|
|
519
|
+
compressedSize !== entry.compressedSize ||
|
|
520
|
+
uncompressedSize !== entry.uncompressedSize
|
|
521
|
+
) {
|
|
522
|
+
throw invalidZipArchive();
|
|
523
|
+
}
|
|
168
524
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
525
|
+
const dataStart = extraStart + extraLength;
|
|
526
|
+
const dataEnd = dataStart + entry.compressedSize;
|
|
527
|
+
if (dataEnd > centralDirectoryStart) throw invalidZipArchive();
|
|
528
|
+
const endOffset = hasDescriptor
|
|
529
|
+
? zipDescriptorEnd(view, dataEnd, centralDirectoryStart, entry, zip64Sizes)
|
|
530
|
+
: dataEnd;
|
|
531
|
+
return { entry: { ...entry, localOffset }, endOffset };
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function readZipDirectory(bytes: Uint8Array): ZipDirectoryEntry[] {
|
|
535
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
536
|
+
const searchStart = Math.max(0, bytes.byteLength - 22 - ZIP_UINT16_MAX);
|
|
537
|
+
let lastError: unknown;
|
|
538
|
+
for (let offset = searchStart; offset <= bytes.byteLength - 22; offset++) {
|
|
539
|
+
if (view.getUint32(offset, true) !== ZIP_END_OF_CENTRAL_DIRECTORY_SIGNATURE) continue;
|
|
540
|
+
const commentLength = view.getUint16(offset + 20, true);
|
|
541
|
+
if (offset + 22 + commentLength !== bytes.byteLength) continue;
|
|
542
|
+
try {
|
|
543
|
+
return parseZipDirectoryAt(bytes, view, offset);
|
|
544
|
+
} catch (error) {
|
|
545
|
+
lastError = error;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
if (lastError instanceof Error) throw lastError;
|
|
549
|
+
throw invalidZipArchive();
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
function parseZipDirectoryAt(bytes: Uint8Array, view: DataView, endOffset: number): ZipDirectoryEntry[] {
|
|
553
|
+
const diskNumber = view.getUint16(endOffset + 4, true);
|
|
554
|
+
const centralDiskNumber = view.getUint16(endOffset + 6, true);
|
|
555
|
+
const entriesOnDisk16 = view.getUint16(endOffset + 8, true);
|
|
556
|
+
const entryCount16 = view.getUint16(endOffset + 10, true);
|
|
557
|
+
const centralSize32 = view.getUint32(endOffset + 12, true);
|
|
558
|
+
const centralOffset32 = view.getUint32(endOffset + 16, true);
|
|
559
|
+
if (
|
|
560
|
+
diskNumber !== 0 ||
|
|
561
|
+
centralDiskNumber !== 0 ||
|
|
562
|
+
(entriesOnDisk16 !== ZIP_UINT16_MAX && entriesOnDisk16 !== entryCount16)
|
|
563
|
+
) {
|
|
564
|
+
throw invalidZipArchive();
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
const needsZip64 =
|
|
568
|
+
entriesOnDisk16 === ZIP_UINT16_MAX ||
|
|
569
|
+
entryCount16 === ZIP_UINT16_MAX ||
|
|
570
|
+
centralSize32 === ZIP_UINT32_MAX ||
|
|
571
|
+
centralOffset32 === ZIP_UINT32_MAX;
|
|
572
|
+
const locatorOffset = endOffset - 20;
|
|
573
|
+
const hasZip64Locator =
|
|
574
|
+
locatorOffset >= 0 && view.getUint32(locatorOffset, true) === ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIGNATURE;
|
|
575
|
+
if (needsZip64 && !hasZip64Locator) throw invalidZipArchive();
|
|
576
|
+
|
|
577
|
+
let entryCount: number;
|
|
578
|
+
let centralSize: number;
|
|
579
|
+
let centralOffset: number;
|
|
580
|
+
let centralDirectoryEnd: number;
|
|
581
|
+
if (hasZip64Locator) {
|
|
582
|
+
const locatorDisk = view.getUint32(locatorOffset + 4, true);
|
|
583
|
+
const zip64EndOffset = zipUint64(view, locatorOffset + 8, locatorOffset + 20);
|
|
584
|
+
const totalDisks = view.getUint32(locatorOffset + 16, true);
|
|
585
|
+
if (locatorDisk !== 0 || totalDisks !== 1) throw invalidZipArchive();
|
|
586
|
+
requireZipRange(view, zip64EndOffset, 56, locatorOffset);
|
|
587
|
+
if (view.getUint32(zip64EndOffset, true) !== ZIP64_END_OF_CENTRAL_DIRECTORY_SIGNATURE) throw invalidZipArchive();
|
|
588
|
+
const zip64RecordSize = zipUint64(view, zip64EndOffset + 4, locatorOffset);
|
|
589
|
+
if (zip64RecordSize < 44 || zip64EndOffset + 12 + zip64RecordSize !== locatorOffset) throw invalidZipArchive();
|
|
590
|
+
if (view.getUint32(zip64EndOffset + 16, true) !== 0 || view.getUint32(zip64EndOffset + 20, true) !== 0) {
|
|
591
|
+
throw invalidZipArchive();
|
|
592
|
+
}
|
|
593
|
+
const entriesOnDisk = zipUint64(view, zip64EndOffset + 24, locatorOffset);
|
|
594
|
+
entryCount = zipUint64(view, zip64EndOffset + 32, locatorOffset);
|
|
595
|
+
centralSize = zipUint64(view, zip64EndOffset + 40, locatorOffset);
|
|
596
|
+
centralOffset = zipUint64(view, zip64EndOffset + 48, locatorOffset);
|
|
597
|
+
if (entriesOnDisk !== entryCount) throw invalidZipArchive();
|
|
598
|
+
if (
|
|
599
|
+
(entriesOnDisk16 !== ZIP_UINT16_MAX && entriesOnDisk16 !== entriesOnDisk) ||
|
|
600
|
+
(entryCount16 !== ZIP_UINT16_MAX && entryCount16 !== entryCount) ||
|
|
601
|
+
(centralSize32 !== ZIP_UINT32_MAX && centralSize32 !== centralSize) ||
|
|
602
|
+
(centralOffset32 !== ZIP_UINT32_MAX && centralOffset32 !== centralOffset)
|
|
603
|
+
) {
|
|
604
|
+
throw invalidZipArchive();
|
|
605
|
+
}
|
|
606
|
+
centralDirectoryEnd = zip64EndOffset;
|
|
607
|
+
} else {
|
|
608
|
+
entryCount = entryCount16;
|
|
609
|
+
centralSize = centralSize32;
|
|
610
|
+
centralOffset = centralOffset32;
|
|
611
|
+
centralDirectoryEnd = endOffset;
|
|
612
|
+
}
|
|
613
|
+
if (entryCount > MAX_ARCHIVE_ENTRY_COUNT) throw archiveLimitError("count");
|
|
614
|
+
if (centralSize > centralDirectoryEnd) throw invalidZipArchive();
|
|
615
|
+
const centralDirectoryStart = centralDirectoryEnd - centralSize;
|
|
616
|
+
const offsetCorrection = centralDirectoryStart - centralOffset;
|
|
617
|
+
if (offsetCorrection < 0) throw invalidZipArchive();
|
|
618
|
+
|
|
619
|
+
const directoryEntries: ZipDirectoryEntry[] = [];
|
|
620
|
+
let totalUncompressed = 0;
|
|
621
|
+
let offset = centralDirectoryStart;
|
|
622
|
+
for (let index = 0; index < entryCount; index++) {
|
|
623
|
+
requireZipRange(view, offset, 46, centralDirectoryEnd);
|
|
624
|
+
if (view.getUint32(offset, true) !== ZIP_CENTRAL_DIRECTORY_SIGNATURE) throw invalidZipArchive();
|
|
625
|
+
const flags = view.getUint16(offset + 8, true);
|
|
626
|
+
const compression = view.getUint16(offset + 10, true);
|
|
627
|
+
const crc32 = view.getUint32(offset + 16, true);
|
|
628
|
+
const compressedSize32 = view.getUint32(offset + 20, true);
|
|
629
|
+
const uncompressedSize32 = view.getUint32(offset + 24, true);
|
|
630
|
+
const nameLength = view.getUint16(offset + 28, true);
|
|
631
|
+
const extraLength = view.getUint16(offset + 30, true);
|
|
632
|
+
const commentLength = view.getUint16(offset + 32, true);
|
|
633
|
+
const diskStart16 = view.getUint16(offset + 34, true);
|
|
634
|
+
const localOffset32 = view.getUint32(offset + 42, true);
|
|
635
|
+
const nameStart = offset + 46;
|
|
636
|
+
const extraStart = nameStart + nameLength;
|
|
637
|
+
const recordLength = 46 + nameLength + extraLength + commentLength;
|
|
638
|
+
requireZipRange(view, offset, recordLength, centralDirectoryEnd);
|
|
639
|
+
const zip64 = parseZip64Extra(view, extraStart, extraLength, {
|
|
640
|
+
uncompressedSize: uncompressedSize32 === ZIP_UINT32_MAX,
|
|
641
|
+
compressedSize: compressedSize32 === ZIP_UINT32_MAX,
|
|
642
|
+
localOffset: localOffset32 === ZIP_UINT32_MAX,
|
|
643
|
+
diskNumber: diskStart16 === ZIP_UINT16_MAX,
|
|
644
|
+
});
|
|
645
|
+
const uncompressedSize = uncompressedSize32 === ZIP_UINT32_MAX ? zip64.uncompressedSize : uncompressedSize32;
|
|
646
|
+
const compressedSize = compressedSize32 === ZIP_UINT32_MAX ? zip64.compressedSize : compressedSize32;
|
|
647
|
+
const localOffset = localOffset32 === ZIP_UINT32_MAX ? zip64.localOffset : localOffset32;
|
|
648
|
+
const diskStart = diskStart16 === ZIP_UINT16_MAX ? zip64.diskNumber : diskStart16;
|
|
649
|
+
if (
|
|
650
|
+
uncompressedSize === undefined ||
|
|
651
|
+
compressedSize === undefined ||
|
|
652
|
+
localOffset === undefined ||
|
|
653
|
+
diskStart !== 0
|
|
654
|
+
) {
|
|
655
|
+
throw invalidZipArchive();
|
|
656
|
+
}
|
|
657
|
+
const nameBytes = bytes.subarray(nameStart, extraStart);
|
|
658
|
+
const name = decodeZipName(nameBytes, flags);
|
|
659
|
+
if (uncompressedSize > MAX_ARCHIVE_ENTRY_BYTES) throw archiveLimitError("entry", name);
|
|
660
|
+
totalUncompressed += uncompressedSize;
|
|
661
|
+
if (totalUncompressed > MAX_ARCHIVE_TOTAL_BYTES) throw archiveLimitError("total");
|
|
662
|
+
directoryEntries.push({
|
|
663
|
+
name,
|
|
664
|
+
nameBytes,
|
|
665
|
+
flags,
|
|
666
|
+
compression,
|
|
667
|
+
crc32,
|
|
668
|
+
compressedSize,
|
|
669
|
+
uncompressedSize,
|
|
670
|
+
localOffset,
|
|
671
|
+
});
|
|
672
|
+
offset += recordLength;
|
|
673
|
+
}
|
|
674
|
+
if (offset < centralDirectoryEnd) {
|
|
675
|
+
requireZipRange(view, offset, 6, centralDirectoryEnd);
|
|
676
|
+
const signatureLength = view.getUint16(offset + 4, true);
|
|
677
|
+
if (
|
|
678
|
+
view.getUint32(offset, true) !== ZIP_CENTRAL_DIRECTORY_DIGITAL_SIGNATURE ||
|
|
679
|
+
offset + 6 + signatureLength !== centralDirectoryEnd
|
|
680
|
+
) {
|
|
681
|
+
throw invalidZipArchive();
|
|
682
|
+
}
|
|
683
|
+
offset = centralDirectoryEnd;
|
|
684
|
+
}
|
|
685
|
+
if (offset !== centralDirectoryEnd) throw invalidZipArchive();
|
|
686
|
+
|
|
687
|
+
const localEntries = directoryEntries
|
|
688
|
+
.map(entry => validateZipLocalEntry(view, bytes, entry, offsetCorrection, centralDirectoryStart))
|
|
689
|
+
.sort((left, right) => left.entry.localOffset - right.entry.localOffset);
|
|
690
|
+
for (let index = 1; index < localEntries.length; index++) {
|
|
691
|
+
const previous = localEntries[index - 1];
|
|
692
|
+
const current = localEntries[index];
|
|
693
|
+
if (previous && current && current.entry.localOffset < previous.endOffset) throw invalidZipArchive();
|
|
174
694
|
}
|
|
695
|
+
return localEntries.map(item => item.entry);
|
|
696
|
+
}
|
|
175
697
|
|
|
698
|
+
async function readZipEntries(bytes: Uint8Array): Promise<ArchiveIndexEntry[]> {
|
|
699
|
+
const directoryEntries = readZipDirectory(bytes);
|
|
700
|
+
const { Inflate } = await loadFflate();
|
|
701
|
+
// Feed validated member ranges directly so descriptor signatures cannot truncate stored or deflate data.
|
|
702
|
+
let activeInflateBudget:
|
|
703
|
+
| {
|
|
704
|
+
entryBytes: number;
|
|
705
|
+
fileName: string;
|
|
706
|
+
}
|
|
707
|
+
| undefined;
|
|
708
|
+
class CappedZipInflate {
|
|
709
|
+
ondata: (error: Error | null, data: Uint8Array, final: boolean) => void = () => {};
|
|
710
|
+
#failed = false;
|
|
711
|
+
#inflater: InstanceType<typeof Inflate>;
|
|
712
|
+
constructor() {
|
|
713
|
+
const budget = activeInflateBudget;
|
|
714
|
+
if (!budget) throw new ToolError("Missing ZIP inflate budget");
|
|
715
|
+
// Inflate consumes compressed chunks incrementally, avoiding a member-sized compressed copy.
|
|
716
|
+
this.#inflater = new Inflate((data, final) => {
|
|
717
|
+
const entryRemaining = MAX_ARCHIVE_ENTRY_BYTES - budget.entryBytes;
|
|
718
|
+
const totalRemaining = MAX_ARCHIVE_TOTAL_BYTES - totalUncompressed;
|
|
719
|
+
if (data.byteLength > Math.min(entryRemaining, totalRemaining)) {
|
|
720
|
+
this.#failed = true;
|
|
721
|
+
throw archiveLimitError(entryRemaining <= totalRemaining ? "entry" : "total", budget.fileName);
|
|
722
|
+
}
|
|
723
|
+
budget.entryBytes += data.byteLength;
|
|
724
|
+
this.ondata(null, data, final);
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
push(data: Uint8Array, final: boolean): void {
|
|
728
|
+
if (this.#failed) return;
|
|
729
|
+
try {
|
|
730
|
+
this.#inflater.push(data, final);
|
|
731
|
+
} catch (error) {
|
|
732
|
+
this.#failed = true;
|
|
733
|
+
this.ondata(error instanceof Error ? error : new Error(String(error)), new Uint8Array(0), true);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
}
|
|
176
737
|
const entries: ArchiveIndexEntry[] = [];
|
|
177
|
-
|
|
178
|
-
|
|
738
|
+
let totalUncompressed = 0;
|
|
739
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
740
|
+
for (const expected of directoryEntries) {
|
|
741
|
+
const normalizedPath = normalizeArchiveEntryPath(expected.name);
|
|
179
742
|
if (!normalizedPath) continue;
|
|
180
|
-
|
|
743
|
+
if (archiveEntryDepth(expected.name) > MAX_ARCHIVE_PATH_DEPTH) throw archiveLimitError("depth", expected.name);
|
|
744
|
+
const isDirectory = isArchiveDirectoryName(expected.name);
|
|
745
|
+
const nameLength = view.getUint16(expected.localOffset + 26, true);
|
|
746
|
+
const extraLength = view.getUint16(expected.localOffset + 28, true);
|
|
747
|
+
const dataStart = expected.localOffset + 30 + nameLength + extraLength;
|
|
748
|
+
const dataEnd = dataStart + expected.compressedSize;
|
|
749
|
+
if (dataEnd > bytes.byteLength) throw invalidZipArchive();
|
|
750
|
+
const compressed = bytes.subarray(dataStart, dataEnd);
|
|
751
|
+
const chunks: Uint8Array[] = [];
|
|
752
|
+
let entryBytes = 0;
|
|
753
|
+
let failed: ToolError | undefined;
|
|
754
|
+
const take = (data: Uint8Array, final: boolean) => {
|
|
755
|
+
if (failed) return;
|
|
756
|
+
const size = data.byteLength;
|
|
757
|
+
if (entryBytes + size > MAX_ARCHIVE_ENTRY_BYTES) failed = archiveLimitError("entry", normalizedPath);
|
|
758
|
+
else if (totalUncompressed + size > MAX_ARCHIVE_TOTAL_BYTES) failed = archiveLimitError("total");
|
|
759
|
+
else {
|
|
760
|
+
entryBytes += size;
|
|
761
|
+
totalUncompressed += size;
|
|
762
|
+
if (size > 0 && !isDirectory) chunks.push(data);
|
|
763
|
+
}
|
|
764
|
+
if (final && !failed && entryBytes !== expected.uncompressedSize) failed = invalidZipArchive();
|
|
765
|
+
};
|
|
766
|
+
if (expected.compression === 0) take(compressed, true);
|
|
767
|
+
else if (expected.compression !== 8) throw invalidZipArchive();
|
|
768
|
+
else {
|
|
769
|
+
activeInflateBudget = { entryBytes: 0, fileName: normalizedPath };
|
|
770
|
+
const inflator = new CappedZipInflate();
|
|
771
|
+
inflator.ondata = (error, data, final) => {
|
|
772
|
+
if (error) failed = new ToolError(error.message);
|
|
773
|
+
else take(data ?? new Uint8Array(0), final);
|
|
774
|
+
};
|
|
775
|
+
try {
|
|
776
|
+
if (compressed.byteLength === 0) {
|
|
777
|
+
inflator.push(compressed, true);
|
|
778
|
+
} else {
|
|
779
|
+
for (let offset = 0; offset < compressed.byteLength && !failed; offset += ZIP_INFLATE_CHUNK_BYTES) {
|
|
780
|
+
const end = Math.min(offset + ZIP_INFLATE_CHUNK_BYTES, compressed.byteLength);
|
|
781
|
+
inflator.push(compressed.subarray(offset, end), end === compressed.byteLength);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
} catch (error) {
|
|
785
|
+
if (!failed) failed = new ToolError(error instanceof Error ? error.message : String(error));
|
|
786
|
+
} finally {
|
|
787
|
+
activeInflateBudget = undefined;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
if (failed) throw failed;
|
|
181
791
|
entries.push({
|
|
182
792
|
path: normalizedPath,
|
|
183
793
|
isDirectory,
|
|
184
|
-
size: isDirectory ? 0 :
|
|
185
|
-
storage: isDirectory
|
|
794
|
+
size: isDirectory ? 0 : entryBytes,
|
|
795
|
+
storage: isDirectory
|
|
796
|
+
? undefined
|
|
797
|
+
: { type: "zip", bytes: expected.compression === 0 ? compressed : concatBytes(chunks, entryBytes) },
|
|
186
798
|
});
|
|
187
799
|
}
|
|
188
800
|
|
|
189
|
-
return
|
|
801
|
+
return [
|
|
802
|
+
...entries.reduce((map, entry) => map.set(entry.path, entry), new Map<string, ArchiveIndexEntry>()).values(),
|
|
803
|
+
];
|
|
190
804
|
}
|
|
191
805
|
|
|
192
806
|
export function parseArchivePathCandidates(filePath: string): ArchivePathCandidate[] {
|
|
@@ -325,8 +939,7 @@ export async function openArchive(filePath: string): Promise<ArchiveReader> {
|
|
|
325
939
|
}
|
|
326
940
|
|
|
327
941
|
// F20: cap the compressed archive read so opening a multi-GB archive cannot buffer it
|
|
328
|
-
// whole into memory.
|
|
329
|
-
const MAX_ARCHIVE_BYTES = 256 * 1024 * 1024;
|
|
942
|
+
// whole into memory. Inflate stops at one entry, the total, the entry count, and path depth.
|
|
330
943
|
const file = Bun.file(filePath);
|
|
331
944
|
if (file.size > MAX_ARCHIVE_BYTES) {
|
|
332
945
|
throw new ToolError(
|
|
@@ -334,6 +947,6 @@ export async function openArchive(filePath: string): Promise<ArchiveReader> {
|
|
|
334
947
|
);
|
|
335
948
|
}
|
|
336
949
|
const bytes = await file.bytes();
|
|
337
|
-
const entries = format === "zip" ? await readZipEntries(bytes) : await readTarEntries(bytes);
|
|
950
|
+
const entries = format === "zip" ? await readZipEntries(bytes) : await readTarEntries(bytes, format === "tar.gz");
|
|
338
951
|
return new ArchiveReader(format, entries);
|
|
339
952
|
}
|