@animalabs/agent-framework 0.32.0 → 0.34.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 +340 -22
- package/dist/src/admission/admit.d.ts +18 -0
- package/dist/src/admission/admit.d.ts.map +1 -1
- package/dist/src/admission/admit.js +16 -3
- package/dist/src/admission/admit.js.map +1 -1
- package/dist/src/agent.d.ts +208 -80
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +345 -113
- 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 +50 -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 +4 -20
- package/dist/src/api/server.js.map +1 -1
- package/dist/src/append-all.d.ts +5 -0
- package/dist/src/append-all.d.ts.map +1 -0
- package/dist/src/append-all.js +8 -0
- package/dist/src/append-all.js.map +1 -0
- package/dist/src/code-execution/isolate/scripts.d.ts +12 -8
- package/dist/src/code-execution/isolate/scripts.d.ts.map +1 -1
- package/dist/src/code-execution/isolate/scripts.js +24 -0
- package/dist/src/code-execution/isolate/scripts.js.map +1 -1
- package/dist/src/config-checks.d.ts +7 -0
- package/dist/src/config-checks.d.ts.map +1 -1
- package/dist/src/config-checks.js +14 -0
- package/dist/src/config-checks.js.map +1 -1
- package/dist/src/context-manager-errors.d.ts +25 -0
- package/dist/src/context-manager-errors.d.ts.map +1 -0
- package/dist/src/context-manager-errors.js +31 -0
- package/dist/src/context-manager-errors.js.map +1 -0
- package/dist/src/context-options.d.ts +70 -0
- package/dist/src/context-options.d.ts.map +1 -0
- package/dist/src/context-options.js +90 -0
- package/dist/src/context-options.js.map +1 -0
- package/dist/src/cut-off-round.js +4 -4
- package/dist/src/deferred-import.d.ts +93 -0
- package/dist/src/deferred-import.d.ts.map +1 -0
- package/dist/src/deferred-import.js +177 -0
- package/dist/src/deferred-import.js.map +1 -0
- package/dist/src/dendrite/audience.d.ts +223 -0
- package/dist/src/dendrite/audience.d.ts.map +1 -0
- package/dist/src/dendrite/audience.js +202 -0
- package/dist/src/dendrite/audience.js.map +1 -0
- package/dist/src/dendrite/index.d.ts +5 -3
- package/dist/src/dendrite/index.d.ts.map +1 -1
- package/dist/src/dendrite/index.js +2 -1
- package/dist/src/dendrite/index.js.map +1 -1
- package/dist/src/dendrite/presets.d.ts +10 -5
- package/dist/src/dendrite/presets.d.ts.map +1 -1
- package/dist/src/dendrite/presets.js +17 -5
- package/dist/src/dendrite/presets.js.map +1 -1
- package/dist/src/dendrite/registry.d.ts +62 -7
- package/dist/src/dendrite/registry.d.ts.map +1 -1
- package/dist/src/dendrite/registry.js +183 -12
- package/dist/src/dendrite/registry.js.map +1 -1
- package/dist/src/dendrite/types.d.ts +91 -8
- package/dist/src/dendrite/types.d.ts.map +1 -1
- package/dist/src/failure-text.d.ts +36 -0
- package/dist/src/failure-text.d.ts.map +1 -0
- package/dist/src/failure-text.js +89 -0
- package/dist/src/failure-text.js.map +1 -0
- package/dist/src/foreign-rounds.d.ts +69 -0
- package/dist/src/foreign-rounds.d.ts.map +1 -0
- package/dist/src/foreign-rounds.js +152 -0
- package/dist/src/foreign-rounds.js.map +1 -0
- package/dist/src/framework.d.ts +2064 -842
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +8586 -3739
- package/dist/src/framework.js.map +1 -1
- package/dist/src/gate/event-gate.d.ts +125 -21
- package/dist/src/gate/event-gate.d.ts.map +1 -1
- package/dist/src/gate/event-gate.js +487 -72
- 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.map +1 -1
- package/dist/src/gate/reply-judge.d.ts +4 -0
- package/dist/src/gate/reply-judge.d.ts.map +1 -1
- package/dist/src/gate/reply-judge.js +12 -3
- package/dist/src/gate/reply-judge.js.map +1 -1
- package/dist/src/gate/rule-scope.d.ts +30 -0
- package/dist/src/gate/rule-scope.d.ts.map +1 -0
- package/dist/src/gate/rule-scope.js +41 -0
- package/dist/src/gate/rule-scope.js.map +1 -0
- package/dist/src/gate/suppress-script-worker.d.ts +2 -0
- package/dist/src/gate/suppress-script-worker.d.ts.map +1 -0
- package/dist/src/gate/suppress-script-worker.js +103 -0
- package/dist/src/gate/suppress-script-worker.js.map +1 -0
- package/dist/src/gate/suppress-scripts.d.ts +105 -0
- package/dist/src/gate/suppress-scripts.d.ts.map +1 -0
- package/dist/src/gate/suppress-scripts.js +403 -0
- package/dist/src/gate/suppress-scripts.js.map +1 -0
- package/dist/src/gate/suppression-review.d.ts +143 -0
- package/dist/src/gate/suppression-review.d.ts.map +1 -0
- package/dist/src/gate/suppression-review.js +463 -0
- package/dist/src/gate/suppression-review.js.map +1 -0
- package/dist/src/gate/suppression-stats.d.ts +125 -0
- package/dist/src/gate/suppression-stats.d.ts.map +1 -0
- package/dist/src/gate/suppression-stats.js +214 -0
- package/dist/src/gate/suppression-stats.js.map +1 -0
- package/dist/src/gate/types.d.ts +142 -7
- package/dist/src/gate/types.d.ts.map +1 -1
- package/dist/src/gate/wake-onboarding.d.ts +119 -0
- package/dist/src/gate/wake-onboarding.d.ts.map +1 -0
- package/dist/src/gate/wake-onboarding.js +277 -0
- package/dist/src/gate/wake-onboarding.js.map +1 -0
- package/dist/src/heartbeat/heartbeat.js +1 -1
- package/dist/src/heartbeat/heartbeat.js.map +1 -1
- package/dist/src/held-writes.d.ts +31 -23
- package/dist/src/held-writes.d.ts.map +1 -1
- package/dist/src/held-writes.js +44 -59
- package/dist/src/held-writes.js.map +1 -1
- package/dist/src/index.d.ts +6 -2
- 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/inference-bounds.d.ts +2 -1
- package/dist/src/inference-bounds.d.ts.map +1 -1
- package/dist/src/inference-bounds.js +9 -6
- package/dist/src/inference-bounds.js.map +1 -1
- package/dist/src/jsonrpc-envelope.d.ts +69 -0
- package/dist/src/jsonrpc-envelope.d.ts.map +1 -0
- package/dist/src/jsonrpc-envelope.js +66 -0
- package/dist/src/jsonrpc-envelope.js.map +1 -0
- package/dist/src/mcpl/channel-registry.d.ts +188 -28
- package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
- package/dist/src/mcpl/channel-registry.js +426 -70
- package/dist/src/mcpl/channel-registry.js.map +1 -1
- package/dist/src/mcpl/checkpoint-manager.d.ts +2 -4
- package/dist/src/mcpl/checkpoint-manager.d.ts.map +1 -1
- package/dist/src/mcpl/checkpoint-manager.js.map +1 -1
- package/dist/src/mcpl/feature-set-manager.d.ts +2 -4
- package/dist/src/mcpl/feature-set-manager.d.ts.map +1 -1
- package/dist/src/mcpl/feature-set-manager.js.map +1 -1
- package/dist/src/mcpl/inbound-source.d.ts +2 -1
- package/dist/src/mcpl/inbound-source.d.ts.map +1 -1
- package/dist/src/mcpl/inbound-source.js +2 -1
- package/dist/src/mcpl/inbound-source.js.map +1 -1
- package/dist/src/mcpl/inference-router.d.ts +6 -6
- package/dist/src/mcpl/inference-router.d.ts.map +1 -1
- package/dist/src/mcpl/inference-router.js.map +1 -1
- package/dist/src/mcpl/modern-connection.d.ts +109 -15
- package/dist/src/mcpl/modern-connection.d.ts.map +1 -1
- package/dist/src/mcpl/modern-connection.js +593 -86
- package/dist/src/mcpl/modern-connection.js.map +1 -1
- package/dist/src/mcpl/protocol-family.d.ts +3 -1
- package/dist/src/mcpl/protocol-family.d.ts.map +1 -1
- package/dist/src/mcpl/protocol-family.js +15 -9
- package/dist/src/mcpl/protocol-family.js.map +1 -1
- package/dist/src/mcpl/push-coalescer.d.ts +132 -22
- package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
- package/dist/src/mcpl/push-coalescer.js +105 -15
- package/dist/src/mcpl/push-coalescer.js.map +1 -1
- package/dist/src/mcpl/push-handler.d.ts +9 -7
- package/dist/src/mcpl/push-handler.d.ts.map +1 -1
- package/dist/src/mcpl/push-handler.js.map +1 -1
- package/dist/src/mcpl/server-connection.d.ts +50 -28
- package/dist/src/mcpl/server-connection.d.ts.map +1 -1
- package/dist/src/mcpl/server-connection.js +165 -72
- package/dist/src/mcpl/server-connection.js.map +1 -1
- package/dist/src/mcpl/tool-classes.d.ts.map +1 -1
- package/dist/src/mcpl/tool-classes.js +3 -0
- package/dist/src/mcpl/tool-classes.js.map +1 -1
- package/dist/src/mcpl/tool-presence.d.ts.map +1 -1
- package/dist/src/mcpl/tool-presence.js +10 -6
- package/dist/src/mcpl/tool-presence.js.map +1 -1
- package/dist/src/mcpl/toolset-host.d.ts.map +1 -1
- package/dist/src/mcpl/toolset-host.js +7 -6
- package/dist/src/mcpl/toolset-host.js.map +1 -1
- package/dist/src/mcpl/toolsets-module.d.ts.map +1 -1
- package/dist/src/mcpl/toolsets-module.js +2 -1
- package/dist/src/mcpl/toolsets-module.js.map +1 -1
- package/dist/src/mcpl/types.d.ts +33 -6
- 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 +24 -1
- package/dist/src/module-registry.d.ts.map +1 -1
- package/dist/src/module-registry.js +68 -18
- package/dist/src/module-registry.js.map +1 -1
- package/dist/src/modules/discord/index.d.ts +45 -8
- package/dist/src/modules/discord/index.d.ts.map +1 -1
- package/dist/src/modules/discord/index.js +238 -87
- package/dist/src/modules/discord/index.js.map +1 -1
- package/dist/src/modules/health/index.js +2 -2
- package/dist/src/modules/health/index.js.map +1 -1
- package/dist/src/modules/history/index.d.ts +59 -0
- package/dist/src/modules/history/index.d.ts.map +1 -1
- package/dist/src/modules/history/index.js +432 -67
- package/dist/src/modules/history/index.js.map +1 -1
- package/dist/src/modules/history/semantic.d.ts +6 -5
- package/dist/src/modules/history/semantic.d.ts.map +1 -1
- package/dist/src/modules/history/semantic.js +18 -25
- package/dist/src/modules/history/semantic.js.map +1 -1
- package/dist/src/modules/workspace/disk-agreement.d.ts +1 -1
- package/dist/src/modules/workspace/disk-agreement.js +1 -1
- package/dist/src/modules/workspace/index.d.ts +28 -7
- package/dist/src/modules/workspace/index.d.ts.map +1 -1
- package/dist/src/modules/workspace/index.js +80 -25
- package/dist/src/modules/workspace/index.js.map +1 -1
- package/dist/src/modules/workspace/observe.d.ts.map +1 -1
- package/dist/src/modules/workspace/observe.js +2 -1
- package/dist/src/modules/workspace/observe.js.map +1 -1
- package/dist/src/modules/workspace/reconcile.d.ts.map +1 -1
- package/dist/src/modules/workspace/reconcile.js +3 -2
- package/dist/src/modules/workspace/reconcile.js.map +1 -1
- package/dist/src/notice-tags.d.ts +20 -9
- package/dist/src/notice-tags.d.ts.map +1 -1
- package/dist/src/notice-tags.js +20 -9
- package/dist/src/notice-tags.js.map +1 -1
- package/dist/src/operator-log.d.ts +36 -3
- package/dist/src/operator-log.d.ts.map +1 -1
- package/dist/src/operator-log.js +2 -2
- package/dist/src/operator-log.js.map +1 -1
- package/dist/src/presentation.d.ts +747 -0
- package/dist/src/presentation.d.ts.map +1 -0
- package/dist/src/presentation.js +1437 -0
- package/dist/src/presentation.js.map +1 -0
- package/dist/src/prose-drafts.d.ts +15 -11
- package/dist/src/prose-drafts.d.ts.map +1 -1
- package/dist/src/prose-drafts.js +16 -12
- package/dist/src/prose-drafts.js.map +1 -1
- package/dist/src/read-marks.d.ts +192 -0
- package/dist/src/read-marks.d.ts.map +1 -0
- package/dist/src/read-marks.js +242 -0
- package/dist/src/read-marks.js.map +1 -0
- package/dist/src/reasoning-loss.d.ts +5 -0
- package/dist/src/reasoning-loss.d.ts.map +1 -0
- package/dist/src/reasoning-loss.js +8 -0
- package/dist/src/reasoning-loss.js.map +1 -0
- package/dist/src/recovery/offline-branch.d.ts +91 -6
- package/dist/src/recovery/offline-branch.d.ts.map +1 -1
- package/dist/src/recovery/offline-branch.js +277 -32
- package/dist/src/recovery/offline-branch.js.map +1 -1
- package/dist/src/recovery/recover-cli.js +24 -4
- package/dist/src/recovery/recover-cli.js.map +1 -1
- package/dist/src/send-tools.d.ts +5 -9
- package/dist/src/send-tools.d.ts.map +1 -1
- package/dist/src/send-tools.js +40 -7
- package/dist/src/send-tools.js.map +1 -1
- package/dist/src/sent-rows.js +1 -1
- package/dist/src/stored-injections.d.ts +2 -2
- package/dist/src/stored-injections.js +2 -2
- package/dist/src/stored-removal.d.ts +17 -4
- package/dist/src/stored-removal.d.ts.map +1 -1
- package/dist/src/stored-removal.js +29 -5
- package/dist/src/stored-removal.js.map +1 -1
- package/dist/src/supplied-text.d.ts +1 -1
- package/dist/src/supplied-text.js +3 -3
- package/dist/src/supplied-text.js.map +1 -1
- package/dist/src/thinking-drops.d.ts +2 -0
- package/dist/src/thinking-drops.d.ts.map +1 -1
- package/dist/src/thinking-drops.js.map +1 -1
- package/dist/src/tool-presentation.d.ts.map +1 -1
- package/dist/src/tool-presentation.js +38 -9
- package/dist/src/tool-presentation.js.map +1 -1
- package/dist/src/tool-result-guard.d.ts +12 -1
- package/dist/src/tool-result-guard.d.ts.map +1 -1
- package/dist/src/tool-result-guard.js +42 -8
- package/dist/src/tool-result-guard.js.map +1 -1
- package/dist/src/tune-out/coordinator.d.ts +1170 -81
- package/dist/src/tune-out/coordinator.d.ts.map +1 -1
- package/dist/src/tune-out/coordinator.js +3732 -369
- package/dist/src/tune-out/coordinator.js.map +1 -1
- package/dist/src/tune-out/tools.d.ts +114 -17
- package/dist/src/tune-out/tools.d.ts.map +1 -1
- package/dist/src/tune-out/tools.js +89 -28
- package/dist/src/tune-out/tools.js.map +1 -1
- package/dist/src/tune-out/window-anchor.d.ts +143 -0
- package/dist/src/tune-out/window-anchor.d.ts.map +1 -0
- package/dist/src/tune-out/window-anchor.js +202 -0
- package/dist/src/tune-out/window-anchor.js.map +1 -0
- package/dist/src/types/agent.d.ts +121 -25
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/events.d.ts +2 -0
- package/dist/src/types/events.d.ts.map +1 -1
- package/dist/src/types/framework.d.ts +72 -17
- package/dist/src/types/framework.d.ts.map +1 -1
- package/dist/src/types/inference-log.d.ts +1 -0
- package/dist/src/types/inference-log.d.ts.map +1 -1
- package/dist/src/types/module.d.ts +180 -69
- package/dist/src/types/module.d.ts.map +1 -1
- package/dist/src/types/trace.d.ts +573 -39
- package/dist/src/types/trace.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/admission/admit.ts +32 -3
- package/src/agent.ts +392 -132
- package/src/api/mcp-server-core.ts +54 -17
- package/src/api/server.ts +4 -23
- package/src/append-all.ts +6 -0
- package/src/code-execution/isolate/scripts.ts +28 -3
- package/src/config-checks.ts +14 -0
- package/src/context-manager-errors.ts +37 -0
- package/src/context-options.ts +106 -0
- package/src/cut-off-round.ts +4 -4
- package/src/deferred-import.ts +239 -0
- package/src/dendrite/audience.ts +315 -0
- package/src/dendrite/index.ts +14 -2
- package/src/dendrite/presets.ts +18 -6
- package/src/dendrite/registry.ts +213 -14
- package/src/dendrite/types.ts +92 -8
- package/src/failure-text.ts +88 -0
- package/src/foreign-rounds.ts +199 -0
- package/src/framework.ts +8796 -3813
- package/src/gate/event-gate.ts +518 -91
- package/src/gate/index.ts +3 -0
- package/src/gate/reply-judge.ts +12 -3
- package/src/gate/rule-scope.ts +45 -0
- package/src/gate/suppress-script-worker.ts +89 -0
- package/src/gate/suppress-scripts.ts +414 -0
- package/src/gate/suppression-review.ts +517 -0
- package/src/gate/suppression-stats.ts +314 -0
- package/src/gate/types.ts +148 -7
- package/src/gate/wake-onboarding.ts +309 -0
- package/src/heartbeat/heartbeat.ts +1 -1
- package/src/held-writes.ts +49 -60
- package/src/index.ts +10 -2
- package/src/inference-bounds.ts +11 -6
- package/src/jsonrpc-envelope.ts +81 -0
- package/src/mcpl/channel-registry.ts +513 -116
- package/src/mcpl/checkpoint-manager.ts +3 -2
- package/src/mcpl/feature-set-manager.ts +2 -1
- package/src/mcpl/inbound-source.ts +2 -1
- package/src/mcpl/inference-router.ts +7 -4
- package/src/mcpl/modern-connection.ts +629 -91
- package/src/mcpl/protocol-family.ts +15 -9
- package/src/mcpl/push-coalescer.ts +193 -34
- package/src/mcpl/push-handler.ts +10 -5
- package/src/mcpl/server-connection.ts +169 -75
- package/src/mcpl/tool-classes.ts +3 -0
- package/src/mcpl/tool-presence.ts +10 -5
- package/src/mcpl/toolset-host.ts +7 -6
- package/src/mcpl/toolsets-module.ts +2 -1
- package/src/mcpl/types.ts +33 -6
- package/src/module-registry.ts +73 -17
- package/src/modules/discord/index.ts +230 -82
- package/src/modules/health/index.ts +2 -2
- package/src/modules/history/index.ts +439 -70
- package/src/modules/history/semantic.ts +18 -24
- package/src/modules/workspace/disk-agreement.ts +1 -1
- package/src/modules/workspace/index.ts +89 -26
- package/src/modules/workspace/observe.ts +2 -1
- package/src/modules/workspace/reconcile.ts +3 -2
- package/src/notice-tags.ts +21 -9
- package/src/operator-log.ts +36 -3
- package/src/presentation.ts +1678 -0
- package/src/prose-drafts.ts +16 -12
- package/src/read-marks.ts +328 -0
- package/src/reasoning-loss.ts +7 -0
- package/src/recovery/offline-branch.ts +350 -35
- package/src/recovery/recover-cli.ts +23 -4
- package/src/send-tools.ts +40 -7
- package/src/sent-rows.ts +1 -1
- package/src/stored-injections.ts +2 -2
- package/src/stored-removal.ts +31 -7
- package/src/supplied-text.ts +3 -3
- package/src/thinking-drops.ts +2 -0
- package/src/tool-presentation.ts +34 -9
- package/src/tool-result-guard.ts +41 -7
- package/src/tune-out/coordinator.ts +4121 -472
- package/src/tune-out/tools.ts +162 -44
- package/src/tune-out/window-anchor.ts +277 -0
- package/src/types/agent.ts +122 -25
- package/src/types/events.ts +2 -0
- package/src/types/framework.ts +73 -17
- package/src/types/inference-log.ts +1 -0
- package/src/types/module.ts +181 -68
- package/src/types/trace.ts +607 -43
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { suppressionRecordOf } from '../gate/suppression-stats.js';
|
|
1
2
|
import { JsStore } from '@animalabs/chronicle';
|
|
2
3
|
import type { SurgeryMarkerReceipt } from '../operator-log.js';
|
|
3
4
|
import { ContextManager, continuesBody, type BodyShard } from '@animalabs/context-manager';
|
|
@@ -15,16 +16,50 @@ import {
|
|
|
15
16
|
type DiscordAwarenessRef,
|
|
16
17
|
type DiscordMessageMetadataCarrier,
|
|
17
18
|
} from './discord-awareness-outbox.js';
|
|
19
|
+
import { appendAll } from '../append-all.js';
|
|
18
20
|
|
|
19
21
|
export interface OfflineRecoveryBranchOptions {
|
|
20
22
|
storePath: string;
|
|
21
23
|
agentName: string;
|
|
22
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* Preferred: Discord message that remains as the last visible anchor. On
|
|
26
|
+
* an agent's window that was presented (#291) the branch is cut at the
|
|
27
|
+
* write that presented the anchor, so rows stored after the anchor and
|
|
28
|
+
* before that write stay on the branch: those the window took after the
|
|
29
|
+
* anchor enter it again at the agent's next presentation point, after the
|
|
30
|
+
* anchor, and the rows that one write presented together with the anchor
|
|
31
|
+
* stay after it in the window. Only rows stored after the cut are removed
|
|
32
|
+
* and counted. A
|
|
33
|
+
* suppression may name any row the branch keeps that has a Discord
|
|
34
|
+
* message id, one the cut keeps after the anchor too; one without (an
|
|
35
|
+
* agent's round, a tool row) takes the live suppressMessages, by its row
|
|
36
|
+
* id. A recovery on that window that would change nothing (no
|
|
37
|
+
* row removed, none an arrival again, no suppression) is refused, as the
|
|
38
|
+
* live rollback refuses it.
|
|
39
|
+
*/
|
|
23
40
|
messageId?: string;
|
|
24
|
-
/** Exact internal ContextManager message ID that remains as the anchor. */
|
|
41
|
+
/** Exact internal ContextManager message ID that remains as the anchor (the cut as for `messageId`). */
|
|
25
42
|
contextId?: string;
|
|
26
|
-
/**
|
|
43
|
+
/**
|
|
44
|
+
* Backward-compatible alternative: remove the newest N context entries,
|
|
45
|
+
* counted in the agent's window once it was presented (#291), over the
|
|
46
|
+
* rows of its own slot, and in the record before that; the anchor is the
|
|
47
|
+
* entry before them, cut as for `messageId`.
|
|
48
|
+
*/
|
|
27
49
|
messages?: number;
|
|
50
|
+
/**
|
|
51
|
+
* Remove every row a suppress rule hid (`metadata.suppressed`) on the new
|
|
52
|
+
* branch, which needs no anchor: the branch then starts from the newest row.
|
|
53
|
+
* For rolling back to a release that doesn't read the stamp, where a row
|
|
54
|
+
* hidden between messages a summary covers makes that summary non-contiguous,
|
|
55
|
+
* and the store is refused at open. The source branch keeps them. Once this
|
|
56
|
+
* release has made a window write of two or more rows (the first
|
|
57
|
+
* write-down of a window that holds a row, as at the first boot on a store
|
|
58
|
+
* with history, or a presentation of more than one row: MANIFEST version
|
|
59
|
+
* 2), no earlier release opens the store at all, and only a copy taken
|
|
60
|
+
* before the upgrade goes back.
|
|
61
|
+
*/
|
|
62
|
+
removeSuppressed?: boolean;
|
|
28
63
|
/** Exact Discord messages to suppress on the new branch. */
|
|
29
64
|
suppressMessageIds?: string[];
|
|
30
65
|
/** Inclusive context-order ranges between Discord message endpoints. */
|
|
@@ -52,6 +87,20 @@ export interface OfflineRecoveryBranchResult {
|
|
|
52
87
|
targetBranch: string;
|
|
53
88
|
messagesRemoved: number;
|
|
54
89
|
messagesSuppressed: number;
|
|
90
|
+
/**
|
|
91
|
+
* On a presented window (#291): the rows after the anchor in the agent's
|
|
92
|
+
* window that the cut keeps in the record (stored before the write that
|
|
93
|
+
* presented the anchor, presented after it), as the live rollback counts
|
|
94
|
+
* them, of the agent's own slot: the recovery reads that slot alone, so a
|
|
95
|
+
* row of a slot the agent observes (the residents' rows in a persistent
|
|
96
|
+
* subconscious's window), which the live rollback counts, isn't counted
|
|
97
|
+
* here, and the check of a cut that would change nothing doesn't see it. They leave the window as arrived and not presented, and enter it
|
|
98
|
+
* again at the agent's next presentation point, after the anchor. Not
|
|
99
|
+
* removed, so not counted in `messagesRemoved`, and not marked; a row the
|
|
100
|
+
* same recovery suppresses is counted there instead. 0 for a run with no
|
|
101
|
+
* anchor; absent on a window that is still the record.
|
|
102
|
+
*/
|
|
103
|
+
returnedToArrivals?: number;
|
|
55
104
|
/** Removed messages that carry a Discord address. */
|
|
56
105
|
discordAddressable: number;
|
|
57
106
|
/** The publication scope recorded for this recovery. */
|
|
@@ -78,9 +127,12 @@ export interface OfflineRecoveryBranchResult {
|
|
|
78
127
|
/**
|
|
79
128
|
* Create a safe Chronicle branch while the normal agent host is stopped.
|
|
80
129
|
*
|
|
81
|
-
* History is scanned in bounded windows and blobs are not re-inlined
|
|
82
|
-
*
|
|
83
|
-
* the
|
|
130
|
+
* History is scanned in bounded windows and blobs are not re-inlined, but a
|
|
131
|
+
* presented window (#291) is read whole where its order counts: for a
|
|
132
|
+
* `messages` count, and for the rows after the anchor that the cut returns
|
|
133
|
+
* to the arrivals or keeps (windowAfterCut). The affected messages are never
|
|
134
|
+
* compiled and their content is never written to the recovery outbox; only
|
|
135
|
+
* Discord addressing metadata leaves Chronicle.
|
|
84
136
|
*/
|
|
85
137
|
export async function createOfflineRecoveryBranch(
|
|
86
138
|
options: OfflineRecoveryBranchOptions,
|
|
@@ -88,7 +140,7 @@ export async function createOfflineRecoveryBranch(
|
|
|
88
140
|
const anchorCount = Number(!!options.messageId)
|
|
89
141
|
+ Number(!!options.contextId)
|
|
90
142
|
+ Number(options.messages !== undefined);
|
|
91
|
-
if (anchorCount !== 1) {
|
|
143
|
+
if (anchorCount !== 1 && !(anchorCount === 0 && options.removeSuppressed === true)) {
|
|
92
144
|
throw new Error('Specify exactly one of messageId, contextId, or messages');
|
|
93
145
|
}
|
|
94
146
|
const count = options.messages === undefined ? undefined : Math.floor(options.messages);
|
|
@@ -119,6 +171,16 @@ export async function createOfflineRecoveryBranch(
|
|
|
119
171
|
let targetIndex: number;
|
|
120
172
|
let removedCount: number;
|
|
121
173
|
let carriers: DiscordMessageMetadataCarrier[];
|
|
174
|
+
// Once the agent's window was presented (#291), the branch cut is the
|
|
175
|
+
// write that presented the anchor (branchSequenceOf), not the anchor's
|
|
176
|
+
// own place in the record: the rows stored after the anchor and before
|
|
177
|
+
// that write stay on the branch, and those the window took after the
|
|
178
|
+
// anchor are arrivals again there. Only the rows stored after the cut
|
|
179
|
+
// are removed, and only they are counted, carried and checked.
|
|
180
|
+
const presented = contextManager.windowMode() === 'presented';
|
|
181
|
+
const firstRemovedAfter = (index: number, anchor: StoredWindowMessage): number => presented
|
|
182
|
+
? firstIndexAfterCut(contextManager!, index, total, contextManager!.branchSequenceOf(anchor.id))
|
|
183
|
+
: index + 1;
|
|
122
184
|
|
|
123
185
|
if (options.messageId) {
|
|
124
186
|
// Search backward in bounded windows: recovery anchors are normally near
|
|
@@ -134,8 +196,9 @@ export async function createOfflineRecoveryBranch(
|
|
|
134
196
|
}
|
|
135
197
|
target = located.message;
|
|
136
198
|
targetIndex = located.index;
|
|
137
|
-
|
|
138
|
-
|
|
199
|
+
const firstRemoved = firstRemovedAfter(targetIndex, target);
|
|
200
|
+
removedCount = total - firstRemoved;
|
|
201
|
+
carriers = collectDiscordCarriers(contextManager, firstRemoved, total);
|
|
139
202
|
} else if (options.contextId) {
|
|
140
203
|
const located = findInternalMessageFromTail(contextManager, total, options.contextId.trim());
|
|
141
204
|
if (!located) {
|
|
@@ -143,39 +206,82 @@ export async function createOfflineRecoveryBranch(
|
|
|
143
206
|
}
|
|
144
207
|
target = located.message;
|
|
145
208
|
targetIndex = located.index;
|
|
146
|
-
|
|
147
|
-
|
|
209
|
+
const firstRemoved = firstRemovedAfter(targetIndex, target);
|
|
210
|
+
removedCount = total - firstRemoved;
|
|
211
|
+
carriers = collectDiscordCarriers(contextManager, firstRemoved, total);
|
|
212
|
+
} else if (presented && count !== undefined) {
|
|
213
|
+
// Counted back in the agent's window, whose order isn't the record's:
|
|
214
|
+
// the anchor is the window's row `count` before its newest. Only the
|
|
215
|
+
// rows of the context's own slot count, as the live undo counts them:
|
|
216
|
+
// the anchor is looked for in that slot, and a window index can name
|
|
217
|
+
// a row of a slot the agent observed (the residents' rows in a
|
|
218
|
+
// persistent subconscious's window). The one context opened here
|
|
219
|
+
// merges no other slot, so its listing holds none of those today; the
|
|
220
|
+
// count doesn't lean on that.
|
|
221
|
+
const window = contextManager.getPresentedMessages().filter((row) => contextManager!.getMessage(row.id) !== null);
|
|
222
|
+
if (count! >= window.length) {
|
|
223
|
+
throw new Error(
|
|
224
|
+
`Cannot remove ${count} message(s) — history has ${window.length}; at least one must remain.`,
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
const anchor = window[window.length - 1 - count!]!;
|
|
228
|
+
const located = findInternalMessageFromTail(contextManager, total, String(anchor.id));
|
|
229
|
+
if (!located) {
|
|
230
|
+
throw new Error(`The recovery anchor ${String(anchor.id)}, ${count} back in the window, is not in this context's record.`);
|
|
231
|
+
}
|
|
232
|
+
target = located.message;
|
|
233
|
+
targetIndex = located.index;
|
|
234
|
+
const firstRemoved = firstRemovedAfter(targetIndex, target);
|
|
235
|
+
removedCount = total - firstRemoved;
|
|
236
|
+
carriers = collectDiscordCarriers(contextManager, firstRemoved, total);
|
|
148
237
|
} else {
|
|
149
238
|
// Count mode remains for compatibility. Read only the target plus suffix,
|
|
150
239
|
// avoiding full-history materialization and attachment blob inflation.
|
|
151
|
-
|
|
240
|
+
// With no anchor (removeSuppressed alone), the target is the newest row,
|
|
241
|
+
// on a presented window too: nothing is cut, the branch starting at the
|
|
242
|
+
// head.
|
|
243
|
+
const dropping = count ?? 0;
|
|
244
|
+
const window = contextManager.getMessageWindow(total - dropping - 1, dropping + 1, {
|
|
152
245
|
resolveBlobs: false,
|
|
153
246
|
}).messages;
|
|
154
247
|
const countTarget = window[0];
|
|
155
248
|
const discarded = window.slice(1);
|
|
156
|
-
if (!countTarget || discarded.length !==
|
|
249
|
+
if (!countTarget || discarded.length !== dropping) {
|
|
157
250
|
throw new Error('Could not read the requested recovery window from Chronicle');
|
|
158
251
|
}
|
|
159
252
|
target = countTarget;
|
|
160
|
-
targetIndex = total -
|
|
161
|
-
removedCount =
|
|
253
|
+
targetIndex = total - dropping - 1;
|
|
254
|
+
removedCount = dropping;
|
|
162
255
|
carriers = discarded.map(addressingOnly);
|
|
163
256
|
}
|
|
164
257
|
|
|
165
258
|
validateAnchorBoundary(contextManager, targetIndex, total, target);
|
|
166
259
|
|
|
260
|
+
// The rows the branch keeps are the record's first `total -
|
|
261
|
+
// removedCount`: a suppression may name any of them, those stored after
|
|
262
|
+
// the anchor and kept by a presented window's cut too (in the record
|
|
263
|
+
// before a presentation, the last kept row is the anchor).
|
|
167
264
|
const suppression = buildSuppressionPlan(
|
|
168
265
|
contextManager,
|
|
169
|
-
|
|
266
|
+
total - removedCount - 1,
|
|
170
267
|
options.suppressMessageIds ?? [],
|
|
171
268
|
options.suppressRanges ?? [],
|
|
269
|
+
options.removeSuppressed === true,
|
|
172
270
|
);
|
|
271
|
+
// A run with no anchor cuts nothing, so there is no anchor to name and
|
|
272
|
+
// nothing returns: one with nothing to suppress makes a branch at the
|
|
273
|
+
// head, as on a legacy window.
|
|
274
|
+
const afterCut = presented && anchorCount > 0 ? windowAfterCut(contextManager, target) : undefined;
|
|
275
|
+
const returnedToArrivals = afterCut?.returned.filter((id) => !suppression.ids.has(String(id))).length;
|
|
276
|
+
if (afterCut && removedCount === 0 && suppression.intervals.length === 0 && afterCut.returned.length === 0) {
|
|
277
|
+
refuseIneffectiveCut(target, afterCut.kept, options.agentName, contextManager.getPresentedMessages() as StoredWindowMessage[]);
|
|
278
|
+
}
|
|
173
279
|
validateRemovalIntegrity(
|
|
174
280
|
contextManager,
|
|
175
281
|
total,
|
|
176
282
|
[
|
|
177
283
|
...(removedCount > 0 ? [{
|
|
178
|
-
start:
|
|
284
|
+
start: total - removedCount,
|
|
179
285
|
end: total - 1,
|
|
180
286
|
fromId: '',
|
|
181
287
|
toId: '',
|
|
@@ -208,6 +314,7 @@ export async function createOfflineRecoveryBranch(
|
|
|
208
314
|
targetBranch,
|
|
209
315
|
messagesRemoved: removedCount,
|
|
210
316
|
messagesSuppressed: suppression.messageCount,
|
|
317
|
+
...(presented ? { returnedToArrivals: returnedToArrivals ?? 0 } : {}),
|
|
211
318
|
discordAddressable: addressable,
|
|
212
319
|
marksScope: marks === 'none' ? 'none' : marks.scope,
|
|
213
320
|
discordMarkersQueued: refs.length,
|
|
@@ -241,11 +348,15 @@ export async function createOfflineRecoveryBranch(
|
|
|
241
348
|
|
|
242
349
|
let createdBranch: string | undefined;
|
|
243
350
|
try {
|
|
244
|
-
// branchAt uses the target message's origin sequence.
|
|
245
|
-
//
|
|
246
|
-
//
|
|
247
|
-
//
|
|
248
|
-
|
|
351
|
+
// branchAt uses the target message's origin sequence. With no anchor
|
|
352
|
+
// nothing is rewound, so the branch starts at the head instead: what
|
|
353
|
+
// was written after the newest message, a removal or a summary, stays.
|
|
354
|
+
// No message content is compiled or submitted to Membrane during this
|
|
355
|
+
// operation. Creating the branch is part of the body: a failure here
|
|
356
|
+
// settles the batch like a failed switch or redaction.
|
|
357
|
+
createdBranch = anchorCount === 0
|
|
358
|
+
? store.createBranchAt(targetBranch, sourceBranch, store.currentSequence()).name
|
|
359
|
+
: contextManager.branchAt(target.id, targetBranch);
|
|
249
360
|
await contextManager.switchBranch(createdBranch);
|
|
250
361
|
// Each interval is removed by its messages' ids, so the order doesn't
|
|
251
362
|
// change the result. removeStoredInterval takes a single message outside
|
|
@@ -428,6 +539,188 @@ function findInternalMessageFromTail(
|
|
|
428
539
|
return null;
|
|
429
540
|
}
|
|
430
541
|
|
|
542
|
+
/**
|
|
543
|
+
* The first slot index after `targetIndex` whose row was stored after
|
|
544
|
+
* `cut`, or `total` when none was: the rows a branch cut at `cut` leaves
|
|
545
|
+
* behind, the slot being in store order, start there. Read in bounded,
|
|
546
|
+
* blob-free windows.
|
|
547
|
+
*/
|
|
548
|
+
function firstIndexAfterCut(
|
|
549
|
+
contextManager: ContextManager,
|
|
550
|
+
targetIndex: number,
|
|
551
|
+
total: number,
|
|
552
|
+
cut: number,
|
|
553
|
+
): number {
|
|
554
|
+
for (let offset = targetIndex + 1; offset < total; offset += RECOVERY_SCAN_WINDOW) {
|
|
555
|
+
const messages = contextManager.getMessageWindow(
|
|
556
|
+
offset,
|
|
557
|
+
Math.min(RECOVERY_SCAN_WINDOW, total - offset),
|
|
558
|
+
{ resolveBlobs: false },
|
|
559
|
+
).messages;
|
|
560
|
+
const at = messages.findIndex((message) => message.sequence > cut);
|
|
561
|
+
if (at >= 0) return offset + at;
|
|
562
|
+
}
|
|
563
|
+
return total;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* The rows after `target` in a presented window, as the live rollback reads
|
|
568
|
+
* them (windowAfterCut): those the write that presented the target
|
|
569
|
+
* presented too (`kept`, which no branch cut separates from it), and those
|
|
570
|
+
* presented after that write but stored at or before it (`returned`, which
|
|
571
|
+
* the cut keeps in the record and makes arrivals again). Read from the
|
|
572
|
+
* window's newest row back to the target or the first row presented before
|
|
573
|
+
* the cut (every row's cut grows in window order).
|
|
574
|
+
*/
|
|
575
|
+
function windowAfterCut(
|
|
576
|
+
contextManager: ContextManager,
|
|
577
|
+
target: StoredWindowMessage,
|
|
578
|
+
): { returned: StoredWindowMessage['id'][]; kept: StoredWindowMessage[] } {
|
|
579
|
+
const cut = contextManager.branchSequenceOf(target.id);
|
|
580
|
+
const window = contextManager.getPresentedMessages();
|
|
581
|
+
const returned: StoredWindowMessage['id'][] = [];
|
|
582
|
+
const kept: StoredWindowMessage[] = [];
|
|
583
|
+
for (let i = window.length - 1; i >= 0; i--) {
|
|
584
|
+
const row = window[i]!;
|
|
585
|
+
if (row.id === target.id) break;
|
|
586
|
+
const at = contextManager.branchSequenceOf(row.id);
|
|
587
|
+
if (at < cut) break;
|
|
588
|
+
if (at === cut) kept.unshift(row as StoredWindowMessage);
|
|
589
|
+
else if (row.sequence <= cut) returned.unshift(row.id);
|
|
590
|
+
}
|
|
591
|
+
return { returned, kept };
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* Refuses a recovery on a presented window that would change nothing, as
|
|
596
|
+
* the live rollback refuses one: the cut at the write that presented the
|
|
597
|
+
* anchor removes no row, and no row the window took after that write
|
|
598
|
+
* becomes an arrival again (windowAfterCut). The rows that write presented
|
|
599
|
+
* after the anchor (`kept`) name the way to take them out. A row holding a
|
|
600
|
+
* result of a call at or before the anchor (`window`, the agent's window:
|
|
601
|
+
* a tool boundary presents a round's reply and its results in one write)
|
|
602
|
+
* isn't to be suppressed, which would leave that call without its result:
|
|
603
|
+
* the advice is to anchor before the call. The others are suppressed: in
|
|
604
|
+
* the same recovery a row by its Discord message id, each one named, which
|
|
605
|
+
* takes every copy of that message; and by its row id with the live
|
|
606
|
+
* `suppressMessages` a row with none (an agent's round, a tool row) or one
|
|
607
|
+
* sharing the anchor's Discord id (a retried write's copy), which the
|
|
608
|
+
* offline suppression would take with the anchor. No range is offered: it
|
|
609
|
+
* takes every record row between its ends, any agent's, rows the window
|
|
610
|
+
* never held among them. Each refusal says the recovery reads the agent's
|
|
611
|
+
* own slot alone: a row of a slot it observes, which the live rollback
|
|
612
|
+
* weighs, isn't weighed here.
|
|
613
|
+
*/
|
|
614
|
+
function refuseIneffectiveCut(
|
|
615
|
+
target: StoredWindowMessage,
|
|
616
|
+
kept: readonly StoredWindowMessage[],
|
|
617
|
+
agentName: string,
|
|
618
|
+
window: readonly StoredWindowMessage[],
|
|
619
|
+
): never {
|
|
620
|
+
const anchorDiscordId = discordIdOf(target);
|
|
621
|
+
const anchor = `${String(target.id)}${anchorDiscordId ? ` (Discord message ${anchorDiscordId})` : ''}`;
|
|
622
|
+
const ownSlotOnly = `. Offline, only ${agentName}'s own slot is read: a row of a slot it observes, which the live rollback weighs, isn't weighed here.`;
|
|
623
|
+
if (kept.length > 0) {
|
|
624
|
+
const n = kept.length;
|
|
625
|
+
const first = kept[0]!;
|
|
626
|
+
const firstDiscordId = discordIdOf(first);
|
|
627
|
+
const named = `${String(first.id)}${firstDiscordId ? `, Discord message ${firstDiscordId}` : ''}`;
|
|
628
|
+
const split = resultsOfAnchoredCalls(window, target.id, kept);
|
|
629
|
+
const suppressible = split ? kept.filter((row) => !split.rows.includes(row)) : kept;
|
|
630
|
+
const copies = suppressible.filter((row) => anchorDiscordId !== undefined && discordIdOf(row) === anchorDiscordId);
|
|
631
|
+
// By row id, live: every one, so following the advice leaves none of them in place.
|
|
632
|
+
const unaddressed = suppressible.filter((row) => discordIdOf(row) === undefined || copies.includes(row));
|
|
633
|
+
const addressed = suppressible.filter((row) => !unaddressed.includes(row));
|
|
634
|
+
const offline = `in the same recovery (--suppress / suppressMessageIds: ${addressed.map((row) => discordIdOf(row)!).join(', ')})`;
|
|
635
|
+
const live = `by row id with the live suppressMessages (${unaddressed.map((row) => String(row.id)).join(', ')}) once the host runs, ` +
|
|
636
|
+
'since the offline recovery suppresses only by Discord message id' +
|
|
637
|
+
(copies.length > 0 ? `, which takes every copy of a message, the anchor's own too` : '');
|
|
638
|
+
const m = suppressible.length;
|
|
639
|
+
const them = m === n ? (n === 1 ? 'it' : 'them') : 'the rest';
|
|
640
|
+
const how = m === 0 ? ''
|
|
641
|
+
: unaddressed.length === 0 ? `suppress ${them} ${offline}`
|
|
642
|
+
: addressed.length === 0 ? `suppress ${them} ${live}` +
|
|
643
|
+
(copies.length > 0 ? '' : ` and ${m === 1 ? 'it has none' : 'none of them has one'}`)
|
|
644
|
+
: `suppress the ${addressed.length} with a Discord message id ${offline}, and the ${unaddressed.length} ` +
|
|
645
|
+
`${copies.length > 0 ? `other${unaddressed.length === 1 ? '' : 's'}` : 'without one'} ${live}`;
|
|
646
|
+
const before = split ? anchorBefore(split, 'anchor the recovery', 'at') : '';
|
|
647
|
+
throw new Error(
|
|
648
|
+
`Recovery anchor ${anchor} was presented to ${agentName} in one write with the ${n} message${n === 1 ? '' : 's'} ` +
|
|
649
|
+
`after it in its window (the first: ${named}), and a branch cut can't separate the rows one write presented, ` +
|
|
650
|
+
`so a recovery anchored there would remove nothing; to take ${n === 1 ? 'that message' : 'those messages'} out, ` +
|
|
651
|
+
(before && how ? `${before}; or, to keep that call and its results, ${how}` : before || how) + ownSlotOnly,
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
throw new Error(
|
|
655
|
+
`Recovery anchor ${anchor} is already the tail of ${agentName}'s window: every message stored after it was ` +
|
|
656
|
+
'stored before the write that presented it, where the branch is cut, and none entered the window after it, so ' +
|
|
657
|
+
'a recovery anchored there would change nothing' + ownSlotOnly,
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* Of the rows a no-op cut keeps after its anchor (`kept`), those holding a
|
|
663
|
+
* result of a call in a row of the agent's window at or before the anchor,
|
|
664
|
+
* which a suppression would leave without its result: a tool boundary
|
|
665
|
+
* presents a round's reply and its results in one write, and so does a
|
|
666
|
+
* write-down for rows stored together. With them, the earliest such call's
|
|
667
|
+
* row and the window row before it, where a cut keeps the call and its
|
|
668
|
+
* results together. Undefined where no kept row answers such a call, or the
|
|
669
|
+
* anchor isn't in `window`. The live rollback's refusal reads it too.
|
|
670
|
+
*/
|
|
671
|
+
export function resultsOfAnchoredCalls<R extends { id: unknown; content: readonly unknown[] }>(
|
|
672
|
+
window: readonly R[],
|
|
673
|
+
anchorId: unknown,
|
|
674
|
+
kept: readonly R[],
|
|
675
|
+
): { rows: R[]; call: R; before?: R } | undefined {
|
|
676
|
+
const anchorAt = window.findIndex((row) => row.id === anchorId);
|
|
677
|
+
if (anchorAt < 0) return undefined;
|
|
678
|
+
const callAt = new Map<string, number>();
|
|
679
|
+
for (let i = 0; i <= anchorAt; i++) {
|
|
680
|
+
for (const raw of window[i]!.content) {
|
|
681
|
+
const block = raw as { type?: unknown; id?: unknown };
|
|
682
|
+
if (block.type === 'tool_use' && typeof block.id === 'string') callAt.set(block.id, i);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
let earliest: number | undefined;
|
|
686
|
+
const rows = kept.filter((row) => {
|
|
687
|
+
let answers = false;
|
|
688
|
+
for (const raw of row.content) {
|
|
689
|
+
const block = raw as { type?: unknown; toolUseId?: unknown };
|
|
690
|
+
const at = block.type === 'tool_result' && typeof block.toolUseId === 'string' ? callAt.get(block.toolUseId) : undefined;
|
|
691
|
+
if (at === undefined) continue;
|
|
692
|
+
answers = true;
|
|
693
|
+
earliest = earliest === undefined ? at : Math.min(earliest, at);
|
|
694
|
+
}
|
|
695
|
+
return answers;
|
|
696
|
+
});
|
|
697
|
+
if (rows.length === 0 || earliest === undefined) return undefined;
|
|
698
|
+
return { rows, call: window[earliest]!, ...(earliest > 0 ? { before: window[earliest - 1]! } : {}) };
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/**
|
|
702
|
+
* A refusal's advice for the kept rows that answer a call at or before the
|
|
703
|
+
* anchor (resultsOfAnchoredCalls): `verb` (anchor the recovery, roll back)
|
|
704
|
+
* before the call's row, naming the row before it (`to`: at, to), and why
|
|
705
|
+
* they aren't to be suppressed.
|
|
706
|
+
*/
|
|
707
|
+
export function anchorBefore(
|
|
708
|
+
split: { rows: ReadonlyArray<{ id: unknown }>; call: { id: unknown }; before?: { id: unknown } },
|
|
709
|
+
verb: string,
|
|
710
|
+
to: string,
|
|
711
|
+
): string {
|
|
712
|
+
const ids = split.rows.map((row) => String(row.id)).join(', ');
|
|
713
|
+
const one = split.rows.length === 1;
|
|
714
|
+
return `${verb} before ${String(split.call.id)}, which holds the call ${one ? 'it answers' : 'they answer'}, instead` +
|
|
715
|
+
`${split.before ? ` (${to} ${String(split.before.id)}, the message before it in the window)` : ''}: ${ids} ${one ? 'holds a result' : 'hold results'} ` +
|
|
716
|
+
`of a call at or before the anchor, and suppressing ${one ? 'it' : 'them'} would leave that call without its result`;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
function discordIdOf(message: { metadata?: Record<string, unknown> }): string | undefined {
|
|
720
|
+
const id = message.metadata?.messageId;
|
|
721
|
+
return id === undefined || id === null || id === '' ? undefined : String(id);
|
|
722
|
+
}
|
|
723
|
+
|
|
431
724
|
function validateAnchorBoundary(
|
|
432
725
|
contextManager: ContextManager,
|
|
433
726
|
targetIndex: number,
|
|
@@ -453,8 +746,20 @@ function validateRemovalIntegrity(
|
|
|
453
746
|
intervals: SuppressionInterval[],
|
|
454
747
|
): void {
|
|
455
748
|
if (intervals.length === 0) return;
|
|
456
|
-
|
|
457
|
-
|
|
749
|
+
// Sorted starts with each one's running furthest end, searched per row: a
|
|
750
|
+
// store can hold a removal for every third row (--remove-suppressed).
|
|
751
|
+
const sorted = [...intervals].sort((a, b) => a.start - b.start);
|
|
752
|
+
const reach: number[] = [];
|
|
753
|
+
for (const interval of sorted) reach.push(Math.max(interval.end, reach.at(-1) ?? -1));
|
|
754
|
+
const removed = (index: number) => {
|
|
755
|
+
let lo = 0;
|
|
756
|
+
let hi = sorted.length - 1;
|
|
757
|
+
while (lo < hi) {
|
|
758
|
+
const mid = Math.ceil((lo + hi) / 2);
|
|
759
|
+
if (sorted[mid]!.start <= index) lo = mid; else hi = mid - 1;
|
|
760
|
+
}
|
|
761
|
+
return sorted[lo]!.start <= index && index <= reach[lo]!;
|
|
762
|
+
};
|
|
458
763
|
const toolUses = new Map<string, boolean>();
|
|
459
764
|
const toolResults = new Map<string, boolean>();
|
|
460
765
|
// The message before, in store order: a selection splits a body when a
|
|
@@ -501,10 +806,11 @@ function validateRemovalIntegrity(
|
|
|
501
806
|
|
|
502
807
|
function buildSuppressionPlan(
|
|
503
808
|
contextManager: ContextManager,
|
|
504
|
-
|
|
809
|
+
lastKeptIndex: number,
|
|
505
810
|
rawMessageIds: string[],
|
|
506
811
|
rawRanges: Array<{ fromMessageId: string; toMessageId: string }>,
|
|
507
|
-
|
|
812
|
+
removeSuppressed = false,
|
|
813
|
+
): { intervals: SuppressionInterval[]; carriers: DiscordMessageMetadataCarrier[]; messageCount: number; ids: Set<string> } {
|
|
508
814
|
const messageIds = rawMessageIds.map(normalizeDiscordMessageId);
|
|
509
815
|
const ranges = rawRanges.map((range) => ({
|
|
510
816
|
fromMessageId: normalizeDiscordMessageId(range.fromMessageId),
|
|
@@ -514,16 +820,20 @@ function buildSuppressionPlan(
|
|
|
514
820
|
...messageIds,
|
|
515
821
|
...ranges.flatMap((range) => [range.fromMessageId, range.toMessageId]),
|
|
516
822
|
]);
|
|
517
|
-
if (wanted.size === 0) return { intervals: [], carriers: [], messageCount: 0 };
|
|
823
|
+
if (wanted.size === 0 && !removeSuppressed) return { intervals: [], carriers: [], messageCount: 0, ids: new Set() };
|
|
824
|
+
const stamped: SuppressionInterval[] = [];
|
|
518
825
|
|
|
519
826
|
const locations = new Map<string, MessageLocation>();
|
|
520
|
-
for (let offset = 0; offset <=
|
|
827
|
+
for (let offset = 0; offset <= lastKeptIndex; offset += RECOVERY_SCAN_WINDOW) {
|
|
521
828
|
const messages = contextManager.getMessageWindow(
|
|
522
829
|
offset,
|
|
523
|
-
Math.min(RECOVERY_SCAN_WINDOW,
|
|
830
|
+
Math.min(RECOVERY_SCAN_WINDOW, lastKeptIndex - offset + 1),
|
|
524
831
|
{ resolveBlobs: false },
|
|
525
832
|
).messages;
|
|
526
833
|
for (let i = 0; i < messages.length; i++) {
|
|
834
|
+
if (removeSuppressed && suppressionRecordOf(messages[i].metadata as Record<string, unknown> | undefined)) {
|
|
835
|
+
stamped.push({ start: offset + i, end: offset + i, fromId: messages[i].id, toId: messages[i].id });
|
|
836
|
+
}
|
|
527
837
|
const externalId = String(messages[i].metadata?.messageId ?? '');
|
|
528
838
|
if (!wanted.has(externalId)) continue;
|
|
529
839
|
const index = offset + i;
|
|
@@ -565,10 +875,10 @@ function buildSuppressionPlan(
|
|
|
565
875
|
}
|
|
566
876
|
}
|
|
567
877
|
|
|
568
|
-
const intervals: SuppressionInterval[] = [];
|
|
878
|
+
const intervals: SuppressionInterval[] = [...stamped];
|
|
569
879
|
for (const id of messageIds) {
|
|
570
880
|
const location = locations.get(id)!;
|
|
571
|
-
intervals
|
|
881
|
+
appendAll(intervals, location.runs.map((run) => ({ ...run })));
|
|
572
882
|
}
|
|
573
883
|
for (const range of ranges) {
|
|
574
884
|
const from = locations.get(range.fromMessageId)!;
|
|
@@ -587,7 +897,7 @@ function buildSuppressionPlan(
|
|
|
587
897
|
fromId: earlier.firstId,
|
|
588
898
|
toId: later.firstId,
|
|
589
899
|
});
|
|
590
|
-
for (const end of [from, to]) intervals
|
|
900
|
+
for (const end of [from, to]) appendAll(intervals, end.runs.map((run) => ({ ...run })));
|
|
591
901
|
}
|
|
592
902
|
|
|
593
903
|
intervals.sort((a, b) => a.start - b.start || a.end - b.end);
|
|
@@ -605,13 +915,15 @@ function buildSuppressionPlan(
|
|
|
605
915
|
}
|
|
606
916
|
|
|
607
917
|
const carriers: DiscordMessageMetadataCarrier[] = [];
|
|
918
|
+
const ids = new Set<string>();
|
|
608
919
|
for (const interval of merged) {
|
|
609
|
-
carriers
|
|
920
|
+
appendAll(carriers, collectDiscordCarriers(contextManager, interval.start, interval.end + 1, ids));
|
|
610
921
|
}
|
|
611
922
|
return {
|
|
612
923
|
intervals: merged,
|
|
613
924
|
carriers,
|
|
614
925
|
messageCount: merged.reduce((sum, interval) => sum + interval.end - interval.start + 1, 0),
|
|
926
|
+
ids,
|
|
615
927
|
};
|
|
616
928
|
}
|
|
617
929
|
|
|
@@ -619,12 +931,14 @@ function buildSuppressionPlan(
|
|
|
619
931
|
* The addressing metadata of removed messages, read in bounded windows: only
|
|
620
932
|
* the Discord address and the MCPL tags (for `addressed` selection) are kept,
|
|
621
933
|
* never content. A sharded message may repeat its metadata across adjacent
|
|
622
|
-
* records; selection dedupes by address.
|
|
934
|
+
* records; selection dedupes by address. Each row's id is added to `ids`
|
|
935
|
+
* where given.
|
|
623
936
|
*/
|
|
624
937
|
function collectDiscordCarriers(
|
|
625
938
|
contextManager: ContextManager,
|
|
626
939
|
start: number,
|
|
627
940
|
end: number,
|
|
941
|
+
ids?: Set<string>,
|
|
628
942
|
): DiscordMessageMetadataCarrier[] {
|
|
629
943
|
const carriers: DiscordMessageMetadataCarrier[] = [];
|
|
630
944
|
for (let offset = start; offset < end; offset += RECOVERY_SCAN_WINDOW) {
|
|
@@ -633,7 +947,8 @@ function collectDiscordCarriers(
|
|
|
633
947
|
Math.min(RECOVERY_SCAN_WINDOW, end - offset),
|
|
634
948
|
{ resolveBlobs: false },
|
|
635
949
|
).messages;
|
|
636
|
-
carriers
|
|
950
|
+
appendAll(carriers, messages.map(addressingOnly));
|
|
951
|
+
if (ids) for (const message of messages) ids.add(String(message.id));
|
|
637
952
|
}
|
|
638
953
|
return carriers;
|
|
639
954
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { createOfflineRecoveryBranch } from './offline-branch.js';
|
|
4
4
|
import type { DiscordAwarenessMarks } from './discord-awareness-outbox.js';
|
|
5
|
+
import { appendAll } from '../append-all.js';
|
|
5
6
|
|
|
6
7
|
interface CliOptions {
|
|
7
8
|
storePath?: string;
|
|
@@ -11,6 +12,7 @@ interface CliOptions {
|
|
|
11
12
|
messages?: number;
|
|
12
13
|
suppressMessageIds: string[];
|
|
13
14
|
suppressRanges: Array<{ fromMessageId: string; toMessageId: string }>;
|
|
15
|
+
removeSuppressed: boolean;
|
|
14
16
|
branchName?: string;
|
|
15
17
|
namespace?: string;
|
|
16
18
|
discordServerId?: string;
|
|
@@ -35,8 +37,23 @@ Options:
|
|
|
35
37
|
--message-id <id|link> Last Discord message the agent should still see
|
|
36
38
|
--context-id <internal-id> Exact internal context message to retain as anchor
|
|
37
39
|
--messages <count> Legacy alternative: remove N context entries
|
|
40
|
+
On a presented window the rows one write
|
|
41
|
+
presented with the anchor stay after it, a
|
|
42
|
+
count is over the agent's window, and a
|
|
43
|
+
recovery that would remove nothing is refused
|
|
38
44
|
--suppress <id|link> Suppress one Discord message; repeat or comma-separate
|
|
39
45
|
--suppress-range <a>..<b> Suppress an inclusive context range; repeatable
|
|
46
|
+
--remove-suppressed Remove every row a suppress rule hid, needing no
|
|
47
|
+
anchor. Run it before rolling back to a release
|
|
48
|
+
without suppress rules, which can refuse the
|
|
49
|
+
store at open otherwise; the source branch keeps
|
|
50
|
+
the rows. Once this release has made a window
|
|
51
|
+
write of two or more rows (the first write-down
|
|
52
|
+
of a window that holds a row, as at the first
|
|
53
|
+
boot on a store with history, or a presentation
|
|
54
|
+
of more than one row: MANIFEST version 2) no
|
|
55
|
+
earlier release opens the store, and only a copy
|
|
56
|
+
taken before the upgrade goes back
|
|
40
57
|
--branch <name> Recovery branch name (generated by default)
|
|
41
58
|
--namespace <namespace> Context namespace (default: agents/<agent>)
|
|
42
59
|
--discord-server <id> Override/inject the Discord MCPL server id
|
|
@@ -53,7 +70,7 @@ The agent host must be stopped before running this command.`;
|
|
|
53
70
|
}
|
|
54
71
|
|
|
55
72
|
function parseArgs(args: string[]): CliOptions {
|
|
56
|
-
const options: CliOptions = { dryRun: false, suppressMessageIds: [], suppressRanges: [], marks: 'none' };
|
|
73
|
+
const options: CliOptions = { dryRun: false, suppressMessageIds: [], suppressRanges: [], removeSuppressed: false, marks: 'none' };
|
|
57
74
|
for (let i = 0; i < args.length; i++) {
|
|
58
75
|
const arg = args[i];
|
|
59
76
|
const value = () => {
|
|
@@ -70,7 +87,7 @@ function parseArgs(args: string[]): CliOptions {
|
|
|
70
87
|
case '--messages': options.messages = Number(value()); break;
|
|
71
88
|
case '--suppress':
|
|
72
89
|
case '--suppress-message':
|
|
73
|
-
options.suppressMessageIds
|
|
90
|
+
appendAll(options.suppressMessageIds, value().split(',').map((item) => item.trim()).filter(Boolean));
|
|
74
91
|
break;
|
|
75
92
|
case '--suppress-range': {
|
|
76
93
|
const raw = value();
|
|
@@ -97,6 +114,7 @@ function parseArgs(args: string[]): CliOptions {
|
|
|
97
114
|
options.marks = marks;
|
|
98
115
|
break;
|
|
99
116
|
}
|
|
117
|
+
case '--remove-suppressed': options.removeSuppressed = true; break;
|
|
100
118
|
case '--dry-run': options.dryRun = true; break;
|
|
101
119
|
case '--help':
|
|
102
120
|
case '-h':
|
|
@@ -113,9 +131,9 @@ async function main(): Promise<void> {
|
|
|
113
131
|
if (
|
|
114
132
|
!options.storePath
|
|
115
133
|
|| !options.agentName
|
|
116
|
-
|| (!options.messageId && !options.contextId && options.messages === undefined)
|
|
134
|
+
|| (!options.messageId && !options.contextId && options.messages === undefined && !options.removeSuppressed)
|
|
117
135
|
) {
|
|
118
|
-
throw new Error(`--store, --agent, and one anchor (--message-id, --context-id, or --messages) are required\n\n${usage()}`);
|
|
136
|
+
throw new Error(`--store, --agent, and one anchor (--message-id, --context-id, or --messages) or --remove-suppressed are required\n\n${usage()}`);
|
|
119
137
|
}
|
|
120
138
|
|
|
121
139
|
// A CLI choice and its application are the same moment (the host is
|
|
@@ -129,6 +147,7 @@ async function main(): Promise<void> {
|
|
|
129
147
|
messages: options.messages,
|
|
130
148
|
suppressMessageIds: options.suppressMessageIds,
|
|
131
149
|
suppressRanges: options.suppressRanges,
|
|
150
|
+
removeSuppressed: options.removeSuppressed,
|
|
132
151
|
branchName: options.branchName,
|
|
133
152
|
namespace: options.namespace,
|
|
134
153
|
discordServerId: options.discordServerId,
|