mixdog 1.3.1 → 1.3.2
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/README.md +8 -4
- package/package.json +2 -2
- package/scripts/native-release-status.mjs +238 -0
- package/src/defaults/shipped-history.json +5 -1
- package/src/defaults/skills/pptx/scripts/qc-pages.mjs +3 -2
- package/src/defaults/skills/setup/SKILL.md +23 -1
- package/src/defaults/skills/setup/references/actions.md +50 -3
- package/src/defaults/skills/setup/references/surfaces.md +23 -3
- package/src/defaults/skills/setup-guide/SKILL.md +7 -3
- package/src/defaults/skills/visualize/SKILL.md +9 -6
- package/src/defaults/skills/visualize/references/craft.md +8 -1
- package/src/help.mjs +1 -1
- package/src/lib/keychain-cjs.cjs +4 -1
- package/src/lib/rules-builder.cjs +14 -7
- package/src/rules/agent-mode/AGENT_MODE.md +8 -5
- package/src/rules/agent-mode/MIXY.md +6 -8
- package/src/rules/lead/LEAD.md +2 -2
- package/src/rules/shared/10-tool-workflow.md +4 -0
- package/src/rules/shared/20-research.md +2 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +5 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/session-prep.mjs +8 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +6 -1
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +114 -26
- package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +8 -0
- package/src/runtime/agent/orchestrator/agent-runtime/room-supervisor-completion.mjs +5 -4
- package/src/runtime/agent/orchestrator/agent-trace.mjs +3 -0
- package/src/runtime/agent/orchestrator/cache-break-trace.mjs +4 -0
- package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
- package/src/runtime/agent/orchestrator/config-storage.mjs +10 -3
- package/src/runtime/agent/orchestrator/config.mjs +11 -0
- package/src/runtime/agent/orchestrator/context/role-instructions.mjs +9 -2
- package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +97 -5
- package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +6 -10
- package/src/runtime/agent/orchestrator/mcp/client.mjs +7 -4
- package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +80 -31
- package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +67 -38
- package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +6 -2
- package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +27 -8
- package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +31 -4
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +11 -8
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -0
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +16 -0
- package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +13 -0
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +16 -1
- package/src/runtime/agent/orchestrator/providers/mixdog-local.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +5 -5
- package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +9 -5
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +9 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +30 -3
- package/src/runtime/agent/orchestrator/providers/openai-model-capabilities.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +61 -8
- package/src/runtime/agent/orchestrator/providers/registry.mjs +9 -0
- package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +90 -24
- package/src/runtime/agent/orchestrator/runtime-core/fs-utils.mjs +8 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-mutations.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-storage.mjs +68 -22
- package/src/runtime/agent/orchestrator/runtime-core/goal-tool-exec.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +4 -6
- package/src/runtime/agent/orchestrator/runtime-core/route-context-state.mjs +81 -0
- package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -1
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +12 -1
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +6 -5
- package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +7 -6
- package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/workflow/packs.mjs +8 -4
- package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/agent-mode.mjs +0 -6
- package/src/runtime/agent/orchestrator/session/agent-room-supervisor.d.mts +1 -1
- package/src/runtime/agent/orchestrator/session/agent-room-supervisor.mjs +22 -4
- package/src/runtime/agent/orchestrator/session/agent-room.d.mts +17 -2
- package/src/runtime/agent/orchestrator/session/agent-room.mjs +133 -38
- package/src/runtime/agent/orchestrator/session/attachment-preflight.mjs +80 -0
- package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +14 -11
- package/src/runtime/agent/orchestrator/session/compact/forked-summary.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +68 -18
- package/src/runtime/agent/orchestrator/session/compact/summary-mode.mjs +16 -0
- package/src/runtime/agent/orchestrator/session/compact/summary-route.mjs +87 -0
- package/src/runtime/agent/orchestrator/session/compact/summary.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +5 -1
- package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +41 -10
- package/src/runtime/agent/orchestrator/session/human-gate/delegation.mjs +230 -0
- package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +136 -32
- package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +20 -0
- package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +21 -9
- package/src/runtime/agent/orchestrator/session/human-gate/run-policy.mjs +73 -0
- package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +55 -27
- package/src/runtime/agent/orchestrator/session/human-gate/unattended-port.mjs +15 -0
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +189 -105
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +6 -2
- package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +5 -5
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +38 -5
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +21 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +12 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +11 -5
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +13 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +44 -18
- package/src/runtime/agent/orchestrator/session/manager/orchestration-mode-switch-notice.mjs +99 -0
- package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +42 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +3 -9
- package/src/runtime/agent/orchestrator/session/manager/pending-spool-file.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/pending-spool-shard.mjs +5 -3
- package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +40 -1
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +12 -0
- package/src/runtime/agent/orchestrator/session/manager/runtime-state.mjs +20 -0
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +55 -27
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +3 -8
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +12 -1
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +6 -15
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +24 -8
- package/src/runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs +94 -13
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +42 -10
- package/src/runtime/agent/orchestrator/session/provider-prefix-guard.mjs +21 -3
- package/src/runtime/agent/orchestrator/session/routine-result.d.mts +18 -3
- package/src/runtime/agent/orchestrator/session/routine-result.mjs +120 -9
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +5 -1
- package/src/runtime/agent/orchestrator/session/side-question-store.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/store/canonical-reader.mjs +41 -7
- package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +10 -2
- package/src/runtime/agent/orchestrator/session/store/load-cache.mjs +14 -2
- package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +15 -1
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-record.mjs +8 -3
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +36 -1
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +40 -5
- package/src/runtime/agent/orchestrator/session/store-summary-last-message.mjs +47 -2
- package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +125 -0
- package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +78 -43
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +35 -5
- package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +42 -2
- package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +35 -13
- package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +25 -5
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +4 -3
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +13 -14
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/transport-artifacts.mjs +9 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +19 -14
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +7 -1
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +6 -14
- package/src/runtime/agent/orchestrator/tools/builtin/pdf-test-fixtures.mjs +5 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +4 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-media.mjs +47 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +264 -87
- package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +10 -4
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +34 -21
- package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +8 -4
- package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +9 -30
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +24 -12
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +3 -2
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +6 -2
- package/src/runtime/agent/orchestrator/tools/patch/delete-quarantine.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/patch/replay-capture.mjs +4 -2
- package/src/runtime/agent/orchestrator/tools/patch/rollback-state.mjs +33 -2
- package/src/runtime/agent/orchestrator/tools/patch-manifest.json +13 -13
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +5 -3
- package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +3 -1
- package/src/runtime/attachments/attachment-reference.mjs +155 -0
- package/src/runtime/attachments/limits.mjs +5 -1
- package/src/runtime/attachments/office-extract.mjs +3 -6
- package/src/runtime/attachments/pdf-extract.mjs +84 -10
- package/src/runtime/attachments/store.mjs +49 -30
- package/src/runtime/browser-bridge/client.mjs +73 -20
- package/src/runtime/channels/lib/runtime-paths.mjs +1 -1
- package/src/runtime/channels/lib/scheduler.mjs +14 -7
- package/src/runtime/channels/lib/state-file.mjs +15 -15
- package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +14 -13
- package/src/runtime/channels/lib/webhook/relay-tunnel.mjs +22 -18
- package/src/runtime/channels/lib/webhook/session-dispatch.mjs +14 -36
- package/src/runtime/channels/lib/worker-main.mjs +3 -2
- package/src/runtime/effort-judge/compact-tokenizer.mjs +8 -2
- package/src/runtime/effort-judge/judge-client.mjs +5 -2
- package/src/runtime/effort-judge/model-install.mjs +16 -8
- package/src/runtime/local-provider/asset-installer.mjs +10 -4
- package/src/runtime/local-provider/catalog.mjs +5 -2
- package/src/runtime/local-provider/registered-models.mjs +64 -27
- package/src/runtime/local-provider/server.mjs +2 -3
- package/src/runtime/media/adapters/image-api.mjs +89 -0
- package/src/runtime/media/auth.mjs +64 -1
- package/src/runtime/media/catalog.mjs +66 -4
- package/src/runtime/media/jobs.mjs +4 -0
- package/src/runtime/media/lanes.mjs +46 -5
- package/src/runtime/media/media-usage.mjs +17 -0
- package/src/runtime/media/renditions.mjs +15 -15
- package/src/runtime/media/store.mjs +36 -35
- package/src/runtime/media/tool.mjs +8 -1
- package/src/runtime/memory/lib/cycle-scheduler/health-ledger.mjs +8 -2
- package/src/runtime/memory/lib/embedding-model-files.mjs +30 -7
- package/src/runtime/memory/lib/ingest/transcript-tail.mjs +16 -7
- package/src/runtime/memory/lib/pg/process.mjs +8 -4
- package/src/runtime/memory/lib/project-id-resolver.mjs +5 -1
- package/src/runtime/memory/lib/runtime-fetcher.mjs +6 -1
- package/src/runtime/office/core/journal.mjs +7 -14
- package/src/runtime/office/design/library/design-library-core.mjs +4 -16
- package/src/runtime/office/pdf/ocr-engine-worker.mjs +37 -0
- package/src/runtime/office/pdf/ocr-engine.mjs +146 -0
- package/src/runtime/office/pdf/pdf-ocr.mjs +51 -21
- package/src/runtime/office/pdf/pdf-render.mjs +30 -18
- package/src/runtime/office/portable/ooxml-validator.mjs +2 -2
- package/src/runtime/office/portable/portable-soffice.mjs +4 -0
- package/src/runtime/shared/agent-kind-migration.mjs +200 -0
- package/src/runtime/shared/agent-message-envelope.d.mts +6 -0
- package/src/runtime/shared/agent-message-envelope.mjs +44 -0
- package/src/runtime/shared/agent-reap-state.d.mts +4 -0
- package/src/runtime/shared/agent-store.mjs +24 -7
- package/src/runtime/shared/agent-task-starts.mjs +27 -0
- package/src/runtime/shared/atomic-file.d.mts +50 -0
- package/src/runtime/shared/atomic-file.mjs +394 -132
- package/src/runtime/shared/background-tasks.mjs +7 -4
- package/src/runtime/shared/bot-hops.mjs +30 -0
- package/src/runtime/shared/bot-split-migration.mjs +1 -1
- package/src/runtime/shared/bounded-download.mjs +4 -2
- package/src/runtime/shared/config.mjs +39 -9
- package/src/runtime/shared/defaults-separation.mjs +6 -2
- package/src/runtime/shared/delegated-approval-host.mjs +74 -0
- package/src/runtime/shared/err-text.mjs +4 -0
- package/src/runtime/shared/error-presentation.mjs +22 -4
- package/src/runtime/shared/file-has-data.mjs +28 -0
- package/src/runtime/shared/llm/usage-ledger-import.mjs +37 -18
- package/src/runtime/shared/pending-agent-work.mjs +54 -0
- package/src/runtime/shared/process-lifecycle.mjs +8 -12
- package/src/runtime/shared/projects.mjs +50 -41
- package/src/runtime/shared/provider-accounts.mjs +17 -12
- package/src/runtime/shared/routine-trigger.d.mts +6 -1
- package/src/runtime/shared/routine-trigger.mjs +14 -6
- package/src/runtime/shared/run-child.d.mts +3 -0
- package/src/runtime/shared/run-child.mjs +24 -3
- package/src/runtime/shared/session-baseline-store.mjs +8 -2
- package/src/runtime/shared/singleton-owner.mjs +7 -6
- package/src/runtime/shared/skill-tool-dependencies.mjs +4 -10
- package/src/runtime/shared/staged-update.mjs +4 -2
- package/src/runtime/shared/statusline/statusline-segments.mjs +4 -15
- package/src/runtime/shared/tool-execution-contract.mjs +6 -0
- package/src/runtime/shared/transcript-metadata.mjs +28 -3
- package/src/runtime/shared/transcript-writer/session-record.mjs +7 -6
- package/src/runtime/shared/turn-snapshot-store.mjs +5 -4
- package/src/runtime/shared/turn-snapshot.mjs +44 -2
- package/src/runtime/shared/user-cwd.mjs +9 -4
- package/src/runtime/shared/user-data-guard.mjs +41 -42
- package/src/runtime/shared/utf8-cut.mjs +10 -0
- package/src/runtime/shared/worktrees/activity-gate.mjs +269 -61
- package/src/runtime/shared/worktrees/registry.mjs +26 -26
- package/src/runtime/tidy/core-install.mjs +26 -4
- package/src/runtime/tidy/install.mjs +91 -23
- package/src/runtime/web-search/lib/config.mjs +4 -16
- package/src/session-runtime/agent-home-delivery.mjs +555 -199
- package/src/session-runtime/agent-room-delivery.mjs +143 -36
- package/src/session-runtime/agent-task-sessions.mjs +667 -165
- package/src/session-runtime/agent-tool-routing.mjs +1 -0
- package/src/session-runtime/boot/apis.mjs +45 -24
- package/src/session-runtime/boot/config.mjs +1 -0
- package/src/session-runtime/boot/facade.mjs +11 -1
- package/src/session-runtime/boot/infrastructure.mjs +14 -2
- package/src/session-runtime/boot/providers.mjs +2 -2
- package/src/session-runtime/boot/shared.mjs +12 -6
- package/src/session-runtime/boot/tools.mjs +2 -2
- package/src/session-runtime/bot-status.mjs +432 -0
- package/src/session-runtime/config-lifecycle/lead-route-sync.mjs +29 -0
- package/src/session-runtime/config-lifecycle.mjs +60 -55
- package/src/session-runtime/cwd-plugins/core-memory-context.mjs +2 -4
- package/src/session-runtime/held-requests.mjs +36 -33
- package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +10 -1
- package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -2
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +19 -4
- package/src/session-runtime/lifecycle/session-catalog.mjs +12 -2
- package/src/session-runtime/lifecycle/teardown.mjs +11 -3
- package/src/session-runtime/mcp-glue/connect.mjs +100 -186
- package/src/session-runtime/mcp-glue/initial-connect-gate.mjs +57 -0
- package/src/session-runtime/mcp-glue/server-connection.mjs +71 -0
- package/src/session-runtime/media-api.mjs +5 -2
- package/src/session-runtime/model-route/route-tuning.mjs +4 -1
- package/src/session-runtime/model-route/set-route.mjs +61 -1
- package/src/session-runtime/owned-automation-run.mjs +8 -1
- package/src/session-runtime/room-member-gate.mjs +16 -3
- package/src/session-runtime/route-resolution.mjs +16 -8
- package/src/session-runtime/route-state.mjs +1 -59
- package/src/session-runtime/routine-home-turn.mjs +5 -3
- package/src/session-runtime/runtime-bootstrap.mjs +5 -3
- package/src/session-runtime/runtime-paths.mjs +11 -5
- package/src/session-runtime/runtime-tunables.mjs +6 -0
- package/src/session-runtime/services/agent-tool/close-flow/close-all.mjs +11 -2
- package/src/session-runtime/services/agent-tool/close-flow.mjs +4 -2
- package/src/session-runtime/services/agent-tool/execute-send.mjs +1 -0
- package/src/session-runtime/services/agent-tool/execute-spawn.mjs +1 -0
- package/src/session-runtime/services/agent-tool/restart-notify.mjs +10 -0
- package/src/session-runtime/services/agent-tool/send-flow.mjs +25 -21
- package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +4 -0
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +6 -3
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +8 -5
- package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +22 -2
- package/src/session-runtime/services/agent-tool/worker-index.mjs +4 -6
- package/src/session-runtime/services/agent-tool.mjs +6 -0
- package/src/session-runtime/services/daemon-crash-capture/daemon-exit-record.mjs +1 -1
- package/src/session-runtime/services/daemon-crash-capture/retention.mjs +4 -3
- package/src/session-runtime/services/hook-bus/config-loader.mjs +14 -5
- package/src/session-runtime/services/hook-bus/rule-file.mjs +49 -11
- package/src/session-runtime/services/plugin-admin.mjs +26 -26
- package/src/session-runtime/services/provider-admin.mjs +14 -4
- package/src/session-runtime/services/schedule-access.mjs +33 -4
- package/src/session-runtime/services/seeds.mjs +4 -3
- package/src/session-runtime/services/usage-dashboard.mjs +1 -1
- package/src/session-runtime/services/usage-stats-model.mjs +2 -1
- package/src/session-runtime/session-create/materialize.mjs +2 -3
- package/src/session-runtime/session-create/session-options.mjs +15 -2
- package/src/session-runtime/settings-builtin-tools-api.mjs +63 -1
- package/src/session-runtime/settings-compaction-api.mjs +86 -39
- package/src/session-runtime/setup-tool/executor.mjs +8 -2
- package/src/session-runtime/setup-tool/settings-contract.mjs +31 -1
- package/src/session-runtime/setup-tool/tool-defs.mjs +20 -3
- package/src/session-runtime/skills-api.mjs +12 -5
- package/src/session-runtime/tool-policy/agent-profile-fold.mjs +265 -0
- package/src/session-runtime/tool-policy/mode-switch.mjs +54 -0
- package/src/session-runtime/tool-policy/pending-switches.mjs +59 -0
- package/src/session-runtime/tool-policy/policy-stage.mjs +165 -0
- package/src/session-runtime/tool-policy/session-switches.mjs +169 -0
- package/src/session-runtime/tool-policy/surface-helpers.mjs +114 -0
- package/src/session-runtime/tool-policy/workflow-overlay.mjs +145 -0
- package/src/session-runtime/tool-policy-refresh.mjs +47 -369
- package/src/session-runtime/tool-surface.mjs +5 -3
- package/src/session-runtime/turn/turn-run/turn-prepare.mjs +95 -88
- package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
- package/src/session-runtime/turn/turn-run.mjs +2 -1
- package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +54 -7
- package/src/session-runtime/workflow-agents-api/agent-editor.mjs +519 -333
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +46 -46
- package/src/session-runtime/workflow-agents-api/shared.mjs +5 -1
- package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +14 -1
- package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +6 -3
- package/src/session-runtime/workspace/workspace-state.mjs +7 -6
- package/src/standalone/daemon-agent-control.mjs +4 -1
- package/src/standalone/daemon-session-control.mjs +51 -10
- package/src/standalone/daemon.mjs +28 -10
- package/src/standalone/local-session-runtime.mjs +22 -14
- package/src/standalone/session-client.mjs +7 -2
- package/src/standalone/session-protocol.mjs +7 -0
- package/src/standalone/session-service.mjs +11 -0
- package/src/standalone/session-state-projection.mjs +19 -9
- package/src/standalone/session-tool-output.mjs +35 -9
- package/src/tui/app/maintenance-pickers/auto-clear-picker.mjs +19 -13
- package/src/tui/app/settings-picker/settings-rows.mjs +2 -2
- package/src/tui/app/slash-commands.mjs +1 -1
- package/src/tui/app/usage-context-panels/context-model.mjs +8 -2
- package/src/tui/dist/index.mjs +529 -333
- package/src/tui/prompt-history-store.mjs +16 -21
- package/src/tui/session/context-state.mjs +4 -0
- package/src/tui/session/session-api/intake/abort.mjs +19 -1
- package/src/tui/session/session-api/media.mjs +2 -1
- package/src/tui/session/session-api/routes-agent-definitions.mjs +75 -0
- package/src/tui/session/session-api/routes-write.mjs +203 -0
- package/src/tui/session/session-api/routes.mjs +23 -247
- package/src/tui/session/session-api/settings.mjs +14 -1
- package/src/tui/session/session-flow/drain.mjs +28 -3
- package/src/tui/session/session-flow/failed-turn-rewind.mjs +4 -2
- package/src/tui/session/session-flow/queue/take-entries.mjs +8 -0
- package/src/tui/session/session-flow/steering.mjs +8 -3
- package/src/tui/session/tool-approval.mjs +185 -20
- package/src/tui/session/transcript-intake.mjs +2 -0
- package/src/tui/session/turn.mjs +23 -14
- package/src/tui/transcript-tool-failures.mjs +8 -2
- package/src/vendor/statusline/src/gateway/route-meta.mjs +7 -8
- package/src/vendor/statusline/src/gateway/session-routes.mjs +6 -7
- package/src/rules/agent-mode/ROUTINE.md +0 -6
|
@@ -12,14 +12,24 @@
|
|
|
12
12
|
// recordHumanDecision stores a "don't ask again" grant and the audit line. Unattended runs (schedule, webhook, room member) register a
|
|
13
13
|
// port here (session-runtime/held-requests.mjs) that turns their asks into
|
|
14
14
|
// held requests instead of an approval card.
|
|
15
|
-
import { MIXY_AGENT_ID, agentIdOfHomeSession } from '../agent-home.mjs';
|
|
15
|
+
import { MIXY_AGENT_ID, agentHomeSessionId, agentIdOfHomeSession } from '../agent-home.mjs';
|
|
16
16
|
import { serverConnector } from '../../mcp/server-connectors.mjs';
|
|
17
17
|
import { readAgentDefinitionKind } from '../../../../shared/agent-kind.mjs';
|
|
18
18
|
import { isContactAgentId } from '../../../../shared/agent-route-config.mjs';
|
|
19
19
|
import { mixdogRoot, resolvePluginData } from '../../../../shared/plugin-paths.mjs';
|
|
20
20
|
import { roomMemberOfSession } from '../agent-room.mjs';
|
|
21
|
-
import {
|
|
21
|
+
import { agentDisplayName } from '../../context/role-instructions.mjs';
|
|
22
|
+
import { delegationOf } from './delegation.mjs';
|
|
23
|
+
import { setUnattendedApprovalPort, unattendedApprovalPort } from './unattended-port.mjs';
|
|
24
|
+
import { classifyAsDelete, classifyOutboundCall, isRememberableAction } from './outbound-policy.mjs';
|
|
22
25
|
import { humanGateStore } from './store.mjs';
|
|
26
|
+
import {
|
|
27
|
+
AUTOMATION_GRANT_REASON,
|
|
28
|
+
RUN_FORBIDDEN_REASON,
|
|
29
|
+
isGrantedRun,
|
|
30
|
+
isRemoteDeleteInRun,
|
|
31
|
+
savesAutomation,
|
|
32
|
+
} from './run-policy.mjs';
|
|
23
33
|
|
|
24
34
|
export const OUTBOUND_APPROVAL_REASON = 'This sends or posts outside Mixdog; it needs your approval.';
|
|
25
35
|
export const BLOCKED_ACTION_REASON = "This agent's permission blocks this action.";
|
|
@@ -27,25 +37,33 @@ export const BLOCKED_ACTION_REASON = "This agent's permission blocks this action
|
|
|
27
37
|
* payments and deletes never. */
|
|
28
38
|
const outboundRemember = (category) => (isRememberableAction(category) ? ['once', 'agent'] : ['once']);
|
|
29
39
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
* run that would need a takeover (session-runtime/held-requests.mjs). */
|
|
34
|
-
export function setUnattendedApprovalPort(port) {
|
|
35
|
-
unattendedPort = port || null;
|
|
36
|
-
}
|
|
37
|
-
export function unattendedApprovalPort() {
|
|
38
|
-
return unattendedPort;
|
|
39
|
-
}
|
|
40
|
+
// The unattended runs' port lives in unattended-port.mjs (delegation.mjs reads
|
|
41
|
+
// it when a session is created); its setter and getter are re-exported here.
|
|
42
|
+
export { setUnattendedApprovalPort, unattendedApprovalPort };
|
|
40
43
|
|
|
41
|
-
/** The agent a session is bound to: its unattended run's owner, a
|
|
42
|
-
* member, a home thread's agent; '' for any other
|
|
43
|
-
function
|
|
44
|
-
const run =
|
|
44
|
+
/** The agent a session is bound to by itself: its unattended run's owner, a
|
|
45
|
+
* room member, a home thread's agent; '' for any other session. */
|
|
46
|
+
function ownBoundAgentId(sessionId) {
|
|
47
|
+
const run = unattendedApprovalPort()?.runOf?.(sessionId);
|
|
45
48
|
if (run?.agentId) return run.agentId;
|
|
46
49
|
return roomMemberOfSession(sessionId)?.agentId || agentIdOfHomeSession(sessionId) || '';
|
|
47
50
|
}
|
|
48
51
|
|
|
52
|
+
/** The agent a session is bound to: its own binding, else — a sub-agent at
|
|
53
|
+
* any depth, a task session a bot created — the root recorded on it when it
|
|
54
|
+
* was created (delegation.mjs); '' for a coding Lead and every agent under
|
|
55
|
+
* it, and for a delegated session whose root is unknown (see isUnresolved). */
|
|
56
|
+
function boundAgentId(sessionId) {
|
|
57
|
+
const own = ownBoundAgentId(sessionId);
|
|
58
|
+
if (own) return own;
|
|
59
|
+
const delegation = delegationOf(sessionId);
|
|
60
|
+
return delegation.state === 'resolved' ? delegation.rootAgentId : '';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** A delegated session whose root could not be established: it may act for a
|
|
64
|
+
* bot, so the gate fails closed (outbound asks, nothing is remembered). */
|
|
65
|
+
const isUnresolved = (sessionId) => !ownBoundAgentId(sessionId) && delegationOf(sessionId).state === 'unresolved';
|
|
66
|
+
|
|
49
67
|
/** The agent a session acts for: its unattended run's owner, a room member,
|
|
50
68
|
* a home thread's agent, else Mixy. */
|
|
51
69
|
export function agentIdForSession(sessionId) {
|
|
@@ -55,10 +73,10 @@ export function agentIdForSession(sessionId) {
|
|
|
55
73
|
/** The bot a session acts for, or '' when none: Mixy in her own home thread
|
|
56
74
|
* (or her unattended run / room member session), or a contact agent (`kind:
|
|
57
75
|
* bot`, isContactAgentId) in its home thread, room member session or
|
|
58
|
-
* unattended run
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
76
|
+
* unattended run — or any session delegated from one of those (a sub-agent,
|
|
77
|
+
* a task session the bot created, nested at any depth), which acts as that
|
|
78
|
+
* root bot. A coding Lead (also id 'lead'), its workflow agents and their
|
|
79
|
+
* sub-agent sessions are bound to nothing here, so they are no bot. */
|
|
62
80
|
function actingBotId(sessionId) {
|
|
63
81
|
const id = boundAgentId(sessionId);
|
|
64
82
|
if (!id) return '';
|
|
@@ -67,6 +85,54 @@ function actingBotId(sessionId) {
|
|
|
67
85
|
return isContactAgentId(id, kind) ? id : '';
|
|
68
86
|
}
|
|
69
87
|
|
|
88
|
+
/** The contact bot (`kind: bot`) a session acts for, or '' for Mixy, a coding
|
|
89
|
+
* Lead, a workflow agent and every other session. Mixy shares the `lead` id
|
|
90
|
+
* with the coding Lead (her connector policy is hers alone, never the Lead's:
|
|
91
|
+
* connectorPolicyAgentOf), and a standing allow is a contact bot's own policy
|
|
92
|
+
* entry: none is offered to Mixy, a Lead or a workflow agent. */
|
|
93
|
+
export function actingContactBotId(sessionId) {
|
|
94
|
+
const id = actingBotId(sessionId);
|
|
95
|
+
// Only where a remembering answer can be given in the bot's OWN home thread
|
|
96
|
+
// (rememberSurfaceOf). A room member, an unattended run (a schedule, a
|
|
97
|
+
// webhook) and work they started act for the bot in a session that is not its chat.
|
|
98
|
+
return id && id !== MIXY_AGENT_ID && rememberSurfaceOf(sessionId) === agentHomeSessionId(id) ? id : '';
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** The one session where an answer to `sessionId`'s request may be
|
|
102
|
+
* remembered ("don't ask again", "always allow"): the bot's home chat when
|
|
103
|
+
* the request lives there, or surfaces there because the work was started
|
|
104
|
+
* from that attended chat; '' otherwise (a room, a run, a coding Lead, an
|
|
105
|
+
* unresolved delegation). The approval queue honours a remembering answer
|
|
106
|
+
* only from that session (tui/session/tool-approval.mjs). */
|
|
107
|
+
export function rememberSurfaceOf(sessionId) {
|
|
108
|
+
// A schedule/webhook running in the home itself is not the attended chat.
|
|
109
|
+
if (unattendedApprovalPort()?.runOf?.(sessionId)) return '';
|
|
110
|
+
if (agentIdOfHomeSession(sessionId)) return sessionId;
|
|
111
|
+
if (ownBoundAgentId(sessionId)) return '';
|
|
112
|
+
const delegation = delegationOf(sessionId);
|
|
113
|
+
return delegation.state === 'resolved' && delegation.via === 'home' ? delegation.rootSessionId : '';
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Chain tokens the desktop translates (ApprovalCard approvalChainLabel).
|
|
117
|
+
const LEAD_NAME = 'Lead';
|
|
118
|
+
const TASK_SESSION_NAME = 'Task session';
|
|
119
|
+
|
|
120
|
+
/** Where a delegated session's approval cards also surface, and who is
|
|
121
|
+
* asking: `{ sessionId, chain }` — the top-level conversation the user
|
|
122
|
+
* watches (the root bot's home chat, a room member session, a coding Lead's
|
|
123
|
+
* session) and the names from there down to the asker ("사무관 › Worker",
|
|
124
|
+
* "사무관 › Task session", "Lead › Reviewer"). A sub-agent at any depth and
|
|
125
|
+
* a task session a bot created have one; null for a top-level session. */
|
|
126
|
+
export function approvalSurface(sessionId) {
|
|
127
|
+
const delegation = delegationOf(sessionId);
|
|
128
|
+
if (delegation.state !== 'resolved') return null;
|
|
129
|
+
const chain = [delegation.rootAgentId ? agentDisplayName(delegation.rootAgentId) : LEAD_NAME];
|
|
130
|
+
for (const step of delegation.path) {
|
|
131
|
+
chain.push(step.kind === 'agent' ? agentDisplayName(step.agent) || step.agent : TASK_SESSION_NAME);
|
|
132
|
+
}
|
|
133
|
+
return { sessionId: delegation.rootSessionId, chain };
|
|
134
|
+
}
|
|
135
|
+
|
|
70
136
|
const actionOf = (decision) => String(decision?.action || decision?.decision || '').toLowerCase();
|
|
71
137
|
|
|
72
138
|
async function mcpAnnotations(name, scopeId) {
|
|
@@ -114,21 +180,47 @@ async function describeEmails(call, outbound, ctx) {
|
|
|
114
180
|
* @param {{ name: string, args: any }} call
|
|
115
181
|
* @param {object|null} decision the PreToolUse decision so far
|
|
116
182
|
* @param {{ sessionId?: string, mcpScopeId?: string, annotations?: object, store?: object }} ctx
|
|
117
|
-
* @returns {Promise<{ decision: object|null, request: object|null, gate: object|null }>}
|
|
183
|
+
* @returns {Promise<{ decision: object|null, request: object|null, gate: object|null, remembered?: true }>}
|
|
118
184
|
* `request` holds the kind-specific fields an `ask` presents; `gate` the
|
|
119
|
-
* audit identity `{ agentId, kind, action, tool, target }
|
|
185
|
+
* audit identity `{ agentId, kind, action, tool, target }`; `remembered`
|
|
186
|
+
* marks an outbound call a stored grant already answered.
|
|
120
187
|
*/
|
|
121
188
|
export async function humanGateDecision(call, decision, ctx = {}) {
|
|
122
189
|
const action = actionOf(decision);
|
|
123
190
|
if (action === 'deny' || action === 'block') return { decision, request: null, gate: null };
|
|
124
|
-
|
|
191
|
+
// Delegated, but its root is unknown: treated as a bot whose identity, grants
|
|
192
|
+
// and remember surface are unknown — every outbound call asks, once only.
|
|
193
|
+
const unresolved = isUnresolved(ctx.sessionId);
|
|
194
|
+
const agentId = unresolved ? '' : agentIdForSession(ctx.sessionId);
|
|
125
195
|
const annotations = ctx.annotations ?? (await mcpAnnotations(call?.name, ctx.mcpScopeId));
|
|
126
|
-
|
|
127
|
-
const
|
|
196
|
+
let classified = classifyOutboundCall({ name: call?.name, args: call?.args, annotations });
|
|
197
|
+
const remoteDelete = isRemoteDeleteInRun({ name: call?.name, annotations }, classified);
|
|
198
|
+
// In a schedule/webhook run only a recoverable trash may delete remote items.
|
|
199
|
+
if (isGrantedRun(unattendedApprovalPort()?.runOf?.(ctx.sessionId)) && remoteDelete) {
|
|
200
|
+
const store = ctx.store || (await humanGateStore());
|
|
201
|
+
await store.recordAudit({
|
|
202
|
+
agentId,
|
|
203
|
+
kind: 'outbound',
|
|
204
|
+
action: 'delete',
|
|
205
|
+
tool: String(call?.name || ''),
|
|
206
|
+
target: '',
|
|
207
|
+
decision: 'run-forbidden',
|
|
208
|
+
device: '',
|
|
209
|
+
});
|
|
210
|
+
const base = decision && typeof decision === 'object' ? decision : {};
|
|
211
|
+
return { decision: { ...base, action: 'deny', reason: RUN_FORBIDDEN_REASON }, request: null, gate: null };
|
|
212
|
+
}
|
|
213
|
+
// Where a bot is attended, the same connector delete the runs refuse
|
|
214
|
+
// (delete_message, remove_member…) is a permanent delete: it asks, never remembered.
|
|
215
|
+
if (remoteDelete && classified?.category !== 'delete' && classified?.category !== 'payment') {
|
|
216
|
+
classified = classifyAsDelete({ name: call?.name, args: call?.args });
|
|
217
|
+
}
|
|
218
|
+
const outbound = classified && (unresolved || actingBotId(ctx.sessionId)) ? classified : null;
|
|
128
219
|
if (!outbound) {
|
|
129
220
|
if (action !== 'ask') return { decision, request: null, gate: null };
|
|
130
221
|
return {
|
|
131
|
-
|
|
222
|
+
// Saving a schedule or webhook says plainly what the approval grants.
|
|
223
|
+
decision: savesAutomation(call) ? { ...decision, reason: AUTOMATION_GRANT_REASON } : decision,
|
|
132
224
|
request: { kind: 'approval', agentId, remember: ['once'] },
|
|
133
225
|
gate: { agentId, kind: 'approval', tool: String(call?.name || ''), target: '' },
|
|
134
226
|
};
|
|
@@ -145,10 +237,16 @@ export async function humanGateDecision(call, decision, ctx = {}) {
|
|
|
145
237
|
editable: outbound.editable,
|
|
146
238
|
details: outbound.details,
|
|
147
239
|
},
|
|
148
|
-
remember:
|
|
240
|
+
remember: ['once'],
|
|
149
241
|
};
|
|
242
|
+
// "Don't ask again" only where it can be answered in the bot's own chat.
|
|
243
|
+
const rememberAt = unresolved ? '' : rememberSurfaceOf(ctx.sessionId);
|
|
244
|
+
if (rememberAt && isRememberableAction(outbound.category)) {
|
|
245
|
+
request.remember = outboundRemember(outbound.category);
|
|
246
|
+
request.rememberAt = rememberAt;
|
|
247
|
+
}
|
|
150
248
|
const store = ctx.store || (await humanGateStore());
|
|
151
|
-
const mode = await store.modeFor({ agentId, action: outbound.category });
|
|
249
|
+
const mode = unresolved ? 'ask' : await store.modeFor({ agentId, action: outbound.category });
|
|
152
250
|
const base = decision && typeof decision === 'object' ? decision : {};
|
|
153
251
|
if (mode === 'block') {
|
|
154
252
|
await store.recordAudit({ ...gate, decision: 'blocked', device: '' });
|
|
@@ -156,7 +254,8 @@ export async function humanGateDecision(call, decision, ctx = {}) {
|
|
|
156
254
|
}
|
|
157
255
|
if (action !== 'ask' && mode === 'allow') {
|
|
158
256
|
await store.recordAudit({ ...gate, decision: 'remembered', device: '' });
|
|
159
|
-
|
|
257
|
+
// The remembered grant answers this call: no later layer asks it again.
|
|
258
|
+
return { decision, request: null, gate: null, remembered: true };
|
|
160
259
|
}
|
|
161
260
|
const described = await describeEmails(call, outbound, ctx);
|
|
162
261
|
if (described) {
|
|
@@ -173,7 +272,8 @@ export async function humanGateDecision(call, decision, ctx = {}) {
|
|
|
173
272
|
return {
|
|
174
273
|
decision: { ...(rewriting ? base : unrewritten), action: 'ask', reason: base.reason || OUTBOUND_APPROVAL_REASON },
|
|
175
274
|
request,
|
|
176
|
-
|
|
275
|
+
// The scopes this request offered: a grant is stored only for one of them.
|
|
276
|
+
gate: { ...gate, remember: request.remember },
|
|
177
277
|
};
|
|
178
278
|
}
|
|
179
279
|
|
|
@@ -185,8 +285,12 @@ export async function recordHumanDecision(gate, approval, { store } = {}) {
|
|
|
185
285
|
let decision = approval?.response || (approval?.approved === true ? 'approve' : 'deny');
|
|
186
286
|
if (approval?.timedOut) decision = 'timeout';
|
|
187
287
|
const device = typeof approval?.device === 'string' ? approval.device : '';
|
|
188
|
-
|
|
288
|
+
// A scope the request did not offer (an answer from a place that may not
|
|
289
|
+
// remember) stores nothing.
|
|
290
|
+
const offered = !Array.isArray(gate.remember) || gate.remember.includes(approval?.remember);
|
|
291
|
+
if (approval?.approved === true && gate.action && offered) {
|
|
189
292
|
await target.grant({ agentId: gate.agentId, action: gate.action, scope: approval.remember, device });
|
|
190
293
|
}
|
|
191
|
-
|
|
294
|
+
const { remember: _offered, ...audit } = gate;
|
|
295
|
+
await target.recordAudit({ ...audit, decision, device });
|
|
192
296
|
}
|
|
@@ -23,6 +23,14 @@ export const ACTION_KINDS = Object.freeze([
|
|
|
23
23
|
'payment',
|
|
24
24
|
'delete',
|
|
25
25
|
'trash',
|
|
26
|
+
// Browser Use gated actions (main/browser/action-approval.ts): a form
|
|
27
|
+
// submit / Enter, a click on a send or post control, and a file upload. They
|
|
28
|
+
// are not classified here (the desktop browser host asks); only their grants
|
|
29
|
+
// live in this store. Operator-confirmed actions
|
|
30
|
+
// (MIXDOG_BROWSER_CONFIRM_ACTIONS) have no kind: they always ask.
|
|
31
|
+
'browser_submit',
|
|
32
|
+
'browser_post',
|
|
33
|
+
'browser_upload',
|
|
26
34
|
]);
|
|
27
35
|
const UNREMEMBERED_ACTIONS = new Set(['payment', 'delete']);
|
|
28
36
|
|
|
@@ -204,6 +212,18 @@ export function classifyOutboundCall({ name, args, annotations } = {}) {
|
|
|
204
212
|
if (!category && annotations?.readOnlyHint === true) return null;
|
|
205
213
|
if (!category && annotations?.outboundHint === true) category = 'message';
|
|
206
214
|
if (!category) return null;
|
|
215
|
+
return mcpOutbound(toolName, server, input, category);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** The classification of connector tool `name` as a permanent delete — for a
|
|
219
|
+
* delete the name-based list above does not know (run-policy.mjs decides which). */
|
|
220
|
+
export function classifyAsDelete({ name, args } = {}) {
|
|
221
|
+
const toolName = String(name || '');
|
|
222
|
+
const [, server = ''] = toolName.split('__');
|
|
223
|
+
return mcpOutbound(toolName, server, isRecord(args) ? args : {}, 'delete');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function mcpOutbound(toolName, server, input, category) {
|
|
207
227
|
const details = detailsOf(category, input);
|
|
208
228
|
const spends = category === 'payment' || category === 'delete';
|
|
209
229
|
return {
|
|
@@ -84,6 +84,8 @@ export function humanRequestFields(input = {}) {
|
|
|
84
84
|
const fields = { remember: rememberScopes(input.remember) };
|
|
85
85
|
// The agent the request acts for (its name on the card and the remembered grant).
|
|
86
86
|
if (text(input.agentId)) fields.agentId = text(input.agentId);
|
|
87
|
+
// The one session a remembering answer may come from (gate.mjs rememberSurfaceOf).
|
|
88
|
+
if (text(input.rememberAt)) fields.rememberAt = text(input.rememberAt);
|
|
87
89
|
if (kind === 'outbound' && isRecord(input.outbound)) {
|
|
88
90
|
const outbound = input.outbound;
|
|
89
91
|
const details = isRecord(outbound.details) ? outbound.details : {};
|
|
@@ -119,13 +121,16 @@ export function humanRequestFields(input = {}) {
|
|
|
119
121
|
// browser page ID, or a computer window id and app, so the card can bring
|
|
120
122
|
// exactly that window forward on the user's click.
|
|
121
123
|
const token = (value, limit) => (/^[\w.:#@-]+$/.test(text(value, limit)) ? text(value, limit) : '');
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
124
|
+
const tab = token(takeover.tab, 64);
|
|
125
|
+
const windowId = token(takeover.windowId, 128);
|
|
126
|
+
const app = text(takeover.app, 120);
|
|
127
|
+
const identity = {};
|
|
128
|
+
if (surface === 'browser') {
|
|
129
|
+
if (tab) identity.tab = tab;
|
|
130
|
+
} else {
|
|
131
|
+
if (windowId) identity.windowId = windowId;
|
|
132
|
+
if (app) identity.app = app;
|
|
133
|
+
}
|
|
129
134
|
// A login takeover (purpose 'login') opens the desktop's sign-in window on
|
|
130
135
|
// its validated page instead of handing over the side panel.
|
|
131
136
|
const login = surface === 'browser' && takeover.purpose === 'login' ? loginPageUrl(takeover.url) : null;
|
|
@@ -143,7 +148,13 @@ export function humanRequestFields(input = {}) {
|
|
|
143
148
|
fields.permission = {
|
|
144
149
|
scopes: scopes
|
|
145
150
|
.filter((scope) => isRecord(scope) && text(scope.id))
|
|
146
|
-
.map((scope) => ({
|
|
151
|
+
.map((scope) => ({
|
|
152
|
+
id: text(scope.id),
|
|
153
|
+
label: text(scope.label) || text(scope.id),
|
|
154
|
+
// The option's kind (allow_always / allow_once / reject, as an ACP or
|
|
155
|
+
// MCP permission request names them); the card maps buttons by it.
|
|
156
|
+
...(text(scope.kind) ? { kind: text(scope.kind) } : {}),
|
|
157
|
+
})),
|
|
147
158
|
};
|
|
148
159
|
}
|
|
149
160
|
return fields;
|
|
@@ -191,5 +202,6 @@ export function normalizeHumanResponse(request, approved, extra = {}) {
|
|
|
191
202
|
/** `args` with the card's edited fields in place of the model's. */
|
|
192
203
|
export function applyEditedArgs(args, editedArgs, editable) {
|
|
193
204
|
const edits = editableArgs(editedArgs, editable);
|
|
194
|
-
|
|
205
|
+
if (!Object.keys(edits).length) return args;
|
|
206
|
+
return { ...(isRecord(args) ? args : {}), ...edits };
|
|
195
207
|
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// session/human-gate/run-policy.mjs
|
|
2
|
+
// Schedule and webhook runs never ask. Saving a schedule or webhook is the
|
|
3
|
+
// user's explicit grant for its runs (and for the work they start: sub-agents,
|
|
4
|
+
// a bot's task sessions): outbound actions and ordinary tool approvals are
|
|
5
|
+
// allowed without a card, a held row or a push, inside the bot's own
|
|
6
|
+
// permissions (a `block` is applied before any ask, gate.mjs). Payments and
|
|
7
|
+
// permanent deletes (the action kinds that can never be remembered) are never
|
|
8
|
+
// done in a run: denied at once, with a result the bot can report. Group-room
|
|
9
|
+
// members are not runs of this kind and keep their held/approval behaviour.
|
|
10
|
+
import { ACTION_KINDS, isRememberableAction } from './outbound-policy.mjs';
|
|
11
|
+
|
|
12
|
+
export const RUN_FORBIDDEN_REASON =
|
|
13
|
+
"Payments and permanent deletes aren't allowed in schedule/webhook runs; do it from the chat.";
|
|
14
|
+
|
|
15
|
+
/** The ask reason of a schedule/webhook save: the grant, said plainly. */
|
|
16
|
+
export const AUTOMATION_GRANT_REASON =
|
|
17
|
+
"Approving lets this schedule's or webhook's runs act without asking, within the bot's permissions. Payments and permanent deletes are never done in runs.";
|
|
18
|
+
|
|
19
|
+
/** Whether `run` (the unattended port's `runOf`) is a schedule or webhook run. */
|
|
20
|
+
export const isGrantedRun = (run) => Boolean(run) && run.kind !== 'room';
|
|
21
|
+
|
|
22
|
+
/** Whether an outbound request is a payment or permanent delete. */
|
|
23
|
+
export function isForbiddenInRun(request) {
|
|
24
|
+
const category = request?.kind === 'outbound' ? request.outbound?.category : '';
|
|
25
|
+
return ACTION_KINDS.includes(category) && !isRememberableAction(category);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const READ_VERBS = new Set([
|
|
29
|
+
'get',
|
|
30
|
+
'list',
|
|
31
|
+
'search',
|
|
32
|
+
'read',
|
|
33
|
+
'fetch',
|
|
34
|
+
'retrieve',
|
|
35
|
+
'find',
|
|
36
|
+
'view',
|
|
37
|
+
'describe',
|
|
38
|
+
'lookup',
|
|
39
|
+
]);
|
|
40
|
+
const DELETE_TOKENS = new Set(['delete', 'remove', 'destroy', 'erase', 'purge', 'wipe']);
|
|
41
|
+
|
|
42
|
+
/** Whether a connector/MCP tool deletes remote items without being a confirmed
|
|
43
|
+
* recoverable `trash`: in a run that counts as a permanent delete. Read-verb
|
|
44
|
+
* names (`list_deleted_items`) stay reads. `classified` is the outbound
|
|
45
|
+
* classification of the call (outbound-policy.mjs), if any. */
|
|
46
|
+
export function isRemoteDeleteInRun({ name, annotations }, classified) {
|
|
47
|
+
const toolName = String(name || '');
|
|
48
|
+
if (!toolName.startsWith('mcp__')) return false;
|
|
49
|
+
if (classified?.category === 'trash') return false;
|
|
50
|
+
if (classified?.category === 'delete') return true;
|
|
51
|
+
const bare = String(annotations?.mcpToolName || toolName.split('__').slice(2).join('__'))
|
|
52
|
+
.toLowerCase()
|
|
53
|
+
.replace(/-/g, '_');
|
|
54
|
+
const tokens = bare.split(/[^a-z0-9]+/).filter(Boolean);
|
|
55
|
+
if (READ_VERBS.has(tokens[0])) return false;
|
|
56
|
+
return tokens.some((token) => DELETE_TOKENS.has(token));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Whether `call` saves a schedule or webhook (the setup tool's `save_automation`). */
|
|
60
|
+
export const savesAutomation = (call) => call?.name === 'setup' && call.args?.action === 'save_automation';
|
|
61
|
+
|
|
62
|
+
/** The answer a granted run gets instead of a card: `{ approval }` to run the
|
|
63
|
+
* call, or `{ denial, approval }` for a payment or permanent delete. The
|
|
64
|
+
* `response` is the audit decision (`run-allowed`, `run-forbidden`). */
|
|
65
|
+
export function runAskOutcome({ name, request }) {
|
|
66
|
+
if (isForbiddenInRun(request)) {
|
|
67
|
+
return {
|
|
68
|
+
denial: `Error: tool "${name}" denied: ${RUN_FORBIDDEN_REASON} Report this to the user; do not retry it or reach the same result another way.`,
|
|
69
|
+
approval: { approved: false, response: 'run-forbidden', reason: RUN_FORBIDDEN_REASON },
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return { approval: { approved: true, response: 'run-allowed', reason: 'schedule/webhook run' } };
|
|
73
|
+
}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import { createHash } from 'node:crypto';
|
|
12
12
|
import { appendFile, mkdir, readFile } from 'node:fs/promises';
|
|
13
13
|
import { dirname, join } from 'node:path';
|
|
14
|
-
import {
|
|
14
|
+
import { readJsonState, updateJsonAtomic } from '../../../../shared/atomic-file.mjs';
|
|
15
15
|
import { ACTION_KINDS, isRememberableAction } from './outbound-policy.mjs';
|
|
16
16
|
|
|
17
17
|
const ANY_AGENT = '*';
|
|
@@ -22,52 +22,64 @@ export const PERMISSION_MODES = Object.freeze(['ask', 'allow', 'block']);
|
|
|
22
22
|
const field = (value) => String(value ?? '').slice(0, FIELD_LIMIT);
|
|
23
23
|
const grantId = (agentId, action) =>
|
|
24
24
|
`grant_${createHash('sha256').update(`${agentId}\n${action}`).digest('hex').slice(0, 24)}`;
|
|
25
|
+
const MODE_RANK = Object.freeze({ block: 0, ask: 1, allow: 2 });
|
|
26
|
+
export const NO_WIDEN_MESSAGE = 'This change would widen what the bot may do without asking; make it on the desktop.';
|
|
25
27
|
const isGrant = (row) => ACTION_KINDS.includes(row?.action) && (row.mode === 'allow' || row.mode === 'block');
|
|
26
28
|
|
|
29
|
+
/** The effective mode among `grants`: the agent's own grant, else an every-agent one. */
|
|
30
|
+
function effectiveMode(grants, agentId, action) {
|
|
31
|
+
const rows = grants.filter((grant) => grant.action === action);
|
|
32
|
+
const own = rows.find((grant) => grant.agentId === agentId) || rows.find((grant) => grant.agentId === ANY_AGENT);
|
|
33
|
+
if (own?.mode === 'allow' && !isRememberableAction(action)) return 'ask';
|
|
34
|
+
return own?.mode || 'ask';
|
|
35
|
+
}
|
|
36
|
+
|
|
27
37
|
export function createHumanGateStore(directory) {
|
|
28
38
|
const grantsFile = join(directory, 'grants.json');
|
|
29
39
|
const auditFile = join(directory, 'audit.jsonl');
|
|
30
40
|
const grantsOf = (doc) => (Array.isArray(doc?.grants) ? doc.grants.filter(isGrant) : []);
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
throw error;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
41
|
+
// A damaged file (empty, NUL, torn) is kept aside by readJsonState and reads
|
|
42
|
+
// as no grants: every action asks again, never "allowed". No `.bak` is ever
|
|
43
|
+
// written or restored (`restoreBackup: false`, also for a stale one left by
|
|
44
|
+
// an older build) — an older copy could bring back a revoked allow.
|
|
45
|
+
const readGrants = async () => grantsOf(await readJsonState(grantsFile, { fallback: null, restoreBackup: false }));
|
|
39
46
|
async function updateGrants(mutate) {
|
|
40
47
|
await mkdir(directory, { recursive: true });
|
|
41
48
|
let result;
|
|
42
|
-
await updateJsonAtomic(
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
await updateJsonAtomic(
|
|
50
|
+
grantsFile,
|
|
51
|
+
(doc) => {
|
|
52
|
+
const out = mutate(grantsOf(doc));
|
|
53
|
+
if (!out) return undefined;
|
|
54
|
+
result = out.result;
|
|
55
|
+
return { grants: out.grants };
|
|
56
|
+
},
|
|
57
|
+
{ restoreBackup: false }
|
|
58
|
+
);
|
|
48
59
|
return result;
|
|
49
60
|
}
|
|
50
61
|
/** Set `agentId`'s mode for `action`; `ask` removes its grant. Returns the
|
|
51
62
|
* grant, or null for `ask` and for an `allow` that may not exist. */
|
|
52
|
-
async function setMode({ agentId, action, mode, device = '' }) {
|
|
63
|
+
async function setMode({ agentId, action, mode, device = '', noWiden = false }) {
|
|
53
64
|
if (!ACTION_KINDS.includes(action) || !PERMISSION_MODES.includes(mode)) return null;
|
|
54
65
|
if (mode === 'allow' && !isRememberableAction(action)) return null;
|
|
55
66
|
const owner = field(agentId);
|
|
56
67
|
const id = grantId(owner, action);
|
|
57
68
|
const grant = mode === 'ask' ? null : { id, agentId: owner, action, mode, device: field(device), at: Date.now() };
|
|
58
|
-
await updateGrants((grants) =>
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
69
|
+
await updateGrants((grants) => {
|
|
70
|
+
const next = [...grants.filter((row) => row.id !== id), ...(grant ? [grant] : [])];
|
|
71
|
+
// `noWiden` (a remote client's call): judged on the effective mode inside the
|
|
72
|
+
// same locked read-modify-write, so a block landing meanwhile is never lifted.
|
|
73
|
+
if (noWiden && MODE_RANK[effectiveMode(next, owner, action)] > MODE_RANK[effectiveMode(grants, owner, action)]) {
|
|
74
|
+
throw new Error(NO_WIDEN_MESSAGE);
|
|
75
|
+
}
|
|
76
|
+
return { grants: next, result: grant };
|
|
77
|
+
});
|
|
62
78
|
return grant;
|
|
63
79
|
}
|
|
64
80
|
/** `allow`, `block` or `ask` for `agentId` + `action`; the agent's own grant wins. */
|
|
65
81
|
async function modeFor({ agentId, action }) {
|
|
66
|
-
|
|
67
|
-
const own =
|
|
68
|
-
grants.find((grant) => grant.agentId === agentId) || grants.find((grant) => grant.agentId === ANY_AGENT);
|
|
69
|
-
if (own?.mode === 'allow' && !isRememberableAction(action)) return 'ask';
|
|
70
|
-
return own?.mode || 'ask';
|
|
82
|
+
return effectiveMode(await readGrants(), agentId, action);
|
|
71
83
|
}
|
|
72
84
|
return {
|
|
73
85
|
setMode,
|
|
@@ -151,16 +163,32 @@ export async function agentPermissions(agentId) {
|
|
|
151
163
|
}
|
|
152
164
|
|
|
153
165
|
/** Set one row of that table (capability setAgentPermission); returns the table. */
|
|
154
|
-
export async function setAgentPermission(agentId, action, mode) {
|
|
166
|
+
export async function setAgentPermission(agentId, action, mode, options) {
|
|
155
167
|
const agent = requiredAgentId(agentId);
|
|
156
168
|
if (!ACTION_KINDS.includes(action)) throw new Error(`action must be one of ${ACTION_KINDS.join(', ')}`);
|
|
157
169
|
if (!PERMISSION_MODES.includes(mode)) throw new Error(`mode must be one of ${PERMISSION_MODES.join(', ')}`);
|
|
158
170
|
if (mode === 'allow' && !isRememberableAction(action)) throw new Error(`${action} always asks; it cannot be allowed`);
|
|
159
171
|
const store = await humanGateStore();
|
|
160
|
-
await store.setMode({ agentId: agent, action, mode });
|
|
172
|
+
await store.setMode({ agentId: agent, action, mode, noWiden: options?.noWiden === true });
|
|
161
173
|
return store.permissions(agent);
|
|
162
174
|
}
|
|
163
175
|
|
|
176
|
+
const BROWSER_AUDIT_DECISIONS = Object.freeze(['approve', 'deny', 'granted', 'remembered', 'run-allowed', 'blocked']);
|
|
177
|
+
|
|
178
|
+
/** Audit one Browser Use approval of a bot (capability recordAgentApprovalAudit):
|
|
179
|
+
* the bot, the action kind and what happened — `approve` / `deny` for the card,
|
|
180
|
+
* `granted` when "always allow" was saved, `remembered` when a saved allow let
|
|
181
|
+
* a step run unasked. Never a page address, target or value. */
|
|
182
|
+
export async function recordAgentApprovalAudit(agentId, action, decision) {
|
|
183
|
+
const agent = requiredAgentId(agentId);
|
|
184
|
+
if (!ACTION_KINDS.includes(action)) throw new Error(`action must be one of ${ACTION_KINDS.join(', ')}`);
|
|
185
|
+
if (!BROWSER_AUDIT_DECISIONS.includes(decision)) {
|
|
186
|
+
throw new Error(`decision must be one of ${BROWSER_AUDIT_DECISIONS.join(', ')}`);
|
|
187
|
+
}
|
|
188
|
+
await (await humanGateStore()).recordAudit({ agentId: agent, kind: 'browser', action, tool: 'browser', decision });
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
|
|
164
192
|
/** Replace the process store (tests); null restores the default. */
|
|
165
193
|
export function setHumanGateStore(store) {
|
|
166
194
|
processStore = store || null;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// session/human-gate/unattended-port.mjs
|
|
2
|
+
// The port unattended runs (schedule, webhook, room member) register
|
|
3
|
+
// (session-runtime/held-requests.mjs): `{ runOf(sessionId) → { kind, agentId,
|
|
4
|
+
// name } | null, hold(request, { signal }) → Promise<approval>,
|
|
5
|
+
// humanNeeded(sessionId, outcome, site) → Promise<boolean> }` — the last ends
|
|
6
|
+
// a schedule/webhook run that would need a takeover. gate.mjs re-exports it.
|
|
7
|
+
let unattendedPort = null;
|
|
8
|
+
|
|
9
|
+
export function setUnattendedApprovalPort(port) {
|
|
10
|
+
unattendedPort = port || null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function unattendedApprovalPort() {
|
|
14
|
+
return unattendedPort;
|
|
15
|
+
}
|
|
@@ -838,10 +838,13 @@ function compactionTelemetryRecord(sessionRef, policy, meta, prev) {
|
|
|
838
838
|
meta.compactError ?? meta.lastError,
|
|
839
839
|
prev.lastError
|
|
840
840
|
),
|
|
841
|
+
// The last compaction's wall time: replaced only by a stage that reports
|
|
842
|
+
// a finished compaction's duration; a plain pre-send check keeps it (the
|
|
843
|
+
// other timing fields stay with `...prev`).
|
|
841
844
|
lastDurationMs:
|
|
842
845
|
meta.durationMs != null && Number.isFinite(Number(meta.durationMs))
|
|
843
846
|
? Math.max(0, Math.round(Number(meta.durationMs)))
|
|
844
|
-
: null,
|
|
847
|
+
: (prev.lastDurationMs ?? null),
|
|
845
848
|
compactCount: (prev.compactCount || 0) + (changed ? 1 : 0),
|
|
846
849
|
singleShotConsumed:
|
|
847
850
|
policy.singleShot === true && meta.stage === 'compacting' ? true : prev.singleShotConsumed === true,
|