@animalabs/agent-framework 0.21.1 → 0.22.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 +43 -0
- package/dist/src/agent.d.ts +17 -4
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +102 -42
- package/dist/src/agent.js.map +1 -1
- package/dist/src/code-execution/py-runner.d.ts +22 -1
- package/dist/src/code-execution/py-runner.d.ts.map +1 -1
- package/dist/src/code-execution/py-runner.js +64 -9
- package/dist/src/code-execution/py-runner.js.map +1 -1
- package/dist/src/code-execution/runtime-py.d.ts +4 -0
- package/dist/src/code-execution/runtime-py.d.ts.map +1 -1
- package/dist/src/code-execution/runtime-py.js +215 -38
- package/dist/src/code-execution/runtime-py.js.map +1 -1
- package/dist/src/framework.d.ts +250 -39
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +1602 -371
- package/dist/src/framework.js.map +1 -1
- package/dist/src/gate/event-gate.d.ts +21 -0
- package/dist/src/gate/event-gate.d.ts.map +1 -1
- package/dist/src/gate/event-gate.js +40 -7
- package/dist/src/gate/event-gate.js.map +1 -1
- package/dist/src/index.d.ts +5 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +4 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
- package/dist/src/mcpl/channel-registry.js +26 -3
- package/dist/src/mcpl/channel-registry.js.map +1 -1
- package/dist/src/mcpl/conversation-router.d.ts +28 -1
- package/dist/src/mcpl/conversation-router.d.ts.map +1 -1
- package/dist/src/mcpl/conversation-router.js +17 -0
- package/dist/src/mcpl/conversation-router.js.map +1 -1
- package/dist/src/mcpl/feature-set-manager.d.ts +11 -0
- package/dist/src/mcpl/feature-set-manager.d.ts.map +1 -1
- package/dist/src/mcpl/feature-set-manager.js +31 -7
- package/dist/src/mcpl/feature-set-manager.js.map +1 -1
- package/dist/src/mcpl/index.d.ts +2 -0
- package/dist/src/mcpl/index.d.ts.map +1 -1
- package/dist/src/mcpl/index.js +1 -0
- package/dist/src/mcpl/index.js.map +1 -1
- package/dist/src/mcpl/prose-stream-router.d.ts +27 -6
- package/dist/src/mcpl/prose-stream-router.d.ts.map +1 -1
- package/dist/src/mcpl/prose-stream-router.js +85 -22
- package/dist/src/mcpl/prose-stream-router.js.map +1 -1
- package/dist/src/mcpl/push-coalescer.d.ts +32 -4
- package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
- package/dist/src/mcpl/push-coalescer.js +46 -13
- package/dist/src/mcpl/push-coalescer.js.map +1 -1
- package/dist/src/mcpl/push-handler.d.ts +5 -3
- package/dist/src/mcpl/push-handler.d.ts.map +1 -1
- package/dist/src/mcpl/push-handler.js +41 -11
- package/dist/src/mcpl/push-handler.js.map +1 -1
- package/dist/src/mcpl/server-connection.d.ts +4 -0
- package/dist/src/mcpl/server-connection.d.ts.map +1 -1
- package/dist/src/mcpl/server-connection.js +45 -8
- package/dist/src/mcpl/server-connection.js.map +1 -1
- package/dist/src/mcpl/server-registry.d.ts +5 -2
- package/dist/src/mcpl/server-registry.d.ts.map +1 -1
- package/dist/src/mcpl/server-registry.js +25 -7
- package/dist/src/mcpl/server-registry.js.map +1 -1
- package/dist/src/mcpl/tool-lifecycle.d.ts +52 -2
- package/dist/src/mcpl/tool-lifecycle.d.ts.map +1 -1
- package/dist/src/mcpl/tool-lifecycle.js +47 -1
- package/dist/src/mcpl/tool-lifecycle.js.map +1 -1
- package/dist/src/mcpl/tool-pattern-check.d.ts +51 -0
- package/dist/src/mcpl/tool-pattern-check.d.ts.map +1 -0
- package/dist/src/mcpl/tool-pattern-check.js +79 -0
- package/dist/src/mcpl/tool-pattern-check.js.map +1 -0
- package/dist/src/mcpl/types.d.ts +15 -5
- 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 +39 -0
- package/dist/src/mcpl/visible-content.d.ts.map +1 -0
- package/dist/src/mcpl/visible-content.js +54 -0
- package/dist/src/mcpl/visible-content.js.map +1 -0
- package/dist/src/module-registry.d.ts +81 -3
- package/dist/src/module-registry.d.ts.map +1 -1
- package/dist/src/module-registry.js +257 -16
- package/dist/src/module-registry.js.map +1 -1
- package/dist/src/modules/workspace/branch-intent.d.ts +72 -0
- package/dist/src/modules/workspace/branch-intent.d.ts.map +1 -0
- package/dist/src/modules/workspace/branch-intent.js +83 -0
- package/dist/src/modules/workspace/branch-intent.js.map +1 -0
- package/dist/src/modules/workspace/disk-agreement.d.ts +167 -0
- package/dist/src/modules/workspace/disk-agreement.d.ts.map +1 -0
- package/dist/src/modules/workspace/disk-agreement.js +259 -0
- package/dist/src/modules/workspace/disk-agreement.js.map +1 -0
- package/dist/src/modules/workspace/effects.d.ts +84 -0
- package/dist/src/modules/workspace/effects.d.ts.map +1 -0
- package/dist/src/modules/workspace/effects.js +368 -0
- package/dist/src/modules/workspace/effects.js.map +1 -0
- package/dist/src/modules/workspace/index.d.ts +155 -17
- package/dist/src/modules/workspace/index.d.ts.map +1 -1
- package/dist/src/modules/workspace/index.js +1059 -424
- package/dist/src/modules/workspace/index.js.map +1 -1
- package/dist/src/modules/workspace/observe.d.ts +278 -0
- package/dist/src/modules/workspace/observe.d.ts.map +1 -0
- package/dist/src/modules/workspace/observe.js +805 -0
- package/dist/src/modules/workspace/observe.js.map +1 -0
- package/dist/src/modules/workspace/reconcile.d.ts +224 -0
- package/dist/src/modules/workspace/reconcile.d.ts.map +1 -0
- package/dist/src/modules/workspace/reconcile.js +901 -0
- package/dist/src/modules/workspace/reconcile.js.map +1 -0
- package/dist/src/modules/workspace/sync.d.ts +6 -59
- package/dist/src/modules/workspace/sync.d.ts.map +1 -1
- package/dist/src/modules/workspace/sync.js +6 -257
- package/dist/src/modules/workspace/sync.js.map +1 -1
- package/dist/src/modules/workspace/types.d.ts +77 -10
- package/dist/src/modules/workspace/types.d.ts.map +1 -1
- package/dist/src/modules/workspace/types.js.map +1 -1
- package/dist/src/record-journal.d.ts +127 -0
- package/dist/src/record-journal.d.ts.map +1 -0
- package/dist/src/record-journal.js +202 -0
- package/dist/src/record-journal.js.map +1 -0
- package/dist/src/shutdown.d.ts +13 -0
- package/dist/src/shutdown.d.ts.map +1 -0
- package/dist/src/shutdown.js +46 -0
- package/dist/src/shutdown.js.map +1 -0
- package/dist/src/silent-heartbeat.d.ts +74 -0
- package/dist/src/silent-heartbeat.d.ts.map +1 -0
- package/dist/src/silent-heartbeat.js +77 -0
- package/dist/src/silent-heartbeat.js.map +1 -0
- package/dist/src/tool-presentation.d.ts +50 -0
- package/dist/src/tool-presentation.d.ts.map +1 -0
- package/dist/src/tool-presentation.js +235 -0
- package/dist/src/tool-presentation.js.map +1 -0
- package/dist/src/tool-result-guard.d.ts +125 -6
- package/dist/src/tool-result-guard.d.ts.map +1 -1
- package/dist/src/tool-result-guard.js +310 -35
- package/dist/src/tool-result-guard.js.map +1 -1
- package/dist/src/tool-result-history.d.ts +13 -0
- package/dist/src/tool-result-history.d.ts.map +1 -1
- package/dist/src/tool-result-history.js +21 -1
- package/dist/src/tool-result-history.js.map +1 -1
- package/dist/src/types/agent.d.ts +18 -2
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/events.d.ts +4 -0
- package/dist/src/types/events.d.ts.map +1 -1
- package/dist/src/types/framework.d.ts +12 -0
- package/dist/src/types/framework.d.ts.map +1 -1
- package/dist/src/types/module.d.ts +14 -0
- package/dist/src/types/module.d.ts.map +1 -1
- package/dist/src/types/trace.d.ts +73 -0
- package/dist/src/types/trace.d.ts.map +1 -1
- package/dist/test/batched-wake-locus.test.d.ts +2 -0
- package/dist/test/batched-wake-locus.test.d.ts.map +1 -0
- package/dist/test/batched-wake-locus.test.js +166 -0
- package/dist/test/batched-wake-locus.test.js.map +1 -0
- package/dist/test/code-execution.test.js +386 -2
- package/dist/test/code-execution.test.js.map +1 -1
- package/dist/test/component-defaults.test.d.ts +2 -0
- package/dist/test/component-defaults.test.d.ts.map +1 -0
- package/dist/test/component-defaults.test.js +54 -0
- package/dist/test/component-defaults.test.js.map +1 -0
- package/dist/test/conversation-routing.test.js +57 -1
- package/dist/test/conversation-routing.test.js.map +1 -1
- package/dist/test/ephemeral-settle.test.js +3 -0
- package/dist/test/ephemeral-settle.test.js.map +1 -1
- package/dist/test/event-gate.test.js +118 -0
- package/dist/test/event-gate.test.js.map +1 -1
- package/dist/test/explicit-prose-routing.test.js +977 -0
- package/dist/test/explicit-prose-routing.test.js.map +1 -1
- package/dist/test/helpers/pytc-stop-rows.d.ts +11 -0
- package/dist/test/helpers/pytc-stop-rows.d.ts.map +1 -0
- package/dist/test/helpers/pytc-stop-rows.js +143 -0
- package/dist/test/helpers/pytc-stop-rows.js.map +1 -0
- package/dist/test/mcpl-coalescing.test.js +19 -6
- package/dist/test/mcpl-coalescing.test.js.map +1 -1
- package/dist/test/mcpl-config-diagnostics.test.d.ts +2 -0
- package/dist/test/mcpl-config-diagnostics.test.d.ts.map +1 -0
- package/dist/test/mcpl-config-diagnostics.test.js +449 -0
- package/dist/test/mcpl-config-diagnostics.test.js.map +1 -0
- package/dist/test/mcpl-empty-content.test.d.ts +2 -0
- package/dist/test/mcpl-empty-content.test.d.ts.map +1 -0
- package/dist/test/mcpl-empty-content.test.js +368 -0
- package/dist/test/mcpl-empty-content.test.js.map +1 -0
- package/dist/test/mcpl-tool-lifecycle-emitter.test.js +106 -1
- package/dist/test/mcpl-tool-lifecycle-emitter.test.js.map +1 -1
- package/dist/test/mcpl-tool-lifecycle-script.test.d.ts +2 -0
- package/dist/test/mcpl-tool-lifecycle-script.test.d.ts.map +1 -0
- package/dist/test/mcpl-tool-lifecycle-script.test.js +288 -0
- package/dist/test/mcpl-tool-lifecycle-script.test.js.map +1 -0
- package/dist/test/prose-stream-router.test.js +41 -0
- package/dist/test/prose-stream-router.test.js.map +1 -1
- package/dist/test/record-journal.test.d.ts +6 -0
- package/dist/test/record-journal.test.d.ts.map +1 -0
- package/dist/test/record-journal.test.js +269 -0
- package/dist/test/record-journal.test.js.map +1 -0
- package/dist/test/save-recent-image.test.js +102 -0
- package/dist/test/save-recent-image.test.js.map +1 -1
- package/dist/test/shutdown-errors.test.d.ts +2 -0
- package/dist/test/shutdown-errors.test.d.ts.map +1 -0
- package/dist/test/shutdown-errors.test.js +662 -0
- package/dist/test/shutdown-errors.test.js.map +1 -0
- package/dist/test/silent-heartbeat-rows.test.d.ts +2 -0
- package/dist/test/silent-heartbeat-rows.test.d.ts.map +1 -0
- package/dist/test/silent-heartbeat-rows.test.js +451 -0
- package/dist/test/silent-heartbeat-rows.test.js.map +1 -0
- package/dist/test/silent-heartbeat.test.js +49 -38
- package/dist/test/silent-heartbeat.test.js.map +1 -1
- package/dist/test/stop-with-work-in-flight.test.d.ts +23 -0
- package/dist/test/stop-with-work-in-flight.test.d.ts.map +1 -0
- package/dist/test/stop-with-work-in-flight.test.js +672 -0
- package/dist/test/stop-with-work-in-flight.test.js.map +1 -0
- package/dist/test/tool-batch-hook.test.d.ts +8 -0
- package/dist/test/tool-batch-hook.test.d.ts.map +1 -0
- package/dist/test/tool-batch-hook.test.js +275 -0
- package/dist/test/tool-batch-hook.test.js.map +1 -0
- package/dist/test/tool-presentation.test.d.ts +2 -0
- package/dist/test/tool-presentation.test.d.ts.map +1 -0
- package/dist/test/tool-presentation.test.js +268 -0
- package/dist/test/tool-presentation.test.js.map +1 -0
- package/dist/test/tool-result-guard.test.js +2330 -18
- package/dist/test/tool-result-guard.test.js.map +1 -1
- package/dist/test/tool-result-history.test.js +2 -1
- package/dist/test/tool-result-history.test.js.map +1 -1
- package/dist/test/turn-trigger-provenance.test.js +35 -0
- package/dist/test/turn-trigger-provenance.test.js.map +1 -1
- package/dist/test/unconvertible-tool-result.test.d.ts +2 -0
- package/dist/test/unconvertible-tool-result.test.d.ts.map +1 -0
- package/dist/test/unconvertible-tool-result.test.js +358 -0
- package/dist/test/unconvertible-tool-result.test.js.map +1 -0
- package/dist/test/workspace-materialize-freshness.test.d.ts +16 -0
- package/dist/test/workspace-materialize-freshness.test.d.ts.map +1 -0
- package/dist/test/workspace-materialize-freshness.test.js +252 -0
- package/dist/test/workspace-materialize-freshness.test.js.map +1 -0
- package/dist/test/workspace-three-way.test.d.ts +12 -0
- package/dist/test/workspace-three-way.test.d.ts.map +1 -0
- package/dist/test/workspace-three-way.test.js +3079 -0
- package/dist/test/workspace-three-way.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/agent.ts +120 -44
- package/src/code-execution/py-runner.ts +66 -9
- package/src/code-execution/runtime-py.ts +215 -38
- package/src/framework.ts +1661 -363
- package/src/gate/event-gate.ts +59 -7
- package/src/index.ts +11 -0
- package/src/mcpl/channel-registry.ts +24 -3
- package/src/mcpl/conversation-router.ts +34 -1
- package/src/mcpl/feature-set-manager.ts +36 -11
- package/src/mcpl/index.ts +2 -0
- package/src/mcpl/prose-stream-router.ts +79 -23
- package/src/mcpl/push-coalescer.ts +62 -13
- package/src/mcpl/push-handler.ts +41 -12
- package/src/mcpl/server-connection.ts +41 -10
- package/src/mcpl/server-registry.ts +24 -7
- package/src/mcpl/tool-lifecycle.ts +77 -2
- package/src/mcpl/tool-pattern-check.ts +106 -0
- package/src/mcpl/types.ts +15 -5
- package/src/mcpl/visible-content.ts +57 -0
- package/src/module-registry.ts +252 -16
- package/src/modules/workspace/branch-intent.ts +123 -0
- package/src/modules/workspace/disk-agreement.ts +325 -0
- package/src/modules/workspace/effects.ts +362 -0
- package/src/modules/workspace/index.ts +1058 -446
- package/src/modules/workspace/observe.ts +839 -0
- package/src/modules/workspace/reconcile.ts +1136 -0
- package/src/modules/workspace/sync.ts +6 -336
- package/src/modules/workspace/types.ts +75 -10
- package/src/record-journal.ts +246 -0
- package/src/shutdown.ts +49 -0
- package/src/silent-heartbeat.ts +141 -0
- package/src/tool-presentation.ts +188 -0
- package/src/tool-result-guard.ts +335 -33
- package/src/tool-result-history.ts +25 -1
- package/src/types/agent.ts +18 -2
- package/src/types/events.ts +4 -0
- package/src/types/framework.ts +12 -0
- package/src/types/module.ts +15 -0
- package/src/types/trace.ts +80 -0
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The workspace's disk effects — a materialize's writes and unlinks, and the
|
|
3
|
+
* barriers that make them durable — under the mount boundary.
|
|
4
|
+
*
|
|
5
|
+
* Each effect is bound at its point of use, not at planning time: the parent
|
|
6
|
+
* directory is checked (located inside the mount, see observe.ts), the file is
|
|
7
|
+
* reached through it, and before anything is changed the effect checks that
|
|
8
|
+
* it acts on what was decided on — a regular file inside the mount, at this
|
|
9
|
+
* path, still holding the bytes planning saw (unless `force`, which overrides
|
|
10
|
+
* that freshness but never the boundary). Content is written only through a
|
|
11
|
+
* descriptor whose own location was confirmed inside the mount first, and
|
|
12
|
+
* only into a file with no other hard links: a write in place changes the
|
|
13
|
+
* file under every name it has, and the others may lie outside the mount (a
|
|
14
|
+
* package store's, say), so such a file is refused. Unlinking one name
|
|
15
|
+
* changes no other, so an unlink needs no such check.
|
|
16
|
+
*
|
|
17
|
+
* What an effect reports matters to the caller's evidence: a failure that
|
|
18
|
+
* never touched the path's entry leaves disk as planning saw it, so the
|
|
19
|
+
* path's evidence can be put back as it was; once the entry was touched
|
|
20
|
+
* (created, truncated, written or unlinked) the outcome is for the next
|
|
21
|
+
* observation to resolve. Directories created for a write don't touch the
|
|
22
|
+
* path's entry; they stay, empty, if the write is refused.
|
|
23
|
+
*
|
|
24
|
+
* Node has no openat, so a create, mkdir or unlink still resolves its path by
|
|
25
|
+
* name after the parent was checked: a parent replaced concurrently in that
|
|
26
|
+
* window can redirect that one operation, leaving an empty file or directory
|
|
27
|
+
* outside the mount or unlinking a same-named file there. Nor is there an
|
|
28
|
+
* unlink bound to an inode: an unlink checks the entry's identity again just
|
|
29
|
+
* before it, and a file replaced at its path after that check is the one
|
|
30
|
+
* unlinked. Content writes stay bound to the descriptor verified before
|
|
31
|
+
* modification: inside the mount, with no other hard links (a link another
|
|
32
|
+
* process makes to it after that check shares the write). The hash, which
|
|
33
|
+
* reads the whole file, comes before the checks of where the file is, and
|
|
34
|
+
* the root is checked again beside the parent, since the parent is found by
|
|
35
|
+
* path beneath the root's. A last look at the file (its descriptor's stat
|
|
36
|
+
* before a write, its entry's lstat before an unlink) refuses one written
|
|
37
|
+
* since it was first seen, so only an edit made in the moment between that
|
|
38
|
+
* look and the effect is overwritten or removed.
|
|
39
|
+
*/
|
|
40
|
+
import { createHash } from 'node:crypto';
|
|
41
|
+
import { constants as fsConstants } from 'node:fs';
|
|
42
|
+
import { lstat, open, realpath, stat, unlink } from 'node:fs/promises';
|
|
43
|
+
import { NONBLOCK, NOT_FOLLOWED, OUTSIDE_PARENT, ROOT_REPLACED, contained, errno, checkDir, checkRoot, nameOf, parentOf, } from './observe.js';
|
|
44
|
+
/** Errors with which an unlink refuses outright, leaving the entry as it was. */
|
|
45
|
+
const UNLINK_REFUSALS = new Set(['EACCES', 'EPERM', 'EROFS', 'EBUSY']);
|
|
46
|
+
export class EffectFailed extends Error {
|
|
47
|
+
/** Whether the path's entry was touched before the failure. */
|
|
48
|
+
touched;
|
|
49
|
+
constructor(message, touched) {
|
|
50
|
+
super(message);
|
|
51
|
+
this.touched = touched;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const CHANGED = 'disk changed since it was checked';
|
|
55
|
+
const MOVED = 'the directory changed since it was checked';
|
|
56
|
+
const HARD_LINKED = 'a file with other hard links, which writing it in place would change too — force does not override '
|
|
57
|
+
+ '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) '
|
|
58
|
+
+ 'and materialize again';
|
|
59
|
+
function message(err) {
|
|
60
|
+
return err instanceof Error ? err.message : String(err);
|
|
61
|
+
}
|
|
62
|
+
function noFollowFlag(view) {
|
|
63
|
+
return !view.followSymlinks && typeof fsConstants.O_NOFOLLOW === 'number' ? fsConstants.O_NOFOLLOW : 0;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The parent of a path, checked for an effect; proven absent; or why neither;
|
|
67
|
+
* with the root. The root comes first: still a directory, and still the one
|
|
68
|
+
* disk last agreed under, as any observation of it checks. An effect under a
|
|
69
|
+
* replaced root acts on nothing, whatever its caller checked before, and the
|
|
70
|
+
* parent is found by path beneath the root's path, so whoever acts checks the
|
|
71
|
+
* root again with the parent (`moved`): a root replaced after it was
|
|
72
|
+
* checked would otherwise lend the effect its replacement's directory.
|
|
73
|
+
*/
|
|
74
|
+
async function parentFor(view, rootReal, rel, create) {
|
|
75
|
+
const root = await checkRoot(view, rootReal);
|
|
76
|
+
if ('reason' in root)
|
|
77
|
+
throw new EffectFailed(root.reason, false);
|
|
78
|
+
const checked = await checkDir(view, rootReal, parentOf(rel), { create });
|
|
79
|
+
if (checked.kind === 'unobserved')
|
|
80
|
+
throw new EffectFailed(checked.outside ? OUTSIDE_PARENT : checked.reason, false);
|
|
81
|
+
return { ...checked, root: root.dir };
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Why the parent, or the root it was found beneath, is no longer the
|
|
85
|
+
* directory its path names; null while each still is. The root is asked
|
|
86
|
+
* first, so a replaced root reads the same wherever it's found.
|
|
87
|
+
*/
|
|
88
|
+
async function moved(root, parent) {
|
|
89
|
+
if (!(await root.stillHere()))
|
|
90
|
+
return ROOT_REPLACED;
|
|
91
|
+
return (await parent.stillHere()) ? null : MOVED;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Whether the file at `target` is still the one `before` described: the same
|
|
95
|
+
* file, with the same size and stamps, so nothing wrote it since. Read just
|
|
96
|
+
* before an effect, after the slower checks, so an edit made while the file
|
|
97
|
+
* was hashed is never overwritten or deleted unseen; what remains is the
|
|
98
|
+
* moment between this and the effect, which Node can't close (see the header).
|
|
99
|
+
*/
|
|
100
|
+
function unchanged(before, now) {
|
|
101
|
+
return now !== null && now.dev === before.dev && now.ino === before.ino && now.size === before.size
|
|
102
|
+
&& now.mtimeNs === before.mtimeNs && now.ctimeNs === before.ctimeNs;
|
|
103
|
+
}
|
|
104
|
+
/** The hash of what `target` holds, read through a descriptor that must be the file `id` names. */
|
|
105
|
+
async function hashAt(target, id, flags) {
|
|
106
|
+
let handle;
|
|
107
|
+
try {
|
|
108
|
+
handle = await open(target, fsConstants.O_RDONLY | NONBLOCK | flags);
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
throw new EffectFailed(`cannot verify the disk copy (${errno(err) ?? message(err)})`, false);
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
const info = await handle.stat({ bigint: true });
|
|
115
|
+
if (info.dev !== id.dev || info.ino !== id.ino)
|
|
116
|
+
throw new EffectFailed(CHANGED, false);
|
|
117
|
+
const hash = createHash('sha256');
|
|
118
|
+
const chunk = Buffer.alloc(1 << 20);
|
|
119
|
+
for (let offset = 0;;) {
|
|
120
|
+
const { bytesRead } = await handle.read(chunk, 0, chunk.length, offset);
|
|
121
|
+
if (bytesRead === 0)
|
|
122
|
+
break;
|
|
123
|
+
hash.update(chunk.subarray(0, bytesRead));
|
|
124
|
+
offset += bytesRead;
|
|
125
|
+
}
|
|
126
|
+
return hash.digest('hex');
|
|
127
|
+
}
|
|
128
|
+
finally {
|
|
129
|
+
await handle.close();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Write `bytes` at a mount-relative path, creating missing parent
|
|
134
|
+
* directories, and make the file durable (its directories are synced by the
|
|
135
|
+
* caller, after all of a push's effects: syncDirectories).
|
|
136
|
+
*/
|
|
137
|
+
export async function writeContained(view, rootReal, rel, bytes, expect) {
|
|
138
|
+
const checked = await parentFor(view, rootReal, rel, true);
|
|
139
|
+
if (checked.kind !== 'checked')
|
|
140
|
+
throw new EffectFailed('a parent path is not a directory', false);
|
|
141
|
+
const parent = checked.dir;
|
|
142
|
+
const root = checked.root;
|
|
143
|
+
const target = parent.at(nameOf(rel));
|
|
144
|
+
const noFollow = noFollowFlag(view);
|
|
145
|
+
if (!view.followSymlinks && !noFollow) {
|
|
146
|
+
// No O_NOFOLLOW (Windows): refuse a symlink by lstat before opening.
|
|
147
|
+
if ((await lstat(target).catch(() => null))?.isSymbolicLink())
|
|
148
|
+
throw new EffectFailed(NOT_FOLLOWED, false);
|
|
149
|
+
}
|
|
150
|
+
let touched = false;
|
|
151
|
+
const create = async () => {
|
|
152
|
+
try {
|
|
153
|
+
// Exclusive: never follows a symlink, never takes over a file that
|
|
154
|
+
// appeared since planning saw none.
|
|
155
|
+
const handle = await open(target, fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL, 0o644);
|
|
156
|
+
touched = true;
|
|
157
|
+
return handle;
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
// O_EXCL refuses any entry: a file, a directory or a link.
|
|
161
|
+
if (errno(err) === 'EEXIST')
|
|
162
|
+
throw new EffectFailed('something appeared at this path since it was checked', false);
|
|
163
|
+
throw new EffectFailed(`cannot create the file (${errno(err) ?? message(err)})`, false);
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
let handle;
|
|
167
|
+
if (expect.kind === 'absent') {
|
|
168
|
+
handle = await create();
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
try {
|
|
172
|
+
// Opened without truncating: nothing changes until it is located.
|
|
173
|
+
handle = await open(target, fsConstants.O_WRONLY | NONBLOCK | noFollow);
|
|
174
|
+
}
|
|
175
|
+
catch (err) {
|
|
176
|
+
const code = errno(err);
|
|
177
|
+
if (code === 'ENOENT' && expect.kind === 'any')
|
|
178
|
+
handle = await create();
|
|
179
|
+
else if (code === 'ENOENT')
|
|
180
|
+
throw new EffectFailed('the file was removed since it was checked', false);
|
|
181
|
+
else if (code === 'ELOOP' && !view.followSymlinks)
|
|
182
|
+
throw new EffectFailed(NOT_FOLLOWED, false);
|
|
183
|
+
else if (code === 'EISDIR')
|
|
184
|
+
throw new EffectFailed('a directory', false);
|
|
185
|
+
else if (code === 'ENXIO')
|
|
186
|
+
throw new EffectFailed('not a regular file', false); // a FIFO with no reader
|
|
187
|
+
else
|
|
188
|
+
throw new EffectFailed(`cannot open the file (${code ?? message(err)})`, false);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
let stage = 'write the file';
|
|
192
|
+
try {
|
|
193
|
+
// Before any byte: a regular file, and — unless forced — still holding
|
|
194
|
+
// what was decided on. The hash reads the whole file, so it comes first,
|
|
195
|
+
// and the checks of where the file is follow it.
|
|
196
|
+
const info = await handle.stat({ bigint: true });
|
|
197
|
+
if (!info.isFile())
|
|
198
|
+
throw new EffectFailed('not a regular file', touched);
|
|
199
|
+
if (expect.kind === 'content' && (await hashAt(target, info, noFollow)) !== expect.hash)
|
|
200
|
+
throw new EffectFailed(CHANGED, touched);
|
|
201
|
+
// Then: canonically inside the mount, the one this path names (and not a
|
|
202
|
+
// symlink, where the mount follows none and the open couldn't refuse
|
|
203
|
+
// one), with no other names, in the directory still at its path beneath
|
|
204
|
+
// the root still at its own.
|
|
205
|
+
const real = await realpath(target).catch(() => null);
|
|
206
|
+
if (real === null)
|
|
207
|
+
throw new EffectFailed(CHANGED, touched);
|
|
208
|
+
if (!contained(rootReal, real))
|
|
209
|
+
throw new EffectFailed(view.followSymlinks ? 'a symlink that leaves the mount' : OUTSIDE_PARENT, touched);
|
|
210
|
+
const named = await stat(real, { bigint: true }).catch(() => null);
|
|
211
|
+
if (named === null || named.dev !== info.dev || named.ino !== info.ino)
|
|
212
|
+
throw new EffectFailed(CHANGED, touched);
|
|
213
|
+
if (!view.followSymlinks) {
|
|
214
|
+
const entry = await lstat(target, { bigint: true }).catch(() => null);
|
|
215
|
+
if (entry?.isSymbolicLink())
|
|
216
|
+
throw new EffectFailed(NOT_FOLLOWED, touched);
|
|
217
|
+
if (entry === null || !entry.isFile() || entry.dev !== info.dev || entry.ino !== info.ino)
|
|
218
|
+
throw new EffectFailed(CHANGED, touched);
|
|
219
|
+
}
|
|
220
|
+
if (info.nlink > 1n)
|
|
221
|
+
throw new EffectFailed(HARD_LINKED, touched);
|
|
222
|
+
const gone = await moved(root, parent);
|
|
223
|
+
if (gone !== null)
|
|
224
|
+
throw new EffectFailed(gone, touched);
|
|
225
|
+
// Nothing wrote it while those ran.
|
|
226
|
+
if (!unchanged(info, await handle.stat({ bigint: true }).catch(() => null)))
|
|
227
|
+
throw new EffectFailed(CHANGED, touched);
|
|
228
|
+
touched = true;
|
|
229
|
+
await handle.truncate(0);
|
|
230
|
+
await handle.writeFile(bytes);
|
|
231
|
+
stage = 'make the write durable (file fsync)';
|
|
232
|
+
await handle.sync();
|
|
233
|
+
}
|
|
234
|
+
catch (err) {
|
|
235
|
+
if (err instanceof EffectFailed)
|
|
236
|
+
throw err;
|
|
237
|
+
throw new EffectFailed(`could not ${stage}: ${message(err)}`, touched);
|
|
238
|
+
}
|
|
239
|
+
finally {
|
|
240
|
+
await handle.close();
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Unlink a mount-relative file, or confirm it gone (`absent` expected: a
|
|
245
|
+
* file found there is never removed). Returns the mount directory whose
|
|
246
|
+
* entries make the absence durable, for the caller's barrier: the file's
|
|
247
|
+
* parent, or — when the parent itself is gone — the directory proven to lack
|
|
248
|
+
* it, since that removal may not be durable either.
|
|
249
|
+
*/
|
|
250
|
+
export async function unlinkContained(view, rootReal, rel, expect) {
|
|
251
|
+
const checked = await parentFor(view, rootReal, rel, false);
|
|
252
|
+
if (checked.kind === 'absent') {
|
|
253
|
+
// No directory, so no file: proven beneath a root still at its path.
|
|
254
|
+
if (!(await checked.root.stillHere()))
|
|
255
|
+
throw new EffectFailed(ROOT_REPLACED, false);
|
|
256
|
+
return checked.by;
|
|
257
|
+
}
|
|
258
|
+
const parent = checked.dir;
|
|
259
|
+
const root = checked.root;
|
|
260
|
+
const target = parent.at(nameOf(rel));
|
|
261
|
+
let info;
|
|
262
|
+
try {
|
|
263
|
+
info = await lstat(target, { bigint: true });
|
|
264
|
+
}
|
|
265
|
+
catch (err) {
|
|
266
|
+
if (errno(err) === 'ENOENT') {
|
|
267
|
+
const gone = await moved(root, parent);
|
|
268
|
+
if (gone !== null)
|
|
269
|
+
throw new EffectFailed(gone, false);
|
|
270
|
+
return parent.rel;
|
|
271
|
+
}
|
|
272
|
+
throw new EffectFailed(`cannot stat (${errno(err) ?? message(err)})`, false);
|
|
273
|
+
}
|
|
274
|
+
// Confirming an unlink that already happened never removes a file that
|
|
275
|
+
// appeared since.
|
|
276
|
+
if (expect.kind === 'absent') {
|
|
277
|
+
const kind = info.isDirectory() ? 'a directory' : info.isSymbolicLink() ? 'a symlink' : 'a file';
|
|
278
|
+
throw new EffectFailed(`${kind} appeared at this path since it was checked`, false);
|
|
279
|
+
}
|
|
280
|
+
let file = info;
|
|
281
|
+
if (info.isSymbolicLink()) {
|
|
282
|
+
// The link itself is what is removed; a followed one must still lie inside.
|
|
283
|
+
if (!view.followSymlinks)
|
|
284
|
+
throw new EffectFailed(NOT_FOLLOWED, false);
|
|
285
|
+
const real = await realpath(target).catch(() => null);
|
|
286
|
+
if (real === null || !contained(rootReal, real))
|
|
287
|
+
throw new EffectFailed('a symlink that leaves the mount', false);
|
|
288
|
+
const through = await stat(real, { bigint: true }).catch(() => null);
|
|
289
|
+
if (through === null)
|
|
290
|
+
throw new EffectFailed(CHANGED, false);
|
|
291
|
+
file = through;
|
|
292
|
+
}
|
|
293
|
+
else if (info.isDirectory()) {
|
|
294
|
+
throw new EffectFailed('a directory', false);
|
|
295
|
+
}
|
|
296
|
+
if (expect.kind === 'content' && (await hashAt(target, file, noFollowFlag(view))) !== expect.hash)
|
|
297
|
+
throw new EffectFailed(CHANGED, false);
|
|
298
|
+
const gone = await moved(root, parent);
|
|
299
|
+
if (gone !== null)
|
|
300
|
+
throw new EffectFailed(gone, false);
|
|
301
|
+
// The entry checked is still the one at the path, unwritten since it was
|
|
302
|
+
// hashed, as close to the unlink as Node allows (see the header): a file
|
|
303
|
+
// replaced or edited while it was hashed isn't removed.
|
|
304
|
+
const again = await lstat(target, { bigint: true }).catch(() => null);
|
|
305
|
+
const named = again?.isSymbolicLink() ? await stat(target, { bigint: true }).catch(() => null) : again;
|
|
306
|
+
if (!unchanged(file, named))
|
|
307
|
+
throw new EffectFailed(CHANGED, false);
|
|
308
|
+
try {
|
|
309
|
+
await unlink(target);
|
|
310
|
+
}
|
|
311
|
+
catch (err) {
|
|
312
|
+
const code = errno(err);
|
|
313
|
+
// An unlink is all or nothing: one refused outright changed nothing, so
|
|
314
|
+
// the path's evidence can go back as it was. Anything else may have
|
|
315
|
+
// landed (a network filesystem's EIO, say): the next observation resolves it.
|
|
316
|
+
if (code !== 'ENOENT')
|
|
317
|
+
throw new EffectFailed(`could not unlink the file: ${message(err)}`, !UNLINK_REFUSALS.has(code ?? ''));
|
|
318
|
+
}
|
|
319
|
+
return parent.rel;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Sync each mount directory (mount-relative), deepest first, so the entries
|
|
323
|
+
* a push created or removed in them are durable. Each is checked first and the
|
|
324
|
+
* directory it holds is the one synced — never a replacement at its path.
|
|
325
|
+
* Returns the ones that failed, with why. Where directories can't be synced
|
|
326
|
+
* at all (Windows, or a filesystem answering EINVAL/ENOTSUP), there is
|
|
327
|
+
* nothing to wait for.
|
|
328
|
+
*/
|
|
329
|
+
export async function syncDirectories(view, rootReal, rels) {
|
|
330
|
+
const failed = new Map();
|
|
331
|
+
const depth = (rel) => (rel === '' ? 0 : rel.split('/').length);
|
|
332
|
+
const sorted = [...new Set(rels)].sort((a, b) => depth(b) - depth(a));
|
|
333
|
+
// Each directory is found by path beneath the root's path: under a root
|
|
334
|
+
// that isn't the one disk agreed under, or that is replaced while they're
|
|
335
|
+
// synced, none of them is the mount's.
|
|
336
|
+
const root = await checkRoot(view, rootReal);
|
|
337
|
+
if ('reason' in root) {
|
|
338
|
+
for (const rel of sorted)
|
|
339
|
+
failed.set(rel, root.reason);
|
|
340
|
+
return failed;
|
|
341
|
+
}
|
|
342
|
+
for (const rel of sorted) {
|
|
343
|
+
const checked = await checkDir(view, rootReal, rel);
|
|
344
|
+
if (checked.kind !== 'checked') {
|
|
345
|
+
failed.set(rel, checked.kind === 'absent' ? 'the directory is gone' : checked.reason);
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
try {
|
|
349
|
+
await checked.dir.sync();
|
|
350
|
+
if (!(await root.dir.stillHere()))
|
|
351
|
+
failed.set(rel, ROOT_REPLACED);
|
|
352
|
+
}
|
|
353
|
+
catch (err) {
|
|
354
|
+
failed.set(rel, message(err));
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return failed;
|
|
358
|
+
}
|
|
359
|
+
/** The directories whose entries make a written file reachable: its parent up to the mount root. */
|
|
360
|
+
export function chainOf(rel) {
|
|
361
|
+
const chain = [];
|
|
362
|
+
for (let dir = parentOf(rel);; dir = parentOf(dir)) {
|
|
363
|
+
chain.push(dir);
|
|
364
|
+
if (dir === '')
|
|
365
|
+
return chain;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
//# sourceMappingURL=effects.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"effects.js","sourceRoot":"","sources":["../../../../src/modules/workspace/effects.ts"],"names":[],"mappings":"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"}
|
|
@@ -58,9 +58,22 @@ export declare class WorkspaceModule implements Module {
|
|
|
58
58
|
readonly name = "workspace";
|
|
59
59
|
private ctx;
|
|
60
60
|
private store;
|
|
61
|
+
/** Set once stop has drained and saved, cleared by start (running()). */
|
|
62
|
+
private stopped;
|
|
61
63
|
private config;
|
|
62
64
|
private mounts;
|
|
63
65
|
private watchers;
|
|
66
|
+
/** What disk last agreed with (global records); created with the store. */
|
|
67
|
+
private agreement;
|
|
68
|
+
/** Branch-local intent per mount. */
|
|
69
|
+
private intents;
|
|
70
|
+
/** Each mount's turn: the tail of its serialized passes and writes. */
|
|
71
|
+
private mountTurns;
|
|
72
|
+
/** Whether an over-bound agentActionScanDeadlineMs has been warned about. */
|
|
73
|
+
private warnedScanDeadline;
|
|
74
|
+
/** Whether a mount root's filesystem maintains ctime, by canonical root. */
|
|
75
|
+
/** Per canonical root: the device of its filesystem when that maintains ctime, else null. */
|
|
76
|
+
private ctimeTrust;
|
|
64
77
|
/** Persisted state decoded in start(), held until mounts exist to apply it
|
|
65
78
|
* to — in the Host ordering start() runs before initStore() creates the
|
|
66
79
|
* mounts, so restoration must be second-callback-safe like watcher
|
|
@@ -77,6 +90,21 @@ export declare class WorkspaceModule implements Module {
|
|
|
77
90
|
* second. `ensureRunning()` gates on `ctx && mounts-populated`.
|
|
78
91
|
*/
|
|
79
92
|
initStore(store: JsStore): void;
|
|
93
|
+
/**
|
|
94
|
+
* Run `fn` with the mount to itself: observation, decision and effect never
|
|
95
|
+
* interleave with another pass or a tool write on the same mount (a disk
|
|
96
|
+
* observation taken before an autoMaterialize write and applied after it
|
|
97
|
+
* would ingest old bytes over the new entry). Not re-entrant: code holding
|
|
98
|
+
* a mount's turn calls the unlocked helpers.
|
|
99
|
+
*/
|
|
100
|
+
private withMount;
|
|
101
|
+
private agreementOrThrow;
|
|
102
|
+
/** The view reconciliation needs of a mount, refreshed per pass. */
|
|
103
|
+
private runtime;
|
|
104
|
+
/** One disk→store pass; the caller holds the mount's turn. Emits its events. */
|
|
105
|
+
private passUnlocked;
|
|
106
|
+
/** One store→disk push; the caller holds the mount's turn. */
|
|
107
|
+
private pushUnlocked;
|
|
80
108
|
start(ctx: ModuleContext): Promise<void>;
|
|
81
109
|
/**
|
|
82
110
|
* Apply persisted per-mount state once both halves of the lifecycle have
|
|
@@ -130,6 +158,12 @@ export declare class WorkspaceModule implements Module {
|
|
|
130
158
|
mode: 'read-write' | 'read-only';
|
|
131
159
|
}>;
|
|
132
160
|
getTools(): ToolDefinition[];
|
|
161
|
+
private generatedTextFiles;
|
|
162
|
+
/** Compare the same normalized physical path for registration and tool access. */
|
|
163
|
+
private generatedFileKey;
|
|
164
|
+
private findGeneratedTextFile;
|
|
165
|
+
/** Generated files bypass stored blobs so every read reflects current definitions. */
|
|
166
|
+
registerGeneratedTextFile(path: string, read: () => string, agentName: string): void;
|
|
133
167
|
handleToolCall(call: ToolCall): Promise<ToolResult>;
|
|
134
168
|
onProcess(event: ProcessEvent, _state: ProcessState): Promise<EventResponse>;
|
|
135
169
|
/**
|
|
@@ -153,8 +187,25 @@ export declare class WorkspaceModule implements Module {
|
|
|
153
187
|
* mount, through the same Chronicle-tree + auto-materialize path as the
|
|
154
188
|
* `write` tool. Public API for the framework's synthesized `save_image`
|
|
155
189
|
* tool and other peer callers that hold bytes rather than text.
|
|
190
|
+
*
|
|
191
|
+
* `branch`, when given, ties the write to that branch: the store's current
|
|
192
|
+
* branch is checked where the tree entry is set, nothing yielding between
|
|
193
|
+
* the check and the entry, so the file's tree entry lands on that branch or
|
|
194
|
+
* the write is refused, with `data: { branchLeft: true }`. A mount has one
|
|
195
|
+
* directory on disk, so with autoMaterialize the file's copy there is
|
|
196
|
+
* written like any other write's. The tool-result guard writes a
|
|
197
|
+
* withheld original this way, for the branch its stub is on
|
|
198
|
+
* (agent-framework #277).
|
|
199
|
+
*
|
|
200
|
+
* A write whose tree entry is set but that then fails returns success
|
|
201
|
+
* false with `data: { inChronicle: true }`: its copy on disk was refused, or
|
|
202
|
+
* something after the entry (recording its intent, the event, the push)
|
|
203
|
+
* threw. The workspace's tools read the file from the tree, so it is there
|
|
204
|
+
* for them, on the branch that was current when the entry was set.
|
|
156
205
|
*/
|
|
157
|
-
writeBinary(mountPrefixedPath: string, data: Buffer, mimeType: string
|
|
206
|
+
writeBinary(mountPrefixedPath: string, data: Buffer, mimeType: string, options?: {
|
|
207
|
+
branch?: string;
|
|
208
|
+
}): Promise<ToolResult>;
|
|
158
209
|
/**
|
|
159
210
|
* Read a file's raw bytes from a mount (through the Chronicle tree, synced
|
|
160
211
|
* first like the `read` tool). Public API for the framework's synthesized
|
|
@@ -192,6 +243,15 @@ export declare class WorkspaceModule implements Module {
|
|
|
192
243
|
* Parse a mount-prefixed path into (mountName, relativePath).
|
|
193
244
|
*/
|
|
194
245
|
private parsePath;
|
|
246
|
+
/**
|
|
247
|
+
* Whether the module takes work: stop hasn't finished. Work admitted while
|
|
248
|
+
* stop drains still runs; once stop has saved the pins and watermarks,
|
|
249
|
+
* nothing more changes them, so it takes nothing until it starts again. A
|
|
250
|
+
* call before start is taken, as it always was (a generated file's read
|
|
251
|
+
* needs no store at all): in the host's order, start runs first, and
|
|
252
|
+
* initStore applies the saved pins.
|
|
253
|
+
*/
|
|
254
|
+
private running;
|
|
195
255
|
private getStore;
|
|
196
256
|
private validateImageBytes;
|
|
197
257
|
private tryReadImageFromTree;
|
|
@@ -238,22 +298,39 @@ export declare class WorkspaceModule implements Module {
|
|
|
238
298
|
private imageErrorFromRead;
|
|
239
299
|
private readImageFromFilesystem;
|
|
240
300
|
/**
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
*
|
|
301
|
+
* Commit a tool's write or delete to the workspace tree, with the branch
|
|
302
|
+
* intent it implies (store origin for a path disk never agreed with; a
|
|
303
|
+
* tombstone for a deletion), and push it to disk when the mount opts in via
|
|
304
|
+
* `autoMaterialize` — required for cross-agent pipelines, since another
|
|
305
|
+
* agent's watcher on the same directory only sees real filesystem events.
|
|
306
|
+
* The push follows materialize's rule: a disk copy that changed since it
|
|
307
|
+
* last agreed is not overwritten or unlinked, and the refusal says why.
|
|
245
308
|
*
|
|
246
|
-
* Returns
|
|
247
|
-
*
|
|
248
|
-
* of truth for downstream agents
|
|
249
|
-
*
|
|
250
|
-
|
|
309
|
+
* Returns the reason when the change reached the workspace but not disk, so
|
|
310
|
+
* callers surface it in the tool result: the autoMaterialize contract is
|
|
311
|
+
* that disk is the source of truth for downstream agents. The caller holds
|
|
312
|
+
* the mount's turn and has settled the path first (settleBeforeMutation).
|
|
313
|
+
*/
|
|
314
|
+
private commitToolChange;
|
|
315
|
+
/**
|
|
316
|
+
* Before a tool reads or changes a path's workspace entry, a pending intent
|
|
317
|
+
* on that path is settled by a pass over it, so the tool works from what
|
|
318
|
+
* disk now holds (an edit sees a disk change the settled adoption took in).
|
|
319
|
+
* The pass may not settle it (a branch that keeps changing, an unreadable
|
|
320
|
+
* disk copy); the precondition that matters is enforced again,
|
|
321
|
+
* synchronously, where the change commits (commitToolChange). The caller
|
|
322
|
+
* holds the mount's turn.
|
|
251
323
|
*/
|
|
252
|
-
private
|
|
324
|
+
private settleBeforeMutation;
|
|
253
325
|
/**
|
|
254
|
-
*
|
|
326
|
+
* Before a read: a path the workspace doesn't hold is observed under the
|
|
327
|
+
* three-way rule, which ingests a new disk file but never brings back a
|
|
328
|
+
* workspace deletion or a file absent from this branch. The caller holds
|
|
329
|
+
* the mount's turn.
|
|
255
330
|
*/
|
|
256
331
|
private ensureSynced;
|
|
332
|
+
/** Why the workspace holds no entry at a path, when disk explains it. */
|
|
333
|
+
private notInWorkspace;
|
|
257
334
|
private handleRead;
|
|
258
335
|
private handleReadImage;
|
|
259
336
|
private handleWrite;
|
|
@@ -263,6 +340,29 @@ export declare class WorkspaceModule implements Module {
|
|
|
263
340
|
private handleGlob;
|
|
264
341
|
private handleGrep;
|
|
265
342
|
private handleStatus;
|
|
343
|
+
/**
|
|
344
|
+
* What a materialize of `mount` should consider: a named file, or every
|
|
345
|
+
* tracked path beneath a named directory; otherwise what the evidence says
|
|
346
|
+
* disk still owes, the entries never checked against disk, recorded
|
|
347
|
+
* conflicts, and workspace deletions still on disk (applied with
|
|
348
|
+
* `applyDeletions`, listed without it). The watermark plays no part: it is
|
|
349
|
+
* lost at an unclean restart, and an entry the evidence shows on disk has
|
|
350
|
+
* nothing to push. If disk changed since, a sync adopts that; a bare
|
|
351
|
+
* materialize, forced or not, never reverts it (naming the path with force
|
|
352
|
+
* does). `inPlace` says whether any tree entry was left out for that reason.
|
|
353
|
+
*/
|
|
354
|
+
private materializeSelection;
|
|
355
|
+
/**
|
|
356
|
+
* The workspace entries by what the evidence says of disk. `owed`: P
|
|
357
|
+
* differs from the entry (a draft, or a push still pending), or there is no
|
|
358
|
+
* P and the entry came from the workspace (a draft disk never had).
|
|
359
|
+
* `unchecked`: no P and no workspace origin, an entry from before the
|
|
360
|
+
* evidence; a push agrees it, writes it where disk lacks it, or refuses it
|
|
361
|
+
* as a conflict. `agreed`: how many have P = S, so disk holds them. Status
|
|
362
|
+
* counts the owed as pending; a materialize without a path takes up the
|
|
363
|
+
* owed and the unchecked.
|
|
364
|
+
*/
|
|
365
|
+
private entriesByEvidence;
|
|
266
366
|
private handleMaterialize;
|
|
267
367
|
/**
|
|
268
368
|
* Programmatically materialize a mount's files from Chronicle tree to filesystem.
|
|
@@ -271,17 +371,55 @@ export declare class WorkspaceModule implements Module {
|
|
|
271
371
|
*/
|
|
272
372
|
materializeMount(mountName: string): Promise<string[]>;
|
|
273
373
|
private handleSync;
|
|
374
|
+
/**
|
|
375
|
+
* Take the mount's root as it is now (acceptRoot), when it is a directory
|
|
376
|
+
* other than the one disk last agreed under. Its evidence is set aside:
|
|
377
|
+
* it described another directory. Returns whether a root was accepted.
|
|
378
|
+
*/
|
|
379
|
+
private acceptRootUnlocked;
|
|
274
380
|
/**
|
|
275
381
|
* Handle filesystem changes detected by watcher (watch: 'always' mode).
|
|
276
382
|
*
|
|
277
|
-
* The watcher
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
* op type.
|
|
383
|
+
* The watcher's ops only choose which paths to look at: each path is
|
|
384
|
+
* observed again and decided under the three-way rule, so an event that
|
|
385
|
+
* arrives late or out of order can't delete or revert anything by itself.
|
|
281
386
|
*/
|
|
282
387
|
private handleFsChanges;
|
|
283
388
|
/**
|
|
284
|
-
*
|
|
389
|
+
* After an agent's completed tool batch, before anything continues its turn:
|
|
390
|
+
* scan every `watch: 'on-agent-action'` mount, so what the batch's tools did
|
|
391
|
+
* on disk (a shell command's files, a deletion) is in the workspace when the
|
|
392
|
+
* agent next looks. A scan that outlasts the deadline stops holding the
|
|
393
|
+
* round: the miss is recorded on the mount's status and pushed as a
|
|
394
|
+
* `workspace:agent-action-scan-incomplete` event, and the scan continues,
|
|
395
|
+
* deciding on the branch selected when it applies.
|
|
396
|
+
*/
|
|
397
|
+
onToolBatchComplete(_agentName?: string): Promise<void>;
|
|
398
|
+
/**
|
|
399
|
+
* Mark a scan late, and the mount as running late until it settles. The
|
|
400
|
+
* marker follows the newest late scan: a mount's scans run one after
|
|
401
|
+
* another, so the newest is the last to settle, and a batch that comes
|
|
402
|
+
* while any late scan of the chain still runs isn't held.
|
|
403
|
+
*/
|
|
404
|
+
private agentActionScanLateUntil;
|
|
405
|
+
/** A batch's scan missed its deadline, or failed: said on status and as an event. */
|
|
406
|
+
private agentActionScanMissed;
|
|
407
|
+
/**
|
|
408
|
+
* The mount's after-batch scan: one may run and one wait behind it. A batch
|
|
409
|
+
* that comes while one waits shares it, since it starts after the batch's
|
|
410
|
+
* tools ran. Each scan records its own outcome when it settles: on status
|
|
411
|
+
* while it is the mount's newest scan (a scan queued behind it describes a
|
|
412
|
+
* later batch), and as an event for regions it couldn't observe (the file
|
|
413
|
+
* cap, an unreadable or changing region, a branch that kept changing) or a
|
|
414
|
+
* failure, whichever scan it is.
|
|
415
|
+
*/
|
|
416
|
+
private agentActionScan;
|
|
417
|
+
/** The scan deadline, kept below the framework's hook bound (warned once). */
|
|
418
|
+
private agentActionScanDeadlineMs;
|
|
419
|
+
/**
|
|
420
|
+
* Push one ProcessEvent per op for a pass's tree changes and new conflicts.
|
|
421
|
+
* A conflict rides the event for the disk change behind it (modified, or
|
|
422
|
+
* deleted for a disk deletion of a file with newer workspace changes).
|
|
285
423
|
*/
|
|
286
424
|
private emitFsEvents;
|
|
287
425
|
}
|