@animalabs/agent-framework 0.27.0 → 0.29.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 +145 -41
- package/dist/src/agent.d.ts +29 -2
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +73 -12
- package/dist/src/agent.js.map +1 -1
- package/dist/src/api/mcp-server-core.d.ts.map +1 -1
- package/dist/src/api/mcp-server-core.js +3 -12
- 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 +11 -5
- package/dist/src/api/server.js.map +1 -1
- package/dist/src/auto-continue.d.ts +5 -2
- package/dist/src/auto-continue.d.ts.map +1 -1
- package/dist/src/auto-continue.js +9 -5
- package/dist/src/auto-continue.js.map +1 -1
- package/dist/src/code-execution/isolate/runner.d.ts +7 -5
- package/dist/src/code-execution/isolate/runner.d.ts.map +1 -1
- package/dist/src/code-execution/isolate/runner.js +7 -5
- package/dist/src/code-execution/isolate/runner.js.map +1 -1
- package/dist/src/code-execution/isolate/scripts.d.ts.map +1 -1
- package/dist/src/code-execution/isolate/scripts.js +3 -2
- package/dist/src/code-execution/isolate/scripts.js.map +1 -1
- package/dist/src/cut-off-round.d.ts +24 -0
- package/dist/src/cut-off-round.d.ts.map +1 -0
- package/dist/src/cut-off-round.js +87 -0
- package/dist/src/cut-off-round.js.map +1 -0
- package/dist/src/framework.d.ts +468 -125
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +3651 -1111
- package/dist/src/framework.js.map +1 -1
- package/dist/src/gate/event-gate.d.ts +133 -13
- package/dist/src/gate/event-gate.d.ts.map +1 -1
- package/dist/src/gate/event-gate.js +654 -59
- package/dist/src/gate/event-gate.js.map +1 -1
- package/dist/src/gate/index.d.ts +3 -2
- package/dist/src/gate/index.d.ts.map +1 -1
- package/dist/src/gate/index.js +2 -1
- package/dist/src/gate/index.js.map +1 -1
- package/dist/src/gate/reply-judge.d.ts +116 -0
- package/dist/src/gate/reply-judge.d.ts.map +1 -0
- package/dist/src/gate/reply-judge.js +467 -0
- package/dist/src/gate/reply-judge.js.map +1 -0
- package/dist/src/gate/types.d.ts +173 -2
- package/dist/src/gate/types.d.ts.map +1 -1
- package/dist/src/heartbeat/heartbeat.d.ts +202 -0
- package/dist/src/heartbeat/heartbeat.d.ts.map +1 -0
- package/dist/src/heartbeat/heartbeat.js +778 -0
- package/dist/src/heartbeat/heartbeat.js.map +1 -0
- package/dist/src/heartbeat/reminder-time.d.ts +5 -0
- package/dist/src/heartbeat/reminder-time.d.ts.map +1 -0
- package/dist/src/heartbeat/reminder-time.js +107 -0
- package/dist/src/heartbeat/reminder-time.js.map +1 -0
- package/dist/src/held-writes.d.ts +3 -3
- package/dist/src/held-writes.js +3 -3
- package/dist/src/host-presentation.d.ts +170 -0
- package/dist/src/host-presentation.d.ts.map +1 -0
- package/dist/src/host-presentation.js +561 -0
- package/dist/src/host-presentation.js.map +1 -0
- package/dist/src/index.d.ts +5 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/line-reader.d.ts +4 -0
- package/dist/src/line-reader.d.ts.map +1 -0
- package/dist/src/line-reader.js +59 -0
- package/dist/src/line-reader.js.map +1 -0
- package/dist/src/mcpl/capability-mask.d.ts.map +1 -1
- package/dist/src/mcpl/capability-mask.js +3 -2
- package/dist/src/mcpl/capability-mask.js.map +1 -1
- package/dist/src/mcpl/channel-registry.d.ts +34 -6
- package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
- package/dist/src/mcpl/channel-registry.js +133 -54
- package/dist/src/mcpl/channel-registry.js.map +1 -1
- package/dist/src/mcpl/conversation-router.d.ts.map +1 -1
- package/dist/src/mcpl/conversation-router.js +2 -1
- package/dist/src/mcpl/conversation-router.js.map +1 -1
- package/dist/src/mcpl/convert-block.d.ts +1 -0
- package/dist/src/mcpl/convert-block.d.ts.map +1 -1
- package/dist/src/mcpl/convert-block.js +16 -4
- package/dist/src/mcpl/convert-block.js.map +1 -1
- package/dist/src/mcpl/hook-orchestrator.d.ts.map +1 -1
- package/dist/src/mcpl/hook-orchestrator.js +4 -3
- package/dist/src/mcpl/hook-orchestrator.js.map +1 -1
- package/dist/src/mcpl/inbound-source.d.ts +29 -11
- package/dist/src/mcpl/inbound-source.d.ts.map +1 -1
- package/dist/src/mcpl/inbound-source.js +43 -70
- package/dist/src/mcpl/inbound-source.js.map +1 -1
- package/dist/src/mcpl/modern-connection.d.ts.map +1 -1
- package/dist/src/mcpl/modern-connection.js +11 -7
- package/dist/src/mcpl/modern-connection.js.map +1 -1
- package/dist/src/mcpl/protocol-family.d.ts +3 -2
- package/dist/src/mcpl/protocol-family.d.ts.map +1 -1
- package/dist/src/mcpl/protocol-family.js +25 -3
- package/dist/src/mcpl/protocol-family.js.map +1 -1
- package/dist/src/mcpl/push-handler.js +2 -2
- package/dist/src/mcpl/push-handler.js.map +1 -1
- package/dist/src/mcpl/references.d.ts +12 -40
- package/dist/src/mcpl/references.d.ts.map +1 -1
- package/dist/src/mcpl/references.js +29 -23
- package/dist/src/mcpl/references.js.map +1 -1
- package/dist/src/mcpl/server-connection.d.ts +18 -8
- package/dist/src/mcpl/server-connection.d.ts.map +1 -1
- package/dist/src/mcpl/server-connection.js +70 -30
- package/dist/src/mcpl/server-connection.js.map +1 -1
- package/dist/src/mcpl/skip-reply-wake.d.ts +25 -0
- package/dist/src/mcpl/skip-reply-wake.d.ts.map +1 -1
- package/dist/src/mcpl/skip-reply-wake.js +25 -0
- package/dist/src/mcpl/skip-reply-wake.js.map +1 -1
- package/dist/src/mcpl/tags.d.ts +9 -2
- package/dist/src/mcpl/tags.d.ts.map +1 -1
- package/dist/src/mcpl/tags.js +12 -2
- package/dist/src/mcpl/tags.js.map +1 -1
- package/dist/src/mcpl/tool-presence.d.ts +66 -14
- package/dist/src/mcpl/tool-presence.d.ts.map +1 -1
- package/dist/src/mcpl/tool-presence.js +150 -80
- package/dist/src/mcpl/tool-presence.js.map +1 -1
- package/dist/src/mcpl/tool-signature.d.ts +10 -3
- package/dist/src/mcpl/tool-signature.d.ts.map +1 -1
- package/dist/src/mcpl/tool-signature.js +60 -30
- package/dist/src/mcpl/tool-signature.js.map +1 -1
- package/dist/src/mcpl/toolset-host.d.ts +88 -55
- package/dist/src/mcpl/toolset-host.d.ts.map +1 -1
- package/dist/src/mcpl/toolset-host.js +220 -111
- package/dist/src/mcpl/toolset-host.js.map +1 -1
- package/dist/src/mcpl/toolsets-module.d.ts +27 -35
- package/dist/src/mcpl/toolsets-module.d.ts.map +1 -1
- package/dist/src/mcpl/toolsets-module.js +313 -91
- package/dist/src/mcpl/toolsets-module.js.map +1 -1
- package/dist/src/mcpl/transport.d.ts +1 -1
- package/dist/src/mcpl/transport.d.ts.map +1 -1
- package/dist/src/mcpl/transport.js +16 -16
- package/dist/src/mcpl/transport.js.map +1 -1
- package/dist/src/mcpl/types.d.ts +31 -13
- package/dist/src/mcpl/types.d.ts.map +1 -1
- package/dist/src/mcpl/types.js.map +1 -1
- package/dist/src/module-registry.d.ts +10 -8
- package/dist/src/module-registry.d.ts.map +1 -1
- package/dist/src/module-registry.js +15 -9
- package/dist/src/module-registry.js.map +1 -1
- package/dist/src/modules/api/index.d.ts.map +1 -1
- package/dist/src/modules/api/index.js +4 -2
- package/dist/src/modules/api/index.js.map +1 -1
- package/dist/src/modules/discord/index.d.ts.map +1 -1
- package/dist/src/modules/discord/index.js +33 -25
- package/dist/src/modules/discord/index.js.map +1 -1
- package/dist/src/modules/history/index.d.ts.map +1 -1
- package/dist/src/modules/history/index.js +30 -17
- package/dist/src/modules/history/index.js.map +1 -1
- package/dist/src/modules/sender-name.d.ts +7 -0
- package/dist/src/modules/sender-name.d.ts.map +1 -0
- package/dist/src/modules/sender-name.js +10 -0
- package/dist/src/modules/sender-name.js.map +1 -0
- package/dist/src/modules/workspace/answers.d.ts +26 -0
- package/dist/src/modules/workspace/answers.d.ts.map +1 -0
- package/dist/src/modules/workspace/answers.js +138 -0
- package/dist/src/modules/workspace/answers.js.map +1 -0
- package/dist/src/modules/workspace/branch-intent.d.ts +5 -3
- package/dist/src/modules/workspace/branch-intent.d.ts.map +1 -1
- package/dist/src/modules/workspace/branch-intent.js +6 -2
- package/dist/src/modules/workspace/branch-intent.js.map +1 -1
- package/dist/src/modules/workspace/direct-write.d.ts +3 -0
- package/dist/src/modules/workspace/direct-write.d.ts.map +1 -1
- package/dist/src/modules/workspace/direct-write.js +59 -24
- package/dist/src/modules/workspace/direct-write.js.map +1 -1
- package/dist/src/modules/workspace/disk-agreement.d.ts +33 -11
- package/dist/src/modules/workspace/disk-agreement.d.ts.map +1 -1
- package/dist/src/modules/workspace/disk-agreement.js +48 -17
- package/dist/src/modules/workspace/disk-agreement.js.map +1 -1
- package/dist/src/modules/workspace/edit-text.d.ts +2 -1
- package/dist/src/modules/workspace/edit-text.d.ts.map +1 -1
- package/dist/src/modules/workspace/edit-text.js +4 -2
- package/dist/src/modules/workspace/edit-text.js.map +1 -1
- package/dist/src/modules/workspace/effects.d.ts +4 -2
- package/dist/src/modules/workspace/effects.d.ts.map +1 -1
- package/dist/src/modules/workspace/effects.js +67 -50
- package/dist/src/modules/workspace/effects.js.map +1 -1
- package/dist/src/modules/workspace/index.d.ts +63 -51
- package/dist/src/modules/workspace/index.d.ts.map +1 -1
- package/dist/src/modules/workspace/index.js +980 -547
- package/dist/src/modules/workspace/index.js.map +1 -1
- package/dist/src/modules/workspace/observe.d.ts +125 -82
- package/dist/src/modules/workspace/observe.d.ts.map +1 -1
- package/dist/src/modules/workspace/observe.js +465 -241
- package/dist/src/modules/workspace/observe.js.map +1 -1
- package/dist/src/modules/workspace/read-page.d.ts.map +1 -1
- package/dist/src/modules/workspace/read-page.js +7 -1
- package/dist/src/modules/workspace/read-page.js.map +1 -1
- package/dist/src/modules/workspace/reconcile.d.ts +7 -1
- package/dist/src/modules/workspace/reconcile.d.ts.map +1 -1
- package/dist/src/modules/workspace/reconcile.js +156 -50
- package/dist/src/modules/workspace/reconcile.js.map +1 -1
- package/dist/src/modules/workspace/result-text.d.ts +1 -0
- package/dist/src/modules/workspace/result-text.d.ts.map +1 -1
- package/dist/src/modules/workspace/result-text.js.map +1 -1
- package/dist/src/modules/workspace/sync.d.ts +9 -0
- package/dist/src/modules/workspace/sync.d.ts.map +1 -1
- package/dist/src/modules/workspace/sync.js +14 -0
- package/dist/src/modules/workspace/sync.js.map +1 -1
- package/dist/src/modules/workspace/types.d.ts +96 -43
- package/dist/src/modules/workspace/types.d.ts.map +1 -1
- package/dist/src/modules/workspace/types.js.map +1 -1
- package/dist/src/modules/workspace/watcher.d.ts.map +1 -1
- package/dist/src/modules/workspace/watcher.js +8 -3
- package/dist/src/modules/workspace/watcher.js.map +1 -1
- package/dist/src/notice-tags.d.ts +79 -0
- package/dist/src/notice-tags.d.ts.map +1 -0
- package/dist/src/notice-tags.js +83 -0
- package/dist/src/notice-tags.js.map +1 -0
- package/dist/src/prose-drafts.d.ts +4 -3
- package/dist/src/prose-drafts.d.ts.map +1 -1
- package/dist/src/prose-drafts.js +3 -2
- package/dist/src/prose-drafts.js.map +1 -1
- package/dist/src/reasoning-loss-notice.d.ts +23 -0
- package/dist/src/reasoning-loss-notice.d.ts.map +1 -0
- package/dist/src/reasoning-loss-notice.js +52 -0
- package/dist/src/reasoning-loss-notice.js.map +1 -0
- package/dist/src/reasoning-loss.d.ts +24 -0
- package/dist/src/reasoning-loss.d.ts.map +1 -0
- package/dist/src/reasoning-loss.js +102 -0
- package/dist/src/reasoning-loss.js.map +1 -0
- package/dist/src/send-tools.d.ts +3 -2
- package/dist/src/send-tools.d.ts.map +1 -1
- package/dist/src/send-tools.js +3 -2
- package/dist/src/send-tools.js.map +1 -1
- package/dist/src/sent-rows.js +1 -1
- package/dist/src/silent-heartbeat.d.ts +1 -53
- package/dist/src/silent-heartbeat.d.ts.map +1 -1
- package/dist/src/silent-heartbeat.js +56 -3
- package/dist/src/silent-heartbeat.js.map +1 -1
- package/dist/src/supplied-text.d.ts +35 -0
- package/dist/src/supplied-text.d.ts.map +1 -0
- package/dist/src/supplied-text.js +141 -0
- package/dist/src/supplied-text.js.map +1 -0
- package/dist/src/thinking-drops.d.ts +4 -11
- package/dist/src/thinking-drops.d.ts.map +1 -1
- package/dist/src/thinking-drops.js +7 -14
- package/dist/src/thinking-drops.js.map +1 -1
- package/dist/src/tool-args.d.ts +12 -0
- package/dist/src/tool-args.d.ts.map +1 -1
- package/dist/src/tool-args.js +39 -4
- package/dist/src/tool-args.js.map +1 -1
- package/dist/src/tool-image-ledger.d.ts.map +1 -1
- package/dist/src/tool-image-ledger.js +2 -1
- package/dist/src/tool-image-ledger.js.map +1 -1
- package/dist/src/tool-presentation.d.ts.map +1 -1
- package/dist/src/tool-presentation.js +5 -4
- package/dist/src/tool-presentation.js.map +1 -1
- package/dist/src/tool-result-guard.d.ts +8 -4
- package/dist/src/tool-result-guard.d.ts.map +1 -1
- package/dist/src/tool-result-guard.js +23 -11
- package/dist/src/tool-result-guard.js.map +1 -1
- package/dist/src/tune-out/coordinator.d.ts +3 -2
- package/dist/src/tune-out/coordinator.d.ts.map +1 -1
- package/dist/src/tune-out/coordinator.js +24 -26
- package/dist/src/tune-out/coordinator.js.map +1 -1
- package/dist/src/tune-out/tools.d.ts +4 -1
- package/dist/src/tune-out/tools.d.ts.map +1 -1
- package/dist/src/types/agent.d.ts +63 -18
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/events.d.ts +18 -8
- package/dist/src/types/events.d.ts.map +1 -1
- package/dist/src/types/framework.d.ts +58 -0
- package/dist/src/types/framework.d.ts.map +1 -1
- package/dist/src/types/module.d.ts +27 -13
- package/dist/src/types/module.d.ts.map +1 -1
- package/dist/src/types/trace.d.ts +34 -1
- package/dist/src/types/trace.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/agent.ts +76 -12
- package/src/api/mcp-server-core.ts +3 -14
- package/src/api/server.ts +11 -5
- package/src/auto-continue.ts +12 -5
- package/src/code-execution/isolate/runner.ts +7 -5
- package/src/code-execution/isolate/scripts.ts +3 -2
- package/src/cut-off-round.ts +98 -0
- package/src/framework.ts +3772 -1138
- package/src/gate/event-gate.ts +705 -60
- package/src/gate/index.ts +9 -1
- package/src/gate/reply-judge.ts +512 -0
- package/src/gate/types.ts +176 -3
- package/src/heartbeat/heartbeat.ts +843 -0
- package/src/heartbeat/reminder-time.ts +107 -0
- package/src/held-writes.ts +3 -3
- package/src/host-presentation.ts +605 -0
- package/src/index.ts +5 -1
- package/src/line-reader.ts +58 -0
- package/src/mcpl/capability-mask.ts +3 -2
- package/src/mcpl/channel-registry.ts +139 -53
- package/src/mcpl/conversation-router.ts +2 -1
- package/src/mcpl/convert-block.ts +17 -4
- package/src/mcpl/hook-orchestrator.ts +4 -3
- package/src/mcpl/inbound-source.ts +45 -70
- package/src/mcpl/modern-connection.ts +11 -7
- package/src/mcpl/protocol-family.ts +24 -3
- package/src/mcpl/push-handler.ts +2 -2
- package/src/mcpl/references.ts +31 -23
- package/src/mcpl/server-connection.ts +76 -34
- package/src/mcpl/skip-reply-wake.ts +35 -0
- package/src/mcpl/tags.ts +13 -2
- package/src/mcpl/tool-presence.ts +149 -82
- package/src/mcpl/tool-signature.ts +64 -32
- package/src/mcpl/toolset-host.ts +212 -110
- package/src/mcpl/toolsets-module.ts +277 -91
- package/src/mcpl/transport.ts +18 -14
- package/src/mcpl/types.ts +32 -13
- package/src/module-registry.ts +14 -9
- package/src/modules/api/index.ts +4 -2
- package/src/modules/discord/index.ts +37 -25
- package/src/modules/history/index.ts +30 -18
- package/src/modules/sender-name.ts +9 -0
- package/src/modules/workspace/answers.ts +151 -0
- package/src/modules/workspace/branch-intent.ts +10 -5
- package/src/modules/workspace/direct-write.ts +59 -23
- package/src/modules/workspace/disk-agreement.ts +52 -21
- package/src/modules/workspace/edit-text.ts +7 -5
- package/src/modules/workspace/effects.ts +69 -50
- package/src/modules/workspace/index.ts +966 -556
- package/src/modules/workspace/observe.ts +497 -242
- package/src/modules/workspace/read-page.ts +7 -1
- package/src/modules/workspace/reconcile.ts +184 -49
- package/src/modules/workspace/result-text.ts +2 -0
- package/src/modules/workspace/sync.ts +16 -0
- package/src/modules/workspace/types.ts +96 -43
- package/src/modules/workspace/watcher.ts +8 -3
- package/src/notice-tags.ts +84 -0
- package/src/prose-drafts.ts +4 -3
- package/src/reasoning-loss-notice.ts +64 -0
- package/src/reasoning-loss.ts +120 -0
- package/src/send-tools.ts +3 -2
- package/src/sent-rows.ts +1 -1
- package/src/silent-heartbeat.ts +4 -3
- package/src/supplied-text.ts +154 -0
- package/src/thinking-drops.ts +10 -16
- package/src/tool-args.ts +38 -4
- package/src/tool-image-ledger.ts +2 -1
- package/src/tool-presentation.ts +5 -4
- package/src/tool-result-guard.ts +26 -11
- package/src/tune-out/coordinator.ts +25 -27
- package/src/tune-out/tools.ts +4 -1
- package/src/types/agent.ts +63 -18
- package/src/types/events.ts +18 -8
- package/src/types/framework.ts +59 -0
- package/src/types/module.ts +28 -13
- package/src/types/trace.ts +36 -1
|
@@ -45,10 +45,16 @@ import { constants as fsConstants } from 'node:fs';
|
|
|
45
45
|
import { lstat, open, realpath, stat, unlink } from 'node:fs/promises';
|
|
46
46
|
import type { FileHandle } from 'node:fs/promises';
|
|
47
47
|
import {
|
|
48
|
+
type Cause,
|
|
48
49
|
type CheckedDir,
|
|
49
50
|
type MountView,
|
|
51
|
+
DIRECTORY,
|
|
52
|
+
LEAVES_MOUNT,
|
|
53
|
+
LINK_TO_DIRECTORY,
|
|
50
54
|
NONBLOCK,
|
|
55
|
+
NOT_A_DIRECTORY,
|
|
51
56
|
NOT_FOLLOWED,
|
|
57
|
+
NOT_REGULAR,
|
|
52
58
|
OUTSIDE_PARENT,
|
|
53
59
|
ROOT_REPLACED,
|
|
54
60
|
contained,
|
|
@@ -58,10 +64,10 @@ import {
|
|
|
58
64
|
leadsInto,
|
|
59
65
|
nameOf,
|
|
60
66
|
coversLanding,
|
|
61
|
-
namesNestedPlace,
|
|
62
67
|
nestedAtReal,
|
|
63
68
|
parentOf,
|
|
64
69
|
} from './observe.js';
|
|
70
|
+
import { errnoCode, systemWords } from './answers.js';
|
|
65
71
|
|
|
66
72
|
/** What a write or unlink may find at its path when it acts. */
|
|
67
73
|
export type Expect =
|
|
@@ -78,12 +84,18 @@ export class EffectFailed extends Error {
|
|
|
78
84
|
/** Whether the path's entry was touched before the failure. */
|
|
79
85
|
readonly touched: boolean;
|
|
80
86
|
|
|
81
|
-
constructor(message: string, touched: boolean) {
|
|
87
|
+
constructor(message: string, touched: boolean, readonly detail: Cause = {}) {
|
|
82
88
|
super(message);
|
|
83
89
|
this.touched = touched;
|
|
84
90
|
}
|
|
85
91
|
}
|
|
86
92
|
|
|
93
|
+
export function callFailed(what: string, err: unknown, touched: boolean, sep: 'paren' | 'colon' = 'paren'): EffectFailed {
|
|
94
|
+
const code = errnoCode(err);
|
|
95
|
+
const words = systemWords(code, err);
|
|
96
|
+
return new EffectFailed(sep === 'paren' ? `${what} (${words})` : `${what}: ${words}`, touched, code !== undefined ? { errno: code } : {});
|
|
97
|
+
}
|
|
98
|
+
|
|
87
99
|
const CHANGED = 'disk changed since it was checked';
|
|
88
100
|
const MODE_CHANGED = 'disk\'s mode changed since it was checked; a sync takes it in';
|
|
89
101
|
/**
|
|
@@ -114,10 +126,6 @@ const HARD_LINKED = 'a file with other hard links, which writing it in place wou
|
|
|
114
126
|
+ 'this; to write this path alone, give it a copy of its own first (copy it to a new name, then move that over it) '
|
|
115
127
|
+ 'and materialize again';
|
|
116
128
|
|
|
117
|
-
function message(err: unknown): string {
|
|
118
|
-
return err instanceof Error ? err.message : String(err);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
129
|
function noFollowFlag(view: MountView): number {
|
|
122
130
|
return !view.followSymlinks && typeof fsConstants.O_NOFOLLOW === 'number' ? fsConstants.O_NOFOLLOW : 0;
|
|
123
131
|
}
|
|
@@ -133,9 +141,9 @@ function noFollowFlag(view: MountView): number {
|
|
|
133
141
|
*/
|
|
134
142
|
async function parentFor(view: MountView, rootReal: string, rel: string, create: boolean) {
|
|
135
143
|
const root = await checkRoot(view, rootReal);
|
|
136
|
-
if ('reason' in root) throw new EffectFailed(root.reason, false);
|
|
144
|
+
if ('reason' in root) throw new EffectFailed(root.reason, false, root.errno !== undefined ? { errno: root.errno } : {});
|
|
137
145
|
const checked = await checkDir(view, rootReal, parentOf(rel), { create });
|
|
138
|
-
if (checked.kind === 'unobserved') throw new EffectFailed(checked.outside ? OUTSIDE_PARENT : checked.reason, false);
|
|
146
|
+
if (checked.kind === 'unobserved') throw new EffectFailed(checked.outside ? OUTSIDE_PARENT : checked.reason, false, checked.errno !== undefined ? { errno: checked.errno } : {});
|
|
139
147
|
return { ...checked, root: root.dir };
|
|
140
148
|
}
|
|
141
149
|
|
|
@@ -170,11 +178,11 @@ async function hashAt(target: string, id: { dev: bigint; ino: bigint }, flags: n
|
|
|
170
178
|
try {
|
|
171
179
|
handle = await open(target, fsConstants.O_RDONLY | NONBLOCK | flags);
|
|
172
180
|
} catch (err) {
|
|
173
|
-
throw
|
|
181
|
+
throw callFailed('cannot verify the disk copy', err, false);
|
|
174
182
|
}
|
|
175
183
|
try {
|
|
176
184
|
const info = await handle.stat({ bigint: true });
|
|
177
|
-
if (info.dev !== id.dev || info.ino !== id.ino) throw new EffectFailed(CHANGED, false);
|
|
185
|
+
if (info.dev !== id.dev || info.ino !== id.ino) throw new EffectFailed(CHANGED, false, { again: true });
|
|
178
186
|
const hash = createHash('sha256');
|
|
179
187
|
const chunk = Buffer.alloc(1 << 20);
|
|
180
188
|
for (let offset = 0; ;) {
|
|
@@ -184,6 +192,9 @@ async function hashAt(target: string, id: { dev: bigint; ino: bigint }, flags: n
|
|
|
184
192
|
offset += bytesRead;
|
|
185
193
|
}
|
|
186
194
|
return hash.digest('hex');
|
|
195
|
+
} catch (err) {
|
|
196
|
+
if (err instanceof EffectFailed) throw err;
|
|
197
|
+
throw callFailed('cannot verify the disk copy', err, false);
|
|
187
198
|
} finally {
|
|
188
199
|
await handle.close();
|
|
189
200
|
}
|
|
@@ -232,7 +243,7 @@ export async function writeContained(
|
|
|
232
243
|
createMode?: number,
|
|
233
244
|
): Promise<{ perm: number; widenRefused: boolean; modeKept: boolean }> {
|
|
234
245
|
const checked = await parentFor(view, rootReal, rel, true);
|
|
235
|
-
if (checked.kind !== 'checked') throw new EffectFailed(
|
|
246
|
+
if (checked.kind !== 'checked') throw new EffectFailed(NOT_A_DIRECTORY, false);
|
|
236
247
|
const parent = checked.dir;
|
|
237
248
|
const root = checked.root;
|
|
238
249
|
const target = parent.at(nameOf(rel));
|
|
@@ -258,9 +269,17 @@ export async function writeContained(
|
|
|
258
269
|
touched = true;
|
|
259
270
|
return handle;
|
|
260
271
|
} catch (err) {
|
|
261
|
-
// O_EXCL refuses any entry: a file, a directory or a link
|
|
262
|
-
|
|
263
|
-
|
|
272
|
+
// O_EXCL refuses any entry: a file, a directory or a link, one that
|
|
273
|
+
// leads nowhere included, which a followed observation reads as absent.
|
|
274
|
+
if (errno(err) === 'EEXIST') {
|
|
275
|
+
const link = (await lstat(target).catch(() => null))?.isSymbolicLink() === true;
|
|
276
|
+
const leads = link ? await stat(target).then(() => 'somewhere', (e: unknown) => errno(e)) : null;
|
|
277
|
+
throw new EffectFailed(leads === 'ENOENT' || leads === 'ENOTDIR'
|
|
278
|
+
? 'a symlink that leads nowhere is at this path, and a push makes a file only where nothing is, forced or not; '
|
|
279
|
+
+ 'whoever runs this host can remove the link, and materializing again then gets past it'
|
|
280
|
+
: 'something appeared at this path since it was checked', false, leads === 'ENOENT' || leads === 'ENOTDIR' ? {} : { again: true });
|
|
281
|
+
}
|
|
282
|
+
throw callFailed('cannot create the file', err, false);
|
|
264
283
|
}
|
|
265
284
|
};
|
|
266
285
|
let handle: FileHandle;
|
|
@@ -273,11 +292,11 @@ export async function writeContained(
|
|
|
273
292
|
} catch (err) {
|
|
274
293
|
const code = errno(err);
|
|
275
294
|
if (code === 'ENOENT' && expect.kind === 'any') handle = await create();
|
|
276
|
-
else if (code === 'ENOENT') throw new EffectFailed('the file was removed since it was checked', false);
|
|
295
|
+
else if (code === 'ENOENT') throw new EffectFailed('the file was removed since it was checked', false, { again: true });
|
|
277
296
|
else if (code === 'ELOOP' && !view.followSymlinks) throw new EffectFailed(NOT_FOLLOWED, false);
|
|
278
|
-
else if (code === 'EISDIR') throw new EffectFailed(
|
|
279
|
-
else if (code === 'ENXIO') throw new EffectFailed(
|
|
280
|
-
else throw
|
|
297
|
+
else if (code === 'EISDIR') throw new EffectFailed((await lstat(target).catch(() => null))?.isSymbolicLink() === true ? LINK_TO_DIRECTORY : DIRECTORY, false);
|
|
298
|
+
else if (code === 'ENXIO') throw new EffectFailed(NOT_REGULAR, false); // a FIFO with no reader
|
|
299
|
+
else throw callFailed('cannot open the file', err, false);
|
|
281
300
|
}
|
|
282
301
|
}
|
|
283
302
|
|
|
@@ -370,7 +389,7 @@ export async function writeContained(
|
|
|
370
389
|
return { perm: Number((await handle.stat({ bigint: true })).mode & 0o777n), widenRefused, modeKept };
|
|
371
390
|
} catch (err) {
|
|
372
391
|
if (err instanceof EffectFailed) throw err;
|
|
373
|
-
throw
|
|
392
|
+
throw callFailed(`could not ${stage}`, err, touched, 'colon');
|
|
374
393
|
} finally {
|
|
375
394
|
await handle.close();
|
|
376
395
|
}
|
|
@@ -386,10 +405,10 @@ export async function writeContained(
|
|
|
386
405
|
* it, and so does a create, before anything is made.
|
|
387
406
|
*/
|
|
388
407
|
export async function refusedLanding(view: MountView, rootReal: string, rel: string, real: string): Promise<string | null> {
|
|
389
|
-
const leaves = view.followSymlinks ?
|
|
408
|
+
const leaves = view.followSymlinks ? LEAVES_MOUNT : OUTSIDE_PARENT;
|
|
390
409
|
if (!contained(rootReal, real)) return leaves;
|
|
391
|
-
const holder = nestedAtReal(real, view.nested);
|
|
392
|
-
if (holder) return
|
|
410
|
+
const holder = await nestedAtReal(real, view.nested, { name: view.name, rootReal });
|
|
411
|
+
if (holder) return holder.names(false) ? leadsInto(view.name, rel, holder) : view.followSymlinks ? leaves : NOT_FOLLOWED;
|
|
393
412
|
if (!(await coversLanding(view, rootReal, real, rel))) return view.followSymlinks ? leaves : NOT_FOLLOWED;
|
|
394
413
|
return null;
|
|
395
414
|
}
|
|
@@ -420,30 +439,30 @@ async function checkOpened(
|
|
|
420
439
|
touched: boolean,
|
|
421
440
|
): Promise<void> {
|
|
422
441
|
const info = await handle.stat({ bigint: true });
|
|
423
|
-
if (!info.isFile()) throw new EffectFailed(
|
|
442
|
+
if (!info.isFile()) throw new EffectFailed(NOT_REGULAR, touched);
|
|
424
443
|
const located = async (): Promise<string> => {
|
|
425
444
|
const real = await realpath(target).catch(() => null);
|
|
426
|
-
if (real === null) throw new EffectFailed(CHANGED, touched);
|
|
445
|
+
if (real === null) throw new EffectFailed(CHANGED, touched, { again: true });
|
|
427
446
|
const refused = await refusedLanding(view, rootReal, rel, real);
|
|
428
447
|
if (refused !== null) throw new EffectFailed(refused, touched);
|
|
429
448
|
return real;
|
|
430
449
|
};
|
|
431
450
|
if (expect.kind === 'content') {
|
|
432
451
|
await located();
|
|
433
|
-
if ((await hashAt(target, info, noFollow)) !== expect.hash) throw new EffectFailed(CHANGED, touched);
|
|
452
|
+
if ((await hashAt(target, info, noFollow)) !== expect.hash) throw new EffectFailed(CHANGED, touched, { again: true });
|
|
434
453
|
}
|
|
435
454
|
const real = await located();
|
|
436
455
|
const named = await stat(real, { bigint: true }).catch(() => null);
|
|
437
|
-
if (named === null || named.dev !== info.dev || named.ino !== info.ino) throw new EffectFailed(CHANGED, touched);
|
|
456
|
+
if (named === null || named.dev !== info.dev || named.ino !== info.ino) throw new EffectFailed(CHANGED, touched, { again: true });
|
|
438
457
|
if (!view.followSymlinks) {
|
|
439
458
|
const entry = await lstat(target, { bigint: true }).catch(() => null);
|
|
440
459
|
if (entry?.isSymbolicLink()) throw new EffectFailed(NOT_FOLLOWED, touched);
|
|
441
|
-
if (entry === null || !entry.isFile() || entry.dev !== info.dev || entry.ino !== info.ino) throw new EffectFailed(CHANGED, touched);
|
|
460
|
+
if (entry === null || !entry.isFile() || entry.dev !== info.dev || entry.ino !== info.ino) throw new EffectFailed(CHANGED, touched, { again: true });
|
|
442
461
|
}
|
|
443
462
|
if (info.nlink > 1n) throw new EffectFailed(HARD_LINKED, touched);
|
|
444
463
|
const gone = await moved(root, parent);
|
|
445
|
-
if (gone !== null) throw new EffectFailed(gone, touched);
|
|
446
|
-
if (!unchanged(info, await handle.stat({ bigint: true }).catch(() => null))) throw new EffectFailed(CHANGED, touched);
|
|
464
|
+
if (gone !== null) throw new EffectFailed(gone, touched, gone === MOVED ? { again: true } : {});
|
|
465
|
+
if (!unchanged(info, await handle.stat({ bigint: true }).catch(() => null))) throw new EffectFailed(CHANGED, touched, { again: true });
|
|
447
466
|
}
|
|
448
467
|
|
|
449
468
|
/** The sticky bit, the one special bit the chmod before a write's bytes keeps. */
|
|
@@ -481,7 +500,7 @@ export async function chmodContained(
|
|
|
481
500
|
view: MountView, rootReal: string, rel: string, mode: number, expect: Expect, from?: number,
|
|
482
501
|
): Promise<{ refused: boolean; narrowing: boolean }> {
|
|
483
502
|
const checked = await parentFor(view, rootReal, rel, false);
|
|
484
|
-
if (checked.kind !== 'checked') throw new EffectFailed('the file was removed since it was checked', false);
|
|
503
|
+
if (checked.kind !== 'checked') throw new EffectFailed('the file was removed since it was checked', false, { again: true });
|
|
485
504
|
const parent = checked.dir;
|
|
486
505
|
const root = checked.root;
|
|
487
506
|
const target = parent.at(nameOf(rel));
|
|
@@ -494,9 +513,9 @@ export async function chmodContained(
|
|
|
494
513
|
handle = await open(target, fsConstants.O_RDONLY | NONBLOCK | noFollow);
|
|
495
514
|
} catch (err) {
|
|
496
515
|
const code = errno(err);
|
|
497
|
-
if (code === 'ENOENT') throw new EffectFailed('the file was removed since it was checked', false);
|
|
516
|
+
if (code === 'ENOENT') throw new EffectFailed('the file was removed since it was checked', false, { again: true });
|
|
498
517
|
if (code === 'ELOOP' && !view.followSymlinks) throw new EffectFailed(NOT_FOLLOWED, false);
|
|
499
|
-
throw
|
|
518
|
+
throw callFailed('cannot open the file', err, false);
|
|
500
519
|
}
|
|
501
520
|
let touched = false;
|
|
502
521
|
let stage = 'set the file\'s mode';
|
|
@@ -517,7 +536,7 @@ export async function chmodContained(
|
|
|
517
536
|
return { refused: false, narrowing };
|
|
518
537
|
} catch (err) {
|
|
519
538
|
if (err instanceof EffectFailed) throw err;
|
|
520
|
-
throw
|
|
539
|
+
throw callFailed(`could not ${stage}`, err, touched, 'colon');
|
|
521
540
|
} finally {
|
|
522
541
|
await handle.close();
|
|
523
542
|
}
|
|
@@ -550,48 +569,48 @@ export async function unlinkContained(view: MountView, rootReal: string, rel: st
|
|
|
550
569
|
} catch (err) {
|
|
551
570
|
if (errno(err) === 'ENOENT') {
|
|
552
571
|
const gone = await moved(root, parent);
|
|
553
|
-
if (gone !== null) throw new EffectFailed(gone, false);
|
|
572
|
+
if (gone !== null) throw new EffectFailed(gone, false, gone === MOVED ? { again: true } : {});
|
|
554
573
|
return parent.rel;
|
|
555
574
|
}
|
|
556
|
-
throw
|
|
575
|
+
throw callFailed('cannot stat', err, false);
|
|
557
576
|
}
|
|
558
577
|
// Confirming an unlink that already happened never removes a file that
|
|
559
578
|
// appeared since.
|
|
560
579
|
if (expect.kind === 'absent') {
|
|
561
580
|
const kind = info.isDirectory() ? 'a directory' : info.isSymbolicLink() ? 'a symlink' : info.isFile() ? 'a file' : 'a pipe, socket or device';
|
|
562
|
-
throw new EffectFailed(`${kind} appeared at this path since it was checked`, false);
|
|
581
|
+
throw new EffectFailed(`${kind} appeared at this path since it was checked`, false, { again: true });
|
|
563
582
|
}
|
|
564
583
|
let file = info;
|
|
565
584
|
if (info.isSymbolicLink()) {
|
|
566
585
|
// The link itself is what is removed; a followed one must still lie inside.
|
|
567
586
|
if (!view.followSymlinks) throw new EffectFailed(NOT_FOLLOWED, false);
|
|
568
587
|
const real = await realpath(target).catch(() => null);
|
|
569
|
-
if (real === null || !contained(rootReal, real)) throw new EffectFailed(
|
|
588
|
+
if (real === null || !contained(rootReal, real)) throw new EffectFailed(LEAVES_MOUNT, false);
|
|
570
589
|
// One leading into a nested mount's directory is that mount's: the link
|
|
571
590
|
// and the file it leads to are left as they are. Where that mount's
|
|
572
591
|
// include leaves the place out, it's no mount's, and the link leaves this
|
|
573
592
|
// one (namesNestedPlace).
|
|
574
|
-
const holder = nestedAtReal(real, view.nested);
|
|
593
|
+
const holder = await nestedAtReal(real, view.nested, { name: view.name, rootReal });
|
|
575
594
|
if (holder) {
|
|
576
|
-
const directory = holder.
|
|
577
|
-
throw new EffectFailed(
|
|
595
|
+
const directory = holder.judgedByInclude && await stat(real).then((s) => s.isDirectory(), () => false);
|
|
596
|
+
throw new EffectFailed(holder.names(directory) ? leadsInto(view.name, rel, holder) : LEAVES_MOUNT, false);
|
|
578
597
|
}
|
|
579
598
|
const through = await stat(real, { bigint: true }).catch(() => null);
|
|
580
|
-
if (through === null) throw new EffectFailed(CHANGED, false);
|
|
599
|
+
if (through === null) throw new EffectFailed(CHANGED, false, { again: true });
|
|
581
600
|
// A followed link stands for the file it leads to, so it's removed only
|
|
582
601
|
// for a regular one, as the file the workspace deleted. This refusal,
|
|
583
602
|
// and the one below for what isn't a regular file at the path, use
|
|
584
603
|
// observation's words for the path, so the push answers a forced unlink
|
|
585
604
|
// and an unforced one alike (reconcile.ts inTheWay).
|
|
586
|
-
if (!through.isFile()) throw new EffectFailed(through.isDirectory() ?
|
|
605
|
+
if (!through.isFile()) throw new EffectFailed(through.isDirectory() ? LINK_TO_DIRECTORY : NOT_REGULAR, false);
|
|
587
606
|
// One leading to a file this mount's own include leaves out leaves the
|
|
588
607
|
// mount too, and that file isn't read for a hash (coversLanding).
|
|
589
|
-
if (!(await coversLanding(view, rootReal, real, rel))) throw new EffectFailed(
|
|
608
|
+
if (!(await coversLanding(view, rootReal, real, rel))) throw new EffectFailed(LEAVES_MOUNT, false);
|
|
590
609
|
file = through;
|
|
591
610
|
} else if (info.isDirectory()) {
|
|
592
|
-
throw new EffectFailed(
|
|
611
|
+
throw new EffectFailed(DIRECTORY, false);
|
|
593
612
|
} else if (!info.isFile()) {
|
|
594
|
-
throw new EffectFailed(
|
|
613
|
+
throw new EffectFailed(NOT_REGULAR, false);
|
|
595
614
|
} else {
|
|
596
615
|
// A regular file reached through a link among its directories lands
|
|
597
616
|
// where the mount may not act: in a nested mount's directory, or on a
|
|
@@ -601,15 +620,15 @@ export async function unlinkContained(view: MountView, rootReal: string, rel: st
|
|
|
601
620
|
const refused = await refusedLanding(view, rootReal, rel, target);
|
|
602
621
|
if (refused !== null) throw new EffectFailed(refused, false);
|
|
603
622
|
}
|
|
604
|
-
if (expect.kind === 'content' && (await hashAt(target, file, noFollowFlag(view))) !== expect.hash) throw new EffectFailed(CHANGED, false);
|
|
623
|
+
if (expect.kind === 'content' && (await hashAt(target, file, noFollowFlag(view))) !== expect.hash) throw new EffectFailed(CHANGED, false, { again: true });
|
|
605
624
|
const gone = await moved(root, parent);
|
|
606
|
-
if (gone !== null) throw new EffectFailed(gone, false);
|
|
625
|
+
if (gone !== null) throw new EffectFailed(gone, false, gone === MOVED ? { again: true } : {});
|
|
607
626
|
// The entry checked is still the one at the path, unwritten since it was
|
|
608
627
|
// hashed, as close to the unlink as Node allows (see the header): a file
|
|
609
628
|
// replaced or edited while it was hashed isn't removed.
|
|
610
629
|
const again = await lstat(target, { bigint: true }).catch(() => null);
|
|
611
630
|
const named = again?.isSymbolicLink() ? await stat(target, { bigint: true }).catch(() => null) : again;
|
|
612
|
-
if (!unchanged(file, named)) throw new EffectFailed(CHANGED, false);
|
|
631
|
+
if (!unchanged(file, named)) throw new EffectFailed(CHANGED, false, { again: true });
|
|
613
632
|
try {
|
|
614
633
|
await unlink(target);
|
|
615
634
|
} catch (err) {
|
|
@@ -617,7 +636,7 @@ export async function unlinkContained(view: MountView, rootReal: string, rel: st
|
|
|
617
636
|
// An unlink is all or nothing: one refused outright changed nothing, so
|
|
618
637
|
// the path's evidence can go back as it was. Anything else may have
|
|
619
638
|
// landed (a network filesystem's EIO, say): the next observation resolves it.
|
|
620
|
-
if (code !== 'ENOENT') throw
|
|
639
|
+
if (code !== 'ENOENT') throw callFailed('could not unlink the file', err, !UNLINK_REFUSALS.has(code ?? ''), 'colon');
|
|
621
640
|
}
|
|
622
641
|
return parent.rel;
|
|
623
642
|
}
|
|
@@ -652,7 +671,7 @@ export async function syncDirectories(view: MountView, rootReal: string, rels: I
|
|
|
652
671
|
await checked.dir.sync();
|
|
653
672
|
if (!(await root.dir.stillHere())) failed.set(rel, ROOT_REPLACED);
|
|
654
673
|
} catch (err) {
|
|
655
|
-
failed.set(rel,
|
|
674
|
+
failed.set(rel, systemWords(errno(err), err));
|
|
656
675
|
}
|
|
657
676
|
}
|
|
658
677
|
return failed;
|