@animalabs/agent-framework 0.27.0 → 0.28.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 +56 -15
- package/dist/src/agent.d.ts +29 -2
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +69 -11
- package/dist/src/agent.js.map +1 -1
- package/dist/src/api/server.d.ts.map +1 -1
- package/dist/src/api/server.js +4 -2
- package/dist/src/api/server.js.map +1 -1
- package/dist/src/auto-continue.d.ts +3 -2
- package/dist/src/auto-continue.d.ts.map +1 -1
- package/dist/src/auto-continue.js +4 -3
- package/dist/src/auto-continue.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 +304 -89
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +2577 -678
- package/dist/src/framework.js.map +1 -1
- package/dist/src/gate/event-gate.d.ts +99 -3
- package/dist/src/gate/event-gate.d.ts.map +1 -1
- package/dist/src/gate/event-gate.js +538 -13
- package/dist/src/gate/event-gate.js.map +1 -1
- package/dist/src/gate/index.d.ts +2 -1
- package/dist/src/gate/index.d.ts.map +1 -1
- package/dist/src/gate/index.js +1 -0
- 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 +161 -0
- package/dist/src/gate/types.d.ts.map +1 -1
- package/dist/src/heartbeat/heartbeat.d.ts +184 -0
- package/dist/src/heartbeat/heartbeat.d.ts.map +1 -0
- package/dist/src/heartbeat/heartbeat.js +752 -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/index.d.ts +4 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -0
- package/dist/src/index.js.map +1 -1
- 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.map +1 -1
- package/dist/src/mcpl/channel-registry.js +2 -1
- package/dist/src/mcpl/channel-registry.js.map +1 -1
- package/dist/src/mcpl/inbound-source.d.ts +26 -10
- package/dist/src/mcpl/inbound-source.d.ts.map +1 -1
- package/dist/src/mcpl/inbound-source.js +40 -69
- package/dist/src/mcpl/inbound-source.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 +50 -10
- package/dist/src/mcpl/server-connection.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 +134 -65
- 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 +47 -18
- package/dist/src/mcpl/tool-signature.js.map +1 -1
- package/dist/src/mcpl/toolset-host.d.ts +88 -37
- package/dist/src/mcpl/toolset-host.d.ts.map +1 -1
- package/dist/src/mcpl/toolset-host.js +207 -108
- package/dist/src/mcpl/toolset-host.js.map +1 -1
- package/dist/src/mcpl/toolsets-module.d.ts +14 -11
- package/dist/src/mcpl/toolsets-module.d.ts.map +1 -1
- package/dist/src/mcpl/toolsets-module.js +141 -49
- package/dist/src/mcpl/toolsets-module.js.map +1 -1
- package/dist/src/mcpl/types.d.ts +3 -1
- package/dist/src/mcpl/types.d.ts.map +1 -1
- package/dist/src/mcpl/types.js.map +1 -1
- package/dist/src/module-registry.d.ts +4 -1
- package/dist/src/module-registry.d.ts.map +1 -1
- package/dist/src/module-registry.js +9 -2
- package/dist/src/module-registry.js.map +1 -1
- package/dist/src/modules/history/index.d.ts.map +1 -1
- package/dist/src/modules/history/index.js +22 -11
- package/dist/src/modules/history/index.js.map +1 -1
- package/dist/src/modules/workspace/answers.d.ts +22 -0
- package/dist/src/modules/workspace/answers.d.ts.map +1 -0
- package/dist/src/modules/workspace/answers.js +123 -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 +57 -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.map +1 -1
- package/dist/src/modules/workspace/effects.js +20 -13
- package/dist/src/modules/workspace/effects.js.map +1 -1
- package/dist/src/modules/workspace/index.d.ts +62 -51
- package/dist/src/modules/workspace/index.d.ts.map +1 -1
- package/dist/src/modules/workspace/index.js +934 -531
- package/dist/src/modules/workspace/index.js.map +1 -1
- package/dist/src/modules/workspace/observe.d.ts +75 -63
- package/dist/src/modules/workspace/observe.d.ts.map +1 -1
- package/dist/src/modules/workspace/observe.js +366 -180
- 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.map +1 -1
- package/dist/src/modules/workspace/reconcile.js +26 -16
- 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/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 +50 -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/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 +35 -1
- package/dist/src/tool-args.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/tools.d.ts +4 -1
- package/dist/src/tune-out/tools.d.ts.map +1 -1
- package/dist/src/types/agent.d.ts +40 -9
- 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 +15 -0
- package/dist/src/types/framework.d.ts.map +1 -1
- package/dist/src/types/module.d.ts +5 -2
- 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 +72 -11
- package/src/api/server.ts +4 -2
- package/src/auto-continue.ts +4 -3
- package/src/cut-off-round.ts +98 -0
- package/src/framework.ts +2694 -700
- package/src/gate/event-gate.ts +579 -15
- package/src/gate/index.ts +8 -0
- package/src/gate/reply-judge.ts +512 -0
- package/src/gate/types.ts +164 -1
- package/src/heartbeat/heartbeat.ts +804 -0
- package/src/heartbeat/reminder-time.ts +107 -0
- package/src/index.ts +4 -0
- package/src/mcpl/capability-mask.ts +3 -2
- package/src/mcpl/channel-registry.ts +2 -1
- package/src/mcpl/inbound-source.ts +42 -69
- package/src/mcpl/server-connection.ts +56 -16
- package/src/mcpl/tags.ts +13 -2
- package/src/mcpl/tool-presence.ts +133 -67
- package/src/mcpl/tool-signature.ts +51 -20
- package/src/mcpl/toolset-host.ts +197 -107
- package/src/mcpl/toolsets-module.ts +143 -49
- package/src/mcpl/types.ts +3 -1
- package/src/module-registry.ts +8 -2
- package/src/modules/history/index.ts +22 -12
- package/src/modules/workspace/answers.ts +133 -0
- package/src/modules/workspace/branch-intent.ts +10 -5
- package/src/modules/workspace/direct-write.ts +58 -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 +21 -12
- package/src/modules/workspace/index.ts +927 -541
- package/src/modules/workspace/observe.ts +381 -177
- package/src/modules/workspace/read-page.ts +7 -1
- package/src/modules/workspace/reconcile.ts +27 -15
- 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/prose-drafts.ts +4 -3
- package/src/reasoning-loss-notice.ts +62 -0
- package/src/reasoning-loss.ts +120 -0
- package/src/send-tools.ts +3 -2
- package/src/sent-rows.ts +1 -1
- package/src/thinking-drops.ts +10 -16
- package/src/tool-args.ts +34 -1
- package/src/tool-result-guard.ts +26 -11
- package/src/tune-out/tools.ts +4 -1
- package/src/types/agent.ts +40 -9
- package/src/types/events.ts +18 -8
- package/src/types/framework.ts +15 -0
- package/src/types/module.ts +5 -2
- package/src/types/trace.ts +36 -1
|
@@ -1,5 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCPL RFC-014, host side: per server, the controller that feeds the
|
|
3
|
+
* server's tool presence (tool-presence.ts) from what its connection reads,
|
|
4
|
+
* sends toolsets/open, close and list, recovers from a lost rev, and renders
|
|
5
|
+
* what changed; and the hub that holds every server's controller for the
|
|
6
|
+
* meta-tools (toolsets-module.ts) and for reminders.
|
|
7
|
+
*
|
|
8
|
+
* Hint changes apply as their message is read (RFC §6.1); a static↔dynamic
|
|
9
|
+
* move takes effect when a tools/list states it (§4.1), and the model's tool
|
|
10
|
+
* list shows it at its next rebuild; until then the tool is called at either
|
|
11
|
+
* place (§4.2). The connection's read tap hands
|
|
12
|
+
* the controller every result of a hinted request (awaited or not, while no
|
|
13
|
+
* more than 1,000 hinted requests await an answer), each tools/list whole, every push/event and every tools/hints, of the
|
|
14
|
+
* current transport, synchronously and in arrival order, before anything
|
|
15
|
+
* routes the message or anything read after it is handled.
|
|
16
|
+
* What a message's changes did to the model's view is kept by the object
|
|
17
|
+
* that carried them (`readChanges`), so whoever later handles that object
|
|
18
|
+
* (the tool result, the push, the meta-tool) renders it next to the content.
|
|
19
|
+
*
|
|
20
|
+
* Reminders (RFC §8.3): after each compile built to send, the hub reads
|
|
21
|
+
* which open toolsets' markers (`[server/toolset:instance]`) the request
|
|
22
|
+
* carried. A marker an agent has had in view, gone from a later request
|
|
23
|
+
* (compressed, or out of the window), makes a reminder due for that agent,
|
|
24
|
+
* which the toolsets module gathers at its next start as a stored
|
|
25
|
+
* `afterUser` injection.
|
|
26
|
+
*/
|
|
27
|
+
import { McplRequestError } from './server-connection.js';
|
|
1
28
|
import { isToolAllowed } from './tool-policy.js';
|
|
2
|
-
import {
|
|
29
|
+
import { redactServerText } from './protocol-family.js';
|
|
30
|
+
import { logDetail, logValueRedacted } from './log-value.js';
|
|
31
|
+
import { ToolPresence, emptyChange, isEmptyChange, keyOf, } from './tool-presence.js';
|
|
3
32
|
/** The meta-tools' module, whose tools are `toolsets--open` and so on. */
|
|
4
33
|
export const TOOLSETS_MODULE = 'toolsets';
|
|
5
34
|
export const META_TOOL_NAMES = {
|
|
@@ -8,7 +37,7 @@ export const META_TOOL_NAMES = {
|
|
|
8
37
|
look: `${TOOLSETS_MODULE}--look`,
|
|
9
38
|
close: `${TOOLSETS_MODULE}--close`,
|
|
10
39
|
};
|
|
11
|
-
/** How long an open
|
|
40
|
+
/** How long an open or close is waited for. A late result still applies when it's read. */
|
|
12
41
|
export const TOOLSET_REQUEST_TIMEOUT_MS = 60_000;
|
|
13
42
|
/** A toolsets/list after a (re)connect or in a recovery. */
|
|
14
43
|
const LIST_TIMEOUT_MS = 15_000;
|
|
@@ -19,8 +48,8 @@ const RECOVERY_DEBOUNCE_MS = 200;
|
|
|
19
48
|
const isObj = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
20
49
|
const metaHints = (res) => (isObj(res._meta) ? res._meta['mcpl/hints'] : undefined);
|
|
21
50
|
/** One server's RFC-014 state and traffic. Lives as long as the server's
|
|
22
|
-
* configuration, across reconnects and restarts (its `ended`
|
|
23
|
-
* the model was shown carry over). */
|
|
51
|
+
* configuration, across reconnects and the server's restarts (its `ended`
|
|
52
|
+
* ids and what the model was shown carry over), in memory only. */
|
|
24
53
|
export class ToolsetController {
|
|
25
54
|
serverId;
|
|
26
55
|
config;
|
|
@@ -31,16 +60,10 @@ export class ToolsetController {
|
|
|
31
60
|
epoch = -1;
|
|
32
61
|
/** What applying a message's hints changed, by the object that carried them. */
|
|
33
62
|
readChanges = new WeakMap();
|
|
34
|
-
/** Results whose change no one has
|
|
35
|
-
unclaimed = new
|
|
63
|
+
/** Results whose change no one has taken yet, in the order they were read, with their method (tellUnlessClaimed). */
|
|
64
|
+
unclaimed = new Map();
|
|
36
65
|
/** eventIds of pushes read on this transport (bounded): a repeat's hints are ignored (RFC §6.1). */
|
|
37
66
|
seenEvents = new Set();
|
|
38
|
-
/** A paged tools/list being read. */
|
|
39
|
-
listPages = null;
|
|
40
|
-
/** Lists this controller asked for itself, whose change it delivers. */
|
|
41
|
-
ownLists = 0;
|
|
42
|
-
/** What lists read while one of its own was out changed, for it to tell. */
|
|
43
|
-
ownListChange = null;
|
|
44
67
|
recoveryTimer = null;
|
|
45
68
|
recovering = false;
|
|
46
69
|
recoverAgain = false;
|
|
@@ -48,11 +71,17 @@ export class ToolsetController {
|
|
|
48
71
|
this.serverId = serverId;
|
|
49
72
|
this.config = config;
|
|
50
73
|
this.deps = deps;
|
|
51
|
-
this.presence = new ToolPresence(serverId, META_TOOL_NAMES, (tool) => isToolAllowed(tool, this.config), (tool) => deps.listedName(serverId, tool));
|
|
74
|
+
this.presence = new ToolPresence(serverId, META_TOOL_NAMES, (tool) => isToolAllowed(tool, this.config), (tool) => deps.listedName(serverId, tool), (tool) => deps.published?.(serverId, tool));
|
|
52
75
|
}
|
|
53
76
|
log(line) {
|
|
54
77
|
this.deps.log(`${this.serverId}: ${line}`);
|
|
55
78
|
}
|
|
79
|
+
field(value) {
|
|
80
|
+
return logValueRedacted(value, (text) => redactServerText(text, this.config));
|
|
81
|
+
}
|
|
82
|
+
failure(error) {
|
|
83
|
+
return logDetail(redactServerText(error instanceof Error ? error.message : String(error), this.config));
|
|
84
|
+
}
|
|
56
85
|
/** A restart can bring a new configuration under the same id. */
|
|
57
86
|
setConfig(config) {
|
|
58
87
|
this.config = config;
|
|
@@ -67,6 +96,10 @@ export class ToolsetController {
|
|
|
67
96
|
this.connection = connection;
|
|
68
97
|
connection.setReadTap((read, epoch) => this.onRead(connection, read, epoch));
|
|
69
98
|
}
|
|
99
|
+
release() {
|
|
100
|
+
if (this.connection)
|
|
101
|
+
this.detach(this.connection);
|
|
102
|
+
}
|
|
70
103
|
/** The server's connection is gone for good (disconnected, removed). */
|
|
71
104
|
detach(connection) {
|
|
72
105
|
if (this.connection !== connection)
|
|
@@ -89,7 +122,7 @@ export class ToolsetController {
|
|
|
89
122
|
live() {
|
|
90
123
|
const c = this.connection;
|
|
91
124
|
if (!c || !c.isConnected)
|
|
92
|
-
throw new
|
|
125
|
+
throw new McplRequestError(`${this.serverId} is not connected right now; try again later`, 'not-sent');
|
|
93
126
|
this.sync(c.transportEpoch, c);
|
|
94
127
|
return c;
|
|
95
128
|
}
|
|
@@ -103,7 +136,6 @@ export class ToolsetController {
|
|
|
103
136
|
return;
|
|
104
137
|
this.epoch = epoch;
|
|
105
138
|
this.seenEvents = new Set();
|
|
106
|
-
this.listPages = null;
|
|
107
139
|
this.presence.beginConnection(connection.toolsetDeclarations, true);
|
|
108
140
|
}
|
|
109
141
|
// ── RFC §6.1: apply on arrival ──
|
|
@@ -112,7 +144,7 @@ export class ToolsetController {
|
|
|
112
144
|
return;
|
|
113
145
|
this.sync(epoch, connection);
|
|
114
146
|
if (read.kind === 'result') {
|
|
115
|
-
this.readResult(read.method, read.params, read.result, read.awaited);
|
|
147
|
+
this.readResult(read.method, read.params, read.result, read.awaited, read.straddled === true);
|
|
116
148
|
return;
|
|
117
149
|
}
|
|
118
150
|
if (!this.presence.active || !isObj(read.params))
|
|
@@ -129,7 +161,7 @@ export class ToolsetController {
|
|
|
129
161
|
// RFC §7: a push carrying hints is never coalesced; when its changes
|
|
130
162
|
// would apply is undefined, so they're ignored and the state re-read.
|
|
131
163
|
if (p.coalesce !== undefined && p.coalesce !== null) {
|
|
132
|
-
this.log(`push ${
|
|
164
|
+
this.log(`push ${this.field(p.eventId ?? '')} carries hints and coalesce (RFC-014 §7 forbids it): its hints are ignored; recovering`);
|
|
133
165
|
this.scheduleRecovery();
|
|
134
166
|
return;
|
|
135
167
|
}
|
|
@@ -137,7 +169,7 @@ export class ToolsetController {
|
|
|
137
169
|
const eventId = typeof p.eventId === 'string' || typeof p.eventId === 'number' ? String(p.eventId) : '';
|
|
138
170
|
if (eventId) {
|
|
139
171
|
if (this.seenEvents.has(eventId)) {
|
|
140
|
-
this.log(`push ${eventId} was read before: its hints are ignored`);
|
|
172
|
+
this.log(`push ${this.field(eventId)} was read before: its hints are ignored`);
|
|
141
173
|
return;
|
|
142
174
|
}
|
|
143
175
|
this.seenEvents.add(eventId);
|
|
@@ -145,28 +177,22 @@ export class ToolsetController {
|
|
|
145
177
|
this.seenEvents.delete(this.seenEvents.values().next().value);
|
|
146
178
|
}
|
|
147
179
|
// Applied now, before (and whatever) the host decides about the push.
|
|
148
|
-
|
|
180
|
+
const cs = this.applyHints(p.hints, 'push');
|
|
181
|
+
this.readChanges.set(p, cs);
|
|
182
|
+
this.tellUnlessClaimed(p, cs, 'push/event');
|
|
149
183
|
}
|
|
150
|
-
readResult(method, params, res, awaited) {
|
|
184
|
+
readResult(method, params, res, awaited, straddled) {
|
|
151
185
|
let cs;
|
|
152
186
|
switch (method) {
|
|
153
187
|
case 'tools/list': {
|
|
154
|
-
|
|
155
|
-
// A first page (no cursor) starts the list; a page with no next cursor ends it.
|
|
156
|
-
if (params.cursor === undefined || this.listPages === null)
|
|
157
|
-
this.listPages = [];
|
|
158
|
-
this.listPages.push(...page);
|
|
159
|
-
if (res.nextCursor !== undefined && res.nextCursor !== null)
|
|
188
|
+
if (!Array.isArray(res.tools))
|
|
160
189
|
return;
|
|
161
|
-
|
|
162
|
-
this.
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
this.deliver(cs);
|
|
168
|
-
else
|
|
169
|
-
this.ownListChange = joinChanges(this.ownListChange ?? emptyChange(), cs);
|
|
190
|
+
cs = this.readToolList(res.tools, res);
|
|
191
|
+
if (straddled && !this.recovering) {
|
|
192
|
+
this.log('tools/list pages carried different revs, so its earlier pages are older than changes read between them: recovering');
|
|
193
|
+
this.scheduleRecovery();
|
|
194
|
+
}
|
|
195
|
+
this.deliver(cs);
|
|
170
196
|
return;
|
|
171
197
|
}
|
|
172
198
|
case 'toolsets/list':
|
|
@@ -218,9 +244,9 @@ export class ToolsetController {
|
|
|
218
244
|
if (h === undefined || !this.presence.active)
|
|
219
245
|
return emptyChange();
|
|
220
246
|
const before = this.presence.view();
|
|
221
|
-
const last = this.presence.rev;
|
|
247
|
+
const last = this.field(this.presence.rev);
|
|
222
248
|
const r = this.presence.apply(h);
|
|
223
|
-
const rev =
|
|
249
|
+
const rev = this.field(h?.rev);
|
|
224
250
|
if (r.behind) {
|
|
225
251
|
this.log(`hint change rev ${rev} (${source}) is not after rev ${last}: ignored; recovering from tools/list and toolsets/list`);
|
|
226
252
|
this.scheduleRecovery();
|
|
@@ -232,6 +258,10 @@ export class ToolsetController {
|
|
|
232
258
|
else if (!r.applied) {
|
|
233
259
|
this.log(`a malformed hint change (${source}) was ignored`);
|
|
234
260
|
}
|
|
261
|
+
if (r.unlisted) {
|
|
262
|
+
this.log(`hint change rev ${rev} (${source}) names a tool tools/list hasn't listed: recovering from tools/list and toolsets/list`);
|
|
263
|
+
this.scheduleRecovery();
|
|
264
|
+
}
|
|
235
265
|
const cs = this.presence.diff(before, this.presence.view());
|
|
236
266
|
cs.suggest.push(...r.suggest);
|
|
237
267
|
return cs;
|
|
@@ -241,7 +271,7 @@ export class ToolsetController {
|
|
|
241
271
|
const before = this.presence.view();
|
|
242
272
|
this.presence.setTools(tools);
|
|
243
273
|
if (this.presence.active && this.presence.noteListRev(isObj(last._meta) ? last._meta['mcpl/rev'] : undefined)) {
|
|
244
|
-
this.log(`tools/list was written after rev ${
|
|
274
|
+
this.log(`tools/list was written after rev ${this.field(isObj(last._meta) ? last._meta['mcpl/rev'] : '')}, past the last rev read: a message was lost; recovering`);
|
|
245
275
|
this.scheduleRecovery();
|
|
246
276
|
}
|
|
247
277
|
return this.presence.diff(before, this.presence.view());
|
|
@@ -250,37 +280,79 @@ export class ToolsetController {
|
|
|
250
280
|
readToolsetList(res) {
|
|
251
281
|
if (!this.presence.active)
|
|
252
282
|
return emptyChange();
|
|
283
|
+
if (!Array.isArray(res.open)) {
|
|
284
|
+
this.log('a toolsets/list result with no open list was ignored');
|
|
285
|
+
return emptyChange();
|
|
286
|
+
}
|
|
253
287
|
const before = this.presence.view();
|
|
254
|
-
const { lost, ended, suggest, skipped } = this.presence.adopt(res);
|
|
288
|
+
const { lost, ended, suggest, skipped, unlisted } = this.presence.adopt(res);
|
|
255
289
|
if (skipped && !this.recovering) {
|
|
256
290
|
// Per-instance state is restated in full; what else was lost (presence) comes from tools/list.
|
|
257
291
|
this.log('toolsets/list skipped a rev: re-reading tools/list');
|
|
258
292
|
this.scheduleRecovery();
|
|
259
293
|
}
|
|
294
|
+
if (unlisted && !this.recovering) {
|
|
295
|
+
this.log('toolsets/list names a tool tools/list hasn\'t listed: re-reading tools/list');
|
|
296
|
+
this.scheduleRecovery();
|
|
297
|
+
}
|
|
260
298
|
const cs = this.presence.diff(before, this.presence.view());
|
|
261
299
|
cs.suggest.push(...suggest);
|
|
262
300
|
// A lost toolset gets its note, not also a "closed" line.
|
|
263
301
|
cs.changes = cs.changes.filter((c) => !lost.some((o) => keyOf(o.toolset, o.instance) === c.key));
|
|
264
|
-
|
|
302
|
+
const told = this.presence.adoptionNotes(lost, ended);
|
|
303
|
+
cs.notes.push(...told.notes);
|
|
304
|
+
if (told.ended.length)
|
|
305
|
+
cs.ended = [...(cs.ended ?? []), ...told.ended];
|
|
265
306
|
return cs;
|
|
266
307
|
}
|
|
267
|
-
/** A change with nothing else to ride on, told as a notice. */
|
|
308
|
+
/** A change with nothing else to ride on, told as a notice, after every result's change still unclaimed. */
|
|
268
309
|
deliver(cs) {
|
|
310
|
+
this.tellUnclaimed();
|
|
311
|
+
this.notice(cs);
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Unclaimed carriers' changes, told as notices in the order they were read,
|
|
315
|
+
* through `upTo` when given. When no notice can be written, a push keeps its
|
|
316
|
+
* change for the framework's push path, which takes it when the push gets
|
|
317
|
+
* there; a result's is dropped, whether or not its caller is still to
|
|
318
|
+
* resume.
|
|
319
|
+
*/
|
|
320
|
+
tellUnclaimed(upTo) {
|
|
321
|
+
for (const [res, method] of [...this.unclaimed]) {
|
|
322
|
+
this.unclaimed.delete(res);
|
|
323
|
+
const cs = this.readChanges.get(res) ?? emptyChange();
|
|
324
|
+
const sent = this.notice(cs);
|
|
325
|
+
if (sent !== 'refused' || method !== 'push/event')
|
|
326
|
+
this.readChanges.delete(res);
|
|
327
|
+
if (sent === 'told')
|
|
328
|
+
this.log(`${method}'s tool change wasn't shown with it: told as a notice`);
|
|
329
|
+
if (res === upTo)
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
/** Whether a notice was written, there was nothing left to tell (`empty`), or one couldn't be written (`refused`). */
|
|
334
|
+
notice(cs) {
|
|
269
335
|
if (isEmptyChange(cs))
|
|
270
|
-
return;
|
|
271
|
-
const text = this.presence.render(cs);
|
|
336
|
+
return 'empty';
|
|
337
|
+
const text = this.presence.render(cs, { mark: false });
|
|
272
338
|
if (!text)
|
|
273
|
-
return;
|
|
274
|
-
this.deps.notify(this.serverId, text, { invite: cs.suggest.some((s) => s.action === 'open') })
|
|
339
|
+
return 'empty';
|
|
340
|
+
if (!this.deps.notify(this.serverId, text, { invite: cs.suggest.some((s) => s.action === 'open') }))
|
|
341
|
+
return 'refused';
|
|
342
|
+
this.presence.markTold(cs.ended);
|
|
343
|
+
this.presence.markShown(cs);
|
|
344
|
+
return 'told';
|
|
275
345
|
}
|
|
276
346
|
/**
|
|
277
347
|
* A dynamic tool called by its namespaced name, as if it were listed (a
|
|
278
348
|
* model imitating an earlier call, a module): refused, so every call into
|
|
279
349
|
* a toolset goes through act, which knows its instance and gates closed
|
|
280
|
-
* groups (RFC §5.4, §5.5). Null for any other tool
|
|
350
|
+
* groups (RFC §5.4, §5.5). Null for any other tool, including one the tool
|
|
351
|
+
* list last published or the server's latest tools/list puts as static
|
|
352
|
+
* (shownAs: RFC §4.2, until its move applies).
|
|
281
353
|
*/
|
|
282
354
|
dynamicCallRefusal(tool, calledAs) {
|
|
283
|
-
if (!this.presence.active || !this.presence.tool(tool) || this.presence.
|
|
355
|
+
if (!this.presence.active || !this.presence.tool(tool) || this.presence.shownAs(tool, 'static'))
|
|
284
356
|
return null;
|
|
285
357
|
return `There's no tool "${calledAs}" in your tool list, so nothing ran: ${tool} is a dynamic tool of ${this.serverId}, ` +
|
|
286
358
|
`called with ${META_TOOL_NAMES.act} {server:"${this.serverId}", tool:"${tool}", args}. ${this.presence.toolStatus(tool)}.`;
|
|
@@ -292,34 +364,54 @@ export class ToolsetController {
|
|
|
292
364
|
* run (the next macrotask), such as the result of a call the framework makes
|
|
293
365
|
* itself (a reaction, a send, an accounted side effect), is told as a notice
|
|
294
366
|
* instead, as a standalone tools/hints is (RFC §6.1: every change reaches the
|
|
295
|
-
* agent once).
|
|
367
|
+
* agent once), or dropped as tellUnclaimed says when none can be written.
|
|
296
368
|
*/
|
|
297
369
|
tellUnlessClaimed(res, cs, method) {
|
|
298
370
|
if (isEmptyChange(cs))
|
|
299
371
|
return;
|
|
300
|
-
this.unclaimed.
|
|
372
|
+
this.unclaimed.set(res, method);
|
|
301
373
|
setImmediate(() => {
|
|
302
|
-
if (
|
|
303
|
-
|
|
304
|
-
this.log(`${method} result's tool change wasn't shown with it: told as a notice`);
|
|
305
|
-
this.deliver(cs);
|
|
374
|
+
if (this.unclaimed.has(res))
|
|
375
|
+
this.tellUnclaimed(res);
|
|
306
376
|
});
|
|
307
377
|
}
|
|
308
|
-
/**
|
|
309
|
-
*
|
|
310
|
-
*
|
|
311
|
-
|
|
378
|
+
/**
|
|
379
|
+
* The change a carrier's hints made (a tool result, a push's params), taken
|
|
380
|
+
* by a caller that shows it to an agent with the carrier: it isn't told as a
|
|
381
|
+
* notice, and the caller says when it has reached an agent (told). Call it
|
|
382
|
+
* synchronously as the carrier is read, before the next macrotask, when an
|
|
383
|
+
* unclaimed carrier's change goes out as a notice instead (tellUnclaimed
|
|
384
|
+
* says what happens when none can be written). A change already gone that
|
|
385
|
+
* way is empty here. Unclaimed carriers read before this one
|
|
386
|
+
* are told first, as notices, so no later change is shown before them.
|
|
387
|
+
*/
|
|
388
|
+
takeChange(carrier) {
|
|
312
389
|
if (!isObj(carrier))
|
|
313
|
-
return
|
|
390
|
+
return emptyChange();
|
|
391
|
+
if (this.unclaimed.has(carrier)) {
|
|
392
|
+
let before;
|
|
393
|
+
for (const res of this.unclaimed.keys()) {
|
|
394
|
+
if (res === carrier)
|
|
395
|
+
break;
|
|
396
|
+
before = res;
|
|
397
|
+
}
|
|
398
|
+
if (before)
|
|
399
|
+
this.tellUnclaimed(before);
|
|
400
|
+
}
|
|
314
401
|
this.unclaimed.delete(carrier);
|
|
315
|
-
|
|
316
|
-
|
|
402
|
+
return this.readChanges.get(carrier) ?? emptyChange();
|
|
403
|
+
}
|
|
404
|
+
/** A change its taker showed to an agent: its ended records count as told, and the definitions it describes as shown, unless the taker showed it without its blocks (`blocksShown: false`). */
|
|
405
|
+
told(cs) {
|
|
406
|
+
this.presence.markTold(cs.ended);
|
|
407
|
+
if (cs.blocksShown !== false)
|
|
408
|
+
this.presence.markShown(cs);
|
|
409
|
+
}
|
|
410
|
+
/** A change its taker couldn't show, told as a notice instead, or dropped when none can be written. */
|
|
411
|
+
tell(cs) {
|
|
412
|
+
this.notice(cs);
|
|
317
413
|
}
|
|
318
|
-
/** The change a carrier's hints made, or an empty one; taken as changeText's is. */
|
|
319
414
|
changeOf(carrier) {
|
|
320
|
-
if (!isObj(carrier))
|
|
321
|
-
return emptyChange();
|
|
322
|
-
this.unclaimed.delete(carrier);
|
|
323
415
|
return this.readChanges.get(carrier) ?? emptyChange();
|
|
324
416
|
}
|
|
325
417
|
// ── recovery (RFC §6.1) ──
|
|
@@ -341,8 +433,11 @@ export class ToolsetController {
|
|
|
341
433
|
/**
|
|
342
434
|
* Recovering: re-read tools/list (presence, declared definitions), then
|
|
343
435
|
* toolsets/list (dynamic-tool state in full), so every tool the statement
|
|
344
|
-
* names is already known
|
|
345
|
-
*
|
|
436
|
+
* names is already known, unless tools/list failed: a tool the last list
|
|
437
|
+
* lacks then stays unknown until a later change or list starts a recovery.
|
|
438
|
+
* Each applies where it's read. What
|
|
439
|
+
* changed is told. (stdio and WebSocket deliver in order, so no early rev is
|
|
440
|
+
* held.)
|
|
346
441
|
*/
|
|
347
442
|
async recover() {
|
|
348
443
|
const connection = this.connection;
|
|
@@ -350,13 +445,14 @@ export class ToolsetController {
|
|
|
350
445
|
return;
|
|
351
446
|
this.recovering = true;
|
|
352
447
|
try {
|
|
353
|
-
|
|
448
|
+
await this.ownToolList(connection);
|
|
449
|
+
// RFC §6.1: toolsets/list after tools/list, so every tool it names is known, unless tools/list failed.
|
|
354
450
|
if (connection !== this.connection)
|
|
355
451
|
return;
|
|
356
452
|
const lists = await this.ownToolsetList(connection, LIST_TIMEOUT_MS, 'recovery');
|
|
357
453
|
if (connection !== this.connection)
|
|
358
454
|
return;
|
|
359
|
-
this.deliver(
|
|
455
|
+
this.deliver(lists);
|
|
360
456
|
}
|
|
361
457
|
finally {
|
|
362
458
|
this.recovering = false;
|
|
@@ -366,34 +462,22 @@ export class ToolsetController {
|
|
|
366
462
|
}
|
|
367
463
|
}
|
|
368
464
|
}
|
|
465
|
+
/** The tap applies the list and tells its change as it's read. */
|
|
369
466
|
async ownToolList(connection) {
|
|
370
|
-
if (this.ownLists++ === 0)
|
|
371
|
-
this.ownListChange = null;
|
|
372
467
|
try {
|
|
373
|
-
// The tap applied each page as it was read, and kept what the whole
|
|
374
|
-
// list changed (ownListChange).
|
|
375
468
|
await connection.sendToolsList();
|
|
376
469
|
}
|
|
377
470
|
catch (error) {
|
|
378
|
-
this.log(`tools/list failed: ${
|
|
471
|
+
this.log(`tools/list failed: ${this.failure(error)}`);
|
|
379
472
|
}
|
|
380
|
-
finally {
|
|
381
|
-
this.ownLists--;
|
|
382
|
-
}
|
|
383
|
-
const collected = this.ownListChange ?? emptyChange();
|
|
384
|
-
if (this.ownLists === 0)
|
|
385
|
-
this.ownListChange = null;
|
|
386
|
-
else
|
|
387
|
-
this.ownListChange = emptyChange();
|
|
388
|
-
return collected;
|
|
389
473
|
}
|
|
390
474
|
async ownToolsetList(connection, timeoutMs, why) {
|
|
391
475
|
try {
|
|
392
476
|
const r = await connection.sendToolsetsList(timeoutMs);
|
|
393
|
-
return this.
|
|
477
|
+
return this.takeChange(r);
|
|
394
478
|
}
|
|
395
479
|
catch (error) {
|
|
396
|
-
this.log(`toolsets/list (${why}) failed: ${
|
|
480
|
+
this.log(`toolsets/list (${why}) failed: ${this.failure(error)}`);
|
|
397
481
|
return emptyChange();
|
|
398
482
|
}
|
|
399
483
|
}
|
|
@@ -411,60 +495,67 @@ export class ToolsetController {
|
|
|
411
495
|
if (!this.presence.active)
|
|
412
496
|
return;
|
|
413
497
|
const epoch = connection.transportEpoch;
|
|
414
|
-
|
|
498
|
+
await this.ownToolList(connection);
|
|
415
499
|
if (connection !== this.connection || connection.transportEpoch !== epoch)
|
|
416
500
|
return;
|
|
417
501
|
const lists = await this.ownToolsetList(connection, LIST_TIMEOUT_MS, 'after connecting');
|
|
418
502
|
if (connection !== this.connection || connection.transportEpoch !== epoch)
|
|
419
503
|
return;
|
|
420
|
-
this.deliver(
|
|
504
|
+
this.deliver(lists);
|
|
421
505
|
}
|
|
422
506
|
/**
|
|
423
507
|
* Re-read what the server holds open (titles, visibility) without telling
|
|
424
|
-
* it
|
|
425
|
-
*
|
|
508
|
+
* it, for look. Returns what the re-read changed, taken from the notice it
|
|
509
|
+
* would otherwise get when `shown` (its result reaches an agent): look then
|
|
510
|
+
* either shows it (told) or hands it back (tell). Otherwise the change is
|
|
511
|
+
* left to its notice, and returned to be shown to the caller alone. Null
|
|
512
|
+
* when nothing was read.
|
|
426
513
|
*/
|
|
427
|
-
async refreshOpen() {
|
|
514
|
+
async refreshOpen(opts) {
|
|
428
515
|
if (!this.presence.active)
|
|
429
|
-
return
|
|
516
|
+
return null;
|
|
430
517
|
const connection = this.connection;
|
|
431
518
|
if (!connection || !connection.isConnected)
|
|
432
|
-
return
|
|
519
|
+
return null;
|
|
433
520
|
try {
|
|
434
521
|
const r = await connection.sendToolsetsList(REFRESH_TIMEOUT_MS);
|
|
435
|
-
|
|
522
|
+
if (!isObj(r))
|
|
523
|
+
return null;
|
|
524
|
+
return opts.shown ? this.takeChange(r) : this.changeOf(r);
|
|
436
525
|
}
|
|
437
526
|
catch (error) {
|
|
438
|
-
this.log(`toolsets/list (look) failed: ${
|
|
439
|
-
return
|
|
527
|
+
this.log(`toolsets/list (look) failed: ${this.failure(error)}`);
|
|
528
|
+
return null;
|
|
440
529
|
}
|
|
441
530
|
}
|
|
442
531
|
// ── the host's acts ──
|
|
443
|
-
/** toolsets/open (RFC §5.2). The server's refusal rejects.
|
|
444
|
-
|
|
532
|
+
/** toolsets/open (RFC §5.2). The server's refusal rejects. Its change is
|
|
533
|
+
* taken when `shown` (its result reaches an agent), else left to its
|
|
534
|
+
* notice and returned for the caller alone; close's is the same. */
|
|
535
|
+
async open(toolset, instance, options, opts) {
|
|
445
536
|
const connection = this.live();
|
|
446
537
|
const r = await connection.sendToolsetsOpen({ toolset, ...(instance ? { instance } : {}), ...(options ? { options } : {}) }, TOOLSET_REQUEST_TIMEOUT_MS);
|
|
447
538
|
const res = isObj(r) ? r : {};
|
|
448
539
|
const inst = typeof res.instance === 'string' && res.instance ? res.instance : instance;
|
|
449
540
|
// RFC §5.2: a proposed name is used as given or the open refused.
|
|
450
541
|
if (instance && inst !== instance)
|
|
451
|
-
this.log(`proposed instance
|
|
542
|
+
this.log(`proposed instance ${this.field(instance)} came back as ${this.field(inst)} (RFC-014 §5.2 forbids renaming): using the server's`);
|
|
452
543
|
const key = keyOf(toolset, this.presence.declared[toolset]?.instances ? inst : undefined);
|
|
453
544
|
const title = typeof res.title === 'string' && res.title ? res.title : this.presence.openRecords().find((o) => keyOf(o.toolset, o.instance) === key)?.title;
|
|
454
545
|
return {
|
|
455
546
|
key,
|
|
456
547
|
...(title ? { title } : {}),
|
|
457
548
|
content: Array.isArray(res.content) ? res.content : [],
|
|
458
|
-
change: this.changeOf(res),
|
|
549
|
+
change: opts.shown ? this.takeChange(res) : this.changeOf(res),
|
|
459
550
|
adopted: res.adopted === true,
|
|
460
551
|
};
|
|
461
552
|
}
|
|
462
553
|
/** toolsets/close (RFC §5.2). */
|
|
463
|
-
async close(toolset, instance) {
|
|
554
|
+
async close(toolset, instance, opts) {
|
|
464
555
|
const connection = this.live();
|
|
465
556
|
const r = await connection.sendToolsetsClose({ toolset, ...(instance ? { instance } : {}) }, TOOLSET_REQUEST_TIMEOUT_MS);
|
|
466
557
|
const res = isObj(r) ? r : {};
|
|
467
|
-
return { content: Array.isArray(res.content) ? res.content : [], change: this.changeOf(res) };
|
|
558
|
+
return { content: Array.isArray(res.content) ? res.content : [], change: opts.shown ? this.takeChange(res) : this.changeOf(res) };
|
|
468
559
|
}
|
|
469
560
|
/** Open or close an undeclared group (RFC §5.4): the host's own act; the server isn't told. */
|
|
470
561
|
setGroupOpen(group, open) {
|
|
@@ -489,7 +580,7 @@ export class ToolsetController {
|
|
|
489
580
|
this.presence.deactivate();
|
|
490
581
|
this.log('the server withdrew toolHints: every tool is static from now on');
|
|
491
582
|
const cs = this.presence.diff(before, this.presence.view());
|
|
492
|
-
cs.notes.push(`${this.serverId} no longer presents toolsets: its tools are
|
|
583
|
+
cs.notes.push(`${this.serverId} no longer presents toolsets: its tools are called directly now, by name, not through ${META_TOOL_NAMES.act}.`);
|
|
493
584
|
this.deliver(cs);
|
|
494
585
|
return;
|
|
495
586
|
}
|
|
@@ -502,11 +593,16 @@ export class ToolsetController {
|
|
|
502
593
|
this.presence.setManifest(declarations);
|
|
503
594
|
this.deliver(this.presence.diff(before, this.presence.view()));
|
|
504
595
|
}
|
|
505
|
-
/**
|
|
506
|
-
applyMoves() {
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
596
|
+
/** At a rebuild of the model's tool list (RFC §4.2), tell where the tools it moved went: `moves`, those still pending, or every one pending. A move a list has undone since the rebuild listed the server isn't told; the next rebuild publishes the undoing. */
|
|
597
|
+
applyMoves(moves) {
|
|
598
|
+
const moved = this.presence.applyMoves(moves);
|
|
599
|
+
if (!moved.length)
|
|
600
|
+
return;
|
|
601
|
+
const cs = emptyChange();
|
|
602
|
+
cs.notes.push(...moved.map(({ name, to }) => to === 'dynamic'
|
|
603
|
+
? `${name} left your tool list: it's announced when available, and called with ${META_TOOL_NAMES.act} {server:"${this.serverId}", tool:"${name}", args}.`
|
|
604
|
+
: `${name} is in your tool list now, as ${this.deps.listedName(this.serverId, name)}.`));
|
|
605
|
+
this.deliver(cs);
|
|
510
606
|
}
|
|
511
607
|
}
|
|
512
608
|
/** Every server's controller, for the meta-tools and reminders. */
|
|
@@ -534,8 +630,11 @@ export class ToolsetHub {
|
|
|
534
630
|
get(serverId) {
|
|
535
631
|
return this.controllers.get(serverId);
|
|
536
632
|
}
|
|
537
|
-
/** A server's configuration went (removed):
|
|
633
|
+
/** A server's configuration went (removed): its controller lets go of the
|
|
634
|
+
* connection it holds, and is forgotten, so a server added later under
|
|
635
|
+
* the same id starts from nothing. */
|
|
538
636
|
forget(serverId) {
|
|
637
|
+
this.controllers.get(serverId)?.release();
|
|
539
638
|
this.controllers.delete(serverId);
|
|
540
639
|
}
|
|
541
640
|
all() {
|
|
@@ -586,7 +685,7 @@ export class ToolsetHub {
|
|
|
586
685
|
reminderFor(agentName) {
|
|
587
686
|
if (!this.due.has(agentName))
|
|
588
687
|
return null;
|
|
589
|
-
const parts = this.presenting().map((c) => c.presence.reminder()).filter((r) => !!r);
|
|
688
|
+
const parts = this.presenting().filter((c) => this.deps.canNotify(c.serverId)).map((c) => c.presence.reminder()).filter((r) => !!r);
|
|
590
689
|
if (parts.length === 0)
|
|
591
690
|
return null;
|
|
592
691
|
return `Open toolsets (a reminder: their earlier announcements have left your context):\n${parts.join('\n')}`;
|