@animalabs/agent-framework 0.28.0 → 0.30.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/admission/admit.d.ts +90 -0
- package/dist/src/admission/admit.d.ts.map +1 -0
- package/dist/src/admission/admit.js +43 -0
- package/dist/src/admission/admit.js.map +1 -0
- package/dist/src/agent.d.ts +21 -0
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +47 -7
- 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/author-presentation.d.ts +50 -0
- package/dist/src/author-presentation.d.ts.map +1 -0
- package/dist/src/author-presentation.js +58 -0
- package/dist/src/author-presentation.js.map +1 -0
- 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/dendrite/index.d.ts +6 -0
- package/dist/src/dendrite/index.d.ts.map +1 -0
- package/dist/src/dendrite/index.js +3 -0
- package/dist/src/dendrite/index.js.map +1 -0
- package/dist/src/dendrite/presets.d.ts +60 -0
- package/dist/src/dendrite/presets.d.ts.map +1 -0
- package/dist/src/dendrite/presets.js +117 -0
- package/dist/src/dendrite/presets.js.map +1 -0
- package/dist/src/dendrite/registry.d.ts +260 -0
- package/dist/src/dendrite/registry.d.ts.map +1 -0
- package/dist/src/dendrite/registry.js +742 -0
- package/dist/src/dendrite/registry.js.map +1 -0
- package/dist/src/dendrite/types.d.ts +392 -0
- package/dist/src/dendrite/types.d.ts.map +1 -0
- package/dist/src/dendrite/types.js +24 -0
- package/dist/src/dendrite/types.js.map +1 -0
- package/dist/src/framework.d.ts +846 -67
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +4213 -924
- package/dist/src/framework.js.map +1 -1
- package/dist/src/gate/event-gate.d.ts +46 -14
- package/dist/src/gate/event-gate.d.ts.map +1 -1
- package/dist/src/gate/event-gate.js +143 -53
- 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 +174 -0
- package/dist/src/host-presentation.d.ts.map +1 -0
- package/dist/src/host-presentation.js +565 -0
- package/dist/src/host-presentation.js.map +1 -0
- package/dist/src/index.d.ts +7 -4
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +7 -2
- 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 +118 -15
- package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
- package/dist/src/mcpl/channel-registry.js +266 -110
- 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-coalescer.d.ts +9 -0
- package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
- package/dist/src/mcpl/push-coalescer.js +21 -0
- package/dist/src/mcpl/push-coalescer.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 +187 -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 +81 -0
- package/dist/src/notice-tags.d.ts.map +1 -0
- package/dist/src/notice-tags.js +85 -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 +5 -55
- package/dist/src/silent-heartbeat.d.ts.map +1 -1
- package/dist/src/silent-heartbeat.js +63 -7
- package/dist/src/silent-heartbeat.js.map +1 -1
- package/dist/src/stored-injections.d.ts +4 -0
- package/dist/src/stored-injections.d.ts.map +1 -1
- package/dist/src/stored-injections.js +6 -0
- package/dist/src/stored-injections.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 +382 -11
- package/dist/src/tune-out/coordinator.d.ts.map +1 -1
- package/dist/src/tune-out/coordinator.js +1100 -119
- package/dist/src/tune-out/coordinator.js.map +1 -1
- package/dist/src/tune-out/tools.d.ts +101 -5
- package/dist/src/tune-out/tools.d.ts.map +1 -1
- package/dist/src/tune-out/tools.js +55 -18
- package/dist/src/tune-out/tools.js.map +1 -1
- package/dist/src/types/agent.d.ts +48 -9
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/framework.d.ts +142 -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/dist/src/types/trace.d.ts +136 -0
- package/dist/src/types/trace.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/admission/admit.ts +113 -0
- package/src/agent.ts +52 -7
- package/src/api/mcp-server-core.ts +3 -14
- package/src/api/server.ts +7 -3
- package/src/author-presentation.ts +81 -0
- 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/dendrite/index.ts +32 -0
- package/src/dendrite/presets.ts +149 -0
- package/src/dendrite/registry.ts +808 -0
- package/src/dendrite/types.ts +397 -0
- package/src/framework.ts +4362 -852
- package/src/gate/event-gate.ts +159 -52
- 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 +612 -0
- package/src/index.ts +17 -4
- package/src/line-reader.ts +58 -0
- package/src/mcpl/channel-registry.ts +306 -111
- 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-coalescer.ts +19 -0
- 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 +149 -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 +86 -0
- package/src/reasoning-loss-notice.ts +5 -3
- package/src/silent-heartbeat.ts +11 -7
- package/src/stored-injections.ts +7 -0
- 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 +1252 -123
- package/src/tune-out/tools.ts +152 -11
- package/src/types/agent.ts +51 -9
- package/src/types/framework.ts +146 -0
- package/src/types/module.ts +23 -11
- package/src/types/trace.ts +150 -0
|
@@ -55,9 +55,25 @@ import {
|
|
|
55
55
|
import type { BranchIntent, BranchIntents, ConflictKind, ConflictRecord, DiskCounterpart } from './branch-intent.js';
|
|
56
56
|
import { storedBytes, type StoredBytes } from './sync.js';
|
|
57
57
|
import {
|
|
58
|
+
BENEATH_SYMLINK,
|
|
59
|
+
type Cause,
|
|
60
|
+
CHANGED,
|
|
61
|
+
CHANGED_WALK,
|
|
62
|
+
DIRECTORY,
|
|
63
|
+
EDITOR_FILE_REASON,
|
|
64
|
+
FILE_CHANGED,
|
|
58
65
|
IGNORED,
|
|
66
|
+
LEAVES_MOUNT,
|
|
67
|
+
LINK_NOT_WALKED,
|
|
68
|
+
LINK_TO_DIRECTORY,
|
|
59
69
|
type MountView,
|
|
60
70
|
NOT_A_DIRECTORY,
|
|
71
|
+
type NotShown,
|
|
72
|
+
NOT_FOLLOWED,
|
|
73
|
+
NOT_REGULAR,
|
|
74
|
+
OUTSIDE_PARENT,
|
|
75
|
+
RESOLVES_OUTSIDE,
|
|
76
|
+
ROOT_UNAVAILABLE,
|
|
61
77
|
OTHER_MOUNT,
|
|
62
78
|
ROOT_REPLACED,
|
|
63
79
|
type Walk,
|
|
@@ -76,8 +92,8 @@ import {
|
|
|
76
92
|
walkScope,
|
|
77
93
|
withinIncludes,
|
|
78
94
|
} from './observe.js';
|
|
79
|
-
import { EffectFailed, type Expect, chainOf, chmodContained, narrowedTo, syncDirectories, unlinkContained, writeContained } from './effects.js';
|
|
80
|
-
import { errnoWords } from './answers.js';
|
|
95
|
+
import { EffectFailed, type Expect, callFailed, chainOf, chmodContained, narrowedTo, syncDirectories, unlinkContained, writeContained } from './effects.js';
|
|
96
|
+
import { OUTSIDE_STEP, RESOLVED_OUTSIDE_STEP, UNFOLLOWED_STEP, errnoClause, errnoWords } from './answers.js';
|
|
81
97
|
|
|
82
98
|
export type EntryState =
|
|
83
99
|
| 'synced'
|
|
@@ -106,6 +122,7 @@ export interface PathReport {
|
|
|
106
122
|
mimeType?: string;
|
|
107
123
|
conflict?: ConflictReport;
|
|
108
124
|
note?: string;
|
|
125
|
+
noteCause?: Cause;
|
|
109
126
|
/**
|
|
110
127
|
* For a conflict, what this pass found on disk at the path: grep's account
|
|
111
128
|
* of a conflict's disk side needs it. Listings don't show it.
|
|
@@ -150,6 +167,8 @@ export interface PassResult {
|
|
|
150
167
|
* still the selected branch, synchronously, before it reads.
|
|
151
168
|
*/
|
|
152
169
|
branchId?: string;
|
|
170
|
+
/** Why it decided nothing, where it didn't. */
|
|
171
|
+
undecided?: string;
|
|
153
172
|
}
|
|
154
173
|
|
|
155
174
|
export interface MountRuntime {
|
|
@@ -225,7 +244,7 @@ export const WALK_CAP = 5000;
|
|
|
225
244
|
/** What a pass learned about D for one path. */
|
|
226
245
|
type DiskFact =
|
|
227
246
|
| { kind: 'absent' }
|
|
228
|
-
| { kind: 'unobserved'
|
|
247
|
+
| ({ kind: 'unobserved' } & NotShown)
|
|
229
248
|
| {
|
|
230
249
|
kind: 'file';
|
|
231
250
|
hash: string;
|
|
@@ -379,6 +398,7 @@ interface Verdict {
|
|
|
379
398
|
conflict?: ConflictKind;
|
|
380
399
|
op?: TreeOp;
|
|
381
400
|
note?: string;
|
|
401
|
+
noteCause?: Cause;
|
|
382
402
|
/**
|
|
383
403
|
* Disk shows what a pending push of ours intended, unconfirmed: the state
|
|
384
404
|
* is decided from it, and nothing at all is recorded (see module doc).
|
|
@@ -467,21 +487,22 @@ function settleAdoption(p: Pending, s: StoreSide | null): Agreed | 'forget' {
|
|
|
467
487
|
/**
|
|
468
488
|
* What's in the way, and the way out, when disk's shape keeps a push from a
|
|
469
489
|
* path: no push writes a file over a directory or unlinks one, unlinks
|
|
470
|
-
* anything but a regular file or a followed symlink to one,
|
|
471
|
-
*
|
|
472
|
-
*
|
|
473
|
-
*
|
|
490
|
+
* anything but a regular file or a followed symlink to one, writes below a
|
|
491
|
+
* file or a symlink the walk won't take, or writes or unlinks through a
|
|
492
|
+
* symlink the mount doesn't follow or one that leads out of the mount
|
|
493
|
+
* (`writeContained`, `unlinkContained`), forced or not. Null for any other
|
|
494
|
+
* reason.
|
|
474
495
|
*/
|
|
475
496
|
function inTheWay(reason: string | undefined, deletion: boolean): string | null {
|
|
476
497
|
// Observation's words for what a deletion never unlinks, which the unlink
|
|
477
498
|
// refuses in the same words.
|
|
478
|
-
if (deletion && (reason ===
|
|
479
|
-
const what = reason ===
|
|
499
|
+
if (deletion && (reason === NOT_REGULAR || reason === LINK_TO_DIRECTORY)) {
|
|
500
|
+
const what = reason === NOT_REGULAR
|
|
480
501
|
? 'a pipe, socket or device at this path, or a symlink to one'
|
|
481
502
|
: 'a symlink to a directory at this path';
|
|
482
503
|
return `disk has ${what}, and no push unlinks it, forced or not — ask whoever runs this host if it should go`;
|
|
483
504
|
}
|
|
484
|
-
if (reason ===
|
|
505
|
+
if (reason === DIRECTORY) {
|
|
485
506
|
return deletion
|
|
486
507
|
? 'disk has a directory at this path, and no push unlinks one, forced or not — if it should go, move or remove it on disk'
|
|
487
508
|
: 'disk has a directory at this path, and no push writes a file over one, forced or not — ' +
|
|
@@ -493,9 +514,95 @@ function inTheWay(reason: string | undefined, deletion: boolean): string | null
|
|
|
493
514
|
return 'disk has a file or a symlink where this path needs a directory, and no push writes below one, forced or not — ' +
|
|
494
515
|
'move or remove it on disk and materialize again, or write the file under another path';
|
|
495
516
|
}
|
|
517
|
+
const link = reason === NOT_FOLLOWED ? 'disk has a symlink at this path, which this mount does not follow'
|
|
518
|
+
: reason === LEAVES_MOUNT ? 'this path leads out of the mount through a symlink on it'
|
|
519
|
+
: null;
|
|
520
|
+
if (link !== null) {
|
|
521
|
+
return deletion
|
|
522
|
+
? `${link}, and no push unlinks ${reason === NOT_FOLLOWED ? 'one' : 'through one'}, forced or not — ask whoever runs this host if it should go`
|
|
523
|
+
: `${link}, and no push writes through one, forced or not — write the file under another path, ` +
|
|
524
|
+
`or whoever runs this host can remove the link, and ${THEN.write}`;
|
|
525
|
+
}
|
|
526
|
+
if (deletion && reason === BENEATH_SYMLINK) {
|
|
527
|
+
return 'a symlink on this path does not lead to a directory inside the mount, so nothing beneath it is in this mount, and no push unlinks beneath one, '
|
|
528
|
+
+ `forced or not — whoever runs this host can remove the link or make it lead to a directory inside the mount, and ${THEN.unlink}`;
|
|
529
|
+
}
|
|
530
|
+
if (reason === OUTSIDE_PARENT) {
|
|
531
|
+
return `a directory on this path leads out of the mount, and no push ${deletion ? 'unlinks' : 'writes'} beneath one, forced or not — ` +
|
|
532
|
+
(deletion ? 'ask whoever runs this host if what is there should go' : 'write the file under another path');
|
|
533
|
+
}
|
|
534
|
+
if (reason === NOT_REGULAR || reason === LINK_TO_DIRECTORY) {
|
|
535
|
+
const what = reason === NOT_REGULAR ? 'a pipe, socket or device at this path, or a symlink to one' : 'a symlink to a directory at this path';
|
|
536
|
+
return `disk has ${what}, and no push writes a file over it, forced or not — write the file under another path, ` +
|
|
537
|
+
`or whoever runs this host can remove it, and ${THEN.write}`;
|
|
538
|
+
}
|
|
496
539
|
return null;
|
|
497
540
|
}
|
|
498
541
|
|
|
542
|
+
export type Meets = 'write' | 'unlink' | 'sync' | 'list' | 'read';
|
|
543
|
+
|
|
544
|
+
const AGAIN: Record<Meets, string> = {
|
|
545
|
+
write: 'materializing again',
|
|
546
|
+
unlink: 'materializing again with applyDeletions',
|
|
547
|
+
sync: 'syncing this path again',
|
|
548
|
+
list: 'listing it',
|
|
549
|
+
read: 'calling again',
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
const THEN: Record<Meets, string> = {
|
|
553
|
+
write: 'materializing again then gets past it',
|
|
554
|
+
unlink: 'materializing again with applyDeletions then gets past it',
|
|
555
|
+
sync: 'a sync of this path then takes it in',
|
|
556
|
+
list: 'the workspace then reaches it',
|
|
557
|
+
read: 'a read then reaches it',
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
const CHANGED_WHILE: ReadonlySet<string> = new Set([CHANGED, CHANGED_WALK, FILE_CHANGED]);
|
|
561
|
+
|
|
562
|
+
const WALKED_PAST: ReadonlySet<string> = new Set([IGNORED, EDITOR_FILE_REASON, LINK_NOT_WALKED]);
|
|
563
|
+
|
|
564
|
+
export function explained(reason: string, cause: Cause, meets: Meets): string {
|
|
565
|
+
if (meets === 'write' || meets === 'unlink') {
|
|
566
|
+
const shaped = inTheWay(reason, meets === 'unlink');
|
|
567
|
+
if (shaped !== null) return shaped;
|
|
568
|
+
}
|
|
569
|
+
return withWayOn(reason, cause, meets);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function withWayOn(reason: string, cause: Cause, meets: Meets): string {
|
|
573
|
+
const on = wayOn(reason, cause, meets);
|
|
574
|
+
return on === null ? reason : `${reason} — ${on}`;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
export function wayOn(reason: string, cause: Cause, meets: Meets): string | null {
|
|
578
|
+
const lasting = `${AGAIN[meets]} won't change that; `;
|
|
579
|
+
if (reason === ROOT_UNAVAILABLE) {
|
|
580
|
+
if (cause.errno === 'ENOENT' || cause.errno === 'ENOTDIR') return `${lasting}whoever runs this host can put the mount's directory back at its path, and ${THEN[meets]}`;
|
|
581
|
+
if (cause.errno === undefined) return `${AGAIN[meets]} checks it afresh; if it's still unavailable, whoever runs this host can check the mount's directory and its path`;
|
|
582
|
+
}
|
|
583
|
+
if (cause.errno !== undefined) return errnoClause(cause.errno, AGAIN[meets], THEN[meets]);
|
|
584
|
+
if (cause.again === true || CHANGED_WHILE.has(reason)) return `${AGAIN[meets]} checks it afresh`;
|
|
585
|
+
switch (reason) {
|
|
586
|
+
case NOT_FOLLOWED:
|
|
587
|
+
return lasting + UNFOLLOWED_STEP;
|
|
588
|
+
case LEAVES_MOUNT:
|
|
589
|
+
return lasting + OUTSIDE_STEP;
|
|
590
|
+
case BENEATH_SYMLINK:
|
|
591
|
+
return `${lasting}nothing beneath it is in this mount, and whoever runs this host can make it lead to a directory inside the mount, or, where it leads to a directory outside, mount that directory`;
|
|
592
|
+
case OUTSIDE_PARENT:
|
|
593
|
+
case RESOLVES_OUTSIDE:
|
|
594
|
+
return lasting + RESOLVED_OUTSIDE_STEP;
|
|
595
|
+
case NOT_REGULAR:
|
|
596
|
+
return `${lasting}whoever runs this host can put a regular file there, and ${THEN[meets]}`;
|
|
597
|
+
case DIRECTORY:
|
|
598
|
+
return `${lasting}ls of this path lists what's in it`;
|
|
599
|
+
case LINK_TO_DIRECTORY:
|
|
600
|
+
return `${lasting}ls of the directory it leads to lists what's in it`;
|
|
601
|
+
default:
|
|
602
|
+
return meets === 'sync' && WALKED_PAST.has(reason) ? 'a sync of this path checks it' : null;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
499
606
|
/**
|
|
500
607
|
* The forced way out the refusal of a disk copy that couldn't be verified
|
|
501
608
|
* names, if any. A forced push skips the verification, so it gets past the
|
|
@@ -513,6 +620,14 @@ function forcedWay(note: string | undefined, deletion: boolean, forced: string):
|
|
|
513
620
|
return '';
|
|
514
621
|
}
|
|
515
622
|
|
|
623
|
+
const UNSETTLED = "whether disk holds the change isn't known until it's checked: a materialize of this path checks it and says how it ended";
|
|
624
|
+
|
|
625
|
+
function unverifiedCopy(v: Verdict, meets: 'write' | 'unlink', forced: string): string {
|
|
626
|
+
const note = v.note ?? 'not observed';
|
|
627
|
+
const on = wayOn(note, v.noteCause ?? {}, meets);
|
|
628
|
+
return `cannot verify disk copy (${note})${on !== null ? ` — ${on}` : ''}${forcedWay(v.note, meets === 'unlink', forced)}`;
|
|
629
|
+
}
|
|
630
|
+
|
|
516
631
|
/**
|
|
517
632
|
* The synchronous precondition of every workspace mutation: evidence owed a
|
|
518
633
|
* barrier is made durable first, and an adoption left pending on the
|
|
@@ -638,6 +753,10 @@ function adoptDisk(d: Extract<DiskFact, { kind: 'absent' | 'file' }>, s: StoreSi
|
|
|
638
753
|
return { state: 'disk-only', ...(s ? { adopt: 'drop' as const, op: 'deleted' as const } : {}), p: 'forget', intent: {} };
|
|
639
754
|
}
|
|
640
755
|
|
|
756
|
+
function causeOf({ errno, ownStep }: NotShown): Cause {
|
|
757
|
+
return { ...(errno !== undefined ? { errno } : {}), ...(ownStep ? { ownStep } : {}) };
|
|
758
|
+
}
|
|
759
|
+
|
|
641
760
|
/** The rule (see module doc). Pure. */
|
|
642
761
|
function decide(
|
|
643
762
|
d: DiskFact,
|
|
@@ -653,9 +772,9 @@ function decide(
|
|
|
653
772
|
if (d.kind === 'unobserved') {
|
|
654
773
|
// Disk proves nothing, but an adoption on this branch settles by the tree.
|
|
655
774
|
if (pIn?.kind === 'pending' && pIn.effect === 'adopt' && pIn.branchId === branchId && !adopt) {
|
|
656
|
-
return { state: 'unverified', note: d.reason, p: settleAdoption(pIn, s) };
|
|
775
|
+
return { state: 'unverified', note: d.reason, noteCause: causeOf(d), p: settleAdoption(pIn, s) };
|
|
657
776
|
}
|
|
658
|
-
return { state: 'unverified', note: d.reason };
|
|
777
|
+
return { state: 'unverified', note: d.reason, noteCause: causeOf(d) };
|
|
659
778
|
}
|
|
660
779
|
if (d.kind === 'unhashed') {
|
|
661
780
|
// Observed this way only when nothing tracks the path, which is then a
|
|
@@ -841,6 +960,7 @@ async function gather(store: JsStore, agreement: DiskAgreement, mount: MountRunt
|
|
|
841
960
|
// is followed, as `rootReal` is: `followSymlinks` governs links within it.
|
|
842
961
|
const root = await locateRoot(mount.view, mount.rootReal);
|
|
843
962
|
const rootUnavailable = 'reason' in root ? root.reason : null;
|
|
963
|
+
const rootCause = 'reason' in root && root.errno !== undefined ? { errno: root.errno } : {};
|
|
844
964
|
|
|
845
965
|
// The branch is labelled where the tracked candidates are read, with no
|
|
846
966
|
// await between: the decision is checked against this label.
|
|
@@ -856,7 +976,7 @@ async function gather(store: JsStore, agreement: DiskAgreement, mount: MountRunt
|
|
|
856
976
|
if (rootUnavailable !== null) {
|
|
857
977
|
// Said whether or not anything is tracked: an unavailable mount must not
|
|
858
978
|
// read as a verified empty directory.
|
|
859
|
-
gathered.incomplete.push({ path: scope.kind === 'dir' ? scope.dir : '', reason: rootUnavailable });
|
|
979
|
+
gathered.incomplete.push({ path: scope.kind === 'dir' ? scope.dir : '', reason: explained(rootUnavailable, rootCause, 'list') });
|
|
860
980
|
}
|
|
861
981
|
|
|
862
982
|
// Which paths to decide: what disk shows, and what the store, P and intent
|
|
@@ -895,7 +1015,7 @@ async function gather(store: JsStore, agreement: DiskAgreement, mount: MountRunt
|
|
|
895
1015
|
// Observe. Tracked facts are read again, synchronously, when deciding.
|
|
896
1016
|
for (const path of candidates) {
|
|
897
1017
|
if (rootUnavailable !== null) {
|
|
898
|
-
gathered.facts.set(path, { kind: 'unobserved', reason: rootUnavailable });
|
|
1018
|
+
gathered.facts.set(path, { kind: 'unobserved', reason: rootUnavailable, ...rootCause });
|
|
899
1019
|
continue;
|
|
900
1020
|
}
|
|
901
1021
|
// What the mount passes over by design stays unobserved by a pass that
|
|
@@ -945,7 +1065,9 @@ async function gather(store: JsStore, agreement: DiskAgreement, mount: MountRunt
|
|
|
945
1065
|
if (r.kind === 'symlink' && r.path === path) continue;
|
|
946
1066
|
if (region === undefined || r.path.length > region.path.length) region = r;
|
|
947
1067
|
}
|
|
948
|
-
gathered.facts.set(path,
|
|
1068
|
+
gathered.facts.set(path, region
|
|
1069
|
+
? { kind: 'unobserved', reason: region.reason, ...(region.errno !== undefined ? { errno: region.errno } : {}) }
|
|
1070
|
+
: { kind: 'unobserved', reason: 'not reached by the walk' });
|
|
949
1071
|
continue;
|
|
950
1072
|
}
|
|
951
1073
|
const p = agreement.get(mount.name, path);
|
|
@@ -962,11 +1084,12 @@ async function gather(store: JsStore, agreement: DiskAgreement, mount: MountRunt
|
|
|
962
1084
|
const after = await locateRoot(mount.view, mount.rootReal);
|
|
963
1085
|
if (!('identity' in after) || !sameRootIdentity(after.identity, gathered.rootIdentity)) {
|
|
964
1086
|
const reason = 'reason' in after ? after.reason : ROOT_REPLACED;
|
|
965
|
-
|
|
1087
|
+
const errno = 'errno' in after ? after.errno : undefined;
|
|
1088
|
+
for (const path of gathered.facts.keys()) gathered.facts.set(path, { kind: 'unobserved', reason, ...(errno !== undefined ? { errno } : {}) });
|
|
966
1089
|
gathered.rootIdentity = null;
|
|
967
1090
|
gathered.walk = null;
|
|
968
1091
|
gathered.dirs = [];
|
|
969
|
-
gathered.incomplete.push({ path: scope.kind === 'dir' ? scope.dir : '', reason });
|
|
1092
|
+
gathered.incomplete.push({ path: scope.kind === 'dir' ? scope.dir : '', reason: explained(reason, errno !== undefined ? { errno } : {}, 'list') });
|
|
970
1093
|
}
|
|
971
1094
|
}
|
|
972
1095
|
return gathered;
|
|
@@ -1003,14 +1126,13 @@ export async function reconcilePass(
|
|
|
1003
1126
|
}
|
|
1004
1127
|
}
|
|
1005
1128
|
if (gathered === null) {
|
|
1006
|
-
result.
|
|
1129
|
+
result.undecided = 'the selected branch kept changing while the scan ran';
|
|
1130
|
+
result.incomplete.push({ path: scope.kind === 'dir' ? scope.dir : '', reason: explained(result.undecided, { again: true }, 'list') });
|
|
1007
1131
|
return result;
|
|
1008
1132
|
}
|
|
1009
1133
|
if (opts.branchId !== undefined && gathered.branchId !== opts.branchId) {
|
|
1010
|
-
result.
|
|
1011
|
-
|
|
1012
|
-
reason: 'the selected branch changed before this sync ran; nothing was taken from disk — sync again on this branch to take it',
|
|
1013
|
-
});
|
|
1134
|
+
result.undecided = 'the selected branch changed before this sync ran; nothing was taken from disk — sync again on this branch to take it';
|
|
1135
|
+
result.incomplete.push({ path: scope.kind === 'dir' ? scope.dir : scope.paths.join(', '), reason: result.undecided });
|
|
1014
1136
|
return result;
|
|
1015
1137
|
}
|
|
1016
1138
|
const { walk, facts } = gathered;
|
|
@@ -1135,6 +1257,7 @@ export async function reconcilePass(
|
|
|
1135
1257
|
if (x.d.kind === 'file' || x.d.kind === 'absent') report.diskNow = x.d.kind;
|
|
1136
1258
|
}
|
|
1137
1259
|
if (x.verdict.note) report.note = x.verdict.note;
|
|
1260
|
+
if (x.verdict.noteCause !== undefined) report.noteCause = x.verdict.noteCause;
|
|
1138
1261
|
if (!(x.verdict.state === 'synced' && !entry)) result.reports.set(x.path, report);
|
|
1139
1262
|
}
|
|
1140
1263
|
if (walk) {
|
|
@@ -1164,7 +1287,9 @@ export async function reconcilePass(
|
|
|
1164
1287
|
// holder), so naming the directory as unreached would never clear.
|
|
1165
1288
|
const wholeScope = scope.kind === 'dir' && scope.dir !== '' && region.path === scope.dir;
|
|
1166
1289
|
const unreached = hides && region.reason !== OTHER_MOUNT;
|
|
1167
|
-
if (region.kind === 'error' || region.kind === 'cap' || unreached || wholeScope)
|
|
1290
|
+
if (region.kind === 'error' || region.kind === 'cap' || unreached || wholeScope) {
|
|
1291
|
+
result.incomplete.push({ path: region.path, reason: explained(region.reason, region.errno !== undefined ? { errno: region.errno } : {}, 'list') });
|
|
1292
|
+
}
|
|
1168
1293
|
// A name passed over by design that hides nothing tracked is reported nowhere else:
|
|
1169
1294
|
// counted, so a sync that says 0 also says what it didn't look at
|
|
1170
1295
|
// (agent-framework #276). A symlink region isn't counted: a link the
|
|
@@ -1271,7 +1396,8 @@ export async function pushPaths(
|
|
|
1271
1396
|
// unmounted from. The push plans nothing, so it reports no branch.
|
|
1272
1397
|
const root = await locateRoot(mount.view, mount.rootReal);
|
|
1273
1398
|
if ('reason' in root) {
|
|
1274
|
-
|
|
1399
|
+
const why = explained(root.reason, root.errno !== undefined ? { errno: root.errno } : {}, 'write');
|
|
1400
|
+
for (const path of paths) result.skipped.push({ path, reason: why });
|
|
1275
1401
|
return result;
|
|
1276
1402
|
}
|
|
1277
1403
|
// Another mount's directory nested in this one is that mount's: no push
|
|
@@ -1344,7 +1470,7 @@ export async function pushPaths(
|
|
|
1344
1470
|
// nothing is decided or recorded from them.
|
|
1345
1471
|
const after = await locateRoot(mount.view, mount.rootReal);
|
|
1346
1472
|
if (!('identity' in after) || !sameRootIdentity(after.identity, root.identity)) {
|
|
1347
|
-
const reason = 'reason' in after ? after.reason : ROOT_REPLACED;
|
|
1473
|
+
const reason = 'reason' in after ? explained(after.reason, after.errno !== undefined ? { errno: after.errno } : {}, 'write') : ROOT_REPLACED;
|
|
1348
1474
|
for (const path of paths) result.skipped.push({ path, reason });
|
|
1349
1475
|
return result;
|
|
1350
1476
|
}
|
|
@@ -1365,9 +1491,6 @@ export async function pushPaths(
|
|
|
1365
1491
|
const p = agreement.get(mount.name, path);
|
|
1366
1492
|
const bi = mount.intents.get(path);
|
|
1367
1493
|
const v = decide(d, s, p, bi, branchId, false);
|
|
1368
|
-
// The ways out a refusal names, as materialize takes them: a deletion
|
|
1369
|
-
// reaches disk only with applyDeletions.
|
|
1370
|
-
const again = s ? 'materialize again' : 'materialize again with applyDeletions';
|
|
1371
1494
|
// Where disk has deleted a file the workspace holds, a sync takes the
|
|
1372
1495
|
// deletion, which removes the workspace's copy, and a forced push writes
|
|
1373
1496
|
// the workspace's copy back: there is no disk copy to adopt or overwrite.
|
|
@@ -1483,8 +1606,7 @@ export async function pushPaths(
|
|
|
1483
1606
|
? d.kind === 'absent' || d.kind === 'file'
|
|
1484
1607
|
? `${v.note} — ${sync}`
|
|
1485
1608
|
: `${v.note}, and disk's copy can't be verified (${d.kind === 'unobserved' ? d.reason : 'not hashed'}) — once it can be, ${sync}`
|
|
1486
|
-
: inTheWay(v.note, !s) ??
|
|
1487
|
-
`cannot verify disk copy (${v.note ?? 'not observed'}) — fix it and ${again}${forcedWay(v.note, !s, forced)}`,
|
|
1609
|
+
: inTheWay(v.note, !s) ?? unverifiedCopy(v, s ? 'write' : 'unlink', forced),
|
|
1488
1610
|
});
|
|
1489
1611
|
continue;
|
|
1490
1612
|
}
|
|
@@ -1549,7 +1671,7 @@ export async function pushPaths(
|
|
|
1549
1671
|
// A deletion no push makes, forced or not (inTheWay), is refused for
|
|
1550
1672
|
// that, as an unforced push refuses it, rather than left for
|
|
1551
1673
|
// applyDeletions, which couldn't make it either.
|
|
1552
|
-
const lasting = inTheWay(v.note, true);
|
|
1674
|
+
const lasting = inTheWay(v.note, true) ?? (v.noteCause?.ownStep === true ? v.note ?? null : null);
|
|
1553
1675
|
if (lasting !== null) result.skipped.push({ path, reason: lasting });
|
|
1554
1676
|
else result.pendingDeletions.push(path);
|
|
1555
1677
|
continue;
|
|
@@ -1679,18 +1801,18 @@ export async function pushPaths(
|
|
|
1679
1801
|
effected.push({ step, dirs: [await unlinkContained(mount.view, mount.rootReal, step.path, step.expect)] });
|
|
1680
1802
|
}
|
|
1681
1803
|
} catch (err) {
|
|
1682
|
-
const failure = err instanceof EffectFailed ? err :
|
|
1804
|
+
const failure = err instanceof EffectFailed ? err : callFailed('the push failed', err, true, 'colon');
|
|
1683
1805
|
if (!failure.touched) {
|
|
1684
1806
|
// Disk at this path is as planning saw it: its evidence goes back to
|
|
1685
1807
|
// exactly what it was, whatever kind that was.
|
|
1686
1808
|
agreement.restore(mount.name, step.path, step.before);
|
|
1687
1809
|
recorded = true;
|
|
1688
|
-
const why =
|
|
1810
|
+
const why = step.kind === 'chmod' ? withWayOn(failure.message, failure.detail, 'write') : explained(failure.message, failure.detail, step.kind);
|
|
1689
1811
|
result.skipped.push({ path: step.path, reason: `not ${step.kind === 'unlink' ? 'unlinked' : step.kind === 'chmod' ? 'given the workspace\'s mode' : 'written'}: ${why}` });
|
|
1690
1812
|
} else {
|
|
1691
1813
|
// No completion: the intent stays pending, and the next observation
|
|
1692
1814
|
// resolves it from whatever disk then holds.
|
|
1693
|
-
result.skipped.push({ path: step.path, reason: `${failure.message}
|
|
1815
|
+
result.skipped.push({ path: step.path, reason: `${failure.message} — ${UNSETTLED}` });
|
|
1694
1816
|
}
|
|
1695
1817
|
}
|
|
1696
1818
|
}
|
|
@@ -1710,7 +1832,7 @@ export async function pushPaths(
|
|
|
1710
1832
|
result.skipped.push({
|
|
1711
1833
|
path: step.path,
|
|
1712
1834
|
reason: `could not make the ${step.kind === 'write' ? 'write' : 'unlink'} durable (directory fsync of ${failed === '' ? 'the mount root' : failed}): ` +
|
|
1713
|
-
`${unsynced.get(failed)}
|
|
1835
|
+
`${unsynced.get(failed)} — ${UNSETTLED}`,
|
|
1714
1836
|
});
|
|
1715
1837
|
continue;
|
|
1716
1838
|
}
|
|
@@ -1784,5 +1906,6 @@ export async function pushPaths(
|
|
|
1784
1906
|
}
|
|
1785
1907
|
|
|
1786
1908
|
function unstoredCopy(stored: Exclude<StoredBytes, { bytes: Buffer }>): string {
|
|
1787
|
-
return 'missing' in stored ? 'the workspace copy is missing from the store' : `the workspace copy is unreadable in the store (${stored.unreadable})
|
|
1909
|
+
return `${'missing' in stored ? 'the workspace copy is missing from the store' : `the workspace copy is unreadable in the store (${stored.unreadable})`}`
|
|
1910
|
+
+ " — a read of this path takes disk's copy where it can, and otherwise says what can be done";
|
|
1788
1911
|
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build every fixed bracket opening the framework writes into an agent's
|
|
3
|
+
* context outside a tool result (a notice, a heartbeat or reminder text, a
|
|
4
|
+
* placeholder in a message's content) from a tag here, as `[tag]`,
|
|
5
|
+
* `[tag: …]` or `[tag …]`: text the framework didn't write is marked
|
|
6
|
+
* wherever it would open with one of these (supplied-text.ts). A toolset's
|
|
7
|
+
* `[server/toolset:instance]` label is built from its server's id and names
|
|
8
|
+
* instead.
|
|
9
|
+
*/
|
|
10
|
+
export const NOTICE_TAG = {
|
|
11
|
+
turnInterrupted: 'turn-interrupted',
|
|
12
|
+
closedChannel: 'closed channel',
|
|
13
|
+
tunedOut: 'tuned out',
|
|
14
|
+
refusalRewind: 'refusal-rewind',
|
|
15
|
+
drafts: 'drafts',
|
|
16
|
+
delivered: 'delivered',
|
|
17
|
+
routing: 'routing',
|
|
18
|
+
channels: 'channels',
|
|
19
|
+
continue: 'continue',
|
|
20
|
+
proseRouting: 'prose-routing',
|
|
21
|
+
toolBoundary: 'tool-boundary',
|
|
22
|
+
gate: 'Gate',
|
|
23
|
+
selfWake: 'self-wake',
|
|
24
|
+
discordSendFailed: 'discord-send-failed',
|
|
25
|
+
sendUndeliverable: 'send-undeliverable',
|
|
26
|
+
inferenceFailed: 'inference-failed',
|
|
27
|
+
speechNotPosted: 'speech-not-posted',
|
|
28
|
+
reasoning: 'reasoning',
|
|
29
|
+
workspace: 'workspace',
|
|
30
|
+
workspaceEvent: 'workspace event',
|
|
31
|
+
conflictsWithDisk: 'conflicts with disk',
|
|
32
|
+
tuneOut: 'Tune-out',
|
|
33
|
+
tuneOutWake: 'Tune-out wake',
|
|
34
|
+
tuneOutCadence: 'Tune-out cadence',
|
|
35
|
+
tuneOutCancelled: 'Tune-out cancelled',
|
|
36
|
+
focus: 'Focus',
|
|
37
|
+
agentLifecycle: 'Agent lifecycle',
|
|
38
|
+
standingDispositions: 'Standing dispositions',
|
|
39
|
+
channelInvitation: 'Channel invitation',
|
|
40
|
+
system: 'system',
|
|
41
|
+
toolsets: 'toolsets',
|
|
42
|
+
heartbeat: 'heartbeat',
|
|
43
|
+
silentHeartbeat: 'silent heartbeat',
|
|
44
|
+
heartbeatTick: 'heartbeat tick',
|
|
45
|
+
heartbeatContinue: 'Continue',
|
|
46
|
+
currentTime: 'current time',
|
|
47
|
+
reminder: 'reminder',
|
|
48
|
+
attachment: 'Attachment',
|
|
49
|
+
emptyMessage: 'Empty message',
|
|
50
|
+
imageNotShown: 'Image not shown',
|
|
51
|
+
audioNotShown: 'Audio not shown',
|
|
52
|
+
contentNotShown: 'Content not shown',
|
|
53
|
+
resourceNotShown: 'Resource not shown',
|
|
54
|
+
referenceNotShown: 'Reference not shown',
|
|
55
|
+
textNotShown: 'Text not shown',
|
|
56
|
+
reference: 'reference',
|
|
57
|
+
referenceId: 'ref',
|
|
58
|
+
resourceLink: 'resource link',
|
|
59
|
+
withheld: 'withheld',
|
|
60
|
+
unserializableConnectorValue: 'unserializable connector value',
|
|
61
|
+
unprintableValue: 'a value that cannot be printed',
|
|
62
|
+
undescribedFailure: 'failure could not be described',
|
|
63
|
+
script: 'script',
|
|
64
|
+
truncated: 'truncated',
|
|
65
|
+
cutToFit: 'cut to fit',
|
|
66
|
+
blockNotShown: 'block not shown',
|
|
67
|
+
} as const;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The bracket openings context-manager 0.23.0 and 0.23.1 write into an
|
|
71
|
+
* agent's context outside a tool result or read back from it, marked as the
|
|
72
|
+
* framework's own are. Check them against each context-manager release the
|
|
73
|
+
* framework takes.
|
|
74
|
+
*/
|
|
75
|
+
export const CONTEXT_MANAGER_TAGS = [
|
|
76
|
+
'content omitted', 'image not shown', 'Operator note', 'Recall', 'tool call omitted', 'tool result moved', 'Topic Transition',
|
|
77
|
+
] as const;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The bracket openings membrane 0.8.0 writes into an agent's request outside
|
|
81
|
+
* a tool result, marked as the framework's own are. Check them against each
|
|
82
|
+
* membrane release the framework takes.
|
|
83
|
+
*/
|
|
84
|
+
export const MEMBRANE_TAGS = [
|
|
85
|
+
'Audio', 'continuing', 'Document', 'duplicate tool_result for', 'Image', 'orphan tool_result for', 'Start', 'Tool Result', 'Video',
|
|
86
|
+
] as const;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { NOTICE_TAG } from './notice-tags.js';
|
|
2
|
+
import { oneLine, suppliedValue } from './supplied-text.js';
|
|
1
3
|
import { safeSlice } from './safe-slice.js';
|
|
2
4
|
|
|
3
5
|
export type ReasoningLossCause = 'tools' | 'system' | 'model';
|
|
@@ -45,12 +47,12 @@ export function reasoningLossNotice(input: ReasoningLossNoticeInput): string {
|
|
|
45
47
|
lost = `Some of your earlier reasoning is no longer in your context: the reasoning behind ${replies}${first}${among}${because}.`;
|
|
46
48
|
reread = named ? 'Re-read before relying on a decision made since then.' : 'Re-read before relying on an earlier decision.';
|
|
47
49
|
}
|
|
48
|
-
return `[reasoning] ${input.agentName}: ${lost} Their words are still here. ${reread}`;
|
|
50
|
+
return `[${NOTICE_TAG.reasoning}] ${input.agentName}: ${lost} Their words are still here. ${reread}`;
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
function anchorWords(anchor: ReasoningLossAnchor): string | undefined {
|
|
52
|
-
if ('toolCall' in anchor) return anchor.toolCall ? `that called \`${anchor.toolCall}\`` : undefined;
|
|
53
|
-
const text = anchor.text.replace(/\s+/g, ' ').trim();
|
|
54
|
+
if ('toolCall' in anchor) return anchor.toolCall ? `that called \`${suppliedValue(anchor.toolCall)}\`` : undefined;
|
|
55
|
+
const text = oneLine(anchor.text).replace(/\s+/g, ' ').trim();
|
|
54
56
|
if (!text) return undefined;
|
|
55
57
|
const clipped = text.length > ANCHOR_CHARS ? `${safeSlice(text, 0, ANCHOR_CHARS).trimEnd()}…` : text;
|
|
56
58
|
return `beginning "${clipped}"`;
|
package/src/silent-heartbeat.ts
CHANGED
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
* another reply's row and rewrite cached history. Exact matching for every
|
|
51
51
|
* row would need the compile result to expose each message's source rows.
|
|
52
52
|
*/
|
|
53
|
+
import { NOTICE_TAG } from './notice-tags.js';
|
|
53
54
|
import type { ContentBlock, NormalizedMessage } from '@animalabs/membrane';
|
|
54
55
|
|
|
55
56
|
/** Request-only separator a silent tick's request ends on. It carries the
|
|
@@ -59,15 +60,15 @@ import type { ContentBlock, NormalizedMessage } from '@animalabs/membrane';
|
|
|
59
60
|
* text their request rendered (`SilentHeartbeatStamp.separator`), so changing
|
|
60
61
|
* this constant changes only ticks that open afterwards. */
|
|
61
62
|
export const SILENT_HEARTBEAT_SEPARATOR =
|
|
62
|
-
|
|
63
|
+
`[${NOTICE_TAG.silentHeartbeat}] Scheduled private self-check. Review pending matters privately. ` +
|
|
63
64
|
'Do not narrate or publish plain prose; use an explicit send tool only if you deliberately choose to contact someone.';
|
|
64
65
|
|
|
65
66
|
/** The separator ticks rendered before their rows recorded its text: rows
|
|
66
67
|
* stamped without `separator` still render it, byte for byte. */
|
|
67
|
-
const LEGACY_SILENT_HEARTBEAT_SEPARATOR =
|
|
68
|
+
const LEGACY_SILENT_HEARTBEAT_SEPARATOR = `[${NOTICE_TAG.heartbeatTick}]`;
|
|
68
69
|
|
|
69
70
|
/** Request-only turn a compile that ends on the agent's own message gets. */
|
|
70
|
-
export const CONTINUE_ROW =
|
|
71
|
+
export const CONTINUE_ROW = `[${NOTICE_TAG.heartbeatContinue}]`;
|
|
71
72
|
|
|
72
73
|
/** Identity of one authenticated silent heartbeat tick. */
|
|
73
74
|
export interface SilentHeartbeatTick {
|
|
@@ -189,14 +190,17 @@ export interface RequestOnlyRowIndex {
|
|
|
189
190
|
/** Index the agent's own rows that follow request-only turns: the rows of its
|
|
190
191
|
* silent ticks, and the rows of each reply to a `[Continue]`. The two are
|
|
191
192
|
* independent, so a row can be both. Only stamped rows are keyed, so an
|
|
192
|
-
* agent's history walk costs a metadata read per row.
|
|
193
|
-
|
|
193
|
+
* agent's history walk costs a metadata read per row. `own` is the agent's
|
|
194
|
+
* name, or every participant that is its own voice, as a derived agent's
|
|
195
|
+
* inherited rows are. */
|
|
196
|
+
export function indexRequestOnlyRows(stored: readonly StoredRowLike[], own: string | ReadonlySet<string>): RequestOnlyRowIndex {
|
|
197
|
+
const ownNames = typeof own === 'string' ? new Set([own]) : own;
|
|
194
198
|
const ticks = new Map<string, { tick: string; text: string }>();
|
|
195
199
|
const prompts = new Map<string, RequestOnlyPrompt>();
|
|
196
200
|
for (const row of stored) {
|
|
197
|
-
if (row.participant
|
|
201
|
+
if (!ownNames.has(row.participant)) continue;
|
|
198
202
|
const stamp = silentHeartbeatOf(row.metadata);
|
|
199
|
-
const ownTick = stamp !== undefined && stamp.agentName ===
|
|
203
|
+
const ownTick = stamp !== undefined && stamp.agentName === row.participant;
|
|
200
204
|
const prompt = promptedByOf(row.metadata);
|
|
201
205
|
if (!ownTick && !prompt) continue;
|
|
202
206
|
const keys = identityKeys(row.content);
|
package/src/stored-injections.ts
CHANGED
|
@@ -84,6 +84,13 @@ export function isAnotherAgentsInjection(metadata: unknown, agentName: string):
|
|
|
84
84
|
return mark !== undefined && mark.agent !== agentName;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
/** Whether a row is `agentName`'s own stored injection: its context, which
|
|
88
|
+
* its view keeps however the rest is read, as a shared fork's does, whose
|
|
89
|
+
* view is otherwise its source's (AgentFramework's deriveSharedContext). */
|
|
90
|
+
export function isOwnInjection(metadata: unknown, agentName: string): boolean {
|
|
91
|
+
return storedInjectionMarkOf(metadata)?.agent === agentName;
|
|
92
|
+
}
|
|
93
|
+
|
|
87
94
|
/**
|
|
88
95
|
* Whether an injection is stored, rather than placed by `compile()` each
|
|
89
96
|
* time: an `afterUser` one, unless it opts out
|