@animalabs/agent-framework 0.28.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 +88 -25
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +4 -1
- 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 +7 -3
- package/dist/src/api/server.js.map +1 -1
- package/dist/src/auto-continue.d.ts +2 -0
- package/dist/src/auto-continue.d.ts.map +1 -1
- package/dist/src/auto-continue.js +5 -2
- 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/framework.d.ts +164 -36
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +1120 -479
- package/dist/src/framework.js.map +1 -1
- package/dist/src/gate/event-gate.d.ts +34 -10
- package/dist/src/gate/event-gate.d.ts.map +1 -1
- package/dist/src/gate/event-gate.js +118 -48
- package/dist/src/gate/event-gate.js.map +1 -1
- package/dist/src/gate/index.d.ts +1 -1
- package/dist/src/gate/index.d.ts.map +1 -1
- package/dist/src/gate/index.js +1 -1
- package/dist/src/gate/index.js.map +1 -1
- package/dist/src/gate/types.d.ts +12 -2
- package/dist/src/gate/types.d.ts.map +1 -1
- package/dist/src/heartbeat/heartbeat.d.ts +18 -0
- package/dist/src/heartbeat/heartbeat.d.ts.map +1 -1
- package/dist/src/heartbeat/heartbeat.js +34 -8
- package/dist/src/heartbeat/heartbeat.js.map +1 -1
- 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 +2 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -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/channel-registry.d.ts +34 -6
- package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
- package/dist/src/mcpl/channel-registry.js +131 -53
- 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 +3 -1
- package/dist/src/mcpl/inbound-source.d.ts.map +1 -1
- package/dist/src/mcpl/inbound-source.js +3 -1
- 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.map +1 -1
- package/dist/src/mcpl/server-connection.js +20 -20
- 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/tool-presence.d.ts.map +1 -1
- package/dist/src/mcpl/tool-presence.js +18 -17
- package/dist/src/mcpl/tool-presence.js.map +1 -1
- package/dist/src/mcpl/tool-signature.d.ts.map +1 -1
- package/dist/src/mcpl/tool-signature.js +17 -16
- package/dist/src/mcpl/tool-signature.js.map +1 -1
- package/dist/src/mcpl/toolset-host.d.ts +8 -26
- package/dist/src/mcpl/toolset-host.d.ts.map +1 -1
- package/dist/src/mcpl/toolset-host.js +16 -6
- package/dist/src/mcpl/toolset-host.js.map +1 -1
- package/dist/src/mcpl/toolsets-module.d.ts +14 -25
- package/dist/src/mcpl/toolsets-module.d.ts.map +1 -1
- package/dist/src/mcpl/toolsets-module.js +180 -50
- 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 +28 -12
- 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 +6 -7
- package/dist/src/module-registry.d.ts.map +1 -1
- package/dist/src/module-registry.js +6 -7
- 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.js +8 -6
- 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 +5 -1
- package/dist/src/modules/workspace/answers.d.ts.map +1 -1
- package/dist/src/modules/workspace/answers.js +16 -1
- package/dist/src/modules/workspace/answers.js.map +1 -1
- package/dist/src/modules/workspace/direct-write.d.ts.map +1 -1
- package/dist/src/modules/workspace/direct-write.js +6 -4
- package/dist/src/modules/workspace/direct-write.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 +50 -40
- package/dist/src/modules/workspace/effects.js.map +1 -1
- package/dist/src/modules/workspace/index.d.ts +1 -0
- package/dist/src/modules/workspace/index.d.ts.map +1 -1
- package/dist/src/modules/workspace/index.js +66 -36
- package/dist/src/modules/workspace/index.js.map +1 -1
- package/dist/src/modules/workspace/observe.d.ts +51 -20
- package/dist/src/modules/workspace/observe.d.ts.map +1 -1
- package/dist/src/modules/workspace/observe.js +120 -82
- package/dist/src/modules/workspace/observe.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 +133 -37
- package/dist/src/modules/workspace/reconcile.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/reasoning-loss-notice.d.ts.map +1 -1
- package/dist/src/reasoning-loss-notice.js +5 -3
- package/dist/src/reasoning-loss-notice.js.map +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/tool-args.d.ts.map +1 -1
- package/dist/src/tool-args.js +9 -8
- 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/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/types/agent.d.ts +23 -9
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/framework.d.ts +43 -0
- package/dist/src/types/framework.d.ts.map +1 -1
- package/dist/src/types/module.d.ts +22 -11
- package/dist/src/types/module.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/agent.ts +4 -1
- package/src/api/mcp-server-core.ts +3 -14
- package/src/api/server.ts +7 -3
- package/src/auto-continue.ts +8 -2
- package/src/code-execution/isolate/runner.ts +7 -5
- package/src/code-execution/isolate/scripts.ts +3 -2
- package/src/framework.ts +1132 -492
- package/src/gate/event-gate.ts +128 -47
- package/src/gate/index.ts +1 -1
- package/src/gate/types.ts +12 -2
- package/src/heartbeat/heartbeat.ts +48 -9
- package/src/held-writes.ts +3 -3
- package/src/host-presentation.ts +605 -0
- package/src/index.ts +2 -2
- package/src/line-reader.ts +58 -0
- package/src/mcpl/channel-registry.ts +137 -52
- 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 +3 -1
- 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 +20 -18
- package/src/mcpl/skip-reply-wake.ts +35 -0
- package/src/mcpl/tool-presence.ts +18 -17
- package/src/mcpl/tool-signature.ts +17 -16
- package/src/mcpl/toolset-host.ts +18 -6
- package/src/mcpl/toolsets-module.ts +142 -50
- package/src/mcpl/transport.ts +18 -14
- package/src/mcpl/types.ts +29 -12
- package/src/module-registry.ts +6 -7
- package/src/modules/api/index.ts +4 -2
- package/src/modules/discord/index.ts +37 -25
- package/src/modules/history/index.ts +8 -6
- package/src/modules/sender-name.ts +9 -0
- package/src/modules/workspace/answers.ts +20 -2
- package/src/modules/workspace/direct-write.ts +4 -3
- package/src/modules/workspace/effects.ts +50 -40
- package/src/modules/workspace/index.ts +58 -34
- package/src/modules/workspace/observe.ts +139 -88
- package/src/modules/workspace/reconcile.ts +159 -36
- package/src/notice-tags.ts +84 -0
- package/src/reasoning-loss-notice.ts +5 -3
- package/src/silent-heartbeat.ts +4 -3
- package/src/supplied-text.ts +154 -0
- package/src/tool-args.ts +9 -8
- package/src/tool-image-ledger.ts +2 -1
- package/src/tool-presentation.ts +5 -4
- package/src/tune-out/coordinator.ts +25 -27
- package/src/types/agent.ts +23 -9
- package/src/types/framework.ts +44 -0
- package/src/types/module.ts +23 -11
|
@@ -44,7 +44,7 @@ import { constants as fsConstants } from 'node:fs';
|
|
|
44
44
|
import { lstat, mkdir, open, readdir, readlink, realpath, stat, statfs } from 'node:fs/promises';
|
|
45
45
|
import { basename, dirname, isAbsolute, join, posix, relative, resolve, sep } from 'node:path';
|
|
46
46
|
import { sameRootIdentity } from './disk-agreement.js';
|
|
47
|
-
import { errnoCode, systemWords } from './answers.js';
|
|
47
|
+
import { OUTSIDE_STEP, UNFOLLOWED_STEP, errnoClause, errnoCode, systemWords } from './answers.js';
|
|
48
48
|
/**
|
|
49
49
|
* Opened with every open of a workspace path that may already be there, a
|
|
50
50
|
* read, a write in place or a directory's sync, where the platform has it:
|
|
@@ -70,11 +70,18 @@ export const ROOT_UNAVAILABLE = 'the mount root is unavailable';
|
|
|
70
70
|
export const ROOT_REPLACED = 'the mount root is not the directory disk last agreed with (an unmounted drive, or a replaced directory): ' +
|
|
71
71
|
'reconnect it; if it was replaced on purpose, sync the mount with acceptRoot to take it as it is now';
|
|
72
72
|
export const CHANGED = 'it changed while it was observed';
|
|
73
|
-
const CHANGED_WALK = 'the directory changed while the walk ran';
|
|
74
|
-
const
|
|
73
|
+
export const CHANGED_WALK = 'the directory changed while the walk ran';
|
|
74
|
+
export const FILE_CHANGED = 'the file changed while it was read';
|
|
75
|
+
export const RESOLVES_OUTSIDE = 'the directory resolves outside the mount';
|
|
76
|
+
export const LINK_TO_DIRECTORY = 'a symlink to a directory, not walked';
|
|
77
|
+
export const BENEATH_SYMLINK = 'beneath a symlink that does not lead to a directory inside the mount';
|
|
75
78
|
export function errno(err) {
|
|
76
79
|
return err?.code;
|
|
77
80
|
}
|
|
81
|
+
function failedOn(what, err) {
|
|
82
|
+
const code = errno(err);
|
|
83
|
+
return { reason: `${what} (${systemWords(code, err)})`, ...(code !== undefined ? { errno: code } : {}) };
|
|
84
|
+
}
|
|
78
85
|
export function contained(rootReal, candidate) {
|
|
79
86
|
return candidate === rootReal || candidate.startsWith(rootReal.endsWith(sep) ? rootReal : rootReal + sep);
|
|
80
87
|
}
|
|
@@ -157,9 +164,14 @@ export async function linkRefusal(view, rootReal, rel, real, through = 'path') {
|
|
|
157
164
|
return linkOnTheWay(view, rel, through);
|
|
158
165
|
}
|
|
159
166
|
export async function linkLeads(root, rootReal, link) {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
167
|
+
let leads;
|
|
168
|
+
try {
|
|
169
|
+
leads = await realpath(join(root, link));
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
const code = errno(err);
|
|
173
|
+
return code === 'ENOENT' || code === 'ENOTDIR' || code === 'ELOOP' ? { kind: 'nowhere' } : null;
|
|
174
|
+
}
|
|
163
175
|
if (!contained(rootReal, leads))
|
|
164
176
|
return { kind: 'outside' };
|
|
165
177
|
const directory = await stat(leads).then((info) => info.isDirectory(), () => false);
|
|
@@ -278,16 +290,23 @@ export async function coversLanding(view, rootReal, real, given) {
|
|
|
278
290
|
}
|
|
279
291
|
/**
|
|
280
292
|
* Why a path is refused for what linkOnTheWay found, given where a link
|
|
281
|
-
* leads (linkLeads). For a link, the link is the subject,
|
|
282
|
-
* leads: for a link inside the mount, its target,
|
|
283
|
-
*
|
|
284
|
-
*
|
|
285
|
-
*
|
|
286
|
-
*
|
|
287
|
-
*
|
|
288
|
-
*
|
|
289
|
-
*
|
|
290
|
-
*
|
|
293
|
+
* leads (linkLeads), and the way on. For a link, the link is the subject,
|
|
294
|
+
* with where it leads: for a link inside the mount, its target, as the path
|
|
295
|
+
* to use in its place, and for a link to a directory there, the setting
|
|
296
|
+
* whoever runs the host can change too; for a link to something that isn't
|
|
297
|
+
* a directory, that nothing lies beneath it (the link stands among the
|
|
298
|
+
* path's directories, so following it can't make the path beneath it
|
|
299
|
+
* exist); for one leading outside the mount, that it does, and that
|
|
300
|
+
* whoever runs the host can mount what it leads to, or a directory that
|
|
301
|
+
* holds it; for one leading nowhere, that whoever runs the host can remove it or make
|
|
302
|
+
* it lead to what it stood for; for one whose target the refusal doesn't
|
|
303
|
+
* tell, what whoever runs the host can do wherever it leads
|
|
304
|
+
* (UNFOLLOWED_STEP).
|
|
305
|
+
* A target in a nested mount's directory (`nested`) is that mount's: the
|
|
306
|
+
* refusal names the mount and the path to use there in place of the link,
|
|
307
|
+
* and offers no setting, since following the link would only reach into
|
|
308
|
+
* another mount's directory. For a directory the walk couldn't ask, which
|
|
309
|
+
* one, the answer it got, and the errno's way on (errnoClause). Paths
|
|
291
310
|
* are spelled as the tools take them, from the mount's name. On a mount with
|
|
292
311
|
* an include, an inside target is named only where a tool could name it: a
|
|
293
312
|
* file the include covers, or a directory, which a listing names whatever
|
|
@@ -298,28 +317,37 @@ export async function coversLanding(view, rootReal, real, given) {
|
|
|
298
317
|
*/
|
|
299
318
|
export function linkWords(name, found, leads, nested = [], include) {
|
|
300
319
|
const spell = (rel) => `"${rel === '' ? name : `${name}/${rel}`}"`;
|
|
301
|
-
if (!('link' in found))
|
|
302
|
-
|
|
320
|
+
if (!('link' in found)) {
|
|
321
|
+
const code = errnoCode({ code: found.code });
|
|
322
|
+
return `${UNSURE_PREFIX}${name}/${found.unsure}" is a symlink (${systemWords(code, found.code)}); ${errnoClause(code, 'calling again', 'calling again then gets past it')}`;
|
|
323
|
+
}
|
|
303
324
|
const link = `${spell(found.link)} is a symlink`;
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
325
|
+
const unknown = `${link}, which this mount does not follow; ${UNFOLLOWED_STEP}`;
|
|
326
|
+
if (leads === null)
|
|
327
|
+
return unknown;
|
|
328
|
+
if (leads.kind === 'nowhere') {
|
|
329
|
+
return `${link} that leads nowhere, which this mount does not follow; whoever runs this host can remove it or make it lead to what it stood for`;
|
|
330
|
+
}
|
|
331
|
+
if (leads.kind === 'outside') {
|
|
332
|
+
return `${link} leading outside the mount, which this mount does not follow; followed or not, ${OUTSIDE_STEP}`;
|
|
333
|
+
}
|
|
308
334
|
const holder = nestedHolder(leads.target, nested);
|
|
309
335
|
if (holder) {
|
|
310
336
|
if (!namesHolder(leads, nested))
|
|
311
|
-
return
|
|
337
|
+
return unknown;
|
|
312
338
|
const rest = restBeneath(leads.target, holder);
|
|
313
339
|
const guess = nestedGuess(name, leads.target.split('/'), holder);
|
|
314
340
|
return `${link} to ${spell(leads.target)}, which this mount does not follow; it ${guess === undefined ? 'lies' : 'may lie'} in mount ` +
|
|
315
341
|
`"${holder.name}", nested in mount "${name}": ${nestedStep({ name: holder.name, use: rest ? `${holder.name}/${rest}` : holder.name, guess }, { after: ` in place of ${spell(found.link)}` })}`;
|
|
316
342
|
}
|
|
317
343
|
if (!leads.directory && !insideMount(leads.target, include))
|
|
318
|
-
return
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
344
|
+
return unknown;
|
|
345
|
+
if (!leads.directory)
|
|
346
|
+
return `${link} to ${spell(leads.target)}, which this mount does not follow; that isn't a directory, so nothing lies beneath it`;
|
|
347
|
+
// Either way serves only what the include covers where it leads (judgedAt).
|
|
348
|
+
const covered = include === undefined ? '' : '; either way, the mount reaches only what its include covers there';
|
|
349
|
+
return `${link} to ${spell(leads.target)}, which this mount does not follow; use ${spell(leads.target)} in place of ${spell(found.link)}, ` +
|
|
350
|
+
`or whoever runs this host can set followSymlinks: true on mount "${name}"${covered}`;
|
|
323
351
|
}
|
|
324
352
|
/**
|
|
325
353
|
* Where a link linkOnTheWay found leads, as its refusal may tell it
|
|
@@ -335,7 +363,7 @@ export async function shownLeads(root, rootReal, found, include) {
|
|
|
335
363
|
}
|
|
336
364
|
/** Why a path is refused for what linkOnTheWay found (linkWords, with where the link leads, as shownLeads tells it). */
|
|
337
365
|
export async function linkReason(view, rootReal, found) {
|
|
338
|
-
return linkWords(view.name, found, await shownLeads(view.root, rootReal, found, view.include), view.nested, view.include);
|
|
366
|
+
return { reason: linkWords(view.name, found, await shownLeads(view.root, rootReal, found, view.include), view.nested, view.include), ownStep: true };
|
|
339
367
|
}
|
|
340
368
|
export function realSegments(path) {
|
|
341
369
|
return path.split(sep).filter((segment, i) => i === 0 || segment !== '');
|
|
@@ -930,14 +958,14 @@ async function locateDir(view, rootReal, rel) {
|
|
|
930
958
|
// link this mount doesn't follow.
|
|
931
959
|
const found = rel === '' ? null : await linkOnTheWay(view, rel);
|
|
932
960
|
if (found !== null)
|
|
933
|
-
return { kind: 'unobserved',
|
|
934
|
-
return { kind: 'unobserved',
|
|
961
|
+
return { kind: 'unobserved', ...(await linkReason(view, rootReal, found)) };
|
|
962
|
+
return { kind: 'unobserved', ...failedOn('cannot resolve the directory', err) };
|
|
935
963
|
}
|
|
936
964
|
// A mount that doesn't follow symlinks goes through none on the way to a
|
|
937
965
|
// directory, inside the mount or out of it.
|
|
938
966
|
const found = await linkRefusal(view, rootReal, rel, real);
|
|
939
967
|
if (found !== null)
|
|
940
|
-
return { kind: 'unobserved',
|
|
968
|
+
return { kind: 'unobserved', ...(await linkReason(view, rootReal, found)) };
|
|
941
969
|
if (rel === '') {
|
|
942
970
|
// The root is whatever the configured path names, and it must still be
|
|
943
971
|
// the root the pass was given.
|
|
@@ -955,7 +983,7 @@ async function locateDir(view, rootReal, rel) {
|
|
|
955
983
|
const holder = await nestedAtReal(real, view.nested, { name: view.name, rootReal });
|
|
956
984
|
if (holder) {
|
|
957
985
|
const directory = holder.judgedByInclude && await stat(real).then((info) => info.isDirectory(), () => false);
|
|
958
|
-
return holder.names(directory) ? { kind: 'unobserved', reason: leadsInto(view.name, rel, holder) } : { kind: 'missing' };
|
|
986
|
+
return holder.names(directory) ? { kind: 'unobserved', reason: leadsInto(view.name, rel, holder), ownStep: true } : { kind: 'missing' };
|
|
959
987
|
}
|
|
960
988
|
}
|
|
961
989
|
const checked = await checkedAt(view, rel, real);
|
|
@@ -983,7 +1011,10 @@ export async function checkRoot(view, rootReal) {
|
|
|
983
1011
|
const found = await locateDir(view, rootReal, '');
|
|
984
1012
|
if (found.kind === 'checked')
|
|
985
1013
|
return { dir: found.dir };
|
|
986
|
-
|
|
1014
|
+
const reason = found.kind === 'unobserved' ? found.reason : ROOT_UNAVAILABLE;
|
|
1015
|
+
// What is at the root's path now says whether the directory is gone from it.
|
|
1016
|
+
const code = found.kind === 'unobserved' && found.errno !== undefined ? found.errno : reason === ROOT_UNAVAILABLE ? await rootAbsence(view.root) : undefined;
|
|
1017
|
+
return { reason, ...(code !== undefined ? { errno: code } : {}) };
|
|
987
1018
|
}
|
|
988
1019
|
/**
|
|
989
1020
|
* Check the directory at a mount-relative path. With `create`, missing
|
|
@@ -999,7 +1030,7 @@ export async function checkDir(view, rootReal, rel, opts = {}) {
|
|
|
999
1030
|
if (opts.create)
|
|
1000
1031
|
return createDir(view, rootReal, rel);
|
|
1001
1032
|
const proof = await confirmAbsent(view, rootReal, rel, 'beneath');
|
|
1002
|
-
return
|
|
1033
|
+
return 'reason' in proof ? { kind: 'unobserved', ...proof } : { kind: 'absent', by: proof.by };
|
|
1003
1034
|
}
|
|
1004
1035
|
async function createDir(view, rootReal, rel) {
|
|
1005
1036
|
const parts = rel.split('/');
|
|
@@ -1022,14 +1053,14 @@ async function createDir(view, rootReal, rel) {
|
|
|
1022
1053
|
// it isn't made yet, by a path that reaches it through a link.
|
|
1023
1054
|
const holder = await nestedAtReal(base.at(parts[i]), view.nested, { name: view.name, rootReal });
|
|
1024
1055
|
if (holder)
|
|
1025
|
-
return { kind: 'unobserved', reason: leadsInto(view.name, childRel, holder) };
|
|
1056
|
+
return { kind: 'unobserved', reason: leadsInto(view.name, childRel, holder), ownStep: true };
|
|
1026
1057
|
try {
|
|
1027
1058
|
await mkdir(base.at(parts[i]));
|
|
1028
1059
|
}
|
|
1029
1060
|
catch (err) {
|
|
1030
1061
|
const code = errno(err);
|
|
1031
1062
|
if (code !== 'EEXIST')
|
|
1032
|
-
return { kind: 'unobserved',
|
|
1063
|
+
return { kind: 'unobserved', ...failedOn(`cannot create the directory ${childRel}`, err) };
|
|
1033
1064
|
}
|
|
1034
1065
|
// Made, or there already (made meanwhile, or not a directory): it must
|
|
1035
1066
|
// be a real directory now, never a symlink or file in its place.
|
|
@@ -1039,7 +1070,7 @@ async function createDir(view, rootReal, rel) {
|
|
|
1039
1070
|
// doesn't follow symlinks, is named as one.
|
|
1040
1071
|
const there = view.followSymlinks ? null : await lstat(base.at(parts[i])).catch(() => null);
|
|
1041
1072
|
if (there?.isSymbolicLink())
|
|
1042
|
-
return { kind: 'unobserved',
|
|
1073
|
+
return { kind: 'unobserved', ...(await linkReason(view, rootReal, { link: childRel })) };
|
|
1043
1074
|
return { kind: 'unobserved', reason: NOT_A_DIRECTORY };
|
|
1044
1075
|
}
|
|
1045
1076
|
base = checked;
|
|
@@ -1064,14 +1095,14 @@ async function createDir(view, rootReal, rel) {
|
|
|
1064
1095
|
export async function confirmAbsent(view, rootReal, rel, target = 'entry') {
|
|
1065
1096
|
const root = await locateDir(view, rootReal, '');
|
|
1066
1097
|
if (root.kind !== 'checked')
|
|
1067
|
-
return ROOT_UNAVAILABLE;
|
|
1098
|
+
return { reason: ROOT_UNAVAILABLE };
|
|
1068
1099
|
let cur = root.dir;
|
|
1069
1100
|
/** Every directory the walk went through, the root first. */
|
|
1070
1101
|
const walked = [cur];
|
|
1071
1102
|
const proven = async () => {
|
|
1072
1103
|
for (const dir of walked)
|
|
1073
1104
|
if (!(await dir.stillHere()))
|
|
1074
|
-
return CHANGED;
|
|
1105
|
+
return { reason: CHANGED };
|
|
1075
1106
|
return { by: cur.rel };
|
|
1076
1107
|
};
|
|
1077
1108
|
const parts = rel.split('/');
|
|
@@ -1086,48 +1117,51 @@ export async function confirmAbsent(view, rootReal, rel, target = 'entry') {
|
|
|
1086
1117
|
catch (err) {
|
|
1087
1118
|
const code = errno(err);
|
|
1088
1119
|
if (code === 'ENOTDIR')
|
|
1089
|
-
return CHANGED;
|
|
1120
|
+
return { reason: CHANGED };
|
|
1090
1121
|
if (code !== 'ENOENT')
|
|
1091
|
-
return
|
|
1122
|
+
return failedOn('cannot stat', err);
|
|
1092
1123
|
// Absent from a directory that, like each above it, is still the one its path names.
|
|
1093
1124
|
return proven();
|
|
1094
1125
|
}
|
|
1095
1126
|
if (info.isDirectory()) {
|
|
1096
1127
|
if (last)
|
|
1097
|
-
return CHANGED; // present after all
|
|
1128
|
+
return { reason: CHANGED }; // present after all
|
|
1098
1129
|
cur = new CheckedDir(childRel, cur.at(name), lexicalOf(view, childRel), info.dev, info.ino, info.birthtimeNs);
|
|
1099
1130
|
walked.push(cur);
|
|
1100
1131
|
continue;
|
|
1101
1132
|
}
|
|
1102
1133
|
if (info.isSymbolicLink()) {
|
|
1103
1134
|
if (last && target === 'entry')
|
|
1104
|
-
return CHANGED; // present: the link itself
|
|
1135
|
+
return { reason: CHANGED }; // present: the link itself
|
|
1105
1136
|
// A mount that doesn't follow symlinks goes through none on the way.
|
|
1106
1137
|
if (!view.followSymlinks)
|
|
1107
1138
|
return linkReason(view, rootReal, { link: childRel });
|
|
1108
1139
|
const located = await locateDir(view, rootReal, childRel);
|
|
1140
|
+
if (located.kind === 'unobserved' && located.outside !== true) {
|
|
1141
|
+
return { reason: located.reason, ...(located.errno !== undefined ? { errno: located.errno } : {}), ...(located.ownStep === true ? { ownStep: true } : {}) };
|
|
1142
|
+
}
|
|
1109
1143
|
if (located.kind !== 'checked')
|
|
1110
|
-
return BENEATH_SYMLINK;
|
|
1144
|
+
return { reason: BENEATH_SYMLINK };
|
|
1111
1145
|
if (last)
|
|
1112
|
-
return CHANGED; // a directory there after all
|
|
1146
|
+
return { reason: CHANGED }; // a directory there after all
|
|
1113
1147
|
cur = located.dir;
|
|
1114
1148
|
walked.push(cur);
|
|
1115
1149
|
continue;
|
|
1116
1150
|
}
|
|
1117
1151
|
if (last && target === 'entry')
|
|
1118
|
-
return CHANGED; // present after all
|
|
1152
|
+
return { reason: CHANGED }; // present after all
|
|
1119
1153
|
// A non-directory: nothing can exist beneath it, while it is still this
|
|
1120
1154
|
// same one, in the directory still at its path.
|
|
1121
1155
|
const again = await lstat(cur.at(name), { bigint: true }).catch(() => null);
|
|
1122
1156
|
if (again === null || again.isDirectory() || again.isSymbolicLink() || again.dev !== info.dev || again.ino !== info.ino)
|
|
1123
|
-
return CHANGED;
|
|
1157
|
+
return { reason: CHANGED };
|
|
1124
1158
|
return proven();
|
|
1125
1159
|
}
|
|
1126
|
-
return CHANGED;
|
|
1160
|
+
return { reason: CHANGED };
|
|
1127
1161
|
}
|
|
1128
1162
|
async function absence(view, rootReal, rel) {
|
|
1129
1163
|
const proof = await confirmAbsent(view, rootReal, rel);
|
|
1130
|
-
return
|
|
1164
|
+
return 'reason' in proof ? { kind: 'unobserved', ...proof } : { kind: 'absent' };
|
|
1131
1165
|
}
|
|
1132
1166
|
// ===========================================================================
|
|
1133
1167
|
// Fingerprints
|
|
@@ -1202,7 +1236,7 @@ async function parentPlacement(view, rootReal, relativePath) {
|
|
|
1202
1236
|
const parent = await realpath(dirname(join(view.root, relativePath)));
|
|
1203
1237
|
const found = await linkRefusal(view, rootReal, relativePath, parent, 'parents');
|
|
1204
1238
|
if (found !== null)
|
|
1205
|
-
return
|
|
1239
|
+
return linkReason(view, rootReal, found);
|
|
1206
1240
|
if (!contained(rootReal, parent))
|
|
1207
1241
|
return { reason: OUTSIDE_PARENT };
|
|
1208
1242
|
const holder = await nestedAtReal(parent, view.nested, { name: view.name, rootReal });
|
|
@@ -1211,7 +1245,7 @@ async function parentPlacement(view, rootReal, relativePath) {
|
|
|
1211
1245
|
// A place there that the nested mount's include leaves out is no mount's,
|
|
1212
1246
|
// and holds nothing beneath it, as beneath any file (namesNestedPlace).
|
|
1213
1247
|
const directory = holder.judgedByInclude && await stat(parent).then((info) => info.isDirectory(), () => false);
|
|
1214
|
-
return holder.names(directory) ? { reason: leadsInto(view.name, parentOf(relativePath), holder) } : 'gone';
|
|
1248
|
+
return holder.names(directory) ? { reason: leadsInto(view.name, parentOf(relativePath), holder), ownStep: true } : 'gone';
|
|
1215
1249
|
}
|
|
1216
1250
|
catch (err) {
|
|
1217
1251
|
const code = errno(err);
|
|
@@ -1221,8 +1255,8 @@ async function parentPlacement(view, rootReal, relativePath) {
|
|
|
1221
1255
|
// link this mount doesn't follow.
|
|
1222
1256
|
const found = await linkOnTheWay(view, relativePath, 'parents');
|
|
1223
1257
|
if (found !== null)
|
|
1224
|
-
return
|
|
1225
|
-
return
|
|
1258
|
+
return linkReason(view, rootReal, found);
|
|
1259
|
+
return failedOn('cannot resolve the parent directory', err);
|
|
1226
1260
|
}
|
|
1227
1261
|
}
|
|
1228
1262
|
/**
|
|
@@ -1237,7 +1271,7 @@ export async function observePath(view, rootReal, relativePath) {
|
|
|
1237
1271
|
if (placement === 'gone')
|
|
1238
1272
|
return absence(view, rootReal, relativePath);
|
|
1239
1273
|
if ('reason' in placement)
|
|
1240
|
-
return { kind: 'unobserved',
|
|
1274
|
+
return { kind: 'unobserved', ...placement };
|
|
1241
1275
|
let info;
|
|
1242
1276
|
try {
|
|
1243
1277
|
info = await lstat(lexical, { bigint: true });
|
|
@@ -1246,7 +1280,7 @@ export async function observePath(view, rootReal, relativePath) {
|
|
|
1246
1280
|
const code = errno(err);
|
|
1247
1281
|
if (code === 'ENOENT' || code === 'ENOTDIR')
|
|
1248
1282
|
return absence(view, rootReal, relativePath);
|
|
1249
|
-
return { kind: 'unobserved',
|
|
1283
|
+
return { kind: 'unobserved', ...failedOn('cannot stat', err) };
|
|
1250
1284
|
}
|
|
1251
1285
|
if (info.isSymbolicLink()) {
|
|
1252
1286
|
if (!view.followSymlinks)
|
|
@@ -1256,14 +1290,14 @@ export async function observePath(view, rootReal, relativePath) {
|
|
|
1256
1290
|
target = await stat(lexical, { bigint: true });
|
|
1257
1291
|
}
|
|
1258
1292
|
catch (err) {
|
|
1259
|
-
return { kind: 'unobserved',
|
|
1293
|
+
return { kind: 'unobserved', ...failedOn('a symlink whose target cannot be read', err) };
|
|
1260
1294
|
}
|
|
1261
1295
|
let real;
|
|
1262
1296
|
try {
|
|
1263
1297
|
real = await realpath(lexical);
|
|
1264
1298
|
}
|
|
1265
1299
|
catch (err) {
|
|
1266
|
-
return { kind: 'unobserved',
|
|
1300
|
+
return { kind: 'unobserved', ...failedOn('a symlink that cannot be resolved', err) };
|
|
1267
1301
|
}
|
|
1268
1302
|
if (!contained(rootReal, real))
|
|
1269
1303
|
return { kind: 'unobserved', reason: LEAVES_MOUNT };
|
|
@@ -1271,10 +1305,10 @@ export async function observePath(view, rootReal, relativePath) {
|
|
|
1271
1305
|
// A place a nested mount's include leaves out is no mount's, so the link
|
|
1272
1306
|
// leaves this one (namesNestedPlace).
|
|
1273
1307
|
if (holder) {
|
|
1274
|
-
return { kind: 'unobserved', reason:
|
|
1308
|
+
return holder.names(target.isDirectory()) ? { kind: 'unobserved', reason: leadsInto(view.name, relativePath, holder), ownStep: true } : { kind: 'unobserved', reason: LEAVES_MOUNT };
|
|
1275
1309
|
}
|
|
1276
1310
|
if (!target.isFile()) {
|
|
1277
|
-
return { kind: 'unobserved', reason: target.isDirectory() ?
|
|
1311
|
+
return { kind: 'unobserved', reason: target.isDirectory() ? LINK_TO_DIRECTORY : NOT_REGULAR };
|
|
1278
1312
|
}
|
|
1279
1313
|
// A file this mount's own include leaves out where the link lands is no
|
|
1280
1314
|
// file of this mount's (coversLanding). It's judged here, and not only
|
|
@@ -1325,18 +1359,18 @@ async function openContained(view, rootReal, relativePath) {
|
|
|
1325
1359
|
if (code === 'ELOOP' && !view.followSymlinks) {
|
|
1326
1360
|
// The link at the path, or a looping one on its way.
|
|
1327
1361
|
const found = await linkOnTheWay(view, relativePath, 'parents');
|
|
1328
|
-
return { kind: 'unobserved', reason:
|
|
1362
|
+
return found === null ? { kind: 'unobserved', reason: NOT_FOLLOWED } : { kind: 'unobserved', ...(await linkReason(view, rootReal, found)) };
|
|
1329
1363
|
}
|
|
1330
1364
|
if (code === 'EISDIR')
|
|
1331
1365
|
return { kind: 'unobserved', reason: DIRECTORY };
|
|
1332
|
-
return { kind: 'unobserved',
|
|
1366
|
+
return { kind: 'unobserved', ...failedOn('cannot open', err) };
|
|
1333
1367
|
}
|
|
1334
1368
|
let keep = false;
|
|
1335
1369
|
try {
|
|
1336
1370
|
const info = await handle.stat({ bigint: true });
|
|
1337
1371
|
const real = await realpath(lexical).catch(() => null);
|
|
1338
1372
|
if (real === null)
|
|
1339
|
-
return { kind: 'unobserved', reason:
|
|
1373
|
+
return { kind: 'unobserved', reason: FILE_CHANGED };
|
|
1340
1374
|
// O_NOFOLLOW guards only the final component: a mount that doesn't
|
|
1341
1375
|
// follow symlinks goes through none among the directories on the way
|
|
1342
1376
|
// either. Asked of what the path resolved to after the open, which the
|
|
@@ -1344,7 +1378,7 @@ async function openContained(view, rootReal, relativePath) {
|
|
|
1344
1378
|
// descriptor holds, so a link gets its own answer whatever it leads to.
|
|
1345
1379
|
const found = await linkRefusal(view, rootReal, relativePath, real);
|
|
1346
1380
|
if (found !== null)
|
|
1347
|
-
return { kind: 'unobserved',
|
|
1381
|
+
return { kind: 'unobserved', ...(await linkReason(view, rootReal, found)) };
|
|
1348
1382
|
if (!info.isFile())
|
|
1349
1383
|
return { kind: 'unobserved', reason: info.isDirectory() ? DIRECTORY : NOT_REGULAR };
|
|
1350
1384
|
const leaves = view.followSymlinks ? LEAVES_MOUNT : OUTSIDE_PARENT;
|
|
@@ -1354,13 +1388,13 @@ async function openContained(view, rootReal, relativePath) {
|
|
|
1354
1388
|
// A place a nested mount's include leaves out is no mount's. Reached
|
|
1355
1389
|
// through a link this mount doesn't follow, the link is what's refused.
|
|
1356
1390
|
if (holder)
|
|
1357
|
-
return { kind: 'unobserved', reason:
|
|
1391
|
+
return holder.names(false) ? { kind: 'unobserved', reason: leadsInto(view.name, relativePath, holder), ownStep: true } : { kind: 'unobserved', reason: view.followSymlinks ? leaves : NOT_FOLLOWED };
|
|
1358
1392
|
// So is a file this mount's own include leaves out (coversLanding).
|
|
1359
1393
|
if (!(await coversLanding(view, rootReal, real, relativePath)))
|
|
1360
1394
|
return { kind: 'unobserved', reason: view.followSymlinks ? leaves : NOT_FOLLOWED };
|
|
1361
1395
|
const named = await stat(real, { bigint: true }).catch(() => null);
|
|
1362
1396
|
if (named === null || named.dev !== info.dev || named.ino !== info.ino) {
|
|
1363
|
-
return { kind: 'unobserved', reason:
|
|
1397
|
+
return { kind: 'unobserved', reason: FILE_CHANGED };
|
|
1364
1398
|
}
|
|
1365
1399
|
if (!view.followSymlinks) {
|
|
1366
1400
|
// Where no symlink is followed, the path itself must be the file the
|
|
@@ -1370,7 +1404,7 @@ async function openContained(view, rootReal, relativePath) {
|
|
|
1370
1404
|
if (entry?.isSymbolicLink())
|
|
1371
1405
|
return { kind: 'unobserved', reason: NOT_FOLLOWED };
|
|
1372
1406
|
if (entry === null || !entry.isFile() || entry.dev !== info.dev || entry.ino !== info.ino) {
|
|
1373
|
-
return { kind: 'unobserved', reason:
|
|
1407
|
+
return { kind: 'unobserved', reason: FILE_CHANGED };
|
|
1374
1408
|
}
|
|
1375
1409
|
}
|
|
1376
1410
|
keep = true;
|
|
@@ -1380,7 +1414,7 @@ async function openContained(view, rootReal, relativePath) {
|
|
|
1380
1414
|
const code = errno(err);
|
|
1381
1415
|
if (code === 'ENOENT')
|
|
1382
1416
|
return absence(view, rootReal, relativePath);
|
|
1383
|
-
return { kind: 'unobserved',
|
|
1417
|
+
return { kind: 'unobserved', ...failedOn('cannot read', err) };
|
|
1384
1418
|
}
|
|
1385
1419
|
finally {
|
|
1386
1420
|
if (!keep)
|
|
@@ -1481,7 +1515,7 @@ export async function readPath(view, rootReal, relativePath, limit) {
|
|
|
1481
1515
|
const code = errno(err);
|
|
1482
1516
|
if (code === 'ENOENT')
|
|
1483
1517
|
return absence(view, rootReal, relativePath);
|
|
1484
|
-
return { kind: 'unobserved',
|
|
1518
|
+
return { kind: 'unobserved', ...failedOn('cannot read', err) };
|
|
1485
1519
|
}
|
|
1486
1520
|
finally {
|
|
1487
1521
|
await handle.close();
|
|
@@ -1523,7 +1557,7 @@ export async function readBytes(view, rootReal, relativePath, limit) {
|
|
|
1523
1557
|
return { kind: 'bytes', size: bytes.byteLength, bytes, head: bytes.subarray(0, HEAD_BYTES) };
|
|
1524
1558
|
}
|
|
1525
1559
|
catch (err) {
|
|
1526
|
-
return { kind: 'unobserved',
|
|
1560
|
+
return { kind: 'unobserved', ...failedOn('cannot read', err) };
|
|
1527
1561
|
}
|
|
1528
1562
|
finally {
|
|
1529
1563
|
await handle.close();
|
|
@@ -1545,7 +1579,7 @@ export async function readHead(view, rootReal, relativePath) {
|
|
|
1545
1579
|
return { kind: 'head', size: Number(info.size), mtimeMs: Number(info.mtimeMs), head: head.subarray(0, bytesRead) };
|
|
1546
1580
|
}
|
|
1547
1581
|
catch (err) {
|
|
1548
|
-
return { kind: 'unobserved',
|
|
1582
|
+
return { kind: 'unobserved', ...failedOn('cannot read', err) };
|
|
1549
1583
|
}
|
|
1550
1584
|
finally {
|
|
1551
1585
|
await handle.close();
|
|
@@ -1781,7 +1815,7 @@ const EDITOR_FILE = /^\..+\.sw[px]$|~$|\.subl.*\.tmp$/;
|
|
|
1781
1815
|
/** The walk's reason for an entry the mount's ignore list covers. */
|
|
1782
1816
|
export const IGNORED = 'ignored by the mount';
|
|
1783
1817
|
/** The walk's reason for an entry `EDITOR_FILE` names. */
|
|
1784
|
-
const EDITOR_FILE_REASON = "an editor's swap or backup file";
|
|
1818
|
+
export const EDITOR_FILE_REASON = "an editor's swap or backup file";
|
|
1785
1819
|
/**
|
|
1786
1820
|
* The walk's reason for a link it doesn't enter. On a mount with an include,
|
|
1787
1821
|
* a link the include's names don't cover is passed over with it, by design,
|
|
@@ -1789,7 +1823,7 @@ const EDITOR_FILE_REASON = "an editor's swap or backup file";
|
|
|
1789
1823
|
* that follows links, one that leads to a directory, and on one that
|
|
1790
1824
|
* doesn't, every one.
|
|
1791
1825
|
*/
|
|
1792
|
-
const LINK_NOT_WALKED = 'a symlink, not walked';
|
|
1826
|
+
export const LINK_NOT_WALKED = 'a symlink, not walked';
|
|
1793
1827
|
/** Whether a reason the walk gave an entry is one it passes over by design. */
|
|
1794
1828
|
export function passedOverByDesign(why) {
|
|
1795
1829
|
return why === IGNORED || why === EDITOR_FILE_REASON || why === OTHER_MOUNT || why === OUTSIDE_INCLUDE || why === LINK_NOT_WALKED;
|
|
@@ -1877,9 +1911,9 @@ async function checkChild(view, rootReal, parent, rel) {
|
|
|
1877
1911
|
}
|
|
1878
1912
|
catch (err) {
|
|
1879
1913
|
if (errno(err) !== 'ENOENT')
|
|
1880
|
-
return { kind: 'unobserved',
|
|
1914
|
+
return { kind: 'unobserved', ...failedOn('cannot stat', err) };
|
|
1881
1915
|
const proof = await confirmAbsent(view, rootReal, rel, 'beneath');
|
|
1882
|
-
return
|
|
1916
|
+
return 'reason' in proof ? { kind: 'unobserved', ...proof } : { kind: 'absent', by: proof.by };
|
|
1883
1917
|
}
|
|
1884
1918
|
if (!info.isDirectory())
|
|
1885
1919
|
return { kind: 'unobserved', reason: CHANGED_WALK };
|
|
@@ -1942,7 +1976,7 @@ export async function walkScope(view, rootReal, scope, recursive, cap, opts = {}
|
|
|
1942
1976
|
return walk;
|
|
1943
1977
|
}
|
|
1944
1978
|
if (top.kind === 'unobserved') {
|
|
1945
|
-
walk.incomplete.push({ path: scope, reason: top.outside ?
|
|
1979
|
+
walk.incomplete.push({ path: scope, reason: top.outside ? RESOLVES_OUTSIDE : top.reason, kind: 'error', ...(top.errno !== undefined ? { errno: top.errno } : {}) });
|
|
1946
1980
|
return walk;
|
|
1947
1981
|
}
|
|
1948
1982
|
// A scope a path sync names is walked even where the ignore list covers
|
|
@@ -1967,11 +2001,11 @@ export async function walkScope(view, rootReal, scope, recursive, cap, opts = {}
|
|
|
1967
2001
|
const visited = new Set();
|
|
1968
2002
|
/** Paths already reported as errors, so a second visit doesn't report them again. */
|
|
1969
2003
|
const failed = new Set();
|
|
1970
|
-
const fail = (path, reason) => {
|
|
2004
|
+
const fail = (path, reason, code) => {
|
|
1971
2005
|
if (failed.has(path))
|
|
1972
2006
|
return;
|
|
1973
2007
|
failed.add(path);
|
|
1974
|
-
walk.incomplete.push({ path, reason, kind: 'error' });
|
|
2008
|
+
walk.incomplete.push({ path, reason, kind: 'error', ...(code !== undefined ? { errno: code } : {}) });
|
|
1975
2009
|
};
|
|
1976
2010
|
let left = cap;
|
|
1977
2011
|
let lastTaken;
|
|
@@ -2047,6 +2081,8 @@ export async function walkScope(view, rootReal, scope, recursive, cap, opts = {}
|
|
|
2047
2081
|
const proof = rel === '' ? ROOT_UNAVAILABLE : await confirmAbsent(view, rootReal, rel, 'beneath');
|
|
2048
2082
|
if (typeof proof === 'string')
|
|
2049
2083
|
fail(rel, proof);
|
|
2084
|
+
else if ('reason' in proof)
|
|
2085
|
+
fail(rel, proof.reason, proof.errno);
|
|
2050
2086
|
else {
|
|
2051
2087
|
walk.missing.add(rel);
|
|
2052
2088
|
// Nothing was listed, so a later check that finds it again, on the
|
|
@@ -2055,7 +2091,8 @@ export async function walkScope(view, rootReal, scope, recursive, cap, opts = {}
|
|
|
2055
2091
|
}
|
|
2056
2092
|
return null;
|
|
2057
2093
|
}
|
|
2058
|
-
|
|
2094
|
+
const listed = failedOn('cannot list', err);
|
|
2095
|
+
fail(rel, listed.reason, listed.errno);
|
|
2059
2096
|
return null;
|
|
2060
2097
|
}
|
|
2061
2098
|
entries.sort((a, b) => compareNames(a.name, b.name));
|
|
@@ -2229,7 +2266,7 @@ export async function walkScope(view, rootReal, scope, recursive, cap, opts = {}
|
|
|
2229
2266
|
if (child.kind === 'absent')
|
|
2230
2267
|
walk.missing.add(sub);
|
|
2231
2268
|
else
|
|
2232
|
-
fail(sub, child.reason);
|
|
2269
|
+
fail(sub, child.reason, child.errno);
|
|
2233
2270
|
}
|
|
2234
2271
|
};
|
|
2235
2272
|
if (after === undefined) {
|
|
@@ -2244,6 +2281,7 @@ export async function walkScope(view, rootReal, scope, recursive, cap, opts = {}
|
|
|
2244
2281
|
// directories a listing showed that weren't listed. A walk that doesn't
|
|
2245
2282
|
// descend leaves its subdirectories by design.
|
|
2246
2283
|
const capped = `the walk cap (${cap}) was reached`;
|
|
2284
|
+
const raise = "whoever runs this host can raise the mount's walkCap";
|
|
2247
2285
|
const holdingUntaken = new Set();
|
|
2248
2286
|
for (const f of walk.seen)
|
|
2249
2287
|
holdingUntaken.add(parentOf(f));
|
|
@@ -2251,7 +2289,7 @@ export async function walkScope(view, rootReal, scope, recursive, cap, opts = {}
|
|
|
2251
2289
|
if (!passedOverByDesign(why) && !walk.othersTaken.has(p))
|
|
2252
2290
|
holdingUntaken.add(parentOf(p));
|
|
2253
2291
|
for (const d of holdingUntaken) {
|
|
2254
|
-
walk.incomplete.push({ path: d, reason: `${capped} before everything here was taken`, kind: 'cap' });
|
|
2292
|
+
walk.incomplete.push({ path: d, reason: `${capped} before everything here was taken; naming a path beneath it walks only that path, and ${raise}`, kind: 'cap' });
|
|
2255
2293
|
}
|
|
2256
2294
|
let unlisted = 0;
|
|
2257
2295
|
if (recursive) {
|
|
@@ -2259,7 +2297,7 @@ export async function walkScope(view, rootReal, scope, recursive, cap, opts = {}
|
|
|
2259
2297
|
if (visited.has(d) || walk.missing.has(d) || failed.has(d))
|
|
2260
2298
|
continue;
|
|
2261
2299
|
unlisted++;
|
|
2262
|
-
walk.incomplete.push({ path: d, reason: `not visited: ${capped}`, kind: 'cap' });
|
|
2300
|
+
walk.incomplete.push({ path: d, reason: `not visited: ${capped}; naming this path walks only it, and ${raise}`, kind: 'cap' });
|
|
2263
2301
|
}
|
|
2264
2302
|
}
|
|
2265
2303
|
// The cap stopped the walk only if it left something: a walk whose last
|