@animalabs/agent-framework 0.27.0 → 0.29.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 +145 -41
- package/dist/src/agent.d.ts +29 -2
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +73 -12
- package/dist/src/agent.js.map +1 -1
- package/dist/src/api/mcp-server-core.d.ts.map +1 -1
- package/dist/src/api/mcp-server-core.js +3 -12
- package/dist/src/api/mcp-server-core.js.map +1 -1
- package/dist/src/api/server.d.ts.map +1 -1
- package/dist/src/api/server.js +11 -5
- package/dist/src/api/server.js.map +1 -1
- package/dist/src/auto-continue.d.ts +5 -2
- package/dist/src/auto-continue.d.ts.map +1 -1
- package/dist/src/auto-continue.js +9 -5
- package/dist/src/auto-continue.js.map +1 -1
- package/dist/src/code-execution/isolate/runner.d.ts +7 -5
- package/dist/src/code-execution/isolate/runner.d.ts.map +1 -1
- package/dist/src/code-execution/isolate/runner.js +7 -5
- package/dist/src/code-execution/isolate/runner.js.map +1 -1
- package/dist/src/code-execution/isolate/scripts.d.ts.map +1 -1
- package/dist/src/code-execution/isolate/scripts.js +3 -2
- package/dist/src/code-execution/isolate/scripts.js.map +1 -1
- package/dist/src/cut-off-round.d.ts +24 -0
- package/dist/src/cut-off-round.d.ts.map +1 -0
- package/dist/src/cut-off-round.js +87 -0
- package/dist/src/cut-off-round.js.map +1 -0
- package/dist/src/framework.d.ts +468 -125
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +3651 -1111
- package/dist/src/framework.js.map +1 -1
- package/dist/src/gate/event-gate.d.ts +133 -13
- package/dist/src/gate/event-gate.d.ts.map +1 -1
- package/dist/src/gate/event-gate.js +654 -59
- package/dist/src/gate/event-gate.js.map +1 -1
- package/dist/src/gate/index.d.ts +3 -2
- package/dist/src/gate/index.d.ts.map +1 -1
- package/dist/src/gate/index.js +2 -1
- package/dist/src/gate/index.js.map +1 -1
- package/dist/src/gate/reply-judge.d.ts +116 -0
- package/dist/src/gate/reply-judge.d.ts.map +1 -0
- package/dist/src/gate/reply-judge.js +467 -0
- package/dist/src/gate/reply-judge.js.map +1 -0
- package/dist/src/gate/types.d.ts +173 -2
- package/dist/src/gate/types.d.ts.map +1 -1
- package/dist/src/heartbeat/heartbeat.d.ts +202 -0
- package/dist/src/heartbeat/heartbeat.d.ts.map +1 -0
- package/dist/src/heartbeat/heartbeat.js +778 -0
- package/dist/src/heartbeat/heartbeat.js.map +1 -0
- package/dist/src/heartbeat/reminder-time.d.ts +5 -0
- package/dist/src/heartbeat/reminder-time.d.ts.map +1 -0
- package/dist/src/heartbeat/reminder-time.js +107 -0
- package/dist/src/heartbeat/reminder-time.js.map +1 -0
- package/dist/src/held-writes.d.ts +3 -3
- package/dist/src/held-writes.js +3 -3
- package/dist/src/host-presentation.d.ts +170 -0
- package/dist/src/host-presentation.d.ts.map +1 -0
- package/dist/src/host-presentation.js +561 -0
- package/dist/src/host-presentation.js.map +1 -0
- package/dist/src/index.d.ts +5 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/line-reader.d.ts +4 -0
- package/dist/src/line-reader.d.ts.map +1 -0
- package/dist/src/line-reader.js +59 -0
- package/dist/src/line-reader.js.map +1 -0
- package/dist/src/mcpl/capability-mask.d.ts.map +1 -1
- package/dist/src/mcpl/capability-mask.js +3 -2
- package/dist/src/mcpl/capability-mask.js.map +1 -1
- package/dist/src/mcpl/channel-registry.d.ts +34 -6
- package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
- package/dist/src/mcpl/channel-registry.js +133 -54
- package/dist/src/mcpl/channel-registry.js.map +1 -1
- package/dist/src/mcpl/conversation-router.d.ts.map +1 -1
- package/dist/src/mcpl/conversation-router.js +2 -1
- package/dist/src/mcpl/conversation-router.js.map +1 -1
- package/dist/src/mcpl/convert-block.d.ts +1 -0
- package/dist/src/mcpl/convert-block.d.ts.map +1 -1
- package/dist/src/mcpl/convert-block.js +16 -4
- package/dist/src/mcpl/convert-block.js.map +1 -1
- package/dist/src/mcpl/hook-orchestrator.d.ts.map +1 -1
- package/dist/src/mcpl/hook-orchestrator.js +4 -3
- package/dist/src/mcpl/hook-orchestrator.js.map +1 -1
- package/dist/src/mcpl/inbound-source.d.ts +29 -11
- package/dist/src/mcpl/inbound-source.d.ts.map +1 -1
- package/dist/src/mcpl/inbound-source.js +43 -70
- package/dist/src/mcpl/inbound-source.js.map +1 -1
- package/dist/src/mcpl/modern-connection.d.ts.map +1 -1
- package/dist/src/mcpl/modern-connection.js +11 -7
- package/dist/src/mcpl/modern-connection.js.map +1 -1
- package/dist/src/mcpl/protocol-family.d.ts +3 -2
- package/dist/src/mcpl/protocol-family.d.ts.map +1 -1
- package/dist/src/mcpl/protocol-family.js +25 -3
- package/dist/src/mcpl/protocol-family.js.map +1 -1
- package/dist/src/mcpl/push-handler.js +2 -2
- package/dist/src/mcpl/push-handler.js.map +1 -1
- package/dist/src/mcpl/references.d.ts +12 -40
- package/dist/src/mcpl/references.d.ts.map +1 -1
- package/dist/src/mcpl/references.js +29 -23
- package/dist/src/mcpl/references.js.map +1 -1
- package/dist/src/mcpl/server-connection.d.ts +18 -8
- package/dist/src/mcpl/server-connection.d.ts.map +1 -1
- package/dist/src/mcpl/server-connection.js +70 -30
- package/dist/src/mcpl/server-connection.js.map +1 -1
- package/dist/src/mcpl/skip-reply-wake.d.ts +25 -0
- package/dist/src/mcpl/skip-reply-wake.d.ts.map +1 -1
- package/dist/src/mcpl/skip-reply-wake.js +25 -0
- package/dist/src/mcpl/skip-reply-wake.js.map +1 -1
- package/dist/src/mcpl/tags.d.ts +9 -2
- package/dist/src/mcpl/tags.d.ts.map +1 -1
- package/dist/src/mcpl/tags.js +12 -2
- package/dist/src/mcpl/tags.js.map +1 -1
- package/dist/src/mcpl/tool-presence.d.ts +66 -14
- package/dist/src/mcpl/tool-presence.d.ts.map +1 -1
- package/dist/src/mcpl/tool-presence.js +150 -80
- package/dist/src/mcpl/tool-presence.js.map +1 -1
- package/dist/src/mcpl/tool-signature.d.ts +10 -3
- package/dist/src/mcpl/tool-signature.d.ts.map +1 -1
- package/dist/src/mcpl/tool-signature.js +60 -30
- package/dist/src/mcpl/tool-signature.js.map +1 -1
- package/dist/src/mcpl/toolset-host.d.ts +88 -55
- package/dist/src/mcpl/toolset-host.d.ts.map +1 -1
- package/dist/src/mcpl/toolset-host.js +220 -111
- package/dist/src/mcpl/toolset-host.js.map +1 -1
- package/dist/src/mcpl/toolsets-module.d.ts +27 -35
- package/dist/src/mcpl/toolsets-module.d.ts.map +1 -1
- package/dist/src/mcpl/toolsets-module.js +313 -91
- package/dist/src/mcpl/toolsets-module.js.map +1 -1
- package/dist/src/mcpl/transport.d.ts +1 -1
- package/dist/src/mcpl/transport.d.ts.map +1 -1
- package/dist/src/mcpl/transport.js +16 -16
- package/dist/src/mcpl/transport.js.map +1 -1
- package/dist/src/mcpl/types.d.ts +31 -13
- 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 +10 -8
- package/dist/src/module-registry.d.ts.map +1 -1
- package/dist/src/module-registry.js +15 -9
- package/dist/src/module-registry.js.map +1 -1
- package/dist/src/modules/api/index.d.ts.map +1 -1
- package/dist/src/modules/api/index.js +4 -2
- package/dist/src/modules/api/index.js.map +1 -1
- package/dist/src/modules/discord/index.d.ts.map +1 -1
- package/dist/src/modules/discord/index.js +33 -25
- package/dist/src/modules/discord/index.js.map +1 -1
- package/dist/src/modules/history/index.d.ts.map +1 -1
- package/dist/src/modules/history/index.js +30 -17
- package/dist/src/modules/history/index.js.map +1 -1
- package/dist/src/modules/sender-name.d.ts +7 -0
- package/dist/src/modules/sender-name.d.ts.map +1 -0
- package/dist/src/modules/sender-name.js +10 -0
- package/dist/src/modules/sender-name.js.map +1 -0
- package/dist/src/modules/workspace/answers.d.ts +26 -0
- package/dist/src/modules/workspace/answers.d.ts.map +1 -0
- package/dist/src/modules/workspace/answers.js +138 -0
- package/dist/src/modules/workspace/answers.js.map +1 -0
- package/dist/src/modules/workspace/branch-intent.d.ts +5 -3
- package/dist/src/modules/workspace/branch-intent.d.ts.map +1 -1
- package/dist/src/modules/workspace/branch-intent.js +6 -2
- package/dist/src/modules/workspace/branch-intent.js.map +1 -1
- package/dist/src/modules/workspace/direct-write.d.ts +3 -0
- package/dist/src/modules/workspace/direct-write.d.ts.map +1 -1
- package/dist/src/modules/workspace/direct-write.js +59 -24
- package/dist/src/modules/workspace/direct-write.js.map +1 -1
- package/dist/src/modules/workspace/disk-agreement.d.ts +33 -11
- package/dist/src/modules/workspace/disk-agreement.d.ts.map +1 -1
- package/dist/src/modules/workspace/disk-agreement.js +48 -17
- package/dist/src/modules/workspace/disk-agreement.js.map +1 -1
- package/dist/src/modules/workspace/edit-text.d.ts +2 -1
- package/dist/src/modules/workspace/edit-text.d.ts.map +1 -1
- package/dist/src/modules/workspace/edit-text.js +4 -2
- package/dist/src/modules/workspace/edit-text.js.map +1 -1
- package/dist/src/modules/workspace/effects.d.ts +4 -2
- package/dist/src/modules/workspace/effects.d.ts.map +1 -1
- package/dist/src/modules/workspace/effects.js +67 -50
- package/dist/src/modules/workspace/effects.js.map +1 -1
- package/dist/src/modules/workspace/index.d.ts +63 -51
- package/dist/src/modules/workspace/index.d.ts.map +1 -1
- package/dist/src/modules/workspace/index.js +980 -547
- package/dist/src/modules/workspace/index.js.map +1 -1
- package/dist/src/modules/workspace/observe.d.ts +125 -82
- package/dist/src/modules/workspace/observe.d.ts.map +1 -1
- package/dist/src/modules/workspace/observe.js +465 -241
- package/dist/src/modules/workspace/observe.js.map +1 -1
- package/dist/src/modules/workspace/read-page.d.ts.map +1 -1
- package/dist/src/modules/workspace/read-page.js +7 -1
- package/dist/src/modules/workspace/read-page.js.map +1 -1
- package/dist/src/modules/workspace/reconcile.d.ts +7 -1
- package/dist/src/modules/workspace/reconcile.d.ts.map +1 -1
- package/dist/src/modules/workspace/reconcile.js +156 -50
- package/dist/src/modules/workspace/reconcile.js.map +1 -1
- package/dist/src/modules/workspace/result-text.d.ts +1 -0
- package/dist/src/modules/workspace/result-text.d.ts.map +1 -1
- package/dist/src/modules/workspace/result-text.js.map +1 -1
- package/dist/src/modules/workspace/sync.d.ts +9 -0
- package/dist/src/modules/workspace/sync.d.ts.map +1 -1
- package/dist/src/modules/workspace/sync.js +14 -0
- package/dist/src/modules/workspace/sync.js.map +1 -1
- package/dist/src/modules/workspace/types.d.ts +96 -43
- 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.map +1 -1
- package/dist/src/modules/workspace/watcher.js +8 -3
- package/dist/src/modules/workspace/watcher.js.map +1 -1
- package/dist/src/notice-tags.d.ts +79 -0
- package/dist/src/notice-tags.d.ts.map +1 -0
- package/dist/src/notice-tags.js +83 -0
- package/dist/src/notice-tags.js.map +1 -0
- package/dist/src/prose-drafts.d.ts +4 -3
- package/dist/src/prose-drafts.d.ts.map +1 -1
- package/dist/src/prose-drafts.js +3 -2
- package/dist/src/prose-drafts.js.map +1 -1
- package/dist/src/reasoning-loss-notice.d.ts +23 -0
- package/dist/src/reasoning-loss-notice.d.ts.map +1 -0
- package/dist/src/reasoning-loss-notice.js +52 -0
- package/dist/src/reasoning-loss-notice.js.map +1 -0
- package/dist/src/reasoning-loss.d.ts +24 -0
- package/dist/src/reasoning-loss.d.ts.map +1 -0
- package/dist/src/reasoning-loss.js +102 -0
- package/dist/src/reasoning-loss.js.map +1 -0
- package/dist/src/send-tools.d.ts +3 -2
- package/dist/src/send-tools.d.ts.map +1 -1
- package/dist/src/send-tools.js +3 -2
- package/dist/src/send-tools.js.map +1 -1
- package/dist/src/sent-rows.js +1 -1
- package/dist/src/silent-heartbeat.d.ts +1 -53
- package/dist/src/silent-heartbeat.d.ts.map +1 -1
- package/dist/src/silent-heartbeat.js +56 -3
- package/dist/src/silent-heartbeat.js.map +1 -1
- package/dist/src/supplied-text.d.ts +35 -0
- package/dist/src/supplied-text.d.ts.map +1 -0
- package/dist/src/supplied-text.js +141 -0
- package/dist/src/supplied-text.js.map +1 -0
- package/dist/src/thinking-drops.d.ts +4 -11
- package/dist/src/thinking-drops.d.ts.map +1 -1
- package/dist/src/thinking-drops.js +7 -14
- package/dist/src/thinking-drops.js.map +1 -1
- package/dist/src/tool-args.d.ts +12 -0
- package/dist/src/tool-args.d.ts.map +1 -1
- package/dist/src/tool-args.js +39 -4
- package/dist/src/tool-args.js.map +1 -1
- package/dist/src/tool-image-ledger.d.ts.map +1 -1
- package/dist/src/tool-image-ledger.js +2 -1
- package/dist/src/tool-image-ledger.js.map +1 -1
- package/dist/src/tool-presentation.d.ts.map +1 -1
- package/dist/src/tool-presentation.js +5 -4
- package/dist/src/tool-presentation.js.map +1 -1
- package/dist/src/tool-result-guard.d.ts +8 -4
- package/dist/src/tool-result-guard.d.ts.map +1 -1
- package/dist/src/tool-result-guard.js +23 -11
- package/dist/src/tool-result-guard.js.map +1 -1
- package/dist/src/tune-out/coordinator.d.ts +3 -2
- package/dist/src/tune-out/coordinator.d.ts.map +1 -1
- package/dist/src/tune-out/coordinator.js +24 -26
- package/dist/src/tune-out/coordinator.js.map +1 -1
- package/dist/src/tune-out/tools.d.ts +4 -1
- package/dist/src/tune-out/tools.d.ts.map +1 -1
- package/dist/src/types/agent.d.ts +63 -18
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/events.d.ts +18 -8
- package/dist/src/types/events.d.ts.map +1 -1
- package/dist/src/types/framework.d.ts +58 -0
- package/dist/src/types/framework.d.ts.map +1 -1
- package/dist/src/types/module.d.ts +27 -13
- package/dist/src/types/module.d.ts.map +1 -1
- package/dist/src/types/trace.d.ts +34 -1
- package/dist/src/types/trace.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/agent.ts +76 -12
- package/src/api/mcp-server-core.ts +3 -14
- package/src/api/server.ts +11 -5
- package/src/auto-continue.ts +12 -5
- package/src/code-execution/isolate/runner.ts +7 -5
- package/src/code-execution/isolate/scripts.ts +3 -2
- package/src/cut-off-round.ts +98 -0
- package/src/framework.ts +3772 -1138
- package/src/gate/event-gate.ts +705 -60
- package/src/gate/index.ts +9 -1
- package/src/gate/reply-judge.ts +512 -0
- package/src/gate/types.ts +176 -3
- package/src/heartbeat/heartbeat.ts +843 -0
- package/src/heartbeat/reminder-time.ts +107 -0
- package/src/held-writes.ts +3 -3
- package/src/host-presentation.ts +605 -0
- package/src/index.ts +5 -1
- package/src/line-reader.ts +58 -0
- package/src/mcpl/capability-mask.ts +3 -2
- package/src/mcpl/channel-registry.ts +139 -53
- package/src/mcpl/conversation-router.ts +2 -1
- package/src/mcpl/convert-block.ts +17 -4
- package/src/mcpl/hook-orchestrator.ts +4 -3
- package/src/mcpl/inbound-source.ts +45 -70
- package/src/mcpl/modern-connection.ts +11 -7
- package/src/mcpl/protocol-family.ts +24 -3
- package/src/mcpl/push-handler.ts +2 -2
- package/src/mcpl/references.ts +31 -23
- package/src/mcpl/server-connection.ts +76 -34
- package/src/mcpl/skip-reply-wake.ts +35 -0
- package/src/mcpl/tags.ts +13 -2
- package/src/mcpl/tool-presence.ts +149 -82
- package/src/mcpl/tool-signature.ts +64 -32
- package/src/mcpl/toolset-host.ts +212 -110
- package/src/mcpl/toolsets-module.ts +277 -91
- package/src/mcpl/transport.ts +18 -14
- package/src/mcpl/types.ts +32 -13
- package/src/module-registry.ts +14 -9
- package/src/modules/api/index.ts +4 -2
- package/src/modules/discord/index.ts +37 -25
- package/src/modules/history/index.ts +30 -18
- package/src/modules/sender-name.ts +9 -0
- package/src/modules/workspace/answers.ts +151 -0
- package/src/modules/workspace/branch-intent.ts +10 -5
- package/src/modules/workspace/direct-write.ts +59 -23
- package/src/modules/workspace/disk-agreement.ts +52 -21
- package/src/modules/workspace/edit-text.ts +7 -5
- package/src/modules/workspace/effects.ts +69 -50
- package/src/modules/workspace/index.ts +966 -556
- package/src/modules/workspace/observe.ts +497 -242
- package/src/modules/workspace/read-page.ts +7 -1
- package/src/modules/workspace/reconcile.ts +184 -49
- package/src/modules/workspace/result-text.ts +2 -0
- package/src/modules/workspace/sync.ts +16 -0
- package/src/modules/workspace/types.ts +96 -43
- package/src/modules/workspace/watcher.ts +8 -3
- package/src/notice-tags.ts +84 -0
- package/src/prose-drafts.ts +4 -3
- package/src/reasoning-loss-notice.ts +64 -0
- package/src/reasoning-loss.ts +120 -0
- package/src/send-tools.ts +3 -2
- package/src/sent-rows.ts +1 -1
- package/src/silent-heartbeat.ts +4 -3
- package/src/supplied-text.ts +154 -0
- package/src/thinking-drops.ts +10 -16
- package/src/tool-args.ts +38 -4
- package/src/tool-image-ledger.ts +2 -1
- package/src/tool-presentation.ts +5 -4
- package/src/tool-result-guard.ts +26 -11
- package/src/tune-out/coordinator.ts +25 -27
- package/src/tune-out/tools.ts +4 -1
- package/src/types/agent.ts +63 -18
- package/src/types/events.ts +18 -8
- package/src/types/framework.ts +59 -0
- package/src/types/module.ts +28 -13
- package/src/types/trace.ts +36 -1
|
@@ -43,6 +43,41 @@ export function readSkipReplyWake(input: unknown): SkipReplyWake {
|
|
|
43
43
|
return { kind: 'wake', seconds: Math.floor(Math.min(SKIP_REPLY_WAKE_MAX_SECONDS, seconds)) };
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
/**
|
|
47
|
+
* A wake the heartbeat has due before a skip_reply's self-wake: its next tick
|
|
48
|
+
* or a reminder, one the agent's rules would let wake it as they stand when
|
|
49
|
+
* the self-wake is armed. `what` names it to the agent ("your heartbeat
|
|
50
|
+
* (…)"); `inSeconds` is how far off it is then.
|
|
51
|
+
*/
|
|
52
|
+
export interface SkipReplyEarlierWake {
|
|
53
|
+
inSeconds: number;
|
|
54
|
+
what: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The note a skip_reply's result carries: that the turn ended, and, when the
|
|
59
|
+
* agent set a wake, when it fires unless something wakes the agent first.
|
|
60
|
+
* When the heartbeat has a wake due sooner, the note adds it, and what it
|
|
61
|
+
* would mean: any turn start cancels a pending self-wake
|
|
62
|
+
* (EventGate.onInferenceStarted), so a timer the agent still wants after the
|
|
63
|
+
* heartbeat's turn is one it sets again then, for the time that was left.
|
|
64
|
+
*
|
|
65
|
+
* It says when the heartbeat is due, not that it will wake the agent: the
|
|
66
|
+
* rules can change before then, a rate limit can have no token left, a
|
|
67
|
+
* gate.js or a host's inference policy can hold the wake back, and a batch or
|
|
68
|
+
* a queued request can wake the agent sooner. Each of those leaves the note
|
|
69
|
+
* true.
|
|
70
|
+
*/
|
|
71
|
+
export function skipReplyNote(selfWakeSeconds: number | undefined, earlier?: SkipReplyEarlierWake): string {
|
|
72
|
+
if (selfWakeSeconds === undefined) return 'Turn ended; nothing sent.';
|
|
73
|
+
const note = `Turn ended; nothing sent. Self-wake in ~${selfWakeSeconds}s unless something wakes you first.`;
|
|
74
|
+
if (earlier === undefined) return note;
|
|
75
|
+
const what = earlier.what.charAt(0).toUpperCase() + earlier.what.slice(1);
|
|
76
|
+
const left = Math.max(1, selfWakeSeconds - earlier.inSeconds);
|
|
77
|
+
return `${note} ${what} is due sooner, in ~${earlier.inSeconds}s; if it wakes you, the self-wake is cancelled, `
|
|
78
|
+
+ `so set it again for the ~${left}s left if you still want it.`;
|
|
79
|
+
}
|
|
80
|
+
|
|
46
81
|
/** The refusal the model reads when its host has no EventGate to set a timer. */
|
|
47
82
|
export const SKIP_REPLY_WAKE_NO_GATE_REFUSAL =
|
|
48
83
|
"Your turn didn't end, and no wake was set: this host can't wake you on a timer, so skip_reply can't take wake_in_seconds here. To end the turn and wait for the next message, call skip_reply without it. Anything you meant to come back to, do now.";
|
package/src/mcpl/tags.ts
CHANGED
|
@@ -21,8 +21,10 @@ const CORE_IMPLIES: Readonly<Record<string, readonly string[]>> = {
|
|
|
21
21
|
/**
|
|
22
22
|
* RFC-001 core tags that mark a message as being ABOUT the conversation
|
|
23
23
|
* rather than part of it: reactions (added or removed), edits and deletions
|
|
24
|
-
* of an existing message
|
|
25
|
-
*
|
|
24
|
+
* of an existing message, and (revision 3) `chat:system` notices from the
|
|
25
|
+
* platform or the place: a join, a pin, the weather changing in a world.
|
|
26
|
+
* These are markers, not turns — they must not clear explicit-send
|
|
27
|
+
* suppression, count as a new conversational round, or
|
|
26
28
|
* retarget the default publish channel. An edit can also be the first
|
|
27
29
|
* version of a message the agent sees, and then counts as that message:
|
|
28
30
|
* isMarkerTagged reads the host's mark for the case the coalescer knows
|
|
@@ -36,6 +38,7 @@ export const NON_CONVERSATIONAL_TAGS: ReadonlySet<string> = new Set([
|
|
|
36
38
|
'chat:reaction-remove',
|
|
37
39
|
'chat:edited',
|
|
38
40
|
'chat:deleted',
|
|
41
|
+
'chat:system',
|
|
39
42
|
]);
|
|
40
43
|
|
|
41
44
|
/** True when a (possibly untyped) tag list carries a non-conversational marker tag. */
|
|
@@ -54,11 +57,19 @@ export function isMarkerTagged(tags: unknown, firstView: boolean): boolean {
|
|
|
54
57
|
return hasNonConversationalTag(firstView && Array.isArray(tags) ? tags.filter((t) => t !== 'chat:edited') : tags);
|
|
55
58
|
}
|
|
56
59
|
|
|
60
|
+
/** True when a (possibly untyped) tag list marks a `chat:system` notice: an
|
|
61
|
+
* event about the place, not a message anyone sent (RFC-001 rev 3). */
|
|
62
|
+
export function isSystemNotice(tags: unknown): boolean {
|
|
63
|
+
return Array.isArray(tags) && tags.includes('chat:system');
|
|
64
|
+
}
|
|
65
|
+
|
|
57
66
|
/**
|
|
58
67
|
* Expand a raw tag list over the normative core closure, then resolve the
|
|
59
68
|
* §16.2 mutual exclusion: `chat:addressed` and `chat:ambient` cannot both
|
|
60
69
|
* stand after expansion, and addressed wins — a message that is both
|
|
61
70
|
* specifically-to-you and background noise is specifically-to-you.
|
|
71
|
+
* `chat:system` is another facet (kind, not addressing) and stands beside
|
|
72
|
+
* either: a pin in a DM is addressed by its place and still a notice.
|
|
62
73
|
*
|
|
63
74
|
* Pure; input order is preserved for the tags that survive, with implied
|
|
64
75
|
* tags appended in first-cause order. Unknown and producer-namespaced tags
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
*
|
|
26
26
|
* Ported from mcpl-cc-bridge's presence.ts, the reference host.
|
|
27
27
|
*/
|
|
28
|
-
import { objectSignature, renderToolLines, toolDigest, type HintedTool } from './tool-signature.js';
|
|
28
|
+
import { objectSignature, renderToolLines, toolDigest, type HintedTool, type Shown } from './tool-signature.js';
|
|
29
|
+
import { nameToTypeBack, quotedValue, suppliedValue } from '../supplied-text.js';
|
|
29
30
|
|
|
30
31
|
export interface ToolsetDecl {
|
|
31
32
|
title?: string;
|
|
@@ -69,15 +70,20 @@ export interface Change {
|
|
|
69
70
|
/** The visible tools after the change. */
|
|
70
71
|
now: HintedTool[];
|
|
71
72
|
}
|
|
72
|
-
export interface ChangeSet {
|
|
73
|
+
export interface ChangeSet {
|
|
74
|
+
changes: Change[];
|
|
75
|
+
suggest: Suggestion[];
|
|
76
|
+
notes: string[];
|
|
77
|
+
/** Ended records' notes, by id: rendered while the id is untold, told once the set reaches an agent (markTold). */
|
|
78
|
+
ended?: Array<{ id: string; note: string }>;
|
|
79
|
+
/** false when its taker showed the change without its blocks (a look that restates the present shows the live catalog instead): told() then counts none of its definitions shown, while a notice that tells it still shows them. */
|
|
80
|
+
blocksShown?: false;
|
|
81
|
+
}
|
|
73
82
|
|
|
74
83
|
export const emptyChange = (): ChangeSet => ({ changes: [], suggest: [], notes: [] });
|
|
75
|
-
export const isEmptyChange = (c: ChangeSet): boolean => !c.changes.length && !c.suggest.length && !c.notes.length;
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
suggest: sets.flatMap((s) => s.suggest),
|
|
79
|
-
notes: sets.flatMap((s) => s.notes),
|
|
80
|
-
});
|
|
84
|
+
export const isEmptyChange = (c: ChangeSet): boolean => !c.changes.length && !c.suggest.length && !c.notes.length && !c.ended?.length;
|
|
85
|
+
/** Whether a change to one key shows that key's tools in a block: what render prints and markShown counts as shown. */
|
|
86
|
+
const showsBlock = (c: Change): boolean => !c.closed && (c.opened || c.appeared.length > 0 || c.redefined.length > 0 || c.gone.length > 0);
|
|
81
87
|
|
|
82
88
|
const isObj = (v: unknown): v is Record<string, unknown> => typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
83
89
|
const arr = (v: unknown): unknown[] => (Array.isArray(v) ? v : []);
|
|
@@ -90,7 +96,7 @@ const str = (v: unknown): string | undefined => (typeof v === 'string' && v ? v
|
|
|
90
96
|
* the last + 1 (a message was lost unread): recover.
|
|
91
97
|
* behind: at or below the last, not applied: recover.
|
|
92
98
|
*/
|
|
93
|
-
export interface ApplyOutcome { applied: boolean; skipped: boolean; behind: boolean; suggest: Suggestion[] }
|
|
99
|
+
export interface ApplyOutcome { applied: boolean; skipped: boolean; behind: boolean; suggest: Suggestion[]; unlisted: boolean }
|
|
94
100
|
|
|
95
101
|
/** What a hint `definition` may change (RFC §6.1): everything else stays the listed tool's. */
|
|
96
102
|
interface DefOverride { description?: string; inputSchema?: Record<string, unknown>; annotations?: Record<string, unknown> }
|
|
@@ -117,10 +123,10 @@ export class ToolPresence {
|
|
|
117
123
|
private visible = new Map<string, Set<string>>();
|
|
118
124
|
/** Definition overrides from hints: per key (an instance's own, or a toolset's), per tool. */
|
|
119
125
|
private defs = new Map<string, Map<string, DefOverride>>();
|
|
120
|
-
/** `ended` ids told to the agent
|
|
126
|
+
/** `ended` ids told to the agent, kept while the server still reports them (RFC §5.3). */
|
|
121
127
|
private toldEnded = new Set<string>();
|
|
128
|
+
/** Moves a tools/list made that the model's tool list doesn't show yet: told once a rebuild has published them. */
|
|
122
129
|
private pendingMoves = new Map<string, Presence>();
|
|
123
|
-
private moved = new Map<string, Presence>();
|
|
124
130
|
/** Digests of definitions the model has been shown with their first sentence. */
|
|
125
131
|
readonly shown = new Set<string>();
|
|
126
132
|
|
|
@@ -132,6 +138,8 @@ export class ToolPresence {
|
|
|
132
138
|
private readonly allowed: (tool: string) => boolean = () => true,
|
|
133
139
|
/** How a static tool is named in the API tool list, for `look`. */
|
|
134
140
|
private readonly listedName: (tool: string) => string = (tool) => tool,
|
|
141
|
+
/** Where the model's published tool list last put a tool, if it's there (shownAs). */
|
|
142
|
+
private readonly published: (tool: string) => Presence | undefined = () => undefined,
|
|
135
143
|
) {}
|
|
136
144
|
|
|
137
145
|
// ── connection lifecycle ──
|
|
@@ -166,6 +174,7 @@ export class ToolPresence {
|
|
|
166
174
|
}
|
|
167
175
|
}
|
|
168
176
|
this.declared = decl;
|
|
177
|
+
for (const name of Object.keys(decl)) this.groups.delete(name);
|
|
169
178
|
this.surface = mcpl && isObj(mcpl.surface) ? { title: str(mcpl.surface.title), summary: str(mcpl.surface.summary) } : null;
|
|
170
179
|
// RFC §5.1: removing a toolset, or turning its instances on or off, ends
|
|
171
180
|
// its open instances; one that becomes undeclared starts closed under the
|
|
@@ -190,21 +199,35 @@ export class ToolPresence {
|
|
|
190
199
|
this.visible.clear();
|
|
191
200
|
this.defs.clear();
|
|
192
201
|
this.pendingMoves.clear();
|
|
193
|
-
this.moved.clear();
|
|
194
202
|
}
|
|
195
203
|
|
|
196
204
|
/** tools/list: every definition, visible or not (RFC §4.1). A tool the host's policy refuses is left out. */
|
|
197
205
|
setTools(list: HintedTool[]): void {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
206
|
+
const seen = new Map([...this.tools.keys()].map((name) => [name, this.presenceOf(name)]));
|
|
207
|
+
const named = list.filter((t) => isObj(t) && typeof t.name === 'string');
|
|
208
|
+
this.tools = new Map();
|
|
209
|
+
for (const t of named) if (this.allowed(t.name) && !this.tools.has(t.name)) this.tools.set(t.name, t);
|
|
201
210
|
for (const set of this.visible.values()) for (const n of [...set]) if (!this.tools.has(n)) set.delete(n);
|
|
211
|
+
// RFC §4.1: the list states each tool's presence, and presence follows
|
|
212
|
+
// it. A changed presence is a move the model's tool list shows at its
|
|
213
|
+
// next rebuild (§4.2; applyMoves); one the list reverses before then
|
|
214
|
+
// never reached that list.
|
|
215
|
+
for (const name of [...this.pendingMoves.keys()]) if (!this.tools.has(name)) this.pendingMoves.delete(name);
|
|
216
|
+
for (const name of this.active ? this.tools.keys() : []) {
|
|
217
|
+
const was = seen.get(name);
|
|
218
|
+
const listed = this.listedPresence(name);
|
|
219
|
+
if (this.pendingMoves.has(name)) {
|
|
220
|
+
if (this.pendingMoves.get(name) !== listed) this.pendingMoves.delete(name);
|
|
221
|
+
} else if (was !== undefined && was !== listed) {
|
|
222
|
+
this.pendingMoves.set(name, listed);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
202
225
|
// The list's `mcpl/visible` is the server's word only where nothing else
|
|
203
226
|
// speaks: toolset-less dynamic tools and undeclared groups. For a declared
|
|
204
227
|
// toolset it's an aggregate ("visible in some open instance"); per-instance
|
|
205
228
|
// state comes from toolsets/list and hint changes (RFC §4.1, §5.3).
|
|
206
229
|
for (const t of this.tools.values()) {
|
|
207
|
-
if (this.
|
|
230
|
+
if (this.listedPresence(t.name) !== 'dynamic') continue;
|
|
208
231
|
const ts = this.toolsetOf(t.name);
|
|
209
232
|
if (ts && this.isDeclared(ts)) continue;
|
|
210
233
|
this.setVisible(keyOf(ts ?? NO_TOOLSET), t.name, t._meta?.['mcpl/visible'] !== false);
|
|
@@ -234,12 +257,23 @@ export class ToolPresence {
|
|
|
234
257
|
return m && typeof m['mcpl/toolset'] === 'string' && m['mcpl/toolset'] ? (m['mcpl/toolset'] as string) : undefined;
|
|
235
258
|
}
|
|
236
259
|
|
|
237
|
-
/**
|
|
260
|
+
/** As the list states it: a toolset's tool is dynamic whatever a hint says (RFC §4.1). */
|
|
238
261
|
presenceOf(name: string): Presence {
|
|
239
262
|
if (!this.active) return 'static';
|
|
263
|
+
return this.listedPresence(name);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** RFC §4.2: "Until it applies the move, the tool keeps working where it
|
|
267
|
+
* is." Whether a tool is at `place` in the tool list last published, or
|
|
268
|
+
* in the server's latest tools/list (its presence). Until a rebuild
|
|
269
|
+
* applies a move the two differ, and a call is taken at either. */
|
|
270
|
+
shownAs(name: string, place: Presence): boolean {
|
|
271
|
+
if (!this.active) return place === 'static';
|
|
272
|
+
return this.published(name) === place || this.listedPresence(name) === place;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
private listedPresence(name: string): Presence {
|
|
240
276
|
if (this.toolsetOf(name)) return 'dynamic';
|
|
241
|
-
const moved = this.moved.get(name);
|
|
242
|
-
if (moved) return moved;
|
|
243
277
|
return this.tools.get(name)?._meta?.['mcpl/presence'] === 'dynamic' ? 'dynamic' : 'static';
|
|
244
278
|
}
|
|
245
279
|
|
|
@@ -332,12 +366,12 @@ export class ToolPresence {
|
|
|
332
366
|
|
|
333
367
|
/** Apply a HintChanges (RFC §6.1) from any of its carriers, as it is read (see ApplyOutcome). */
|
|
334
368
|
apply(h: unknown): ApplyOutcome {
|
|
335
|
-
const none: ApplyOutcome = { applied: false, skipped: false, behind: false, suggest: [] };
|
|
369
|
+
const none: ApplyOutcome = { applied: false, skipped: false, behind: false, suggest: [], unlisted: false };
|
|
336
370
|
if (!isObj(h) || typeof h.rev !== 'number' || !Number.isFinite(h.rev)) return none;
|
|
337
371
|
if (h.rev <= this.rev) return { ...none, behind: true };
|
|
338
372
|
const skipped = h.rev !== this.rev + 1;
|
|
339
373
|
this.rev = h.rev;
|
|
340
|
-
return { ...none, applied: true, skipped,
|
|
374
|
+
return { ...none, applied: true, skipped, ...this.applyBody(h) };
|
|
341
375
|
}
|
|
342
376
|
|
|
343
377
|
/**
|
|
@@ -352,7 +386,8 @@ export class ToolPresence {
|
|
|
352
386
|
return lost;
|
|
353
387
|
}
|
|
354
388
|
|
|
355
|
-
private applyBody(h: Record<string, unknown>): Suggestion[] {
|
|
389
|
+
private applyBody(h: Record<string, unknown>): { suggest: Suggestion[]; unlisted: boolean } {
|
|
390
|
+
let unlisted = false;
|
|
356
391
|
for (const e of arr(h.toolsets)) {
|
|
357
392
|
if (!isObj(e) || !str(e.toolset) || !this.isDeclared(e.toolset as string)) continue; // undeclared toolsets are host-local
|
|
358
393
|
const ts = e.toolset as string;
|
|
@@ -366,20 +401,20 @@ export class ToolPresence {
|
|
|
366
401
|
else if (e.state === 'closed') this.dropKey(key);
|
|
367
402
|
}
|
|
368
403
|
for (const e of arr(h.tools)) {
|
|
369
|
-
if (!isObj(e) || !str(e.name)
|
|
404
|
+
if (!isObj(e) || !str(e.name)) continue;
|
|
370
405
|
const name = e.name as string;
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
if (e.presence !== this.presenceOf(name)) this.pendingMoves.set(name, e.presence);
|
|
375
|
-
else this.pendingMoves.delete(name);
|
|
406
|
+
if (!this.tools.has(name)) {
|
|
407
|
+
if (this.allowed(name)) unlisted = true;
|
|
408
|
+
continue;
|
|
376
409
|
}
|
|
377
|
-
|
|
410
|
+
// A hint's presence stages nothing: a hinted move takes effect when
|
|
411
|
+
// the server's tools/list states it (RFC §4.1), as the rebuild's own
|
|
412
|
+
// list does. A definition it gives is the tool's current one, kept
|
|
413
|
+
// whatever its presence now, for when it's shown dynamic (§6.1).
|
|
414
|
+
if (isObj(e.definition)) {
|
|
378
415
|
const key = this.defKeyFor(name, str(e.instance));
|
|
379
|
-
if (key
|
|
380
|
-
|
|
381
|
-
this.defs.get(key)!.set(name, overrideOf(e.definition));
|
|
382
|
-
}
|
|
416
|
+
if (!this.defs.has(key)) this.defs.set(key, new Map());
|
|
417
|
+
this.defs.get(key)!.set(name, overrideOf(e.definition));
|
|
383
418
|
}
|
|
384
419
|
if (typeof e.visible === 'boolean') for (const key of this.keysFor(name, str(e.instance))) this.setVisible(key, name, e.visible);
|
|
385
420
|
}
|
|
@@ -388,14 +423,14 @@ export class ToolPresence {
|
|
|
388
423
|
if (!isObj(s) || !str(s.toolset) || (s.action !== 'open' && s.action !== 'close')) continue;
|
|
389
424
|
suggest.push({ toolset: s.toolset as string, ...(str(s.instance) ? { instance: str(s.instance) } : {}), action: s.action, reason: str(s.reason) ?? '' });
|
|
390
425
|
}
|
|
391
|
-
return suggest;
|
|
426
|
+
return { suggest, unlisted };
|
|
392
427
|
}
|
|
393
428
|
|
|
394
429
|
/**
|
|
395
430
|
* Where a definition lands (RFC §6.1): with `instance`, that instance's own;
|
|
396
431
|
* without, the toolset's (for a toolset without instances, its only one's).
|
|
397
432
|
*/
|
|
398
|
-
private defKeyFor(name: string, instance: string | undefined): string
|
|
433
|
+
private defKeyFor(name: string, instance: string | undefined): string {
|
|
399
434
|
const ts = this.toolsetOf(name);
|
|
400
435
|
if (!ts) return keyOf(NO_TOOLSET);
|
|
401
436
|
return keyOf(ts, this.declared[ts]?.instances ? instance : undefined);
|
|
@@ -445,7 +480,7 @@ export class ToolPresence {
|
|
|
445
480
|
* Returns what was lost, what the server says ended while no connection was
|
|
446
481
|
* told, its suggestions, and whether its rev skipped one.
|
|
447
482
|
*/
|
|
448
|
-
adopt(result: unknown): { lost: OpenRecord[]; ended: Ended[]; suggest: Suggestion[]; skipped: boolean } {
|
|
483
|
+
adopt(result: unknown): { lost: OpenRecord[]; ended: Ended[]; suggest: Suggestion[]; skipped: boolean; unlisted: boolean } {
|
|
449
484
|
const had = [...this.open.values()];
|
|
450
485
|
// A full statement: a dynamic tool not stated visible is invisible.
|
|
451
486
|
// Undeclared groups stay as the host has them open or closed.
|
|
@@ -463,9 +498,10 @@ export class ToolPresence {
|
|
|
463
498
|
}
|
|
464
499
|
let suggest: Suggestion[] = [];
|
|
465
500
|
let skipped = false;
|
|
501
|
+
let unlisted = false;
|
|
466
502
|
const h = isObj(r._meta) ? r._meta['mcpl/hints'] : undefined;
|
|
467
503
|
if (isObj(h)) {
|
|
468
|
-
suggest = this.applyBody(h);
|
|
504
|
+
({ suggest, unlisted } = this.applyBody(h));
|
|
469
505
|
// Its own rev, at its place in order (RFC §6.1).
|
|
470
506
|
if (typeof h.rev === 'number' && Number.isFinite(h.rev)) {
|
|
471
507
|
skipped = h.rev !== this.rev + 1;
|
|
@@ -477,29 +513,51 @@ export class ToolPresence {
|
|
|
477
513
|
if (!isObj(e) || !str(e.toolset)) continue;
|
|
478
514
|
ended.push({ ...(str(e.id) ? { id: str(e.id) } : {}), toolset: e.toolset as string, ...(str(e.instance) ? { instance: str(e.instance) } : {}), reason: str(e.reason) ?? '' });
|
|
479
515
|
}
|
|
480
|
-
|
|
516
|
+
// RFC §5.3: a record is reported on every toolsets/list until it expires, and its id is never
|
|
517
|
+
// reused, so an id the server no longer reports needn't be remembered.
|
|
518
|
+
const reported = new Set(ended.flatMap((e) => (e.id ? [e.id] : [])));
|
|
519
|
+
for (const id of [...this.toldEnded]) if (!reported.has(id)) this.toldEnded.delete(id);
|
|
520
|
+
return { lost: had.filter((o) => !this.open.has(keyOf(o.toolset, o.instance))), ended, suggest, skipped, unlisted };
|
|
481
521
|
}
|
|
482
522
|
|
|
483
523
|
/**
|
|
484
524
|
* What the agent should hear about an adoption: what ended while it was
|
|
485
|
-
* away, each `ended` id
|
|
486
|
-
*
|
|
525
|
+
* away, each `ended` id until it has been told (RFC §5.3; markTold), and
|
|
526
|
+
* what is closed now. An ended record with an id is in `ended`, rendered
|
|
527
|
+
* while its id is untold.
|
|
487
528
|
*/
|
|
488
|
-
adoptionNotes(lost: OpenRecord[], ended: Ended[]): string[] {
|
|
529
|
+
adoptionNotes(lost: OpenRecord[], ended: Ended[]): { notes: string[]; ended: Array<{ id: string; note: string }> } {
|
|
489
530
|
const notes: string[] = [];
|
|
490
|
-
const
|
|
531
|
+
const told: Array<{ id: string; note: string }> = [];
|
|
532
|
+
const endedKeys = new Set<string>();
|
|
491
533
|
for (const e of ended) {
|
|
534
|
+
if (!e.id || !this.toldEnded.has(e.id)) endedKeys.add(keyOf(e.toolset, e.instance));
|
|
535
|
+
const note = `${this.label(keyOf(e.toolset, e.instance))} ended${e.reason ? `: ${suppliedValue(e.reason)}` : ''}.`;
|
|
492
536
|
if (e.id) {
|
|
493
|
-
if (this.toldEnded.has(e.id))
|
|
494
|
-
|
|
495
|
-
}
|
|
496
|
-
notes.push(`${this.label(keyOf(e.toolset, e.instance))} ended${e.reason ? `: ${e.reason}` : ''}.`);
|
|
537
|
+
if (!this.toldEnded.has(e.id)) told.push({ id: e.id, note });
|
|
538
|
+
} else notes.push(note);
|
|
497
539
|
}
|
|
498
540
|
for (const o of lost) {
|
|
499
541
|
if (endedKeys.has(keyOf(o.toolset, o.instance))) continue;
|
|
500
542
|
notes.push(`${this.label(keyOf(o.toolset, o.instance))} is closed: ${this.server} no longer has it open. ${this.openHint(o.toolset)} starts a new one.`);
|
|
501
543
|
}
|
|
502
|
-
return notes;
|
|
544
|
+
return { notes, ended: told };
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/** The agent has been told these ended records (a notice written, a result it reads); the next statement forgets any it no longer reports. */
|
|
548
|
+
markTold(ended: ChangeSet['ended']): void {
|
|
549
|
+
for (const { id } of ended ?? []) this.toldEnded.add(id);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/** The definitions a change set's tool blocks describe have reached the model. */
|
|
553
|
+
markShown(cs: ChangeSet): void {
|
|
554
|
+
for (const c of cs.changes) if (showsBlock(c)) for (const t of c.now) this.shown.add(toolDigest(t));
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/** A set's ended notes whose ids are still untold, each once. */
|
|
558
|
+
untoldNotes(cs: ChangeSet): string[] {
|
|
559
|
+
const seen = new Set<string>();
|
|
560
|
+
return (cs.ended ?? []).filter(({ id }) => !this.toldEnded.has(id) && !seen.has(id) && !!seen.add(id)).map(({ note }) => note);
|
|
503
561
|
}
|
|
504
562
|
|
|
505
563
|
// ── host-made opens of undeclared groups (RFC §5.4) ──
|
|
@@ -514,15 +572,15 @@ export class ToolPresence {
|
|
|
514
572
|
|
|
515
573
|
// ── boundaries ──
|
|
516
574
|
|
|
517
|
-
/**
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
}
|
|
525
|
-
this.pendingMoves.
|
|
575
|
+
/** The static↔dynamic moves the lists made since the last rebuild, still to be shown. */
|
|
576
|
+
moves(): Array<{ name: string; to: Presence }> {
|
|
577
|
+
return [...this.pendingMoves].map(([name, to]) => ({ name, to }));
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/** At a rebuild of the model's tool list (RFC §4.2): those of `moves` still pending, now shown. */
|
|
581
|
+
applyMoves(moves = this.moves()): Array<{ name: string; to: Presence }> {
|
|
582
|
+
const out = moves.filter(({ name, to }) => this.pendingMoves.get(name) === to);
|
|
583
|
+
for (const { name } of out) this.pendingMoves.delete(name);
|
|
526
584
|
return out;
|
|
527
585
|
}
|
|
528
586
|
|
|
@@ -531,60 +589,67 @@ export class ToolPresence {
|
|
|
531
589
|
/** The marker a key's announcements begin with, `[server/toolset:instance]`, which a reminder looks for. */
|
|
532
590
|
label(key: string): string {
|
|
533
591
|
const { toolset, instance } = splitKey(key);
|
|
534
|
-
return `[${this.server}${toolset ? `/${toolset}` : ''}${instance ? `:${instance}` : ''}]`;
|
|
592
|
+
return `[${this.server}${toolset ? `/${nameToTypeBack(toolset)}` : ''}${instance ? `:${nameToTypeBack(instance)}` : ''}]`;
|
|
535
593
|
}
|
|
536
594
|
|
|
537
595
|
callHint(key: string): string {
|
|
538
596
|
const { instance } = splitKey(key);
|
|
539
|
-
return `${this.names.act} {server:"${this.server}"${instance ? `, instance
|
|
597
|
+
return `${this.names.act} {server:"${this.server}"${instance ? `, instance:${quotedValue(instance)}` : ''}, tool, args}`;
|
|
540
598
|
}
|
|
541
599
|
|
|
542
600
|
closeHint(key: string): string | null {
|
|
543
601
|
const { toolset, instance } = splitKey(key);
|
|
544
602
|
if (!toolset) return null;
|
|
545
|
-
return `${this.names.close} {server:"${this.server}", toolset
|
|
603
|
+
return `${this.names.close} {server:"${this.server}", toolset:${quotedValue(toolset)}${instance ? `, instance:${quotedValue(instance)}` : ''}}`;
|
|
546
604
|
}
|
|
547
605
|
|
|
548
606
|
openHint(toolset: string, instance?: string): string {
|
|
549
|
-
return `${this.names.open} {server:"${this.server}", toolset
|
|
607
|
+
return `${this.names.open} {server:"${this.server}", toolset:${quotedValue(toolset)}${instance ? `, instance:${quotedValue(instance)}` : ''}}`;
|
|
550
608
|
}
|
|
551
609
|
|
|
552
610
|
/** The tool block for one key: its label, how to call, and the visible tools (new or changed ones described). */
|
|
553
|
-
toolBlock(key: string, tools: HintedTool[], opts: { describeAll?: boolean; headed?: boolean; title?: string } = {}): string[] {
|
|
554
|
-
const head = opts.headed === false ? '' : `${this.label(key)}${opts.title ? ` ${opts.title}` : ''} `;
|
|
611
|
+
toolBlock(key: string, tools: HintedTool[], opts: { describeAll?: boolean; headed?: boolean; title?: string; seen?: Shown } = {}): string[] {
|
|
612
|
+
const head = opts.headed === false ? '' : `${this.label(key)}${opts.title ? ` ${suppliedValue(opts.title)}` : ''} `;
|
|
555
613
|
if (!tools.length) return [`${head}no tools available right now.`];
|
|
556
|
-
return [`${head}tools now — ${this.callHint(key)}:`, ...renderToolLines(tools, this.shown, { describeAll: opts.describeAll })];
|
|
614
|
+
return [`${head}tools now — ${this.callHint(key)}:`, ...renderToolLines(tools, opts.seen ?? this.shown, { describeAll: opts.describeAll })];
|
|
557
615
|
}
|
|
558
616
|
|
|
559
|
-
/**
|
|
560
|
-
|
|
617
|
+
/**
|
|
618
|
+
* What the model reads for a change set. `bare`: a key whose header the
|
|
619
|
+
* caller already printed. `mark: false` renders without counting the
|
|
620
|
+
* definitions it describes as shown, for text that may not reach the model
|
|
621
|
+
* (markShown counts them once it has). `describeAll`: every tool with its
|
|
622
|
+
* first sentence, as for a result that may reach no agent.
|
|
623
|
+
*/
|
|
624
|
+
render(cs: ChangeSet, opts: { bare?: string; mark?: boolean; describeAll?: boolean } = {}): string {
|
|
625
|
+
const local = new Set<string>();
|
|
626
|
+
const seen: Shown = opts.mark === false ? { has: (d) => this.shown.has(d) || local.has(d), add: (d) => { local.add(d); } } : this.shown;
|
|
561
627
|
const lines: string[] = [];
|
|
562
628
|
for (const c of cs.changes) {
|
|
563
629
|
const bare = opts.bare === c.key;
|
|
564
630
|
if (c.closed) {
|
|
565
|
-
lines.push(`${this.label(c.key)} closed.${c.gone.length ? ` Now unavailable: ${c.gone.join(', ')}.` : ''}`);
|
|
631
|
+
lines.push(`${this.label(c.key)} closed.${c.gone.length ? ` Now unavailable: ${c.gone.map(suppliedValue).join(', ')}.` : ''}`);
|
|
566
632
|
continue;
|
|
567
633
|
}
|
|
568
634
|
const title = (c.opened || c.retitled) && !bare ? c.title : undefined;
|
|
569
|
-
|
|
570
|
-
if (
|
|
571
|
-
|
|
572
|
-
if (c.gone.length) lines.push(`now unavailable: ${c.gone.join(', ')}`);
|
|
635
|
+
if (showsBlock(c)) lines.push(...this.toolBlock(c.key, c.now, { headed: !bare, title, seen, describeAll: opts.describeAll }));
|
|
636
|
+
else if (title) lines.push(`${this.label(c.key)} ${suppliedValue(title)}`);
|
|
637
|
+
if (c.gone.length) lines.push(`now unavailable: ${c.gone.map(suppliedValue).join(', ')}`);
|
|
573
638
|
if (c.opened && !bare) {
|
|
574
639
|
const close = this.closeHint(c.key);
|
|
575
640
|
if (close) lines.push(`${close} closes it.`);
|
|
576
641
|
}
|
|
577
642
|
}
|
|
578
643
|
for (const s of cs.suggest) lines.push(this.renderSuggestion(s));
|
|
579
|
-
lines.push(...cs.notes);
|
|
644
|
+
lines.push(...this.untoldNotes(cs), ...cs.notes);
|
|
580
645
|
return lines.join('\n');
|
|
581
646
|
}
|
|
582
647
|
|
|
583
648
|
/** RFC §6.2: a suggestion is shown, never acted on. */
|
|
584
649
|
renderSuggestion(s: Suggestion): string {
|
|
585
|
-
const what = `${s.toolset}${s.instance ? `:${s.instance}` : ''}
|
|
650
|
+
const what = suppliedValue(`${s.toolset}${s.instance ? `:${s.instance}` : ''}`);
|
|
586
651
|
const how = s.action === 'open' ? this.openHint(s.toolset, s.instance) : this.closeHint(keyOf(s.toolset, s.instance));
|
|
587
|
-
return `${this.server} suggests ${s.action === 'open' ? 'opening' : 'closing'} ${what}${s.reason ? `: ${s.reason}` : ''} — ${how} if you want to (nothing was done).`;
|
|
652
|
+
return `${this.server} suggests ${s.action === 'open' ? 'opening' : 'closing'} ${what}${s.reason ? `: ${suppliedValue(s.reason)}` : ''} — ${how} if you want to (nothing was done).`;
|
|
588
653
|
}
|
|
589
654
|
|
|
590
655
|
/** The keys a reminder restates: every open key of this server's view. */
|
|
@@ -605,14 +670,16 @@ export class ToolPresence {
|
|
|
605
670
|
return lines.join('\n');
|
|
606
671
|
}
|
|
607
672
|
|
|
608
|
-
/** look naming this server: what is open (with tools) and what can be
|
|
609
|
-
|
|
673
|
+
/** look naming this server: what is open (with tools) and what can be
|
|
674
|
+
* opened. `mark: false`, as for render, for a look whose result may
|
|
675
|
+
* reach no agent. */
|
|
676
|
+
catalog(opts: { mark?: boolean } = {}): string {
|
|
610
677
|
const lines: string[] = [];
|
|
611
|
-
lines.push(this.surface ? `${this.server} — ${[this.surface.title, this.surface.summary].filter(Boolean).join(': ')}` : this.server);
|
|
678
|
+
lines.push(this.surface ? `${this.server} — ${[this.surface.title, this.surface.summary].filter(Boolean).map(suppliedValue).join(': ')}` : this.server);
|
|
612
679
|
const v = this.view();
|
|
613
680
|
if (v.size) {
|
|
614
681
|
for (const [key, e] of v) {
|
|
615
|
-
lines.push(...this.toolBlock(key, [...e.tools.values()], { describeAll: true, title: e.title }).map((l) => ` ${l}`));
|
|
682
|
+
lines.push(...this.toolBlock(key, [...e.tools.values()], { describeAll: true, title: e.title, seen: opts.mark === false ? new Set<string>() : undefined }).map((l) => ` ${l}`));
|
|
616
683
|
const close = this.closeHint(key);
|
|
617
684
|
if (close) lines.push(` ${close} closes it.`);
|
|
618
685
|
}
|
|
@@ -623,7 +690,7 @@ export class ToolPresence {
|
|
|
623
690
|
for (const [name, d] of Object.entries(this.declared)) {
|
|
624
691
|
if (!d.instances && this.isOpen(name)) continue;
|
|
625
692
|
openable.push(
|
|
626
|
-
` ${name}${d.title ? ` — ${d.title}` : ''}${d.summary ? `: ${d.summary}` : ''}` +
|
|
693
|
+
` ${nameToTypeBack(name)}${d.title ? ` — ${suppliedValue(d.title)}` : ''}${d.summary ? `: ${suppliedValue(d.summary)}` : ''}` +
|
|
627
694
|
(d.instances ? ' (several can be open at once; name each with instance)' : '') +
|
|
628
695
|
(d.options ? ` · options ${objectSignature(d.options)}` : ''),
|
|
629
696
|
);
|
|
@@ -631,7 +698,7 @@ export class ToolPresence {
|
|
|
631
698
|
for (const g of this.undeclaredGroups) {
|
|
632
699
|
if (this.groups.has(g)) continue;
|
|
633
700
|
const names = this.allTools.filter((t) => this.toolsetOf(t.name) === g).map((t) => t.name);
|
|
634
|
-
openable.push(` ${g}: ${names.join(', ')}`);
|
|
701
|
+
openable.push(` ${nameToTypeBack(g)}: ${names.map(nameToTypeBack).join(', ')}`);
|
|
635
702
|
}
|
|
636
703
|
if (openable.length) lines.push(`can open — ${this.names.open} {server:"${this.server}", toolset, instance?, options?}:`, ...openable);
|
|
637
704
|
return lines.join('\n');
|
|
@@ -639,12 +706,12 @@ export class ToolPresence {
|
|
|
639
706
|
|
|
640
707
|
/** Where a tool can be called from now, for `look` naming it. */
|
|
641
708
|
toolStatus(tool: string): string {
|
|
642
|
-
if (this.presenceOf(tool) === 'static') return `static —
|
|
709
|
+
if (this.presenceOf(tool) === 'static') return `static — called by name, as ${nameToTypeBack(this.listedName(tool))}`;
|
|
643
710
|
const where = [...this.view().entries()].filter(([, e]) => e.tools.has(tool)).map(([k]) => k);
|
|
644
711
|
const ts = this.toolsetOf(tool);
|
|
645
712
|
return where.length
|
|
646
|
-
? `available now in ${where.map((k) => this.label(k)).join(', ')} — ${this.names.act} {server:"${this.server}", tool
|
|
647
|
-
: `not available right now${ts ? ` (toolset ${ts})` : ''}`;
|
|
713
|
+
? `available now in ${where.map((k) => this.label(k)).join(', ')} — ${this.names.act} {server:"${this.server}", tool:${quotedValue(tool)}${where.length > 1 ? ', instance' : ''}, args}`
|
|
714
|
+
: `not available right now${ts ? ` (toolset ${nameToTypeBack(ts)})` : ''}`;
|
|
648
715
|
}
|
|
649
716
|
|
|
650
717
|
}
|