mixdog 1.3.5 → 1.4.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/NOTICE.md +2 -2
- package/README.md +2 -2
- package/package.json +2 -11
- package/scripts/daemon-stub.mjs +1 -1
- package/scripts/gate-local.mjs +4 -2
- package/scripts/native-release-status.mjs +2 -2
- package/scripts/recall-history-cases.json +1 -1
- package/scripts/suite-health.mjs +10 -1
- package/scripts/test.mjs +7 -11
- package/src/agents/reviewer/AGENT.md +7 -11
- package/src/agents/reviewer/agent.json +1 -1
- package/src/defaults/shipped-history.json +17 -1
- package/src/defaults/skills/browser-use/SKILL.md +59 -11
- package/src/defaults/skills/code-tidy/SKILL.md +142 -270
- package/src/defaults/skills/code-tidy/references/agent-cleanup.md +4 -4
- package/src/defaults/skills/code-tidy/references/cleanup-report.md +31 -76
- package/src/defaults/skills/code-tidy/references/dead-code.md +6 -12
- package/src/defaults/skills/computer-use/SKILL.md +33 -9
- package/src/defaults/skills/setup/SKILL.md +22 -0
- package/src/defaults/skills/setup/references/actions.md +39 -5
- package/src/defaults/skills/setup/references/surfaces.md +11 -2
- package/src/defaults/skills/visualize/SKILL.md +2 -1
- package/src/lib/rules-builder.cjs +10 -8
- package/src/rules/agent/AGENT.md +6 -6
- package/src/rules/lead/LEAD.md +18 -20
- package/src/rules/shared/05-tool-calls.md +5 -6
- package/src/rules/shared/10-tool-workflow.md +2 -0
- package/src/rules/shared/60-verification.md +7 -5
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -3
- package/src/runtime/agent/orchestrator/agent-runtime/title-completion.mjs +3 -1
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +3 -1
- package/src/runtime/agent/orchestrator/config.mjs +17 -1
- package/src/runtime/agent/orchestrator/context/collect.mjs +3 -0
- package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +72 -67
- package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +54 -1
- package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +3 -3
- package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +176 -72
- package/src/runtime/agent/orchestrator/mcp/client.mjs +19 -0
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +181 -133
- package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +42 -6
- package/src/runtime/agent/orchestrator/providers/anthropic-cache-warm.mjs +165 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/gzip-post.mjs +14 -14
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +145 -66
- package/src/runtime/agent/orchestrator/providers/anthropic-overload-fallback.mjs +25 -3
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +64 -33
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +135 -87
- package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +123 -102
- package/src/runtime/agent/orchestrator/providers/cache-warm-admission.mjs +90 -0
- package/src/runtime/agent/orchestrator/providers/cursor-wire-transport.mjs +25 -9
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +22 -13
- package/src/runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs +17 -3
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +52 -10
- package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +8 -1
- package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/npm-cli-version.mjs +11 -3
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +10 -9
- package/src/runtime/agent/orchestrator/providers/openai-ws-send/send-context.mjs +22 -3
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/attempt-context.mjs +43 -5
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/handshake-failure.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/stream-failure.mjs +6 -3
- package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +65 -1
- package/src/runtime/agent/orchestrator/providers/quota-reading.mjs +38 -12
- package/src/runtime/agent/orchestrator/providers/registry.mjs +26 -0
- package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +39 -7
- package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +6 -0
- package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +12 -7
- package/src/runtime/agent/orchestrator/runtime-core/compaction-config-keys.mjs +62 -0
- package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +37 -16
- package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-delta.mjs +100 -23
- package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +19 -4
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +125 -30
- package/src/runtime/agent/orchestrator/session/cache-warm.mjs +792 -0
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +133 -22
- package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +475 -58
- package/src/runtime/agent/orchestrator/session/compaction-archive.mjs +42 -17
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +11 -5
- package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +0 -2
- package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +36 -4
- package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +18 -22
- package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +32 -3
- package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +17 -3
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +7 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/after-hook.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +3 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +16 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +16 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +10 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +3 -10
- package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +12 -3
- package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +12 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +19 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +133 -90
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-state.mjs +31 -4
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +30 -18
- package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +23 -13
- package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +11 -2
- package/src/runtime/agent/orchestrator/session/manager/provider-replay.mjs +13 -0
- package/src/runtime/agent/orchestrator/session/manager/route-fallback-chain.mjs +129 -0
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +20 -2
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +8 -0
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +7 -2
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +102 -15
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +18 -5
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +19 -8
- package/src/runtime/agent/orchestrator/session/prompt-cache-idle-gaps.mjs +113 -0
- package/src/runtime/agent/orchestrator/session/prompt-cache-touch-store.mjs +127 -0
- package/src/runtime/agent/orchestrator/session/routine-result.mjs +36 -9
- package/src/runtime/agent/orchestrator/session/sensitive-projection.mjs +100 -0
- package/src/runtime/agent/orchestrator/session/sensitive-values.mjs +257 -0
- package/src/runtime/agent/orchestrator/session/side-question-store.mjs +20 -7
- package/src/runtime/agent/orchestrator/session/side-question.mjs +18 -0
- package/src/runtime/agent/orchestrator/session/skill-update-notice.mjs +60 -0
- package/src/runtime/agent/orchestrator/session/store/live-state.mjs +6 -3
- package/src/runtime/agent/orchestrator/session/store/save-delta.mjs +9 -5
- package/src/runtime/agent/orchestrator/session/store/serialize.mjs +9 -8
- package/src/runtime/agent/orchestrator/session/store/session-persistence-events.mjs +29 -0
- package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +8 -6
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +12 -2
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +44 -14
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +84 -3
- package/src/runtime/agent/orchestrator/session/stored-attachment-history.mjs +32 -0
- package/src/runtime/agent/orchestrator/session/thinking-binding.mjs +108 -0
- package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/tool-batch/record-outcome.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +18 -1
- package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-status.mjs +9 -7
- package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-text.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/turn-failure.mjs +5 -1
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +5 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +12 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/misuse-guard.mjs +240 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/result-format.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/shell-cwd.mjs +38 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +25 -5
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +65 -7
- package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +7 -1
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +36 -13
- package/src/runtime/browser-bridge/action-schema.mjs +13 -2
- package/src/runtime/browser-bridge/client.mjs +73 -8
- package/src/runtime/browser-bridge/input-fields.mjs +8 -9
- package/src/runtime/browser-bridge/mail-links.mjs +107 -0
- package/src/runtime/browser-bridge/tool-defs.mjs +1 -1
- package/src/runtime/channels/lib/config.mjs +6 -61
- package/src/runtime/channels/lib/crash-log.mjs +1 -1
- package/src/runtime/channels/lib/event-pipeline.mjs +4 -19
- package/src/runtime/channels/lib/event-queue.mjs +3 -18
- package/src/runtime/channels/lib/owned-runtime/automation-services.mjs +2 -2
- package/src/runtime/channels/lib/owned-runtime/automation-start.mjs +3 -4
- package/src/runtime/channels/lib/owned-runtime/config-reload.mjs +4 -59
- package/src/runtime/channels/lib/owned-runtime/owned-start.mjs +33 -115
- package/src/runtime/channels/lib/owned-runtime/owned-stop.mjs +5 -22
- package/src/runtime/channels/lib/owned-runtime/state.mjs +2 -8
- package/src/runtime/channels/lib/owned-runtime.mjs +6 -23
- package/src/runtime/channels/lib/parent-bridge.mjs +2 -2
- package/src/runtime/channels/lib/runtime-paths.mjs +4 -43
- package/src/runtime/channels/lib/scheduler.mjs +33 -147
- package/src/runtime/channels/lib/schedules-db.mjs +34 -11
- package/src/runtime/channels/lib/status-snapshot.mjs +1 -1
- package/src/runtime/channels/lib/tool-dispatch.mjs +1 -2
- package/src/runtime/channels/lib/webhooks-db.mjs +5 -9
- package/src/runtime/channels/lib/worker-bootstrap.mjs +0 -1
- package/src/runtime/channels/lib/worker-ipc.mjs +0 -1
- package/src/runtime/channels/lib/worker-main.mjs +10 -34
- package/src/runtime/computer-bridge/action-schema.mjs +42 -1
- package/src/runtime/computer-bridge/actions.mjs +9 -1
- package/src/runtime/computer-bridge/client.mjs +180 -8
- package/src/runtime/computer-bridge/core-actions.mjs +2 -2
- package/src/runtime/computer-bridge/error-recovery.mjs +27 -2
- package/src/runtime/computer-bridge/linux-readiness.mjs +65 -23
- package/src/runtime/computer-bridge/pending-continuation.mjs +1 -1
- package/src/runtime/connectors/connector-accounts-mcp.mjs +230 -0
- package/src/runtime/connectors/gmail-app-password.mjs +172 -39
- package/src/runtime/connectors/google-mcp.mjs +10 -12
- package/src/runtime/memory/lib/pg/adapter.mjs +77 -70
- package/src/runtime/memory/lib/pg/supervisor.mjs +24 -12
- package/src/runtime/memory/lib/runtime-fetcher.mjs +23 -5
- package/src/runtime/session-search/session-search-db.mjs +73 -9
- package/src/runtime/session-search/session-search-ingest.mjs +110 -20
- package/src/runtime/shared/abort-race.mjs +89 -0
- package/src/runtime/shared/agent-avatar.d.mts +0 -1
- package/src/runtime/shared/agent-kind.mjs +4 -3
- package/src/runtime/shared/bounded-download.mjs +8 -3
- package/src/runtime/shared/config.mjs +2 -13
- package/src/runtime/shared/connector-accounts.mjs +20 -10
- package/src/runtime/shared/connector-catalog.d.mts +29 -0
- package/src/runtime/shared/connector-catalog.mjs +150 -0
- package/src/runtime/shared/connector-triggers.mjs +22 -19
- package/src/runtime/shared/defaults-separation.mjs +2 -14
- package/src/runtime/shared/delegated-approval-host.mjs +103 -11
- package/src/runtime/shared/err-text.mjs +24 -15
- package/src/runtime/shared/file-permissions.d.mts +0 -8
- package/src/runtime/shared/file-permissions.mjs +0 -11
- package/src/runtime/shared/git-direct.test-support.mjs +19 -0
- package/src/runtime/shared/heartbeat-interval.d.mts +4 -0
- package/src/runtime/shared/heartbeat-interval.mjs +19 -0
- package/src/runtime/shared/idle-auto-clear-gate.mjs +104 -0
- package/src/runtime/shared/llm/usage-rollup.mjs +1 -1
- package/src/runtime/shared/mixy-presence.mjs +22 -0
- package/src/runtime/shared/orchestration.d.mts +2 -0
- package/src/runtime/shared/pg-schema-db.mjs +27 -16
- package/src/runtime/shared/profile-config.cjs +1 -1
- package/src/runtime/shared/prompt-origin.d.mts +4 -0
- package/src/runtime/shared/prompt-origin.mjs +21 -0
- package/src/runtime/shared/provider-accounts.mjs +7 -1
- package/src/runtime/shared/provider-display-name.mjs +29 -0
- package/src/runtime/shared/provider-timeout-phase.mjs +2 -0
- package/src/runtime/shared/routine-trigger.d.mts +2 -0
- package/src/runtime/shared/routine-trigger.mjs +10 -0
- package/src/runtime/shared/sensitive-path-rules.d.mts +2 -10
- package/src/runtime/shared/sensitive-path-rules.mjs +19 -41
- package/src/runtime/shared/session-activity-signal.mjs +34 -1
- package/src/runtime/shared/session-sidebar-host.d.mts +1 -1
- package/src/runtime/shared/session-sidebar-host.mjs +7 -3
- package/src/runtime/shared/session-turn-prelude.mjs +18 -0
- package/src/runtime/shared/tool-result-summary.mjs +1 -14
- package/src/runtime/shared/worktrees/test-support.mjs +9 -4
- package/src/runtime/terminal-bridge/client.mjs +10 -1
- package/src/runtime/visualize-bridge/client.mjs +27 -8
- package/src/runtime/visualize-bridge/tool-defs.mjs +22 -8
- package/src/runtime/visualize-bridge/tool-meta.d.mts +5 -0
- package/src/runtime/visualize-bridge/tool-meta.mjs +22 -0
- package/src/runtime/web-search/lib/fetch-pipeline.mjs +2 -2
- package/src/session-runtime/agent-home-delivery.mjs +448 -185
- package/src/session-runtime/agent-task-sessions.mjs +329 -180
- package/src/session-runtime/boot/apis.mjs +9 -0
- package/src/session-runtime/boot/infrastructure.mjs +4 -0
- package/src/session-runtime/boot/providers.mjs +3 -0
- package/src/session-runtime/boot/tools.mjs +6 -1
- package/src/session-runtime/config-lifecycle.mjs +6 -0
- package/src/session-runtime/connector-merge-migration.mjs +305 -0
- package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +132 -28
- package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +2 -0
- package/src/session-runtime/lifecycle/inheritance.mjs +16 -3
- package/src/session-runtime/lifecycle/session-catalog.mjs +3 -1
- package/src/session-runtime/lifecycle/teardown/runtime-stops.mjs +50 -4
- package/src/session-runtime/lifecycle/teardown.mjs +23 -5
- package/src/session-runtime/mcp-glue/server-catalog.mjs +55 -3
- package/src/session-runtime/mcp-oauth-login.mjs +5 -3
- package/src/session-runtime/mixy-heartbeat.mjs +153 -33
- package/src/session-runtime/notification-bus.mjs +31 -117
- package/src/session-runtime/notification-completion-queue.mjs +54 -0
- package/src/session-runtime/notification-listeners.mjs +76 -0
- package/src/session-runtime/owned-automation-run.mjs +2 -1
- package/src/session-runtime/prewarm/channel-start.mjs +3 -7
- package/src/session-runtime/resource-mcp-api.mjs +143 -224
- package/src/session-runtime/resource-mcp-connector-accounts.mjs +163 -0
- package/src/session-runtime/resource-mcp-oauth-api.mjs +217 -0
- package/src/session-runtime/resource-mcp-secrets.mjs +82 -0
- package/src/session-runtime/resource-mcp-servers.mjs +78 -0
- package/src/session-runtime/resource-mcp-store.mjs +49 -0
- package/src/session-runtime/routine-owner.mjs +40 -9
- package/src/session-runtime/services/agent-tool/close-flow.mjs +3 -0
- package/src/session-runtime/services/agent-tool/spawn-preset.mjs +7 -2
- package/src/session-runtime/services/agent-tool/tag-registry.mjs +1 -0
- package/src/session-runtime/services/agent-tool/worker-index/awaiting-user-sync.mjs +51 -0
- package/src/session-runtime/services/agent-tool/worker-index/disposal.mjs +55 -0
- package/src/session-runtime/services/agent-tool/worker-index/released-store.mjs +123 -0
- package/src/session-runtime/services/agent-tool/worker-index/restart-settlement.mjs +112 -0
- package/src/session-runtime/services/agent-tool/worker-index/row-reads.mjs +36 -0
- package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +2 -0
- package/src/session-runtime/services/agent-tool/worker-index/row-store.mjs +17 -4
- package/src/session-runtime/services/agent-tool/worker-index/row-writes.mjs +58 -0
- package/src/session-runtime/services/agent-tool/worker-index.mjs +41 -241
- package/src/session-runtime/services/agent-tool/worker-rows.mjs +3 -0
- package/src/session-runtime/services/channel-admin.mjs +7 -13
- package/src/session-runtime/services/channel-session-router.mjs +4 -2
- package/src/session-runtime/services/seeds.mjs +0 -28
- package/src/session-runtime/session-compaction-sync.mjs +27 -0
- package/src/session-runtime/session-create/session-options.mjs +1 -0
- package/src/session-runtime/settings-builtin-tools-api.mjs +11 -0
- package/src/session-runtime/settings-compaction-api.mjs +2 -40
- package/src/session-runtime/settings-system-api.mjs +13 -1
- package/src/session-runtime/setup-tool/agent-settings-actions.mjs +65 -0
- package/src/session-runtime/setup-tool/executor.mjs +145 -22
- package/src/session-runtime/setup-tool/extended-actions.mjs +8 -3
- package/src/session-runtime/setup-tool/session-route-actions.mjs +152 -0
- package/src/session-runtime/setup-tool/settings-contract.mjs +4 -3
- package/src/session-runtime/setup-tool/tool-defs.mjs +64 -5
- package/src/session-runtime/tool-policy/agent-profile-fold.mjs +17 -13
- package/src/session-runtime/tool-policy/policy-stage.mjs +16 -2
- package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +1 -0
- package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +4 -0
- package/src/standalone/channel-transport/client-registry/client-records.mjs +65 -0
- package/src/standalone/channel-transport/client-registry/client-registration.mjs +118 -0
- package/src/standalone/channel-transport/client-registry.mjs +24 -163
- package/src/standalone/channel-transport.mjs +1 -1
- package/src/standalone/daemon-channels-loader.mjs +10 -8
- package/src/standalone/daemon-session-control/list-page.mjs +72 -0
- package/src/standalone/daemon-session-control/request.mjs +51 -0
- package/src/standalone/daemon-session-control/room-host.mjs +57 -0
- package/src/standalone/daemon-session-control/route-control.mjs +48 -0
- package/src/standalone/daemon-session-control/send-turn.mjs +60 -0
- package/src/standalone/daemon-session-control/worktree-control.mjs +40 -0
- package/src/standalone/daemon-session-control.mjs +124 -188
- package/src/standalone/daemon.mjs +26 -3
- package/src/standalone/session-client.mjs +7 -3
- package/src/standalone/session-protocol.mjs +5 -0
- package/src/standalone/session-service/agent-tree/agent-child.mjs +1 -0
- package/src/standalone/session-service/quota-resume.mjs +3 -0
- package/src/standalone/session-service/retention/busy.mjs +65 -1
- package/src/standalone/session-service/session-calls/session-action.mjs +23 -2
- package/src/standalone/session-service/session-calls/session-views.mjs +8 -0
- package/src/standalone/session-tool-output.mjs +15 -7
- package/src/tui/app/model-options.mjs +3 -28
- package/src/tui/dist/index.mjs +58 -64
- package/src/tui/session/agent-job-feed/model-notification.mjs +3 -0
- package/src/tui/session/initial-state.mjs +3 -0
- package/src/tui/session/notice-surface.mjs +7 -1
- package/src/tui/session/queue-helpers.mjs +3 -42
- package/src/tui/session/session-api/routes-agent-definitions.mjs +14 -2
- package/src/tui/session/session-api/routes-write.mjs +5 -1
- package/src/tui/session/session-api/routes.mjs +1 -0
- package/src/tui/session/session-api/settings.mjs +17 -1
- package/src/tui/session/session-flow/auto-clear.mjs +4 -33
- package/src/tui/session/session-flow/drain-closed-queue.mjs +38 -0
- package/src/tui/session/session-flow/drain-enqueue.mjs +86 -0
- package/src/tui/session/session-flow/drain-retry.mjs +35 -0
- package/src/tui/session/session-flow/drain.mjs +118 -149
- package/src/tui/session/session-flow/queue/entry-shape.mjs +5 -0
- package/src/tui/session/session-flow/queue/take-entries.mjs +5 -2
- package/src/tui/session/tool-approval-delegation.mjs +64 -0
- package/src/tui/session/tool-approval-request.mjs +39 -0
- package/src/tui/session/tool-approval-settle.mjs +62 -0
- package/src/tui/session/tool-approval.mjs +69 -115
- package/src/tui/session/tui-steering-persist.mjs +8 -1
- package/src/tui/session/turn-stream.mjs +19 -2
- package/src/tui/session/turn.mjs +1 -0
- package/src/vendor/statusline/bin/statusline-route.mjs +1 -219
- package/src/vendor/statusline/src/gateway/route-meta.mjs +35 -13
- package/scripts/code-graph-description-contract.mjs +0 -188
- package/src/runtime/channels/lib/owned-runtime/ownership-refresh.mjs +0 -36
|
@@ -27,13 +27,67 @@ export const TASK_WAIT_TIMEOUT_DEFAULT_MS = TASK_WAIT_DEFAULT_MS;
|
|
|
27
27
|
export const TASK_WAIT_TIMEOUT_MIN_MS = TASK_WAIT_MIN_MS;
|
|
28
28
|
export const TASK_WAIT_TIMEOUT_MAX_MS = TASK_WAIT_MAX_MS;
|
|
29
29
|
const _holdS = budgetSeconds(SHELL_HOLD_DEFAULT_MS);
|
|
30
|
-
const _shellSyntaxCheat =
|
|
31
|
-
process.platform === 'win32'
|
|
32
|
-
? " PowerShell: ; between independent commands, if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } between dependent ones; no heredoc, </dev/null or | head/tail/wc (Select-Object -First/-Last); multiline python/node via @'…'@ | python - (node -); 2>$null; /c/→C:\\; $PID is reserved."
|
|
33
|
-
: ' Bash: chain dependent commands with &&; multiline input via a quoted heredoc delimiter, not extra quoting/escape layers.';
|
|
34
30
|
// Process-stable switch used to describe foreground-only execution accurately.
|
|
35
31
|
const _shellBackgroundDisabled = envFlag('MIXDOG_SHELL_DISABLE_BACKGROUND_TASKS');
|
|
36
32
|
|
|
33
|
+
// Shell kinds the description is written for: 'powershell' or 'posix' (bash,
|
|
34
|
+
// zsh, sh, Git Bash, WSL), the `shellType` of the runtime's resolved shell.
|
|
35
|
+
// BUILTIN_TOOLS is static, so it carries the platform's default kind; a
|
|
36
|
+
// session whose configured system shell is the other kind gets the matching
|
|
37
|
+
// text through bindShellKind where the session tool schema is assembled.
|
|
38
|
+
const _defaultShellKind = process.platform === 'win32' ? 'powershell' : 'posix';
|
|
39
|
+
|
|
40
|
+
// The kind's banned commands → replacement tool. `edit/apply_patch` is
|
|
41
|
+
// rewritten per model by the edit-dialect surface filter.
|
|
42
|
+
export function buildShellRoutingRules(kind = _defaultShellKind) {
|
|
43
|
+
const pairs =
|
|
44
|
+
kind === 'powershell'
|
|
45
|
+
? 'Get-Content/gc/cat/type→read; Get-ChildItem/dir/ls→list/glob/find; Select-String/findstr→grep; Set-Content/Add-Content/Out-File/`>`→edit/apply_patch'
|
|
46
|
+
: 'cat/head/tail/less→read; ls/tree→list; find→glob/find; grep/rg/ag→grep; file authoring via sed/awk/echo >/tee/heredoc→edit/apply_patch';
|
|
47
|
+
return `Never use shell commands/scripts for work other tools cover: ${pairs}; git→git (-C <dir> for other folders); code structure→code_graph.`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function buildShellDescription(kind) {
|
|
51
|
+
const run = _shellBackgroundDisabled
|
|
52
|
+
? 'Commands run in the foreground until completion.'
|
|
53
|
+
: `Holds ${_holdS}s; then, or on input/task notification, backgrounds with task_id (command keeps running). Completion is automatic; never poll, and never sleep/poll-loop inside the shell (background:true or wait_ms instead; for an external job, background a command that exits when it ends). Result needed now: task wait. Known-long finite run: wait_ms up to ${SHELL_HOLD_MAX_MS}. Independent long runs, servers/watchers: background:true; cancel them when done.`;
|
|
54
|
+
return `Run programs, builds, tests and computation. ${buildShellRoutingRules(kind)} ${run}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function buildShellCommandDescription(kind) {
|
|
58
|
+
const cheat =
|
|
59
|
+
kind === 'powershell'
|
|
60
|
+
? " PowerShell: ; between independent commands, if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } between dependent ones; no Bash heredocs or </dev/null; Select-Object shapes program output, not file reads; multiline python/node via @'…'@ | python - (node -); 2>$null; /c/→C:\\; $PID is reserved."
|
|
61
|
+
: ' Bash: chain dependent commands with &&; multiline input via a quoted heredoc delimiter, not extra quoting/escape layers.';
|
|
62
|
+
return `Command.${cheat}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// One bound copy per (tool, kind), so a session's shell tool keeps its identity
|
|
66
|
+
// and bytes from turn to turn.
|
|
67
|
+
const _boundShellTools = new WeakMap();
|
|
68
|
+
|
|
69
|
+
/** The shell tool with the text of `kind`, when that differs from the static default. */
|
|
70
|
+
export function bindShellKind(tool, kind) {
|
|
71
|
+
if (tool?.name !== 'shell' || kind === _defaultShellKind || (kind !== 'powershell' && kind !== 'posix')) return tool;
|
|
72
|
+
const cached = _boundShellTools.get(tool)?.get(kind);
|
|
73
|
+
if (cached) return cached;
|
|
74
|
+
const properties = tool.inputSchema?.properties || {};
|
|
75
|
+
const bound = {
|
|
76
|
+
...tool,
|
|
77
|
+
description: buildShellDescription(kind),
|
|
78
|
+
inputSchema: {
|
|
79
|
+
...tool.inputSchema,
|
|
80
|
+
properties: {
|
|
81
|
+
...properties,
|
|
82
|
+
command: { ...properties.command, description: buildShellCommandDescription(kind) },
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
if (!_boundShellTools.has(tool)) _boundShellTools.set(tool, new Map());
|
|
87
|
+
_boundShellTools.get(tool).set(kind, bound);
|
|
88
|
+
return bound;
|
|
89
|
+
}
|
|
90
|
+
|
|
37
91
|
export const BUILTIN_TOOLS = [
|
|
38
92
|
{
|
|
39
93
|
name: 'read',
|
|
@@ -151,11 +205,15 @@ export const BUILTIN_TOOLS = [
|
|
|
151
205
|
openWorldHint: true,
|
|
152
206
|
compressible: true,
|
|
153
207
|
},
|
|
154
|
-
description:
|
|
208
|
+
description: buildShellDescription(_defaultShellKind),
|
|
155
209
|
inputSchema: {
|
|
156
210
|
type: 'object',
|
|
157
211
|
properties: {
|
|
158
|
-
command: { type: 'string', description:
|
|
212
|
+
command: { type: 'string', description: buildShellCommandDescription(_defaultShellKind) },
|
|
213
|
+
cwd: {
|
|
214
|
+
type: 'string',
|
|
215
|
+
description: 'Working directory; default the Project. Use it instead of cd/Set-Location.',
|
|
216
|
+
},
|
|
159
217
|
timeout_ms: {
|
|
160
218
|
type: 'integer',
|
|
161
219
|
minimum: 0,
|
|
@@ -165,7 +223,7 @@ export const BUILTIN_TOOLS = [
|
|
|
165
223
|
type: 'integer',
|
|
166
224
|
minimum: SHELL_HOLD_MIN_MS,
|
|
167
225
|
maximum: SHELL_HOLD_MAX_MS,
|
|
168
|
-
description: `Hold in ms before a still-running command moves to the background (default ${SHELL_HOLD_DEFAULT_MS}). Usually omit.`,
|
|
226
|
+
description: `Hold in ms before a still-running command moves to the background (default ${SHELL_HOLD_DEFAULT_MS}). Usually omit; for a long finite command needed next, set up to ${SHELL_HOLD_MAX_MS}.`,
|
|
169
227
|
},
|
|
170
228
|
background: {
|
|
171
229
|
type: 'boolean',
|
|
@@ -17,6 +17,10 @@ export function setConfiguredShell(value = '') {
|
|
|
17
17
|
_resolvedShellByKind.clear();
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
export function getConfiguredShell() {
|
|
21
|
+
return _configuredShell || '';
|
|
22
|
+
}
|
|
23
|
+
|
|
20
24
|
function shellTypeFor(shell) {
|
|
21
25
|
const stem = basename(String(shell || ''))
|
|
22
26
|
.toLowerCase()
|
|
@@ -49,7 +53,9 @@ function allExistingPathsFromPath(commandName) {
|
|
|
49
53
|
let cwd = '';
|
|
50
54
|
try {
|
|
51
55
|
cwd = process.cwd();
|
|
52
|
-
} catch {
|
|
56
|
+
} catch {
|
|
57
|
+
// the working directory may have been removed; search PATH entries only
|
|
58
|
+
}
|
|
53
59
|
const entries = [cwd, ...rawPath.split(delimiter)]
|
|
54
60
|
.map((entry) => entry.trim().replace(/^"(.*)"$/, '$1'))
|
|
55
61
|
.filter(Boolean);
|
|
@@ -112,7 +112,9 @@ function _abortableDelay(ms, signal) {
|
|
|
112
112
|
if (onAbort && signal) {
|
|
113
113
|
try {
|
|
114
114
|
signal.removeEventListener('abort', onAbort);
|
|
115
|
-
} catch {
|
|
115
|
+
} catch {
|
|
116
|
+
// the abort listener may already be gone
|
|
117
|
+
}
|
|
116
118
|
}
|
|
117
119
|
resolveDelay();
|
|
118
120
|
}, ms);
|
|
@@ -173,7 +175,9 @@ async function acquireShellLeaseBounded(
|
|
|
173
175
|
} catch {
|
|
174
176
|
try {
|
|
175
177
|
ctl.abort();
|
|
176
|
-
} catch {
|
|
178
|
+
} catch {
|
|
179
|
+
// the controller may already be aborted
|
|
180
|
+
}
|
|
177
181
|
}
|
|
178
182
|
};
|
|
179
183
|
if (abortSignal) {
|
|
@@ -184,7 +188,9 @@ async function acquireShellLeaseBounded(
|
|
|
184
188
|
const deadline = setTimeout(() => {
|
|
185
189
|
try {
|
|
186
190
|
ctl.abort(_admissionSaturationError(admission, waitMs, ownerKey));
|
|
187
|
-
} catch {
|
|
191
|
+
} catch {
|
|
192
|
+
// the controller may already be aborted
|
|
193
|
+
}
|
|
188
194
|
}, waitMs);
|
|
189
195
|
if (deadline.unref) deadline.unref();
|
|
190
196
|
try {
|
|
@@ -212,7 +218,9 @@ async function acquireShellLeaseBounded(
|
|
|
212
218
|
if (abortSignal) {
|
|
213
219
|
try {
|
|
214
220
|
abortSignal.removeEventListener('abort', onAbort);
|
|
215
|
-
} catch {
|
|
221
|
+
} catch {
|
|
222
|
+
// the abort listener may already be gone
|
|
223
|
+
}
|
|
216
224
|
}
|
|
217
225
|
}
|
|
218
226
|
}
|
|
@@ -238,7 +246,7 @@ async function admitAndSpawn(run, params, { admissionWaitMs, onSaturated }) {
|
|
|
238
246
|
run.resourceLease = await acquireShellLeaseBounded(admission, {
|
|
239
247
|
abortSignal,
|
|
240
248
|
waitMs: admissionWaitMs,
|
|
241
|
-
label: `[task_id: ${run.foregroundRecordId}] ${String(command
|
|
249
|
+
label: `[task_id: ${run.foregroundRecordId}] ${String(params.displayCommand ?? command ?? '').slice(0, 120)}`,
|
|
242
250
|
ownerKey: ownerSessionId,
|
|
243
251
|
});
|
|
244
252
|
const policyErr = await _execPolicyBlockMessage(command, elevationApproved === true);
|
|
@@ -295,11 +303,11 @@ function queueSaturatedAsTask(run, saturation, spec) {
|
|
|
295
303
|
taskId,
|
|
296
304
|
surface: 'shell',
|
|
297
305
|
operation: 'shell',
|
|
298
|
-
label: String(spec.command
|
|
306
|
+
label: String(spec.displayCommand ?? spec.command ?? '')
|
|
299
307
|
.replace(/\s+/g, ' ')
|
|
300
308
|
.slice(0, 120),
|
|
301
309
|
context: { callerSessionId: spec.ownerSessionId, clientHostPid: spec.clientHostPid },
|
|
302
|
-
input: { command: spec.command, cwd: spec.cwd },
|
|
310
|
+
input: { command: spec.displayCommand ?? spec.command, cwd: spec.cwd },
|
|
303
311
|
meta: { task_id: taskId, stdout: null, stderr: null, cwd: spec.cwd, timeoutMs: spec.backgroundDeadlineMs || 0 },
|
|
304
312
|
resultType: 'shell_task_result',
|
|
305
313
|
// While the command runs in the foreground, its abort handler snapshots
|
|
@@ -311,7 +319,9 @@ function queueSaturatedAsTask(run, saturation, spec) {
|
|
|
311
319
|
if (!promotedJobId) return;
|
|
312
320
|
try {
|
|
313
321
|
killShellJob(promotedJobId);
|
|
314
|
-
} catch {
|
|
322
|
+
} catch {
|
|
323
|
+
// the job may already have exited
|
|
324
|
+
}
|
|
315
325
|
},
|
|
316
326
|
run: async () => {
|
|
317
327
|
const result = await execShellCommand({
|
|
@@ -332,7 +342,9 @@ function queueSaturatedAsTask(run, saturation, spec) {
|
|
|
332
342
|
if (queuedAbort.signal.aborted) {
|
|
333
343
|
try {
|
|
334
344
|
killShellJob(promotedJobId);
|
|
335
|
-
} catch {
|
|
345
|
+
} catch {
|
|
346
|
+
// the promoted job may already have exited
|
|
347
|
+
}
|
|
336
348
|
}
|
|
337
349
|
return settleQueuedPromotion(promotedJobId);
|
|
338
350
|
}
|
|
@@ -375,7 +387,9 @@ function settleSetupFailure(run, err) {
|
|
|
375
387
|
clearForegroundTimers(run);
|
|
376
388
|
try {
|
|
377
389
|
treeKill(run.child);
|
|
378
|
-
} catch {
|
|
390
|
+
} catch {
|
|
391
|
+
// the child may already have exited
|
|
392
|
+
}
|
|
379
393
|
void releaseResourceLease(run);
|
|
380
394
|
run.resolveResult(failedResult(run, String(err?.message || err), 'spawn failed'));
|
|
381
395
|
}
|
|
@@ -451,7 +465,9 @@ function armProgressTimers(run, { onProgress, onOutputTail }) {
|
|
|
451
465
|
const secs = Math.round(elapsedSinceStart(run) / 1000);
|
|
452
466
|
try {
|
|
453
467
|
onProgress(`running ${secs}s`);
|
|
454
|
-
} catch {
|
|
468
|
+
} catch {
|
|
469
|
+
// the progress sink is advisory
|
|
470
|
+
}
|
|
455
471
|
}, 2000);
|
|
456
472
|
if (run.progressTimer.unref) run.progressTimer.unref();
|
|
457
473
|
}
|
|
@@ -549,7 +565,9 @@ function armDeadlines(
|
|
|
549
565
|
};
|
|
550
566
|
try {
|
|
551
567
|
abortSignal.addEventListener('abort', run.abortHandler, { once: true });
|
|
552
|
-
} catch {
|
|
568
|
+
} catch {
|
|
569
|
+
// caller cancellation registration is best-effort
|
|
570
|
+
}
|
|
553
571
|
}
|
|
554
572
|
}
|
|
555
573
|
|
|
@@ -588,6 +606,10 @@ export function execShellCommand(options) {
|
|
|
588
606
|
// hands the script to the shell out-of-band — see the POSIX env+eval path in
|
|
589
607
|
// bash-tool.mjs, which keeps the command body out of the child's argv.
|
|
590
608
|
execScript = null,
|
|
609
|
+
// What the agent passed, when `command` carries execution wrappers (the
|
|
610
|
+
// PowerShell UTF-8 prefix, the filter-tee rewrite). Job labels and
|
|
611
|
+
// records show this text; only the spawn runs `command`.
|
|
612
|
+
displayCommand = null,
|
|
591
613
|
// The user approved this call's elevation 'ask' decision (runtime-set).
|
|
592
614
|
elevationApproved = false,
|
|
593
615
|
} = options;
|
|
@@ -612,6 +634,7 @@ export function execShellCommand(options) {
|
|
|
612
634
|
abortSignal,
|
|
613
635
|
directArgv,
|
|
614
636
|
execScript,
|
|
637
|
+
displayCommand,
|
|
615
638
|
elevationApproved,
|
|
616
639
|
ownerSessionId,
|
|
617
640
|
clientHostPid,
|
|
@@ -635,7 +658,7 @@ export function execShellCommand(options) {
|
|
|
635
658
|
attachChildExit(run, releasePromotedCapture);
|
|
636
659
|
const fireAutoBackground = createAutoBackground({
|
|
637
660
|
run,
|
|
638
|
-
command,
|
|
661
|
+
command: displayCommand ?? command,
|
|
639
662
|
cwd,
|
|
640
663
|
clientHostPid,
|
|
641
664
|
ownerSessionId,
|
|
@@ -101,7 +101,7 @@ const CONTRACT_ROWS = [
|
|
|
101
101
|
// the host.
|
|
102
102
|
contract(
|
|
103
103
|
'fill',
|
|
104
|
-
[...POST_ACTION_SNAPSHOT, 'ref', 'target', 'text', 'checked', 'fields', 'submit', 'savedAccount'],
|
|
104
|
+
[...POST_ACTION_SNAPSHOT, 'ref', 'target', 'text', 'checked', 'fields', 'submit', 'savedAccount', 'sensitive'],
|
|
105
105
|
[['ref', 'text'], ['target', 'text'], ['ref', 'checked'], ['target', 'checked'], ['fields'], ['savedAccount']]
|
|
106
106
|
),
|
|
107
107
|
contract(
|
|
@@ -257,7 +257,6 @@ const SEQUENCE_STEP_STRING_LIMITS = [
|
|
|
257
257
|
['textGone', 10_000],
|
|
258
258
|
['url', 8_192],
|
|
259
259
|
['key', 100],
|
|
260
|
-
['savedAccount', 320],
|
|
261
260
|
];
|
|
262
261
|
|
|
263
262
|
// The validation error of one sequence step, '' when it is well-formed.
|
|
@@ -688,10 +687,22 @@ function ruleOperationError(action, input) {
|
|
|
688
687
|
return '';
|
|
689
688
|
}
|
|
690
689
|
|
|
690
|
+
/** A sensitive fill's text is kept out of every stored transcript (sensitive-values.mjs)
|
|
691
|
+
* at any length; an empty one has nothing to protect and is refused. */
|
|
692
|
+
function sensitiveFillError(action, input) {
|
|
693
|
+
if (action !== 'fill' || input.sensitive !== true) return '';
|
|
694
|
+
const fields = Array.isArray(input.fields) ? input.fields : [];
|
|
695
|
+
const values = [input.text, ...fields.flatMap((field) => [field?.text, field?.value])];
|
|
696
|
+
return values.some((value) => typeof value === 'string' && value.length === 0)
|
|
697
|
+
? 'browser action "fill" input.sensitive text must not be empty'
|
|
698
|
+
: '';
|
|
699
|
+
}
|
|
700
|
+
|
|
691
701
|
// Action-specific rules, in the order they are reported.
|
|
692
702
|
const ACTION_RULES = [
|
|
693
703
|
navigateError,
|
|
694
704
|
fillFieldsError,
|
|
705
|
+
sensitiveFillError,
|
|
695
706
|
scrollError,
|
|
696
707
|
screenshotOptionsError,
|
|
697
708
|
snapshotTargetError,
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { BROWSER_OBSERVATION_ACTIONS, validateBrowserToolArgs } from './action-schema.mjs';
|
|
11
11
|
import { bridgeDiscoveryChanged, readBridgeDiscovery, readBridgeDiscoveryDetail } from '../bridge-discovery.mjs';
|
|
12
|
+
import { mailLinkFor } from './mail-links.mjs';
|
|
12
13
|
import { traceBrowserTiming } from './timing.mjs';
|
|
13
14
|
import { base64ByteLength, inlineFileKind } from '../shared/inline-file-kind.mjs';
|
|
14
15
|
import { MAX_IMAGE_BYTES, SUPPORTED_IMAGE_MIME_TYPES } from '../attachments/limits.mjs';
|
|
@@ -28,6 +29,39 @@ const MAX_IMAGE_BASE64_CHARS = Math.ceil((MAX_IMAGE_BYTES * 4) / 3) + 4;
|
|
|
28
29
|
const MAX_FILE_BASE64_CHARS = Math.ceil((8 * 1024 * 1024 * 4) / 3) + 4;
|
|
29
30
|
const RETRYABLE_ACTIONS = new Set(BROWSER_OBSERVATION_ACTIONS);
|
|
30
31
|
const browserTurns = new Map();
|
|
32
|
+
/** Sessions that have issued a browser command; only they have turn
|
|
33
|
+
* boundaries worth telling the desktop. */
|
|
34
|
+
const browserSessions = new Set();
|
|
35
|
+
|
|
36
|
+
/** A turn's start, sent before it reasons or runs any tool: the desktop keeps
|
|
37
|
+
* the session's pages out of reclaim while a turn is live, even one that never
|
|
38
|
+
* issues a browser command. Remembers the bridge so the turn's end reaches it.
|
|
39
|
+
* A session that never used the browser has nothing to tell the desktop (its
|
|
40
|
+
* first command marks it live there). */
|
|
41
|
+
export async function beginBrowserTurn(sessionId, turnId) {
|
|
42
|
+
if (!browserSessions.has(sessionId)) return;
|
|
43
|
+
const discovery = readBridgeDiscovery(DISCOVERY_FILE);
|
|
44
|
+
if (!discovery || !(turnId > 0)) return;
|
|
45
|
+
rememberBrowserTurn(sessionId, turnId, discovery);
|
|
46
|
+
const result = await requestBridgeWithin(
|
|
47
|
+
discovery,
|
|
48
|
+
JSON.stringify({ action: 'begin_turn', session_id: sessionId, turn_id: turnId }),
|
|
49
|
+
undefined,
|
|
50
|
+
{ sessionId, turnId, action: 'begin_turn' }
|
|
51
|
+
);
|
|
52
|
+
if (result.status !== 200 || result.body?.ok === false || result.body?.error) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`Browser turn start failed (HTTP ${result.status}): ${result.body?.error || 'bridge rejected start'}`
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** The session is gone for good (deleted): it no longer has turn boundaries to
|
|
60
|
+
* tell the desktop, and no turn of it will finish. */
|
|
61
|
+
export function forgetBrowserSession(sessionId) {
|
|
62
|
+
browserSessions.delete(sessionId);
|
|
63
|
+
browserTurns.delete(sessionId);
|
|
64
|
+
}
|
|
31
65
|
|
|
32
66
|
/** Terminal lifecycle cleanup uses the exact bridge that owned this turn.
|
|
33
67
|
* It is never retried after dispatch and never inherits the cancelled tool signal. */
|
|
@@ -57,13 +91,16 @@ export async function finishBrowserTurn(sessionId, turnId, { aborted = false } =
|
|
|
57
91
|
}
|
|
58
92
|
}
|
|
59
93
|
|
|
94
|
+
const SECRET_FILL_OUTCOMES = new Set(['entered', 'no_field', 'page_changed', 'blocked', 'expired']);
|
|
95
|
+
|
|
60
96
|
/** Runtime-only: fill the secret the user handed the desktop for `requestId`
|
|
61
|
-
* into the page
|
|
62
|
-
* fill. The value never passes through here; the
|
|
63
|
-
* '
|
|
97
|
+
* into the field of the page it was bound to (browserHandoffPage), through
|
|
98
|
+
* the host's isolated-world fill. The value never passes through here; the
|
|
99
|
+
* host answers only the outcome ('entered', 'no_field', 'page_changed',
|
|
100
|
+
* 'blocked', 'expired'); no desktop or an unknown answer is 'blocked'. */
|
|
64
101
|
export async function browserSecretInput(sessionId, requestId, { signal } = {}) {
|
|
65
102
|
const discovery = readBridgeDiscovery(DISCOVERY_FILE);
|
|
66
|
-
if (!discovery || !sessionId || !requestId) return '
|
|
103
|
+
if (!discovery || !sessionId || !requestId) return 'blocked';
|
|
67
104
|
try {
|
|
68
105
|
const result = await requestBridgeWithin(
|
|
69
106
|
discovery,
|
|
@@ -71,11 +108,33 @@ export async function browserSecretInput(sessionId, requestId, { signal } = {})
|
|
|
71
108
|
signal,
|
|
72
109
|
{ sessionId, action: 'secret_input' }
|
|
73
110
|
);
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
111
|
+
const text = result.status === 200 && result.body?.ok !== false ? result.body?.value?.text : '';
|
|
112
|
+
return SECRET_FILL_OUTCOMES.has(text) ? text : 'blocked';
|
|
113
|
+
} catch {
|
|
114
|
+
return 'blocked';
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Runtime-only facts about a session's page for an ask_user handoff, with no
|
|
119
|
+
* value in either direction: `secret_bind` binds `request_id` to the page
|
|
120
|
+
* (`tab`, else the agent's page) and says its origin and field kind;
|
|
121
|
+
* `login_accounts` lists the masked saved logins of `url`'s origin;
|
|
122
|
+
* `page_url` says where the page is now. null when the desktop is absent or
|
|
123
|
+
* refuses. */
|
|
124
|
+
export async function browserHandoffPage(sessionId, action, fields = {}, { signal } = {}) {
|
|
125
|
+
const discovery = readBridgeDiscovery(DISCOVERY_FILE);
|
|
126
|
+
if (!discovery || !sessionId) return null;
|
|
127
|
+
try {
|
|
128
|
+
const result = await requestBridgeWithin(
|
|
129
|
+
discovery,
|
|
130
|
+
JSON.stringify({ ...fields, action, session_id: String(sessionId) }),
|
|
131
|
+
signal,
|
|
132
|
+
{ sessionId, action }
|
|
133
|
+
);
|
|
134
|
+
const value = result.status === 200 && result.body?.ok !== false ? result.body?.value : null;
|
|
135
|
+
return value && typeof value === 'object' ? value : null;
|
|
77
136
|
} catch {
|
|
78
|
-
return
|
|
137
|
+
return null;
|
|
79
138
|
}
|
|
80
139
|
}
|
|
81
140
|
|
|
@@ -271,10 +330,15 @@ export async function executeBrowserTool(args, options = {}) {
|
|
|
271
330
|
if (!validated.ok) return browserToolError(validated.error);
|
|
272
331
|
const sessionId = String(options.sessionId || '').trim();
|
|
273
332
|
if (!sessionId) return browserToolError('browser session context is unavailable');
|
|
333
|
+
const mailLink = validated.action === 'navigate' ? mailLinkFor(sessionId, validated.input.url) : null;
|
|
274
334
|
const payload = {
|
|
275
335
|
action: validated.action,
|
|
276
336
|
...validated.input,
|
|
277
337
|
session_id: sessionId,
|
|
338
|
+
// Runtime-set, never from the model's arguments: this exact URL is a link
|
|
339
|
+
// of a mail the runtime read for this session (mail-links.mjs), which the
|
|
340
|
+
// desktop may open although it carries a token.
|
|
341
|
+
...(mailLink ? { mail_link: mailLink } : {}),
|
|
278
342
|
// Set by the runtime from the session, never from the model's arguments: a
|
|
279
343
|
// schedule/webhook run (or work it started) whose saved routine is the
|
|
280
344
|
// user's grant for submits, posts and uploads (human-gate/run-policy.mjs).
|
|
@@ -291,6 +355,7 @@ export async function executeBrowserTool(args, options = {}) {
|
|
|
291
355
|
}
|
|
292
356
|
let discovery = readBridgeDiscovery(DISCOVERY_FILE);
|
|
293
357
|
if (!discovery) return browserToolError(unavailableMessage());
|
|
358
|
+
browserSessions.add(sessionId);
|
|
294
359
|
rememberBrowserTurn(sessionId, payload.turn_id, discovery);
|
|
295
360
|
// An observation can be reported as a plain failure; a state-changing action
|
|
296
361
|
// that may already have run must say so instead.
|
|
@@ -56,7 +56,7 @@ export const BROWSER_INPUT_FIELDS = {
|
|
|
56
56
|
type: 'string',
|
|
57
57
|
enum: ['semantic', 'visual', 'both'],
|
|
58
58
|
description:
|
|
59
|
-
'snapshot only: semantic refs (default), visual
|
|
59
|
+
'snapshot only: semantic refs (default), visual, or both. Coordinates need both; fullPage and ref/target crops need visual. navigate: includeScreenshot.',
|
|
60
60
|
},
|
|
61
61
|
fullPage: { type: 'boolean', description: 'Full-document screenshot; inspection-only.' },
|
|
62
62
|
format: {
|
|
@@ -132,7 +132,7 @@ export const BROWSER_INPUT_FIELDS = {
|
|
|
132
132
|
items: { type: 'string', maxLength: 60 },
|
|
133
133
|
minItems: 1,
|
|
134
134
|
maxItems: 12,
|
|
135
|
-
description: 'extract only: attribute names per match
|
|
135
|
+
description: 'extract only: attribute names per match (text and name always come).',
|
|
136
136
|
},
|
|
137
137
|
operation: {
|
|
138
138
|
type: 'string',
|
|
@@ -233,8 +233,7 @@ export const BROWSER_INPUT_FIELDS = {
|
|
|
233
233
|
downloadId: {
|
|
234
234
|
type: 'string',
|
|
235
235
|
maxLength: 100,
|
|
236
|
-
description:
|
|
237
|
-
'downloads only: d1/d2 ID; omitted, wait pins the newest (or next) download and attach takes the newest completed file.',
|
|
236
|
+
description: 'downloads only: d1/d2 ID; omitted: wait pins the newest (or next), attach the newest completed.',
|
|
238
237
|
},
|
|
239
238
|
wait: { type: 'boolean', description: 'downloads only: wait up to timeoutMs for completion.' },
|
|
240
239
|
attach: { type: 'boolean', description: 'downloads only: attach the completed file (≤ 8 MiB) to the result.' },
|
|
@@ -245,11 +244,11 @@ export const BROWSER_INPUT_FIELDS = {
|
|
|
245
244
|
},
|
|
246
245
|
textGone: { type: 'string', maxLength: 10000, description: 'wait: page-text substring that must disappear.' },
|
|
247
246
|
submit: { type: 'boolean', description: 'fill or type: press Enter afterward.' },
|
|
247
|
+
sensitive: { type: 'boolean', description: 'fill: code/secret text; redacted in output and transcript.' },
|
|
248
248
|
savedAccount: {
|
|
249
249
|
type: 'string',
|
|
250
|
-
maxLength: 320,
|
|
251
250
|
description:
|
|
252
|
-
'fill: account or masked label
|
|
251
|
+
'fill: saved login (account or masked label) of this HTTPS site; fills the sign-in form, password stays on the host. No ref/target/text.',
|
|
253
252
|
},
|
|
254
253
|
key: {
|
|
255
254
|
type: 'string',
|
|
@@ -326,7 +325,7 @@ export const BROWSER_INPUT_FIELDS = {
|
|
|
326
325
|
checked: { type: 'boolean' },
|
|
327
326
|
key: { type: 'string', maxLength: 100 },
|
|
328
327
|
submit: { type: 'boolean' },
|
|
329
|
-
savedAccount: { type: 'string',
|
|
328
|
+
savedAccount: { type: 'string', description: 'fill: as input.savedAccount.' },
|
|
330
329
|
dx: { type: 'integer' },
|
|
331
330
|
dy: { type: 'integer' },
|
|
332
331
|
textGone: { type: 'string', maxLength: 10000 },
|
|
@@ -399,12 +398,12 @@ export const BROWSER_INPUT_FIELDS = {
|
|
|
399
398
|
type: 'string',
|
|
400
399
|
maxLength: 64,
|
|
401
400
|
description:
|
|
402
|
-
'
|
|
401
|
+
'Page ID p1/p2… from list_tabs (or v1/v2), or a background page name (background:true makes one; popups get one).',
|
|
403
402
|
},
|
|
404
403
|
background: {
|
|
405
404
|
type: 'boolean',
|
|
406
405
|
description:
|
|
407
|
-
'true
|
|
406
|
+
'true: create/target a hidden support page; false: reveal it until task end; omitted never promotes a named one. open without true keeps it for handoff.',
|
|
408
407
|
},
|
|
409
408
|
},
|
|
410
409
|
};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// Links in mails the runtime itself read for a session through Mixdog's own
|
|
2
|
+
// Gmail connector (connectors/gmail-app-password.mjs): the one way a link
|
|
3
|
+
// carrying a token (a sign-up or sign-in confirmation) becomes navigable in
|
|
4
|
+
// Browser Use, whose desktop host refuses every other agent URL with one
|
|
5
|
+
// (apps/desktop/src/main/browser/url-policy.ts). A link is recorded from the
|
|
6
|
+
// read_email result the runtime received (session/loop/tool-exec/dispatch.mjs),
|
|
7
|
+
// never from anything the model passes, and is offered only for that exact
|
|
8
|
+
// link, in the same session, for a short time; the desktop checks it again.
|
|
9
|
+
// Only a mail whose one From mailbox Gmail authenticated counts, and only the
|
|
10
|
+
// link targets the connector read from it (its `links`: HTML hyperlinks and
|
|
11
|
+
// the URIs of its text part, exactly as written).
|
|
12
|
+
import { getDomain } from 'tldts';
|
|
13
|
+
|
|
14
|
+
/** How long after the read a mail's link stays navigable: the lifetime the
|
|
15
|
+
* desktop gives a sign-in flow, a 2-step check included
|
|
16
|
+
* (apps/desktop/src/main/browser/linked-sign-in.ts FLOW_TTL_MS), which its
|
|
17
|
+
* url-policy.ts MAIL_LINK_WINDOW_MS takes and enforces again. */
|
|
18
|
+
export const MAIL_LINK_WINDOW_MS = 30 * 60_000;
|
|
19
|
+
|
|
20
|
+
/** sessionId → [{ url, from_domain, read_at }], oldest first. */
|
|
21
|
+
const linksBySession = new Map();
|
|
22
|
+
|
|
23
|
+
/** `domain` as a URL host spells it (IDNs in punycode), '' when it is none. */
|
|
24
|
+
function hostOf(domain) {
|
|
25
|
+
const text = String(domain || '').toLowerCase();
|
|
26
|
+
if (!/^[\p{L}\p{N}-]+(?:\.[\p{L}\p{N}-]+)+$/u.test(text)) return '';
|
|
27
|
+
try {
|
|
28
|
+
return new URL(`https://${text}/`).hostname;
|
|
29
|
+
} catch {
|
|
30
|
+
return '';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** The Organizational Domain (RFC 7489 §3.2: by the public suffix list; its
|
|
35
|
+
* private suffixes included, as the desktop's url-policy.ts reads it, so
|
|
36
|
+
* `a.github.io` is not `b.github.io`); '' for none. */
|
|
37
|
+
const siteOf = (host) => (host ? getDomain(host, { allowPrivateDomains: true }) || '' : '');
|
|
38
|
+
|
|
39
|
+
/** The From domain of read_email header `head` (connectors/gmail-app-password.mjs)
|
|
40
|
+
* when From names exactly one mailbox (with several, RFC 7489 §6.6.1 checks
|
|
41
|
+
* each author domain, and one verdict cannot vouch for the one a link names)
|
|
42
|
+
* and Gmail authenticated it: DMARC passed for that very
|
|
43
|
+
* domain, or a passing DKIM signature's d= is aligned with it in relaxed
|
|
44
|
+
* mode — the same Organizational Domain (RFC 7489 §3.1.1). '' otherwise. */
|
|
45
|
+
function authenticatedSender(head) {
|
|
46
|
+
const from = Array.isArray(head?.fromAddresses) ? head.fromAddresses : [];
|
|
47
|
+
const address = from.length === 1 ? String(from[0]?.address || '') : '';
|
|
48
|
+
const host = address.includes('@') ? hostOf(address.slice(address.lastIndexOf('@') + 1)) : '';
|
|
49
|
+
if (!host) return '';
|
|
50
|
+
const { dmarc, dkim } = head.authentication || {};
|
|
51
|
+
if (dmarc?.result === 'pass' && hostOf(dmarc.from) === host) return host;
|
|
52
|
+
const site = siteOf(host);
|
|
53
|
+
const signed =
|
|
54
|
+
Array.isArray(dkim) && dkim.some((sig) => sig?.result === 'pass' && siteOf(hostOf(sig.domain)) === site);
|
|
55
|
+
return site && signed ? host : '';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** The normalized https URL `raw` is, or ''. */
|
|
59
|
+
function httpsHref(raw) {
|
|
60
|
+
try {
|
|
61
|
+
const url = new URL(raw);
|
|
62
|
+
return url.protocol === 'https:' && !url.username && !url.password ? url.href : '';
|
|
63
|
+
} catch {
|
|
64
|
+
return '';
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function pruneExpired(now) {
|
|
69
|
+
for (const [sessionId, links] of linksBySession) {
|
|
70
|
+
const live = links.filter((link) => now - link.read_at <= MAIL_LINK_WINDOW_MS);
|
|
71
|
+
if (live.length) linksBySession.set(sessionId, live);
|
|
72
|
+
else linksBySession.delete(sessionId);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Records the https link targets of read_email result `result` (its header
|
|
77
|
+
* line — `links` among its fields — a blank line, the body) for `sessionId`,
|
|
78
|
+
* with the authenticated sender's domain. Links leave only by the window. */
|
|
79
|
+
export function recordMailLinks(sessionId, result, now = Date.now()) {
|
|
80
|
+
pruneExpired(now);
|
|
81
|
+
const owner = String(sessionId || '');
|
|
82
|
+
if (!owner || typeof result !== 'string') return;
|
|
83
|
+
const split = result.indexOf('\n\n');
|
|
84
|
+
if (split < 0) return;
|
|
85
|
+
let head;
|
|
86
|
+
try {
|
|
87
|
+
head = JSON.parse(result.slice(0, split));
|
|
88
|
+
} catch {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const from_domain = authenticatedSender(head);
|
|
92
|
+
if (!from_domain || !Array.isArray(head.links)) return;
|
|
93
|
+
const found = new Set(head.links.map((link) => (typeof link === 'string' ? httpsHref(link) : '')).filter(Boolean));
|
|
94
|
+
if (!found.size) return;
|
|
95
|
+
const kept = (linksBySession.get(owner) || []).filter((link) => !found.has(link.url));
|
|
96
|
+
for (const url of found) kept.push({ url, from_domain, read_at: now });
|
|
97
|
+
linksBySession.set(owner, kept);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** `{ url, from_domain, read_at }` when `url` is exactly a link of a mail read
|
|
101
|
+
* for `sessionId` within the window, else null. */
|
|
102
|
+
export function mailLinkFor(sessionId, url, now = Date.now()) {
|
|
103
|
+
pruneExpired(now);
|
|
104
|
+
const href = httpsHref(String(url || '').trim());
|
|
105
|
+
const link = href ? linksBySession.get(String(sessionId || ''))?.find((entry) => entry.url === href) : null;
|
|
106
|
+
return link ? { ...link } : null;
|
|
107
|
+
}
|
|
@@ -22,7 +22,7 @@ import { BROWSER_INPUT_FIELDS } from './input-fields.mjs';
|
|
|
22
22
|
const BROWSER_DESCRIPTION =
|
|
23
23
|
"Drive Mixdog's in-app Chromium directly for rendered, signed-in or interactive pages; public page text uses web_fetch, external windows computer. " +
|
|
24
24
|
'Load the browser-use skill before first use. ' +
|
|
25
|
-
'Background by default;
|
|
25
|
+
'Background by default; open (deliverable/handoff) says shown, not on screen yet, or failed; say only that. ' +
|
|
26
26
|
'Task end cleans owned pages/restores temporary panels; user pages persist. ' +
|
|
27
27
|
'hide folds the panel; close_tab closes named background pages only. ' +
|
|
28
28
|
'Pages are session-local; sign-in/storage shared; never provide session_id. ' +
|