@animalabs/agent-framework 0.22.2 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +108 -4
- package/dist/src/agent.d.ts +108 -17
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +207 -67
- package/dist/src/agent.js.map +1 -1
- package/dist/src/api/mcp-server-core.js +1 -1
- package/dist/src/api/mcp-server-core.js.map +1 -1
- package/dist/src/api/server.d.ts.map +1 -1
- package/dist/src/api/server.js +5 -1
- package/dist/src/api/server.js.map +1 -1
- package/dist/src/api/types.d.ts +1 -1
- package/dist/src/api/types.d.ts.map +1 -1
- package/dist/src/branch-names.d.ts +14 -0
- package/dist/src/branch-names.d.ts.map +1 -0
- package/dist/src/branch-names.js +22 -0
- package/dist/src/branch-names.js.map +1 -0
- package/dist/src/closest-names.d.ts +28 -0
- package/dist/src/closest-names.d.ts.map +1 -0
- package/dist/src/closest-names.js +98 -0
- package/dist/src/closest-names.js.map +1 -0
- package/dist/src/code-execution/index.d.ts +1 -1
- package/dist/src/code-execution/index.d.ts.map +1 -1
- package/dist/src/code-execution/index.js.map +1 -1
- package/dist/src/code-execution/py-runner.d.ts +50 -7
- package/dist/src/code-execution/py-runner.d.ts.map +1 -1
- package/dist/src/code-execution/py-runner.js +258 -90
- package/dist/src/code-execution/py-runner.js.map +1 -1
- package/dist/src/code-execution/runtime-py.d.ts +3 -2
- package/dist/src/code-execution/runtime-py.d.ts.map +1 -1
- package/dist/src/code-execution/runtime-py.js +94 -8
- package/dist/src/code-execution/runtime-py.js.map +1 -1
- package/dist/src/code-execution/tool-definition.d.ts +17 -1
- package/dist/src/code-execution/tool-definition.d.ts.map +1 -1
- package/dist/src/code-execution/tool-definition.js +72 -6
- package/dist/src/code-execution/tool-definition.js.map +1 -1
- package/dist/src/config-checks.d.ts +28 -0
- package/dist/src/config-checks.d.ts.map +1 -0
- package/dist/src/config-checks.js +56 -0
- package/dist/src/config-checks.js.map +1 -0
- package/dist/src/deadline-timer.d.ts +21 -0
- package/dist/src/deadline-timer.d.ts.map +1 -0
- package/dist/src/deadline-timer.js +43 -0
- package/dist/src/deadline-timer.js.map +1 -0
- package/dist/src/error-message.d.ts +30 -0
- package/dist/src/error-message.d.ts.map +1 -0
- package/dist/src/error-message.js +50 -0
- package/dist/src/error-message.js.map +1 -0
- package/dist/src/framework.d.ts +1529 -165
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +8264 -1939
- package/dist/src/framework.js.map +1 -1
- package/dist/src/gate/event-gate.d.ts +8 -0
- package/dist/src/gate/event-gate.d.ts.map +1 -1
- package/dist/src/gate/event-gate.js +36 -5
- package/dist/src/gate/event-gate.js.map +1 -1
- package/dist/src/held-writes.d.ts +16 -0
- package/dist/src/held-writes.d.ts.map +1 -0
- package/dist/src/held-writes.js +56 -0
- package/dist/src/held-writes.js.map +1 -0
- package/dist/src/index.d.ts +10 -4
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +11 -5
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcpl/capability-grant.d.ts +4 -0
- package/dist/src/mcpl/capability-grant.d.ts.map +1 -1
- package/dist/src/mcpl/capability-grant.js +1 -1
- package/dist/src/mcpl/capability-grant.js.map +1 -1
- package/dist/src/mcpl/channel-registry.d.ts +233 -18
- package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
- package/dist/src/mcpl/channel-registry.js +975 -386
- package/dist/src/mcpl/channel-registry.js.map +1 -1
- package/dist/src/mcpl/checkpoint-manager.d.ts +21 -1
- package/dist/src/mcpl/checkpoint-manager.d.ts.map +1 -1
- package/dist/src/mcpl/checkpoint-manager.js +91 -30
- package/dist/src/mcpl/checkpoint-manager.js.map +1 -1
- package/dist/src/mcpl/conversation-router.d.ts +19 -3
- package/dist/src/mcpl/conversation-router.d.ts.map +1 -1
- package/dist/src/mcpl/conversation-router.js +23 -8
- package/dist/src/mcpl/conversation-router.js.map +1 -1
- package/dist/src/mcpl/convert-block.d.ts +20 -0
- package/dist/src/mcpl/convert-block.d.ts.map +1 -0
- package/dist/src/mcpl/convert-block.js +88 -0
- package/dist/src/mcpl/convert-block.js.map +1 -0
- package/dist/src/mcpl/errors.d.ts +13 -0
- package/dist/src/mcpl/errors.d.ts.map +1 -1
- package/dist/src/mcpl/errors.js +28 -0
- package/dist/src/mcpl/errors.js.map +1 -1
- package/dist/src/mcpl/hook-orchestrator.d.ts +23 -0
- package/dist/src/mcpl/hook-orchestrator.d.ts.map +1 -1
- package/dist/src/mcpl/hook-orchestrator.js +83 -72
- package/dist/src/mcpl/hook-orchestrator.js.map +1 -1
- package/dist/src/mcpl/inbound-source.d.ts +114 -0
- package/dist/src/mcpl/inbound-source.d.ts.map +1 -0
- package/dist/src/mcpl/inbound-source.js +83 -0
- package/dist/src/mcpl/inbound-source.js.map +1 -0
- package/dist/src/mcpl/index.d.ts +7 -1
- package/dist/src/mcpl/index.d.ts.map +1 -1
- package/dist/src/mcpl/index.js +5 -1
- package/dist/src/mcpl/index.js.map +1 -1
- package/dist/src/mcpl/inference-router.d.ts +19 -13
- package/dist/src/mcpl/inference-router.d.ts.map +1 -1
- package/dist/src/mcpl/inference-router.js +155 -87
- package/dist/src/mcpl/inference-router.js.map +1 -1
- package/dist/src/mcpl/log-value.d.ts +7 -0
- package/dist/src/mcpl/log-value.d.ts.map +1 -1
- package/dist/src/mcpl/log-value.js +9 -0
- package/dist/src/mcpl/log-value.js.map +1 -1
- package/dist/src/mcpl/modern-connection.d.ts +227 -0
- package/dist/src/mcpl/modern-connection.d.ts.map +1 -0
- package/dist/src/mcpl/modern-connection.js +1088 -0
- package/dist/src/mcpl/modern-connection.js.map +1 -0
- package/dist/src/mcpl/prefix-rule.d.ts +18 -0
- package/dist/src/mcpl/prefix-rule.d.ts.map +1 -0
- package/dist/src/mcpl/prefix-rule.js +25 -0
- package/dist/src/mcpl/prefix-rule.js.map +1 -0
- package/dist/src/mcpl/protocol-family.d.ts +179 -0
- package/dist/src/mcpl/protocol-family.d.ts.map +1 -0
- package/dist/src/mcpl/protocol-family.js +697 -0
- package/dist/src/mcpl/protocol-family.js.map +1 -0
- package/dist/src/mcpl/push-coalescer.d.ts +58 -17
- package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
- package/dist/src/mcpl/push-coalescer.js +93 -32
- package/dist/src/mcpl/push-coalescer.js.map +1 -1
- package/dist/src/mcpl/push-handler.d.ts +16 -9
- package/dist/src/mcpl/push-handler.d.ts.map +1 -1
- package/dist/src/mcpl/push-handler.js +18 -83
- package/dist/src/mcpl/push-handler.js.map +1 -1
- package/dist/src/mcpl/reference-fetcher.d.ts +76 -13
- package/dist/src/mcpl/reference-fetcher.d.ts.map +1 -1
- package/dist/src/mcpl/reference-fetcher.js +275 -66
- package/dist/src/mcpl/reference-fetcher.js.map +1 -1
- package/dist/src/mcpl/references.d.ts +98 -18
- package/dist/src/mcpl/references.d.ts.map +1 -1
- package/dist/src/mcpl/references.js +170 -32
- package/dist/src/mcpl/references.js.map +1 -1
- package/dist/src/mcpl/server-connection.d.ts +406 -31
- package/dist/src/mcpl/server-connection.d.ts.map +1 -1
- package/dist/src/mcpl/server-connection.js +1331 -272
- package/dist/src/mcpl/server-connection.js.map +1 -1
- package/dist/src/mcpl/server-registry.d.ts +35 -6
- package/dist/src/mcpl/server-registry.d.ts.map +1 -1
- package/dist/src/mcpl/server-registry.js +63 -13
- package/dist/src/mcpl/server-registry.js.map +1 -1
- package/dist/src/mcpl/skip-reply-wake.d.ts +36 -0
- package/dist/src/mcpl/skip-reply-wake.d.ts.map +1 -0
- package/dist/src/mcpl/skip-reply-wake.js +55 -0
- package/dist/src/mcpl/skip-reply-wake.js.map +1 -0
- package/dist/src/mcpl/tool-result-normalize.d.ts +54 -0
- package/dist/src/mcpl/tool-result-normalize.d.ts.map +1 -0
- package/dist/src/mcpl/tool-result-normalize.js +232 -0
- package/dist/src/mcpl/tool-result-normalize.js.map +1 -0
- package/dist/src/mcpl/transport.d.ts +104 -12
- package/dist/src/mcpl/transport.d.ts.map +1 -1
- package/dist/src/mcpl/transport.js +315 -85
- package/dist/src/mcpl/transport.js.map +1 -1
- package/dist/src/mcpl/types.d.ts +76 -19
- package/dist/src/mcpl/types.d.ts.map +1 -1
- package/dist/src/mcpl/types.js.map +1 -1
- package/dist/src/mcpl/visible-content.d.ts +16 -0
- package/dist/src/mcpl/visible-content.d.ts.map +1 -1
- package/dist/src/mcpl/visible-content.js +26 -0
- package/dist/src/mcpl/visible-content.js.map +1 -1
- package/dist/src/module-registry.d.ts +34 -6
- package/dist/src/module-registry.d.ts.map +1 -1
- package/dist/src/module-registry.js +119 -31
- package/dist/src/module-registry.js.map +1 -1
- package/dist/src/modules/api/index.d.ts +1 -1
- package/dist/src/modules/api/index.d.ts.map +1 -1
- package/dist/src/modules/api/index.js +3 -2
- package/dist/src/modules/api/index.js.map +1 -1
- package/dist/src/modules/discord/discord-js-client.d.ts +43 -2
- package/dist/src/modules/discord/discord-js-client.d.ts.map +1 -1
- package/dist/src/modules/discord/discord-js-client.js +286 -67
- package/dist/src/modules/discord/discord-js-client.js.map +1 -1
- package/dist/src/modules/discord/index.d.ts +53 -5
- package/dist/src/modules/discord/index.d.ts.map +1 -1
- package/dist/src/modules/discord/index.js +712 -186
- package/dist/src/modules/discord/index.js.map +1 -1
- package/dist/src/modules/discord/refusal.d.ts +14 -0
- package/dist/src/modules/discord/refusal.d.ts.map +1 -0
- package/dist/src/modules/discord/refusal.js +14 -0
- package/dist/src/modules/discord/refusal.js.map +1 -0
- package/dist/src/modules/discord/types.d.ts +27 -4
- package/dist/src/modules/discord/types.d.ts.map +1 -1
- package/dist/src/modules/health/index.d.ts.map +1 -1
- package/dist/src/modules/health/index.js +8 -3
- package/dist/src/modules/health/index.js.map +1 -1
- package/dist/src/modules/history/index.d.ts +3 -1
- package/dist/src/modules/history/index.d.ts.map +1 -1
- package/dist/src/modules/history/index.js +359 -127
- package/dist/src/modules/history/index.js.map +1 -1
- package/dist/src/modules/history/semantic.d.ts +28 -1
- package/dist/src/modules/history/semantic.d.ts.map +1 -1
- package/dist/src/modules/history/semantic.js +177 -32
- package/dist/src/modules/history/semantic.js.map +1 -1
- package/dist/src/modules/index.d.ts +1 -1
- package/dist/src/modules/index.d.ts.map +1 -1
- package/dist/src/modules/index.js +1 -1
- package/dist/src/modules/index.js.map +1 -1
- package/dist/src/modules/unknown-tool.d.ts +10 -0
- package/dist/src/modules/unknown-tool.d.ts.map +1 -0
- package/dist/src/modules/unknown-tool.js +17 -0
- package/dist/src/modules/unknown-tool.js.map +1 -0
- package/dist/src/modules/workspace/disk-agreement.d.ts +57 -4
- package/dist/src/modules/workspace/disk-agreement.d.ts.map +1 -1
- package/dist/src/modules/workspace/disk-agreement.js +75 -7
- package/dist/src/modules/workspace/disk-agreement.js.map +1 -1
- package/dist/src/modules/workspace/effects.d.ts +66 -7
- package/dist/src/modules/workspace/effects.d.ts.map +1 -1
- package/dist/src/modules/workspace/effects.js +303 -46
- package/dist/src/modules/workspace/effects.js.map +1 -1
- package/dist/src/modules/workspace/index.d.ts +313 -45
- package/dist/src/modules/workspace/index.d.ts.map +1 -1
- package/dist/src/modules/workspace/index.js +1404 -304
- package/dist/src/modules/workspace/index.js.map +1 -1
- package/dist/src/modules/workspace/observe.d.ts +315 -16
- package/dist/src/modules/workspace/observe.d.ts.map +1 -1
- package/dist/src/modules/workspace/observe.js +727 -37
- package/dist/src/modules/workspace/observe.js.map +1 -1
- package/dist/src/modules/workspace/reconcile.d.ts +58 -7
- package/dist/src/modules/workspace/reconcile.d.ts.map +1 -1
- package/dist/src/modules/workspace/reconcile.js +521 -56
- package/dist/src/modules/workspace/reconcile.js.map +1 -1
- package/dist/src/modules/workspace/types.d.ts +28 -5
- 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 +49 -2
- package/dist/src/modules/workspace/watcher.d.ts.map +1 -1
- package/dist/src/modules/workspace/watcher.js +158 -29
- package/dist/src/modules/workspace/watcher.js.map +1 -1
- package/dist/src/prose-drafts.d.ts +240 -0
- package/dist/src/prose-drafts.d.ts.map +1 -0
- package/dist/src/prose-drafts.js +362 -0
- package/dist/src/prose-drafts.js.map +1 -0
- package/dist/src/prose-segments.d.ts +21 -2
- package/dist/src/prose-segments.d.ts.map +1 -1
- package/dist/src/prose-segments.js +46 -9
- package/dist/src/prose-segments.js.map +1 -1
- package/dist/src/recovery/offline-branch.d.ts +1 -0
- package/dist/src/recovery/offline-branch.d.ts.map +1 -1
- package/dist/src/recovery/offline-branch.js +3 -1
- package/dist/src/recovery/offline-branch.js.map +1 -1
- package/dist/src/safe-slice.d.ts +5 -1
- package/dist/src/safe-slice.d.ts.map +1 -1
- package/dist/src/safe-slice.js +9 -1
- package/dist/src/safe-slice.js.map +1 -1
- package/dist/src/silent-heartbeat.d.ts +112 -39
- package/dist/src/silent-heartbeat.d.ts.map +1 -1
- package/dist/src/silent-heartbeat.js +100 -29
- package/dist/src/silent-heartbeat.js.map +1 -1
- package/dist/src/tool-args.d.ts +40 -0
- package/dist/src/tool-args.d.ts.map +1 -0
- package/dist/src/tool-args.js +207 -0
- package/dist/src/tool-args.js.map +1 -0
- package/dist/src/tool-image-ledger.d.ts +87 -19
- package/dist/src/tool-image-ledger.d.ts.map +1 -1
- package/dist/src/tool-image-ledger.js +135 -32
- package/dist/src/tool-image-ledger.js.map +1 -1
- package/dist/src/tool-presentation.d.ts +9 -0
- package/dist/src/tool-presentation.d.ts.map +1 -1
- package/dist/src/tool-presentation.js +266 -62
- package/dist/src/tool-presentation.js.map +1 -1
- package/dist/src/tool-result-guard.d.ts.map +1 -1
- package/dist/src/tool-result-guard.js +7 -10
- package/dist/src/tool-result-guard.js.map +1 -1
- package/dist/src/tool-result-history.d.ts +32 -3
- package/dist/src/tool-result-history.d.ts.map +1 -1
- package/dist/src/tool-result-history.js +106 -19
- package/dist/src/tool-result-history.js.map +1 -1
- package/dist/src/tune-out/coordinator.d.ts +25 -1
- package/dist/src/tune-out/coordinator.d.ts.map +1 -1
- package/dist/src/tune-out/coordinator.js +260 -65
- package/dist/src/tune-out/coordinator.js.map +1 -1
- package/dist/src/types/agent.d.ts +44 -22
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/events.d.ts +25 -0
- package/dist/src/types/events.d.ts.map +1 -1
- package/dist/src/types/framework.d.ts +38 -7
- package/dist/src/types/framework.d.ts.map +1 -1
- package/dist/src/types/inference-log.d.ts +1 -1
- package/dist/src/types/inference-log.d.ts.map +1 -1
- package/dist/src/types/module.d.ts +77 -14
- package/dist/src/types/module.d.ts.map +1 -1
- package/dist/src/types/trace.d.ts +97 -8
- package/dist/src/types/trace.d.ts.map +1 -1
- package/dist/src/usage/usage-tracker.d.ts +5 -0
- package/dist/src/usage/usage-tracker.d.ts.map +1 -1
- package/dist/src/usage/usage-tracker.js +35 -8
- package/dist/src/usage/usage-tracker.js.map +1 -1
- package/package.json +5 -4
- package/src/agent.ts +257 -72
- package/src/api/mcp-server-core.ts +1 -1
- package/src/api/server.ts +4 -1
- package/src/api/types.ts +1 -1
- package/src/branch-names.ts +19 -0
- package/src/closest-names.ts +94 -0
- package/src/code-execution/index.ts +1 -0
- package/src/code-execution/py-runner.ts +304 -93
- package/src/code-execution/runtime-py.ts +94 -8
- package/src/code-execution/tool-definition.ts +85 -7
- package/src/config-checks.ts +50 -0
- package/src/deadline-timer.ts +44 -0
- package/src/error-message.ts +48 -0
- package/src/framework.ts +8625 -1984
- package/src/gate/event-gate.ts +38 -5
- package/src/held-writes.ts +59 -0
- package/src/index.ts +29 -5
- package/src/mcpl/capability-grant.ts +1 -1
- package/src/mcpl/channel-registry.ts +1095 -411
- package/src/mcpl/checkpoint-manager.ts +104 -29
- package/src/mcpl/conversation-router.ts +28 -8
- package/src/mcpl/convert-block.ts +105 -0
- package/src/mcpl/errors.ts +28 -0
- package/src/mcpl/hook-orchestrator.ts +84 -77
- package/src/mcpl/inbound-source.ts +156 -0
- package/src/mcpl/index.ts +23 -1
- package/src/mcpl/inference-router.ts +179 -103
- package/src/mcpl/log-value.ts +10 -0
- package/src/mcpl/modern-connection.ts +1182 -0
- package/src/mcpl/prefix-rule.ts +32 -0
- package/src/mcpl/protocol-family.ts +691 -0
- package/src/mcpl/push-coalescer.ts +110 -36
- package/src/mcpl/push-handler.ts +20 -90
- package/src/mcpl/reference-fetcher.ts +318 -67
- package/src/mcpl/references.ts +183 -34
- package/src/mcpl/server-connection.ts +1404 -280
- package/src/mcpl/server-registry.ts +63 -14
- package/src/mcpl/skip-reply-wake.ts +73 -0
- package/src/mcpl/tool-result-normalize.ts +294 -0
- package/src/mcpl/transport.ts +320 -76
- package/src/mcpl/types.ts +77 -19
- package/src/mcpl/visible-content.ts +28 -0
- package/src/module-registry.ts +126 -36
- package/src/modules/api/index.ts +3 -2
- package/src/modules/discord/discord-js-client.ts +306 -74
- package/src/modules/discord/index.ts +737 -201
- package/src/modules/discord/refusal.ts +13 -0
- package/src/modules/discord/types.ts +27 -6
- package/src/modules/health/index.ts +8 -3
- package/src/modules/history/index.ts +410 -120
- package/src/modules/history/semantic.ts +189 -32
- package/src/modules/index.ts +1 -1
- package/src/modules/unknown-tool.ts +16 -0
- package/src/modules/workspace/disk-agreement.ts +97 -8
- package/src/modules/workspace/effects.ts +291 -36
- package/src/modules/workspace/index.ts +1469 -321
- package/src/modules/workspace/observe.ts +784 -41
- package/src/modules/workspace/reconcile.ts +619 -64
- package/src/modules/workspace/types.ts +28 -5
- package/src/modules/workspace/watcher.ts +154 -30
- package/src/prose-drafts.ts +481 -0
- package/src/prose-segments.ts +51 -8
- package/src/recovery/offline-branch.ts +4 -1
- package/src/safe-slice.ts +9 -1
- package/src/silent-heartbeat.ts +184 -59
- package/src/tool-args.ts +213 -0
- package/src/tool-image-ledger.ts +153 -34
- package/src/tool-presentation.ts +233 -42
- package/src/tool-result-guard.ts +7 -8
- package/src/tool-result-history.ts +124 -20
- package/src/tune-out/coordinator.ts +259 -65
- package/src/types/agent.ts +44 -24
- package/src/types/events.ts +25 -0
- package/src/types/framework.ts +38 -7
- package/src/types/inference-log.ts +1 -1
- package/src/types/module.ts +79 -14
- package/src/types/trace.ts +103 -8
- package/src/usage/usage-tracker.ts +32 -8
- package/dist/test/activation-request-sanitize.test.d.ts +0 -2
- package/dist/test/activation-request-sanitize.test.d.ts.map +0 -1
- package/dist/test/activation-request-sanitize.test.js +0 -89
- package/dist/test/activation-request-sanitize.test.js.map +0 -1
- package/dist/test/agent-cache-ttl.test.d.ts +0 -2
- package/dist/test/agent-cache-ttl.test.d.ts.map +0 -1
- package/dist/test/agent-cache-ttl.test.js +0 -41
- package/dist/test/agent-cache-ttl.test.js.map +0 -1
- package/dist/test/agent-runtime-settings.test.d.ts +0 -2
- package/dist/test/agent-runtime-settings.test.d.ts.map +0 -1
- package/dist/test/agent-runtime-settings.test.js +0 -1598
- package/dist/test/agent-runtime-settings.test.js.map +0 -1
- package/dist/test/api-admin-gate.test.d.ts +0 -2
- package/dist/test/api-admin-gate.test.d.ts.map +0 -1
- package/dist/test/api-admin-gate.test.js +0 -116
- package/dist/test/api-admin-gate.test.js.map +0 -1
- package/dist/test/assistant-persist-refusal.test.d.ts +0 -2
- package/dist/test/assistant-persist-refusal.test.d.ts.map +0 -1
- package/dist/test/assistant-persist-refusal.test.js +0 -63
- package/dist/test/assistant-persist-refusal.test.js.map +0 -1
- package/dist/test/batched-wake-locus.test.d.ts +0 -2
- package/dist/test/batched-wake-locus.test.d.ts.map +0 -1
- package/dist/test/batched-wake-locus.test.js +0 -166
- package/dist/test/batched-wake-locus.test.js.map +0 -1
- package/dist/test/busy-agent-scheduler.test.d.ts +0 -2
- package/dist/test/busy-agent-scheduler.test.d.ts.map +0 -1
- package/dist/test/busy-agent-scheduler.test.js +0 -92
- package/dist/test/busy-agent-scheduler.test.js.map +0 -1
- package/dist/test/channel-incoming-targeting.test.d.ts +0 -2
- package/dist/test/channel-incoming-targeting.test.d.ts.map +0 -1
- package/dist/test/channel-incoming-targeting.test.js +0 -81
- package/dist/test/channel-incoming-targeting.test.js.map +0 -1
- package/dist/test/channel-label-history.test.d.ts +0 -2
- package/dist/test/channel-label-history.test.d.ts.map +0 -1
- package/dist/test/channel-label-history.test.js +0 -424
- package/dist/test/channel-label-history.test.js.map +0 -1
- package/dist/test/channel-lifecycle-provenance.test.d.ts +0 -2
- package/dist/test/channel-lifecycle-provenance.test.d.ts.map +0 -1
- package/dist/test/channel-lifecycle-provenance.test.js +0 -47
- package/dist/test/channel-lifecycle-provenance.test.js.map +0 -1
- package/dist/test/channel-registry-routing.test.d.ts +0 -2
- package/dist/test/channel-registry-routing.test.d.ts.map +0 -1
- package/dist/test/channel-registry-routing.test.js +0 -446
- package/dist/test/channel-registry-routing.test.js.map +0 -1
- package/dist/test/channel-registry-typing.test.d.ts +0 -2
- package/dist/test/channel-registry-typing.test.d.ts.map +0 -1
- package/dist/test/channel-registry-typing.test.js +0 -91
- package/dist/test/channel-registry-typing.test.js.map +0 -1
- package/dist/test/channels-changed-respond-first.test.d.ts +0 -2
- package/dist/test/channels-changed-respond-first.test.d.ts.map +0 -1
- package/dist/test/channels-changed-respond-first.test.js +0 -74
- package/dist/test/channels-changed-respond-first.test.js.map +0 -1
- package/dist/test/checkpoint-attribution.test.d.ts +0 -2
- package/dist/test/checkpoint-attribution.test.d.ts.map +0 -1
- package/dist/test/checkpoint-attribution.test.js +0 -200
- package/dist/test/checkpoint-attribution.test.js.map +0 -1
- package/dist/test/code-execution.test.d.ts +0 -11
- package/dist/test/code-execution.test.d.ts.map +0 -1
- package/dist/test/code-execution.test.js +0 -1272
- package/dist/test/code-execution.test.js.map +0 -1
- package/dist/test/component-defaults.test.d.ts +0 -2
- package/dist/test/component-defaults.test.d.ts.map +0 -1
- package/dist/test/component-defaults.test.js +0 -54
- package/dist/test/component-defaults.test.js.map +0 -1
- package/dist/test/conversation-fork-injection-scoping.test.d.ts +0 -2
- package/dist/test/conversation-fork-injection-scoping.test.d.ts.map +0 -1
- package/dist/test/conversation-fork-injection-scoping.test.js +0 -200
- package/dist/test/conversation-fork-injection-scoping.test.js.map +0 -1
- package/dist/test/conversation-router.test.d.ts +0 -2
- package/dist/test/conversation-router.test.d.ts.map +0 -1
- package/dist/test/conversation-router.test.js +0 -174
- package/dist/test/conversation-router.test.js.map +0 -1
- package/dist/test/conversation-routing.test.d.ts +0 -7
- package/dist/test/conversation-routing.test.d.ts.map +0 -1
- package/dist/test/conversation-routing.test.js +0 -621
- package/dist/test/conversation-routing.test.js.map +0 -1
- package/dist/test/deferred-wake-integrity.test.d.ts +0 -29
- package/dist/test/deferred-wake-integrity.test.d.ts.map +0 -1
- package/dist/test/deferred-wake-integrity.test.js +0 -320
- package/dist/test/deferred-wake-integrity.test.js.map +0 -1
- package/dist/test/discord-awareness-outbox.test.d.ts +0 -2
- package/dist/test/discord-awareness-outbox.test.d.ts.map +0 -1
- package/dist/test/discord-awareness-outbox.test.js +0 -371
- package/dist/test/discord-awareness-outbox.test.js.map +0 -1
- package/dist/test/discord-history-delete-window.test.d.ts +0 -2
- package/dist/test/discord-history-delete-window.test.d.ts.map +0 -1
- package/dist/test/discord-history-delete-window.test.js +0 -81
- package/dist/test/discord-history-delete-window.test.js.map +0 -1
- package/dist/test/discord-history-sync.test.d.ts +0 -2
- package/dist/test/discord-history-sync.test.d.ts.map +0 -1
- package/dist/test/discord-history-sync.test.js +0 -56
- package/dist/test/discord-history-sync.test.js.map +0 -1
- package/dist/test/discord-typing-keepalive.test.d.ts +0 -2
- package/dist/test/discord-typing-keepalive.test.d.ts.map +0 -1
- package/dist/test/discord-typing-keepalive.test.js +0 -77
- package/dist/test/discord-typing-keepalive.test.js.map +0 -1
- package/dist/test/ephemeral-settle.test.d.ts +0 -2
- package/dist/test/ephemeral-settle.test.d.ts.map +0 -1
- package/dist/test/ephemeral-settle.test.js +0 -354
- package/dist/test/ephemeral-settle.test.js.map +0 -1
- package/dist/test/event-gate-passthrough.test.d.ts +0 -15
- package/dist/test/event-gate-passthrough.test.d.ts.map +0 -1
- package/dist/test/event-gate-passthrough.test.js +0 -289
- package/dist/test/event-gate-passthrough.test.js.map +0 -1
- package/dist/test/event-gate-policy-mutation.test.d.ts +0 -2
- package/dist/test/event-gate-policy-mutation.test.d.ts.map +0 -1
- package/dist/test/event-gate-policy-mutation.test.js +0 -137
- package/dist/test/event-gate-policy-mutation.test.js.map +0 -1
- package/dist/test/event-gate-rate-limit.test.d.ts +0 -2
- package/dist/test/event-gate-rate-limit.test.d.ts.map +0 -1
- package/dist/test/event-gate-rate-limit.test.js +0 -627
- package/dist/test/event-gate-rate-limit.test.js.map +0 -1
- package/dist/test/event-gate-script.test.d.ts +0 -2
- package/dist/test/event-gate-script.test.d.ts.map +0 -1
- package/dist/test/event-gate-script.test.js +0 -95
- package/dist/test/event-gate-script.test.js.map +0 -1
- package/dist/test/event-gate-selfwake.test.d.ts +0 -15
- package/dist/test/event-gate-selfwake.test.d.ts.map +0 -1
- package/dist/test/event-gate-selfwake.test.js +0 -125
- package/dist/test/event-gate-selfwake.test.js.map +0 -1
- package/dist/test/event-gate-tags.test.d.ts +0 -2
- package/dist/test/event-gate-tags.test.d.ts.map +0 -1
- package/dist/test/event-gate-tags.test.js +0 -76
- package/dist/test/event-gate-tags.test.js.map +0 -1
- package/dist/test/event-gate.test.d.ts +0 -2
- package/dist/test/event-gate.test.d.ts.map +0 -1
- package/dist/test/event-gate.test.js +0 -919
- package/dist/test/event-gate.test.js.map +0 -1
- package/dist/test/explicit-prose-routing.test.d.ts +0 -11
- package/dist/test/explicit-prose-routing.test.d.ts.map +0 -1
- package/dist/test/explicit-prose-routing.test.js +0 -1715
- package/dist/test/explicit-prose-routing.test.js.map +0 -1
- package/dist/test/framework.test.d.ts +0 -5
- package/dist/test/framework.test.d.ts.map +0 -1
- package/dist/test/framework.test.js +0 -1048
- package/dist/test/framework.test.js.map +0 -1
- package/dist/test/gather-context-timeout.test.d.ts +0 -2
- package/dist/test/gather-context-timeout.test.d.ts.map +0 -1
- package/dist/test/gather-context-timeout.test.js +0 -120
- package/dist/test/gather-context-timeout.test.js.map +0 -1
- package/dist/test/health-snapshot.test.d.ts +0 -2
- package/dist/test/health-snapshot.test.d.ts.map +0 -1
- package/dist/test/health-snapshot.test.js +0 -74
- package/dist/test/health-snapshot.test.js.map +0 -1
- package/dist/test/helpers/coalescing-fixture.d.ts +0 -74
- package/dist/test/helpers/coalescing-fixture.d.ts.map +0 -1
- package/dist/test/helpers/coalescing-fixture.js +0 -152
- package/dist/test/helpers/coalescing-fixture.js.map +0 -1
- package/dist/test/helpers/coalescing-kill-child.d.ts +0 -2
- package/dist/test/helpers/coalescing-kill-child.d.ts.map +0 -1
- package/dist/test/helpers/coalescing-kill-child.js +0 -24
- package/dist/test/helpers/coalescing-kill-child.js.map +0 -1
- package/dist/test/helpers/mock-membrane.d.ts +0 -53
- package/dist/test/helpers/mock-membrane.d.ts.map +0 -1
- package/dist/test/helpers/mock-membrane.js +0 -168
- package/dist/test/helpers/mock-membrane.js.map +0 -1
- package/dist/test/helpers/pytc-stop-rows.d.ts +0 -11
- package/dist/test/helpers/pytc-stop-rows.d.ts.map +0 -1
- package/dist/test/helpers/pytc-stop-rows.js +0 -143
- package/dist/test/helpers/pytc-stop-rows.js.map +0 -1
- package/dist/test/helpers/quiesce-crash-child.d.ts +0 -2
- package/dist/test/helpers/quiesce-crash-child.d.ts.map +0 -1
- package/dist/test/helpers/quiesce-crash-child.js +0 -156
- package/dist/test/helpers/quiesce-crash-child.js.map +0 -1
- package/dist/test/hide-then-turn.test.d.ts +0 -2
- package/dist/test/hide-then-turn.test.d.ts.map +0 -1
- package/dist/test/hide-then-turn.test.js +0 -101
- package/dist/test/hide-then-turn.test.js.map +0 -1
- package/dist/test/history-module-overview.test.d.ts +0 -2
- package/dist/test/history-module-overview.test.d.ts.map +0 -1
- package/dist/test/history-module-overview.test.js +0 -711
- package/dist/test/history-module-overview.test.js.map +0 -1
- package/dist/test/history-module-realstore.test.d.ts +0 -2
- package/dist/test/history-module-realstore.test.d.ts.map +0 -1
- package/dist/test/history-module-realstore.test.js +0 -363
- package/dist/test/history-module-realstore.test.js.map +0 -1
- package/dist/test/history-module-semantic.test.d.ts +0 -2
- package/dist/test/history-module-semantic.test.d.ts.map +0 -1
- package/dist/test/history-module-semantic.test.js +0 -213
- package/dist/test/history-module-semantic.test.js.map +0 -1
- package/dist/test/history-module-ux.test.d.ts +0 -2
- package/dist/test/history-module-ux.test.d.ts.map +0 -1
- package/dist/test/history-module-ux.test.js +0 -304
- package/dist/test/history-module-ux.test.js.map +0 -1
- package/dist/test/history-module.test.d.ts +0 -2
- package/dist/test/history-module.test.d.ts.map +0 -1
- package/dist/test/history-module.test.js +0 -619
- package/dist/test/history-module.test.js.map +0 -1
- package/dist/test/history-semantic-review-findings.test.d.ts +0 -2
- package/dist/test/history-semantic-review-findings.test.d.ts.map +0 -1
- package/dist/test/history-semantic-review-findings.test.js +0 -338
- package/dist/test/history-semantic-review-findings.test.js.map +0 -1
- package/dist/test/history-semantic-sync-converges.test.d.ts +0 -2
- package/dist/test/history-semantic-sync-converges.test.d.ts.map +0 -1
- package/dist/test/history-semantic-sync-converges.test.js +0 -109
- package/dist/test/history-semantic-sync-converges.test.js.map +0 -1
- package/dist/test/host-quiesce.test.d.ts +0 -2
- package/dist/test/host-quiesce.test.d.ts.map +0 -1
- package/dist/test/host-quiesce.test.js +0 -988
- package/dist/test/host-quiesce.test.js.map +0 -1
- package/dist/test/inference-exhaustion-rewind.test.d.ts +0 -2
- package/dist/test/inference-exhaustion-rewind.test.d.ts.map +0 -1
- package/dist/test/inference-exhaustion-rewind.test.js +0 -387
- package/dist/test/inference-exhaustion-rewind.test.js.map +0 -1
- package/dist/test/inference-failure-bounds.test.d.ts +0 -2
- package/dist/test/inference-failure-bounds.test.d.ts.map +0 -1
- package/dist/test/inference-failure-bounds.test.js +0 -1202
- package/dist/test/inference-failure-bounds.test.js.map +0 -1
- package/dist/test/inference-failure-observability.test.d.ts +0 -2
- package/dist/test/inference-failure-observability.test.d.ts.map +0 -1
- package/dist/test/inference-failure-observability.test.js +0 -84
- package/dist/test/inference-failure-observability.test.js.map +0 -1
- package/dist/test/journal-tool.test.d.ts +0 -2
- package/dist/test/journal-tool.test.d.ts.map +0 -1
- package/dist/test/journal-tool.test.js +0 -33
- package/dist/test/journal-tool.test.js.map +0 -1
- package/dist/test/kv-unified-open-flight-retry.test.d.ts +0 -2
- package/dist/test/kv-unified-open-flight-retry.test.d.ts.map +0 -1
- package/dist/test/kv-unified-open-flight-retry.test.js +0 -189
- package/dist/test/kv-unified-open-flight-retry.test.js.map +0 -1
- package/dist/test/kv-unified-wiring.test.d.ts +0 -2
- package/dist/test/kv-unified-wiring.test.d.ts.map +0 -1
- package/dist/test/kv-unified-wiring.test.js +0 -95
- package/dist/test/kv-unified-wiring.test.js.map +0 -1
- package/dist/test/late-events-after-stop.test.d.ts +0 -2
- package/dist/test/late-events-after-stop.test.d.ts.map +0 -1
- package/dist/test/late-events-after-stop.test.js +0 -122
- package/dist/test/late-events-after-stop.test.js.map +0 -1
- package/dist/test/liveness-watchdog.test.d.ts +0 -2
- package/dist/test/liveness-watchdog.test.d.ts.map +0 -1
- package/dist/test/liveness-watchdog.test.js +0 -94
- package/dist/test/liveness-watchdog.test.js.map +0 -1
- package/dist/test/machine-close-provenance.test.d.ts +0 -2
- package/dist/test/machine-close-provenance.test.d.ts.map +0 -1
- package/dist/test/machine-close-provenance.test.js +0 -116
- package/dist/test/machine-close-provenance.test.js.map +0 -1
- package/dist/test/maintenance-timer.test.d.ts +0 -2
- package/dist/test/maintenance-timer.test.d.ts.map +0 -1
- package/dist/test/maintenance-timer.test.js +0 -1827
- package/dist/test/maintenance-timer.test.js.map +0 -1
- package/dist/test/mcp-server-bin.test.d.ts +0 -2
- package/dist/test/mcp-server-bin.test.d.ts.map +0 -1
- package/dist/test/mcp-server-bin.test.js +0 -142
- package/dist/test/mcp-server-bin.test.js.map +0 -1
- package/dist/test/mcpl-access-provider.test.d.ts +0 -2
- package/dist/test/mcpl-access-provider.test.d.ts.map +0 -1
- package/dist/test/mcpl-access-provider.test.js +0 -20
- package/dist/test/mcpl-access-provider.test.js.map +0 -1
- package/dist/test/mcpl-awareness-barrier.test.d.ts +0 -2
- package/dist/test/mcpl-awareness-barrier.test.d.ts.map +0 -1
- package/dist/test/mcpl-awareness-barrier.test.js +0 -769
- package/dist/test/mcpl-awareness-barrier.test.js.map +0 -1
- package/dist/test/mcpl-capability-grant.test.d.ts +0 -10
- package/dist/test/mcpl-capability-grant.test.d.ts.map +0 -1
- package/dist/test/mcpl-capability-grant.test.js +0 -111
- package/dist/test/mcpl-capability-grant.test.js.map +0 -1
- package/dist/test/mcpl-capability-scoping.test.d.ts +0 -2
- package/dist/test/mcpl-capability-scoping.test.d.ts.map +0 -1
- package/dist/test/mcpl-capability-scoping.test.js +0 -266
- package/dist/test/mcpl-capability-scoping.test.js.map +0 -1
- package/dist/test/mcpl-child-env.test.d.ts +0 -2
- package/dist/test/mcpl-child-env.test.d.ts.map +0 -1
- package/dist/test/mcpl-child-env.test.js +0 -158
- package/dist/test/mcpl-child-env.test.js.map +0 -1
- package/dist/test/mcpl-coalesced-content-parity.test.d.ts +0 -2
- package/dist/test/mcpl-coalesced-content-parity.test.d.ts.map +0 -1
- package/dist/test/mcpl-coalesced-content-parity.test.js +0 -532
- package/dist/test/mcpl-coalesced-content-parity.test.js.map +0 -1
- package/dist/test/mcpl-coalescing.test.d.ts +0 -2
- package/dist/test/mcpl-coalescing.test.d.ts.map +0 -1
- package/dist/test/mcpl-coalescing.test.js +0 -1125
- package/dist/test/mcpl-coalescing.test.js.map +0 -1
- package/dist/test/mcpl-config-diagnostics.test.d.ts +0 -2
- package/dist/test/mcpl-config-diagnostics.test.d.ts.map +0 -1
- package/dist/test/mcpl-config-diagnostics.test.js +0 -452
- package/dist/test/mcpl-config-diagnostics.test.js.map +0 -1
- package/dist/test/mcpl-empty-content.test.d.ts +0 -2
- package/dist/test/mcpl-empty-content.test.d.ts.map +0 -1
- package/dist/test/mcpl-empty-content.test.js +0 -368
- package/dist/test/mcpl-empty-content.test.js.map +0 -1
- package/dist/test/mcpl-gate-roundtrip.test.d.ts +0 -2
- package/dist/test/mcpl-gate-roundtrip.test.d.ts.map +0 -1
- package/dist/test/mcpl-gate-roundtrip.test.js +0 -285
- package/dist/test/mcpl-gate-roundtrip.test.js.map +0 -1
- package/dist/test/mcpl-list-status.test.d.ts +0 -2
- package/dist/test/mcpl-list-status.test.d.ts.map +0 -1
- package/dist/test/mcpl-list-status.test.js +0 -82
- package/dist/test/mcpl-list-status.test.js.map +0 -1
- package/dist/test/mcpl-manifest-changed.test.d.ts +0 -2
- package/dist/test/mcpl-manifest-changed.test.d.ts.map +0 -1
- package/dist/test/mcpl-manifest-changed.test.js +0 -178
- package/dist/test/mcpl-manifest-changed.test.js.map +0 -1
- package/dist/test/mcpl-push-convert.test.d.ts +0 -2
- package/dist/test/mcpl-push-convert.test.d.ts.map +0 -1
- package/dist/test/mcpl-push-convert.test.js +0 -42
- package/dist/test/mcpl-push-convert.test.js.map +0 -1
- package/dist/test/mcpl-reconnect-events.test.d.ts +0 -2
- package/dist/test/mcpl-reconnect-events.test.d.ts.map +0 -1
- package/dist/test/mcpl-reconnect-events.test.js +0 -144
- package/dist/test/mcpl-reconnect-events.test.js.map +0 -1
- package/dist/test/mcpl-reconnect-reregistration.test.d.ts +0 -2
- package/dist/test/mcpl-reconnect-reregistration.test.d.ts.map +0 -1
- package/dist/test/mcpl-reconnect-reregistration.test.js +0 -238
- package/dist/test/mcpl-reconnect-reregistration.test.js.map +0 -1
- package/dist/test/mcpl-reference-fetcher.test.d.ts +0 -2
- package/dist/test/mcpl-reference-fetcher.test.d.ts.map +0 -1
- package/dist/test/mcpl-reference-fetcher.test.js +0 -427
- package/dist/test/mcpl-reference-fetcher.test.js.map +0 -1
- package/dist/test/mcpl-reference-fields.test.d.ts +0 -2
- package/dist/test/mcpl-reference-fields.test.d.ts.map +0 -1
- package/dist/test/mcpl-reference-fields.test.js +0 -149
- package/dist/test/mcpl-reference-fields.test.js.map +0 -1
- package/dist/test/mcpl-rejection-log-lines.test.d.ts +0 -2
- package/dist/test/mcpl-rejection-log-lines.test.d.ts.map +0 -1
- package/dist/test/mcpl-rejection-log-lines.test.js +0 -349
- package/dist/test/mcpl-rejection-log-lines.test.js.map +0 -1
- package/dist/test/mcpl-request-timeout.test.d.ts +0 -2
- package/dist/test/mcpl-request-timeout.test.d.ts.map +0 -1
- package/dist/test/mcpl-request-timeout.test.js +0 -138
- package/dist/test/mcpl-request-timeout.test.js.map +0 -1
- package/dist/test/mcpl-runtime-lifecycle.test.d.ts +0 -7
- package/dist/test/mcpl-runtime-lifecycle.test.d.ts.map +0 -1
- package/dist/test/mcpl-runtime-lifecycle.test.js +0 -142
- package/dist/test/mcpl-runtime-lifecycle.test.js.map +0 -1
- package/dist/test/mcpl-stderr-tail.test.d.ts +0 -2
- package/dist/test/mcpl-stderr-tail.test.d.ts.map +0 -1
- package/dist/test/mcpl-stderr-tail.test.js +0 -210
- package/dist/test/mcpl-stderr-tail.test.js.map +0 -1
- package/dist/test/mcpl-tool-class-listing.test.d.ts +0 -2
- package/dist/test/mcpl-tool-class-listing.test.d.ts.map +0 -1
- package/dist/test/mcpl-tool-class-listing.test.js +0 -147
- package/dist/test/mcpl-tool-class-listing.test.js.map +0 -1
- package/dist/test/mcpl-tool-classes.test.d.ts +0 -7
- package/dist/test/mcpl-tool-classes.test.d.ts.map +0 -1
- package/dist/test/mcpl-tool-classes.test.js +0 -80
- package/dist/test/mcpl-tool-classes.test.js.map +0 -1
- package/dist/test/mcpl-tool-lifecycle-e2e.test.d.ts +0 -2
- package/dist/test/mcpl-tool-lifecycle-e2e.test.d.ts.map +0 -1
- package/dist/test/mcpl-tool-lifecycle-e2e.test.js +0 -237
- package/dist/test/mcpl-tool-lifecycle-e2e.test.js.map +0 -1
- package/dist/test/mcpl-tool-lifecycle-emitter.test.d.ts +0 -11
- package/dist/test/mcpl-tool-lifecycle-emitter.test.d.ts.map +0 -1
- package/dist/test/mcpl-tool-lifecycle-emitter.test.js +0 -412
- package/dist/test/mcpl-tool-lifecycle-emitter.test.js.map +0 -1
- package/dist/test/mcpl-tool-lifecycle-script.test.d.ts +0 -2
- package/dist/test/mcpl-tool-lifecycle-script.test.d.ts.map +0 -1
- package/dist/test/mcpl-tool-lifecycle-script.test.js +0 -288
- package/dist/test/mcpl-tool-lifecycle-script.test.js.map +0 -1
- package/dist/test/mcpl-tool-lifecycle.test.d.ts +0 -11
- package/dist/test/mcpl-tool-lifecycle.test.d.ts.map +0 -1
- package/dist/test/mcpl-tool-lifecycle.test.js +0 -270
- package/dist/test/mcpl-tool-lifecycle.test.js.map +0 -1
- package/dist/test/mcpl-transport-close.test.d.ts +0 -2
- package/dist/test/mcpl-transport-close.test.d.ts.map +0 -1
- package/dist/test/mcpl-transport-close.test.js +0 -373
- package/dist/test/mcpl-transport-close.test.js.map +0 -1
- package/dist/test/mcpl-transport-spawn.test.d.ts +0 -2
- package/dist/test/mcpl-transport-spawn.test.d.ts.map +0 -1
- package/dist/test/mcpl-transport-spawn.test.js +0 -58
- package/dist/test/mcpl-transport-spawn.test.js.map +0 -1
- package/dist/test/mcpl-ws-transport.test.d.ts +0 -2
- package/dist/test/mcpl-ws-transport.test.d.ts.map +0 -1
- package/dist/test/mcpl-ws-transport.test.js +0 -259
- package/dist/test/mcpl-ws-transport.test.js.map +0 -1
- package/dist/test/module-context-integration.test.d.ts +0 -18
- package/dist/test/module-context-integration.test.d.ts.map +0 -1
- package/dist/test/module-context-integration.test.js +0 -181
- package/dist/test/module-context-integration.test.js.map +0 -1
- package/dist/test/offline-recovery-branch.test.d.ts +0 -2
- package/dist/test/offline-recovery-branch.test.d.ts.map +0 -1
- package/dist/test/offline-recovery-branch.test.js +0 -570
- package/dist/test/offline-recovery-branch.test.js.map +0 -1
- package/dist/test/operator-surgery.test.d.ts +0 -2
- package/dist/test/operator-surgery.test.d.ts.map +0 -1
- package/dist/test/operator-surgery.test.js +0 -595
- package/dist/test/operator-surgery.test.js.map +0 -1
- package/dist/test/ops-alert.test.d.ts +0 -2
- package/dist/test/ops-alert.test.d.ts.map +0 -1
- package/dist/test/ops-alert.test.js +0 -153
- package/dist/test/ops-alert.test.js.map +0 -1
- package/dist/test/overbudget-drain-breaker.test.d.ts +0 -2
- package/dist/test/overbudget-drain-breaker.test.d.ts.map +0 -1
- package/dist/test/overbudget-drain-breaker.test.js +0 -198
- package/dist/test/overbudget-drain-breaker.test.js.map +0 -1
- package/dist/test/pack-installs-alone.test.d.ts +0 -2
- package/dist/test/pack-installs-alone.test.d.ts.map +0 -1
- package/dist/test/pack-installs-alone.test.js +0 -284
- package/dist/test/pack-installs-alone.test.js.map +0 -1
- package/dist/test/per-agent-delivery.test.d.ts +0 -2
- package/dist/test/per-agent-delivery.test.d.ts.map +0 -1
- package/dist/test/per-agent-delivery.test.js +0 -108
- package/dist/test/per-agent-delivery.test.js.map +0 -1
- package/dist/test/physical-window-restart.test.d.ts +0 -16
- package/dist/test/physical-window-restart.test.d.ts.map +0 -1
- package/dist/test/physical-window-restart.test.js +0 -487
- package/dist/test/physical-window-restart.test.js.map +0 -1
- package/dist/test/placed-reaction-baseline.test.d.ts +0 -2
- package/dist/test/placed-reaction-baseline.test.d.ts.map +0 -1
- package/dist/test/placed-reaction-baseline.test.js +0 -312
- package/dist/test/placed-reaction-baseline.test.js.map +0 -1
- package/dist/test/present-while-acting.test.d.ts +0 -19
- package/dist/test/present-while-acting.test.d.ts.map +0 -1
- package/dist/test/present-while-acting.test.js +0 -1649
- package/dist/test/present-while-acting.test.js.map +0 -1
- package/dist/test/prose-segments.test.d.ts +0 -2
- package/dist/test/prose-segments.test.d.ts.map +0 -1
- package/dist/test/prose-segments.test.js +0 -52
- package/dist/test/prose-segments.test.js.map +0 -1
- package/dist/test/prose-stream-router.test.d.ts +0 -2
- package/dist/test/prose-stream-router.test.d.ts.map +0 -1
- package/dist/test/prose-stream-router.test.js +0 -171
- package/dist/test/prose-stream-router.test.js.map +0 -1
- package/dist/test/provider-acceleration-cooldown.test.d.ts +0 -2
- package/dist/test/provider-acceleration-cooldown.test.d.ts.map +0 -1
- package/dist/test/provider-acceleration-cooldown.test.js +0 -435
- package/dist/test/provider-acceleration-cooldown.test.js.map +0 -1
- package/dist/test/provider-waits.test.d.ts +0 -2
- package/dist/test/provider-waits.test.d.ts.map +0 -1
- package/dist/test/provider-waits.test.js +0 -2259
- package/dist/test/provider-waits.test.js.map +0 -1
- package/dist/test/puppet-tool-call.test.d.ts +0 -2
- package/dist/test/puppet-tool-call.test.d.ts.map +0 -1
- package/dist/test/puppet-tool-call.test.js +0 -226
- package/dist/test/puppet-tool-call.test.js.map +0 -1
- package/dist/test/puppet-turn-reservation.test.d.ts +0 -2
- package/dist/test/puppet-turn-reservation.test.d.ts.map +0 -1
- package/dist/test/puppet-turn-reservation.test.js +0 -139
- package/dist/test/puppet-turn-reservation.test.js.map +0 -1
- package/dist/test/reaction-baseline-platform.test.d.ts +0 -2
- package/dist/test/reaction-baseline-platform.test.d.ts.map +0 -1
- package/dist/test/reaction-baseline-platform.test.js +0 -124
- package/dist/test/reaction-baseline-platform.test.js.map +0 -1
- package/dist/test/record-journal.test.d.ts +0 -6
- package/dist/test/record-journal.test.d.ts.map +0 -1
- package/dist/test/record-journal.test.js +0 -269
- package/dist/test/record-journal.test.js.map +0 -1
- package/dist/test/refusal-reaction-baseline.test.d.ts +0 -2
- package/dist/test/refusal-reaction-baseline.test.d.ts.map +0 -1
- package/dist/test/refusal-reaction-baseline.test.js +0 -81
- package/dist/test/refusal-reaction-baseline.test.js.map +0 -1
- package/dist/test/refusal-rewind.test.d.ts +0 -2
- package/dist/test/refusal-rewind.test.d.ts.map +0 -1
- package/dist/test/refusal-rewind.test.js +0 -117
- package/dist/test/refusal-rewind.test.js.map +0 -1
- package/dist/test/refusal-single-call.test.d.ts +0 -2
- package/dist/test/refusal-single-call.test.d.ts.map +0 -1
- package/dist/test/refusal-single-call.test.js +0 -177
- package/dist/test/refusal-single-call.test.js.map +0 -1
- package/dist/test/release-bump.test.d.ts +0 -2
- package/dist/test/release-bump.test.d.ts.map +0 -1
- package/dist/test/release-bump.test.js +0 -248
- package/dist/test/release-bump.test.js.map +0 -1
- package/dist/test/release-changelog.test.d.ts +0 -2
- package/dist/test/release-changelog.test.d.ts.map +0 -1
- package/dist/test/release-changelog.test.js +0 -159
- package/dist/test/release-changelog.test.js.map +0 -1
- package/dist/test/release-holds.test.d.ts +0 -2
- package/dist/test/release-holds.test.d.ts.map +0 -1
- package/dist/test/release-holds.test.js +0 -456
- package/dist/test/release-holds.test.js.map +0 -1
- package/dist/test/same-round-think-policy.test.d.ts +0 -2
- package/dist/test/same-round-think-policy.test.d.ts.map +0 -1
- package/dist/test/same-round-think-policy.test.js +0 -853
- package/dist/test/same-round-think-policy.test.js.map +0 -1
- package/dist/test/save-recent-image.test.d.ts +0 -26
- package/dist/test/save-recent-image.test.d.ts.map +0 -1
- package/dist/test/save-recent-image.test.js +0 -974
- package/dist/test/save-recent-image.test.js.map +0 -1
- package/dist/test/shutdown-errors.test.d.ts +0 -2
- package/dist/test/shutdown-errors.test.d.ts.map +0 -1
- package/dist/test/shutdown-errors.test.js +0 -662
- package/dist/test/shutdown-errors.test.js.map +0 -1
- package/dist/test/silent-heartbeat-rows.test.d.ts +0 -2
- package/dist/test/silent-heartbeat-rows.test.d.ts.map +0 -1
- package/dist/test/silent-heartbeat-rows.test.js +0 -451
- package/dist/test/silent-heartbeat-rows.test.js.map +0 -1
- package/dist/test/silent-heartbeat.test.d.ts +0 -2
- package/dist/test/silent-heartbeat.test.d.ts.map +0 -1
- package/dist/test/silent-heartbeat.test.js +0 -165
- package/dist/test/silent-heartbeat.test.js.map +0 -1
- package/dist/test/state-scaling-e2e.test.d.ts +0 -22
- package/dist/test/state-scaling-e2e.test.d.ts.map +0 -1
- package/dist/test/state-scaling-e2e.test.js +0 -206
- package/dist/test/state-scaling-e2e.test.js.map +0 -1
- package/dist/test/state-scaling.test.d.ts +0 -12
- package/dist/test/state-scaling.test.d.ts.map +0 -1
- package/dist/test/state-scaling.test.js +0 -251
- package/dist/test/state-scaling.test.js.map +0 -1
- package/dist/test/stop-with-host-operations.test.d.ts +0 -20
- package/dist/test/stop-with-host-operations.test.d.ts.map +0 -1
- package/dist/test/stop-with-host-operations.test.js +0 -1174
- package/dist/test/stop-with-host-operations.test.js.map +0 -1
- package/dist/test/stop-with-work-in-flight.test.d.ts +0 -23
- package/dist/test/stop-with-work-in-flight.test.d.ts.map +0 -1
- package/dist/test/stop-with-work-in-flight.test.js +0 -784
- package/dist/test/stop-with-work-in-flight.test.js.map +0 -1
- package/dist/test/surgery-marks-receipt.test.d.ts +0 -2
- package/dist/test/surgery-marks-receipt.test.d.ts.map +0 -1
- package/dist/test/surgery-marks-receipt.test.js +0 -540
- package/dist/test/surgery-marks-receipt.test.js.map +0 -1
- package/dist/test/thinking-token-stamp.test.d.ts +0 -12
- package/dist/test/thinking-token-stamp.test.d.ts.map +0 -1
- package/dist/test/thinking-token-stamp.test.js +0 -129
- package/dist/test/thinking-token-stamp.test.js.map +0 -1
- package/dist/test/timezone.test.d.ts +0 -2
- package/dist/test/timezone.test.d.ts.map +0 -1
- package/dist/test/timezone.test.js +0 -18
- package/dist/test/timezone.test.js.map +0 -1
- package/dist/test/tool-batch-hook.test.d.ts +0 -8
- package/dist/test/tool-batch-hook.test.d.ts.map +0 -1
- package/dist/test/tool-batch-hook.test.js +0 -275
- package/dist/test/tool-batch-hook.test.js.map +0 -1
- package/dist/test/tool-policy.test.d.ts +0 -2
- package/dist/test/tool-policy.test.d.ts.map +0 -1
- package/dist/test/tool-policy.test.js +0 -51
- package/dist/test/tool-policy.test.js.map +0 -1
- package/dist/test/tool-presentation.test.d.ts +0 -2
- package/dist/test/tool-presentation.test.d.ts.map +0 -1
- package/dist/test/tool-presentation.test.js +0 -268
- package/dist/test/tool-presentation.test.js.map +0 -1
- package/dist/test/tool-result-content.test.d.ts +0 -2
- package/dist/test/tool-result-content.test.d.ts.map +0 -1
- package/dist/test/tool-result-content.test.js +0 -37
- package/dist/test/tool-result-content.test.js.map +0 -1
- package/dist/test/tool-result-guard.test.d.ts +0 -2
- package/dist/test/tool-result-guard.test.d.ts.map +0 -1
- package/dist/test/tool-result-guard.test.js +0 -3729
- package/dist/test/tool-result-guard.test.js.map +0 -1
- package/dist/test/tool-result-history.test.d.ts +0 -7
- package/dist/test/tool-result-history.test.d.ts.map +0 -1
- package/dist/test/tool-result-history.test.js +0 -156
- package/dist/test/tool-result-history.test.js.map +0 -1
- package/dist/test/tool-result-spill.test.d.ts +0 -13
- package/dist/test/tool-result-spill.test.d.ts.map +0 -1
- package/dist/test/tool-result-spill.test.js +0 -586
- package/dist/test/tool-result-spill.test.js.map +0 -1
- package/dist/test/tool-wrapper-prose-guard-integration.test.d.ts +0 -2
- package/dist/test/tool-wrapper-prose-guard-integration.test.d.ts.map +0 -1
- package/dist/test/tool-wrapper-prose-guard-integration.test.js +0 -485
- package/dist/test/tool-wrapper-prose-guard-integration.test.js.map +0 -1
- package/dist/test/tool-wrapper-prose-guard.test.d.ts +0 -2
- package/dist/test/tool-wrapper-prose-guard.test.d.ts.map +0 -1
- package/dist/test/tool-wrapper-prose-guard.test.js +0 -34
- package/dist/test/tool-wrapper-prose-guard.test.js.map +0 -1
- package/dist/test/tools-observe-parser-conformance.test.d.ts +0 -2
- package/dist/test/tools-observe-parser-conformance.test.d.ts.map +0 -1
- package/dist/test/tools-observe-parser-conformance.test.js +0 -129
- package/dist/test/tools-observe-parser-conformance.test.js.map +0 -1
- package/dist/test/trunk-channel-routing.test.d.ts +0 -19
- package/dist/test/trunk-channel-routing.test.d.ts.map +0 -1
- package/dist/test/trunk-channel-routing.test.js +0 -652
- package/dist/test/trunk-channel-routing.test.js.map +0 -1
- package/dist/test/tune-out-e2e.test.d.ts +0 -2
- package/dist/test/tune-out-e2e.test.d.ts.map +0 -1
- package/dist/test/tune-out-e2e.test.js +0 -142
- package/dist/test/tune-out-e2e.test.js.map +0 -1
- package/dist/test/tune-out-gate-composition.test.d.ts +0 -2
- package/dist/test/tune-out-gate-composition.test.d.ts.map +0 -1
- package/dist/test/tune-out-gate-composition.test.js +0 -221
- package/dist/test/tune-out-gate-composition.test.js.map +0 -1
- package/dist/test/tune-out-lifecycle.test.d.ts +0 -2
- package/dist/test/tune-out-lifecycle.test.d.ts.map +0 -1
- package/dist/test/tune-out-lifecycle.test.js +0 -121
- package/dist/test/tune-out-lifecycle.test.js.map +0 -1
- package/dist/test/turn-trigger-provenance.test.d.ts +0 -2
- package/dist/test/turn-trigger-provenance.test.d.ts.map +0 -1
- package/dist/test/turn-trigger-provenance.test.js +0 -137
- package/dist/test/turn-trigger-provenance.test.js.map +0 -1
- package/dist/test/unconvertible-tool-result.test.d.ts +0 -2
- package/dist/test/unconvertible-tool-result.test.d.ts.map +0 -1
- package/dist/test/unconvertible-tool-result.test.js +0 -358
- package/dist/test/unconvertible-tool-result.test.js.map +0 -1
- package/dist/test/unsent-compiles.test.d.ts +0 -2
- package/dist/test/unsent-compiles.test.d.ts.map +0 -1
- package/dist/test/unsent-compiles.test.js +0 -139
- package/dist/test/unsent-compiles.test.js.map +0 -1
- package/dist/test/usage-accounting.test.d.ts +0 -2
- package/dist/test/usage-accounting.test.d.ts.map +0 -1
- package/dist/test/usage-accounting.test.js +0 -13
- package/dist/test/usage-accounting.test.js.map +0 -1
- package/dist/test/usage-tracker.test.d.ts +0 -2
- package/dist/test/usage-tracker.test.d.ts.map +0 -1
- package/dist/test/usage-tracker.test.js +0 -129
- package/dist/test/usage-tracker.test.js.map +0 -1
- package/dist/test/utils-surface.test.d.ts +0 -2
- package/dist/test/utils-surface.test.d.ts.map +0 -1
- package/dist/test/utils-surface.test.js +0 -145
- package/dist/test/utils-surface.test.js.map +0 -1
- package/dist/test/workspace-branch-continuation.test.d.ts +0 -15
- package/dist/test/workspace-branch-continuation.test.d.ts.map +0 -1
- package/dist/test/workspace-branch-continuation.test.js +0 -183
- package/dist/test/workspace-branch-continuation.test.js.map +0 -1
- package/dist/test/workspace-materialize-freshness.test.d.ts +0 -16
- package/dist/test/workspace-materialize-freshness.test.d.ts.map +0 -1
- package/dist/test/workspace-materialize-freshness.test.js +0 -252
- package/dist/test/workspace-materialize-freshness.test.js.map +0 -1
- package/dist/test/workspace-path-containment.test.d.ts +0 -19
- package/dist/test/workspace-path-containment.test.d.ts.map +0 -1
- package/dist/test/workspace-path-containment.test.js +0 -91
- package/dist/test/workspace-path-containment.test.js.map +0 -1
- package/dist/test/workspace-read-file-from-disk.test.d.ts +0 -17
- package/dist/test/workspace-read-file-from-disk.test.d.ts.map +0 -1
- package/dist/test/workspace-read-file-from-disk.test.js +0 -174
- package/dist/test/workspace-read-file-from-disk.test.js.map +0 -1
- package/dist/test/workspace-read-image.test.d.ts +0 -2
- package/dist/test/workspace-read-image.test.d.ts.map +0 -1
- package/dist/test/workspace-read-image.test.js +0 -597
- package/dist/test/workspace-read-image.test.js.map +0 -1
- package/dist/test/workspace-read-text.test.d.ts +0 -2
- package/dist/test/workspace-read-text.test.d.ts.map +0 -1
- package/dist/test/workspace-read-text.test.js +0 -101
- package/dist/test/workspace-read-text.test.js.map +0 -1
- package/dist/test/workspace-restore-order.test.d.ts +0 -16
- package/dist/test/workspace-restore-order.test.d.ts.map +0 -1
- package/dist/test/workspace-restore-order.test.js +0 -140
- package/dist/test/workspace-restore-order.test.js.map +0 -1
- package/dist/test/workspace-three-way.test.d.ts +0 -12
- package/dist/test/workspace-three-way.test.d.ts.map +0 -1
- package/dist/test/workspace-three-way.test.js +0 -4094
- package/dist/test/workspace-three-way.test.js.map +0 -1
- package/dist/test/workspace-watcher.test.d.ts +0 -2
- package/dist/test/workspace-watcher.test.d.ts.map +0 -1
- package/dist/test/workspace-watcher.test.js +0 -281
- package/dist/test/workspace-watcher.test.js.map +0 -1
- package/dist/test/xml-round-persist.test.d.ts +0 -19
- package/dist/test/xml-round-persist.test.d.ts.map +0 -1
- package/dist/test/xml-round-persist.test.js +0 -235
- package/dist/test/xml-round-persist.test.js.map +0 -1
- package/dist/tsconfig.tsbuildinfo +0 -1
|
@@ -39,14 +39,16 @@
|
|
|
39
39
|
import { createHash } from 'node:crypto';
|
|
40
40
|
import { constants as fsConstants } from 'node:fs';
|
|
41
41
|
import type { BigIntStats } from 'node:fs';
|
|
42
|
-
import { lstat, mkdir, open, readdir, realpath, stat, statfs } from 'node:fs/promises';
|
|
42
|
+
import { lstat, mkdir, open, readdir, readlink, realpath, stat, statfs } from 'node:fs/promises';
|
|
43
43
|
import type { FileHandle } from 'node:fs/promises';
|
|
44
|
-
import { dirname, join, sep } from 'node:path';
|
|
44
|
+
import { basename, dirname, isAbsolute, join, posix, relative, resolve, sep } from 'node:path';
|
|
45
45
|
import { sameRootIdentity, type Fingerprint, type RootIdentity } from './disk-agreement.js';
|
|
46
46
|
|
|
47
47
|
export interface MountView {
|
|
48
48
|
/** Mount root as configured. */
|
|
49
49
|
root: string;
|
|
50
|
+
/** The mount's name, which a tool puts before a path in the mount. */
|
|
51
|
+
name: string;
|
|
50
52
|
/**
|
|
51
53
|
* The root directory disk last agreed under, once the mount has evidence.
|
|
52
54
|
* A root that resolves to any other directory is unavailable.
|
|
@@ -56,27 +58,58 @@ export interface MountView {
|
|
|
56
58
|
/** Text above this size is not ingested. */
|
|
57
59
|
maxFileSize: number;
|
|
58
60
|
ignore: string[];
|
|
61
|
+
/**
|
|
62
|
+
* Other mounts' directories nested inside this one, mount-relative. Every
|
|
63
|
+
* pass passes over them, named or not, as another mount's directory.
|
|
64
|
+
*/
|
|
65
|
+
others: readonly string[];
|
|
66
|
+
/**
|
|
67
|
+
* The same mounts, each by its directory, its name and, where it has one,
|
|
68
|
+
* its real root (NestedMount), for a refusal that names the mount a path
|
|
69
|
+
* leads into: through a symlink this mount doesn't follow (linkWords), or
|
|
70
|
+
* through one it follows, or a root configured through one (nestedAtReal).
|
|
71
|
+
* Without it, no path is refused for where its links lead, and a link's
|
|
72
|
+
* refusal names no mount.
|
|
73
|
+
*/
|
|
74
|
+
nested?: readonly NestedMount[];
|
|
59
75
|
}
|
|
60
76
|
|
|
77
|
+
/**
|
|
78
|
+
* A mount nested in another: its directory in one of its spellings,
|
|
79
|
+
* mount-relative and `/`-separated, its name, and its real root, which
|
|
80
|
+
* every spelling shares. The real root is the root as realpath resolves it
|
|
81
|
+
* (the nearest existing directory's, where the root isn't made yet) once
|
|
82
|
+
* the module's start has read it, and the root as configured until then.
|
|
83
|
+
* There's none where it's this mount's own or holds it: the mount is nested
|
|
84
|
+
* by its configured path alone, since through its real root it would claim
|
|
85
|
+
* this mount's every path.
|
|
86
|
+
*/
|
|
87
|
+
export type NestedMount = { dir: string; name: string; real?: string };
|
|
88
|
+
|
|
61
89
|
export type StatFingerprint = Omit<Fingerprint, 'hashedAt'>;
|
|
62
90
|
|
|
63
91
|
/**
|
|
64
|
-
* Opened with every
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
92
|
+
* Opened with every open of a workspace path that may already be there, a
|
|
93
|
+
* read, a write in place or a directory's sync, where the platform has it:
|
|
94
|
+
* for a regular file or a directory it changes nothing, and a FIFO or other
|
|
95
|
+
* special file swapped in at the path neither blocks the open nor passes the
|
|
96
|
+
* check of its kind that follows. A create needs none: O_CREAT | O_EXCL fails
|
|
97
|
+
* at once on anything already at the path.
|
|
68
98
|
*/
|
|
69
99
|
export const NONBLOCK = typeof fsConstants.O_NONBLOCK === 'number' ? fsConstants.O_NONBLOCK : 0;
|
|
70
100
|
|
|
71
101
|
export type Observation =
|
|
72
102
|
| { kind: 'absent' }
|
|
73
|
-
|
|
103
|
+
/** `mode`: the file's permission bits (`st_mode & 0o777`). */
|
|
104
|
+
| { kind: 'file'; size: number; mtimeMs: number; mode: number; fp: StatFingerprint }
|
|
74
105
|
| { kind: 'unobserved'; reason: string };
|
|
75
106
|
|
|
76
107
|
export interface ReadResult {
|
|
77
108
|
kind: 'read';
|
|
78
109
|
size: number;
|
|
79
110
|
mtimeMs: number;
|
|
111
|
+
/** The permission bits of the inode the bytes were read from. */
|
|
112
|
+
mode: number;
|
|
80
113
|
fp: Fingerprint;
|
|
81
114
|
hash: string;
|
|
82
115
|
/** The whole file, when it is at most the requested limit. */
|
|
@@ -133,6 +166,494 @@ function lexicalOf(view: MountView, rel: string): string {
|
|
|
133
166
|
return rel === '' ? view.root : join(view.root, rel);
|
|
134
167
|
}
|
|
135
168
|
|
|
169
|
+
/**
|
|
170
|
+
* What linkOnTheWay found on a path's way beneath the mount's root: a
|
|
171
|
+
* symlink (`link`, mount-relative), or a directory there the walk couldn't
|
|
172
|
+
* ask (`unsure`, with the errno it got).
|
|
173
|
+
*/
|
|
174
|
+
export type LinkFound = { link: string } | { unsure: string; code: string };
|
|
175
|
+
|
|
176
|
+
const UNSURE_PREFIX = 'cannot tell whether "';
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* On a mount that doesn't follow symlinks, what is on the way to a
|
|
180
|
+
* mount-relative path: the first directory, from beneath the mount's root
|
|
181
|
+
* down to the path itself (or, `through` 'parents', down to its parent), that
|
|
182
|
+
* is a symlink, or one the walk couldn't ask. Null when there is none, or
|
|
183
|
+
* when the mount follows symlinks. The path is
|
|
184
|
+
* walked as the lookups that follow resolve it, with `.` and `..` taken out,
|
|
185
|
+
* so a `..` after a name that isn't there hides nothing beyond it, and the
|
|
186
|
+
* root, which is the configured path whatever it resolves to, is never on the
|
|
187
|
+
* way, even where the path names it on its way back into the mount. Each
|
|
188
|
+
* directory is asked only whether it is a link, by readlink, from the root
|
|
189
|
+
* down. EINVAL means it isn't, and the walk goes on. Nothing there, a file in
|
|
190
|
+
* the way, or a directory this user can't search (ENOENT, ENOTDIR, EACCES)
|
|
191
|
+
* ends it with nothing found, since nothing beneath is reached through a link
|
|
192
|
+
* and the caller's own lookup meets the same answer. Any other answer, EPERM
|
|
193
|
+
* included, leaves it unsure of what's beneath, and the path is refused for
|
|
194
|
+
* that.
|
|
195
|
+
*/
|
|
196
|
+
export async function linkOnTheWay(
|
|
197
|
+
view: Pick<MountView, 'root' | 'followSymlinks'>,
|
|
198
|
+
rel: string,
|
|
199
|
+
through: 'path' | 'parents' = 'path',
|
|
200
|
+
): Promise<LinkFound | null> {
|
|
201
|
+
if (view.followSymlinks) return null;
|
|
202
|
+
const resolved = relative(resolve(view.root), resolve(view.root, rel));
|
|
203
|
+
if (resolved === '' || resolved === '..' || resolved.startsWith(`..${sep}`) || isAbsolute(resolved)) return null;
|
|
204
|
+
const parts = resolved.split(sep);
|
|
205
|
+
const last = through === 'path' ? parts.length : parts.length - 1;
|
|
206
|
+
for (let i = 1; i <= last; i++) {
|
|
207
|
+
const at = parts.slice(0, i).join('/');
|
|
208
|
+
try {
|
|
209
|
+
await readlink(join(view.root, at));
|
|
210
|
+
return { link: at };
|
|
211
|
+
} catch (err) {
|
|
212
|
+
const code = errno(err);
|
|
213
|
+
if (code === 'EINVAL') continue; // not a link: on to the next directory
|
|
214
|
+
if (code === 'ENOENT' || code === 'ENOTDIR' || code === 'EACCES') return null;
|
|
215
|
+
return { unsure: at, code: code ?? String(err) };
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* On a mount that doesn't follow symlinks, what refuses the mount-relative
|
|
223
|
+
* `rel`, which realpath resolved to `real` (or, `through` 'parents', whose
|
|
224
|
+
* parent it resolved to `real`): the first symlink on its way, or a
|
|
225
|
+
* directory there the walk couldn't ask. Null when the mount follows
|
|
226
|
+
* symlinks, or there's none. Where `real` is the root's canonical path joined
|
|
227
|
+
* with what was resolved (`rel`, or with `through` 'parents', its parent),
|
|
228
|
+
* nothing beneath the root resolved through a link, and nothing more is
|
|
229
|
+
* asked: that is every path through ordinary directories, at no cost
|
|
230
|
+
* per path. Otherwise each directory is asked in turn (linkOnTheWay), since a
|
|
231
|
+
* name spelled otherwise than disk spells it, on a file system that ignores
|
|
232
|
+
* case or Unicode normalization, also resolves to another string, and isn't
|
|
233
|
+
* a link.
|
|
234
|
+
*/
|
|
235
|
+
export async function linkRefusal(
|
|
236
|
+
view: Pick<MountView, 'root' | 'followSymlinks'>,
|
|
237
|
+
rootReal: string,
|
|
238
|
+
rel: string,
|
|
239
|
+
real: string,
|
|
240
|
+
through: 'path' | 'parents' = 'path',
|
|
241
|
+
): Promise<LinkFound | null> {
|
|
242
|
+
if (view.followSymlinks) return null;
|
|
243
|
+
const resolvedAt = through === 'path' ? rel : parentOf(rel);
|
|
244
|
+
if (resolvedAt === '' || real === join(rootReal, resolvedAt)) return null;
|
|
245
|
+
return linkOnTheWay(view, rel, through);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Where a symlink the walk found leads, for its refusal: to a path inside
|
|
250
|
+
* the mount (mount-relative, and whether it's a directory), outside the
|
|
251
|
+
* mount, or nowhere it can be resolved to. One realpath and one stat, made
|
|
252
|
+
* only when refusing.
|
|
253
|
+
*/
|
|
254
|
+
export type LinkLeads = { kind: 'inside'; target: string; directory: boolean } | { kind: 'outside' } | { kind: 'nowhere' };
|
|
255
|
+
|
|
256
|
+
export async function linkLeads(root: string, rootReal: string, link: string): Promise<LinkLeads> {
|
|
257
|
+
const leads = await realpath(join(root, link)).catch(() => null);
|
|
258
|
+
if (leads === null) return { kind: 'nowhere' };
|
|
259
|
+
if (!contained(rootReal, leads)) return { kind: 'outside' };
|
|
260
|
+
const directory = await stat(leads).then((info) => info.isDirectory(), () => false);
|
|
261
|
+
return { kind: 'inside', target: relative(rootReal, leads).split(sep).join('/'), directory };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* The nested mount whose directory holds `target` (mount-relative), the
|
|
266
|
+
* innermost where nested mounts nest, as #460 picks one; undefined when none
|
|
267
|
+
* does.
|
|
268
|
+
*/
|
|
269
|
+
export function nestedHolder(target: string, nested: readonly { dir: string; name: string }[]): { dir: string; name: string } | undefined {
|
|
270
|
+
return nested
|
|
271
|
+
.filter((n) => target === n.dir || target.startsWith(`${n.dir}/`))
|
|
272
|
+
.sort((a, b) => b.dir.length - a.dir.length)[0];
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Why a path is refused for what linkOnTheWay found, given where a link
|
|
277
|
+
* leads (linkLeads). For a link, the link is the subject, with where it
|
|
278
|
+
* leads: for a link inside the mount, its target, and for a link to a
|
|
279
|
+
* directory there, the setting the mount's host can change (the link stands
|
|
280
|
+
* among the path's directories, so following a link to a file can't make the
|
|
281
|
+
* path beneath it exist); for one leading outside the mount, that it does;
|
|
282
|
+
* for one that can't be resolved, nothing more. A target in a nested mount's
|
|
283
|
+
* directory (`nested`) is that mount's: the refusal names the mount and the
|
|
284
|
+
* path to use there in place of the link, and offers no setting, since
|
|
285
|
+
* following the link would only reach into another mount's directory. For a
|
|
286
|
+
* directory the walk couldn't ask, which one, and the answer it got. Paths
|
|
287
|
+
* are spelled as the tools take them, from the mount's name.
|
|
288
|
+
*/
|
|
289
|
+
export function linkWords(name: string, found: LinkFound, leads: LinkLeads | null, nested: readonly { dir: string; name: string }[] = []): string {
|
|
290
|
+
const spell = (rel: string) => `"${rel === '' ? name : `${name}/${rel}`}"`;
|
|
291
|
+
if (!('link' in found)) return `${UNSURE_PREFIX}${name}/${found.unsure}" is a symlink (${found.code})`;
|
|
292
|
+
const link = `${spell(found.link)} is a symlink`;
|
|
293
|
+
if (leads === null || leads.kind === 'nowhere') return `${link}, which this mount does not follow`;
|
|
294
|
+
if (leads.kind === 'outside') return `${link} leading outside the mount, which this mount does not follow`;
|
|
295
|
+
const holder = nestedHolder(leads.target, nested);
|
|
296
|
+
if (holder) {
|
|
297
|
+
const rest = leads.target.slice(holder.dir.length + 1);
|
|
298
|
+
return `${link} to ${spell(leads.target)}, which this mount does not follow; it lies in mount "${holder.name}", ` +
|
|
299
|
+
`nested in mount "${name}": use "${rest ? `${holder.name}/${rest}` : holder.name}" in place of ${spell(found.link)}`;
|
|
300
|
+
}
|
|
301
|
+
const setting = leads.directory ? "; the mount's host can set followSymlinks: true" : '';
|
|
302
|
+
return `${link} to ${spell(leads.target)}, which this mount does not follow${setting}`;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** Why a path is refused for what linkOnTheWay found (linkWords, with where the link leads). */
|
|
306
|
+
export async function linkReason(view: Pick<MountView, 'root' | 'name' | 'nested'>, rootReal: string, found: LinkFound): Promise<string> {
|
|
307
|
+
return linkWords(view.name, found, 'link' in found ? await linkLeads(view.root, rootReal, found.link) : null, view.nested);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* The nested mount whose root holds `real`, a canonical path, the innermost,
|
|
312
|
+
* with the path to use there; undefined when none does. A path the lexical
|
|
313
|
+
* checks pass (inOtherMount, the tools' holderOf) reaches another mount's
|
|
314
|
+
* directory this way through a symlink the mount follows, so each check that
|
|
315
|
+
* resolves a path canonically asks this too.
|
|
316
|
+
*/
|
|
317
|
+
export function nestedAtReal(real: string, nested: readonly NestedMount[] = []): { name: string; use: string } | undefined {
|
|
318
|
+
const holder = nested
|
|
319
|
+
.flatMap((n) => (n.real !== undefined && contained(n.real, real) ? [{ name: n.name, real: n.real }] : []))
|
|
320
|
+
.sort((a, b) => b.real.length - a.real.length)[0];
|
|
321
|
+
if (!holder) return undefined;
|
|
322
|
+
const rest = relative(holder.real, real).split(sep).join('/');
|
|
323
|
+
return { name: holder.name, use: rest ? `${holder.name}/${rest}` : holder.name };
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Why a mount-relative path that resolves into a nested mount's directory is
|
|
328
|
+
* refused (nestedAtReal): what it leads into, and the path to use in its
|
|
329
|
+
* place. Spelled as the tools take paths, from the mount's name.
|
|
330
|
+
*/
|
|
331
|
+
export function leadsInto(name: string, rel: string, holder: { name: string; use: string }): string {
|
|
332
|
+
return `"${rel === '' ? name : `${name}/${rel}`}" leads into mount "${holder.name}", nested in mount "${name}": use "${holder.use}" in its place`;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// ===========================================================================
|
|
336
|
+
// Disk's spelling of a path
|
|
337
|
+
// ===========================================================================
|
|
338
|
+
|
|
339
|
+
const sameFile = (a: BigIntStats, b: BigIntStats): boolean => a.dev === b.dev && a.ino === b.ino;
|
|
340
|
+
|
|
341
|
+
/** A letter's other case: its upper case if it's in lower case, else its lower case. */
|
|
342
|
+
const otherCase = (c: string): string => (c === c.toLowerCase() ? c.toUpperCase() : c.toLowerCase());
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* The name with each letter's case flipped, where its other case is one
|
|
346
|
+
* letter whose own other case is the letter again, so the flip changes only
|
|
347
|
+
* pairs that any folding disk folds together. `ß`, whose upper case is `SS`,
|
|
348
|
+
* and dotless `ı`, whose upper case `I` lowers to `i`, stay as they are.
|
|
349
|
+
*/
|
|
350
|
+
const flippedCase = (name: string): string =>
|
|
351
|
+
[...name].map((c) => {
|
|
352
|
+
const other = otherCase(c);
|
|
353
|
+
return [...other].length === 1 && otherCase(other) === c ? other : c;
|
|
354
|
+
}).join('');
|
|
355
|
+
|
|
356
|
+
/** Names compared as NFC in lower case. */
|
|
357
|
+
const folded = (name: string): string => name.normalize('NFC').toLowerCase();
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Names compared more loosely, through upper case and back, which also folds
|
|
361
|
+
* `ß` with `ss`, `ſ` with `s` and `ς` with `σ`, as a disk that folds fully
|
|
362
|
+
* does. It also folds dotless `ı` with `i`, which a disk may keep apart;
|
|
363
|
+
* the same-file check keeps that from naming another file.
|
|
364
|
+
*/
|
|
365
|
+
export const foldedLoosely = (name: string): string => name.normalize('NFC').toLowerCase().toUpperCase().toLowerCase();
|
|
366
|
+
|
|
367
|
+
/** What the workspace holds of a mount, as the spelling step reads it (diskSpelling). */
|
|
368
|
+
export interface WorkspaceEntries {
|
|
369
|
+
/**
|
|
370
|
+
* Whether the workspace holds an entry at exactly this path, or records an
|
|
371
|
+
* intent there, such as a deletion disk hasn't been given yet.
|
|
372
|
+
*/
|
|
373
|
+
has(path: string): boolean;
|
|
374
|
+
/** The paths of the entries under `prefix`, a directory's path and `/`, or `''` for every entry. */
|
|
375
|
+
under(prefix: string): string[];
|
|
376
|
+
/**
|
|
377
|
+
* The nested mount whose directory holds this path, by the nested-mount
|
|
378
|
+
* checks' own reading of it, or undefined where the path is the mount's
|
|
379
|
+
* own.
|
|
380
|
+
*/
|
|
381
|
+
holder(path: string): string | undefined;
|
|
382
|
+
/** Whether the mount's root folds case (foldsCase). */
|
|
383
|
+
rootFoldsCase: boolean;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Whether `dir` folds case, told without writing: by looking up flipped a
|
|
388
|
+
* name it lists whose flipped spelling it doesn't list too (a name with no
|
|
389
|
+
* letter to flip is its own flipped spelling, and two hard links that differ
|
|
390
|
+
* only in case are one file on any disk). Where no name it lists tells, or
|
|
391
|
+
* it can't be listed, the directory holding it is asked instead, where both
|
|
392
|
+
* are on one device, each as a link to it leads: its own name looked up
|
|
393
|
+
* flipped there, or, for a name with no letter to flip, that directory's own
|
|
394
|
+
* folding. A directory on a device of its own is taken to tell case apart. A
|
|
395
|
+
* directory not there yet folds as the one it would be made in. Where
|
|
396
|
+
* nothing tells, it's taken to tell case apart.
|
|
397
|
+
*/
|
|
398
|
+
export async function foldsCase(dir: string): Promise<boolean> {
|
|
399
|
+
const holder = dirname(dir);
|
|
400
|
+
let listed: Set<string>;
|
|
401
|
+
try {
|
|
402
|
+
listed = new Set(await readdir(dir));
|
|
403
|
+
} catch (error) {
|
|
404
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT' && holder !== dir) return foldsCase(holder);
|
|
405
|
+
listed = new Set();
|
|
406
|
+
}
|
|
407
|
+
const look = (path: string) => lstat(path, { bigint: true }).catch(() => null);
|
|
408
|
+
const name = [...listed].find((n) => !listed.has(flippedCase(n)));
|
|
409
|
+
if (name !== undefined) {
|
|
410
|
+
const [named, flipped] = await Promise.all([look(join(dir, name)), look(join(dir, flippedCase(name)))]);
|
|
411
|
+
return named != null && flipped != null && sameFile(named, flipped);
|
|
412
|
+
}
|
|
413
|
+
if (holder === dir) return false;
|
|
414
|
+
// The directory and its holder are looked up through links, so a root
|
|
415
|
+
// that is one, or is reached through one, is compared by the device it
|
|
416
|
+
// lies on. Whether the holder folds is told by its own entries, the root's
|
|
417
|
+
// name and the flipped name, each looked up without following a link
|
|
418
|
+
// there: a link at the flipped name to the root is another file, and a
|
|
419
|
+
// root that is a link is found as itself.
|
|
420
|
+
const see = (path: string) => stat(path, { bigint: true }).catch(() => null);
|
|
421
|
+
const own = basename(dir);
|
|
422
|
+
const [self, held, entry, flipped] = await Promise.all([see(dir), see(holder), look(dir), look(join(holder, flippedCase(own)))]);
|
|
423
|
+
if (self == null || held == null || self.dev !== held.dev) return false;
|
|
424
|
+
if (flippedCase(own) === own) return foldsCase(holder);
|
|
425
|
+
return entry != null && flipped != null && sameFile(entry, flipped);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* A name in simple case folding, letter by letter: each letter as the lower
|
|
430
|
+
* case of its upper case, so `Σ` and a word's last `ς` are both `σ`, `ſ` is
|
|
431
|
+
* `s` and `µ` is `μ`. A letter whose upper case is more than one letter
|
|
432
|
+
* takes its own lower case instead, as the Greek `ᾈ` takes `ᾀ` and `ß` stays
|
|
433
|
+
* `ß`. A letter whose lower case is more than one letter too, as `İ`'s is,
|
|
434
|
+
* stays as it is, and so does dotless `ı`, whose upper case `I` lowers to
|
|
435
|
+
* `i`, which case folding keeps apart from it. Unicode normalization is left
|
|
436
|
+
* alone, and so are letters only a fuller folding joins, such as `ß` with
|
|
437
|
+
* `ss`: no listing shows two such names to be one file, and a disk that
|
|
438
|
+
* folds case needn't join them.
|
|
439
|
+
*/
|
|
440
|
+
export const lowered = (name: string): string =>
|
|
441
|
+
[...name].map((c) => {
|
|
442
|
+
if (c === '\u0131') return c;
|
|
443
|
+
const upper = c.toUpperCase();
|
|
444
|
+
const lower = [...upper].length === 1 ? upper.toLowerCase() : c.toLowerCase();
|
|
445
|
+
return [...lower].length === 1 ? lower : c;
|
|
446
|
+
}).join('');
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* The name `segment` takes among the workspace's entries directly under the
|
|
450
|
+
* directory `prefix` (its path and `/`, or `''` for the mount's root): the
|
|
451
|
+
* segment itself where an entry is there under it, else the first name, in
|
|
452
|
+
* the order given, equal to it in simple case folding (lowered), else null.
|
|
453
|
+
* A name is compared again only where another name came between its entries,
|
|
454
|
+
* as `N.txt` comes between `N` and `N/x` in path order, so the step doesn't
|
|
455
|
+
* fold a name once for every entry beneath it.
|
|
456
|
+
*/
|
|
457
|
+
function entryName(paths: readonly string[], prefix: string, segment: string): string | null {
|
|
458
|
+
const want = lowered(segment);
|
|
459
|
+
let found: string | null = null;
|
|
460
|
+
let previous: string | null = null;
|
|
461
|
+
for (const path of paths) {
|
|
462
|
+
if (!path.startsWith(prefix)) continue;
|
|
463
|
+
const rest = path.slice(prefix.length);
|
|
464
|
+
const slash = rest.indexOf('/');
|
|
465
|
+
const name = slash === -1 ? rest : rest.slice(0, slash);
|
|
466
|
+
if (name === previous) continue;
|
|
467
|
+
previous = name;
|
|
468
|
+
if (name === segment) return segment;
|
|
469
|
+
if (found === null && lowered(name) === want) found = name;
|
|
470
|
+
}
|
|
471
|
+
return found;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* The name `dir` lists for `segment`: `segment` itself, unless disk folds it
|
|
476
|
+
* there and lists it otherwise. `dir` is listed only where a variant of
|
|
477
|
+
* `segment`, its case flipped or its other Unicode normalization form, finds
|
|
478
|
+
* the same file (device and inode) as `given`, the segment's own entry, as
|
|
479
|
+
* on a disk that folds names. The last segment isn't looked up as given, so
|
|
480
|
+
* for it any variant found leads to the listing: on a disk that tells
|
|
481
|
+
* spellings apart, that's only where `dir` holds a variant as a name of its
|
|
482
|
+
* own. The name as given wins if it's listed; otherwise the segment takes
|
|
483
|
+
* the listed name that folds to it and is the same file, so the name
|
|
484
|
+
* returned reaches the file the segment does, or is the segment as given.
|
|
485
|
+
* Names are compared as NFC in lower case first, and only where none of
|
|
486
|
+
* those is the file, more loosely (foldedLoosely). A directory that can't be
|
|
487
|
+
* listed, or a name that can't be looked up, leaves the segment as given. A
|
|
488
|
+
* segment with no variant at all, no letter to flip and one normalization
|
|
489
|
+
* form, is looked up as given and keeps its spelling; for a last segment
|
|
490
|
+
* with a letter that case maps, such as `ς`, the listing then says whether
|
|
491
|
+
* disk lists it as given. `found` says whether the lookups found disk's
|
|
492
|
+
* file: for a directory, its own lookup; for a last segment, whether a
|
|
493
|
+
* variant was found, which on a disk that tells spellings apart may be
|
|
494
|
+
* another file, or for one with no variant, whether disk has it as given, by
|
|
495
|
+
* the listing where it was read. `caseFolds` says what looking the segment
|
|
496
|
+
* up with its case flipped showed of `dir`: that it folds case, that it
|
|
497
|
+
* tells case apart, or nothing (null), for a segment with no letter to flip.
|
|
498
|
+
* For a segment not looked up as given (`given` null), `caseFolds` is set
|
|
499
|
+
* from whatever file the flipped spelling finds, which tells nothing of
|
|
500
|
+
* `dir`; diskSpelling reads it only for directories, which are looked up as
|
|
501
|
+
* given.
|
|
502
|
+
*/
|
|
503
|
+
async function listedName(dir: string, segment: string, given: BigIntStats | null): Promise<{ name: string; found: boolean; caseFolds: boolean | null }> {
|
|
504
|
+
const flipped = flippedCase(segment);
|
|
505
|
+
const variants = new Set([flipped, segment.normalize('NFC'), segment.normalize('NFD')]);
|
|
506
|
+
variants.delete(segment);
|
|
507
|
+
// A segment with no variant to try, such as `2024` or `ς`, is looked up
|
|
508
|
+
// as given, the only way to learn that disk has it. A last segment with a
|
|
509
|
+
// letter that case maps, as `ς` does, can reach a file disk lists in
|
|
510
|
+
// another spelling, such as `σ`, on a disk that folds more than case
|
|
511
|
+
// pairs, so there the listing settles whether disk lists it as given.
|
|
512
|
+
if (variants.size === 0) {
|
|
513
|
+
const own = given ?? await lstat(join(dir, segment), { bigint: true }).catch(() => null);
|
|
514
|
+
if (given !== null || own === null || (segment.toLowerCase() === segment && segment.toUpperCase() === segment)) {
|
|
515
|
+
return { name: segment, found: own !== null, caseFolds: null };
|
|
516
|
+
}
|
|
517
|
+
const listed = await readdir(dir).catch(() => null);
|
|
518
|
+
return { name: segment, found: listed?.includes(segment) ?? true, caseFolds: null };
|
|
519
|
+
}
|
|
520
|
+
let folds = false;
|
|
521
|
+
let caseFolds: boolean | null = flipped === segment ? null : false;
|
|
522
|
+
for (const variant of variants) {
|
|
523
|
+
const other = await lstat(join(dir, variant), { bigint: true }).catch(() => null);
|
|
524
|
+
if (other !== null && (given === null || sameFile(other, given))) {
|
|
525
|
+
folds = true;
|
|
526
|
+
if (variant === flipped) caseFolds = true;
|
|
527
|
+
break;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
const as = (name: string) => ({ name, found: folds, caseFolds });
|
|
531
|
+
if (!folds) return as(segment);
|
|
532
|
+
const listed = await readdir(dir).catch(() => null);
|
|
533
|
+
if (listed === null || listed.includes(segment)) return as(segment);
|
|
534
|
+
const target = given ?? await lstat(join(dir, segment), { bigint: true }).catch(() => null);
|
|
535
|
+
if (target === null) return as(segment);
|
|
536
|
+
for (const fold of [folded, foldedLoosely]) {
|
|
537
|
+
for (const name of listed) {
|
|
538
|
+
if (fold(name) !== fold(segment)) continue;
|
|
539
|
+
const info = await lstat(join(dir, name), { bigint: true }).catch(() => null);
|
|
540
|
+
if (info !== null && sameFile(info, target)) return as(name);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
return as(segment);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* `relativePath` as disk spells it. On a disk that folds names, as macOS's
|
|
548
|
+
* does by default (case and Unicode normalization), `SRC/a.ts` reaches
|
|
549
|
+
* `src/a.ts`, and whatever names what it finds from the spelling it was
|
|
550
|
+
* given takes the file in a second time, under a name disk doesn't list. So
|
|
551
|
+
* each segment, from the mount's root down, takes the name its directory
|
|
552
|
+
* lists for it (listedName). The walk goes on beneath a segment only if it's
|
|
553
|
+
* a directory, a symlink not included, so nothing past a link is read. It
|
|
554
|
+
* passes over an empty or `.` segment, which names the directory reached,
|
|
555
|
+
* and goes back up at `..`, to the directory it came from. At the mount's
|
|
556
|
+
* root a `..` stops it, keeping the rest as given: a path can climb above
|
|
557
|
+
* the root and come back in, as `../proj/x` does for a root named `proj`, and
|
|
558
|
+
* walking that would list the directory above the root. An absolute path
|
|
559
|
+
* keeps its spelling too: it names a place from the filesystem's root, not
|
|
560
|
+
* the mount's, so walking it from the mount's root would respell it against
|
|
561
|
+
* other directories, and the path returned could lead outside the mount.
|
|
562
|
+
*
|
|
563
|
+
* Given the workspace's `entries`, an entry not yet on disk spells its path
|
|
564
|
+
* as its file will. A path the workspace holds an entry at exactly keeps its
|
|
565
|
+
* spelling whole, and so does a last segment where the workspace holds an
|
|
566
|
+
* entry at exactly that path once its directories have taken disk's
|
|
567
|
+
* spelling, so an entry that disk lists in another spelling, or in a
|
|
568
|
+
* directory disk lists in another, stays reachable by its own path. `has`
|
|
569
|
+
* counts a path the workspace records an intent at, such as a deletion disk
|
|
570
|
+
* hasn't been given yet, so that's reached by its own path too. And where
|
|
571
|
+
* the directory reached folds case, each segment disk has nothing for takes
|
|
572
|
+
* the name of an entry it equals in simple case folding (entryName), as the
|
|
573
|
+
* disk will once that entry is written: a second draft spelled in another
|
|
574
|
+
* case is the first one. None of this applies where disk's spelling puts the
|
|
575
|
+
* path in a nested mount's directory, by the nested-mount checks' own
|
|
576
|
+
* reading (`holder`): there the path keeps disk's spelling, so they refuse
|
|
577
|
+
* at least what they refuse without the entries. The root's folding is read
|
|
578
|
+
* at start (`rootFoldsCase`), and a directory beneath it is taken to fold as
|
|
579
|
+
* the directory holding it did when its name was looked up with its case
|
|
580
|
+
* flipped, or, for a name with no letter to flip, as the nearest directory
|
|
581
|
+
* above that told, so the step reads nothing more of disk for this.
|
|
582
|
+
*/
|
|
583
|
+
export async function diskSpelling(root: string, relativePath: string, entries?: WorkspaceEntries): Promise<string> {
|
|
584
|
+
if (relativePath === '' || isAbsolute(relativePath)) return relativePath;
|
|
585
|
+
// A path that names an entry exactly keeps its spelling, read before the
|
|
586
|
+
// walk awaits disk, so the entries' walk needn't run for it.
|
|
587
|
+
const exact = entries?.has(relativePath) ?? false;
|
|
588
|
+
// The path as disk spells it, and as the workspace's entries spell it.
|
|
589
|
+
const segments = relativePath.split('/');
|
|
590
|
+
const spelled = [...segments];
|
|
591
|
+
// The directories walked into, the mount's root first; their names below
|
|
592
|
+
// the root, as returned; and whether each folds case: the root's as read at
|
|
593
|
+
// start, and beneath it, whether looking its name up with its case flipped
|
|
594
|
+
// found it in the directory holding it (null for a name with no letter to
|
|
595
|
+
// flip).
|
|
596
|
+
const dirs = [root];
|
|
597
|
+
const names: string[] = [];
|
|
598
|
+
const folding: (boolean | null)[] = [entries?.rootFoldsCase ?? null];
|
|
599
|
+
// From segment `from` on, disk has nothing: each segment takes an entry's
|
|
600
|
+
// name, where the directory reached folds case, passing over an empty or
|
|
601
|
+
// `.` segment, and the rest keeps its spelling from the first segment no
|
|
602
|
+
// entry has a name for, a `..` included.
|
|
603
|
+
const fromEntries = (from: number): void => {
|
|
604
|
+
// The nearest directory, from the one reached up, that told.
|
|
605
|
+
let folds: boolean | null = null;
|
|
606
|
+
for (let k = folding.length - 1; folds === null && k >= 0; k--) folds = folding[k]!;
|
|
607
|
+
if (!entries || exact || !folds) return;
|
|
608
|
+
let prefix = names.length === 0 ? '' : `${names.join('/')}/`;
|
|
609
|
+
const paths = entries.under(prefix);
|
|
610
|
+
for (let j = from; j < spelled.length; j++) {
|
|
611
|
+
const segment = spelled[j]!;
|
|
612
|
+
if (segment === '' || segment === '.') continue;
|
|
613
|
+
const name = entryName(paths, prefix, segment);
|
|
614
|
+
if (name === null) return;
|
|
615
|
+
spelled[j] = name;
|
|
616
|
+
prefix = `${prefix}${name}/`;
|
|
617
|
+
}
|
|
618
|
+
};
|
|
619
|
+
for (let i = 0; i < segments.length; i++) {
|
|
620
|
+
const segment = segments[i]!;
|
|
621
|
+
if (segment === '' || segment === '.') continue;
|
|
622
|
+
if (segment === '..') {
|
|
623
|
+
if (dirs.length === 1) break;
|
|
624
|
+
dirs.pop();
|
|
625
|
+
names.pop();
|
|
626
|
+
folding.pop();
|
|
627
|
+
continue;
|
|
628
|
+
}
|
|
629
|
+
const dir = dirs[dirs.length - 1]!;
|
|
630
|
+
if (i === segments.length - 1) {
|
|
631
|
+
const { name, found } = await listedName(dir, segment, null);
|
|
632
|
+
segments[i] = name;
|
|
633
|
+
if (!exact && !entries?.has([...names, segment].join('/'))) {
|
|
634
|
+
spelled[i] = name;
|
|
635
|
+
if (!found) fromEntries(i);
|
|
636
|
+
}
|
|
637
|
+
break;
|
|
638
|
+
}
|
|
639
|
+
const given = await lstat(join(dir, segment), { bigint: true }).catch(() => null);
|
|
640
|
+
if (given === null) {
|
|
641
|
+
fromEntries(i);
|
|
642
|
+
break;
|
|
643
|
+
}
|
|
644
|
+
const { name, caseFolds } = await listedName(dir, segment, given);
|
|
645
|
+
segments[i] = name;
|
|
646
|
+
spelled[i] = name;
|
|
647
|
+
if (!given.isDirectory()) break;
|
|
648
|
+
dirs.push(join(dir, name));
|
|
649
|
+
names.push(name);
|
|
650
|
+
folding.push(caseFolds);
|
|
651
|
+
}
|
|
652
|
+
const asDisk = segments.join('/');
|
|
653
|
+
if (!entries || entries.holder(asDisk) !== undefined) return asDisk;
|
|
654
|
+
return exact ? relativePath : spelled.join('/');
|
|
655
|
+
}
|
|
656
|
+
|
|
136
657
|
// ===========================================================================
|
|
137
658
|
// Checked directories
|
|
138
659
|
// ===========================================================================
|
|
@@ -193,7 +714,11 @@ export class CheckedDir {
|
|
|
193
714
|
*/
|
|
194
715
|
async sync(): Promise<void> {
|
|
195
716
|
if (process.platform === 'win32') return;
|
|
196
|
-
|
|
717
|
+
// NONBLOCK: a FIFO put at its path since it was checked opens at once,
|
|
718
|
+
// and is refused below as not this directory, instead of holding the
|
|
719
|
+
// open, and with it the mount's turn, until something opens it for
|
|
720
|
+
// writing.
|
|
721
|
+
const handle = await open(this.real, fsConstants.O_RDONLY | NONBLOCK);
|
|
197
722
|
try {
|
|
198
723
|
const info = await handle.stat({ bigint: true });
|
|
199
724
|
if (!info.isDirectory() || info.dev !== this.dev || info.ino !== this.ino) throw new Error(CHANGED);
|
|
@@ -227,14 +752,27 @@ async function locateDir(view: MountView, rootReal: string, rel: string): Promis
|
|
|
227
752
|
} catch (err) {
|
|
228
753
|
const code = errno(err);
|
|
229
754
|
if (code === 'ENOENT' || code === 'ENOTDIR') return rel === '' ? { kind: 'unobserved', reason: ROOT_UNAVAILABLE } : { kind: 'missing' };
|
|
755
|
+
// A link on the way that can't be resolved, such as a loop, is still a
|
|
756
|
+
// link this mount doesn't follow.
|
|
757
|
+
const found = rel === '' ? null : await linkOnTheWay(view, rel);
|
|
758
|
+
if (found !== null) return { kind: 'unobserved', reason: await linkReason(view, rootReal, found) };
|
|
230
759
|
return { kind: 'unobserved', reason: `cannot resolve the directory (${code ?? String(err)})` };
|
|
231
760
|
}
|
|
761
|
+
// A mount that doesn't follow symlinks goes through none on the way to a
|
|
762
|
+
// directory, inside the mount or out of it.
|
|
763
|
+
const found = await linkRefusal(view, rootReal, rel, real);
|
|
764
|
+
if (found !== null) return { kind: 'unobserved', reason: await linkReason(view, rootReal, found) };
|
|
232
765
|
if (rel === '') {
|
|
233
766
|
// The root is whatever the configured path names, and it must still be
|
|
234
767
|
// the root the pass was given.
|
|
235
768
|
if (real !== rootReal) return { kind: 'unobserved', reason: ROOT_UNAVAILABLE };
|
|
236
769
|
} else if (!contained(rootReal, real)) {
|
|
237
770
|
return { kind: 'unobserved', reason: OUTSIDE_PARENT, outside: true };
|
|
771
|
+
} else {
|
|
772
|
+
// Through a link this mount follows, a directory can be a nested
|
|
773
|
+
// mount's, which the mount neither reads nor writes.
|
|
774
|
+
const holder = nestedAtReal(real, view.nested);
|
|
775
|
+
if (holder) return { kind: 'unobserved', reason: leadsInto(view.name, rel, holder) };
|
|
238
776
|
}
|
|
239
777
|
const checked = await checkedAt(view, rel, real);
|
|
240
778
|
if (checked) {
|
|
@@ -295,6 +833,10 @@ async function createDir(view: MountView, rootReal: string, rel: string): Promis
|
|
|
295
833
|
if (base === null) return { kind: 'unobserved', reason: ROOT_UNAVAILABLE };
|
|
296
834
|
for (let i = depth; i < parts.length; i++) {
|
|
297
835
|
const childRel = parts.slice(0, i + 1).join('/');
|
|
836
|
+
// Nothing is made in a nested mount's directory, its root included where
|
|
837
|
+
// it isn't made yet, by a path that reaches it through a link.
|
|
838
|
+
const holder = nestedAtReal(base.at(parts[i]!), view.nested);
|
|
839
|
+
if (holder) return { kind: 'unobserved', reason: leadsInto(view.name, childRel, holder) };
|
|
298
840
|
try {
|
|
299
841
|
await mkdir(base.at(parts[i]!));
|
|
300
842
|
} catch (err) {
|
|
@@ -304,7 +846,13 @@ async function createDir(view: MountView, rootReal: string, rel: string): Promis
|
|
|
304
846
|
// Made, or there already (made meanwhile, or not a directory): it must
|
|
305
847
|
// be a real directory now, never a symlink or file in its place.
|
|
306
848
|
const checked = await checkedAt(view, childRel, base.at(parts[i]!));
|
|
307
|
-
if (!checked)
|
|
849
|
+
if (!checked) {
|
|
850
|
+
// A symlink in the way, such as one that leads nowhere, on a mount that
|
|
851
|
+
// doesn't follow symlinks, is named as one.
|
|
852
|
+
const there = view.followSymlinks ? null : await lstat(base.at(parts[i]!)).catch(() => null);
|
|
853
|
+
if (there?.isSymbolicLink()) return { kind: 'unobserved', reason: await linkReason(view, rootReal, { link: childRel }) };
|
|
854
|
+
return { kind: 'unobserved', reason: 'a parent path is not a directory' };
|
|
855
|
+
}
|
|
308
856
|
base = checked;
|
|
309
857
|
}
|
|
310
858
|
return { kind: 'checked', dir: base };
|
|
@@ -318,10 +866,12 @@ async function createDir(view: MountView, rootReal: string, rel: string): Promis
|
|
|
318
866
|
* which nothing can exist. Either proves it only while every directory the
|
|
319
867
|
* walk went through, the root included, is still the one its path names
|
|
320
868
|
* afterwards: a root or parent replaced during the walk would otherwise lend
|
|
321
|
-
* its replacement's absence to the original's path.
|
|
322
|
-
* inside the mount are followed as parents;
|
|
323
|
-
*
|
|
324
|
-
*
|
|
869
|
+
* its replacement's absence to the original's path. On a mount that follows
|
|
870
|
+
* symlinks, symlinked directories inside the mount are followed as parents;
|
|
871
|
+
* on one that doesn't, a symlinked directory on the way proves nothing, and
|
|
872
|
+
* the reason says so (linkReason). Any other symlink proves nothing.
|
|
873
|
+
* Returns the directory whose entries prove it, or why absence couldn't be
|
|
874
|
+
* shown.
|
|
325
875
|
*/
|
|
326
876
|
export async function confirmAbsent(view: MountView, rootReal: string, rel: string, target: 'entry' | 'beneath' = 'entry'): Promise<{ by: string } | string> {
|
|
327
877
|
const root = await locateDir(view, rootReal, '');
|
|
@@ -356,6 +906,8 @@ export async function confirmAbsent(view: MountView, rootReal: string, rel: stri
|
|
|
356
906
|
}
|
|
357
907
|
if (info.isSymbolicLink()) {
|
|
358
908
|
if (last && target === 'entry') return CHANGED; // present: the link itself
|
|
909
|
+
// A mount that doesn't follow symlinks goes through none on the way.
|
|
910
|
+
if (!view.followSymlinks) return linkReason(view, rootReal, { link: childRel });
|
|
359
911
|
const located = await locateDir(view, rootReal, childRel);
|
|
360
912
|
if (located.kind !== 'checked') return BENEATH_SYMLINK;
|
|
361
913
|
if (last) return CHANGED; // a directory there after all
|
|
@@ -447,15 +999,24 @@ export async function filesystemTrustsCtime(root: string): Promise<boolean> {
|
|
|
447
999
|
/**
|
|
448
1000
|
* Where a path's parent directory canonically lies: inside the mount, gone
|
|
449
1001
|
* (absence is then proven separately), or somewhere a no-follow open would
|
|
450
|
-
* still reach through — O_NOFOLLOW guards only the final component
|
|
1002
|
+
* still reach through — O_NOFOLLOW guards only the final component — which
|
|
1003
|
+
* on a mount that doesn't follow symlinks is any symlink on the way.
|
|
451
1004
|
*/
|
|
452
|
-
async function parentPlacement(rootReal: string,
|
|
1005
|
+
async function parentPlacement(view: MountView, rootReal: string, relativePath: string): Promise<'inside' | 'gone' | { reason: string }> {
|
|
453
1006
|
try {
|
|
454
|
-
const parent = await realpath(dirname(
|
|
455
|
-
|
|
1007
|
+
const parent = await realpath(dirname(join(view.root, relativePath)));
|
|
1008
|
+
const found = await linkRefusal(view, rootReal, relativePath, parent, 'parents');
|
|
1009
|
+
if (found !== null) return { reason: await linkReason(view, rootReal, found) };
|
|
1010
|
+
if (!contained(rootReal, parent)) return { reason: OUTSIDE_PARENT };
|
|
1011
|
+
const holder = nestedAtReal(parent, view.nested);
|
|
1012
|
+
return holder ? { reason: leadsInto(view.name, parentOf(relativePath), holder) } : 'inside';
|
|
456
1013
|
} catch (err) {
|
|
457
1014
|
const code = errno(err);
|
|
458
1015
|
if (code === 'ENOENT' || code === 'ENOTDIR') return 'gone';
|
|
1016
|
+
// A link on the way that can't be resolved, such as a loop, is still a
|
|
1017
|
+
// link this mount doesn't follow.
|
|
1018
|
+
const found = await linkOnTheWay(view, relativePath, 'parents');
|
|
1019
|
+
if (found !== null) return { reason: await linkReason(view, rootReal, found) };
|
|
459
1020
|
return { reason: `cannot resolve the parent directory (${code ?? String(err)})` };
|
|
460
1021
|
}
|
|
461
1022
|
}
|
|
@@ -468,7 +1029,7 @@ async function parentPlacement(rootReal: string, lexical: string): Promise<'insi
|
|
|
468
1029
|
*/
|
|
469
1030
|
export async function observePath(view: MountView, rootReal: string, relativePath: string): Promise<Observation> {
|
|
470
1031
|
const lexical = join(view.root, relativePath);
|
|
471
|
-
const placement = await parentPlacement(rootReal,
|
|
1032
|
+
const placement = await parentPlacement(view, rootReal, relativePath);
|
|
472
1033
|
if (placement === 'gone') return absence(view, rootReal, relativePath);
|
|
473
1034
|
if (placement !== 'inside') return { kind: 'unobserved', reason: placement.reason };
|
|
474
1035
|
let info;
|
|
@@ -494,6 +1055,8 @@ export async function observePath(view: MountView, rootReal: string, relativePat
|
|
|
494
1055
|
return { kind: 'unobserved', reason: `a symlink that cannot be resolved (${errno(err) ?? String(err)})` };
|
|
495
1056
|
}
|
|
496
1057
|
if (!contained(rootReal, real)) return { kind: 'unobserved', reason: 'a symlink that leaves the mount' };
|
|
1058
|
+
const holder = nestedAtReal(real, view.nested);
|
|
1059
|
+
if (holder) return { kind: 'unobserved', reason: leadsInto(view.name, relativePath, holder) };
|
|
497
1060
|
if (!target.isFile()) {
|
|
498
1061
|
return { kind: 'unobserved', reason: target.isDirectory() ? 'a symlink to a directory, not walked' : 'not a regular file' };
|
|
499
1062
|
}
|
|
@@ -503,14 +1066,15 @@ export async function observePath(view: MountView, rootReal: string, relativePat
|
|
|
503
1066
|
} else if (!info.isFile()) {
|
|
504
1067
|
return { kind: 'unobserved', reason: 'not a regular file' };
|
|
505
1068
|
}
|
|
506
|
-
return { kind: 'file', size: Number(info.size), mtimeMs: Number(info.mtimeMs), fp: statFingerprint(info) };
|
|
1069
|
+
return { kind: 'file', size: Number(info.size), mtimeMs: Number(info.mtimeMs), mode: Number(info.mode & 0o777n), fp: statFingerprint(info) };
|
|
507
1070
|
}
|
|
508
1071
|
|
|
509
1072
|
/**
|
|
510
1073
|
* Open a mount-relative file for reading, located: a regular file that
|
|
511
|
-
* canonically lies inside the mount
|
|
512
|
-
*
|
|
513
|
-
*
|
|
1074
|
+
* canonically lies inside the mount, whatever the symlink policy, since
|
|
1075
|
+
* O_NOFOLLOW guards only the final component; on a mount that doesn't follow
|
|
1076
|
+
* symlinks, reached through no symlinked directory either (linkRefusal); and
|
|
1077
|
+
* the file the canonical path names now. The handle is the caller's to close.
|
|
514
1078
|
*/
|
|
515
1079
|
async function openContained(
|
|
516
1080
|
view: MountView,
|
|
@@ -532,19 +1096,32 @@ async function openContained(
|
|
|
532
1096
|
} catch (err) {
|
|
533
1097
|
const code = errno(err);
|
|
534
1098
|
if (code === 'ENOENT' || code === 'ENOTDIR') return absence(view, rootReal, relativePath);
|
|
535
|
-
if (code === 'ELOOP' && !view.followSymlinks)
|
|
1099
|
+
if (code === 'ELOOP' && !view.followSymlinks) {
|
|
1100
|
+
// The link at the path, or a looping one on its way.
|
|
1101
|
+
const found = await linkOnTheWay(view, relativePath, 'parents');
|
|
1102
|
+
return { kind: 'unobserved', reason: found === null ? NOT_FOLLOWED : await linkReason(view, rootReal, found) };
|
|
1103
|
+
}
|
|
536
1104
|
if (code === 'EISDIR') return { kind: 'unobserved', reason: 'a directory' };
|
|
537
1105
|
return { kind: 'unobserved', reason: `cannot open (${code ?? String(err)})` };
|
|
538
1106
|
}
|
|
539
1107
|
let keep = false;
|
|
540
1108
|
try {
|
|
541
1109
|
const info = await handle.stat({ bigint: true });
|
|
542
|
-
if (!info.isFile()) return { kind: 'unobserved', reason: info.isDirectory() ? 'a directory' : 'not a regular file' };
|
|
543
1110
|
const real = await realpath(lexical).catch(() => null);
|
|
544
1111
|
if (real === null) return { kind: 'unobserved', reason: 'the file changed while it was read' };
|
|
1112
|
+
// O_NOFOLLOW guards only the final component: a mount that doesn't
|
|
1113
|
+
// follow symlinks goes through none among the directories on the way
|
|
1114
|
+
// either. Asked of what the path resolved to after the open, which the
|
|
1115
|
+
// check below holds to the descriptor's file, and before what the
|
|
1116
|
+
// descriptor holds, so a link gets its own answer whatever it leads to.
|
|
1117
|
+
const found = await linkRefusal(view, rootReal, relativePath, real);
|
|
1118
|
+
if (found !== null) return { kind: 'unobserved', reason: await linkReason(view, rootReal, found) };
|
|
1119
|
+
if (!info.isFile()) return { kind: 'unobserved', reason: info.isDirectory() ? 'a directory' : 'not a regular file' };
|
|
545
1120
|
if (!contained(rootReal, real)) {
|
|
546
1121
|
return { kind: 'unobserved', reason: view.followSymlinks ? 'a symlink that leaves the mount' : OUTSIDE_PARENT };
|
|
547
1122
|
}
|
|
1123
|
+
const holder = nestedAtReal(real, view.nested);
|
|
1124
|
+
if (holder) return { kind: 'unobserved', reason: leadsInto(view.name, relativePath, holder) };
|
|
548
1125
|
const named = await stat(real, { bigint: true }).catch(() => null);
|
|
549
1126
|
if (named === null || named.dev !== info.dev || named.ino !== info.ino) {
|
|
550
1127
|
return { kind: 'unobserved', reason: 'the file changed while it was read' };
|
|
@@ -570,10 +1147,42 @@ async function openContained(
|
|
|
570
1147
|
}
|
|
571
1148
|
}
|
|
572
1149
|
|
|
1150
|
+
/**
|
|
1151
|
+
* Read an open file from its start, at most `limit` bytes, and whether it
|
|
1152
|
+
* goes on past them. Where the file ends is what the reads find: `sizeHint`,
|
|
1153
|
+
* the size its fstat gave, only sizes the first buffer. Once a buffer is
|
|
1154
|
+
* full, one byte read past it tells whether the file goes on. At the limit,
|
|
1155
|
+
* that byte says it does, and isn't kept. Short of it, the file has grown
|
|
1156
|
+
* since its fstat: the byte is kept, and the read goes on in a larger buffer,
|
|
1157
|
+
* up to the limit. A file that has shrunk ends where the reads find its end.
|
|
1158
|
+
* A fractional `limit` allows the whole bytes in it.
|
|
1159
|
+
*/
|
|
1160
|
+
export async function readPrefix(handle: FileHandle, limit: number, sizeHint: number): Promise<{ bytes: Buffer; more: boolean }> {
|
|
1161
|
+
const cap = Math.floor(limit);
|
|
1162
|
+
const next = Buffer.alloc(1);
|
|
1163
|
+
let bytes = Buffer.alloc(Math.min(cap, sizeHint));
|
|
1164
|
+
let length = 0;
|
|
1165
|
+
for (;;) {
|
|
1166
|
+
while (length < bytes.length) {
|
|
1167
|
+
const { bytesRead } = await handle.read(bytes, length, bytes.length - length, length);
|
|
1168
|
+
if (bytesRead === 0) return { bytes: bytes.subarray(0, length), more: false };
|
|
1169
|
+
length += bytesRead;
|
|
1170
|
+
}
|
|
1171
|
+
if ((await handle.read(next, 0, 1, length)).bytesRead === 0) return { bytes, more: false };
|
|
1172
|
+
if (length === cap) return { bytes, more: true };
|
|
1173
|
+
// Longer than its fstat said: read on, up to the limit.
|
|
1174
|
+
const grown = Buffer.alloc(Math.min(cap, Math.max(2 * length, length + 64 * 1024)));
|
|
1175
|
+
bytes.copy(grown, 0, 0, length);
|
|
1176
|
+
grown[length++] = next[0];
|
|
1177
|
+
bytes = grown;
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
|
|
573
1181
|
/**
|
|
574
1182
|
* Read and hash a mount-relative file through one located descriptor. The
|
|
575
|
-
* whole file is returned when it is at most `limit` bytes
|
|
576
|
-
*
|
|
1183
|
+
* whole file is returned when it is at most `limit` bytes, both at its fstat
|
|
1184
|
+
* and as read; any other file, including one that grew past `limit` since its
|
|
1185
|
+
* fstat, is hashed in chunks and only its head is kept.
|
|
577
1186
|
*/
|
|
578
1187
|
export async function readPath(
|
|
579
1188
|
view: MountView,
|
|
@@ -588,12 +1197,18 @@ export async function readPath(
|
|
|
588
1197
|
const hashedAt = Date.now();
|
|
589
1198
|
const size = Number(info.size);
|
|
590
1199
|
const fp: Fingerprint = { ...statFingerprint(info), hashedAt };
|
|
591
|
-
|
|
592
|
-
|
|
1200
|
+
// A file within the limit at its fstat is read no further than the limit,
|
|
1201
|
+
// so one that has grown past it since is hashed below, from its start,
|
|
1202
|
+
// never loaded whole. One over the limit at its fstat is hashed below
|
|
1203
|
+
// without that read.
|
|
1204
|
+
const within = size <= limit ? await readPrefix(handle, limit, size) : null;
|
|
1205
|
+
if (within && !within.more) {
|
|
1206
|
+
const { bytes } = within;
|
|
593
1207
|
return {
|
|
594
1208
|
kind: 'read',
|
|
595
1209
|
size: bytes.byteLength,
|
|
596
1210
|
mtimeMs: Number(info.mtimeMs),
|
|
1211
|
+
mode: Number(info.mode & 0o777n),
|
|
597
1212
|
fp: { ...fp, size: bytes.byteLength },
|
|
598
1213
|
hash: createHash('sha256').update(bytes).digest('hex'),
|
|
599
1214
|
bytes,
|
|
@@ -615,6 +1230,7 @@ export async function readPath(
|
|
|
615
1230
|
kind: 'read',
|
|
616
1231
|
size: offset,
|
|
617
1232
|
mtimeMs: Number(info.mtimeMs),
|
|
1233
|
+
mode: Number(info.mode & 0o777n),
|
|
618
1234
|
fp: { ...fp, size: offset },
|
|
619
1235
|
hash: hash.digest('hex'),
|
|
620
1236
|
bytes: null,
|
|
@@ -681,14 +1297,18 @@ export interface Walk {
|
|
|
681
1297
|
*/
|
|
682
1298
|
seen: Set<string>;
|
|
683
1299
|
/**
|
|
684
|
-
* The names in `others` that
|
|
1300
|
+
* The names in `others` that it doesn't pass over by design and that the walk took, as it
|
|
685
1301
|
* takes files, against its cap: the pass observes these by path. The rest
|
|
686
1302
|
* are present, and not observed by this pass.
|
|
687
1303
|
*/
|
|
688
1304
|
othersTaken: Set<string>;
|
|
689
1305
|
/** Directories seen. */
|
|
690
1306
|
dirs: Set<string>;
|
|
691
|
-
/**
|
|
1307
|
+
/**
|
|
1308
|
+
* Names seen that are neither (symlinks, sockets…), or that the walk passes
|
|
1309
|
+
* over by design (another mount's directory, the ignore list, an editor's
|
|
1310
|
+
* file name): present, unproven.
|
|
1311
|
+
*/
|
|
692
1312
|
others: Map<string, string>;
|
|
693
1313
|
/**
|
|
694
1314
|
* Directories whose listing the walk read whole, and that were still the
|
|
@@ -701,9 +1321,10 @@ export interface Walk {
|
|
|
701
1321
|
/**
|
|
702
1322
|
* Where the walk didn't reach, and why. `error` and `cap` regions may hide
|
|
703
1323
|
* files the listing would show. An `ignored` region is passed over by
|
|
704
|
-
* design: one that hides a tracked entry is
|
|
705
|
-
*
|
|
706
|
-
* never walked; the pass reports the link itself by
|
|
1324
|
+
* design: one that hides a tracked entry, or is the pass's whole scope, is
|
|
1325
|
+
* reported as incomplete, and any other is counted as passed over. A
|
|
1326
|
+
* `symlink` region is never walked; the pass reports the link itself by
|
|
1327
|
+
* path.
|
|
707
1328
|
*/
|
|
708
1329
|
incomplete: Array<{ path: string; reason: string; kind: 'error' | 'cap' | 'ignored' | 'symlink' }>;
|
|
709
1330
|
/**
|
|
@@ -774,6 +1395,101 @@ export function coveredByIgnore(relativePath: string, patterns: string[]): boole
|
|
|
774
1395
|
return false;
|
|
775
1396
|
}
|
|
776
1397
|
|
|
1398
|
+
/**
|
|
1399
|
+
* The ignore patterns a walk that names `scope` reads beneath it: a pattern
|
|
1400
|
+
* covering the scope itself, or a directory above it, doesn't apply beneath
|
|
1401
|
+
* it (#276); one covering only something inside it does.
|
|
1402
|
+
*/
|
|
1403
|
+
export function ignoreBeneath(scope: string, patterns: string[]): string[] {
|
|
1404
|
+
return patterns.filter((pattern) => !coveredByIgnore(scope, [pattern]));
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
/**
|
|
1408
|
+
* Names editors give the files they keep beside the one being edited: vim's
|
|
1409
|
+
* swap files (`.name.swp`, and `.name.swx` while it checks it can make one),
|
|
1410
|
+
* backups ending in `~`, and Sublime Text's save-in-progress files, ending
|
|
1411
|
+
* in `.subl…tmp`. A walk passes over an entry with such a name as it passes
|
|
1412
|
+
* over what the ignore list covers, a directory with what lies beneath it,
|
|
1413
|
+
* and the watcher ignores it; a sync that names one takes it in. They are
|
|
1414
|
+
* the families chokidar's `atomic` option hid from the watcher, but that
|
|
1415
|
+
* tested them against the whole path, unanchored, so it also hid
|
|
1416
|
+
* `report.v2.swp`, and under a directory with a dot in its name, a plain
|
|
1417
|
+
* `notes.swp`. Here each is matched against an entry's name, to its end,
|
|
1418
|
+
* and vim's from its start.
|
|
1419
|
+
*/
|
|
1420
|
+
const EDITOR_FILE = /^\..+\.sw[px]$|~$|\.subl.*\.tmp$/;
|
|
1421
|
+
|
|
1422
|
+
/** The walk's reason for an entry the mount's ignore list covers. */
|
|
1423
|
+
export const IGNORED = 'ignored by the mount';
|
|
1424
|
+
/** The walk's reason for an entry `EDITOR_FILE` names. */
|
|
1425
|
+
const EDITOR_FILE_REASON = "an editor's swap or backup file";
|
|
1426
|
+
|
|
1427
|
+
/** Whether a reason the walk gave an entry is one it passes over by design. */
|
|
1428
|
+
export function passedOverByDesign(why: string | undefined): boolean {
|
|
1429
|
+
return why === IGNORED || why === EDITOR_FILE_REASON || why === OTHER_MOUNT;
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
/** Why a pass passes over a directory that another mount nested in this one holds, and what lies beneath it. */
|
|
1433
|
+
export const OTHER_MOUNT = "another mount's directory";
|
|
1434
|
+
|
|
1435
|
+
/**
|
|
1436
|
+
* Whether a mount-relative path is another mount's directory, or lies
|
|
1437
|
+
* beneath one, read as a join reads it: `.` and `..` segments within the
|
|
1438
|
+
* path and repeated or trailing separators resolved, links not followed. A
|
|
1439
|
+
* stored key such as `a//b/x`, which chronicle accepts, is read as `a/b/x`.
|
|
1440
|
+
* A leading `..` that climbs above the mount's root is left as written.
|
|
1441
|
+
* Every nested-mount check (a pass's, a walk's, a push's, grep's) asks this.
|
|
1442
|
+
*/
|
|
1443
|
+
export function inOtherMount(relativePath: string, others: readonly string[]): boolean {
|
|
1444
|
+
const lexical = posix.normalize(relativePath).replace(/\/+$/, '');
|
|
1445
|
+
return others.some((dir) => lexical === dir || lexical.startsWith(dir + '/'));
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
/**
|
|
1449
|
+
* Why a walk passes over an entry by design, by its mount-relative path and
|
|
1450
|
+
* its name: another mount's directory, the ignore list, or an editor's file
|
|
1451
|
+
* name. Undefined when none covers it.
|
|
1452
|
+
*/
|
|
1453
|
+
function passedOverBecause(relativePath: string, name: string, patterns: string[], others: readonly string[]): string | undefined {
|
|
1454
|
+
if (inOtherMount(relativePath, others)) return OTHER_MOUNT;
|
|
1455
|
+
if (isIgnored(relativePath, name, patterns)) return IGNORED;
|
|
1456
|
+
if (EDITOR_FILE.test(name)) return EDITOR_FILE_REASON;
|
|
1457
|
+
return undefined;
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
/**
|
|
1461
|
+
* Why a sync without a path, and the watcher, pass over a mount-relative
|
|
1462
|
+
* path: the path itself or a directory above it is passed over by design, as
|
|
1463
|
+
* the walk reads each entry by its path and name. A path in another mount's
|
|
1464
|
+
* directory is that mount's, whatever else covers a directory above it.
|
|
1465
|
+
* Undefined when nothing covers it.
|
|
1466
|
+
*/
|
|
1467
|
+
export function passedOverAt(relativePath: string, patterns: string[], others: readonly string[]): string | undefined {
|
|
1468
|
+
if (inOtherMount(relativePath, others)) return OTHER_MOUNT;
|
|
1469
|
+
const segments = relativePath.split('/').filter(Boolean);
|
|
1470
|
+
for (let i = 0; i < segments.length; i++) {
|
|
1471
|
+
const why = passedOverBecause(segments.slice(0, i + 1).join('/'), segments[i]!, patterns, others);
|
|
1472
|
+
if (why !== undefined) return why;
|
|
1473
|
+
}
|
|
1474
|
+
return undefined;
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
/**
|
|
1478
|
+
* Why a walk of `scope` passes over a directory between it and a path beneath
|
|
1479
|
+
* it: the first directory strictly beneath the scope and above the path that
|
|
1480
|
+
* the mount passes over by design, by its path and name, with the patterns
|
|
1481
|
+
* the walk reads there. Undefined when none is.
|
|
1482
|
+
*/
|
|
1483
|
+
export function passedOverBetween(scope: string, relativePath: string, patterns: string[], others: readonly string[]): string | undefined {
|
|
1484
|
+
const segments = relativePath.split('/').filter(Boolean);
|
|
1485
|
+
const from = scope === '' ? 0 : scope.split('/').filter(Boolean).length;
|
|
1486
|
+
for (let i = from; i < segments.length - 1; i++) {
|
|
1487
|
+
const why = passedOverBecause(segments.slice(0, i + 1).join('/'), segments[i]!, patterns, others);
|
|
1488
|
+
if (why !== undefined) return why;
|
|
1489
|
+
}
|
|
1490
|
+
return undefined;
|
|
1491
|
+
}
|
|
1492
|
+
|
|
777
1493
|
/** Simple ignore patterns, as the watcher and the old walk read them. */
|
|
778
1494
|
export function isIgnored(relativePath: string, name: string, patterns: string[]): boolean {
|
|
779
1495
|
for (const pattern of patterns) {
|
|
@@ -835,6 +1551,29 @@ export async function walkScope(
|
|
|
835
1551
|
files: new Set(), seen: new Set(), othersTaken: new Set(), dirs: new Set(), others: new Map(), complete: new Set(), missing: new Set(),
|
|
836
1552
|
incomplete: [],
|
|
837
1553
|
};
|
|
1554
|
+
// A walk that isn't named passes over a scope the mount passes over by
|
|
1555
|
+
// design, at it or a directory above it, as its parent's listing would pass
|
|
1556
|
+
// over its entry: a name rule (a bare-name ignore entry, an editor's name)
|
|
1557
|
+
// doesn't match what lies beneath, as a path rule (`cache/**`) does. So a
|
|
1558
|
+
// listing, glob or grep there never starts tracking what the mount leaves
|
|
1559
|
+
// out, and a stored copy beneath it is unobserved, as beneath any region
|
|
1560
|
+
// passed over by design. Its region says how to take what's there in: a
|
|
1561
|
+
// sync that names it, whose scope is walked (#276), below.
|
|
1562
|
+
// Another mount's directory is passed over by every walk, named or not,
|
|
1563
|
+
// and no sync of this mount takes it in, so its region gives no advice.
|
|
1564
|
+
if (scope !== '' && inOtherMount(scope, view.others)) {
|
|
1565
|
+
walk.others.set(scope, OTHER_MOUNT);
|
|
1566
|
+
walk.incomplete.push({ path: scope, reason: OTHER_MOUNT, kind: 'ignored' });
|
|
1567
|
+
return walk;
|
|
1568
|
+
}
|
|
1569
|
+
if (!opts.named && scope !== '') {
|
|
1570
|
+
const why = passedOverAt(scope, view.ignore, view.others);
|
|
1571
|
+
if (why !== undefined) {
|
|
1572
|
+
walk.others.set(scope, why);
|
|
1573
|
+
walk.incomplete.push({ path: scope, reason: `${why}: a sync that names this path takes in what's there, editors' files aside`, kind: 'ignored' });
|
|
1574
|
+
return walk;
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
838
1577
|
const top = await checkDir(view, rootReal, scope);
|
|
839
1578
|
if (top.kind === 'absent') {
|
|
840
1579
|
walk.missing.add(scope);
|
|
@@ -847,9 +1586,11 @@ export async function walkScope(
|
|
|
847
1586
|
// A scope a path sync names is walked even where the ignore list covers
|
|
848
1587
|
// it: a pattern covering the scope itself, or a directory above it, doesn't
|
|
849
1588
|
// apply beneath it. So a named directory's contents are synced, as a named
|
|
850
|
-
// file is (agent-framework #276)
|
|
851
|
-
//
|
|
852
|
-
|
|
1589
|
+
// file is (agent-framework #276), apart from what a pattern covering only
|
|
1590
|
+
// something inside it covers, and editors' files, which are passed over
|
|
1591
|
+
// there as anywhere. A listing keeps the ignore: its pass would otherwise
|
|
1592
|
+
// start tracking what it lists.
|
|
1593
|
+
const ignore = opts.named ? ignoreBeneath(scope, view.ignore) : view.ignore;
|
|
853
1594
|
// A position outside the scope, or for a walk that doesn't descend, is no
|
|
854
1595
|
// position in this walk's order.
|
|
855
1596
|
const after = recursive && opts.resumeAfter !== undefined && (scope === '' || opts.resumeAfter.startsWith(scope + '/'))
|
|
@@ -950,9 +1691,10 @@ export async function walkScope(
|
|
|
950
1691
|
const regions: Walk['incomplete'] = [];
|
|
951
1692
|
for (const entry of entries) {
|
|
952
1693
|
const childRel = rel ? `${rel}/${entry.name}` : entry.name;
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
1694
|
+
const passedOver = passedOverBecause(childRel, entry.name, ignore, view.others);
|
|
1695
|
+
if (passedOver !== undefined) {
|
|
1696
|
+
others.push([childRel, passedOver]);
|
|
1697
|
+
regions.push({ path: childRel, reason: passedOver, kind: 'ignored' });
|
|
956
1698
|
continue;
|
|
957
1699
|
}
|
|
958
1700
|
if (entry.isDirectory()) {
|
|
@@ -1046,7 +1788,7 @@ export async function walkScope(
|
|
|
1046
1788
|
const capped = `the walk cap (${cap}) was reached`;
|
|
1047
1789
|
const holdingUntaken = new Set<string>();
|
|
1048
1790
|
for (const f of walk.seen) holdingUntaken.add(parentOf(f));
|
|
1049
|
-
for (const [p, why] of walk.others) if (why
|
|
1791
|
+
for (const [p, why] of walk.others) if (!passedOverByDesign(why) && !walk.othersTaken.has(p)) holdingUntaken.add(parentOf(p));
|
|
1050
1792
|
for (const d of holdingUntaken) {
|
|
1051
1793
|
walk.incomplete.push({ path: d, reason: `${capped} before everything here was taken`, kind: 'cap' });
|
|
1052
1794
|
}
|
|
@@ -1061,7 +1803,8 @@ export async function walkScope(
|
|
|
1061
1803
|
// The cap stopped the walk only if it left something: a walk whose last
|
|
1062
1804
|
// file used up the cap covered its scope. A directory the walk didn't list
|
|
1063
1805
|
// counts as left, even one that holds nothing it would take (empty, or
|
|
1064
|
-
// only
|
|
1806
|
+
// only names it passes over by design): telling them apart would take
|
|
1807
|
+
// another read. Such a
|
|
1065
1808
|
// walk keeps its place and reports the directory as not visited, and the
|
|
1066
1809
|
// next one lists it on its way to the wrap.
|
|
1067
1810
|
if (left === 0 && lastTaken !== undefined && (holdingUntaken.size > 0 || unlisted > 0)) walk.stoppedAfter = lastTaken;
|