@animalabs/agent-framework 0.23.3 → 0.24.1
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 +212 -42
- package/dist/src/agent.d.ts +65 -9
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +98 -12
- package/dist/src/agent.js.map +1 -1
- package/dist/src/api/server.d.ts.map +1 -1
- package/dist/src/api/server.js +14 -12
- package/dist/src/api/server.js.map +1 -1
- package/dist/src/clock-rows.d.ts +115 -0
- package/dist/src/clock-rows.d.ts.map +1 -0
- package/dist/src/clock-rows.js +222 -0
- package/dist/src/clock-rows.js.map +1 -0
- package/dist/src/code-execution/engine.d.ts +177 -0
- package/dist/src/code-execution/engine.d.ts.map +1 -0
- package/dist/src/code-execution/engine.js +494 -0
- package/dist/src/code-execution/engine.js.map +1 -0
- package/dist/src/code-execution/index.d.ts +5 -10
- package/dist/src/code-execution/index.d.ts.map +1 -1
- package/dist/src/code-execution/index.js +5 -9
- package/dist/src/code-execution/index.js.map +1 -1
- package/dist/src/code-execution/isolate/calls.d.ts +114 -0
- package/dist/src/code-execution/isolate/calls.d.ts.map +1 -0
- package/dist/src/code-execution/isolate/calls.js +145 -0
- package/dist/src/code-execution/isolate/calls.js.map +1 -0
- package/dist/src/code-execution/isolate/child-entry.d.ts +2 -0
- package/dist/src/code-execution/isolate/child-entry.d.ts.map +1 -0
- package/dist/src/code-execution/isolate/child-entry.js +5 -0
- package/dist/src/code-execution/isolate/child-entry.js.map +1 -0
- package/dist/src/code-execution/isolate/child-main.d.ts +26 -0
- package/dist/src/code-execution/isolate/child-main.d.ts.map +1 -0
- package/dist/src/code-execution/isolate/child-main.js +132 -0
- package/dist/src/code-execution/isolate/child-main.js.map +1 -0
- package/dist/src/code-execution/isolate/child.d.ts +44 -0
- package/dist/src/code-execution/isolate/child.d.ts.map +1 -0
- package/dist/src/code-execution/isolate/child.js +315 -0
- package/dist/src/code-execution/isolate/child.js.map +1 -0
- package/dist/src/code-execution/isolate/frames.d.ts +80 -0
- package/dist/src/code-execution/isolate/frames.d.ts.map +1 -0
- package/dist/src/code-execution/isolate/frames.js +210 -0
- package/dist/src/code-execution/isolate/frames.js.map +1 -0
- package/dist/src/code-execution/isolate/output.d.ts +68 -0
- package/dist/src/code-execution/isolate/output.d.ts.map +1 -0
- package/dist/src/code-execution/isolate/output.js +143 -0
- package/dist/src/code-execution/isolate/output.js.map +1 -0
- package/dist/src/code-execution/isolate/prelude.d.ts +109 -0
- package/dist/src/code-execution/isolate/prelude.d.ts.map +1 -0
- package/dist/src/code-execution/isolate/prelude.js +401 -0
- package/dist/src/code-execution/isolate/prelude.js.map +1 -0
- package/dist/src/code-execution/isolate/protocol.d.ts +151 -0
- package/dist/src/code-execution/isolate/protocol.d.ts.map +1 -0
- package/dist/src/code-execution/isolate/protocol.js +150 -0
- package/dist/src/code-execution/isolate/protocol.js.map +1 -0
- package/dist/src/code-execution/isolate/render.d.ts +57 -0
- package/dist/src/code-execution/isolate/render.d.ts.map +1 -0
- package/dist/src/code-execution/isolate/render.js +214 -0
- package/dist/src/code-execution/isolate/render.js.map +1 -0
- package/dist/src/code-execution/isolate/runner.d.ts +250 -0
- package/dist/src/code-execution/isolate/runner.d.ts.map +1 -0
- package/dist/src/code-execution/isolate/runner.js +854 -0
- package/dist/src/code-execution/isolate/runner.js.map +1 -0
- package/dist/src/code-execution/isolate/scripts.d.ts +175 -0
- package/dist/src/code-execution/isolate/scripts.d.ts.map +1 -0
- package/dist/src/code-execution/isolate/scripts.js +357 -0
- package/dist/src/code-execution/isolate/scripts.js.map +1 -0
- package/dist/src/code-execution/quickjs.wasm +0 -0
- package/dist/src/code-execution/tool-definition.d.ts +93 -43
- package/dist/src/code-execution/tool-definition.d.ts.map +1 -1
- package/dist/src/code-execution/tool-definition.js +176 -142
- package/dist/src/code-execution/tool-definition.js.map +1 -1
- package/dist/src/deadline-timer.d.ts +3 -0
- package/dist/src/deadline-timer.d.ts.map +1 -1
- package/dist/src/deadline-timer.js +1 -1
- package/dist/src/deadline-timer.js.map +1 -1
- package/dist/src/framework.d.ts +1120 -457
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +4635 -2505
- package/dist/src/framework.js.map +1 -1
- package/dist/src/gate/event-gate.d.ts +28 -4
- package/dist/src/gate/event-gate.d.ts.map +1 -1
- package/dist/src/gate/event-gate.js +89 -13
- package/dist/src/gate/event-gate.js.map +1 -1
- package/dist/src/held-writes.d.ts +16 -3
- package/dist/src/held-writes.d.ts.map +1 -1
- package/dist/src/held-writes.js +16 -3
- package/dist/src/held-writes.js.map +1 -1
- package/dist/src/index.d.ts +8 -6
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +10 -8
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcpl/channel-registry.d.ts +483 -115
- package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
- package/dist/src/mcpl/channel-registry.js +1735 -425
- package/dist/src/mcpl/channel-registry.js.map +1 -1
- package/dist/src/mcpl/conversation-router.d.ts +19 -39
- package/dist/src/mcpl/conversation-router.d.ts.map +1 -1
- package/dist/src/mcpl/conversation-router.js +27 -59
- package/dist/src/mcpl/conversation-router.js.map +1 -1
- package/dist/src/mcpl/convert-block.d.ts.map +1 -1
- package/dist/src/mcpl/convert-block.js +9 -5
- package/dist/src/mcpl/convert-block.js.map +1 -1
- package/dist/src/mcpl/inbound-source.d.ts +50 -0
- package/dist/src/mcpl/inbound-source.d.ts.map +1 -1
- package/dist/src/mcpl/inbound-source.js +130 -0
- package/dist/src/mcpl/inbound-source.js.map +1 -1
- package/dist/src/mcpl/index.d.ts +2 -1
- package/dist/src/mcpl/index.d.ts.map +1 -1
- package/dist/src/mcpl/index.js +3 -2
- package/dist/src/mcpl/index.js.map +1 -1
- package/dist/src/mcpl/log-value.d.ts +6 -0
- package/dist/src/mcpl/log-value.d.ts.map +1 -1
- package/dist/src/mcpl/log-value.js +8 -0
- package/dist/src/mcpl/log-value.js.map +1 -1
- package/dist/src/mcpl/modern-connection.d.ts +15 -6
- package/dist/src/mcpl/modern-connection.d.ts.map +1 -1
- package/dist/src/mcpl/modern-connection.js +168 -53
- package/dist/src/mcpl/modern-connection.js.map +1 -1
- package/dist/src/mcpl/prose-grammar.d.ts +4 -5
- package/dist/src/mcpl/prose-grammar.d.ts.map +1 -1
- package/dist/src/mcpl/prose-grammar.js +4 -5
- package/dist/src/mcpl/prose-grammar.js.map +1 -1
- package/dist/src/mcpl/protocol-family.d.ts +97 -10
- package/dist/src/mcpl/protocol-family.d.ts.map +1 -1
- package/dist/src/mcpl/protocol-family.js +174 -17
- package/dist/src/mcpl/protocol-family.js.map +1 -1
- package/dist/src/mcpl/push-coalescer.d.ts +132 -14
- package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
- package/dist/src/mcpl/push-coalescer.js +227 -50
- package/dist/src/mcpl/push-coalescer.js.map +1 -1
- package/dist/src/mcpl/push-handler.d.ts +9 -20
- package/dist/src/mcpl/push-handler.d.ts.map +1 -1
- package/dist/src/mcpl/push-handler.js +7 -49
- package/dist/src/mcpl/push-handler.js.map +1 -1
- package/dist/src/mcpl/reference-fetcher.d.ts +2 -2
- package/dist/src/mcpl/reference-fetcher.d.ts.map +1 -1
- package/dist/src/mcpl/reference-fetcher.js +4 -4
- package/dist/src/mcpl/reference-fetcher.js.map +1 -1
- package/dist/src/mcpl/server-connection.d.ts +50 -21
- package/dist/src/mcpl/server-connection.d.ts.map +1 -1
- package/dist/src/mcpl/server-connection.js +158 -74
- package/dist/src/mcpl/server-connection.js.map +1 -1
- package/dist/src/mcpl/tags.d.ts +24 -0
- package/dist/src/mcpl/tags.d.ts.map +1 -1
- package/dist/src/mcpl/tags.js +33 -0
- package/dist/src/mcpl/tags.js.map +1 -1
- package/dist/src/mcpl/tool-result-normalize.d.ts +2 -1
- package/dist/src/mcpl/tool-result-normalize.d.ts.map +1 -1
- package/dist/src/mcpl/tool-result-normalize.js.map +1 -1
- package/dist/src/mcpl/transport.d.ts +55 -7
- package/dist/src/mcpl/transport.d.ts.map +1 -1
- package/dist/src/mcpl/transport.js +163 -34
- package/dist/src/mcpl/transport.js.map +1 -1
- package/dist/src/mcpl/types.d.ts +22 -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 +4 -2
- package/dist/src/module-registry.d.ts.map +1 -1
- package/dist/src/module-registry.js +100 -13
- package/dist/src/module-registry.js.map +1 -1
- package/dist/src/modules/discord/index.d.ts.map +1 -1
- package/dist/src/modules/discord/index.js +8 -13
- package/dist/src/modules/discord/index.js.map +1 -1
- package/dist/src/modules/history/index.d.ts.map +1 -1
- package/dist/src/modules/history/index.js +27 -4
- package/dist/src/modules/history/index.js.map +1 -1
- package/dist/src/modules/history/semantic.d.ts +15 -3
- package/dist/src/modules/history/semantic.d.ts.map +1 -1
- package/dist/src/modules/history/semantic.js +27 -5
- package/dist/src/modules/history/semantic.js.map +1 -1
- package/dist/src/modules/index.d.ts +1 -1
- package/dist/src/modules/index.d.ts.map +1 -1
- package/dist/src/modules/index.js.map +1 -1
- package/dist/src/modules/unknown-tool.d.ts +4 -1
- package/dist/src/modules/unknown-tool.d.ts.map +1 -1
- package/dist/src/modules/unknown-tool.js +4 -1
- package/dist/src/modules/unknown-tool.js.map +1 -1
- package/dist/src/modules/workspace/branch-intent.d.ts +3 -1
- package/dist/src/modules/workspace/branch-intent.d.ts.map +1 -1
- package/dist/src/modules/workspace/branch-intent.js +3 -1
- package/dist/src/modules/workspace/branch-intent.js.map +1 -1
- package/dist/src/modules/workspace/edit-plan.d.ts +265 -0
- package/dist/src/modules/workspace/edit-plan.d.ts.map +1 -0
- package/dist/src/modules/workspace/edit-plan.js +794 -0
- package/dist/src/modules/workspace/edit-plan.js.map +1 -0
- package/dist/src/modules/workspace/edit-text.d.ts +117 -0
- package/dist/src/modules/workspace/edit-text.d.ts.map +1 -0
- package/dist/src/modules/workspace/edit-text.js +584 -0
- package/dist/src/modules/workspace/edit-text.js.map +1 -0
- package/dist/src/modules/workspace/effects.d.ts +5 -4
- package/dist/src/modules/workspace/effects.d.ts.map +1 -1
- package/dist/src/modules/workspace/effects.js +78 -25
- package/dist/src/modules/workspace/effects.js.map +1 -1
- package/dist/src/modules/workspace/index.d.ts +493 -62
- package/dist/src/modules/workspace/index.d.ts.map +1 -1
- package/dist/src/modules/workspace/index.js +2216 -652
- package/dist/src/modules/workspace/index.js.map +1 -1
- package/dist/src/modules/workspace/observe.d.ts +213 -38
- package/dist/src/modules/workspace/observe.d.ts.map +1 -1
- package/dist/src/modules/workspace/observe.js +416 -36
- 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 +15 -8
- package/dist/src/modules/workspace/reconcile.js.map +1 -1
- package/dist/src/modules/workspace/text.d.ts +32 -0
- package/dist/src/modules/workspace/text.d.ts.map +1 -0
- package/dist/src/modules/workspace/text.js +67 -0
- package/dist/src/modules/workspace/text.js.map +1 -0
- package/dist/src/modules/workspace/types.d.ts +210 -6
- package/dist/src/modules/workspace/types.d.ts.map +1 -1
- package/dist/src/modules/workspace/types.js.map +1 -1
- package/dist/src/modules/workspace/watcher.d.ts +3 -2
- package/dist/src/modules/workspace/watcher.d.ts.map +1 -1
- package/dist/src/modules/workspace/watcher.js.map +1 -1
- package/dist/src/operator-log.d.ts +52 -24
- package/dist/src/operator-log.d.ts.map +1 -1
- package/dist/src/operator-log.js.map +1 -1
- package/dist/src/recovery/discord-awareness-outbox.d.ts +496 -72
- package/dist/src/recovery/discord-awareness-outbox.d.ts.map +1 -1
- package/dist/src/recovery/discord-awareness-outbox.js +1196 -289
- package/dist/src/recovery/discord-awareness-outbox.js.map +1 -1
- package/dist/src/recovery/offline-branch.d.ts +26 -1
- package/dist/src/recovery/offline-branch.d.ts.map +1 -1
- package/dist/src/recovery/offline-branch.js +164 -39
- package/dist/src/recovery/offline-branch.js.map +1 -1
- package/dist/src/recovery/recover-cli.js +23 -4
- package/dist/src/recovery/recover-cli.js.map +1 -1
- package/dist/src/send-tools.d.ts +11 -0
- package/dist/src/send-tools.d.ts.map +1 -0
- package/dist/src/send-tools.js +11 -0
- package/dist/src/send-tools.js.map +1 -0
- package/dist/src/timezone.d.ts +4 -2
- package/dist/src/timezone.d.ts.map +1 -1
- package/dist/src/timezone.js +4 -2
- package/dist/src/timezone.js.map +1 -1
- package/dist/src/tool-image-ledger.d.ts +46 -7
- package/dist/src/tool-image-ledger.d.ts.map +1 -1
- package/dist/src/tool-image-ledger.js +60 -7
- package/dist/src/tool-image-ledger.js.map +1 -1
- package/dist/src/tool-result-guard.d.ts +14 -11
- package/dist/src/tool-result-guard.d.ts.map +1 -1
- package/dist/src/tool-result-guard.js +17 -14
- package/dist/src/tool-result-guard.js.map +1 -1
- package/dist/src/tool-result-history.d.ts +3 -2
- package/dist/src/tool-result-history.d.ts.map +1 -1
- package/dist/src/tool-result-history.js +3 -2
- package/dist/src/tool-result-history.js.map +1 -1
- package/dist/src/tune-out/coordinator.d.ts +21 -0
- package/dist/src/tune-out/coordinator.d.ts.map +1 -1
- package/dist/src/tune-out/coordinator.js +142 -40
- package/dist/src/tune-out/coordinator.js.map +1 -1
- package/dist/src/types/agent.d.ts +31 -6
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/events.d.ts +66 -9
- package/dist/src/types/events.d.ts.map +1 -1
- package/dist/src/types/framework.d.ts +69 -62
- package/dist/src/types/framework.d.ts.map +1 -1
- package/dist/src/types/module.d.ts +40 -8
- package/dist/src/types/module.d.ts.map +1 -1
- package/dist/src/types/trace.d.ts +111 -10
- package/dist/src/types/trace.d.ts.map +1 -1
- package/dist/src/usage/types.d.ts +16 -0
- package/dist/src/usage/types.d.ts.map +1 -1
- package/dist/src/usage/usage-tracker.d.ts +14 -1
- package/dist/src/usage/usage-tracker.d.ts.map +1 -1
- package/dist/src/usage/usage-tracker.js +81 -41
- package/dist/src/usage/usage-tracker.js.map +1 -1
- package/package.json +6 -8
- package/src/agent.ts +99 -12
- package/src/api/server.ts +14 -12
- package/src/clock-rows.ts +302 -0
- package/src/code-execution/engine.ts +595 -0
- package/src/code-execution/index.ts +5 -17
- package/src/code-execution/isolate/calls.ts +199 -0
- package/src/code-execution/isolate/child-entry.ts +5 -0
- package/src/code-execution/isolate/child-main.ts +132 -0
- package/src/code-execution/isolate/child.ts +327 -0
- package/src/code-execution/isolate/frames.ts +209 -0
- package/src/code-execution/isolate/output.ts +161 -0
- package/src/code-execution/isolate/prelude.ts +409 -0
- package/src/code-execution/isolate/protocol.ts +240 -0
- package/src/code-execution/isolate/render.ts +230 -0
- package/src/code-execution/isolate/runner.ts +911 -0
- package/src/code-execution/isolate/scripts.ts +479 -0
- package/src/code-execution/quickjs.wasm +0 -0
- package/src/code-execution/quickjs.wasm.LICENSES +149 -0
- package/src/code-execution/tool-definition.ts +236 -167
- package/src/deadline-timer.ts +1 -1
- package/src/framework.ts +4978 -2671
- package/src/gate/event-gate.ts +79 -17
- package/src/held-writes.ts +17 -3
- package/src/index.ts +32 -22
- package/src/mcpl/channel-registry.ts +1892 -471
- package/src/mcpl/conversation-router.ts +26 -72
- package/src/mcpl/convert-block.ts +9 -5
- package/src/mcpl/inbound-source.ts +131 -0
- package/src/mcpl/index.ts +4 -2
- package/src/mcpl/log-value.ts +9 -0
- package/src/mcpl/modern-connection.ts +162 -53
- package/src/mcpl/prose-grammar.ts +4 -5
- package/src/mcpl/protocol-family.ts +200 -22
- package/src/mcpl/push-coalescer.ts +306 -62
- package/src/mcpl/push-handler.ts +12 -54
- package/src/mcpl/reference-fetcher.ts +4 -4
- package/src/mcpl/server-connection.ts +180 -79
- package/src/mcpl/tags.ts +36 -0
- package/src/mcpl/tool-result-normalize.ts +2 -1
- package/src/mcpl/transport.ts +162 -33
- package/src/mcpl/types.ts +23 -6
- package/src/module-registry.ts +92 -13
- package/src/modules/discord/index.ts +8 -13
- package/src/modules/history/index.ts +25 -3
- package/src/modules/history/semantic.ts +29 -6
- package/src/modules/index.ts +1 -0
- package/src/modules/unknown-tool.ts +4 -1
- package/src/modules/workspace/branch-intent.ts +3 -1
- package/src/modules/workspace/edit-plan.ts +954 -0
- package/src/modules/workspace/edit-text.ts +618 -0
- package/src/modules/workspace/effects.ts +70 -19
- package/src/modules/workspace/index.ts +2293 -650
- package/src/modules/workspace/observe.ts +465 -56
- package/src/modules/workspace/reconcile.ts +14 -7
- package/src/modules/workspace/text.ts +68 -0
- package/src/modules/workspace/types.ts +211 -6
- package/src/modules/workspace/watcher.ts +3 -2
- package/src/operator-log.ts +47 -19
- package/src/recovery/discord-awareness-outbox.ts +1500 -356
- package/src/recovery/offline-branch.ts +189 -55
- package/src/recovery/recover-cli.ts +25 -4
- package/src/send-tools.ts +11 -0
- package/src/timezone.ts +4 -2
- package/src/tool-image-ledger.ts +62 -8
- package/src/tool-result-guard.ts +18 -15
- package/src/tool-result-history.ts +3 -2
- package/src/tune-out/coordinator.ts +166 -45
- package/src/types/agent.ts +32 -6
- package/src/types/events.ts +66 -9
- package/src/types/framework.ts +69 -62
- package/src/types/module.ts +40 -8
- package/src/types/trace.ts +121 -15
- package/src/usage/types.ts +16 -0
- package/src/usage/usage-tracker.ts +86 -43
- package/dist/src/code-execution/py-runner.d.ts +0 -191
- package/dist/src/code-execution/py-runner.d.ts.map +0 -1
- package/dist/src/code-execution/py-runner.js +0 -655
- package/dist/src/code-execution/py-runner.js.map +0 -1
- package/dist/src/code-execution/runtime-py.d.ts +0 -50
- package/dist/src/code-execution/runtime-py.d.ts.map +0 -1
- package/dist/src/code-execution/runtime-py.js +0 -677
- package/dist/src/code-execution/runtime-py.js.map +0 -1
- package/dist/src/mcpl/prose-stream-router.d.ts +0 -88
- package/dist/src/mcpl/prose-stream-router.d.ts.map +0 -1
- package/dist/src/mcpl/prose-stream-router.js +0 -291
- package/dist/src/mcpl/prose-stream-router.js.map +0 -1
- package/src/code-execution/py-runner.ts +0 -791
- package/src/code-execution/runtime-py.ts +0 -677
- package/src/mcpl/prose-stream-router.ts +0 -268
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import { JsStore } from '@animalabs/chronicle';
|
|
2
|
+
import type { SurgeryMarkerReceipt } from '../operator-log.js';
|
|
2
3
|
import { ContextManager, continuesBody, type BodyShard } from '@animalabs/context-manager';
|
|
3
4
|
import { removeStoredInterval } from '../stored-removal.js';
|
|
4
5
|
import { firstFreeBranchName } from '../branch-names.js';
|
|
5
6
|
import {
|
|
6
7
|
DEFAULT_DISCORD_AWARENESS_EMOJI,
|
|
7
8
|
DiscordAwarenessOutbox,
|
|
9
|
+
boundDiscordAwarenessText,
|
|
8
10
|
defaultDiscordAwarenessOutboxPath,
|
|
11
|
+
checkedDiscordAwarenessMarks,
|
|
9
12
|
extractDiscordAwarenessRefs,
|
|
13
|
+
selectDiscordAwarenessRefs,
|
|
14
|
+
type DiscordAwarenessMarks,
|
|
10
15
|
type DiscordAwarenessRef,
|
|
16
|
+
type DiscordMessageMetadataCarrier,
|
|
11
17
|
} from './discord-awareness-outbox.js';
|
|
12
18
|
|
|
13
19
|
export interface OfflineRecoveryBranchOptions {
|
|
@@ -29,8 +35,14 @@ export interface OfflineRecoveryBranchOptions {
|
|
|
29
35
|
namespace?: string;
|
|
30
36
|
/** Use for old message records that do not carry metadata.serverId. */
|
|
31
37
|
discordServerId?: string;
|
|
38
|
+
/** The awareness ledger (default: under the store). */
|
|
32
39
|
outboxPath?: string;
|
|
33
40
|
emoji?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Awareness marks on the removed Discord messages: an explicit publication
|
|
43
|
+
* choice, `none` by default. The recovery itself stays local either way.
|
|
44
|
+
*/
|
|
45
|
+
marks?: DiscordAwarenessMarks;
|
|
34
46
|
dryRun?: boolean;
|
|
35
47
|
}
|
|
36
48
|
|
|
@@ -40,9 +52,27 @@ export interface OfflineRecoveryBranchResult {
|
|
|
40
52
|
targetBranch: string;
|
|
41
53
|
messagesRemoved: number;
|
|
42
54
|
messagesSuppressed: number;
|
|
55
|
+
/** Removed messages that carry a Discord address. */
|
|
56
|
+
discordAddressable: number;
|
|
57
|
+
/** The publication scope recorded for this recovery. */
|
|
58
|
+
marksScope: 'none' | 'addressed' | 'all';
|
|
59
|
+
/** Marks queued for delivery (planned, for a dry run); not delivered. */
|
|
43
60
|
discordMarkersQueued: number;
|
|
61
|
+
/** What became of the marks once the branch was made (absent on a dry
|
|
62
|
+
* run): scheduling only, never Discord acceptance. */
|
|
63
|
+
markers?: SurgeryMarkerReceipt;
|
|
64
|
+
/** The refs that will be marked (empty unless marks were chosen). */
|
|
44
65
|
refs: DiscordAwarenessRef[];
|
|
66
|
+
/** Removed addressable messages the marks choice leaves unmarked (outside
|
|
67
|
+
* its scope); `markers` says whether the chosen ones are scheduled. */
|
|
68
|
+
unmarked: number;
|
|
69
|
+
/** Authorized refs (marks.refs) this recovery does not remove. */
|
|
70
|
+
notRemoved: number;
|
|
71
|
+
/** The awareness ledger the batch is recorded in. */
|
|
45
72
|
outboxPath: string;
|
|
73
|
+
/** Set when a suppression-only batch's resume record couldn't be closed:
|
|
74
|
+
* what of it is left, as the host's surgeries report it. */
|
|
75
|
+
suppressionRecord?: string;
|
|
46
76
|
}
|
|
47
77
|
|
|
48
78
|
/**
|
|
@@ -65,6 +95,10 @@ export async function createOfflineRecoveryBranch(
|
|
|
65
95
|
if (count !== undefined && (!Number.isFinite(count) || count < 1)) {
|
|
66
96
|
throw new Error('messages must be a positive integer');
|
|
67
97
|
}
|
|
98
|
+
// The marks choice is checked as the live methods check it, before the
|
|
99
|
+
// store is opened: a malformed one would otherwise mark every removed
|
|
100
|
+
// message.
|
|
101
|
+
const marks = checkedDiscordAwarenessMarks(options.marks);
|
|
68
102
|
|
|
69
103
|
const store = JsStore.openOrCreate({ path: options.storePath });
|
|
70
104
|
let contextManager: ContextManager | null = null;
|
|
@@ -84,7 +118,7 @@ export async function createOfflineRecoveryBranch(
|
|
|
84
118
|
let target: StoredWindowMessage;
|
|
85
119
|
let targetIndex: number;
|
|
86
120
|
let removedCount: number;
|
|
87
|
-
let
|
|
121
|
+
let carriers: DiscordMessageMetadataCarrier[];
|
|
88
122
|
|
|
89
123
|
if (options.messageId) {
|
|
90
124
|
// Search backward in bounded windows: recovery anchors are normally near
|
|
@@ -101,12 +135,7 @@ export async function createOfflineRecoveryBranch(
|
|
|
101
135
|
target = located.message;
|
|
102
136
|
targetIndex = located.index;
|
|
103
137
|
removedCount = total - located.index - 1;
|
|
104
|
-
|
|
105
|
-
contextManager,
|
|
106
|
-
located.index + 1,
|
|
107
|
-
total,
|
|
108
|
-
options.discordServerId,
|
|
109
|
-
);
|
|
138
|
+
carriers = collectDiscordCarriers(contextManager, located.index + 1, total);
|
|
110
139
|
} else if (options.contextId) {
|
|
111
140
|
const located = findInternalMessageFromTail(contextManager, total, options.contextId.trim());
|
|
112
141
|
if (!located) {
|
|
@@ -115,12 +144,7 @@ export async function createOfflineRecoveryBranch(
|
|
|
115
144
|
target = located.message;
|
|
116
145
|
targetIndex = located.index;
|
|
117
146
|
removedCount = total - located.index - 1;
|
|
118
|
-
|
|
119
|
-
contextManager,
|
|
120
|
-
located.index + 1,
|
|
121
|
-
total,
|
|
122
|
-
options.discordServerId,
|
|
123
|
-
);
|
|
147
|
+
carriers = collectDiscordCarriers(contextManager, located.index + 1, total);
|
|
124
148
|
} else {
|
|
125
149
|
// Count mode remains for compatibility. Read only the target plus suffix,
|
|
126
150
|
// avoiding full-history materialization and attachment blob inflation.
|
|
@@ -135,7 +159,7 @@ export async function createOfflineRecoveryBranch(
|
|
|
135
159
|
target = countTarget;
|
|
136
160
|
targetIndex = total - count! - 1;
|
|
137
161
|
removedCount = count!;
|
|
138
|
-
|
|
162
|
+
carriers = discarded.map(addressingOnly);
|
|
139
163
|
}
|
|
140
164
|
|
|
141
165
|
validateAnchorBoundary(contextManager, targetIndex, total, target);
|
|
@@ -145,7 +169,6 @@ export async function createOfflineRecoveryBranch(
|
|
|
145
169
|
targetIndex,
|
|
146
170
|
options.suppressMessageIds ?? [],
|
|
147
171
|
options.suppressRanges ?? [],
|
|
148
|
-
options.discordServerId,
|
|
149
172
|
);
|
|
150
173
|
validateRemovalIntegrity(
|
|
151
174
|
contextManager,
|
|
@@ -160,7 +183,10 @@ export async function createOfflineRecoveryBranch(
|
|
|
160
183
|
...suppression.intervals,
|
|
161
184
|
],
|
|
162
185
|
);
|
|
163
|
-
|
|
186
|
+
carriers = [...carriers, ...suppression.carriers];
|
|
187
|
+
const addressable = extractDiscordAwarenessRefs(carriers, options.discordServerId).length;
|
|
188
|
+
const selection = selectDiscordAwarenessRefs(carriers, marks, options.discordServerId);
|
|
189
|
+
const refs = selection.refs;
|
|
164
190
|
|
|
165
191
|
const sourceBranch = store.currentBranch().name;
|
|
166
192
|
const held = new Set(store.listBranches().map((branch) => branch.name));
|
|
@@ -169,6 +195,10 @@ export async function createOfflineRecoveryBranch(
|
|
|
169
195
|
if (targetBranch === sourceBranch) {
|
|
170
196
|
throw new Error('Recovery branch name must differ from the active source branch');
|
|
171
197
|
}
|
|
198
|
+
// A known collision is refused before anything is prepared.
|
|
199
|
+
if (store.listBranches().some((branch) => branch.name === targetBranch)) {
|
|
200
|
+
throw new Error(`Recovery branch ${targetBranch} already exists; choose another --branch`);
|
|
201
|
+
}
|
|
172
202
|
const outboxPath = options.outboxPath
|
|
173
203
|
?? defaultDiscordAwarenessOutboxPath(options.storePath);
|
|
174
204
|
|
|
@@ -178,33 +208,44 @@ export async function createOfflineRecoveryBranch(
|
|
|
178
208
|
targetBranch,
|
|
179
209
|
messagesRemoved: removedCount,
|
|
180
210
|
messagesSuppressed: suppression.messageCount,
|
|
211
|
+
discordAddressable: addressable,
|
|
212
|
+
marksScope: marks === 'none' ? 'none' : marks.scope,
|
|
181
213
|
discordMarkersQueued: refs.length,
|
|
182
214
|
refs,
|
|
215
|
+
unmarked: selection.unmarked,
|
|
216
|
+
notRemoved: selection.notRemoved,
|
|
183
217
|
outboxPath,
|
|
184
218
|
};
|
|
185
219
|
if (options.dryRun) return result;
|
|
186
220
|
|
|
187
|
-
|
|
188
|
-
const
|
|
221
|
+
// The host is stopped, so this process is the ledger's only writer.
|
|
222
|
+
const outbox = new DiscordAwarenessOutbox(outboxPath, { store });
|
|
223
|
+
const batch = await outbox.prepare({
|
|
189
224
|
agentName: options.agentName,
|
|
190
225
|
sourceBranch,
|
|
191
226
|
targetBranch,
|
|
192
227
|
refs,
|
|
228
|
+
scope: marks === 'none' ? 'none' : marks.scope,
|
|
229
|
+
unmarked: selection.unmarked,
|
|
230
|
+
notRemoved: selection.notRemoved,
|
|
193
231
|
emoji: options.emoji ?? DEFAULT_DISCORD_AWARENESS_EMOJI,
|
|
194
232
|
// Merely seeing targetBranch active does not prove branch-local
|
|
195
|
-
// suppressions finished
|
|
196
|
-
// batch
|
|
197
|
-
|
|
233
|
+
// suppressions finished: a start on it applies them again while the
|
|
234
|
+
// batch is prepared. Only the recovery may activate it, after every
|
|
235
|
+
// removal below succeeds.
|
|
198
236
|
suppressionIntervals: suppression.intervals.map((interval) => ({
|
|
199
237
|
fromId: String(interval.fromId),
|
|
200
238
|
toId: String(interval.toId),
|
|
201
239
|
})),
|
|
202
240
|
});
|
|
203
241
|
|
|
204
|
-
|
|
205
|
-
// compiled or submitted to Membrane during this operation.
|
|
206
|
-
const createdBranch = contextManager.branchAt(target.id, targetBranch);
|
|
242
|
+
let createdBranch: string | undefined;
|
|
207
243
|
try {
|
|
244
|
+
// branchAt uses the target message's origin sequence. No message
|
|
245
|
+
// content is compiled or submitted to Membrane during this operation.
|
|
246
|
+
// Creating the branch is part of the body: a failure here settles the
|
|
247
|
+
// batch like a failed switch or redaction.
|
|
248
|
+
createdBranch = contextManager.branchAt(target.id, targetBranch);
|
|
208
249
|
await contextManager.switchBranch(createdBranch);
|
|
209
250
|
// Each interval is removed by its messages' ids, so the order doesn't
|
|
210
251
|
// change the result. removeStoredInterval takes a single message outside
|
|
@@ -213,18 +254,109 @@ export async function createOfflineRecoveryBranch(
|
|
|
213
254
|
for (const interval of [...suppression.intervals].reverse()) {
|
|
214
255
|
removeStoredInterval(contextManager, interval.fromId, interval.toId);
|
|
215
256
|
}
|
|
216
|
-
if (batch) outbox.activate(batch.id);
|
|
217
257
|
} catch (error) {
|
|
218
258
|
// A partially suppressed branch is not safe to boot into. Preserve it
|
|
219
|
-
// for diagnosis, but
|
|
220
|
-
//
|
|
221
|
-
|
|
222
|
-
|
|
259
|
+
// for diagnosis, but put Chronicle back on the untouched source branch.
|
|
260
|
+
// Once the source is confirmed, the batch is retired (discard syncs the
|
|
261
|
+
// restored state before it records that): its surgery never happened.
|
|
262
|
+
// If it can't be confirmed, the batch stays prepared as the body's
|
|
263
|
+
// record: a start on its target branch finishes a suppression's
|
|
264
|
+
// removals, since its body is in effect there, and a start retires the
|
|
265
|
+
// batch, none of its marks sent. The error says which, and names the
|
|
266
|
+
// branch.
|
|
267
|
+
const failure = error instanceof Error ? error.message : String(error);
|
|
268
|
+
let restore = '';
|
|
269
|
+
try {
|
|
270
|
+
if (store.currentBranch().name !== sourceBranch) await contextManager.switchBranch(sourceBranch);
|
|
271
|
+
} catch (restoreError) {
|
|
272
|
+
restore = `; restoring ${sourceBranch} failed: ${boundDiscordAwarenessText(
|
|
273
|
+
restoreError instanceof Error ? restoreError.message : String(restoreError),
|
|
274
|
+
)}`;
|
|
275
|
+
}
|
|
276
|
+
const sourceConfirmed = store.currentBranch().name === sourceBranch;
|
|
277
|
+
let retire = '';
|
|
278
|
+
// A batch with marks never sends them once kept: the recovery, run
|
|
279
|
+
// again, chooses them again.
|
|
280
|
+
const redo = batch && batch.refs.length > 0
|
|
281
|
+
? '. To apply its marks, run the recovery again onto a new --branch, choosing them again' : '';
|
|
282
|
+
if (batch) {
|
|
283
|
+
if (!sourceConfirmed) {
|
|
284
|
+
// The recovery starts from the store's current branch, which this
|
|
285
|
+
// one couldn't put back: a run from there would start from the fork.
|
|
286
|
+
const redoFromSource = batch.refs.length > 0
|
|
287
|
+
? `. To apply its marks, redo it from ${sourceBranch}, choosing them again: the recovery starts from the ` +
|
|
288
|
+
`store's current branch, which isn't ${sourceBranch} now, so start the host, switch to ${sourceBranch} ` +
|
|
289
|
+
'through the host API\'s branch.switch, stop the host, and run the recovery again onto a new --branch'
|
|
290
|
+
: '';
|
|
291
|
+
retire = `; awareness batch ${batch.id} kept, since the source branch could not be confirmed: ` +
|
|
292
|
+
(suppression.intervals.length > 0
|
|
293
|
+
? `a start on ${targetBranch} finishes its removals and retires it`
|
|
294
|
+
: 'the next start retires it') + `, none of its marks sent${redoFromSource}`;
|
|
295
|
+
} else {
|
|
296
|
+
try {
|
|
297
|
+
await outbox.discard(batch.id);
|
|
298
|
+
retire = `; awareness batch ${batch.id} retired, none of its marks sent${redo}`;
|
|
299
|
+
} catch (discardError) {
|
|
300
|
+
// None of its marks is ever sent: a start retires it. A write that
|
|
301
|
+
// fails leaves the batch as it was, prepared, as in the host's
|
|
302
|
+
// retireAfterRestore. A suppression's removals stay pending for
|
|
303
|
+
// its target branch: a start on a branch with that name finishes
|
|
304
|
+
// them.
|
|
305
|
+
const detail = boundDiscordAwarenessText(
|
|
306
|
+
discardError instanceof Error ? discardError.message : String(discardError),
|
|
307
|
+
);
|
|
308
|
+
const kept = `; awareness batch ${batch.id} could not be retired (${detail}); it stays prepared, none of ` +
|
|
309
|
+
'its marks to be sent';
|
|
310
|
+
if (suppression.intervals.length === 0) {
|
|
311
|
+
retire = `${kept}, until the next start retires it${redo}`;
|
|
312
|
+
} else {
|
|
313
|
+
let forked = true;
|
|
314
|
+
try {
|
|
315
|
+
forked = store.listBranches().some((branch) => branch.name === targetBranch);
|
|
316
|
+
} catch { /* unknown: say what a start on an existing branch would do */ }
|
|
317
|
+
retire = `${kept}. Its removals stay pending for ${targetBranch}: a start on a branch with that ` +
|
|
318
|
+
'name would finish them there and retire the batch' + (forked
|
|
319
|
+
? `, and ${targetBranch} is kept, so delete it unless you want that, through the host API's ` +
|
|
320
|
+
'branch.delete once the host runs'
|
|
321
|
+
: '') + redo;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
223
325
|
}
|
|
224
|
-
|
|
326
|
+
// A partly suppressed branch is unsafe to start on, unless its batch was
|
|
327
|
+
// kept because the source couldn't be confirmed: a start on it then
|
|
328
|
+
// completes the suppression, as the batch's note says.
|
|
329
|
+
const kept = createdBranch === undefined ? ''
|
|
330
|
+
: suppression.intervals.length === 0 ? `; branch ${createdBranch} is kept for diagnosis`
|
|
331
|
+
: batch && !sourceConfirmed ? `; branch ${createdBranch} is kept: it may be partly suppressed`
|
|
332
|
+
: `; branch ${createdBranch} is kept for diagnosis: it may be partly suppressed, so don't start on it, and ` +
|
|
333
|
+
'delete it once it has been looked at (the host API\'s branch.delete, once the host runs)';
|
|
334
|
+
if (!restore && !retire && !kept) throw error;
|
|
335
|
+
throw new Error(`${failure}${restore}${kept}${retire}`, { cause: error });
|
|
225
336
|
}
|
|
226
337
|
|
|
227
|
-
|
|
338
|
+
// The body change landed. Marker bookkeeping is reported apart from it
|
|
339
|
+
// and never undoes it.
|
|
340
|
+
const facts = {
|
|
341
|
+
scope: (marks === 'none' ? 'none' : marks.scope) as SurgeryMarkerReceipt['scope'],
|
|
342
|
+
unmarked: selection.unmarked,
|
|
343
|
+
notRemoved: selection.notRemoved,
|
|
344
|
+
};
|
|
345
|
+
let markers: SurgeryMarkerReceipt = { ...facts, status: 'none', queued: 0 };
|
|
346
|
+
let suppressionRecord: string | undefined;
|
|
347
|
+
if (batch) {
|
|
348
|
+
const settled = await outbox.settleActivation(batch.id);
|
|
349
|
+
if (batch.refs.length > 0) {
|
|
350
|
+
markers = settled.status === 'queued'
|
|
351
|
+
? { ...facts, status: 'queued', queued: settled.queued, batchId: batch.id }
|
|
352
|
+
: { ...facts, status: 'not-scheduled', queued: 0, error: settled.error };
|
|
353
|
+
} else if (settled.status === 'not-scheduled') {
|
|
354
|
+
// A suppression-only batch carries no marks, and its activation
|
|
355
|
+
// closes the body's resume record: if that failed, say what became of it.
|
|
356
|
+
suppressionRecord = `the resume record (awareness batch ${batch.id}, no marks) could not be closed: ${settled.error}`;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return { ...result, discordMarkersQueued: markers.queued, markers, ...(suppressionRecord ? { suppressionRecord } : {}) };
|
|
228
360
|
} finally {
|
|
229
361
|
contextManager?.close();
|
|
230
362
|
store.close();
|
|
@@ -372,8 +504,7 @@ function buildSuppressionPlan(
|
|
|
372
504
|
targetIndex: number,
|
|
373
505
|
rawMessageIds: string[],
|
|
374
506
|
rawRanges: Array<{ fromMessageId: string; toMessageId: string }>,
|
|
375
|
-
|
|
376
|
-
): { intervals: SuppressionInterval[]; refs: DiscordAwarenessRef[]; messageCount: number } {
|
|
507
|
+
): { intervals: SuppressionInterval[]; carriers: DiscordMessageMetadataCarrier[]; messageCount: number } {
|
|
377
508
|
const messageIds = rawMessageIds.map(normalizeDiscordMessageId);
|
|
378
509
|
const ranges = rawRanges.map((range) => ({
|
|
379
510
|
fromMessageId: normalizeDiscordMessageId(range.fromMessageId),
|
|
@@ -383,7 +514,7 @@ function buildSuppressionPlan(
|
|
|
383
514
|
...messageIds,
|
|
384
515
|
...ranges.flatMap((range) => [range.fromMessageId, range.toMessageId]),
|
|
385
516
|
]);
|
|
386
|
-
if (wanted.size === 0) return { intervals: [],
|
|
517
|
+
if (wanted.size === 0) return { intervals: [], carriers: [], messageCount: 0 };
|
|
387
518
|
|
|
388
519
|
const locations = new Map<string, MessageLocation>();
|
|
389
520
|
for (let offset = 0; offset <= targetIndex; offset += RECOVERY_SCAN_WINDOW) {
|
|
@@ -473,45 +604,48 @@ function buildSuppressionPlan(
|
|
|
473
604
|
}
|
|
474
605
|
}
|
|
475
606
|
|
|
476
|
-
const
|
|
607
|
+
const carriers: DiscordMessageMetadataCarrier[] = [];
|
|
477
608
|
for (const interval of merged) {
|
|
478
|
-
|
|
479
|
-
contextManager,
|
|
480
|
-
interval.start,
|
|
481
|
-
interval.end + 1,
|
|
482
|
-
forcedServerId,
|
|
483
|
-
));
|
|
609
|
+
carriers.push(...collectDiscordCarriers(contextManager, interval.start, interval.end + 1));
|
|
484
610
|
}
|
|
485
611
|
return {
|
|
486
612
|
intervals: merged,
|
|
487
|
-
|
|
613
|
+
carriers,
|
|
488
614
|
messageCount: merged.reduce((sum, interval) => sum + interval.end - interval.start + 1, 0),
|
|
489
615
|
};
|
|
490
616
|
}
|
|
491
617
|
|
|
492
|
-
|
|
618
|
+
/**
|
|
619
|
+
* The addressing metadata of removed messages, read in bounded windows: only
|
|
620
|
+
* the Discord address and the MCPL tags (for `addressed` selection) are kept,
|
|
621
|
+
* never content. A sharded message may repeat its metadata across adjacent
|
|
622
|
+
* records; selection dedupes by address.
|
|
623
|
+
*/
|
|
624
|
+
function collectDiscordCarriers(
|
|
493
625
|
contextManager: ContextManager,
|
|
494
626
|
start: number,
|
|
495
627
|
end: number,
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
const refs: DiscordAwarenessRef[] = [];
|
|
628
|
+
): DiscordMessageMetadataCarrier[] {
|
|
629
|
+
const carriers: DiscordMessageMetadataCarrier[] = [];
|
|
499
630
|
for (let offset = start; offset < end; offset += RECOVERY_SCAN_WINDOW) {
|
|
500
631
|
const messages = contextManager.getMessageWindow(
|
|
501
632
|
offset,
|
|
502
633
|
Math.min(RECOVERY_SCAN_WINDOW, end - offset),
|
|
503
634
|
{ resolveBlobs: false },
|
|
504
635
|
).messages;
|
|
505
|
-
|
|
636
|
+
carriers.push(...messages.map(addressingOnly));
|
|
506
637
|
}
|
|
507
|
-
|
|
508
|
-
return dedupeDiscordRefs(refs);
|
|
638
|
+
return carriers;
|
|
509
639
|
}
|
|
510
640
|
|
|
511
|
-
function
|
|
512
|
-
const
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
641
|
+
function addressingOnly(message: { metadata?: Record<string, unknown> }): DiscordMessageMetadataCarrier {
|
|
642
|
+
const metadata = message.metadata ?? {};
|
|
643
|
+
return {
|
|
644
|
+
metadata: {
|
|
645
|
+
serverId: metadata.serverId,
|
|
646
|
+
channelId: metadata.channelId,
|
|
647
|
+
messageId: metadata.messageId,
|
|
648
|
+
tags: metadata.tags,
|
|
649
|
+
},
|
|
650
|
+
};
|
|
517
651
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { createOfflineRecoveryBranch } from './offline-branch.js';
|
|
4
|
+
import type { DiscordAwarenessMarks } from './discord-awareness-outbox.js';
|
|
4
5
|
|
|
5
6
|
interface CliOptions {
|
|
6
7
|
storePath?: string;
|
|
@@ -15,6 +16,7 @@ interface CliOptions {
|
|
|
15
16
|
discordServerId?: string;
|
|
16
17
|
outboxPath?: string;
|
|
17
18
|
emoji?: string;
|
|
19
|
+
marks: 'none' | 'addressed' | 'all';
|
|
18
20
|
dryRun: boolean;
|
|
19
21
|
}
|
|
20
22
|
|
|
@@ -22,9 +24,12 @@ function usage(): string {
|
|
|
22
24
|
return `Usage:
|
|
23
25
|
agent-framework-recover --store <path> --agent <name> <anchor> [options]
|
|
24
26
|
|
|
25
|
-
Creates and activates a Chronicle recovery branch while the normal host is
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
Creates and activates a Chronicle recovery branch while the normal host is
|
|
28
|
+
down. The recovery is local to the resident: by default no Discord reaction is
|
|
29
|
+
placed. --marks chooses to mark removed Discord messages with an awareness
|
|
30
|
+
reaction (queued here, delivered when the host and its Discord MCPL server
|
|
31
|
+
next connect). Message content is never compiled or written to the awareness
|
|
32
|
+
ledger.
|
|
28
33
|
|
|
29
34
|
Options:
|
|
30
35
|
--message-id <id|link> Last Discord message the agent should still see
|
|
@@ -35,6 +40,10 @@ Options:
|
|
|
35
40
|
--branch <name> Recovery branch name (generated by default)
|
|
36
41
|
--namespace <namespace> Context namespace (default: agents/<agent>)
|
|
37
42
|
--discord-server <id> Override/inject the Discord MCPL server id
|
|
43
|
+
--marks <none|addressed|all>
|
|
44
|
+
Awareness marks on removed Discord messages
|
|
45
|
+
(default none; addressed = mentions, replies to
|
|
46
|
+
the bot and DMs; all = every addressable message)
|
|
38
47
|
--outbox <path> Override the branch-independent outbox path
|
|
39
48
|
--emoji <emoji> Marker reaction (default: 💤)
|
|
40
49
|
--dry-run Inspect counts/addresses without writing
|
|
@@ -44,7 +53,7 @@ The agent host must be stopped before running this command.`;
|
|
|
44
53
|
}
|
|
45
54
|
|
|
46
55
|
function parseArgs(args: string[]): CliOptions {
|
|
47
|
-
const options: CliOptions = { dryRun: false, suppressMessageIds: [], suppressRanges: [] };
|
|
56
|
+
const options: CliOptions = { dryRun: false, suppressMessageIds: [], suppressRanges: [], marks: 'none' };
|
|
48
57
|
for (let i = 0; i < args.length; i++) {
|
|
49
58
|
const arg = args[i];
|
|
50
59
|
const value = () => {
|
|
@@ -80,6 +89,14 @@ function parseArgs(args: string[]): CliOptions {
|
|
|
80
89
|
case '--discord-server': options.discordServerId = value(); break;
|
|
81
90
|
case '--outbox': options.outboxPath = value(); break;
|
|
82
91
|
case '--emoji': options.emoji = value(); break;
|
|
92
|
+
case '--marks': {
|
|
93
|
+
const marks = value();
|
|
94
|
+
if (marks !== 'none' && marks !== 'addressed' && marks !== 'all') {
|
|
95
|
+
throw new Error('--marks must be none, addressed or all');
|
|
96
|
+
}
|
|
97
|
+
options.marks = marks;
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
83
100
|
case '--dry-run': options.dryRun = true; break;
|
|
84
101
|
case '--help':
|
|
85
102
|
case '-h':
|
|
@@ -101,6 +118,9 @@ async function main(): Promise<void> {
|
|
|
101
118
|
throw new Error(`--store, --agent, and one anchor (--message-id, --context-id, or --messages) are required\n\n${usage()}`);
|
|
102
119
|
}
|
|
103
120
|
|
|
121
|
+
// A CLI choice and its application are the same moment (the host is
|
|
122
|
+
// stopped), so the scope is evaluated now rather than frozen to refs.
|
|
123
|
+
const marks: DiscordAwarenessMarks = options.marks === 'none' ? 'none' : { scope: options.marks };
|
|
104
124
|
const result = await createOfflineRecoveryBranch({
|
|
105
125
|
storePath: options.storePath,
|
|
106
126
|
agentName: options.agentName,
|
|
@@ -114,6 +134,7 @@ async function main(): Promise<void> {
|
|
|
114
134
|
discordServerId: options.discordServerId,
|
|
115
135
|
outboxPath: options.outboxPath,
|
|
116
136
|
emoji: options.emoji,
|
|
137
|
+
marks,
|
|
117
138
|
dryRun: options.dryRun,
|
|
118
139
|
});
|
|
119
140
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tools whose call delivers the resident's message to a channel or person,
|
|
3
|
+
* by bare name (an MCPL server's `<prefix>--` stripped). The framework reads
|
|
4
|
+
* a call of one as an explicit send that silences the round's auto-routed
|
|
5
|
+
* prose; the history module's semantic index reads its message argument as
|
|
6
|
+
* what the resident wrote to someone, whether or not it was delivered.
|
|
7
|
+
*/
|
|
8
|
+
export const SEND_TOOLS = new Set(['channel_publish', 'send_message', 'reply_message', 'send_dm']);
|
|
9
|
+
|
|
10
|
+
/** Strip the `server--` MCPL prefix from a tool name. */
|
|
11
|
+
export const bareToolName = (n: string): string => n.split('--').pop()!;
|
package/src/timezone.ts
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
* Agent-visible wall-clock formatting.
|
|
3
3
|
*
|
|
4
4
|
* Persistence and protocol timestamps should remain epoch milliseconds or UTC
|
|
5
|
-
* ISO strings. These helpers are
|
|
6
|
-
* timestamp is rendered into an agent's context or a tool result
|
|
5
|
+
* ISO strings. These helpers are for presentation boundaries where a
|
|
6
|
+
* timestamp is rendered into an agent's context or a tool result, and the
|
|
7
|
+
* zone resolveTimeZone gives also places clock rows' boundaries
|
|
8
|
+
* (clock-rows.ts).
|
|
7
9
|
*/
|
|
8
10
|
|
|
9
11
|
export function isValidTimeZone(timeZone: string): boolean {
|
package/src/tool-image-ledger.ts
CHANGED
|
@@ -316,7 +316,8 @@ export interface StoredCut {
|
|
|
316
316
|
head: string;
|
|
317
317
|
/** The image slots past the cut that the stored copy re-appends after its
|
|
318
318
|
* truncation notice, in text order: the newest MAX_KEPT_IMAGE_SLOTS of
|
|
319
|
-
* those the live copy showed
|
|
319
|
+
* those the live copy showed, or under a readers' bound the newest of
|
|
320
|
+
* those that fit it, down to none (cutStoredToolResultToFit). */
|
|
320
321
|
kept: string[];
|
|
321
322
|
/** How many image slots past the cut the live copy showed. */
|
|
322
323
|
fell: number;
|
|
@@ -326,8 +327,10 @@ export interface StoredCut {
|
|
|
326
327
|
* Where a tool result's text longer than `cap` is cut: by its stored copy
|
|
327
328
|
* (cutStoredToolResult), by history's own truncation (truncateForHistory),
|
|
328
329
|
* and by its live copy, for each text block beside an image
|
|
329
|
-
* (tryNativeToolResultContent, framework.ts), so the stored copy
|
|
330
|
-
* copy cut alike.
|
|
330
|
+
* (tryNativeToolResultContent, framework.ts), so at one cap the stored copy
|
|
331
|
+
* and the live copy cut alike. Under a readers' bound the stored copy's cut
|
|
332
|
+
* can come earlier (cutStoredToolResultToFit). The text's length when it
|
|
333
|
+
* isn't longer than `cap`.
|
|
331
334
|
*
|
|
332
335
|
* The cut is made at the cap, or before it when the cap would split a
|
|
333
336
|
* surrogate pair (safeSlice's rule), and that is where the moves back are
|
|
@@ -395,9 +398,13 @@ export function cutStoredToolResult(text: string, cap: number, shown: ShownRange
|
|
|
395
398
|
/** What a stored copy re-appends after its truncation notice: the kept
|
|
396
399
|
* slots, each on its own line so a reference stub stays parseable, under a
|
|
397
400
|
* note saying how many fell past the cut, and how many of those were left
|
|
398
|
-
* out and aren't counted
|
|
401
|
+
* out and aren't counted, all of them where none fit the bound
|
|
402
|
+
* (cutStoredToolResultToFit). Empty when none fell past the cut. */
|
|
399
403
|
export function formatKeptImageSlots(cut: StoredCut): string {
|
|
400
404
|
if (cut.fell === 0) return '';
|
|
405
|
+
if (cut.kept.length === 0) {
|
|
406
|
+
return `\n[${cut.fell} image slot(s) fell past the cut and are left out, and save_recent_image doesn't count them.]`;
|
|
407
|
+
}
|
|
401
408
|
const leftOut = cut.fell - cut.kept.length;
|
|
402
409
|
const note = leftOut === 0
|
|
403
410
|
? `${cut.fell} image slot(s) fell past the cut; kept here in order so save_recent_image still counts them:`
|
|
@@ -406,14 +413,61 @@ export function formatKeptImageSlots(cut: StoredCut): string {
|
|
|
406
413
|
return `\n[${note}]\n${cut.kept.join('\n')}`;
|
|
407
414
|
}
|
|
408
415
|
|
|
416
|
+
/** The most a stored copy's truncation notice takes (spillOrTruncate,
|
|
417
|
+
* framework.ts): the spill notice names its file twice, once in its paging
|
|
418
|
+
* advice, and fits this on a path from a mount name of up to about seventy
|
|
419
|
+
* characters. */
|
|
420
|
+
export const TOOL_RESULT_NOTICE_ROOM = 640;
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* A result's cap and bound when its message holds `n` results. A reader's
|
|
424
|
+
* strategy spends its per-message bound across the message's blocks in
|
|
425
|
+
* order, so each result takes an equal share of the readers' smallest
|
|
426
|
+
* bound, fixed before any result is cut, since its siblings may still be
|
|
427
|
+
* running when save_recent_image counts it, and its cap is at most that
|
|
428
|
+
* share. `keptCap` is where a withheld stub's kept fields are cut, leaving
|
|
429
|
+
* the stub's notice its room (tool-result-guard.ts). With no bound, each
|
|
430
|
+
* keeps the cap.
|
|
431
|
+
*/
|
|
432
|
+
export function toolResultShare(cap: number, bound: number | undefined, n: number): { cap: number; bound: number | undefined; keptCap: number } {
|
|
433
|
+
if (bound === undefined) return { cap, bound, keptCap: cap };
|
|
434
|
+
const share = Math.floor(bound / Math.max(1, n));
|
|
435
|
+
return { cap: Math.min(cap, share), bound: share, keptCap: Math.min(cap, Math.max(1, share - TOOL_RESULT_NOTICE_ROOM)) };
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Cut a tool result's text at `cap` as cutStoredToolResult does, unless its
|
|
440
|
+
* stored copy would then pass `bound`, the most a reader's strategy keeps of
|
|
441
|
+
* it (toolResultShare): the head, room for the notice after it, and the kept
|
|
442
|
+
* slots after that (formatKeptImageSlots). A strategy cuts a longer message
|
|
443
|
+
* from its end, and with it the notice that holds the way to the rest, so
|
|
444
|
+
* there the head moves back until the copy fits. The kept slots grow as the
|
|
445
|
+
* head moves back, so the cut is found again until head and slots fit or the
|
|
446
|
+
* head is empty. Slots that alone pass the room keep the newest that fit,
|
|
447
|
+
* the rest left out and uncounted, as past MAX_KEPT_IMAGE_SLOTS. A bound
|
|
448
|
+
* under the notice's room can't hold the notice. With no bound, or where the
|
|
449
|
+
* cut at the cap already fits, the cut is cutStoredToolResult's.
|
|
450
|
+
*/
|
|
451
|
+
export function cutStoredToolResultToFit(text: string, cap: number, shown: ShownRanges | null, bound: number | undefined): StoredCut {
|
|
452
|
+
let cut = cutStoredToolResult(text, cap, shown);
|
|
453
|
+
if (bound === undefined || text.length <= cap) return cut;
|
|
454
|
+
const room = bound - TOOL_RESULT_NOTICE_ROOM;
|
|
455
|
+
while (cut.head.length > 0 && cut.head.length + formatKeptImageSlots(cut).length > room) {
|
|
456
|
+
cut = cutStoredToolResult(text, Math.max(0, room - formatKeptImageSlots(cut).length), shown);
|
|
457
|
+
}
|
|
458
|
+
while (cut.kept.length > 0 && formatKeptImageSlots(cut).length > room) cut = { ...cut, kept: cut.kept.slice(1) };
|
|
459
|
+
return cut;
|
|
460
|
+
}
|
|
461
|
+
|
|
409
462
|
/**
|
|
410
463
|
* The image slots a tool result's stored copy holds: its text's, or, when the
|
|
411
464
|
* text is longer than `cap`, those the cut keeps, in head and after the
|
|
412
|
-
* notice (
|
|
413
|
-
* result before its stored copy is
|
|
465
|
+
* notice (cutStoredToolResultToFit, under the readers' `bound`). The notice
|
|
466
|
+
* itself holds none. For counting a result before its stored copy is
|
|
467
|
+
* written, as it will count after.
|
|
414
468
|
*/
|
|
415
|
-
export function storedImageSlots(text: string, cap: number, shown: ShownRanges | null): ParsedImagePlaceholder[] {
|
|
416
|
-
const cut =
|
|
469
|
+
export function storedImageSlots(text: string, cap: number, shown: ShownRanges | null, bound: number | undefined): ParsedImagePlaceholder[] {
|
|
470
|
+
const cut = cutStoredToolResultToFit(text, cap, shown, bound);
|
|
417
471
|
return parseImagePlaceholders(cut.head + formatKeptImageSlots(cut));
|
|
418
472
|
}
|
|
419
473
|
|