@animalabs/agent-framework 0.31.0 → 0.33.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 +204 -14
- 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 +185 -78
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +279 -102
- 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/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 +67 -0
- package/dist/src/context-options.d.ts.map +1 -0
- package/dist/src/context-options.js +85 -0
- package/dist/src/context-options.js.map +1 -0
- 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 +179 -10
- 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/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 +1977 -820
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +7571 -3457
- package/dist/src/framework.js.map +1 -1
- package/dist/src/gate/event-gate.d.ts +85 -15
- package/dist/src/gate/event-gate.d.ts.map +1 -1
- package/dist/src/gate/event-gate.js +421 -47
- 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/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 +135 -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/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 +5 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -0
- package/dist/src/index.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 +131 -15
- package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
- package/dist/src/mcpl/channel-registry.js +290 -46
- 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/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 +13 -10
- package/dist/src/mcpl/server-connection.d.ts.map +1 -1
- package/dist/src/mcpl/server-connection.js +134 -56
- 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/types.d.ts +8 -2
- 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 +237 -86
- package/dist/src/modules/discord/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 +15 -22
- 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 +76 -22
- package/dist/src/modules/workspace/index.js.map +1 -1
- package/dist/src/notice-tags.d.ts +10 -1
- package/dist/src/notice-tags.d.ts.map +1 -1
- package/dist/src/notice-tags.js +10 -1
- 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 +746 -0
- package/dist/src/presentation.d.ts.map +1 -0
- package/dist/src/presentation.js +1435 -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/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 +273 -29
- package/dist/src/recovery/offline-branch.js.map +1 -1
- package/dist/src/recovery/recover-cli.js +22 -3
- 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/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/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 +1159 -79
- package/dist/src/tune-out/coordinator.d.ts.map +1 -1
- package/dist/src/tune-out/coordinator.js +3709 -363
- 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 +87 -27
- 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 +86 -31
- 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 +43 -21
- package/dist/src/types/framework.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 +510 -36
- package/dist/src/types/trace.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/admission/admit.ts +32 -3
- package/src/agent.ts +323 -120
- package/src/api/mcp-server-core.ts +54 -17
- package/src/api/server.ts +4 -23
- 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 +96 -0
- 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 +209 -12
- package/src/dendrite/types.ts +92 -8
- package/src/foreign-rounds.ts +199 -0
- package/src/framework.ts +7814 -3585
- package/src/gate/event-gate.ts +428 -65
- package/src/gate/index.ts +3 -0
- 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 +141 -7
- package/src/gate/wake-onboarding.ts +309 -0
- package/src/held-writes.ts +49 -60
- package/src/index.ts +9 -2
- package/src/jsonrpc-envelope.ts +81 -0
- package/src/mcpl/channel-registry.ts +353 -85
- 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/push-coalescer.ts +193 -34
- package/src/mcpl/push-handler.ts +10 -5
- package/src/mcpl/server-connection.ts +141 -60
- package/src/mcpl/tool-classes.ts +3 -0
- package/src/mcpl/types.ts +8 -2
- package/src/module-registry.ts +73 -17
- package/src/modules/discord/index.ts +229 -81
- package/src/modules/history/index.ts +439 -70
- package/src/modules/history/semantic.ts +15 -21
- package/src/modules/workspace/disk-agreement.ts +1 -1
- package/src/modules/workspace/index.ts +85 -23
- package/src/notice-tags.ts +10 -1
- package/src/operator-log.ts +36 -3
- package/src/presentation.ts +1676 -0
- package/src/prose-drafts.ts +16 -12
- package/src/read-marks.ts +328 -0
- package/src/recovery/offline-branch.ts +346 -32
- package/src/recovery/recover-cli.ts +21 -3
- package/src/send-tools.ts +40 -7
- package/src/stored-injections.ts +2 -2
- package/src/stored-removal.ts +31 -7
- package/src/tool-result-guard.ts +41 -7
- package/src/tune-out/coordinator.ts +4089 -457
- package/src/tune-out/tools.ts +160 -43
- package/src/tune-out/window-anchor.ts +277 -0
- package/src/types/agent.ts +90 -31
- package/src/types/events.ts +2 -0
- package/src/types/framework.ts +43 -21
- package/src/types/module.ts +181 -68
- package/src/types/trace.ts +542 -40
- package/dist/src/reasoning-loss-notice.d.ts +0 -23
- package/dist/src/reasoning-loss-notice.d.ts.map +0 -1
- package/dist/src/reasoning-loss-notice.js +0 -52
- package/dist/src/reasoning-loss-notice.js.map +0 -1
- package/dist/src/reasoning-loss.d.ts +0 -24
- package/dist/src/reasoning-loss.d.ts.map +0 -1
- package/dist/src/reasoning-loss.js +0 -102
- package/dist/src/reasoning-loss.js.map +0 -1
- package/src/reasoning-loss-notice.ts +0 -64
- package/src/reasoning-loss.ts +0 -120
|
@@ -32,6 +32,35 @@
|
|
|
32
32
|
* Kept deliberately read-only, same posture as HealthModule: no side
|
|
33
33
|
* effects, no message mutation. `onProcess` is a no-op.
|
|
34
34
|
*
|
|
35
|
+
* The tools read the record, not an agent's window (#291): an agent's
|
|
36
|
+
* window is the rows its requests were given, which once agents share a
|
|
37
|
+
* slot is not the whole record. Where the framework offers the window
|
|
38
|
+
* lookup (ModuleContext.windowStatusOf), each entry `extract`, `search` and
|
|
39
|
+
* `semantic_search` return for a row the caller isn't shown says so
|
|
40
|
+
* (`window: "not-shown" | "undone"`), relative to the agent whose call it
|
|
41
|
+
* is, or to the delivery agent for a call no agent made. A row the caller
|
|
42
|
+
* is shown is one its window holds and its view keeps
|
|
43
|
+
* (ModuleContext.viewKeeps): a window written down from the shared record
|
|
44
|
+
* can hold another agent's stored context injection or a held row, which
|
|
45
|
+
* its requests never carry. For an agent's call, another agent's own rows
|
|
46
|
+
* (its rounds, its notes, its stored injections) that it isn't shown are
|
|
47
|
+
* left out unless it observes that agent (decision 1), counted as
|
|
48
|
+
* `leftOut`; without an author filter extract's `offset` counts them, so a
|
|
49
|
+
* caller pages on by `returned + leftOut`, and with one it counts only the
|
|
50
|
+
* rows the filter keeps, which a caller pages on by `resume`. A row stored before the audience stamp, whose owner
|
|
51
|
+
* isn't recorded, is nobody's own and isn't left out: it carries `window`
|
|
52
|
+
* where the caller isn't shown it. The summaries `overview` and `semantic_search` read fold the
|
|
53
|
+
* bound context's agent's window (ModuleContext.agentNameOf says whose), so
|
|
54
|
+
* for another agent's call that doesn't observe that agent they are left
|
|
55
|
+
* out too: `overview` keeps each summarized span's time, level and counts
|
|
56
|
+
* and leaves out its text (`content: null`, `contentLeftOut: true`,
|
|
57
|
+
* counted as `summariesLeftOut`), and `semantic_search` leaves out summary
|
|
58
|
+
* hits, counted in `index.leftOut`. That is for now; serving such a caller
|
|
59
|
+
* its own context's summaries is later work. An operator's call stays
|
|
60
|
+
* record-wide, and so does the bound agent's own. `stats` stays an
|
|
61
|
+
* aggregate over the record, unmarked, as do overview's counts. Without
|
|
62
|
+
* the lookup every tool and every answer is as it was before the index.
|
|
63
|
+
*
|
|
35
64
|
* Every query dispatch collapses onto `queryMessagesByTimeAndChannel` — its
|
|
36
65
|
* own implementation already handles "only a time range", "only a channel",
|
|
37
66
|
* or "neither" by delegating to the single-filter native queries (see
|
|
@@ -42,16 +71,19 @@
|
|
|
42
71
|
import { Worker } from 'node:worker_threads';
|
|
43
72
|
import { fileURLToPath } from 'node:url';
|
|
44
73
|
import { dirname, join } from 'node:path';
|
|
45
|
-
import type { ContextManager, StoredMessage, ChannelCount, ChannelTokenStats, TimeRangeSummaryEntry } from '@animalabs/context-manager';
|
|
74
|
+
import type { ContextManager, StoredMessage, ChannelCount, ChannelTokenStats, TimeRangeSummaryEntry, WindowStatus } from '@animalabs/context-manager';
|
|
46
75
|
import type { ContentBlock } from '@animalabs/membrane';
|
|
47
76
|
import type { Module, ModuleContext } from '../../types/module.js';
|
|
48
77
|
import type { ToolDefinition, ToolCall, ToolResult, ProcessEvent } from '../../types/events.js';
|
|
49
78
|
import type { EventResponse, ProcessState } from '../../types/module.js';
|
|
50
79
|
import type { SearchWorkerMessage, SearchWorkerMatch } from './search-regex-worker.js';
|
|
51
80
|
import type { ChannelRegistry } from '../../mcpl/channel-registry.js';
|
|
52
|
-
import { EmbedServiceError, SemanticIndexClient, SemanticIndexer, messageIndexText, parseSummaryKey, summaryIndexText, type SearchResponse, type SemanticIndexConfig, type SyncReport, type PendingChanges, type SyncCursors } from './semantic.js';
|
|
81
|
+
import { ERROR_MARK, EmbedServiceError, SemanticIndexClient, SemanticIndexer, messageIndexText, parseSummaryKey, summaryIndexText, type SearchResponse, type SemanticIndexConfig, type SyncReport, type PendingChanges, type SyncCursors } from './semantic.js';
|
|
53
82
|
import { unknownToolMessage } from '../unknown-tool.js';
|
|
83
|
+
import { speechOf } from '../../send-tools.js';
|
|
54
84
|
import { safeSlice } from '../../safe-slice.js';
|
|
85
|
+
import { audienceOf } from '../../dendrite/audience.js';
|
|
86
|
+
import { storedInjectionMarkOf } from '../../stored-injections.js';
|
|
55
87
|
|
|
56
88
|
// ============================================================================
|
|
57
89
|
// Tool input shapes
|
|
@@ -672,6 +704,16 @@ export class HistoryModule implements Module {
|
|
|
672
704
|
}
|
|
673
705
|
|
|
674
706
|
getTools(): ToolDefinition[] {
|
|
707
|
+
// With the framework's window lookup (#291), entries say which rows the
|
|
708
|
+
// caller isn't being shown, and another agent's own rows are left out
|
|
709
|
+
// for an agent that doesn't observe it: one sentence more in each
|
|
710
|
+
// description that returns rows, and in overview's, whose summaries'
|
|
711
|
+
// text is left out the same way. Without it, the tools are as they were.
|
|
712
|
+
const windowed = !!this.ctx?.windowStatusOf;
|
|
713
|
+
const windowNote = windowed ? WINDOW_NOTE : '';
|
|
714
|
+
// extract's offset counts the rows left out for the caller, as the page's
|
|
715
|
+
// place in the window does.
|
|
716
|
+
const extractWindowNote = windowed ? WINDOW_NOTE + EXTRACT_OFFSET_NOTE : '';
|
|
675
717
|
return [
|
|
676
718
|
{
|
|
677
719
|
name: 'stats',
|
|
@@ -695,7 +737,9 @@ export class HistoryModule implements Module {
|
|
|
695
737
|
description:
|
|
696
738
|
'Fetch raw, uncompressed messages for a time range and/or channel, paginated oldest-first. ' +
|
|
697
739
|
'Use after `stats` to pull the actual content. format:"text" flattens each message to a short ' +
|
|
698
|
-
'readable string (
|
|
740
|
+
'readable string (a call of a tool the framework names for sending, editing or saying a message, such as ' +
|
|
741
|
+
'send_message, as its name and its words when it carries words, such as "[telegram--send_message] …", its name ending ", error" when its result, stored after it, is flagged as an error; ' +
|
|
742
|
+
'other tool_use/tool_result/thinking/images as bracketed labels, a tool_result flagged as an error ending ", error"); ' +
|
|
699
743
|
'format:"raw" returns the content blocks unmodified. ' +
|
|
700
744
|
'Two extra modes: (1) aroundId — pass a message id (e.g. from `search`) to get the conversation ' +
|
|
701
745
|
'around it: `before` messages before it, the message itself (anchor:true), and `after` messages ' +
|
|
@@ -704,7 +748,8 @@ export class HistoryModule implements Module {
|
|
|
704
748
|
'authors; this is filtered in-process over at most maxScan messages of the window, so a response ' +
|
|
705
749
|
'may report truncated:true with a `resume` object ({windowOffset, offset, afterId}) — repeat the call with ' +
|
|
706
750
|
'those fields added to continue exactly where it stopped. If messages were added or removed in the window ' +
|
|
707
|
-
'meanwhile, the resume re-anchors on afterId and says so (windowChanged), or fails loudly when it can\'t.'
|
|
751
|
+
'meanwhile, the resume re-anchors on afterId and says so (windowChanged), or fails loudly when it can\'t.' +
|
|
752
|
+
extractWindowNote,
|
|
708
753
|
inputSchema: {
|
|
709
754
|
type: 'object' as const,
|
|
710
755
|
properties: {
|
|
@@ -742,7 +787,8 @@ export class HistoryModule implements Module {
|
|
|
742
787
|
'wholeWord:true stops "mission" from matching inside "uncommissioned". Each match carries an `id` ' +
|
|
743
788
|
'you can hand to extract({aroundId}) to read the conversation around it. ' +
|
|
744
789
|
'regex:true matching runs under a wall-clock deadline and is cleanly failed ' +
|
|
745
|
-
`(not silently empty) if a pattern is too slow — avoid nested-quantifier patterns like (a+)
|
|
790
|
+
`(not silently empty) if a pattern is too slow — avoid nested-quantifier patterns like (a+)+.` +
|
|
791
|
+
windowNote,
|
|
746
792
|
inputSchema: {
|
|
747
793
|
type: 'object' as const,
|
|
748
794
|
properties: {
|
|
@@ -790,7 +836,8 @@ export class HistoryModule implements Module {
|
|
|
790
836
|
'with a clear error rather than silently returning no spans. An entry marked boundaryUncertain:true ' +
|
|
791
837
|
'had a millisecond-timestamp collision at its edge with another span (rare — two distinct messages ' +
|
|
792
838
|
'landing in the same millisecond); its messageCount is still exact but tokensEstimate may be off by ' +
|
|
793
|
-
'a small, bounded amount.'
|
|
839
|
+
'a small, bounded amount.' +
|
|
840
|
+
(windowed ? WINDOW_NOTE_OVERVIEW : ''),
|
|
794
841
|
inputSchema: {
|
|
795
842
|
type: 'object' as const,
|
|
796
843
|
properties: {
|
|
@@ -817,6 +864,9 @@ export class HistoryModule implements Module {
|
|
|
817
864
|
}
|
|
818
865
|
|
|
819
866
|
private semanticSearchTool(): ToolDefinition {
|
|
867
|
+
// An undo moves no branch since #291, so with the window lookup an
|
|
868
|
+
// undone message is a hit, marked, rather than dropped off the branch.
|
|
869
|
+
const windowed = !!this.ctx?.windowStatusOf;
|
|
820
870
|
return {
|
|
821
871
|
name: 'semantic_search',
|
|
822
872
|
description:
|
|
@@ -831,12 +881,13 @@ export class HistoryModule implements Module {
|
|
|
831
881
|
'searching (bounded, so a huge backlog is reported as `index.behind` rather than blocking; if a ' +
|
|
832
882
|
'background sync is already running, the search waits for that run to finish instead). ' +
|
|
833
883
|
'Purely a read: nothing is written to your history. Hits are checked against your current branch ' +
|
|
834
|
-
|
|
884
|
+
`before they come back: ${windowed ? 'a message or ' : 'a message you undid or '}a summary from a branch you left is dropped and ` +
|
|
835
885
|
'counted in index.droppedOffBranch; a message edited down to nothing searchable is dropped and ' +
|
|
836
886
|
'counted in index.droppedStale. When that leaves fewer than `limit` hits and the index may hold more, it ' +
|
|
837
887
|
'searches again, deeper, down to its first 200 hits; if a search after the first fails, the hits found so ' +
|
|
838
888
|
'far come back, with why in index.searchAgainError. Message snippets ' +
|
|
839
|
-
'always show the current text of the message.'
|
|
889
|
+
'always show the current text of the message.' +
|
|
890
|
+
(windowed ? WINDOW_NOTE_SEMANTIC : ''),
|
|
840
891
|
inputSchema: {
|
|
841
892
|
type: 'object' as const,
|
|
842
893
|
properties: {
|
|
@@ -869,13 +920,13 @@ export class HistoryModule implements Module {
|
|
|
869
920
|
case 'stats':
|
|
870
921
|
return this.handleStats((call.input ?? {}) as StatsInput);
|
|
871
922
|
case 'extract':
|
|
872
|
-
return this.handleExtract((call.input ?? {}) as ExtractInput);
|
|
923
|
+
return this.handleExtract((call.input ?? {}) as ExtractInput, this.viewFor(call));
|
|
873
924
|
case 'search':
|
|
874
|
-
return await this.handleSearch((call.input ?? {}) as SearchInput);
|
|
925
|
+
return await this.handleSearch((call.input ?? {}) as SearchInput, this.viewFor(call));
|
|
875
926
|
case 'overview':
|
|
876
|
-
return this.handleOverview((call.input ?? {}) as OverviewInput);
|
|
927
|
+
return this.handleOverview((call.input ?? {}) as OverviewInput, this.viewFor(call));
|
|
877
928
|
case 'semantic_search':
|
|
878
|
-
return await this.handleSemanticSearch((call.input ?? {}) as SemanticSearchInput);
|
|
929
|
+
return await this.handleSemanticSearch((call.input ?? {}) as SemanticSearchInput, this.viewFor(call));
|
|
879
930
|
default:
|
|
880
931
|
return { success: false, isError: true, error: unknownToolMessage(this.name, call.name, this.getTools()) };
|
|
881
932
|
}
|
|
@@ -903,6 +954,119 @@ export class HistoryModule implements Module {
|
|
|
903
954
|
return {};
|
|
904
955
|
}
|
|
905
956
|
|
|
957
|
+
/**
|
|
958
|
+
* How one call's caller sees the record (#291), when the framework offers
|
|
959
|
+
* the window lookup (ModuleContext.windowStatusOf); undefined otherwise,
|
|
960
|
+
* and then every entry is as it was before the presentation index. The
|
|
961
|
+
* caller is the agent whose call it is (callerAgentName), or, for a call
|
|
962
|
+
* no agent made (an operator's, a host's), the delivery agent, for marks
|
|
963
|
+
* only. A row the caller is shown is one its window holds and its view
|
|
964
|
+
* keeps (ModuleContext.viewKeeps): a window written down from the record
|
|
965
|
+
* the residents share can hold another agent's stored context injection,
|
|
966
|
+
* or a row a tune-out holds from the residents, which the caller's
|
|
967
|
+
* requests never carry. A row the caller isn't shown is marked
|
|
968
|
+
* (`window`); for an agent's call, another agent's own row (its rounds,
|
|
969
|
+
* its notes, its stored injections) that it isn't shown is left out,
|
|
970
|
+
* unless it observes that agent: seeing another agent's thread is a
|
|
971
|
+
* declared relationship (decision 1), an observe edge, such as a
|
|
972
|
+
* resident's `AgentConfig.observes`. Each row's status is read once per
|
|
973
|
+
* call.
|
|
974
|
+
*
|
|
975
|
+
* The bound context's summaries fold its agent's window, that agent's own
|
|
976
|
+
* rounds and notes among it, so for another agent's call that doesn't
|
|
977
|
+
* observe that agent they are left out too (`leavesSummaries`). That is
|
|
978
|
+
* for now: serving such a caller summaries of its own context is later
|
|
979
|
+
* work. An agent's call where the framework can't say whose context the
|
|
980
|
+
* module is bound to (ModuleContext.agentNameOf missing, or answering
|
|
981
|
+
* that it is no live agent's) leaves them out as well. A call no agent
|
|
982
|
+
* made, and the bound agent's own, read them as before.
|
|
983
|
+
*/
|
|
984
|
+
private viewFor(call: ToolCall): CallerView | undefined {
|
|
985
|
+
const ctx = this.ctx;
|
|
986
|
+
if (!ctx?.windowStatusOf) return undefined;
|
|
987
|
+
const caller = call.callerAgentName;
|
|
988
|
+
const forCaller = caller === undefined ? undefined : { forAgent: caller };
|
|
989
|
+
const statuses = new Map<string, WindowStatus | null>();
|
|
990
|
+
// Set once a lookup throws: an agent the framework no longer knows. Its
|
|
991
|
+
// window is taken as holding nothing, so each row is marked not shown.
|
|
992
|
+
let unknown = false;
|
|
993
|
+
const statusOf = (msg: StoredMessage): WindowStatus | null => {
|
|
994
|
+
if (unknown) return null;
|
|
995
|
+
const id = String(msg.id);
|
|
996
|
+
let status = statuses.get(id);
|
|
997
|
+
if (status === undefined) {
|
|
998
|
+
try {
|
|
999
|
+
status = ctx.windowStatusOf!(msg.id, forCaller);
|
|
1000
|
+
} catch {
|
|
1001
|
+
unknown = true;
|
|
1002
|
+
return null;
|
|
1003
|
+
}
|
|
1004
|
+
statuses.set(id, status);
|
|
1005
|
+
}
|
|
1006
|
+
return status;
|
|
1007
|
+
};
|
|
1008
|
+
// Whether the caller's view keeps a row its window holds. Without the
|
|
1009
|
+
// lookup, every row its window holds is taken as shown, as before it.
|
|
1010
|
+
const kept = new Map<string, boolean>();
|
|
1011
|
+
const keeps = (msg: StoredMessage): boolean => {
|
|
1012
|
+
if (!ctx.viewKeeps) return true;
|
|
1013
|
+
const id = String(msg.id);
|
|
1014
|
+
let keep = kept.get(id);
|
|
1015
|
+
if (keep === undefined) {
|
|
1016
|
+
try {
|
|
1017
|
+
keep = ctx.viewKeeps(msg, forCaller);
|
|
1018
|
+
} catch {
|
|
1019
|
+
unknown = true;
|
|
1020
|
+
return false;
|
|
1021
|
+
}
|
|
1022
|
+
kept.set(id, keep);
|
|
1023
|
+
}
|
|
1024
|
+
return keep;
|
|
1025
|
+
};
|
|
1026
|
+
const shown = (msg: StoredMessage): boolean => statusOf(msg) === 'in' && keeps(msg);
|
|
1027
|
+
const observed = new Map<string, boolean>();
|
|
1028
|
+
const observes = (owner: string): boolean => {
|
|
1029
|
+
let sees = observed.get(owner);
|
|
1030
|
+
if (sees === undefined) {
|
|
1031
|
+
try {
|
|
1032
|
+
sees = ctx.observes?.(caller!, owner) ?? false;
|
|
1033
|
+
} catch {
|
|
1034
|
+
sees = false;
|
|
1035
|
+
}
|
|
1036
|
+
observed.set(owner, sees);
|
|
1037
|
+
}
|
|
1038
|
+
return sees;
|
|
1039
|
+
};
|
|
1040
|
+
let leavesSummaries = false;
|
|
1041
|
+
if (caller !== undefined) {
|
|
1042
|
+
let bound: string | undefined;
|
|
1043
|
+
try {
|
|
1044
|
+
bound = this.cm ? ctx.agentNameOf?.(this.cm) : undefined;
|
|
1045
|
+
} catch {
|
|
1046
|
+
bound = undefined;
|
|
1047
|
+
}
|
|
1048
|
+
leavesSummaries = bound === undefined || (bound !== caller && !observes(bound));
|
|
1049
|
+
}
|
|
1050
|
+
return {
|
|
1051
|
+
mark: (msg) => {
|
|
1052
|
+
const status = statusOf(msg);
|
|
1053
|
+
if (unknown) return 'not-shown';
|
|
1054
|
+
if (status === 'undone') return 'undone';
|
|
1055
|
+
if (status === 'out' || status === 'absent') return 'not-shown';
|
|
1056
|
+
return status === 'in' && !keeps(msg) ? 'not-shown' : undefined;
|
|
1057
|
+
},
|
|
1058
|
+
leaves: (msg) => {
|
|
1059
|
+
if (caller === undefined) return false;
|
|
1060
|
+
// A stored injection from before the audience stamp names its agent
|
|
1061
|
+
// in its own mark: it is that agent's context, as every view reads it.
|
|
1062
|
+
const owner = audienceOf(msg.metadata)?.ownRowOf ?? storedInjectionMarkOf(msg.metadata)?.agent;
|
|
1063
|
+
if (owner === undefined || owner === caller) return false;
|
|
1064
|
+
return !shown(msg) && !observes(owner);
|
|
1065
|
+
},
|
|
1066
|
+
leavesSummaries,
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
|
|
906
1070
|
// ==========================================================================
|
|
907
1071
|
// stats
|
|
908
1072
|
// ==========================================================================
|
|
@@ -944,8 +1108,8 @@ export class HistoryModule implements Module {
|
|
|
944
1108
|
// extract
|
|
945
1109
|
// ==========================================================================
|
|
946
1110
|
|
|
947
|
-
private handleExtract(input: ExtractInput): ToolResult {
|
|
948
|
-
if (input.aroundId !== undefined) return this.handleExtractAround(input);
|
|
1111
|
+
private handleExtract(input: ExtractInput, view?: CallerView): ToolResult {
|
|
1112
|
+
if (input.aroundId !== undefined) return this.handleExtractAround(input, view);
|
|
949
1113
|
const aroundOnly = (['before', 'after', 'allChannels'] as const).filter((k) => input[k] !== undefined);
|
|
950
1114
|
if (aroundOnly.length > 0) {
|
|
951
1115
|
const one = aroundOnly.length === 1;
|
|
@@ -976,6 +1140,15 @@ export class HistoryModule implements Module {
|
|
|
976
1140
|
}
|
|
977
1141
|
|
|
978
1142
|
if (authorFilter) {
|
|
1143
|
+
// A row left out for the caller (viewFor) is one the filter doesn't
|
|
1144
|
+
// keep, counted in leftOut.
|
|
1145
|
+
let leftOut = 0;
|
|
1146
|
+
const keeps = (m: StoredMessage): boolean => {
|
|
1147
|
+
if (!authorFilter(m)) return false;
|
|
1148
|
+
if (!view?.leaves(m)) return true;
|
|
1149
|
+
leftOut++;
|
|
1150
|
+
return false;
|
|
1151
|
+
};
|
|
979
1152
|
// No native author index: page through the time/channel window and
|
|
980
1153
|
// filter here. offset/limit apply to the FILTERED sequence. Stop as
|
|
981
1154
|
// soon as the requested page is full; otherwise scan to maxScan. Only
|
|
@@ -992,7 +1165,7 @@ export class HistoryModule implements Module {
|
|
|
992
1165
|
);
|
|
993
1166
|
const requestedOffset = clampCount(input.windowOffset, 0, NATIVE_OFFSET_MAX, 'windowOffset', 'read');
|
|
994
1167
|
// A position is only meaningful against the window it was taken in:
|
|
995
|
-
// a removal before it (every discord:delete, host hide
|
|
1168
|
+
// a removal before it (every discord:delete, host hide) shifts
|
|
996
1169
|
// later positions left, and a message inserted before it (a
|
|
997
1170
|
// time-ordered window with backfill) shifts them right. Either would
|
|
998
1171
|
// silently skip or repeat a message. afterId pins the position to the
|
|
@@ -1035,9 +1208,13 @@ export class HistoryModule implements Module {
|
|
|
1035
1208
|
let kept = 0;
|
|
1036
1209
|
let scanned = 0;
|
|
1037
1210
|
let lastScanned: StoredMessage | undefined;
|
|
1038
|
-
// Window position just past the last message put on the page,
|
|
1211
|
+
// Window position just past the last message put on the page, that
|
|
1212
|
+
// message, and the rows left out up to it: a page-full stop resumes
|
|
1213
|
+
// there, so the rows the lookahead passed after it are the next page's
|
|
1214
|
+
// to count.
|
|
1039
1215
|
let afterPage = windowOffset;
|
|
1040
1216
|
let lastOnPage: StoredMessage | undefined;
|
|
1217
|
+
let leftOutAtPage = 0;
|
|
1041
1218
|
let exhausted = false;
|
|
1042
1219
|
let pageFull = false;
|
|
1043
1220
|
outer: for (;;) {
|
|
@@ -1053,11 +1230,12 @@ export class HistoryModule implements Module {
|
|
|
1053
1230
|
for (const m of chunk) {
|
|
1054
1231
|
scanned++;
|
|
1055
1232
|
lastScanned = m;
|
|
1056
|
-
if (!
|
|
1233
|
+
if (!keeps(m)) continue;
|
|
1057
1234
|
if (kept >= offset && page.length < limit) {
|
|
1058
1235
|
page.push(m);
|
|
1059
1236
|
afterPage = windowOffset + scanned;
|
|
1060
1237
|
lastOnPage = m;
|
|
1238
|
+
leftOutAtPage = leftOut;
|
|
1061
1239
|
}
|
|
1062
1240
|
kept++;
|
|
1063
1241
|
if (limit > 0 && page.length >= limit && kept > offset + limit) {
|
|
@@ -1083,19 +1261,22 @@ export class HistoryModule implements Module {
|
|
|
1083
1261
|
}).messages;
|
|
1084
1262
|
if (next.length === 0) exhausted = true;
|
|
1085
1263
|
}
|
|
1264
|
+
const counted = pageFull ? leftOutAtPage : leftOut;
|
|
1086
1265
|
return {
|
|
1087
1266
|
success: true,
|
|
1088
1267
|
data: {
|
|
1089
1268
|
// A resumed scan (windowOffset > 0) only saw the window from that
|
|
1090
1269
|
// position on, so its count is named for that — never a total. A
|
|
1091
1270
|
// page-full stop's lookahead match lies past the resume point and
|
|
1092
|
-
// is left out, so counts add up across a chain of resumes
|
|
1271
|
+
// is left out, so counts add up across a chain of resumes, and so
|
|
1272
|
+
// do the rows left out on the way to it, which the resume scans.
|
|
1093
1273
|
...(() => {
|
|
1094
1274
|
const n = pageFull ? kept - 1 : kept;
|
|
1095
1275
|
const base = windowOffset > 0 ? 'matchedSinceWindowOffset' : 'matchedCount';
|
|
1096
1276
|
return { [exhausted ? base : `${base}AtLeast`]: n };
|
|
1097
1277
|
})(),
|
|
1098
1278
|
returned: page.length,
|
|
1279
|
+
...(counted > 0 ? { leftOut: counted } : {}),
|
|
1099
1280
|
scanned,
|
|
1100
1281
|
truncated: !exhausted,
|
|
1101
1282
|
...(shift !== 0 || missed.length > 0 || unverified
|
|
@@ -1115,7 +1296,7 @@ export class HistoryModule implements Module {
|
|
|
1115
1296
|
? {
|
|
1116
1297
|
addedBefore: missed.length,
|
|
1117
1298
|
// the ones this filter would have kept — fetch them with extract({aroundId})
|
|
1118
|
-
missedIds: missed.filter(authorFilter).map((m) => String(m.id)),
|
|
1299
|
+
missedIds: missed.filter((m) => authorFilter(m) && !view?.leaves(m)).map((m) => String(m.id)),
|
|
1119
1300
|
}
|
|
1120
1301
|
: {}),
|
|
1121
1302
|
note:
|
|
@@ -1157,7 +1338,7 @@ export class HistoryModule implements Module {
|
|
|
1157
1338
|
};
|
|
1158
1339
|
})()
|
|
1159
1340
|
: {}),
|
|
1160
|
-
messages: page.map((msg) => projectMessage(msg, format)),
|
|
1341
|
+
messages: page.map((msg) => projectMessage(msg, format, (m) => this.failedSpeech(m), view?.mark(msg))),
|
|
1161
1342
|
},
|
|
1162
1343
|
};
|
|
1163
1344
|
}
|
|
@@ -1173,15 +1354,20 @@ export class HistoryModule implements Module {
|
|
|
1173
1354
|
limit: limit + 1,
|
|
1174
1355
|
offset,
|
|
1175
1356
|
});
|
|
1176
|
-
const
|
|
1357
|
+
const fetched = result.messages.slice(0, limit);
|
|
1358
|
+
// Rows left out for the caller (viewFor) leave the page; the page's
|
|
1359
|
+
// place in the window, and so offset and hasMore, stay the window's.
|
|
1360
|
+
const page = view ? fetched.filter((msg) => !view.leaves(msg)) : fetched;
|
|
1361
|
+
const leftOut = fetched.length - page.length;
|
|
1177
1362
|
|
|
1178
1363
|
return {
|
|
1179
1364
|
success: true,
|
|
1180
1365
|
data: {
|
|
1181
1366
|
...(channelId !== undefined ? { matchedCount: result.matchedCount } : {}),
|
|
1182
1367
|
returned: page.length,
|
|
1368
|
+
...(leftOut > 0 ? { leftOut } : {}),
|
|
1183
1369
|
hasMore: result.messages.length > limit,
|
|
1184
|
-
messages: page.map((msg) => projectMessage(msg, format)),
|
|
1370
|
+
messages: page.map((msg) => projectMessage(msg, format, (m) => this.failedSpeech(m), view?.mark(msg))),
|
|
1185
1371
|
},
|
|
1186
1372
|
};
|
|
1187
1373
|
}
|
|
@@ -1193,7 +1379,7 @@ export class HistoryModule implements Module {
|
|
|
1193
1379
|
* side still needs, the nearest messages strictly before/after it (both
|
|
1194
1380
|
* via edgeWindow, which is exact by timestamp).
|
|
1195
1381
|
*/
|
|
1196
|
-
private handleExtractAround(input: ExtractInput): ToolResult {
|
|
1382
|
+
private handleExtractAround(input: ExtractInput, view?: CallerView): ToolResult {
|
|
1197
1383
|
const windowFields = (['from', 'to', 'offset', 'windowOffset', 'limit'] as const).filter((k) => input[k] !== undefined);
|
|
1198
1384
|
if (windowFields.length > 0) {
|
|
1199
1385
|
throw new Error(
|
|
@@ -1230,7 +1416,9 @@ export class HistoryModule implements Module {
|
|
|
1230
1416
|
: 'the "id" of a history--search or history--extract result.'),
|
|
1231
1417
|
);
|
|
1232
1418
|
}
|
|
1233
|
-
const
|
|
1419
|
+
const found = cm.getMessage(rawId.replace(/^msg:/, ''));
|
|
1420
|
+
// A row left out for the caller (viewFor) is answered as one not there.
|
|
1421
|
+
const anchor = found && !view?.leaves(found) ? found : null;
|
|
1234
1422
|
if (!anchor) {
|
|
1235
1423
|
throw new Error(
|
|
1236
1424
|
`Nothing was read: no message in your history has the id ${shown(input.aroundId)}. Pass the "id" ` +
|
|
@@ -1275,15 +1463,18 @@ export class HistoryModule implements Module {
|
|
|
1275
1463
|
const earlier =
|
|
1276
1464
|
needBefore > 0 ? this.edgeWindow({ toMs: t - 1, channelId, n: needBefore, side: 'newest' }).messages.reverse() : [];
|
|
1277
1465
|
const later = needAfter > 0 ? this.edgeWindow({ fromMs: t + 1, channelId, n: needAfter, side: 'oldest' }).messages : [];
|
|
1278
|
-
const
|
|
1466
|
+
const around = [...earlier, ...tiesBefore, ties[idx]!, ...tiesAfter, ...later];
|
|
1467
|
+
const window = view ? around.filter((m) => !view.leaves(m)) : around;
|
|
1468
|
+
const leftOut = around.length - window.length;
|
|
1279
1469
|
return {
|
|
1280
1470
|
success: true,
|
|
1281
1471
|
data: {
|
|
1282
1472
|
anchorId,
|
|
1283
1473
|
channelId: channelId ?? null,
|
|
1284
1474
|
returned: window.length,
|
|
1475
|
+
...(leftOut > 0 ? { leftOut } : {}),
|
|
1285
1476
|
messages: window.map((m) => ({
|
|
1286
|
-
...projectMessage(m, format),
|
|
1477
|
+
...projectMessage(m, format, (n) => this.failedSpeech(n), view?.mark(m)),
|
|
1287
1478
|
...(String(m.id) === anchorId ? { anchor: true } : {}),
|
|
1288
1479
|
})),
|
|
1289
1480
|
},
|
|
@@ -1294,7 +1485,7 @@ export class HistoryModule implements Module {
|
|
|
1294
1485
|
// search
|
|
1295
1486
|
// ==========================================================================
|
|
1296
1487
|
|
|
1297
|
-
private async handleSearch(input: SearchInput): Promise<ToolResult> {
|
|
1488
|
+
private async handleSearch(input: SearchInput, view?: CallerView): Promise<ToolResult> {
|
|
1298
1489
|
// A finite number is searched as its text, as a regular expression made
|
|
1299
1490
|
// from it reads it; any other value that isn't text is refused.
|
|
1300
1491
|
const given: unknown = input.query;
|
|
@@ -1386,11 +1577,29 @@ export class HistoryModule implements Module {
|
|
|
1386
1577
|
if (w.total !== undefined) unscanned = w.total - (w.messages.length - pool.length) - windowMessages.length;
|
|
1387
1578
|
}
|
|
1388
1579
|
const candidatePoolSize = windowMessages.length;
|
|
1389
|
-
const
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1580
|
+
const byAuthor = authorFilter ? windowMessages.filter(authorFilter) : windowMessages;
|
|
1581
|
+
// A row left out for the caller (viewFor) is no candidate. Beside each
|
|
1582
|
+
// candidate, how many were left out before it.
|
|
1583
|
+
const candidates: StoredMessage[] = [];
|
|
1584
|
+
const leftOutBefore: number[] = [];
|
|
1585
|
+
let leftOutOfPool = 0;
|
|
1586
|
+
for (const m of byAuthor) {
|
|
1587
|
+
if (view?.leaves(m)) { leftOutOfPool++; continue; }
|
|
1588
|
+
candidates.push(m);
|
|
1589
|
+
leftOutBefore.push(leftOutOfPool);
|
|
1590
|
+
}
|
|
1591
|
+
// A scan stopped at `limit` resumes after its last scanned candidate, so
|
|
1592
|
+
// the rows left out past it are the next call's to count, as extract's
|
|
1593
|
+
// page-full stop counts them (leftOutAtPage): counts add up across a
|
|
1594
|
+
// chain of resumes.
|
|
1595
|
+
const poolInfo = (scanned: number) => {
|
|
1596
|
+
const leftOut = scanned > 0 && scanned < candidates.length ? leftOutBefore[scanned - 1]! : leftOutOfPool;
|
|
1597
|
+
return {
|
|
1598
|
+
candidatePoolSize,
|
|
1599
|
+
...(authorFilter ? { afterAuthorFilter: byAuthor.length } : {}),
|
|
1600
|
+
...(leftOut > 0 ? { leftOut } : {}),
|
|
1601
|
+
order,
|
|
1602
|
+
};
|
|
1394
1603
|
};
|
|
1395
1604
|
// Where the scan stopped, when it stopped before the end of the window
|
|
1396
1605
|
// the caller asked about: either `limit` matches came first (the rest of
|
|
@@ -1458,8 +1667,8 @@ export class HistoryModule implements Module {
|
|
|
1458
1667
|
// through a worker thread instead, which can be forcibly terminated
|
|
1459
1668
|
// on a deadline. See search-regex-worker.ts's header for the full
|
|
1460
1669
|
// rationale.
|
|
1461
|
-
const { matches, scanned } = await this.searchWithRegexWorker(candidates, query, flags, limit);
|
|
1462
|
-
return { success: true, data: { scanned, ...poolInfo, truncated, ...resumeInfo(scanned, matches.length), matches } };
|
|
1670
|
+
const { matches, scanned } = await this.searchWithRegexWorker(candidates, query, flags, limit, view);
|
|
1671
|
+
return { success: true, data: { scanned, ...poolInfo(scanned), truncated, ...resumeInfo(scanned, matches.length), matches } };
|
|
1463
1672
|
}
|
|
1464
1673
|
|
|
1465
1674
|
// Plain substring search (String.prototype.indexOf) is inherently
|
|
@@ -1485,16 +1694,68 @@ export class HistoryModule implements Module {
|
|
|
1485
1694
|
participant: msg.participant,
|
|
1486
1695
|
author: authorName(msg),
|
|
1487
1696
|
channelId: getChannelId(msg) ?? null,
|
|
1488
|
-
|
|
1697
|
+
...windowField(view?.mark(msg)),
|
|
1698
|
+
snippet: this.hitSnippet(msg, text, hit.index, hit.length),
|
|
1489
1699
|
});
|
|
1490
1700
|
}
|
|
1491
1701
|
|
|
1492
1702
|
return {
|
|
1493
1703
|
success: true,
|
|
1494
|
-
data: { scanned, ...poolInfo, truncated, ...resumeInfo(scanned, matches.length), matches },
|
|
1704
|
+
data: { scanned, ...poolInfo(scanned), truncated, ...resumeInfo(scanned, matches.length), matches },
|
|
1495
1705
|
};
|
|
1496
1706
|
}
|
|
1497
1707
|
|
|
1708
|
+
private failedSpeech(msg: StoredMessage): ReadonlySet<string> {
|
|
1709
|
+
const pending = new Set<string>();
|
|
1710
|
+
for (const block of msg.content as ContentBlock[]) if (block.type === 'tool_use' && speechOf(block.name, block.input)) pending.add(block.id);
|
|
1711
|
+
if (!pending.size) return NO_FAILURES;
|
|
1712
|
+
const cm = this.cm as ContextManager;
|
|
1713
|
+
const fromMs = msg.timestamp.getTime();
|
|
1714
|
+
const failed = new Set<string>();
|
|
1715
|
+
const answer = (blocks: ContentBlock[]) => {
|
|
1716
|
+
for (const block of blocks) {
|
|
1717
|
+
if (block.type === 'tool_result' && pending.delete(block.toolUseId) && block.isError) failed.add(block.toolUseId);
|
|
1718
|
+
}
|
|
1719
|
+
};
|
|
1720
|
+
answer(msg.content as ContentBlock[]);
|
|
1721
|
+
let past = false;
|
|
1722
|
+
let body = msg.bodyGroupId;
|
|
1723
|
+
for (let offset = 0; pending.size > 0; offset++) {
|
|
1724
|
+
const later = cm.queryMessagesByTime({ fromMs, limit: 1, offset }).messages[0];
|
|
1725
|
+
if (!later) break;
|
|
1726
|
+
if (!past) {
|
|
1727
|
+
if (later.timestamp.getTime() > fromMs) break;
|
|
1728
|
+
past = String(later.id) === String(msg.id);
|
|
1729
|
+
continue;
|
|
1730
|
+
}
|
|
1731
|
+
const blocks = later.content as ContentBlock[];
|
|
1732
|
+
if (later.bodyGroupId === undefined || later.bodyGroupId !== body) {
|
|
1733
|
+
if (blocks.some((block) => block.type === 'tool_use') || !blocks.some((block) => block.type === 'tool_result')) break;
|
|
1734
|
+
body = later.bodyGroupId;
|
|
1735
|
+
}
|
|
1736
|
+
answer(blocks);
|
|
1737
|
+
}
|
|
1738
|
+
return failed;
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
private hitSnippet(msg: StoredMessage, text: string, index: number, length: number): string {
|
|
1742
|
+
const content = msg.content as ContentBlock[];
|
|
1743
|
+
const failed = this.failedSpeech(msg);
|
|
1744
|
+
const spans = blockSpans(content);
|
|
1745
|
+
for (const { block, at, label } of spans) {
|
|
1746
|
+
if (block.type !== 'tool_use') continue;
|
|
1747
|
+
const said = speechOf(block.name, block.input);
|
|
1748
|
+
const wordsAt = at + label.length - (said?.words.length ?? 0);
|
|
1749
|
+
if (said && index >= wordsAt && index < at + label.length && index + length <= at + label.length) {
|
|
1750
|
+
return `[${block.name}${failed.has(block.id) ? ERROR_MARK : ''}] ${snippetAround(said.words, index - wordsAt, length)}`;
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
if (!failed.size) return snippetAround(text, index, length);
|
|
1754
|
+
const marks = spans.flatMap(({ block, at }) => (block.type === 'tool_use' && failed.has(block.id) ? [at + 1 + block.name.length] : []));
|
|
1755
|
+
const moved = (i: number) => i + ERROR_MARK.length * marks.filter((mark) => mark <= i).length;
|
|
1756
|
+
return snippetAround(flattenContent(content, failed), moved(index), moved(index + length) - moved(index));
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1498
1759
|
/**
|
|
1499
1760
|
* Run regex matching for `search` on a worker thread with a hard
|
|
1500
1761
|
* wall-clock deadline, so a catastrophically-backtracking pattern can be
|
|
@@ -1514,6 +1775,7 @@ export class HistoryModule implements Module {
|
|
|
1514
1775
|
pattern: string,
|
|
1515
1776
|
flags: string,
|
|
1516
1777
|
limit: number,
|
|
1778
|
+
view?: CallerView,
|
|
1517
1779
|
): Promise<{ matches: SearchMatch[]; scanned: number }> {
|
|
1518
1780
|
const texts = candidates.map((msg) => flattenContent(msg.content));
|
|
1519
1781
|
let worker: Worker | undefined;
|
|
@@ -1563,7 +1825,8 @@ export class HistoryModule implements Module {
|
|
|
1563
1825
|
participant: msg.participant,
|
|
1564
1826
|
author: authorName(msg),
|
|
1565
1827
|
channelId: getChannelId(msg) ?? null,
|
|
1566
|
-
|
|
1828
|
+
...windowField(view?.mark(msg)),
|
|
1829
|
+
snippet: this.hitSnippet(msg, text, matchIndex, matchLength),
|
|
1567
1830
|
};
|
|
1568
1831
|
});
|
|
1569
1832
|
return { matches, scanned };
|
|
@@ -1579,7 +1842,7 @@ export class HistoryModule implements Module {
|
|
|
1579
1842
|
// overview
|
|
1580
1843
|
// ==========================================================================
|
|
1581
1844
|
|
|
1582
|
-
private async handleSemanticSearch(input: SemanticSearchInput): Promise<ToolResult> {
|
|
1845
|
+
private async handleSemanticSearch(input: SemanticSearchInput, view?: CallerView): Promise<ToolResult> {
|
|
1583
1846
|
if (!this.semanticCfg || !this.semanticClient) {
|
|
1584
1847
|
throw new Error(
|
|
1585
1848
|
"Nothing was searched: this host has no semantic index set up, so history--semantic_search isn't available " +
|
|
@@ -1692,16 +1955,23 @@ export class HistoryModule implements Module {
|
|
|
1692
1955
|
};
|
|
1693
1956
|
};
|
|
1694
1957
|
// The remote index is append-only and knows nothing about branches: a
|
|
1695
|
-
// message removed from the agent's reach by
|
|
1696
|
-
// /newtopic keeps its id (a new message gets a fresh one) and
|
|
1697
|
-
// indexed. Check every hit against the CURRENT branch before it goes
|
|
1698
|
-
// to the model — getMessage/getSummary rebuild on branch switch, so
|
|
1699
|
-
//
|
|
1700
|
-
//
|
|
1701
|
-
//
|
|
1702
|
-
//
|
|
1703
|
-
// is
|
|
1704
|
-
//
|
|
1958
|
+
// message removed from the agent's reach by a rollback, /checkout,
|
|
1959
|
+
// /restore or /newtopic keeps its id (a new message gets a fresh one) and
|
|
1960
|
+
// stays indexed. Check every hit against the CURRENT branch before it goes
|
|
1961
|
+
// back to the model — getMessage/getSummary rebuild on branch switch, so a
|
|
1962
|
+
// message, or a summary, this branch doesn't hold answers null. An undo
|
|
1963
|
+
// works on the agent's window since #291 and moves no branch, so an
|
|
1964
|
+
// undone message stays on it and comes back, marked `window: "undone"`
|
|
1965
|
+
// for the caller when the framework offers the window lookup (viewFor),
|
|
1966
|
+
// as is one the caller wasn't shown; another agent's own row the caller
|
|
1967
|
+
// doesn't observe is left out (index.leftOut), and so is a summary hit
|
|
1968
|
+
// for another agent's call that doesn't observe the agent whose context
|
|
1969
|
+
// the summaries fold (viewFor's leavesSummaries).
|
|
1970
|
+
// A branch made from an earlier point, as a rollback makes one, can mint
|
|
1971
|
+
// a summary id another branch already used, so a summary's key carries
|
|
1972
|
+
// its creation time too, and a hit whose time isn't this branch's
|
|
1973
|
+
// summary's is another branch's. k is small, so this is at most `limit`
|
|
1974
|
+
// local lookups.
|
|
1705
1975
|
// A message hit whose current text has nothing left to index (edited
|
|
1706
1976
|
// blank, delete not yet applied) is dropped too, so its stale snippet is
|
|
1707
1977
|
// never served. Dropped hits are replaced by over-fetching: when the
|
|
@@ -1714,16 +1984,22 @@ export class HistoryModule implements Module {
|
|
|
1714
1984
|
// must not leak a passage the edit removed. Only the ranking can be stale.
|
|
1715
1985
|
// A summary hit's snippet is rebuilt from the local summary too, so a
|
|
1716
1986
|
// service that keeps only vectors and ids (no text) still yields one.
|
|
1717
|
-
const currentText = new Map<string, string>();
|
|
1718
|
-
|
|
1987
|
+
const currentText = new Map<string, { text: string; shown: string }>();
|
|
1988
|
+
// Each message hit's mark for the caller (viewFor), by the hit's id.
|
|
1989
|
+
const marks = new Map<string, WindowMark>();
|
|
1990
|
+
const keep = (h: { id: string }): 'ok' | 'offBranch' | 'stale' | 'superseded' | 'leftOut' => {
|
|
1719
1991
|
const msgId = /^msg:(.+)$/.exec(h.id)?.[1];
|
|
1720
1992
|
const sum = parseSummaryKey(h.id);
|
|
1721
1993
|
if (msgId !== undefined) {
|
|
1722
1994
|
const m = cm.getMessage(msgId);
|
|
1723
1995
|
if (!m) return 'offBranch';
|
|
1996
|
+
if (view?.leaves(m)) return 'leftOut';
|
|
1997
|
+
const mark = view?.mark(m);
|
|
1998
|
+
if (mark !== undefined) marks.set(h.id, mark);
|
|
1724
1999
|
const text = safeSlice(messageIndexText(m, includePrivate), 0, this.semanticCfg!.maxChars ?? 32_000);
|
|
1725
2000
|
if (!text) return 'stale';
|
|
1726
|
-
|
|
2001
|
+
const failed = this.failedSpeech(m);
|
|
2002
|
+
currentText.set(h.id, { text, shown: failed.size ? safeSlice(messageIndexText(m, includePrivate, failed), 0, this.semanticCfg!.maxChars ?? 32_000) : text });
|
|
1727
2003
|
return 'ok';
|
|
1728
2004
|
}
|
|
1729
2005
|
if (sum !== null) {
|
|
@@ -1738,6 +2014,7 @@ export class HistoryModule implements Module {
|
|
|
1738
2014
|
if (!s) return 'offBranch';
|
|
1739
2015
|
if (sum.createdMs === null) return 'superseded';
|
|
1740
2016
|
if (s.created !== sum.createdMs) return 'offBranch';
|
|
2017
|
+
if (view?.leavesSummaries) return 'leftOut';
|
|
1741
2018
|
const text = summaryIndexText(s, this.semanticCfg!);
|
|
1742
2019
|
// An empty text here is mostly defensive: context-manager drops a
|
|
1743
2020
|
// summary that's empty after trimming when it loads, and refuses to
|
|
@@ -1745,7 +2022,7 @@ export class HistoryModule implements Module {
|
|
|
1745
2022
|
// nothing gives one. The hit is dropped rather than show the index's
|
|
1746
2023
|
// text.
|
|
1747
2024
|
if (!text) return 'stale';
|
|
1748
|
-
currentText.set(h.id, text);
|
|
2025
|
+
currentText.set(h.id, { text, shown: text });
|
|
1749
2026
|
return 'ok';
|
|
1750
2027
|
}
|
|
1751
2028
|
return 'ok';
|
|
@@ -1760,23 +2037,28 @@ export class HistoryModule implements Module {
|
|
|
1760
2037
|
}
|
|
1761
2038
|
let droppedOffBranch = 0;
|
|
1762
2039
|
let droppedStale = 0;
|
|
2040
|
+
let leftOut = 0;
|
|
1763
2041
|
// Why searching again for more hits failed, when it did. The search
|
|
1764
2042
|
// before it answered, so its hits on this branch are returned.
|
|
1765
2043
|
let searchAgainError: string | null = null;
|
|
1766
2044
|
let onBranch: typeof res.hits = [];
|
|
1767
2045
|
// The last answer's hits on this branch, checked as they are now.
|
|
1768
2046
|
const check = (): void => {
|
|
1769
|
-
droppedOffBranch = 0; droppedStale = 0;
|
|
2047
|
+
droppedOffBranch = 0; droppedStale = 0; leftOut = 0;
|
|
1770
2048
|
onBranch = res.hits.filter((h) => {
|
|
1771
2049
|
const v = keep(h);
|
|
1772
2050
|
if (v === 'offBranch') droppedOffBranch++;
|
|
1773
2051
|
else if (v === 'stale') droppedStale++;
|
|
2052
|
+
else if (v === 'leftOut') leftOut++;
|
|
1774
2053
|
return v === 'ok';
|
|
1775
2054
|
});
|
|
1776
2055
|
};
|
|
2056
|
+
// A search of summaries alone, for a caller they're left out for, keeps
|
|
2057
|
+
// no hit however deep it goes, so it isn't searched again.
|
|
2058
|
+
const keepsNone = wantsSummariesOnly && view?.leavesSummaries === true;
|
|
1777
2059
|
for (;;) {
|
|
1778
2060
|
check();
|
|
1779
|
-
if (onBranch.length >= limit || res.hits.length < k || k >= SEMANTIC_MAX_FETCH) break;
|
|
2061
|
+
if (keepsNone || onBranch.length >= limit || res.hits.length < k || k >= SEMANTIC_MAX_FETCH) break;
|
|
1780
2062
|
k = Math.min(SEMANTIC_MAX_FETCH, k * 4);
|
|
1781
2063
|
try {
|
|
1782
2064
|
res = await runSearch(k);
|
|
@@ -1819,10 +2101,11 @@ export class HistoryModule implements Module {
|
|
|
1819
2101
|
score: h.score,
|
|
1820
2102
|
timestamp,
|
|
1821
2103
|
channelId: h.channel,
|
|
2104
|
+
...windowField(marks.get(h.id)),
|
|
1822
2105
|
participant: meta?.participant ?? null,
|
|
1823
2106
|
author: meta?.author ?? null,
|
|
1824
|
-
snippet: currentText.has(h.id) ? snippetOf(currentText.get(h.id)
|
|
1825
|
-
chars: currentText.has(h.id) ? currentText.get(h.id)!.length : h.chars,
|
|
2107
|
+
snippet: currentText.has(h.id) ? snippetOf(currentText.get(h.id)!.shown) : (h.text ?? ''),
|
|
2108
|
+
chars: currentText.has(h.id) ? currentText.get(h.id)!.text.length : h.chars,
|
|
1826
2109
|
};
|
|
1827
2110
|
});
|
|
1828
2111
|
if (unread.length > 0) {
|
|
@@ -1844,6 +2127,8 @@ export class HistoryModule implements Module {
|
|
|
1844
2127
|
droppedOffBranch,
|
|
1845
2128
|
/** Hits for messages whose current text has nothing indexable (edited blank; the index keeps such a message until its delete goes through, and on a service without /delete it never does). */
|
|
1846
2129
|
droppedStale,
|
|
2130
|
+
/** Hits left out for the caller (#291): another agent's own rows it isn't shown and doesn't observe, and, for another agent's call that doesn't observe the agent whose context the summaries fold, summary hits; only when there were any. */
|
|
2131
|
+
...(leftOut > 0 ? { leftOut } : {}),
|
|
1847
2132
|
/** When too few hits were on this branch and searching again for more failed: why, and whether calling again can help. */
|
|
1848
2133
|
searchAgainError,
|
|
1849
2134
|
},
|
|
@@ -1852,7 +2137,7 @@ export class HistoryModule implements Module {
|
|
|
1852
2137
|
};
|
|
1853
2138
|
}
|
|
1854
2139
|
|
|
1855
|
-
private handleOverview(input: OverviewInput): ToolResult {
|
|
2140
|
+
private handleOverview(input: OverviewInput, view?: CallerView): ToolResult {
|
|
1856
2141
|
const channelId = this.resolveChannel(input.channelId);
|
|
1857
2142
|
const fromMs = parseIsoDate(input.from, 'from');
|
|
1858
2143
|
const toMs = parseIsoDate(input.to, 'to');
|
|
@@ -2171,6 +2456,14 @@ export class HistoryModule implements Module {
|
|
|
2171
2456
|
// `filtered.length - limit` has no such cliff, at limit:0 it's
|
|
2172
2457
|
// `slice(filtered.length)` -> `[]`, the honest answer.
|
|
2173
2458
|
const capped = truncated ? filtered.slice(filtered.length - limit) : filtered;
|
|
2459
|
+
// The summaries fold the bound context's agent's window, its own rounds
|
|
2460
|
+
// and notes among it (#291). For another agent's call that doesn't
|
|
2461
|
+
// observe that agent (viewFor's leavesSummaries) a summarized span keeps
|
|
2462
|
+
// its time, level and counts, which are the record's, and leaves out its
|
|
2463
|
+
// text: `content: null` and `contentLeftOut: true`, counted at the top as
|
|
2464
|
+
// `summariesLeftOut`.
|
|
2465
|
+
const leavesContent = view?.leavesSummaries === true;
|
|
2466
|
+
const summariesLeftOut = leavesContent ? capped.filter((e) => e.summarized).length : 0;
|
|
2174
2467
|
|
|
2175
2468
|
return {
|
|
2176
2469
|
success: true,
|
|
@@ -2188,6 +2481,7 @@ export class HistoryModule implements Module {
|
|
|
2188
2481
|
...(input.level !== undefined ? { level: input.level } : { maxLevelAvailable: cm.getMaxSummaryLevel() }),
|
|
2189
2482
|
truncated,
|
|
2190
2483
|
totalSpans,
|
|
2484
|
+
...(summariesLeftOut > 0 ? { summariesLeftOut } : {}),
|
|
2191
2485
|
entries: capped.map((e) => {
|
|
2192
2486
|
// When channelId narrows the response, the numbers an agent will
|
|
2193
2487
|
// actually reason over (messageCount/tokensEstimate) are scoped
|
|
@@ -2205,7 +2499,11 @@ export class HistoryModule implements Module {
|
|
|
2205
2499
|
from: new Date(e.startMs).toISOString(),
|
|
2206
2500
|
to: new Date(e.endMs).toISOString(),
|
|
2207
2501
|
summarized: e.summarized,
|
|
2208
|
-
...(e.summarized
|
|
2502
|
+
...(e.summarized
|
|
2503
|
+
? leavesContent
|
|
2504
|
+
? { content: null, contentLeftOut: true, level: e.level }
|
|
2505
|
+
: { content: e.content, level: e.level }
|
|
2506
|
+
: {}),
|
|
2209
2507
|
// Set only when this entry's boundary-tie disambiguation (see
|
|
2210
2508
|
// the correction pass above) found and corrected a
|
|
2211
2509
|
// millisecond-timestamp collision at its edge. messageCount is
|
|
@@ -2440,33 +2738,102 @@ function mergeForeignIntoGap(
|
|
|
2440
2738
|
}
|
|
2441
2739
|
}
|
|
2442
2740
|
|
|
2443
|
-
|
|
2741
|
+
/** The sentence extract's and search's descriptions gain with the window lookup (getTools). */
|
|
2742
|
+
const WINDOW_NOTE =
|
|
2743
|
+
' A message that isn\'t in your context carries `window`: "undone" for one an undo or a refusal rewind took ' +
|
|
2744
|
+
'out of it, "not-shown" for one not in your context now, never shown to you or returned by an undo or rollback ' +
|
|
2745
|
+
'to be shown again; another agent\'s own turns and notes that aren\'t in your context are left out unless you ' +
|
|
2746
|
+
'observe that agent, and how many were is `leftOut`.';
|
|
2747
|
+
|
|
2748
|
+
/** extract's addition to it: without an author filter its offset counts the rows left out, as its page's place in the window does; with one, only the rows the filter keeps. */
|
|
2749
|
+
const EXTRACT_OFFSET_NOTE = ' Without an author filter, `offset` counts the messages left out too: to page on, advance it by returned + leftOut. ' +
|
|
2750
|
+
'With one, it counts only the messages the filter keeps: page on by `resume`.';
|
|
2751
|
+
|
|
2752
|
+
/** semantic_search's form of it. */
|
|
2753
|
+
const WINDOW_NOTE_SEMANTIC =
|
|
2754
|
+
' A message hit that isn\'t in your context carries `window` ("undone" for one an undo or a refusal rewind took ' +
|
|
2755
|
+
'out of it, "not-shown" for one not in your context now, never shown to you or returned by an undo or rollback ' +
|
|
2756
|
+
'to be shown again); another agent\'s own turns and notes that aren\'t in your ' +
|
|
2757
|
+
'context are left out unless you observe that agent, and so are summary hits when the summaries fold another ' +
|
|
2758
|
+
'agent\'s context (the one this history is bound to) and you don\'t observe that agent, all counted in ' +
|
|
2759
|
+
'index.leftOut.';
|
|
2760
|
+
|
|
2761
|
+
/** overview's form of it: its entries are spans, and only a summary's text is the bound agent's. */
|
|
2762
|
+
const WINDOW_NOTE_OVERVIEW =
|
|
2763
|
+
' The summaries fold one agent\'s context, the one this history is bound to: when that isn\'t yours and you don\'t ' +
|
|
2764
|
+
'observe that agent, a summarized span comes back with its time, level and counts but not its text ' +
|
|
2765
|
+
'(content:null, contentLeftOut:true), and summariesLeftOut says how many did.';
|
|
2766
|
+
|
|
2767
|
+
/**
|
|
2768
|
+
* Where a row an entry returns stands with the caller's window (#291), when
|
|
2769
|
+
* it isn't in it: `undone` for a row an undo or a refusal rewind took out
|
|
2770
|
+
* of it, `not-shown` for one it was never shown (stored and not presented
|
|
2771
|
+
* to it, taken back to arrived, or in a slot it doesn't read). An entry for
|
|
2772
|
+
* a row in the window carries none.
|
|
2773
|
+
*/
|
|
2774
|
+
type WindowMark = 'not-shown' | 'undone';
|
|
2775
|
+
|
|
2776
|
+
/** How one call's caller sees the record (HistoryModule.viewFor). */
|
|
2777
|
+
interface CallerView {
|
|
2778
|
+
/** The row's mark for the caller, or undefined for a row it is shown (its window holds it and its view keeps it). */
|
|
2779
|
+
mark(msg: StoredMessage): WindowMark | undefined;
|
|
2780
|
+
/** Whether the row is left out for the caller: another agent's own row it isn't shown and whose agent it doesn't observe. */
|
|
2781
|
+
leaves(msg: StoredMessage): boolean;
|
|
2782
|
+
/** Whether the bound context's summaries are left out for the caller: another agent's call that doesn't observe the agent whose context it is. */
|
|
2783
|
+
readonly leavesSummaries: boolean;
|
|
2784
|
+
}
|
|
2785
|
+
|
|
2786
|
+
/** `{ window }` for a defined mark, nothing otherwise, so an entry in the caller's window is as it always was. */
|
|
2787
|
+
function windowField(mark: WindowMark | undefined): { window?: WindowMark } {
|
|
2788
|
+
return mark === undefined ? {} : { window: mark };
|
|
2789
|
+
}
|
|
2790
|
+
|
|
2791
|
+
function projectMessage(msg: StoredMessage, format: 'text' | 'raw', failedOf: (msg: StoredMessage) => ReadonlySet<string>, mark?: WindowMark): Record<string, unknown> {
|
|
2444
2792
|
return {
|
|
2445
2793
|
id: String(msg.id),
|
|
2446
2794
|
timestamp: msg.timestamp.toISOString(),
|
|
2447
2795
|
participant: msg.participant,
|
|
2448
2796
|
author: authorName(msg),
|
|
2449
2797
|
channelId: getChannelId(msg) ?? null,
|
|
2450
|
-
|
|
2798
|
+
...windowField(mark),
|
|
2799
|
+
content: format === 'raw' ? msg.content : flattenContent(msg.content, failedOf(msg)),
|
|
2451
2800
|
};
|
|
2452
2801
|
}
|
|
2453
2802
|
|
|
2803
|
+
const NO_FAILURES: ReadonlySet<string> = new Set();
|
|
2804
|
+
|
|
2454
2805
|
/** Flatten a message's content blocks to one short, readable string. Text
|
|
2455
|
-
* blocks verbatim;
|
|
2456
|
-
*
|
|
2457
|
-
* matching, not
|
|
2458
|
-
|
|
2459
|
-
|
|
2806
|
+
* blocks verbatim; a speech tool's call (send-tools.ts) as its name and
|
|
2807
|
+
* words; everything else (tool_use/tool_result/thinking/media) as a short
|
|
2808
|
+
* bracketed label — this is for agent readability and search matching, not
|
|
2809
|
+
* byte-faithful reconstruction. */
|
|
2810
|
+
function flattenContent(content: ContentBlock[], failed: ReadonlySet<string> = NO_FAILURES): string {
|
|
2811
|
+
return content.map((block) => blockLabel(block, failed)).join(' ').trim();
|
|
2812
|
+
}
|
|
2813
|
+
|
|
2814
|
+
function blockSpans(content: ContentBlock[]): Array<{ block: ContentBlock; at: number; label: string }> {
|
|
2815
|
+
const labels = content.map((block) => blockLabel(block));
|
|
2816
|
+
const joined = labels.join(' ');
|
|
2817
|
+
let at = joined.trimStart().length - joined.length;
|
|
2818
|
+
return content.map((block, k) => {
|
|
2819
|
+
const span = { block, at, label: labels[k]! };
|
|
2820
|
+
at += span.label.length + 1;
|
|
2821
|
+
return span;
|
|
2822
|
+
});
|
|
2460
2823
|
}
|
|
2461
2824
|
|
|
2462
|
-
function blockLabel(block: ContentBlock): string {
|
|
2825
|
+
function blockLabel(block: ContentBlock, failed: ReadonlySet<string> = NO_FAILURES): string {
|
|
2463
2826
|
switch (block.type) {
|
|
2464
2827
|
case 'text':
|
|
2465
2828
|
return block.text;
|
|
2466
|
-
case 'tool_use':
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2829
|
+
case 'tool_use': {
|
|
2830
|
+
const said = speechOf(block.name, block.input);
|
|
2831
|
+
return said ? `[${block.name}${failed.has(block.id) ? ERROR_MARK : ''}] ${said.words}` : `[tool_use: ${block.name}]`;
|
|
2832
|
+
}
|
|
2833
|
+
case 'tool_result': {
|
|
2834
|
+
const mark = block.isError ? ERROR_MARK : '';
|
|
2835
|
+
return block.toolName ? `[tool_result: ${block.toolName}${mark}]` : `[tool_result${mark}]`;
|
|
2836
|
+
}
|
|
2470
2837
|
case 'thinking':
|
|
2471
2838
|
case 'redacted_thinking':
|
|
2472
2839
|
return '[thinking]';
|
|
@@ -2501,6 +2868,8 @@ interface SearchMatch {
|
|
|
2501
2868
|
/** Display name from metadata.author (null when absent — e.g. the agent's own turns; see participant). */
|
|
2502
2869
|
author: string | null;
|
|
2503
2870
|
channelId: string | null;
|
|
2871
|
+
/** Set only for a row the caller's window doesn't hold (#291; WindowMark). */
|
|
2872
|
+
window?: WindowMark;
|
|
2504
2873
|
snippet: string;
|
|
2505
2874
|
}
|
|
2506
2875
|
|