@animalabs/agent-framework 0.22.0 → 0.22.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 +12 -5
- package/dist/src/agent.d.ts +192 -19
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +407 -66
- package/dist/src/agent.js.map +1 -1
- package/dist/src/api/mcp-server-core.d.ts +41 -0
- package/dist/src/api/mcp-server-core.d.ts.map +1 -0
- package/dist/src/api/mcp-server-core.js +593 -0
- package/dist/src/api/mcp-server-core.js.map +1 -0
- package/dist/src/api/mcp-server.d.ts +2 -36
- package/dist/src/api/mcp-server.d.ts.map +1 -1
- package/dist/src/api/mcp-server.js +15 -595
- package/dist/src/api/mcp-server.js.map +1 -1
- package/dist/src/api/server.d.ts +9 -1
- package/dist/src/api/server.d.ts.map +1 -1
- package/dist/src/api/server.js +84 -23
- package/dist/src/api/server.js.map +1 -1
- package/dist/src/api/types.d.ts +9 -1
- package/dist/src/api/types.d.ts.map +1 -1
- package/dist/src/code-execution/py-runner.d.ts.map +1 -1
- package/dist/src/code-execution/py-runner.js.map +1 -1
- package/dist/src/framework.d.ts +682 -103
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +3202 -718
- package/dist/src/framework.js.map +1 -1
- package/dist/src/gate/event-gate.d.ts.map +1 -1
- package/dist/src/gate/gate-script.d.ts.map +1 -1
- package/dist/src/gate/gate-script.js.map +1 -1
- package/dist/src/index.d.ts +4 -4
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcpl/capability-grant.d.ts.map +1 -1
- package/dist/src/mcpl/channel-registry.d.ts +26 -7
- package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
- package/dist/src/mcpl/channel-registry.js +102 -22
- package/dist/src/mcpl/channel-registry.js.map +1 -1
- package/dist/src/mcpl/checkpoint-manager.d.ts.map +1 -1
- package/dist/src/mcpl/checkpoint-manager.js.map +1 -1
- package/dist/src/mcpl/conversation-router.d.ts.map +1 -1
- package/dist/src/mcpl/errors.js.map +1 -1
- package/dist/src/mcpl/feature-set-manager.d.ts.map +1 -1
- package/dist/src/mcpl/feature-set-manager.js +4 -3
- package/dist/src/mcpl/feature-set-manager.js.map +1 -1
- package/dist/src/mcpl/hook-orchestrator.d.ts.map +1 -1
- package/dist/src/mcpl/index.d.ts +2 -0
- package/dist/src/mcpl/index.d.ts.map +1 -1
- package/dist/src/mcpl/index.js.map +1 -1
- package/dist/src/mcpl/inference-router.d.ts.map +1 -1
- package/dist/src/mcpl/log-value.d.ts +54 -0
- package/dist/src/mcpl/log-value.d.ts.map +1 -0
- package/dist/src/mcpl/log-value.js +109 -0
- package/dist/src/mcpl/log-value.js.map +1 -0
- package/dist/src/mcpl/prose-stream-router.d.ts +6 -6
- package/dist/src/mcpl/prose-stream-router.d.ts.map +1 -1
- package/dist/src/mcpl/prose-stream-router.js +4 -4
- package/dist/src/mcpl/prose-stream-router.js.map +1 -1
- package/dist/src/mcpl/push-coalescer.d.ts +38 -3
- package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
- package/dist/src/mcpl/push-coalescer.js +81 -16
- package/dist/src/mcpl/push-coalescer.js.map +1 -1
- package/dist/src/mcpl/push-handler.d.ts +26 -3
- package/dist/src/mcpl/push-handler.d.ts.map +1 -1
- package/dist/src/mcpl/push-handler.js +87 -23
- package/dist/src/mcpl/push-handler.js.map +1 -1
- package/dist/src/mcpl/reference-fetcher.d.ts +2 -1
- package/dist/src/mcpl/reference-fetcher.d.ts.map +1 -1
- package/dist/src/mcpl/reference-fetcher.js +20 -9
- package/dist/src/mcpl/reference-fetcher.js.map +1 -1
- package/dist/src/mcpl/references.d.ts +29 -6
- package/dist/src/mcpl/references.d.ts.map +1 -1
- package/dist/src/mcpl/references.js +57 -16
- package/dist/src/mcpl/references.js.map +1 -1
- package/dist/src/mcpl/refusal-lines.d.ts +37 -0
- package/dist/src/mcpl/refusal-lines.d.ts.map +1 -0
- package/dist/src/mcpl/refusal-lines.js +61 -0
- package/dist/src/mcpl/refusal-lines.js.map +1 -0
- package/dist/src/mcpl/scope-manager.d.ts.map +1 -1
- package/dist/src/mcpl/server-connection.d.ts +46 -3
- package/dist/src/mcpl/server-connection.d.ts.map +1 -1
- package/dist/src/mcpl/server-connection.js +98 -15
- package/dist/src/mcpl/server-connection.js.map +1 -1
- package/dist/src/mcpl/server-registry.d.ts.map +1 -1
- package/dist/src/mcpl/tool-classes.d.ts +1 -1
- package/dist/src/mcpl/tool-classes.d.ts.map +1 -1
- package/dist/src/mcpl/tool-classes.js +3 -2
- package/dist/src/mcpl/tool-classes.js.map +1 -1
- package/dist/src/mcpl/tool-lifecycle.d.ts.map +1 -1
- package/dist/src/mcpl/tool-lifecycle.js.map +1 -1
- package/dist/src/mcpl/transport.d.ts +23 -5
- package/dist/src/mcpl/transport.d.ts.map +1 -1
- package/dist/src/mcpl/transport.js +103 -10
- package/dist/src/mcpl/transport.js.map +1 -1
- package/dist/src/mcpl/types.d.ts +30 -26
- package/dist/src/mcpl/types.d.ts.map +1 -1
- package/dist/src/mcpl/types.js.map +1 -1
- package/dist/src/module-registry.d.ts +9 -3
- package/dist/src/module-registry.d.ts.map +1 -1
- package/dist/src/module-registry.js +3 -2
- package/dist/src/module-registry.js.map +1 -1
- package/dist/src/modules/api/index.d.ts.map +1 -1
- package/dist/src/modules/discord/discord-js-client.d.ts.map +1 -1
- package/dist/src/modules/discord/discord-js-client.js.map +1 -1
- package/dist/src/modules/discord/index.d.ts.map +1 -1
- package/dist/src/modules/discord/index.js +4 -2
- package/dist/src/modules/discord/index.js.map +1 -1
- package/dist/src/modules/health/index.d.ts.map +1 -1
- package/dist/src/modules/health/index.js.map +1 -1
- package/dist/src/modules/history/index.d.ts.map +1 -1
- package/dist/src/modules/history/index.js.map +1 -1
- package/dist/src/modules/history/semantic.d.ts.map +1 -1
- package/dist/src/modules/history/semantic.js.map +1 -1
- package/dist/src/modules/workspace/branch-intent.d.ts.map +1 -1
- package/dist/src/modules/workspace/branch-intent.js.map +1 -1
- package/dist/src/modules/workspace/disk-agreement.d.ts +22 -4
- package/dist/src/modules/workspace/disk-agreement.d.ts.map +1 -1
- package/dist/src/modules/workspace/disk-agreement.js +63 -25
- package/dist/src/modules/workspace/disk-agreement.js.map +1 -1
- package/dist/src/modules/workspace/effects.d.ts.map +1 -1
- package/dist/src/modules/workspace/index.d.ts +8 -4
- package/dist/src/modules/workspace/index.d.ts.map +1 -1
- package/dist/src/modules/workspace/index.js +85 -16
- package/dist/src/modules/workspace/index.js.map +1 -1
- package/dist/src/modules/workspace/observe.d.ts +56 -11
- package/dist/src/modules/workspace/observe.d.ts.map +1 -1
- package/dist/src/modules/workspace/observe.js +288 -43
- package/dist/src/modules/workspace/observe.js.map +1 -1
- package/dist/src/modules/workspace/reconcile.d.ts +31 -1
- package/dist/src/modules/workspace/reconcile.d.ts.map +1 -1
- package/dist/src/modules/workspace/reconcile.js +63 -12
- package/dist/src/modules/workspace/reconcile.js.map +1 -1
- package/dist/src/modules/workspace/types.d.ts +27 -2
- package/dist/src/modules/workspace/types.d.ts.map +1 -1
- package/dist/src/modules/workspace/types.js.map +1 -1
- package/dist/src/modules/workspace/watcher.d.ts.map +1 -1
- package/dist/src/modules/workspace/watcher.js.map +1 -1
- package/dist/src/operator-log.d.ts +41 -0
- package/dist/src/operator-log.d.ts.map +1 -1
- package/dist/src/operator-log.js.map +1 -1
- package/dist/src/provider-waits.d.ts +216 -0
- package/dist/src/provider-waits.d.ts.map +1 -0
- package/dist/src/provider-waits.js +482 -0
- package/dist/src/provider-waits.js.map +1 -0
- package/dist/src/queue.d.ts.map +1 -1
- package/dist/src/record-journal.d.ts.map +1 -1
- package/dist/src/record-journal.js.map +1 -1
- package/dist/src/recovery/discord-awareness-outbox.d.ts.map +1 -1
- package/dist/src/recovery/discord-awareness-outbox.js.map +1 -1
- package/dist/src/recovery/offline-branch.d.ts.map +1 -1
- package/dist/src/recovery/offline-branch.js +29 -24
- package/dist/src/recovery/offline-branch.js.map +1 -1
- package/dist/src/recovery/recover-cli.js.map +1 -1
- package/dist/src/refusal-reactions.d.ts +14 -12
- package/dist/src/refusal-reactions.d.ts.map +1 -1
- package/dist/src/refusal-reactions.js +14 -12
- package/dist/src/refusal-reactions.js.map +1 -1
- package/dist/src/runtime/liveness-watchdog.d.ts.map +1 -1
- package/dist/src/runtime/liveness-watchdog.js.map +1 -1
- package/dist/src/shutdown.d.ts.map +1 -1
- package/dist/src/shutdown.js.map +1 -1
- package/dist/src/stored-removal.d.ts +73 -0
- package/dist/src/stored-removal.d.ts.map +1 -0
- package/dist/src/stored-removal.js +100 -0
- package/dist/src/stored-removal.js.map +1 -0
- package/dist/src/thinking-token-stamp.js.map +1 -1
- package/dist/src/tool-image-ledger.d.ts.map +1 -1
- package/dist/src/tool-image-ledger.js.map +1 -1
- package/dist/src/tool-presentation.d.ts.map +1 -1
- package/dist/src/tool-presentation.js.map +1 -1
- package/dist/src/tool-result-guard.d.ts +30 -13
- package/dist/src/tool-result-guard.d.ts.map +1 -1
- package/dist/src/tool-result-guard.js +71 -25
- package/dist/src/tool-result-guard.js.map +1 -1
- package/dist/src/tune-out/coordinator.d.ts.map +1 -1
- package/dist/src/tune-out/coordinator.js.map +1 -1
- package/dist/src/tune-out/tools.d.ts +1 -1
- package/dist/src/tune-out/tools.d.ts.map +1 -1
- package/dist/src/types/agent.d.ts +42 -6
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/events.d.ts +12 -0
- package/dist/src/types/events.d.ts.map +1 -1
- package/dist/src/types/framework.d.ts +10 -0
- package/dist/src/types/framework.d.ts.map +1 -1
- package/dist/src/types/module.d.ts +20 -8
- package/dist/src/types/module.d.ts.map +1 -1
- package/dist/src/types/trace.d.ts +75 -13
- package/dist/src/types/trace.d.ts.map +1 -1
- package/dist/src/usage/usage-tracker.d.ts.map +1 -1
- package/dist/test/agent-runtime-settings.test.js +1139 -3
- package/dist/test/agent-runtime-settings.test.js.map +1 -1
- package/dist/test/batched-wake-locus.test.js.map +1 -1
- package/dist/test/channel-label-history.test.js.map +1 -1
- package/dist/test/code-execution.test.js.map +1 -1
- package/dist/test/conversation-fork-injection-scoping.test.js.map +1 -1
- package/dist/test/conversation-router.test.js.map +1 -1
- package/dist/test/conversation-routing.test.js +240 -6
- package/dist/test/conversation-routing.test.js.map +1 -1
- package/dist/test/discord-awareness-outbox.test.js +107 -0
- package/dist/test/discord-awareness-outbox.test.js.map +1 -1
- package/dist/test/discord-typing-keepalive.test.d.ts +2 -0
- package/dist/test/discord-typing-keepalive.test.d.ts.map +1 -0
- package/dist/test/discord-typing-keepalive.test.js +77 -0
- package/dist/test/discord-typing-keepalive.test.js.map +1 -0
- package/dist/test/ephemeral-settle.test.js.map +1 -1
- package/dist/test/event-gate-passthrough.test.js.map +1 -1
- package/dist/test/event-gate-policy-mutation.test.js.map +1 -1
- package/dist/test/event-gate-rate-limit.test.js.map +1 -1
- package/dist/test/event-gate-script.test.js.map +1 -1
- package/dist/test/event-gate-selfwake.test.js.map +1 -1
- package/dist/test/event-gate-tags.test.js.map +1 -1
- package/dist/test/event-gate.test.js.map +1 -1
- package/dist/test/explicit-prose-routing.test.js +363 -44
- package/dist/test/explicit-prose-routing.test.js.map +1 -1
- package/dist/test/framework.test.js +160 -1
- package/dist/test/framework.test.js.map +1 -1
- package/dist/test/helpers/coalescing-fixture.d.ts +2 -0
- package/dist/test/helpers/coalescing-fixture.d.ts.map +1 -1
- package/dist/test/helpers/coalescing-fixture.js +11 -4
- package/dist/test/helpers/coalescing-fixture.js.map +1 -1
- package/dist/test/helpers/mock-membrane.d.ts.map +1 -1
- package/dist/test/helpers/mock-membrane.js.map +1 -1
- package/dist/test/hide-then-turn.test.d.ts +2 -0
- package/dist/test/hide-then-turn.test.d.ts.map +1 -0
- package/dist/test/hide-then-turn.test.js +101 -0
- package/dist/test/hide-then-turn.test.js.map +1 -0
- package/dist/test/history-module-overview.test.js.map +1 -1
- package/dist/test/history-module-semantic.test.js.map +1 -1
- package/dist/test/history-module.test.js.map +1 -1
- package/dist/test/history-semantic-review-findings.test.js.map +1 -1
- package/dist/test/host-quiesce.test.js +125 -0
- package/dist/test/host-quiesce.test.js.map +1 -1
- package/dist/test/inference-exhaustion-rewind.test.js +220 -14
- package/dist/test/inference-exhaustion-rewind.test.js.map +1 -1
- package/dist/test/inference-failure-bounds.test.d.ts +2 -0
- package/dist/test/inference-failure-bounds.test.d.ts.map +1 -0
- package/dist/test/inference-failure-bounds.test.js +1202 -0
- package/dist/test/inference-failure-bounds.test.js.map +1 -0
- package/dist/test/kv-unified-open-flight-retry.test.js.map +1 -1
- package/dist/test/late-events-after-stop.test.d.ts +2 -0
- package/dist/test/late-events-after-stop.test.d.ts.map +1 -0
- package/dist/test/late-events-after-stop.test.js +122 -0
- package/dist/test/late-events-after-stop.test.js.map +1 -0
- package/dist/test/liveness-watchdog.test.js.map +1 -1
- package/dist/test/maintenance-timer.test.js +1564 -2
- package/dist/test/maintenance-timer.test.js.map +1 -1
- package/dist/test/mcp-server-bin.test.d.ts +2 -0
- package/dist/test/mcp-server-bin.test.d.ts.map +1 -0
- package/dist/test/mcp-server-bin.test.js +142 -0
- package/dist/test/mcp-server-bin.test.js.map +1 -0
- package/dist/test/mcpl-awareness-barrier.test.js +141 -0
- package/dist/test/mcpl-awareness-barrier.test.js.map +1 -1
- package/dist/test/mcpl-child-env.test.js +1 -1
- package/dist/test/mcpl-child-env.test.js.map +1 -1
- package/dist/test/mcpl-coalesced-content-parity.test.d.ts +2 -0
- package/dist/test/mcpl-coalesced-content-parity.test.d.ts.map +1 -0
- package/dist/test/mcpl-coalesced-content-parity.test.js +532 -0
- package/dist/test/mcpl-coalesced-content-parity.test.js.map +1 -0
- package/dist/test/mcpl-coalescing.test.js +78 -16
- package/dist/test/mcpl-coalescing.test.js.map +1 -1
- package/dist/test/mcpl-config-diagnostics.test.js +6 -3
- package/dist/test/mcpl-config-diagnostics.test.js.map +1 -1
- package/dist/test/mcpl-gate-roundtrip.test.js.map +1 -1
- package/dist/test/mcpl-manifest-changed.test.js +2 -1
- package/dist/test/mcpl-manifest-changed.test.js.map +1 -1
- package/dist/test/mcpl-reconnect-events.test.js.map +1 -1
- package/dist/test/mcpl-reconnect-reregistration.test.js +2 -0
- package/dist/test/mcpl-reconnect-reregistration.test.js.map +1 -1
- package/dist/test/mcpl-reference-fetcher.test.js +232 -4
- package/dist/test/mcpl-reference-fetcher.test.js.map +1 -1
- package/dist/test/mcpl-reference-fields.test.js +69 -2
- package/dist/test/mcpl-reference-fields.test.js.map +1 -1
- package/dist/test/mcpl-rejection-log-lines.test.d.ts +2 -0
- package/dist/test/mcpl-rejection-log-lines.test.d.ts.map +1 -0
- package/dist/test/mcpl-rejection-log-lines.test.js +349 -0
- package/dist/test/mcpl-rejection-log-lines.test.js.map +1 -0
- package/dist/test/mcpl-stderr-tail.test.d.ts +2 -0
- package/dist/test/mcpl-stderr-tail.test.d.ts.map +1 -0
- package/dist/test/mcpl-stderr-tail.test.js +210 -0
- package/dist/test/mcpl-stderr-tail.test.js.map +1 -0
- package/dist/test/mcpl-tool-lifecycle-e2e.test.js.map +1 -1
- package/dist/test/mcpl-tool-lifecycle-emitter.test.js.map +1 -1
- package/dist/test/mcpl-tool-lifecycle-script.test.js.map +1 -1
- package/dist/test/mcpl-tool-lifecycle.test.js.map +1 -1
- package/dist/test/mcpl-transport-close.test.d.ts +2 -0
- package/dist/test/mcpl-transport-close.test.d.ts.map +1 -0
- package/dist/test/mcpl-transport-close.test.js +373 -0
- package/dist/test/mcpl-transport-close.test.js.map +1 -0
- package/dist/test/mcpl-transport-spawn.test.d.ts +2 -0
- package/dist/test/mcpl-transport-spawn.test.d.ts.map +1 -0
- package/dist/test/mcpl-transport-spawn.test.js +58 -0
- package/dist/test/mcpl-transport-spawn.test.js.map +1 -0
- package/dist/test/mcpl-ws-transport.test.js +104 -0
- package/dist/test/mcpl-ws-transport.test.js.map +1 -1
- package/dist/test/offline-recovery-branch.test.js +344 -0
- package/dist/test/offline-recovery-branch.test.js.map +1 -1
- package/dist/test/operator-surgery.test.js +197 -0
- package/dist/test/operator-surgery.test.js.map +1 -1
- package/dist/test/ops-alert.test.js +2 -1
- package/dist/test/ops-alert.test.js.map +1 -1
- package/dist/test/overbudget-drain-breaker.test.js +1 -0
- package/dist/test/overbudget-drain-breaker.test.js.map +1 -1
- package/dist/test/pack-installs-alone.test.d.ts +2 -0
- package/dist/test/pack-installs-alone.test.d.ts.map +1 -0
- package/dist/test/pack-installs-alone.test.js +284 -0
- package/dist/test/pack-installs-alone.test.js.map +1 -0
- package/dist/test/physical-window-restart.test.d.ts +6 -4
- package/dist/test/physical-window-restart.test.d.ts.map +1 -1
- package/dist/test/physical-window-restart.test.js +275 -10
- package/dist/test/physical-window-restart.test.js.map +1 -1
- package/dist/test/placed-reaction-baseline.test.d.ts +2 -0
- package/dist/test/placed-reaction-baseline.test.d.ts.map +1 -0
- package/dist/test/placed-reaction-baseline.test.js +312 -0
- package/dist/test/placed-reaction-baseline.test.js.map +1 -0
- package/dist/test/present-while-acting.test.js +949 -6
- package/dist/test/present-while-acting.test.js.map +1 -1
- package/dist/test/provider-acceleration-cooldown.test.js +5 -1
- package/dist/test/provider-acceleration-cooldown.test.js.map +1 -1
- package/dist/test/provider-waits.test.d.ts +2 -0
- package/dist/test/provider-waits.test.d.ts.map +1 -0
- package/dist/test/provider-waits.test.js +2259 -0
- package/dist/test/provider-waits.test.js.map +1 -0
- package/dist/test/puppet-tool-call.test.js +2 -0
- package/dist/test/puppet-tool-call.test.js.map +1 -1
- package/dist/test/reaction-baseline-platform.test.d.ts +2 -0
- package/dist/test/reaction-baseline-platform.test.d.ts.map +1 -0
- package/dist/test/reaction-baseline-platform.test.js +124 -0
- package/dist/test/reaction-baseline-platform.test.js.map +1 -0
- package/dist/test/record-journal.test.js.map +1 -1
- package/dist/test/refusal-reaction-baseline.test.js +10 -3
- package/dist/test/refusal-reaction-baseline.test.js.map +1 -1
- package/dist/test/refusal-rewind.test.js +9 -0
- package/dist/test/refusal-rewind.test.js.map +1 -1
- package/dist/test/refusal-single-call.test.js.map +1 -1
- package/dist/test/release-bump.test.d.ts +2 -0
- package/dist/test/release-bump.test.d.ts.map +1 -0
- package/dist/test/release-bump.test.js +248 -0
- package/dist/test/release-bump.test.js.map +1 -0
- package/dist/test/release-changelog.test.js.map +1 -1
- package/dist/test/release-holds.test.d.ts +2 -0
- package/dist/test/release-holds.test.d.ts.map +1 -0
- package/dist/test/release-holds.test.js +456 -0
- package/dist/test/release-holds.test.js.map +1 -0
- package/dist/test/same-round-think-policy.test.js.map +1 -1
- package/dist/test/save-recent-image.test.js +138 -2
- package/dist/test/save-recent-image.test.js.map +1 -1
- package/dist/test/shutdown-errors.test.js.map +1 -1
- package/dist/test/silent-heartbeat-rows.test.js.map +1 -1
- package/dist/test/stop-with-host-operations.test.d.ts +20 -0
- package/dist/test/stop-with-host-operations.test.d.ts.map +1 -0
- package/dist/test/stop-with-host-operations.test.js +1174 -0
- package/dist/test/stop-with-host-operations.test.js.map +1 -0
- package/dist/test/stop-with-work-in-flight.test.js +162 -50
- package/dist/test/stop-with-work-in-flight.test.js.map +1 -1
- package/dist/test/surgery-marks-receipt.test.d.ts +2 -0
- package/dist/test/surgery-marks-receipt.test.d.ts.map +1 -0
- package/dist/test/surgery-marks-receipt.test.js +540 -0
- package/dist/test/surgery-marks-receipt.test.js.map +1 -0
- package/dist/test/tool-batch-hook.test.js.map +1 -1
- package/dist/test/tool-result-guard.test.js +352 -16
- package/dist/test/tool-result-guard.test.js.map +1 -1
- package/dist/test/tool-result-spill.test.js.map +1 -1
- package/dist/test/tool-wrapper-prose-guard-integration.test.js.map +1 -1
- package/dist/test/trunk-channel-routing.test.js +330 -7
- package/dist/test/trunk-channel-routing.test.js.map +1 -1
- package/dist/test/tune-out-e2e.test.js.map +1 -1
- package/dist/test/unconvertible-tool-result.test.js.map +1 -1
- package/dist/test/unsent-compiles.test.d.ts +2 -0
- package/dist/test/unsent-compiles.test.d.ts.map +1 -0
- package/dist/test/unsent-compiles.test.js +139 -0
- package/dist/test/unsent-compiles.test.js.map +1 -0
- package/dist/test/workspace-branch-continuation.test.js.map +1 -1
- package/dist/test/workspace-materialize-freshness.test.js.map +1 -1
- package/dist/test/workspace-read-text.test.js.map +1 -1
- package/dist/test/workspace-three-way.test.js +1022 -7
- package/dist/test/workspace-three-way.test.js.map +1 -1
- package/dist/test/workspace-watcher.test.js.map +1 -1
- package/dist/test/xml-round-persist.test.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +8 -6
- package/src/agent.ts +415 -67
- package/src/api/mcp-server-core.ts +681 -0
- package/src/api/mcp-server.ts +16 -685
- package/src/api/server.ts +87 -26
- package/src/api/types.ts +9 -1
- package/src/framework.ts +3389 -782
- package/src/index.ts +9 -4
- package/src/mcpl/channel-registry.ts +126 -31
- package/src/mcpl/feature-set-manager.ts +4 -3
- package/src/mcpl/index.ts +2 -0
- package/src/mcpl/log-value.ts +116 -0
- package/src/mcpl/prose-stream-router.ts +6 -6
- package/src/mcpl/push-coalescer.ts +76 -12
- package/src/mcpl/push-handler.ts +95 -23
- package/src/mcpl/reference-fetcher.ts +18 -8
- package/src/mcpl/references.ts +57 -13
- package/src/mcpl/refusal-lines.ts +59 -0
- package/src/mcpl/server-connection.ts +125 -15
- package/src/mcpl/tool-classes.ts +3 -2
- package/src/mcpl/transport.ts +105 -14
- package/src/mcpl/types.ts +5 -1
- package/src/module-registry.ts +17 -9
- package/src/modules/discord/index.ts +4 -2
- package/src/modules/workspace/disk-agreement.ts +74 -18
- package/src/modules/workspace/index.ts +85 -15
- package/src/modules/workspace/observe.ts +295 -43
- package/src/modules/workspace/reconcile.ts +90 -12
- package/src/modules/workspace/types.ts +26 -1
- package/src/operator-log.ts +30 -0
- package/src/provider-waits.ts +523 -0
- package/src/recovery/offline-branch.ts +32 -28
- package/src/refusal-reactions.ts +14 -12
- package/src/stored-removal.ts +126 -0
- package/src/tool-result-guard.ts +72 -24
- package/src/types/agent.ts +42 -6
- package/src/types/events.ts +12 -0
- package/src/types/framework.ts +10 -0
- package/src/types/module.ts +20 -8
- package/src/types/trace.ts +76 -14
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* identity, and what they showed — a listing, an absence — is accepted only
|
|
20
20
|
* if each is still the directory its logical path names afterwards. A walk records which
|
|
21
21
|
* directories it listed completely, and only those prove a file absent: an
|
|
22
|
-
* unreadable directory, an ignored subtree, the
|
|
22
|
+
* unreadable directory, an ignored subtree, the walk cap, a symlink the walk
|
|
23
23
|
* doesn't descend, or a directory that changed while it was listed leave
|
|
24
24
|
* everything beneath them unproven. Outside a walk, absence is proven the
|
|
25
25
|
* same way: by a directory inside the mount, still at its path, that lacks
|
|
@@ -494,7 +494,9 @@ export async function observePath(view: MountView, rootReal: string, relativePat
|
|
|
494
494
|
return { kind: 'unobserved', reason: `a symlink that cannot be resolved (${errno(err) ?? String(err)})` };
|
|
495
495
|
}
|
|
496
496
|
if (!contained(rootReal, real)) return { kind: 'unobserved', reason: 'a symlink that leaves the mount' };
|
|
497
|
-
if (!target.isFile())
|
|
497
|
+
if (!target.isFile()) {
|
|
498
|
+
return { kind: 'unobserved', reason: target.isDirectory() ? 'a symlink to a directory, not walked' : 'not a regular file' };
|
|
499
|
+
}
|
|
498
500
|
info = target;
|
|
499
501
|
} else if (info.isDirectory()) {
|
|
500
502
|
return { kind: 'unobserved', reason: 'a directory' };
|
|
@@ -670,22 +672,106 @@ export function sniffImageMime(head: Buffer): string | undefined {
|
|
|
670
672
|
// ===========================================================================
|
|
671
673
|
|
|
672
674
|
export interface Walk {
|
|
673
|
-
/** Regular files
|
|
675
|
+
/** Regular files the walk took: the pass observes each one. */
|
|
674
676
|
files: Set<string>;
|
|
677
|
+
/**
|
|
678
|
+
* Regular files a listing showed that the walk didn't take, past its cap
|
|
679
|
+
* or outside the stretch a resumed walk covered: present, and not
|
|
680
|
+
* observed by this pass.
|
|
681
|
+
*/
|
|
682
|
+
seen: Set<string>;
|
|
683
|
+
/**
|
|
684
|
+
* The names in `others` that aren't ignored and that the walk took, as it
|
|
685
|
+
* takes files, against its cap: the pass observes these by path. The rest
|
|
686
|
+
* are present, and not observed by this pass.
|
|
687
|
+
*/
|
|
688
|
+
othersTaken: Set<string>;
|
|
675
689
|
/** Directories seen. */
|
|
676
690
|
dirs: Set<string>;
|
|
677
691
|
/** Names seen that are neither (symlinks, sockets…), or ignored: present, unproven. */
|
|
678
692
|
others: Map<string, string>;
|
|
679
|
-
/**
|
|
693
|
+
/**
|
|
694
|
+
* Directories whose listing the walk read whole, and that were still the
|
|
695
|
+
* directory at their path once it had: every name in one is in `files`,
|
|
696
|
+
* `seen`, `dirs` or `others`, so a name in none of them is absent.
|
|
697
|
+
*/
|
|
680
698
|
complete: Set<string>;
|
|
681
699
|
/** Directories that don't exist (everything beneath them is absent). */
|
|
682
700
|
missing: Set<string>;
|
|
683
701
|
/**
|
|
684
702
|
* Where the walk didn't reach, and why. `error` and `cap` regions may hide
|
|
685
|
-
* files the listing would show
|
|
686
|
-
*
|
|
703
|
+
* files the listing would show. An `ignored` region is passed over by
|
|
704
|
+
* design: one that hides a tracked entry is reported as incomplete, and
|
|
705
|
+
* one that hides nothing is counted as passed over. A `symlink` region is
|
|
706
|
+
* never walked; the pass reports the link itself by path.
|
|
687
707
|
*/
|
|
688
708
|
incomplete: Array<{ path: string; reason: string; kind: 'error' | 'cap' | 'ignored' | 'symlink' }>;
|
|
709
|
+
/**
|
|
710
|
+
* When the walk cap stopped the walk, the last entry it took. A walk that
|
|
711
|
+
* resumes after it takes the entries after it first, in the walk's order
|
|
712
|
+
* (`compareInWalk`), then wraps to the start of its scope.
|
|
713
|
+
*/
|
|
714
|
+
stoppedAfter?: string;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
/** Names compare by UTF-16 code unit, the same on every platform and filesystem. */
|
|
718
|
+
function compareNames(a: string, b: string): number {
|
|
719
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/**
|
|
723
|
+
* Where file `a` falls against file `b` in the walk's order: in each
|
|
724
|
+
* directory, its files by name, then its subdirectories by name, each with
|
|
725
|
+
* everything beneath it. A resumed walk relies on visiting files in the
|
|
726
|
+
* order it compares positions by, so the walk sorts each listing itself
|
|
727
|
+
* rather than take `readdir`'s order, which is the platform's (Node returns
|
|
728
|
+
* UTF-8 byte order on Linux, which differs from this one for some names).
|
|
729
|
+
*/
|
|
730
|
+
function compareInWalk(a: string, b: string): number {
|
|
731
|
+
const x = a.split('/');
|
|
732
|
+
const y = b.split('/');
|
|
733
|
+
for (let i = 0; ; i++) {
|
|
734
|
+
const lastX = i === x.length - 1;
|
|
735
|
+
const lastY = i === y.length - 1;
|
|
736
|
+
if (x[i] !== y[i]) {
|
|
737
|
+
if (lastX && lastY) return compareNames(x[i]!, y[i]!);
|
|
738
|
+
if (lastX) return -1; // a directory's files come before its subdirectories
|
|
739
|
+
if (lastY) return 1;
|
|
740
|
+
return compareNames(x[i]!, y[i]!);
|
|
741
|
+
}
|
|
742
|
+
if (lastX && lastY) return 0;
|
|
743
|
+
if (lastX) return -1;
|
|
744
|
+
if (lastY) return 1;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
/**
|
|
749
|
+
* Where the subtree of directory `dir` falls against file `at` in the walk's
|
|
750
|
+
* order: wholly before it, holding it (an ancestor of it), or wholly after it.
|
|
751
|
+
*/
|
|
752
|
+
function subtreeAgainst(dir: string, at: string): 'before' | 'holds' | 'after' {
|
|
753
|
+
if (dir === '') return 'holds';
|
|
754
|
+
const d = dir.split('/');
|
|
755
|
+
const p = at.split('/');
|
|
756
|
+
for (let i = 0; i < d.length; i++) {
|
|
757
|
+
// `at` is a file in the directory above d[i], whose files come first.
|
|
758
|
+
if (i === p.length - 1) return 'after';
|
|
759
|
+
if (d[i] !== p[i]) return compareNames(d[i]!, p[i]!) < 0 ? 'before' : 'after';
|
|
760
|
+
}
|
|
761
|
+
return 'holds';
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
/**
|
|
765
|
+
* Whether a mount's ignore list covers a mount-relative path: the path itself
|
|
766
|
+
* or any directory above it, as the walk's own test reads each entry by its
|
|
767
|
+
* path and name.
|
|
768
|
+
*/
|
|
769
|
+
export function coveredByIgnore(relativePath: string, patterns: string[]): boolean {
|
|
770
|
+
const segments = relativePath.split('/').filter(Boolean);
|
|
771
|
+
for (let i = 0; i < segments.length; i++) {
|
|
772
|
+
if (isIgnored(segments.slice(0, i + 1).join('/'), segments[i]!, patterns)) return true;
|
|
773
|
+
}
|
|
774
|
+
return false;
|
|
689
775
|
}
|
|
690
776
|
|
|
691
777
|
/** Simple ignore patterns, as the watcher and the old walk read them. */
|
|
@@ -723,14 +809,32 @@ async function checkChild(view: MountView, rootReal: string, parent: CheckedDir,
|
|
|
723
809
|
|
|
724
810
|
/**
|
|
725
811
|
* List a directory scope ('' for the mount root), descending when recursive,
|
|
726
|
-
* up to `cap`
|
|
727
|
-
*
|
|
728
|
-
*
|
|
729
|
-
*
|
|
730
|
-
*
|
|
812
|
+
* taking up to `cap` entries in the walk's order (`compareInWalk`): files,
|
|
813
|
+
* and links or other entries that aren't directories. A scope
|
|
814
|
+
* that resolves outside the mount is not listed at all; beneath it, the walk
|
|
815
|
+
* only descends real directories, never symlinks. Each directory's listing
|
|
816
|
+
* counts only if, once listed, it is still the directory at its path: a
|
|
817
|
+
* directory moved or replaced meanwhile is an incomplete region, contributing
|
|
818
|
+
* no names and proving nothing absent.
|
|
819
|
+
*
|
|
820
|
+
* Each listing is read whole and every name in it recorded, so the files the
|
|
821
|
+
* cap leaves are known present (`seen`) and a name the listing lacks is
|
|
822
|
+
* proven absent, whichever files were taken. Once the cap is reached, no
|
|
823
|
+
* further directory is listed.
|
|
824
|
+
*
|
|
825
|
+
* With `resumeAfter` (an entry in the scope, from an earlier walk's
|
|
826
|
+
* `stoppedAfter`), a recursive walk takes the entries after it first, then
|
|
827
|
+
* wraps to the start of the scope, so repeated walks cover a scope larger
|
|
828
|
+
* than the cap.
|
|
731
829
|
*/
|
|
732
|
-
export async function walkScope(
|
|
733
|
-
|
|
830
|
+
export async function walkScope(
|
|
831
|
+
view: MountView, rootReal: string, scope: string, recursive: boolean, cap: number,
|
|
832
|
+
opts: { named?: boolean; resumeAfter?: string } = {},
|
|
833
|
+
): Promise<Walk> {
|
|
834
|
+
const walk: Walk = {
|
|
835
|
+
files: new Set(), seen: new Set(), othersTaken: new Set(), dirs: new Set(), others: new Map(), complete: new Set(), missing: new Set(),
|
|
836
|
+
incomplete: [],
|
|
837
|
+
};
|
|
734
838
|
const top = await checkDir(view, rootReal, scope);
|
|
735
839
|
if (top.kind === 'absent') {
|
|
736
840
|
walk.missing.add(scope);
|
|
@@ -740,14 +844,85 @@ export async function walkScope(view: MountView, rootReal: string, scope: string
|
|
|
740
844
|
walk.incomplete.push({ path: scope, reason: top.outside ? 'the directory resolves outside the mount' : top.reason, kind: 'error' });
|
|
741
845
|
return walk;
|
|
742
846
|
}
|
|
743
|
-
|
|
847
|
+
// A scope a path sync names is walked even where the ignore list covers
|
|
848
|
+
// it: a pattern covering the scope itself, or a directory above it, doesn't
|
|
849
|
+
// apply beneath it. So a named directory's contents are synced, as a named
|
|
850
|
+
// file is (agent-framework #276). A listing keeps the ignore: its pass
|
|
851
|
+
// would otherwise start tracking what it lists.
|
|
852
|
+
const ignore = opts.named ? view.ignore.filter((pattern) => !coveredByIgnore(scope, [pattern])) : view.ignore;
|
|
853
|
+
// A position outside the scope, or for a walk that doesn't descend, is no
|
|
854
|
+
// position in this walk's order.
|
|
855
|
+
const after = recursive && opts.resumeAfter !== undefined && (scope === '' || opts.resumeAfter.startsWith(scope + '/'))
|
|
856
|
+
? opts.resumeAfter
|
|
857
|
+
: undefined;
|
|
858
|
+
|
|
859
|
+
/** A listing's entries that aren't directories or ignored, in the walk's order, each a regular file or not. */
|
|
860
|
+
type Listing = { leaves: Array<[string, boolean]>; subdirs: string[]; dev: string; ino: string };
|
|
861
|
+
/**
|
|
862
|
+
* Each directory is listed once: a resumed walk visits the ones holding its
|
|
863
|
+
* position on both sides of it, and the second visit reuses the listing
|
|
864
|
+
* while the directory at its path is still the one it was read from.
|
|
865
|
+
*/
|
|
866
|
+
const listings = new Map<string, Listing | null>();
|
|
867
|
+
const visited = new Set<string>();
|
|
868
|
+
/** Paths already reported as errors, so a second visit doesn't report them again. */
|
|
869
|
+
const failed = new Set<string>();
|
|
870
|
+
const fail = (path: string, reason: string): void => {
|
|
871
|
+
if (failed.has(path)) return;
|
|
872
|
+
failed.add(path);
|
|
873
|
+
walk.incomplete.push({ path, reason, kind: 'error' });
|
|
874
|
+
};
|
|
875
|
+
let left = cap;
|
|
876
|
+
let lastTaken: string | undefined;
|
|
744
877
|
|
|
745
|
-
|
|
878
|
+
/**
|
|
879
|
+
* Take back what the walk read at or beneath `rel`, a directory replaced
|
|
880
|
+
* or gone since it was listed: it describes the directory that was there,
|
|
881
|
+
* so no listing there proves anything absent, the entries and
|
|
882
|
+
* subdirectories they left untaken aren't the cap's to report, and a
|
|
883
|
+
* subdirectory proven missing there proves nothing about what's there
|
|
884
|
+
* now. What was taken stays taken: the pass observes it by path.
|
|
885
|
+
*/
|
|
886
|
+
const dropBeneath = (rel: string): void => {
|
|
887
|
+
const beneath = (path: string): boolean => rel === '' || path.startsWith(rel + '/');
|
|
888
|
+
for (const [path, listing] of listings) {
|
|
889
|
+
if (path !== rel && !beneath(path)) continue;
|
|
890
|
+
if (listing) {
|
|
891
|
+
for (const [leaf, isFile] of listing.leaves) if (isFile) walk.seen.delete(leaf);
|
|
892
|
+
for (const sub of listing.subdirs) walk.dirs.delete(sub);
|
|
893
|
+
}
|
|
894
|
+
walk.complete.delete(path);
|
|
895
|
+
listings.set(path, null);
|
|
896
|
+
}
|
|
897
|
+
// The links, ignored names and other entries those listings showed, and
|
|
898
|
+
// the regions they gave rise to, described the directory that was there.
|
|
899
|
+
for (const [path] of walk.others) if (beneath(path) && !walk.othersTaken.has(path)) walk.others.delete(path);
|
|
900
|
+
for (let i = walk.incomplete.length - 1; i >= 0; i--) {
|
|
901
|
+
const region = walk.incomplete[i]!;
|
|
902
|
+
if ((region.kind === 'symlink' || region.kind === 'ignored') && beneath(region.path)) walk.incomplete.splice(i, 1);
|
|
903
|
+
}
|
|
904
|
+
for (const path of walk.missing) if (beneath(path)) walk.missing.delete(path);
|
|
905
|
+
};
|
|
906
|
+
|
|
907
|
+
const list = async (dir: CheckedDir): Promise<Listing | null> => {
|
|
746
908
|
const rel = dir.rel;
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
909
|
+
const known = listings.get(rel);
|
|
910
|
+
if (known === null) return null;
|
|
911
|
+
if (known !== undefined) {
|
|
912
|
+
// The scope's own directory comes back as the same CheckedDir, so its
|
|
913
|
+
// identity is checked against the path again, as a subdirectory's is
|
|
914
|
+
// by the fresh check that brought it here.
|
|
915
|
+
const now = dir.identity();
|
|
916
|
+
if (known.dev === now.dev && known.ino === now.ino && await dir.stillHere()) return known;
|
|
917
|
+
dropBeneath(rel);
|
|
918
|
+
fail(rel, CHANGED_WALK);
|
|
919
|
+
return null;
|
|
750
920
|
}
|
|
921
|
+
listings.set(rel, null);
|
|
922
|
+
// Reading a directory is a check of it too, and replaces an earlier proof
|
|
923
|
+
// that it was missing: the scope's own directory, read afresh on the
|
|
924
|
+
// wrap, has no check of its own.
|
|
925
|
+
walk.missing.delete(rel);
|
|
751
926
|
let entries;
|
|
752
927
|
try {
|
|
753
928
|
entries = await readdir(dir.real, { withFileTypes: true });
|
|
@@ -756,21 +931,26 @@ export async function walkScope(view: MountView, rootReal: string, scope: string
|
|
|
756
931
|
if (code === 'ENOENT' || code === 'ENOTDIR') {
|
|
757
932
|
// Gone since it was checked: absent beneath only if that can be shown.
|
|
758
933
|
const proof = rel === '' ? ROOT_UNAVAILABLE : await confirmAbsent(view, rootReal, rel, 'beneath');
|
|
759
|
-
if (typeof proof === 'string')
|
|
760
|
-
else
|
|
761
|
-
|
|
934
|
+
if (typeof proof === 'string') fail(rel, proof);
|
|
935
|
+
else {
|
|
936
|
+
walk.missing.add(rel);
|
|
937
|
+
// Nothing was listed, so a later check that finds it again, on the
|
|
938
|
+
// wrap, lists it afresh.
|
|
939
|
+
listings.delete(rel);
|
|
940
|
+
}
|
|
941
|
+
return null;
|
|
762
942
|
}
|
|
763
|
-
|
|
764
|
-
return;
|
|
943
|
+
fail(rel, `cannot list (${code ?? String(err)})`);
|
|
944
|
+
return null;
|
|
765
945
|
}
|
|
766
|
-
|
|
946
|
+
entries.sort((a, b) => compareNames(a.name, b.name));
|
|
947
|
+
const leaves: Array<[string, boolean]> = [];
|
|
767
948
|
const subdirs: string[] = [];
|
|
768
949
|
const others: Array<[string, string]> = [];
|
|
769
950
|
const regions: Walk['incomplete'] = [];
|
|
770
|
-
let reachedCap = false;
|
|
771
951
|
for (const entry of entries) {
|
|
772
952
|
const childRel = rel ? `${rel}/${entry.name}` : entry.name;
|
|
773
|
-
if (isIgnored(childRel, entry.name,
|
|
953
|
+
if (isIgnored(childRel, entry.name, ignore)) {
|
|
774
954
|
others.push([childRel, 'ignored by the mount']);
|
|
775
955
|
regions.push({ path: childRel, reason: 'ignored by the mount', kind: 'ignored' });
|
|
776
956
|
continue;
|
|
@@ -778,15 +958,10 @@ export async function walkScope(view: MountView, rootReal: string, scope: string
|
|
|
778
958
|
if (entry.isDirectory()) {
|
|
779
959
|
subdirs.push(childRel);
|
|
780
960
|
} else if (entry.isFile()) {
|
|
781
|
-
|
|
782
|
-
capped = true;
|
|
783
|
-
reachedCap = true;
|
|
784
|
-
regions.push({ path: rel, reason: `the file cap (${cap}) was reached`, kind: 'cap' });
|
|
785
|
-
break;
|
|
786
|
-
}
|
|
787
|
-
files.push(childRel);
|
|
961
|
+
leaves.push([childRel, true]);
|
|
788
962
|
} else {
|
|
789
963
|
const symlink = entry.isSymbolicLink();
|
|
964
|
+
leaves.push([childRel, false]);
|
|
790
965
|
others.push([childRel, symlink ? 'a symlink' : 'not a regular file']);
|
|
791
966
|
if (symlink) regions.push({ path: childRel, reason: 'a symlink, not walked', kind: 'symlink' });
|
|
792
967
|
}
|
|
@@ -794,25 +969,102 @@ export async function walkScope(view: MountView, rootReal: string, scope: string
|
|
|
794
969
|
// A listing of a directory that was moved or replaced meanwhile says
|
|
795
970
|
// nothing about this path: neither its names nor its absences count.
|
|
796
971
|
if (!(await dir.stillHere())) {
|
|
797
|
-
|
|
798
|
-
return;
|
|
972
|
+
fail(rel, CHANGED_WALK);
|
|
973
|
+
return null;
|
|
799
974
|
}
|
|
800
|
-
for (const
|
|
975
|
+
for (const [p, isFile] of leaves) if (isFile) walk.seen.add(p);
|
|
801
976
|
for (const d of subdirs) walk.dirs.add(d);
|
|
802
977
|
for (const [p, why] of others) walk.others.set(p, why);
|
|
803
978
|
walk.incomplete.push(...regions);
|
|
804
|
-
if (reachedCap) return;
|
|
805
979
|
walk.complete.add(rel);
|
|
980
|
+
const listing = { leaves, subdirs, ...dir.identity() };
|
|
981
|
+
listings.set(rel, listing);
|
|
982
|
+
return listing;
|
|
983
|
+
};
|
|
984
|
+
|
|
985
|
+
type Stretch = 'all' | 'after' | 'upto';
|
|
986
|
+
const fileIn = (stretch: Stretch, file: string): boolean =>
|
|
987
|
+
stretch === 'all' || (compareInWalk(file, after!) > 0) === (stretch === 'after');
|
|
988
|
+
const dirIn = (stretch: Stretch, dir: string): boolean => {
|
|
989
|
+
if (stretch === 'all') return true;
|
|
990
|
+
const at = subtreeAgainst(dir, after!);
|
|
991
|
+
return at === 'holds' || at === (stretch === 'after' ? 'after' : 'before');
|
|
992
|
+
};
|
|
993
|
+
|
|
994
|
+
const visit = async (dir: CheckedDir, stretch: Stretch): Promise<void> => {
|
|
995
|
+
visited.add(dir.rel);
|
|
996
|
+
const listing = await list(dir);
|
|
997
|
+
if (!listing) return;
|
|
998
|
+
// A link or other entry that isn't a directory is taken as a file is,
|
|
999
|
+
// against the cap: the pass observes each one it takes, and following a
|
|
1000
|
+
// link can read as much as a file.
|
|
1001
|
+
for (const [leaf, isFile] of listing.leaves) {
|
|
1002
|
+
if (left === 0) break;
|
|
1003
|
+
if (!fileIn(stretch, leaf)) continue;
|
|
1004
|
+
left--;
|
|
1005
|
+
if (isFile) {
|
|
1006
|
+
walk.seen.delete(leaf);
|
|
1007
|
+
walk.files.add(leaf);
|
|
1008
|
+
} else {
|
|
1009
|
+
walk.othersTaken.add(leaf);
|
|
1010
|
+
}
|
|
1011
|
+
lastTaken = leaf;
|
|
1012
|
+
}
|
|
806
1013
|
if (!recursive) return;
|
|
807
|
-
for (const sub of subdirs) {
|
|
1014
|
+
for (const sub of listing.subdirs) {
|
|
1015
|
+
// Once the cap is reached, no further directory is listed.
|
|
1016
|
+
if (left === 0) break;
|
|
1017
|
+
if (!dirIn(stretch, sub)) continue;
|
|
808
1018
|
const child = await checkChild(view, rootReal, dir, sub);
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
1019
|
+
// This check replaces what an earlier one proved of `sub` itself, such
|
|
1020
|
+
// as missing on the after stretch and found again on the wrap. What was
|
|
1021
|
+
// proven beneath a directory that is still the same one stands, and
|
|
1022
|
+
// list() drops it from one that isn't.
|
|
1023
|
+
walk.missing.delete(sub);
|
|
1024
|
+
if (child.kind === 'checked') {
|
|
1025
|
+
await visit(child.dir, stretch);
|
|
1026
|
+
continue;
|
|
1027
|
+
}
|
|
1028
|
+
// On the wrap, a directory listed on the first stretch that isn't the
|
|
1029
|
+
// directory there now: what the walk read of it describes another.
|
|
1030
|
+
dropBeneath(sub);
|
|
1031
|
+
if (child.kind === 'absent') walk.missing.add(sub);
|
|
1032
|
+
else fail(sub, child.reason);
|
|
812
1033
|
}
|
|
813
1034
|
};
|
|
814
1035
|
|
|
815
|
-
|
|
1036
|
+
if (after === undefined) {
|
|
1037
|
+
await visit(top.dir, 'all');
|
|
1038
|
+
} else {
|
|
1039
|
+
await visit(top.dir, 'after');
|
|
1040
|
+
if (left > 0) await visit(top.dir, 'upto');
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
// What the cap left: directories holding entries not taken, and
|
|
1044
|
+
// directories a listing showed that weren't listed. A walk that doesn't
|
|
1045
|
+
// descend leaves its subdirectories by design.
|
|
1046
|
+
const capped = `the walk cap (${cap}) was reached`;
|
|
1047
|
+
const holdingUntaken = new Set<string>();
|
|
1048
|
+
for (const f of walk.seen) holdingUntaken.add(parentOf(f));
|
|
1049
|
+
for (const [p, why] of walk.others) if (why !== 'ignored by the mount' && !walk.othersTaken.has(p)) holdingUntaken.add(parentOf(p));
|
|
1050
|
+
for (const d of holdingUntaken) {
|
|
1051
|
+
walk.incomplete.push({ path: d, reason: `${capped} before everything here was taken`, kind: 'cap' });
|
|
1052
|
+
}
|
|
1053
|
+
let unlisted = 0;
|
|
1054
|
+
if (recursive) {
|
|
1055
|
+
for (const d of walk.dirs) {
|
|
1056
|
+
if (visited.has(d) || walk.missing.has(d) || failed.has(d)) continue;
|
|
1057
|
+
unlisted++;
|
|
1058
|
+
walk.incomplete.push({ path: d, reason: `not visited: ${capped}`, kind: 'cap' });
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
// The cap stopped the walk only if it left something: a walk whose last
|
|
1062
|
+
// file used up the cap covered its scope. A directory the walk didn't list
|
|
1063
|
+
// counts as left, even one that holds nothing it would take (empty, or
|
|
1064
|
+
// only ignored names): telling them apart would take another read. Such a
|
|
1065
|
+
// walk keeps its place and reports the directory as not visited, and the
|
|
1066
|
+
// next one lists it on its way to the wrap.
|
|
1067
|
+
if (left === 0 && lastTaken !== undefined && (holdingUntaken.size > 0 || unlisted > 0)) walk.stoppedAfter = lastTaken;
|
|
816
1068
|
return walk;
|
|
817
1069
|
}
|
|
818
1070
|
|
|
@@ -829,7 +1081,7 @@ export function provenAbsent(walk: Walk, path: string): boolean {
|
|
|
829
1081
|
if (walk.missing.has(dir)) return true;
|
|
830
1082
|
const child = dir ? `${dir}/${parts[i]}` : parts[i]!;
|
|
831
1083
|
if (walk.complete.has(dir)) {
|
|
832
|
-
if (walk.others.has(child)) return false; // present, but unproven
|
|
1084
|
+
if (walk.others.has(child) || walk.seen.has(child)) return false; // present, but unproven
|
|
833
1085
|
if (i === parts.length - 1) return !walk.files.has(child) && !walk.dirs.has(child);
|
|
834
1086
|
if (!walk.dirs.has(child)) return true; // no directory there: nothing beneath can exist
|
|
835
1087
|
}
|
|
@@ -118,6 +118,15 @@ export interface PassResult {
|
|
|
118
118
|
reports: Map<string, PathReport>;
|
|
119
119
|
/** Regions the pass couldn't observe that matter to its scope. */
|
|
120
120
|
incomplete: Array<{ path: string; reason: string }>;
|
|
121
|
+
/**
|
|
122
|
+
* What the walk passed over by design and reported nowhere else, counted by
|
|
123
|
+
* reason: entries the mount's ignore list covers, when nothing beneath them
|
|
124
|
+
* is tracked (a tracked one is reported under `incomplete`). An ignored
|
|
125
|
+
* directory counts once, everything in it included. Only listings the walk
|
|
126
|
+
* kept count: a directory changed during the walk, or one past the file
|
|
127
|
+
* cap, is reported under `incomplete` instead.
|
|
128
|
+
*/
|
|
129
|
+
passedOver: Map<string, number>;
|
|
121
130
|
/** For a directory scope: the subdirectories disk shows directly beneath it. */
|
|
122
131
|
dirs: string[];
|
|
123
132
|
/**
|
|
@@ -143,6 +152,14 @@ export interface MountRuntime {
|
|
|
143
152
|
treeStateId: string;
|
|
144
153
|
intents: BranchIntents;
|
|
145
154
|
readOnly: boolean;
|
|
155
|
+
/** The most entries one walk of the mount takes, files and the other entries that aren't directories (`MountConfig.walkCap`). */
|
|
156
|
+
walkCap: number;
|
|
157
|
+
/**
|
|
158
|
+
* Whether the mount runs a catch-up scan (`watch: 'always'`, at start and
|
|
159
|
+
* when its watcher reattaches), which takes turns with its whole-mount
|
|
160
|
+
* syncs.
|
|
161
|
+
*/
|
|
162
|
+
catchUpScan: boolean;
|
|
146
163
|
}
|
|
147
164
|
|
|
148
165
|
export type Scope =
|
|
@@ -154,8 +171,21 @@ export interface PassOptions {
|
|
|
154
171
|
rehash?: boolean;
|
|
155
172
|
/** Paths whose disk state is adopted explicitly (a path sync). */
|
|
156
173
|
adopt?: (path: string) => boolean;
|
|
157
|
-
/** Walk cap for directory scopes. */
|
|
174
|
+
/** Walk cap for directory scopes; the mount's `walkCap` when absent. */
|
|
158
175
|
cap?: number;
|
|
176
|
+
/**
|
|
177
|
+
* A recursive directory pass of a sync or a catch-up scan: its walk resumes
|
|
178
|
+
* after the last entry the previous one of this scope took, and records
|
|
179
|
+
* where it stopped, so repeated passes cover a scope larger than the cap.
|
|
180
|
+
* A listing's pass, and an agent-action scan's, start at their scope's
|
|
181
|
+
* start and record no place.
|
|
182
|
+
*/
|
|
183
|
+
resume?: boolean;
|
|
184
|
+
/**
|
|
185
|
+
* The scope was named by a path sync: ignore patterns that cover it don't
|
|
186
|
+
* apply beneath it (see walkScope). A listing's scope keeps them.
|
|
187
|
+
*/
|
|
188
|
+
named?: boolean;
|
|
159
189
|
/**
|
|
160
190
|
* The branch the caller asked on (a path sync). If another is selected when
|
|
161
191
|
* the pass gathers, it decides nothing: an adoption asked for on one branch
|
|
@@ -604,10 +634,16 @@ async function gather(store: JsStore, agreement: DiskAgreement, mount: MountRunt
|
|
|
604
634
|
for (const [p] of agreement.paths(mount.name, scope.dir)) if (inScope(p, scope)) candidates.add(p);
|
|
605
635
|
for (const [p] of mount.intents.list(scope.dir)) if (inScope(p, scope)) candidates.add(p);
|
|
606
636
|
if (rootUnavailable === null) {
|
|
607
|
-
|
|
637
|
+
const resumeAfter = opts.resume ? agreement.walkResumesAfter(mount.name, scope.dir) : undefined;
|
|
638
|
+
walk = await walkScope(mount.view, mount.rootReal, scope.dir, scope.recursive, opts.cap ?? mount.walkCap, {
|
|
639
|
+
named: opts.named,
|
|
640
|
+
...(resumeAfter !== undefined ? { resumeAfter } : {}),
|
|
641
|
+
});
|
|
608
642
|
for (const f of walk.files) if (inScope(f, scope)) candidates.add(f);
|
|
609
|
-
//
|
|
610
|
-
|
|
643
|
+
// The other entries it took (a link, a socket…). Ignored names, and the
|
|
644
|
+
// entries the cap left, are listed only when something tracks them
|
|
645
|
+
// (above).
|
|
646
|
+
for (const p of walk.othersTaken) if (inScope(p, scope)) candidates.add(p);
|
|
611
647
|
for (const d of walk.dirs) if (inScope(d, { kind: 'dir', dir: scope.dir, recursive: false })) gathered.dirs.push(d);
|
|
612
648
|
}
|
|
613
649
|
}
|
|
@@ -623,16 +659,23 @@ async function gather(store: JsStore, agreement: DiskAgreement, mount: MountRunt
|
|
|
623
659
|
gathered.facts.set(path, { kind: 'absent' });
|
|
624
660
|
continue;
|
|
625
661
|
}
|
|
626
|
-
if (walk && !walk.files.has(path) && !walk.others.has(path)) {
|
|
627
|
-
// Beneath a region the walk didn't reach: unproven either way.
|
|
628
|
-
const region = walk.incomplete.find((r) => r.path === '' || path === r.path || path.startsWith(r.path + '/'));
|
|
629
|
-
gathered.facts.set(path, { kind: 'unobserved', reason: region ? region.reason : 'not reached by the walk' });
|
|
630
|
-
continue;
|
|
631
|
-
}
|
|
632
662
|
if (walk?.others.get(path) === 'ignored by the mount') {
|
|
633
663
|
gathered.facts.set(path, { kind: 'unobserved', reason: 'ignored by the mount' });
|
|
634
664
|
continue;
|
|
635
665
|
}
|
|
666
|
+
if (walk && !walk.files.has(path) && !walk.othersTaken.has(path)) {
|
|
667
|
+
// Not taken by the walk, or beneath a region it didn't reach: unproven
|
|
668
|
+
// either way. The nearest region holding it says why.
|
|
669
|
+
let region: Walk['incomplete'][number] | undefined;
|
|
670
|
+
for (const r of walk.incomplete) {
|
|
671
|
+
if (r.path !== '' && path !== r.path && !path.startsWith(r.path + '/')) continue;
|
|
672
|
+
// A link's own region is about what lies beneath it, not the link.
|
|
673
|
+
if (r.kind === 'symlink' && r.path === path) continue;
|
|
674
|
+
if (region === undefined || r.path.length > region.path.length) region = r;
|
|
675
|
+
}
|
|
676
|
+
gathered.facts.set(path, { kind: 'unobserved', reason: region ? region.reason : 'not reached by the walk' });
|
|
677
|
+
continue;
|
|
678
|
+
}
|
|
636
679
|
const p = agreement.get(mount.name, path);
|
|
637
680
|
const hasStore = store.treeGet(mount.treeStateId, path) !== null;
|
|
638
681
|
const hasIntent = mount.intents.get(path) !== null;
|
|
@@ -672,7 +715,7 @@ export async function reconcilePass(
|
|
|
672
715
|
// Evidence a failed barrier left unsynced is settled before anything is
|
|
673
716
|
// decided from it; if the barrier fails again, so does the pass.
|
|
674
717
|
if (agreement.needsBarrier) agreement.barrier();
|
|
675
|
-
const result: PassResult = { ops: [], discarded: [], newConflicts: [], reports: new Map(), incomplete: [], dirs: [] };
|
|
718
|
+
const result: PassResult = { ops: [], discarded: [], newConflicts: [], reports: new Map(), incomplete: [], passedOver: new Map(), dirs: [] };
|
|
676
719
|
|
|
677
720
|
// Enumerate on one branch and observe disk (async). The decision below must
|
|
678
721
|
// describe the same branch the candidates came from: a branch switch during
|
|
@@ -779,6 +822,18 @@ export async function reconcilePass(
|
|
|
779
822
|
agreement.set(mount.name, x.path, next, { afterCommittedState: first });
|
|
780
823
|
first = false;
|
|
781
824
|
}
|
|
825
|
+
// Where the walk stopped, once what it observed is decided: the next pass
|
|
826
|
+
// of this scope resumes after it, or starts afresh once this one covered
|
|
827
|
+
// the scope. A walk that couldn't list its scope's own directory covered
|
|
828
|
+
// none of it, and leaves the place as it was. Unlike evidence, a place
|
|
829
|
+
// binds no root: it only says where the next walk starts, and that walk
|
|
830
|
+
// takes its turn after it in whatever directory is the root by then.
|
|
831
|
+
let resumeAfter: string | null | undefined;
|
|
832
|
+
if (opts.resume && walk && scope.kind === 'dir' && scope.recursive && walk.complete.has(scope.dir)) {
|
|
833
|
+
resumeAfter = walk.stoppedAfter ?? null;
|
|
834
|
+
agreement.recordWalk(mount.name, scope.dir, resumeAfter);
|
|
835
|
+
}
|
|
836
|
+
|
|
782
837
|
// 5. Durable before returning. An agreement advance (D = S) has no pending
|
|
783
838
|
// intent to recover from, so losing its append would leave an older P that
|
|
784
839
|
// another branch would misread as a disk edit.
|
|
@@ -802,9 +857,32 @@ export async function reconcilePass(
|
|
|
802
857
|
}
|
|
803
858
|
if (walk) {
|
|
804
859
|
const tracked = [...result.reports.values()].filter((r) => r.state === 'unverified').map((r) => r.path);
|
|
860
|
+
if (typeof resumeAfter === 'string' && scope.kind === 'dir') {
|
|
861
|
+
// A watched mount's catch-up scan takes turns with its whole-mount
|
|
862
|
+
// syncs; nothing else moves a place.
|
|
863
|
+
const next = scope.dir !== ''
|
|
864
|
+
? 'the next sync of this directory'
|
|
865
|
+
: mount.catchUpScan
|
|
866
|
+
? 'the next sync of the mount, or its catch-up scan if that comes first,'
|
|
867
|
+
: 'the next sync of the mount';
|
|
868
|
+
result.incomplete.push({
|
|
869
|
+
path: scope.dir,
|
|
870
|
+
reason: `the walk cap (${opts.cap ?? mount.walkCap}) was reached: ${next} resumes after ${mount.name}/${resumeAfter}`,
|
|
871
|
+
});
|
|
872
|
+
}
|
|
805
873
|
for (const region of walk.incomplete) {
|
|
806
|
-
|
|
874
|
+
// A link's own region hides only what lies beneath it: the link itself
|
|
875
|
+
// is reported by path, with its own reason.
|
|
876
|
+
const hides = tracked.some((p) => (region.kind !== 'symlink' && p === region.path) || p.startsWith(region.path + '/'));
|
|
807
877
|
if (region.kind === 'error' || region.kind === 'cap' || hides) result.incomplete.push({ path: region.path, reason: region.reason });
|
|
878
|
+
// An ignored name that hides nothing tracked is reported nowhere else:
|
|
879
|
+
// counted, so a sync that says 0 also says what it didn't look at
|
|
880
|
+
// (agent-framework #276). A symlink region isn't counted: a link the
|
|
881
|
+
// walk took is a candidate the pass reports by path, taken through the
|
|
882
|
+
// link when it's a file on a mount that follows symlinks, and
|
|
883
|
+
// otherwise listed under `skipped`; a tracked link the cap left is
|
|
884
|
+
// unverified for the cap.
|
|
885
|
+
else if (region.kind === 'ignored') result.passedOver.set(region.reason, (result.passedOver.get(region.reason) ?? 0) + 1);
|
|
808
886
|
}
|
|
809
887
|
}
|
|
810
888
|
return result;
|
|
@@ -22,7 +22,9 @@ export interface MountConfig {
|
|
|
22
22
|
* - 'on-agent-action': after each completed agent tool batch, the mount is
|
|
23
23
|
* scanned, and the agent's next inference waits for that scan up to
|
|
24
24
|
* `agentActionScanDeadlineMs`; a scan that takes longer finishes while
|
|
25
|
-
* the round goes on
|
|
25
|
+
* the round goes on. Each scan walks from the mount's start, so on a
|
|
26
|
+
* mount over its `walkCap` it covers the first `walkCap` entries every
|
|
27
|
+
* time, and files past them come in as `walkCap` describes
|
|
26
28
|
* - 'never': no background reads; listings and lazy reads still observe
|
|
27
29
|
* the paths they show
|
|
28
30
|
*/
|
|
@@ -42,6 +44,29 @@ export interface MountConfig {
|
|
|
42
44
|
followSymlinks?: boolean;
|
|
43
45
|
/** Maximum file size in bytes (default: 5MB) */
|
|
44
46
|
maxFileSize?: number;
|
|
47
|
+
/**
|
|
48
|
+
* The most entries one walk of this mount takes (default: 5000), a
|
|
49
|
+
* positive integer no larger than Number.MAX_SAFE_INTEGER: files, and
|
|
50
|
+
* links or other entries that aren't
|
|
51
|
+
* directories, each of which the pass observes. A sync that reaches it
|
|
52
|
+
* reports the rest as incomplete, and the next sync of the same scope
|
|
53
|
+
* resumes after the last entry it took, wrapping to the start, so repeated
|
|
54
|
+
* syncs cover a larger mount. A watched mount's catch-up scan (at start,
|
|
55
|
+
* and when its watcher reattaches) takes its turn in the same rotation as
|
|
56
|
+
* the mount's bare syncs, so on a mount over the cap, a change made while
|
|
57
|
+
* the process was down waits for the rotation to reach it. The place is
|
|
58
|
+
* kept with the mount's disk evidence, so a restart resumes there, unless
|
|
59
|
+
* the machine went down before the store made the latest place durable.
|
|
60
|
+
* An agent-action scan (`watch: 'on-agent-action'`) and a listing (`ls`,
|
|
61
|
+
* `glob`, `grep`) take up to it from their scope's start every time. Each
|
|
62
|
+
* directory's listing is read whole, so a deletion is seen wherever the
|
|
63
|
+
* walk lists, whatever it took.
|
|
64
|
+
*
|
|
65
|
+
* It bounds what a walk observes, not the walk's own work: directories
|
|
66
|
+
* don't count against it, and each one the walk visits is read whole, so
|
|
67
|
+
* a tree of many directories that hold nothing is walked in full.
|
|
68
|
+
*/
|
|
69
|
+
walkCap?: number;
|
|
45
70
|
/**
|
|
46
71
|
* If set, filesystem changes on this mount request inference.
|
|
47
72
|
* - `true` — wake on any op (created | modified | deleted)
|