@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
|
@@ -256,7 +256,19 @@ function invoke(
|
|
|
256
256
|
): Promise<ControlValue> | ControlValue {
|
|
257
257
|
switch (operation) {
|
|
258
258
|
case "turn.prompt":
|
|
259
|
-
return surface.prompt(text(input), input.images, input.clientRef as string | undefined);
|
|
259
|
+
return surface.prompt(text(input), input.images, input.clientRef as string | undefined, input.stagedImages);
|
|
260
|
+
case "turn.image.begin":
|
|
261
|
+
if (!surface.imageBegin) invalidInput("Image uploads are not installed for this session.");
|
|
262
|
+
return surface.imageBegin(input);
|
|
263
|
+
case "turn.image.append":
|
|
264
|
+
if (!surface.imageAppend) invalidInput("Image uploads are not installed for this session.");
|
|
265
|
+
return surface.imageAppend(input);
|
|
266
|
+
case "turn.image.finish":
|
|
267
|
+
if (!surface.imageFinish) invalidInput("Image uploads are not installed for this session.");
|
|
268
|
+
return surface.imageFinish(input);
|
|
269
|
+
case "turn.image.discard":
|
|
270
|
+
if (!surface.imageDiscard) invalidInput("Image uploads are not installed for this session.");
|
|
271
|
+
return surface.imageDiscard(input);
|
|
260
272
|
case "turn.steer":
|
|
261
273
|
return surface.steer(
|
|
262
274
|
text(input),
|
|
@@ -21,7 +21,16 @@ export type ControlInput = Record<string, unknown>;
|
|
|
21
21
|
* AgentSession and its controllers without exposing those concrete types here.
|
|
22
22
|
*/
|
|
23
23
|
export interface ControlSurface {
|
|
24
|
-
prompt(
|
|
24
|
+
prompt(
|
|
25
|
+
text: string,
|
|
26
|
+
images?: ControlValue,
|
|
27
|
+
clientRef?: string,
|
|
28
|
+
stagedImages?: ControlValue,
|
|
29
|
+
): Promise<ControlValue> | ControlValue;
|
|
30
|
+
imageBegin?(input: ControlValue): Promise<ControlValue> | ControlValue;
|
|
31
|
+
imageAppend?(input: ControlValue): Promise<ControlValue> | ControlValue;
|
|
32
|
+
imageFinish?(input: ControlValue): Promise<ControlValue> | ControlValue;
|
|
33
|
+
imageDiscard?(input: ControlValue): Promise<ControlValue> | ControlValue;
|
|
25
34
|
steer(text: string, clientRef?: string, expectedSdkRunToken?: string): Promise<ControlValue> | ControlValue;
|
|
26
35
|
followUp(text: string): Promise<ControlValue> | ControlValue;
|
|
27
36
|
abort(): Promise<ControlValue> | ControlValue;
|
package/src/sdk/host/events.ts
CHANGED
|
@@ -26,10 +26,37 @@ export interface EventReplay {
|
|
|
26
26
|
gap?: EventReplayGap;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
/** Kinds emit sites suppress once notification redaction is on. */
|
|
30
|
+
const REDACTED_REPLAY_KINDS: ReadonlySet<string> = new Set([
|
|
31
|
+
"turn_stream",
|
|
32
|
+
"context_update",
|
|
33
|
+
"image_attachment",
|
|
34
|
+
"file_attachment",
|
|
35
|
+
"reasoning_summary",
|
|
36
|
+
"tool_activity",
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
const outboundRedactBySession = new Map<string, boolean>();
|
|
40
|
+
|
|
41
|
+
/** Notification policy refresh records the live redaction bit for one session ring. */
|
|
42
|
+
export function setSessionOutboundRedact(sessionId: string, enabled: boolean): void {
|
|
43
|
+
outboundRedactBySession.set(sessionId, enabled);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function frameSessionId(frame: SdkFrame): string | undefined {
|
|
47
|
+
if (typeof frame.sessionId === "string") return frame.sessionId;
|
|
48
|
+
const payload = frame.payload;
|
|
49
|
+
if (!payload || typeof payload !== "object") return undefined;
|
|
50
|
+
const sessionId = (payload as { sessionId?: unknown }).sessionId;
|
|
51
|
+
return typeof sessionId === "string" ? sessionId : undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
29
54
|
export class SessionEventStream {
|
|
30
55
|
#generation = 0;
|
|
31
56
|
#seq = 0;
|
|
32
57
|
#frames: EventFrame[] = [];
|
|
58
|
+
#outboundRedact = false;
|
|
59
|
+
#sessionId: string | undefined;
|
|
33
60
|
readonly #ringSize: number;
|
|
34
61
|
readonly #resyncQueries: string[];
|
|
35
62
|
readonly #revisionProvider: (() => number | undefined) | undefined;
|
|
@@ -55,6 +82,12 @@ export class SessionEventStream {
|
|
|
55
82
|
return this.#seq;
|
|
56
83
|
}
|
|
57
84
|
|
|
85
|
+
/** Follow the session redaction bit. Replay then drops kinds emit sites already suppress. */
|
|
86
|
+
setOutboundRedact(enabled: boolean): void {
|
|
87
|
+
this.#outboundRedact = enabled;
|
|
88
|
+
if (this.#sessionId !== undefined) outboundRedactBySession.set(this.#sessionId, enabled);
|
|
89
|
+
}
|
|
90
|
+
|
|
58
91
|
restart(): number {
|
|
59
92
|
this.#generation += 1;
|
|
60
93
|
this.#seq = 0;
|
|
@@ -63,6 +96,8 @@ export class SessionEventStream {
|
|
|
63
96
|
}
|
|
64
97
|
|
|
65
98
|
emit(frame: SdkFrame): EventFrame {
|
|
99
|
+
this.#noteSession(frame);
|
|
100
|
+
this.#noteConfigRedact(frame);
|
|
66
101
|
const suppliedRevision = frame.revision;
|
|
67
102
|
const providedRevision = this.#revisionProvider?.();
|
|
68
103
|
const revision =
|
|
@@ -85,10 +120,9 @@ export class SessionEventStream {
|
|
|
85
120
|
|
|
86
121
|
/** Replays retained current-generation events and reports any required resynchronization. */
|
|
87
122
|
replay(lastSeq: number, generation = this.#generation): EventReplay {
|
|
88
|
-
const events = this.#frames.filter(frame => frame.seq > lastSeq);
|
|
89
123
|
if (generation !== this.#generation) {
|
|
90
124
|
return {
|
|
91
|
-
events:
|
|
125
|
+
events: this.#visible(this.#frames),
|
|
92
126
|
gap: {
|
|
93
127
|
kind: "generation_reset",
|
|
94
128
|
fromGeneration: generation,
|
|
@@ -101,7 +135,7 @@ export class SessionEventStream {
|
|
|
101
135
|
const replayFrom = lastSeq + 1;
|
|
102
136
|
if (replayFrom < oldest) {
|
|
103
137
|
return {
|
|
104
|
-
events: this.#frames.filter(frame => frame.seq >= oldest),
|
|
138
|
+
events: this.#visible(this.#frames.filter(frame => frame.seq >= oldest)),
|
|
105
139
|
gap: {
|
|
106
140
|
kind: "sequence_gap",
|
|
107
141
|
fromSeq: replayFrom,
|
|
@@ -110,6 +144,35 @@ export class SessionEventStream {
|
|
|
110
144
|
},
|
|
111
145
|
};
|
|
112
146
|
}
|
|
113
|
-
return { events };
|
|
147
|
+
return { events: this.#visible(this.#frames.filter(frame => frame.seq > lastSeq)) };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
#noteSession(frame: SdkFrame): void {
|
|
151
|
+
if (this.#sessionId !== undefined) return;
|
|
152
|
+
this.#sessionId = frameSessionId(frame);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
#noteConfigRedact(frame: SdkFrame): void {
|
|
156
|
+
if (frame.kind !== "config_update") return;
|
|
157
|
+
const payload = frame.payload;
|
|
158
|
+
if (!payload || typeof payload !== "object") return;
|
|
159
|
+
const redact = (payload as { redact?: unknown }).redact;
|
|
160
|
+
if (typeof redact !== "boolean") return;
|
|
161
|
+
this.setOutboundRedact(redact);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
#replayRedact(): boolean {
|
|
165
|
+
if (this.#sessionId !== undefined) {
|
|
166
|
+
const noted = outboundRedactBySession.get(this.#sessionId);
|
|
167
|
+
if (noted !== undefined) return noted;
|
|
168
|
+
}
|
|
169
|
+
return this.#outboundRedact;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
#visible(frames: readonly EventFrame[]): EventFrame[] {
|
|
173
|
+
const retained = this.#replayRedact()
|
|
174
|
+
? frames.filter(frame => !REDACTED_REPLAY_KINDS.has(String(frame.kind)))
|
|
175
|
+
: frames;
|
|
176
|
+
return [...retained];
|
|
114
177
|
}
|
|
115
178
|
}
|
package/src/sdk/host/host.ts
CHANGED
|
@@ -103,6 +103,7 @@ export interface SessionSdkHostOptions extends HostEndpointAdapters {
|
|
|
103
103
|
* must see the same capability-gated event kinds on both legs, or live and
|
|
104
104
|
* replay delivery diverge for the same subscriber. */
|
|
105
105
|
export const TOOL_ACTIVITY_CAPABILITY = "tool_activity_v2";
|
|
106
|
+
export const AWAITING_INPUT_CAPABILITY = "awaiting_input_v1";
|
|
106
107
|
/** Capability for non-replayable streamed turn content. */
|
|
107
108
|
export const TURN_STREAM_CAPABILITY = "turn_stream";
|
|
108
109
|
/** Capability used by notification adapters that observe a host without owning its work. */
|
|
@@ -113,12 +114,17 @@ export const SESSION_HOST_OBSERVER_CAPABILITY = "session_host_observer_v1";
|
|
|
113
114
|
* of every frame its positioned envelope already reached.
|
|
114
115
|
*/
|
|
115
116
|
export const POSITIONED_NOTIFICATION_EFFECTS_CAPABILITY = "positioned_notification_effects_v1";
|
|
116
|
-
export const CAP_GATED_FRAME_KINDS:
|
|
117
|
+
export const CAP_GATED_FRAME_KINDS: ReadonlyMap<string, string> = new Map([
|
|
118
|
+
["tool_activity", TOOL_ACTIVITY_CAPABILITY],
|
|
119
|
+
["reasoning_summary", TOOL_ACTIVITY_CAPABILITY],
|
|
120
|
+
["awaiting_input", AWAITING_INPUT_CAPABILITY],
|
|
121
|
+
]);
|
|
117
122
|
const EMPTY_CAPABILITIES: ReadonlySet<string> = new Set();
|
|
118
123
|
|
|
119
124
|
/** Keep live delivery and replay on one capability gate. */
|
|
120
125
|
export function canDeliverSdkEvent(kind: string, capabilities: ReadonlySet<string> | undefined): boolean {
|
|
121
|
-
|
|
126
|
+
const requiredCapability = CAP_GATED_FRAME_KINDS.get(kind);
|
|
127
|
+
return requiredCapability === undefined || capabilities?.has(requiredCapability) === true;
|
|
122
128
|
}
|
|
123
129
|
|
|
124
130
|
/** Safe, identifier-free explanations for every refused activation status. */
|
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
import * as crypto from "node:crypto";
|
|
2
|
+
import type { RunSettlementProof } from "@gajae-code/agent-core";
|
|
3
|
+
import type { ImageContent } from "@gajae-code/ai/core";
|
|
4
|
+
import { logger, parseImageMetadata } from "@gajae-code/utils";
|
|
5
|
+
import {
|
|
6
|
+
MAX_IMAGE_INPUT_BYTES,
|
|
7
|
+
MAX_PASTED_IMAGE_DIMENSION,
|
|
8
|
+
MAX_PASTED_IMAGE_PIXELS,
|
|
9
|
+
MAX_PASTED_IMAGE_SOURCE_BYTES,
|
|
10
|
+
MAX_STAGED_PROMPT_IMAGES,
|
|
11
|
+
} from "../../utils/image-limits";
|
|
12
|
+
import { isAnimatedImage } from "../../utils/pasted-image-loading";
|
|
13
|
+
import { TypedControlError } from "./control/dispatch";
|
|
14
|
+
|
|
15
|
+
const MAX_CHUNK_BYTES = 96 * 1024;
|
|
16
|
+
const MIN_RETAINED_BUFFER_BYTES = 4 * 1024;
|
|
17
|
+
const MAX_ACCEPTED_BYTES = 64 * 1024 * 1024;
|
|
18
|
+
const MAX_PROCESS_BYTES = 256 * 1024 * 1024;
|
|
19
|
+
// Each session gets one decoder slot; byte reservations remain process-wide.
|
|
20
|
+
const MAX_CONCURRENT_FINISHES = 1;
|
|
21
|
+
const LEASE_MS = 120_000;
|
|
22
|
+
|
|
23
|
+
/** One definitive event-driven wait; teardown and elapsed time are not settlement proof. */
|
|
24
|
+
export function retirePromptImageReservation(
|
|
25
|
+
handle: string,
|
|
26
|
+
waitForRunSettlement: (handle: string) => Promise<RunSettlementProof>,
|
|
27
|
+
isCurrent: () => boolean,
|
|
28
|
+
release: () => void,
|
|
29
|
+
): void {
|
|
30
|
+
if (!isCurrent()) return;
|
|
31
|
+
void (async (): Promise<void> => {
|
|
32
|
+
try {
|
|
33
|
+
const proof = await waitForRunSettlement(handle);
|
|
34
|
+
if (!isCurrent()) return;
|
|
35
|
+
if (proof.status === "settled") {
|
|
36
|
+
release();
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
logger.warn("SDK image settlement observer returned unfenced proof; capacity retained", {
|
|
40
|
+
handle,
|
|
41
|
+
reason: proof.reason,
|
|
42
|
+
contractViolation: proof.reason === "run_not_sealed" || proof.reason === "resources_pending",
|
|
43
|
+
});
|
|
44
|
+
} catch (error) {
|
|
45
|
+
if (isCurrent())
|
|
46
|
+
logger.warn("SDK image settlement observation failed; capacity retained without settlement proof", {
|
|
47
|
+
handle,
|
|
48
|
+
reason: "observation_failed",
|
|
49
|
+
error,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
})();
|
|
53
|
+
}
|
|
54
|
+
const MAX_BATCH_ID_LENGTH = 128;
|
|
55
|
+
const CANONICAL_BASE64 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
|
|
56
|
+
let processBytes = 0;
|
|
57
|
+
let transientBytes = 0;
|
|
58
|
+
let concurrentFinishes = 0;
|
|
59
|
+
|
|
60
|
+
export interface PromptImageProcessBudgetSnapshot {
|
|
61
|
+
/** Retained upload and accepted source bytes, including accepted base64 copies. */
|
|
62
|
+
readonly sourceBytes: number;
|
|
63
|
+
readonly transientBytes: number;
|
|
64
|
+
readonly concurrentFinishes: number;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Internal process diagnostics only; not an SDK operation or a release capability. */
|
|
68
|
+
export function getPromptImageProcessBudgetSnapshot(): PromptImageProcessBudgetSnapshot {
|
|
69
|
+
return Object.freeze({ sourceBytes: processBytes, transientBytes, concurrentFinishes });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
type Upload = {
|
|
73
|
+
owner: string;
|
|
74
|
+
batchId?: string;
|
|
75
|
+
mimeType: string;
|
|
76
|
+
byteLength: number;
|
|
77
|
+
sha256: string;
|
|
78
|
+
chunks: Buffer[];
|
|
79
|
+
reservedBytes: number;
|
|
80
|
+
length: number;
|
|
81
|
+
sequence: number;
|
|
82
|
+
finishing: boolean;
|
|
83
|
+
finished: boolean;
|
|
84
|
+
expiresAt: number;
|
|
85
|
+
timer: NodeJS.Timeout;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
function invalid(message: string): never {
|
|
89
|
+
throw new TypedControlError("invalid_input", message);
|
|
90
|
+
}
|
|
91
|
+
function busy(message: string): never {
|
|
92
|
+
throw new TypedControlError("busy", message);
|
|
93
|
+
}
|
|
94
|
+
function object(value: unknown, fields: readonly string[]): Record<string, unknown> {
|
|
95
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) invalid("Image input must be an object.");
|
|
96
|
+
const input = value as Record<string, unknown>;
|
|
97
|
+
if (Object.keys(input).some(key => !fields.includes(key))) invalid("Unknown image input field.");
|
|
98
|
+
return input;
|
|
99
|
+
}
|
|
100
|
+
function ownerId(connectionId: string | undefined): string {
|
|
101
|
+
if (!connectionId)
|
|
102
|
+
throw new TypedControlError("operation_prohibited", "An authenticated SDK connection is required.");
|
|
103
|
+
return connectionId;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Session-scoped, connection-owned image leases; no client-supplied path or identity is trusted. */
|
|
107
|
+
export class PromptImageUploadStore {
|
|
108
|
+
readonly #uploads = new Map<string, Upload>();
|
|
109
|
+
readonly #isConnectionOpen: (connectionId: string) => boolean;
|
|
110
|
+
#closed = false;
|
|
111
|
+
#uploadBytes = 0;
|
|
112
|
+
#acceptedBytes = 0;
|
|
113
|
+
#concurrentFinishes = 0;
|
|
114
|
+
|
|
115
|
+
/** The host supplies live socket membership; standalone callers must choose an explicit policy. */
|
|
116
|
+
constructor(isConnectionOpen: (connectionId: string) => boolean) {
|
|
117
|
+
this.#isConnectionOpen = isConnectionOpen;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
begin(connectionId: string | undefined, value: unknown): { id: string; nextSequence: 0 } {
|
|
121
|
+
this.#assertOpen();
|
|
122
|
+
const owner = ownerId(connectionId);
|
|
123
|
+
this.#assertConnected(owner);
|
|
124
|
+
const input = object(value, ["mimeType", "byteLength", "sha256", "batchId"]);
|
|
125
|
+
if (
|
|
126
|
+
typeof input.mimeType !== "string" ||
|
|
127
|
+
!["image/png", "image/jpeg", "image/webp", "image/gif"].includes(input.mimeType)
|
|
128
|
+
)
|
|
129
|
+
invalid("Unsupported image MIME type.");
|
|
130
|
+
if (
|
|
131
|
+
!Number.isSafeInteger(input.byteLength) ||
|
|
132
|
+
(input.byteLength as number) <= 0 ||
|
|
133
|
+
(input.byteLength as number) > MAX_IMAGE_INPUT_BYTES
|
|
134
|
+
)
|
|
135
|
+
invalid("Image byteLength exceeds the 20 MiB limit.");
|
|
136
|
+
if (typeof input.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(input.sha256))
|
|
137
|
+
invalid("Invalid image SHA-256 digest.");
|
|
138
|
+
if (
|
|
139
|
+
Object.hasOwn(input, "batchId") &&
|
|
140
|
+
(typeof input.batchId !== "string" || input.batchId.length === 0 || input.batchId.length > MAX_BATCH_ID_LENGTH)
|
|
141
|
+
)
|
|
142
|
+
invalid("Image batchId must be a nonempty string of at most 128 characters.");
|
|
143
|
+
const now = Date.now();
|
|
144
|
+
for (const [uploadId, upload] of this.#uploads) this.#expire(uploadId, upload, now);
|
|
145
|
+
if (this.#uploads.size >= MAX_STAGED_PROMPT_IMAGES) busy("Too many concurrent image uploads.");
|
|
146
|
+
const id = crypto.randomUUID();
|
|
147
|
+
const timer = setTimeout(() => this.#remove(id), LEASE_MS);
|
|
148
|
+
timer.unref?.();
|
|
149
|
+
const upload: Upload = {
|
|
150
|
+
owner,
|
|
151
|
+
...(typeof input.batchId === "string" ? { batchId: input.batchId } : {}),
|
|
152
|
+
mimeType: input.mimeType,
|
|
153
|
+
byteLength: input.byteLength as number,
|
|
154
|
+
sha256: input.sha256,
|
|
155
|
+
chunks: [],
|
|
156
|
+
reservedBytes: 0,
|
|
157
|
+
length: 0,
|
|
158
|
+
sequence: 0,
|
|
159
|
+
finishing: false,
|
|
160
|
+
finished: false,
|
|
161
|
+
expiresAt: now + LEASE_MS,
|
|
162
|
+
timer,
|
|
163
|
+
};
|
|
164
|
+
this.#uploads.set(id, upload);
|
|
165
|
+
this.#renewBatch(owner, id, upload);
|
|
166
|
+
return { id, nextSequence: 0 };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
append(
|
|
170
|
+
connectionId: string | undefined,
|
|
171
|
+
value: unknown,
|
|
172
|
+
): { id: string; nextSequence: number; receivedBytes: number } {
|
|
173
|
+
const input = object(value, ["id", "sequence", "data"]);
|
|
174
|
+
const upload = this.#owned(connectionId, input.id);
|
|
175
|
+
if (upload.finishing || upload.finished) invalid("Image upload is already finishing or finished.");
|
|
176
|
+
if (!Number.isSafeInteger(input.sequence) || input.sequence !== upload.sequence)
|
|
177
|
+
invalid("Image chunk sequence is out of order.");
|
|
178
|
+
if (
|
|
179
|
+
typeof input.data !== "string" ||
|
|
180
|
+
input.data.length === 0 ||
|
|
181
|
+
input.data.length > (MAX_CHUNK_BYTES * 4) / 3 ||
|
|
182
|
+
!CANONICAL_BASE64.test(input.data)
|
|
183
|
+
)
|
|
184
|
+
invalid("Image chunk must be canonical base64 within the frame limit.");
|
|
185
|
+
const bytes = Buffer.from(input.data, "base64");
|
|
186
|
+
if (bytes.length === 0 || bytes.length > MAX_CHUNK_BYTES || bytes.toString("base64") !== input.data)
|
|
187
|
+
invalid("Image chunk must be canonical base64 within the frame limit.");
|
|
188
|
+
if (upload.length + bytes.length > upload.byteLength) invalid("Image upload exceeds its declared size.");
|
|
189
|
+
const nextLength = upload.length + bytes.length;
|
|
190
|
+
const allocationLimit = Math.max(upload.byteLength, MIN_RETAINED_BUFFER_BYTES);
|
|
191
|
+
const requiredBytes = Math.min(Math.ceil(nextLength / MAX_CHUNK_BYTES) * MAX_CHUNK_BYTES, allocationLimit);
|
|
192
|
+
const addedBytes = requiredBytes - upload.reservedBytes;
|
|
193
|
+
if (
|
|
194
|
+
this.#uploadBytes + addedBytes > MAX_PASTED_IMAGE_SOURCE_BYTES ||
|
|
195
|
+
processBytes + transientBytes + addedBytes > MAX_PROCESS_BYTES
|
|
196
|
+
)
|
|
197
|
+
busy("Image staging capacity exceeded.");
|
|
198
|
+
|
|
199
|
+
// Reserve coalesced fixed-size slabs before allocating or retaining them. Tiny declared
|
|
200
|
+
// uploads reserve at least 4 KiB, avoiding one retained Buffer per admitted byte.
|
|
201
|
+
this.#uploadBytes += addedBytes;
|
|
202
|
+
processBytes += addedBytes;
|
|
203
|
+
const originalChunkCount = upload.chunks.length;
|
|
204
|
+
try {
|
|
205
|
+
const newChunks: Buffer[] = [];
|
|
206
|
+
for (let allocated = upload.reservedBytes; allocated < requiredBytes; ) {
|
|
207
|
+
const chunkBytes = Math.min(MAX_CHUNK_BYTES, requiredBytes - allocated);
|
|
208
|
+
newChunks.push(Buffer.allocUnsafeSlow(chunkBytes));
|
|
209
|
+
allocated += chunkBytes;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
let sourceOffset = 0;
|
|
213
|
+
let targetOffset = upload.length;
|
|
214
|
+
while (sourceOffset < bytes.length) {
|
|
215
|
+
const chunkIndex = Math.floor(targetOffset / MAX_CHUNK_BYTES);
|
|
216
|
+
const chunk =
|
|
217
|
+
chunkIndex < upload.chunks.length
|
|
218
|
+
? upload.chunks[chunkIndex]!
|
|
219
|
+
: newChunks[chunkIndex - upload.chunks.length]!;
|
|
220
|
+
const chunkOffset = targetOffset % MAX_CHUNK_BYTES;
|
|
221
|
+
const copied = Math.min(bytes.length - sourceOffset, chunk.length - chunkOffset);
|
|
222
|
+
bytes.copy(chunk, chunkOffset, sourceOffset, sourceOffset + copied);
|
|
223
|
+
sourceOffset += copied;
|
|
224
|
+
targetOffset += copied;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (newChunks.length > 0) upload.chunks.push(...newChunks);
|
|
228
|
+
upload.reservedBytes = requiredBytes;
|
|
229
|
+
upload.length = nextLength;
|
|
230
|
+
upload.sequence++;
|
|
231
|
+
} catch (error) {
|
|
232
|
+
upload.chunks.length = originalChunkCount;
|
|
233
|
+
this.#uploadBytes -= addedBytes;
|
|
234
|
+
processBytes -= addedBytes;
|
|
235
|
+
throw error;
|
|
236
|
+
}
|
|
237
|
+
this.#renewBatch(upload.owner, input.id as string, upload);
|
|
238
|
+
return { id: input.id as string, nextSequence: upload.sequence, receivedBytes: upload.length };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async finish(
|
|
242
|
+
connectionId: string | undefined,
|
|
243
|
+
value: unknown,
|
|
244
|
+
): Promise<{ id: string; byteLength: number; sha256: string; mimeType: string }> {
|
|
245
|
+
const input = object(value, ["id"]);
|
|
246
|
+
const upload = this.#owned(connectionId, input.id);
|
|
247
|
+
if (upload.finishing || upload.finished) invalid("Image upload is already finishing or finished.");
|
|
248
|
+
if (upload.length !== upload.byteLength) invalid("Image upload is incomplete.");
|
|
249
|
+
// Claim the lease, decoder slot and copy capacity before the first allocation or await.
|
|
250
|
+
if (
|
|
251
|
+
this.#concurrentFinishes >= MAX_CONCURRENT_FINISHES ||
|
|
252
|
+
processBytes + transientBytes + upload.length > MAX_PROCESS_BYTES
|
|
253
|
+
)
|
|
254
|
+
busy("Image finalization capacity exceeded.");
|
|
255
|
+
upload.finishing = true;
|
|
256
|
+
this.#concurrentFinishes++;
|
|
257
|
+
concurrentFinishes++;
|
|
258
|
+
transientBytes += upload.length;
|
|
259
|
+
let transientReservation = upload.length;
|
|
260
|
+
try {
|
|
261
|
+
const bytes = Buffer.concat(upload.chunks, upload.length);
|
|
262
|
+
if (crypto.createHash("sha256").update(bytes).digest("hex") !== upload.sha256)
|
|
263
|
+
invalid("Image SHA-256 digest mismatch.");
|
|
264
|
+
const metadata = parseImageMetadata(bytes);
|
|
265
|
+
if (
|
|
266
|
+
!metadata ||
|
|
267
|
+
metadata.mimeType !== upload.mimeType ||
|
|
268
|
+
!metadata.width ||
|
|
269
|
+
!metadata.height ||
|
|
270
|
+
metadata.width > MAX_PASTED_IMAGE_DIMENSION ||
|
|
271
|
+
metadata.height > MAX_PASTED_IMAGE_DIMENSION ||
|
|
272
|
+
metadata.width * metadata.height > MAX_PASTED_IMAGE_PIXELS
|
|
273
|
+
)
|
|
274
|
+
invalid("Image MIME type, structure or dimensions are invalid.");
|
|
275
|
+
if (isAnimatedImage(bytes, metadata.mimeType) !== false)
|
|
276
|
+
invalid("Animated or malformed images are not supported.");
|
|
277
|
+
const decodedBytes = metadata.width * metadata.height * 4;
|
|
278
|
+
if (processBytes + transientBytes + decodedBytes > MAX_PROCESS_BYTES)
|
|
279
|
+
busy("Image decoding capacity exceeded.");
|
|
280
|
+
transientBytes += decodedBytes;
|
|
281
|
+
transientReservation += decodedBytes;
|
|
282
|
+
try {
|
|
283
|
+
const decoded = await new Bun.Image(bytes).metadata();
|
|
284
|
+
if (decoded.width !== metadata.width || decoded.height !== metadata.height)
|
|
285
|
+
invalid("Image dimensions do not match decoded data.");
|
|
286
|
+
await new Bun.Image(bytes).resize(1, 1).png().bytes();
|
|
287
|
+
} catch (error) {
|
|
288
|
+
if (error instanceof TypedControlError) throw error;
|
|
289
|
+
invalid("Image cannot be decoded.");
|
|
290
|
+
}
|
|
291
|
+
// A custom transport may attest liveness without delivering a close
|
|
292
|
+
// callback. Decode completion must re-authorize, not renew a dead lease.
|
|
293
|
+
if (!this.#isConnectionOpen(upload.owner)) {
|
|
294
|
+
this.#remove(input.id as string);
|
|
295
|
+
throw new TypedControlError("resource_gone", "Image upload connection is closed.");
|
|
296
|
+
}
|
|
297
|
+
// The transport can disconnect while decoding. A removed lease cannot be resurrected.
|
|
298
|
+
if (this.#uploads.get(input.id as string) !== upload || this.#expire(input.id as string, upload))
|
|
299
|
+
throw new TypedControlError("resource_gone", "Image upload expired.");
|
|
300
|
+
const releasedBytes = upload.reservedBytes - upload.length;
|
|
301
|
+
this.#uploadBytes -= releasedBytes;
|
|
302
|
+
processBytes -= releasedBytes;
|
|
303
|
+
upload.chunks = [bytes];
|
|
304
|
+
upload.reservedBytes = upload.length;
|
|
305
|
+
upload.finished = true;
|
|
306
|
+
this.#renewBatch(upload.owner, input.id as string, upload);
|
|
307
|
+
return { id: input.id as string, byteLength: upload.length, sha256: upload.sha256, mimeType: upload.mimeType };
|
|
308
|
+
} finally {
|
|
309
|
+
// A discarded lease still owns its source chunks until this async decode settles.
|
|
310
|
+
if (this.#uploads.get(input.id as string) !== upload) {
|
|
311
|
+
this.#uploadBytes -= upload.reservedBytes;
|
|
312
|
+
processBytes -= upload.reservedBytes;
|
|
313
|
+
}
|
|
314
|
+
transientBytes -= transientReservation;
|
|
315
|
+
concurrentFinishes--;
|
|
316
|
+
this.#concurrentFinishes--;
|
|
317
|
+
upload.finishing = false;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
discard(connectionId: string | undefined, value: unknown): { discarded: true } {
|
|
322
|
+
const input = object(value, ["id"]);
|
|
323
|
+
this.#owned(connectionId, input.id);
|
|
324
|
+
this.#remove(input.id as string);
|
|
325
|
+
return { discarded: true };
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/** Synchronous reserve–validate–copy–consume transaction, called before prompt admission. */
|
|
329
|
+
redeem(connectionId: string | undefined, value: unknown): { images: ImageContent[]; release: () => void } {
|
|
330
|
+
this.#assertOpen();
|
|
331
|
+
const owner = ownerId(connectionId);
|
|
332
|
+
this.#assertConnected(owner);
|
|
333
|
+
if (!Array.isArray(value) || value.length === 0 || value.length > MAX_STAGED_PROMPT_IMAGES)
|
|
334
|
+
invalid(`stagedImages must contain 1–${MAX_STAGED_PROMPT_IMAGES} image IDs.`);
|
|
335
|
+
const seen = new Set<string>();
|
|
336
|
+
const entries: Array<[string, Upload]> = [];
|
|
337
|
+
let bytes = 0;
|
|
338
|
+
for (const descriptor of value) {
|
|
339
|
+
const input = object(descriptor, ["id"]);
|
|
340
|
+
const id = input.id;
|
|
341
|
+
if (typeof id !== "string" || !id || seen.has(id))
|
|
342
|
+
invalid("stagedImages contains an invalid or duplicate ID.");
|
|
343
|
+
seen.add(id);
|
|
344
|
+
const entry = this.#uploads.get(id);
|
|
345
|
+
if (!entry || entry.owner !== owner || this.#expire(id, entry))
|
|
346
|
+
throw new TypedControlError("image_upload_unavailable", "Image upload is unavailable or already consumed.");
|
|
347
|
+
if (!entry.finished) invalid("Image upload is not finished.");
|
|
348
|
+
bytes += entry.length;
|
|
349
|
+
entries.push([id, entry]);
|
|
350
|
+
}
|
|
351
|
+
const encodedBytes = entries.reduce((total, [, entry]) => total + Math.ceil(entry.length / 3) * 4, 0);
|
|
352
|
+
if (
|
|
353
|
+
bytes > MAX_PASTED_IMAGE_SOURCE_BYTES ||
|
|
354
|
+
this.#acceptedBytes + bytes > MAX_ACCEPTED_BYTES ||
|
|
355
|
+
processBytes + transientBytes + bytes + encodedBytes > MAX_PROCESS_BYTES
|
|
356
|
+
)
|
|
357
|
+
busy("Accepted image capacity exceeded.");
|
|
358
|
+
this.#acceptedBytes += bytes;
|
|
359
|
+
processBytes += bytes + encodedBytes;
|
|
360
|
+
let released = false;
|
|
361
|
+
const release = () => {
|
|
362
|
+
if (released) return;
|
|
363
|
+
released = true;
|
|
364
|
+
this.#acceptedBytes -= bytes;
|
|
365
|
+
processBytes -= bytes + encodedBytes;
|
|
366
|
+
};
|
|
367
|
+
try {
|
|
368
|
+
const images: ImageContent[] = entries.map(([, entry]) => ({
|
|
369
|
+
type: "image",
|
|
370
|
+
data: entry.chunks[0]!.toString("base64"),
|
|
371
|
+
mimeType: entry.mimeType,
|
|
372
|
+
}));
|
|
373
|
+
for (const [id] of entries) this.#remove(id);
|
|
374
|
+
return { images, release };
|
|
375
|
+
} catch (error) {
|
|
376
|
+
release();
|
|
377
|
+
throw error;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
disconnect(connectionId: string): void {
|
|
382
|
+
for (const [id, upload] of this.#uploads) if (upload.owner === connectionId) this.#remove(id);
|
|
383
|
+
}
|
|
384
|
+
close(): void {
|
|
385
|
+
this.#closed = true;
|
|
386
|
+
for (const id of this.#uploads.keys()) this.#remove(id);
|
|
387
|
+
}
|
|
388
|
+
#owned(connectionId: string | undefined, value: unknown): Upload {
|
|
389
|
+
this.#assertOpen();
|
|
390
|
+
const owner = ownerId(connectionId);
|
|
391
|
+
this.#assertConnected(owner);
|
|
392
|
+
if (typeof value !== "string" || !value) invalid("Image upload ID is required.");
|
|
393
|
+
const upload = this.#uploads.get(value);
|
|
394
|
+
if (!upload || upload.owner !== owner || this.#expire(value, upload))
|
|
395
|
+
throw new TypedControlError("resource_gone", "Image upload is unavailable.");
|
|
396
|
+
return upload;
|
|
397
|
+
}
|
|
398
|
+
#assertOpen(): void {
|
|
399
|
+
if (this.#closed) throw new TypedControlError("resource_gone", "Image upload session is closed.");
|
|
400
|
+
}
|
|
401
|
+
#assertConnected(owner: string): void {
|
|
402
|
+
if (!this.#isConnectionOpen(owner))
|
|
403
|
+
throw new TypedControlError("resource_gone", "Image upload connection is closed.");
|
|
404
|
+
}
|
|
405
|
+
#expire(id: string, upload: Upload, now = Date.now()): boolean {
|
|
406
|
+
if (now < upload.expiresAt) return false;
|
|
407
|
+
this.#remove(id);
|
|
408
|
+
return true;
|
|
409
|
+
}
|
|
410
|
+
#renewBatch(owner: string, id: string, current: Upload): void {
|
|
411
|
+
const now = Date.now();
|
|
412
|
+
if (this.#uploads.get(id) !== current || this.#expire(id, current, now))
|
|
413
|
+
throw new TypedControlError("resource_gone", "Image upload expired.");
|
|
414
|
+
if (current.batchId === undefined) {
|
|
415
|
+
this.#resetLease(id, current, now);
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
for (const [uploadId, upload] of this.#uploads)
|
|
419
|
+
if (upload.owner === owner && upload.batchId === current.batchId && !this.#expire(uploadId, upload, now))
|
|
420
|
+
this.#resetLease(uploadId, upload, now);
|
|
421
|
+
}
|
|
422
|
+
#resetLease(id: string, upload: Upload, now: number): void {
|
|
423
|
+
clearTimeout(upload.timer);
|
|
424
|
+
upload.expiresAt = now + LEASE_MS;
|
|
425
|
+
upload.timer = setTimeout(() => this.#remove(id), LEASE_MS);
|
|
426
|
+
upload.timer.unref?.();
|
|
427
|
+
}
|
|
428
|
+
#remove(id: string): void {
|
|
429
|
+
const upload = this.#uploads.get(id);
|
|
430
|
+
if (!upload) return;
|
|
431
|
+
this.#uploads.delete(id);
|
|
432
|
+
clearTimeout(upload.timer);
|
|
433
|
+
if (!upload.finishing) {
|
|
434
|
+
this.#uploadBytes -= upload.reservedBytes;
|
|
435
|
+
processBytes -= upload.reservedBytes;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
@@ -133,6 +133,21 @@ describe("SDK surface parity", () => {
|
|
|
133
133
|
expect(policy.installedQueries.has("skill.list/state")).toBe(false);
|
|
134
134
|
});
|
|
135
135
|
|
|
136
|
+
test("private image operations are advertised only when the host installs staging", () => {
|
|
137
|
+
for (const available of [false, true]) {
|
|
138
|
+
const policy = createSdkSurfacePolicy({
|
|
139
|
+
bindings: [],
|
|
140
|
+
workflowGateAvailable: false,
|
|
141
|
+
imageUploadsAvailable: available,
|
|
142
|
+
});
|
|
143
|
+
const capabilities = createSdkCapabilities(policy);
|
|
144
|
+
expect(capabilities.operations.filter(operation => operation.startsWith("turn.image."))).toEqual(
|
|
145
|
+
available ? ["turn.image.begin", "turn.image.append", "turn.image.finish", "turn.image.discard"] : [],
|
|
146
|
+
);
|
|
147
|
+
expect(capabilities.operations).toContain("turn.prompt");
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
136
151
|
test("request results, replay order, and typed protocol errors match across transports", async () => {
|
|
137
152
|
const nativeTransport = memoryTransport();
|
|
138
153
|
const loopbackTransport = memoryTransport();
|