agentfootprint 9.85.0 → 9.86.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +547 -24
- package/canonical-notes.json +1 -1
- package/dist/core/Agent.js +15 -4
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/agent/AgentBuilder.js.map +1 -1
- package/dist/core/agent/buildAgentChart.js +5 -0
- package/dist/core/agent/buildAgentChart.js.map +1 -1
- package/dist/core/agent/buildDynamicAgentChart.js +22 -4
- package/dist/core/agent/buildDynamicAgentChart.js.map +1 -1
- package/dist/core/agent/buildToolRegistry.js +12 -1
- package/dist/core/agent/buildToolRegistry.js.map +1 -1
- package/dist/core/agent/coverage/ledger.js +8 -2
- package/dist/core/agent/coverage/ledger.js.map +1 -1
- package/dist/core/agent/selfCallNotice.js +20 -7
- package/dist/core/agent/selfCallNotice.js.map +1 -1
- package/dist/core/agent/skillBrains.js +3 -2
- package/dist/core/agent/skillBrains.js.map +1 -1
- package/dist/core/agent/stages/stepNudge.js +13 -4
- package/dist/core/agent/stages/stepNudge.js.map +1 -1
- package/dist/core/agent/stages/toolCalls.js +484 -64
- package/dist/core/agent/stages/toolCalls.js.map +1 -1
- package/dist/core/agent/stages/wrapUp.js +54 -6
- package/dist/core/agent/stages/wrapUp.js.map +1 -1
- package/dist/core/agent/window/currentRequest.js +4 -2
- package/dist/core/agent/window/currentRequest.js.map +1 -1
- package/dist/core/runbook/recording.js +3 -2
- package/dist/core/runbook/recording.js.map +1 -1
- package/dist/core/slots/buildToolsSlot.js +20 -4
- package/dist/core/slots/buildToolsSlot.js.map +1 -1
- package/dist/doors/skill-graph.js +4 -1
- package/dist/doors/skill-graph.js.map +1 -1
- package/dist/esm/core/Agent.js +15 -4
- package/dist/esm/core/Agent.js.map +1 -1
- package/dist/esm/core/agent/AgentBuilder.d.ts +2 -1
- package/dist/esm/core/agent/AgentBuilder.js.map +1 -1
- package/dist/esm/core/agent/buildAgentChart.js +5 -0
- package/dist/esm/core/agent/buildAgentChart.js.map +1 -1
- package/dist/esm/core/agent/buildDynamicAgentChart.js +22 -4
- package/dist/esm/core/agent/buildDynamicAgentChart.js.map +1 -1
- package/dist/esm/core/agent/buildToolRegistry.d.ts +20 -0
- package/dist/esm/core/agent/buildToolRegistry.js +12 -1
- package/dist/esm/core/agent/buildToolRegistry.js.map +1 -1
- package/dist/esm/core/agent/coverage/ledger.d.ts +5 -0
- package/dist/esm/core/agent/coverage/ledger.js +8 -2
- package/dist/esm/core/agent/coverage/ledger.js.map +1 -1
- package/dist/esm/core/agent/selfCallNotice.d.ts +20 -7
- package/dist/esm/core/agent/selfCallNotice.js +20 -7
- package/dist/esm/core/agent/selfCallNotice.js.map +1 -1
- package/dist/esm/core/agent/skillBrains.d.ts +12 -4
- package/dist/esm/core/agent/skillBrains.js +3 -2
- package/dist/esm/core/agent/skillBrains.js.map +1 -1
- package/dist/esm/core/agent/stages/stepNudge.d.ts +13 -4
- package/dist/esm/core/agent/stages/stepNudge.js +13 -4
- package/dist/esm/core/agent/stages/stepNudge.js.map +1 -1
- package/dist/esm/core/agent/stages/toolCalls.d.ts +185 -0
- package/dist/esm/core/agent/stages/toolCalls.js +480 -63
- package/dist/esm/core/agent/stages/toolCalls.js.map +1 -1
- package/dist/esm/core/agent/stages/wrapUp.d.ts +44 -2
- package/dist/esm/core/agent/stages/wrapUp.js +52 -5
- package/dist/esm/core/agent/stages/wrapUp.js.map +1 -1
- package/dist/esm/core/agent/types.d.ts +22 -4
- package/dist/esm/core/agent/window/currentRequest.d.ts +4 -2
- package/dist/esm/core/agent/window/currentRequest.js +4 -2
- package/dist/esm/core/agent/window/currentRequest.js.map +1 -1
- package/dist/esm/core/runbook/recording.js +3 -2
- package/dist/esm/core/runbook/recording.js.map +1 -1
- package/dist/esm/core/slots/buildToolsSlot.d.ts +1 -1
- package/dist/esm/core/slots/buildToolsSlot.js +20 -4
- package/dist/esm/core/slots/buildToolsSlot.js.map +1 -1
- package/dist/esm/doors/skill-graph.d.ts +1 -1
- package/dist/esm/doors/skill-graph.js +4 -1
- package/dist/esm/doors/skill-graph.js.map +1 -1
- package/dist/esm/events/payloads.d.ts +34 -2
- package/dist/esm/index.js +6 -5
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/lib/injection-engine/index.d.ts +1 -1
- package/dist/esm/lib/injection-engine/index.js +4 -1
- package/dist/esm/lib/injection-engine/index.js.map +1 -1
- package/dist/esm/lib/injection-engine/skillGraph.d.ts +56 -0
- package/dist/esm/lib/injection-engine/skillGraph.js +29 -3
- package/dist/esm/lib/injection-engine/skillGraph.js.map +1 -1
- package/dist/esm/lib/injection-engine/skillSteps.d.ts +20 -1
- package/dist/esm/lib/injection-engine/skillSteps.js +28 -3
- package/dist/esm/lib/injection-engine/skillSteps.js.map +1 -1
- package/dist/esm/lib/injection-engine/skillToolDescriptors.d.ts +18 -0
- package/dist/esm/lib/injection-engine/skillToolDescriptors.js +103 -10
- package/dist/esm/lib/injection-engine/skillToolDescriptors.js.map +1 -1
- package/dist/esm/lib/injection-engine/types.d.ts +12 -10
- package/dist/esm/lib/injection-engine/types.js +5 -4
- package/dist/esm/lib/injection-engine/types.js.map +1 -1
- package/dist/esm/lib/saidByPerson.d.ts +48 -15
- package/dist/esm/lib/saidByPerson.js +52 -17
- package/dist/esm/lib/saidByPerson.js.map +1 -1
- package/dist/esm/lib/spokenIds.d.ts +52 -0
- package/dist/esm/lib/spokenIds.js +11 -0
- package/dist/esm/lib/spokenIds.js.map +1 -0
- package/dist/esm/lib/trace-toolpack/traceToolpack.js +33 -19
- package/dist/esm/lib/trace-toolpack/traceToolpack.js.map +1 -1
- package/dist/index.js +6 -5
- package/dist/index.js.map +1 -1
- package/dist/lib/injection-engine/index.js +5 -2
- package/dist/lib/injection-engine/index.js.map +1 -1
- package/dist/lib/injection-engine/skillGraph.js +31 -4
- package/dist/lib/injection-engine/skillGraph.js.map +1 -1
- package/dist/lib/injection-engine/skillSteps.js +28 -3
- package/dist/lib/injection-engine/skillSteps.js.map +1 -1
- package/dist/lib/injection-engine/skillToolDescriptors.js +103 -10
- package/dist/lib/injection-engine/skillToolDescriptors.js.map +1 -1
- package/dist/lib/injection-engine/types.js +5 -4
- package/dist/lib/injection-engine/types.js.map +1 -1
- package/dist/lib/saidByPerson.js +53 -18
- package/dist/lib/saidByPerson.js.map +1 -1
- package/dist/lib/spokenIds.js +15 -0
- package/dist/lib/spokenIds.js.map +1 -0
- package/dist/lib/trace-toolpack/traceToolpack.js +33 -19
- package/dist/lib/trace-toolpack/traceToolpack.js.map +1 -1
- package/dist/types/core/Agent.d.ts.map +1 -1
- package/dist/types/core/agent/AgentBuilder.d.ts +2 -1
- package/dist/types/core/agent/AgentBuilder.d.ts.map +1 -1
- package/dist/types/core/agent/buildAgentChart.d.ts.map +1 -1
- package/dist/types/core/agent/buildDynamicAgentChart.d.ts.map +1 -1
- package/dist/types/core/agent/buildToolRegistry.d.ts +20 -0
- package/dist/types/core/agent/buildToolRegistry.d.ts.map +1 -1
- package/dist/types/core/agent/coverage/ledger.d.ts +5 -0
- package/dist/types/core/agent/coverage/ledger.d.ts.map +1 -1
- package/dist/types/core/agent/selfCallNotice.d.ts +20 -7
- package/dist/types/core/agent/selfCallNotice.d.ts.map +1 -1
- package/dist/types/core/agent/skillBrains.d.ts +12 -4
- package/dist/types/core/agent/skillBrains.d.ts.map +1 -1
- package/dist/types/core/agent/stages/stepNudge.d.ts +13 -4
- package/dist/types/core/agent/stages/stepNudge.d.ts.map +1 -1
- package/dist/types/core/agent/stages/toolCalls.d.ts +185 -0
- package/dist/types/core/agent/stages/toolCalls.d.ts.map +1 -1
- package/dist/types/core/agent/stages/wrapUp.d.ts +44 -2
- package/dist/types/core/agent/stages/wrapUp.d.ts.map +1 -1
- package/dist/types/core/agent/types.d.ts +22 -4
- package/dist/types/core/agent/types.d.ts.map +1 -1
- package/dist/types/core/agent/window/currentRequest.d.ts +4 -2
- package/dist/types/core/agent/window/currentRequest.d.ts.map +1 -1
- package/dist/types/core/runbook/recording.d.ts.map +1 -1
- package/dist/types/core/slots/buildToolsSlot.d.ts +1 -1
- package/dist/types/core/slots/buildToolsSlot.d.ts.map +1 -1
- package/dist/types/doors/skill-graph.d.ts +1 -1
- package/dist/types/doors/skill-graph.d.ts.map +1 -1
- package/dist/types/events/payloads.d.ts +34 -2
- package/dist/types/events/payloads.d.ts.map +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/lib/injection-engine/index.d.ts +1 -1
- package/dist/types/lib/injection-engine/index.d.ts.map +1 -1
- package/dist/types/lib/injection-engine/skillGraph.d.ts +56 -0
- package/dist/types/lib/injection-engine/skillGraph.d.ts.map +1 -1
- package/dist/types/lib/injection-engine/skillSteps.d.ts +20 -1
- package/dist/types/lib/injection-engine/skillSteps.d.ts.map +1 -1
- package/dist/types/lib/injection-engine/skillToolDescriptors.d.ts +18 -0
- package/dist/types/lib/injection-engine/skillToolDescriptors.d.ts.map +1 -1
- package/dist/types/lib/injection-engine/types.d.ts +12 -10
- package/dist/types/lib/injection-engine/types.d.ts.map +1 -1
- package/dist/types/lib/saidByPerson.d.ts +48 -15
- package/dist/types/lib/saidByPerson.d.ts.map +1 -1
- package/dist/types/lib/spokenIds.d.ts +53 -0
- package/dist/types/lib/spokenIds.d.ts.map +1 -0
- package/dist/types/lib/trace-toolpack/traceToolpack.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
* activates that Skill (lifetime: turn).
|
|
45
45
|
*/
|
|
46
46
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
47
|
-
exports.buildToolCallsHandler = void 0;
|
|
47
|
+
exports.buildToolCallsHandler = exports.composeReadSkillRefusal = exports.unknownToolResult = exports.atMountedCursor = void 0;
|
|
48
48
|
const codeRunnerTool_js_1 = require("../../codeRunnerTool.js");
|
|
49
49
|
const footprintjs_1 = require("footprintjs");
|
|
50
50
|
const types_js_1 = require("../../../adapters/types.js");
|
|
@@ -52,9 +52,11 @@ const typedEmit_js_1 = require("../../../recorders/core/typedEmit.js");
|
|
|
52
52
|
const extractSequence_js_1 = require("../../../security/extractSequence.js");
|
|
53
53
|
const skillTarget_js_1 = require("../../../security/skillTarget.js");
|
|
54
54
|
const routingPolicy_js_1 = require("../../../lib/injection-engine/routingPolicy.js");
|
|
55
|
+
const skillGraph_js_1 = require("../../../lib/injection-engine/skillGraph.js");
|
|
55
56
|
const skillToolDescriptors_js_1 = require("../../../lib/injection-engine/skillToolDescriptors.js");
|
|
56
57
|
const selfCallNotice_js_1 = require("../selfCallNotice.js");
|
|
57
58
|
const types_js_2 = require("../../../maps/engagement/types.js");
|
|
59
|
+
const spokenIds_js_1 = require("../../../lib/spokenIds.js");
|
|
58
60
|
const types_js_3 = require("../../../identity/types.js");
|
|
59
61
|
const capability_js_1 = require("../../../artifacts/capability.js");
|
|
60
62
|
const wants_js_1 = require("../../../artifacts/wants.js");
|
|
@@ -133,61 +135,232 @@ function reportingCredentials(provider, scope, toolName) {
|
|
|
133
135
|
};
|
|
134
136
|
}
|
|
135
137
|
/**
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
+
* Is this `read_skill` target the cursor's own skill AND is that cursor
|
|
139
|
+
* MOUNTED — the one owner of "this call would change nothing" (9.86.0).
|
|
138
140
|
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
141
|
+
* ── WHY POSITION ALONE IS NOT THE ANSWER ────────────────────────────────
|
|
142
|
+
*
|
|
143
|
+
* `classifySkillTarget` answers a question about POSITION: is the target where
|
|
144
|
+
* the cursor stands? For most of the loop that settles it — the cursor's body
|
|
145
|
+
* is in that call's system prompt and its tools are on that call's wire, so
|
|
146
|
+
* asking to read it activates nothing, moves nothing and reveals nothing the
|
|
147
|
+
* request did not already carry.
|
|
148
|
+
*
|
|
149
|
+
* A PARKED map breaks that. Parking suppresses a map's contribution and leaves
|
|
150
|
+
* the cursor exactly where it was, so at a parked cursor the very same id is a
|
|
151
|
+
* RE-ENGAGEMENT: the gate admits it, the map engages, and the member's body and
|
|
152
|
+
* tools ride again on the next pass. That call moves something, which makes it
|
|
153
|
+
* a capability, which makes it the policy's question to answer.
|
|
154
|
+
*
|
|
155
|
+
* The dispatch gate has known this since 9.59.0 — it tests re-engagement BEFORE
|
|
156
|
+
* its self-call arm, which is this same fact written as two ordered arms. The
|
|
157
|
+
* `skill_read` permission gate, 480 lines upstream, has no arms to order: it
|
|
158
|
+
* asks one question and either spends the policy check or skips it, and it was
|
|
159
|
+
* asking `classifySkillTarget` alone. So a role whose checker hid a skill could
|
|
160
|
+
* have the model un-park and re-activate it. This function is the question that
|
|
161
|
+
* gate now asks, over the parked set `parkedNow` hands both of them.
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* ```ts
|
|
165
|
+
* atMountedCursor({ cursor: 'audit', target: 'audit' }); // true — a stay
|
|
166
|
+
* atMountedCursor({ cursor: 'audit', target: 'audit', parked: new Set(['audit']) }); // false — a re-engagement
|
|
167
|
+
* atMountedCursor({ cursor: 'audit', target: 'billing' }); // false — a move
|
|
168
|
+
* ```
|
|
147
169
|
*/
|
|
148
|
-
function
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
170
|
+
function atMountedCursor(args) {
|
|
171
|
+
const stay = (0, skillGraph_js_1.classifySkillTarget)({
|
|
172
|
+
...(args.cursor !== undefined && { cursor: args.cursor }),
|
|
173
|
+
target: args.target,
|
|
174
|
+
}) === 'self';
|
|
175
|
+
return stay && args.parked?.has(args.target) !== true;
|
|
176
|
+
}
|
|
177
|
+
exports.atMountedCursor = atMountedCursor;
|
|
178
|
+
/**
|
|
179
|
+
* What a call to a name nothing can dispatch gets back (9.86.0).
|
|
180
|
+
*
|
|
181
|
+
* `Unknown tool: X` was the whole sentence, on both dispatch doors, while
|
|
182
|
+
* `mcpServe` — the same library, one directory away — had been answering
|
|
183
|
+
* "Unknown tool 'rm_rf'. Served tools: echo, delete_account." for releases. The
|
|
184
|
+
* model that mistyped a name, or named one from a restored transcript, was told
|
|
185
|
+
* only that it was wrong, never what would have worked, and the cheapest repair
|
|
186
|
+
* (read the roster) was the one it could not make.
|
|
187
|
+
*
|
|
188
|
+
* The roster is the DISPATCH set — `registryByName` plus the provider cache —
|
|
189
|
+
* because that is the true statement about resolution: these are the names that
|
|
190
|
+
* answered to something. It is deliberately NOT the offer: the wire is narrowed
|
|
191
|
+
* by steps, parks and postures, and a held-out tool still dispatches by name, so
|
|
192
|
+
* an offer-shaped list would be a different (and here, false) claim.
|
|
193
|
+
*
|
|
194
|
+
* ── AND IT SAYS "RESOLVED", NOT "COULD BE DISPATCHED" ───────────────────
|
|
195
|
+
*
|
|
196
|
+
* The first draft said "Tools that could be dispatched on that call". Two gates
|
|
197
|
+
* sit between resolution and a tool running — the `tool_call` permission check
|
|
198
|
+
* and the middleware chain — so a name in this map can resolve and still be
|
|
199
|
+
* refused, and the sentence promised the model a dispatch the very next gate
|
|
200
|
+
* would decline. Neither gate is asked here: a `PermissionChecker` is a port
|
|
201
|
+
* (a hub call per name, with its own events and its own audit rows), and
|
|
202
|
+
* spending a whole permission sweep to phrase an error is a cost the sentence
|
|
203
|
+
* does not need. It reports what this map DID — resolve a name — and claims
|
|
204
|
+
* nothing about what would have happened next.
|
|
205
|
+
*
|
|
206
|
+
* The roster is role-filtered before it gets here (`dispatchRoster`), so a tool
|
|
207
|
+
* belonging to a skill the caller's policy hides is never named; and when the
|
|
208
|
+
* filter empties a stocked roster the clause is DROPPED, because "no tool name
|
|
209
|
+
* resolved" would be a denial of what the dispatch map is holding.
|
|
210
|
+
*
|
|
211
|
+
* Anchored past tense, like every other persistent result: "on that call" binds
|
|
212
|
+
* the roster to the finished call the result answers, so a later iteration that
|
|
213
|
+
* adds or drops a provider tool cannot make this sentence wrong. No exhortation
|
|
214
|
+
* — the model does not need to be told to try again.
|
|
215
|
+
*
|
|
216
|
+
* Exported for the producer registry, and used by BOTH dispatch doors so they
|
|
217
|
+
* cannot drift.
|
|
218
|
+
*/
|
|
219
|
+
function unknownToolResult(toolName, dispatchable) {
|
|
220
|
+
const head = `Unknown tool '${toolName}' on that call.`;
|
|
221
|
+
if (dispatchable.named.length > 0) {
|
|
222
|
+
return (`${head} Tool names that resolved to an implementation on that ` +
|
|
223
|
+
`call: ${dispatchable.named.join(', ')}.`);
|
|
158
224
|
}
|
|
159
|
-
|
|
225
|
+
// Held-but-unnameable: the map had entries this caller may not be told about.
|
|
226
|
+
// Omission is free; the negative below would be false.
|
|
227
|
+
return dispatchable.held
|
|
228
|
+
? head
|
|
229
|
+
: `${head} No tool name resolved to an implementation on that call.`;
|
|
160
230
|
}
|
|
231
|
+
exports.unknownToolResult = unknownToolResult;
|
|
161
232
|
/**
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
233
|
+
* THE ONE COMPOSER of every `read_skill` refusal (9.86.0) — reachability,
|
|
234
|
+
* posture and tree, from one set of inputs.
|
|
235
|
+
*
|
|
236
|
+
* ── WHY ONE ─────────────────────────────────────────────────────────────
|
|
237
|
+
*
|
|
238
|
+
* There were two, and they disagreed. `skillRefusal` named "Reachable skills:
|
|
239
|
+
* beta" from the graph's raw hop set; `postureRefusal`, forty lines below it in
|
|
240
|
+
* the same gate, answered a model that took that offer up with "read_skill here
|
|
241
|
+
* reaches only the open skills: gamma". Same call, same turn, opposite claims —
|
|
242
|
+
* and the second one cost a refusal from the escalation budget to discover.
|
|
243
|
+
* Neither had heard of the role filter, so both could name a skill the caller's
|
|
244
|
+
* policy hides. One composer, handed the FILTERED sets and the posture that
|
|
245
|
+
* will judge the next pick, cannot contradict itself that way.
|
|
246
|
+
*
|
|
247
|
+
* ── THE TENSE DISCIPLINE (the law `selfCallNotice` is written against) ──
|
|
248
|
+
*
|
|
249
|
+
* A tool result is composed on iteration N and re-read by the model on every
|
|
250
|
+
* later call of the turn, including the tool-less wrap-up. So every clause here
|
|
251
|
+
* is a PAST fact about ONE named call — the call the model made `read_skill`
|
|
252
|
+
* on — anchored by the phrase "that call", never by deixis ("this turn", "the
|
|
253
|
+
* call you just made") and never by a forecast ("you can call", "pick one of
|
|
254
|
+
* these"). What the model may do NEXT is owned by the `read_skill` DESCRIPTION,
|
|
255
|
+
* which is recomposed for every single request and may therefore speak in the
|
|
256
|
+
* present. Two owners, one tense each, and they can no longer disagree.
|
|
257
|
+
*
|
|
258
|
+
* ── AND WHY IT NEVER NAMES A HOP THE POSTURE WOULD DECLINE ──────────────
|
|
259
|
+
*
|
|
260
|
+
* Under `'rails'` the model does not route at all; under `'guard'` it routes
|
|
261
|
+
* only from an outstanding menu. Naming hops there is an offer the very next
|
|
262
|
+
* arm of this gate refuses. So those arms name no hop: they say who DID route,
|
|
263
|
+
* and (when there are any) which open skills were admitted anyway — open skills
|
|
264
|
+
* never reach the posture arm, so that clause cannot be contradicted.
|
|
265
|
+
*
|
|
266
|
+
* ── AND WHY AN EMPTY LIST IS NEVER A DENIAL ─────────────────────────────
|
|
267
|
+
*
|
|
268
|
+
* Every set arrives as {@link SpokenIds}, which carries whether the UNFILTERED
|
|
269
|
+
* set held anything. That is the difference between "nothing was reachable" and
|
|
270
|
+
* "nothing reachable may be named", and this composer used to say the first
|
|
271
|
+
* when the second was true — over a graph that was routing the cursor the whole
|
|
272
|
+
* time. Where the filter empties a set, the clause is omitted. A model told the
|
|
273
|
+
* map is a dead end stops asking for the door it may not be shown.
|
|
274
|
+
*
|
|
275
|
+
* Exported so a producer registry can list it beside the other model-facing
|
|
276
|
+
* composers rather than re-deriving what it says.
|
|
166
277
|
*/
|
|
167
|
-
function
|
|
168
|
-
const
|
|
169
|
-
|
|
278
|
+
function composeReadSkillRefusal(args) {
|
|
279
|
+
const { requestedId, targetClass, cursorId, hops, openIds, isTree, posture, menuOffered, turnStartedBy, cursorWithheld, } = args;
|
|
280
|
+
const head = `read_skill("${requestedId}") was not granted on that call: `;
|
|
281
|
+
// Open skills are the one list every arm may name: they are admitted from
|
|
282
|
+
// every cursor and no posture governs them (the posture arm below is reached
|
|
283
|
+
// only by hops), so naming them cannot be falsified by the next call. There is
|
|
284
|
+
// no negative arm here and there never was — an unnamed open set is simply
|
|
285
|
+
// omitted, which is the shape the two arms below now share.
|
|
286
|
+
const openClause = openIds.named.length > 0
|
|
287
|
+
? ` Open skills were admitted on that call: ${openIds.named.join(', ')}.`
|
|
170
288
|
: '';
|
|
171
|
-
if (posture
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
289
|
+
if (posture !== undefined) {
|
|
290
|
+
if (posture === 'rails') {
|
|
291
|
+
return (`${head}this graph's 'rails' posture reserves routing to the framework — turn ` +
|
|
292
|
+
`starts resolve by declared rule or scorer and transitions by declared routes — ` +
|
|
293
|
+
`so a model pick was not admitted on that call.${openClause}`);
|
|
294
|
+
}
|
|
295
|
+
if (menuOffered !== undefined && menuOffered.named.length > 0) {
|
|
296
|
+
return (`${head}this graph's 'guard' posture admits a routing pick only from the menu the ` +
|
|
297
|
+
`framework offered, and '${requestedId}' was not on it. The menu outstanding when ` +
|
|
298
|
+
`that call was made: ${menuOffered.named.join(', ')}.${openClause}`);
|
|
299
|
+
}
|
|
300
|
+
// A menu WAS outstanding and not one of its ids may be named to this
|
|
301
|
+
// caller. Neither of the other two sentences is available: naming the menu
|
|
302
|
+
// would leak it, and "no menu was outstanding" is false — as is the
|
|
303
|
+
// "declared routes moved the cursor instead" clause that rides with it,
|
|
304
|
+
// which is a second assertion about a turn that did not route that way. So
|
|
305
|
+
// this arm says the one thing that is true of that call and stops.
|
|
306
|
+
if (menuOffered !== undefined && menuOffered.held) {
|
|
307
|
+
return (`${head}this graph's 'guard' posture admits a routing pick only from the menu the ` +
|
|
308
|
+
`framework offered, and '${requestedId}' was not admitted on that call.${openClause}`);
|
|
309
|
+
}
|
|
310
|
+
// What the gate KNOWS about why no menu was outstanding is `TurnRoute.by`,
|
|
311
|
+
// and each value is a different finished fact. Nothing is asserted about
|
|
312
|
+
// how the cursor came to be where it was beyond that — the old tail,
|
|
313
|
+
// "Declared routes moved the cursor instead.", was composed for every
|
|
314
|
+
// value and was true of none of them in particular.
|
|
315
|
+
const startClause = turnStartedBy === 'entry' || turnStartedBy === 'intent'
|
|
316
|
+
? " — the turn's start had already been resolved decisively"
|
|
317
|
+
: turnStartedBy === 'continuity'
|
|
318
|
+
? ' — the cursor had been carried over from the previous turn'
|
|
319
|
+
: turnStartedBy === 'menu'
|
|
320
|
+
? ' — the menu had already been resolved by an earlier pick'
|
|
321
|
+
: turnStartedBy === 'decider'
|
|
322
|
+
? " — the menu had been resolved by the configured decider before the turn's first call"
|
|
323
|
+
: '';
|
|
324
|
+
return (`${head}this graph's 'guard' posture admits a routing pick only while the framework ` +
|
|
325
|
+
`has declared ambiguity, and no menu was outstanding when that call was made` +
|
|
326
|
+
`${startClause}.${openClause}`);
|
|
176
327
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
`
|
|
181
|
-
`stay (answer without calling read_skill).${openClause}`);
|
|
328
|
+
if (isTree === true) {
|
|
329
|
+
return (`${head}this map is a decision tree. A tree routes by predicate on every iteration ` +
|
|
330
|
+
`and keeps no cursor, so there was nothing for that pick to move and ` +
|
|
331
|
+
`'${requestedId}' was not activated by it.${openClause}`);
|
|
182
332
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
333
|
+
// Reachability. `targetClass` is 'unreachable' by the time the gate calls
|
|
334
|
+
// this arm — 'self' is answered by the notice, 'hop' and 'open' are admitted
|
|
335
|
+
// — and it is carried rather than re-derived so the sentence and the verdict
|
|
336
|
+
// read the same classification.
|
|
337
|
+
// Three anchors, not two: a named cursor, a cursor this caller may not be
|
|
338
|
+
// told the name of, and a genuine cold start. The middle one used to print
|
|
339
|
+
// the raw id — the one name the description's own law withholds.
|
|
340
|
+
const from = cursorId !== undefined
|
|
341
|
+
? `'${cursorId}'`
|
|
342
|
+
: cursorWithheld === true
|
|
343
|
+
? 'the skill the cursor stood in'
|
|
344
|
+
: "the turn's start";
|
|
345
|
+
// The gate reaches this arm only with `'unreachable'`; the weaker sentence is
|
|
346
|
+
// for a caller that refused an admissible class for a reason of its own, and
|
|
347
|
+
// it is deliberately not an invented explanation of one.
|
|
348
|
+
const reason = targetClass === 'unreachable'
|
|
349
|
+
? `'${requestedId}' was not reachable from ${from}.`
|
|
350
|
+
: `'${requestedId}' was not admitted from ${from}.`;
|
|
351
|
+
// OMIT, NEVER DENY. Three cases, not two: hops to name, no hops at all, and
|
|
352
|
+
// hops the graph holds that this caller may not be told about. The third used
|
|
353
|
+
// to fall into the second and assert "No skill was reachable from 'alpha'"
|
|
354
|
+
// while the graph routed 'alpha' to a skill the role hides — a denial of what
|
|
355
|
+
// the run holds, and the one shape omission is always safe against.
|
|
356
|
+
const hopClause = hops.named.length > 0
|
|
357
|
+
? ` Skills reachable from ${from} when that call was made: ${hops.named.join(', ')}.`
|
|
358
|
+
: hops.held
|
|
359
|
+
? ''
|
|
360
|
+
: ` No skill was reachable from ${from} when that call was made.`;
|
|
361
|
+
return `${head}${reason}${hopClause}${openClause}`;
|
|
190
362
|
}
|
|
363
|
+
exports.composeReadSkillRefusal = composeReadSkillRefusal;
|
|
191
364
|
/**
|
|
192
365
|
* The args a `pauseHere` / `askHuman` call was running with, read back on the
|
|
193
366
|
* far side of the pause (8.13.0).
|
|
@@ -778,10 +951,95 @@ function buildToolCallsHandler(deps) {
|
|
|
778
951
|
}
|
|
779
952
|
const currentSkillId = scope.currentSkillId;
|
|
780
953
|
const hops = deps.allowedSkillIds(currentSkillId);
|
|
781
|
-
|
|
954
|
+
const targetClass = (0, skillGraph_js_1.classifySkillTarget)({
|
|
955
|
+
...(currentSkillId !== undefined && { cursor: currentSkillId }),
|
|
956
|
+
target,
|
|
957
|
+
hops,
|
|
958
|
+
});
|
|
959
|
+
// ── A PROPOSAL TO THE CURSOR'S OWN SKILL IS A STAY (9.86.0) ──────
|
|
960
|
+
// `makeReachableSkills` filters the cursor out of its own successor
|
|
961
|
+
// set — right for a MOVE, and this judge read it as "not reachable"
|
|
962
|
+
// and refused. A tool saying "stay where you are" asks for the state
|
|
963
|
+
// the run is already in: there is no cursor to move, no activation to
|
|
964
|
+
// append, and nothing to refuse. It is accepted as a no-op — no
|
|
965
|
+
// `pendingToolTransition`, no cursor move, and no `[tool effect
|
|
966
|
+
// refused: …]` suffix on a result that proposed nothing wrong.
|
|
967
|
+
//
|
|
968
|
+
// The event carries the existing `'accepted'` outcome plus an
|
|
969
|
+
// ADDITIVE `stay: true`, deliberately not a new enum member: an
|
|
970
|
+
// exhaustive switch over `outcome` in a consumer must keep compiling.
|
|
971
|
+
//
|
|
972
|
+
// A STAY COMPETES FOR THE SLOT LIKE ANY OTHER ACCEPTED PROPOSAL
|
|
973
|
+
// (9.86.1). The batch law is "first ACCEPTED proposal wins, later
|
|
974
|
+
// proposals to OTHER targets are superseded", and a stay is accepted
|
|
975
|
+
// — so a stay judged first holds the slot (with nothing written to
|
|
976
|
+
// `pendingToolTransition`, because a stay moves nothing) and a later
|
|
977
|
+
// sibling proposing a hop is `'superseded'` and lands in the
|
|
978
|
+
// `route_conflict` losers, exactly as two conflicting hops do. Before
|
|
979
|
+
// this the stay `continue`d past the bookkeeping, so the tool that
|
|
980
|
+
// judged its data first lost to whichever sibling came second, with
|
|
981
|
+
// two `'accepted'` events in one batch and no conflict on the record.
|
|
982
|
+
// The same law in the other order: a stay proposed AFTER an accepted
|
|
983
|
+
// hop is superseded by it, not accepted beside it.
|
|
984
|
+
if (targetClass === 'self') {
|
|
985
|
+
if (state.winner !== undefined && state.winner.targetSkillId !== target) {
|
|
986
|
+
state.losers.push({
|
|
987
|
+
toolCallId: call.toolCallId,
|
|
988
|
+
toolName: call.toolName,
|
|
989
|
+
target,
|
|
990
|
+
});
|
|
991
|
+
(0, typedEmit_js_1.typedEmit)(scope, 'agentfootprint.tools.effect', {
|
|
992
|
+
kind: 'propose-transition',
|
|
993
|
+
outcome: 'superseded',
|
|
994
|
+
toolName: call.toolName,
|
|
995
|
+
toolCallId: call.toolCallId,
|
|
996
|
+
iteration: call.iteration,
|
|
997
|
+
targetSkillId: target,
|
|
998
|
+
reason: effect.reason,
|
|
999
|
+
supersededBy: 'earlier-proposal',
|
|
1000
|
+
});
|
|
1001
|
+
continue;
|
|
1002
|
+
}
|
|
1003
|
+
if (state.winner === undefined) {
|
|
1004
|
+
state.winner = {
|
|
1005
|
+
targetSkillId: target,
|
|
1006
|
+
toolName: call.toolName,
|
|
1007
|
+
toolCallId: call.toolCallId,
|
|
1008
|
+
reason: effect.reason,
|
|
1009
|
+
iteration: call.iteration,
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
1012
|
+
(0, typedEmit_js_1.typedEmit)(scope, 'agentfootprint.tools.effect', {
|
|
1013
|
+
kind: 'propose-transition',
|
|
1014
|
+
outcome: 'accepted',
|
|
1015
|
+
stay: true,
|
|
1016
|
+
toolName: call.toolName,
|
|
1017
|
+
toolCallId: call.toolCallId,
|
|
1018
|
+
iteration: call.iteration,
|
|
1019
|
+
targetSkillId: target,
|
|
1020
|
+
reason: effect.reason,
|
|
1021
|
+
});
|
|
1022
|
+
continue;
|
|
1023
|
+
}
|
|
1024
|
+
if (targetClass === 'unreachable') {
|
|
1025
|
+
// ── THE REFUSAL SPEAKS WITH THE FILTERED SETS (9.86.1) ──────────
|
|
1026
|
+
// This sentence is appended to the tool result the model reads and
|
|
1027
|
+
// rides the `tools.effect` payload, and it named the raw hop set and
|
|
1028
|
+
// the raw cursor — the leak the `read_skill` refusals closed one
|
|
1029
|
+
// function up. Same filter, same law: the hidden set the tools slot
|
|
1030
|
+
// resolved for this iteration, and OMIT, NEVER DENY — a hop set the
|
|
1031
|
+
// filter emptied composes no reachable clause rather than an empty
|
|
1032
|
+
// one. Admission is still judged on the raw `hops`.
|
|
1033
|
+
const hiddenIds = new Set(scope.hiddenSkillIds ?? []);
|
|
1034
|
+
const mayName = (id) => !hiddenIds.has(id);
|
|
1035
|
+
const reachable = (0, spokenIds_js_1.spoken)(hops, mayName);
|
|
1036
|
+
const from = currentSkillId === undefined
|
|
1037
|
+
? 'the turn start'
|
|
1038
|
+
: mayName(currentSkillId)
|
|
1039
|
+
? `'${currentSkillId}'`
|
|
1040
|
+
: 'the skill the cursor stood in';
|
|
782
1041
|
refuse('propose-transition', `propose-transition → '${target}' was refused: '${target}' is not reachable from ` +
|
|
783
|
-
`${
|
|
784
|
-
`the graph's own law${hops.length > 0 ? ` (reachable: ${hops.join(', ')})` : ''}. The graph decides — a proposal is evidence, never authority.`, { targetSkillId: target, reason: effect.reason });
|
|
1042
|
+
`${from} per the graph's own law${reachable.named.length > 0 ? ` (reachable: ${reachable.named.join(', ')})` : ''}. The graph decides — a proposal is evidence, never authority.`, { targetSkillId: target, reason: effect.reason });
|
|
785
1043
|
continue;
|
|
786
1044
|
}
|
|
787
1045
|
if (state.winner !== undefined && state.winner.targetSkillId !== target) {
|
|
@@ -1427,6 +1685,62 @@ function buildToolCallsHandler(deps) {
|
|
|
1427
1685
|
const cached = providerToolCache?.current ?? [];
|
|
1428
1686
|
return cached.find((t) => t.schema.name === toolName);
|
|
1429
1687
|
};
|
|
1688
|
+
/**
|
|
1689
|
+
* Every member id of every map the kernel currently holds PARKED (9.86.0
|
|
1690
|
+
* fix pass) — asked of `parkedMemberIds`, the kernel's own owner of the fact.
|
|
1691
|
+
*
|
|
1692
|
+
* ONE reader for TWO gates. The `skill_read` permission gate and the graph's
|
|
1693
|
+
* dispatch gate both have to tell a stay from a re-engagement, and they sit
|
|
1694
|
+
* 480 lines apart; the second computed this and the first did not, which is
|
|
1695
|
+
* exactly how a role-hidden parked member became re-activatable. Answers with
|
|
1696
|
+
* `undefined` when no engagement plan is mounted, which is every agent that
|
|
1697
|
+
* never called `.maps()`.
|
|
1698
|
+
*/
|
|
1699
|
+
const parkedNow = (scope) => deps.engagementPlan === undefined
|
|
1700
|
+
? undefined
|
|
1701
|
+
: (0, types_js_2.parkedMemberIds)(deps.engagementPlan, scope.mapEngagement);
|
|
1702
|
+
/**
|
|
1703
|
+
* Every name `lookupTool` would resolve, in resolution order, through the
|
|
1704
|
+
* role filter (9.86.0) — the roster `unknownToolResult` names.
|
|
1705
|
+
*
|
|
1706
|
+
* Derived from the SAME two sources the lookup reads, so a name it reports is
|
|
1707
|
+
* a name that resolved. Then filtered by the SAME fact every other sentence in
|
|
1708
|
+
* this stage filters by: `scope.hiddenSkillIds`, the per-iteration answer to
|
|
1709
|
+
* "which skills may this caller be told about". The first draft applied
|
|
1710
|
+
* neither, and named a hidden skill's tool to a role that may not see the
|
|
1711
|
+
* skill — the leak the `read_skill` refusals had just closed, reappearing one
|
|
1712
|
+
* sentence over because the roster read the dispatch map raw.
|
|
1713
|
+
*
|
|
1714
|
+
* A tool is unnameable only when EVERY skill declaring it is hidden
|
|
1715
|
+
* (`deps.toolDeclaringSkills`, the build-time owner of that fact). A tool two
|
|
1716
|
+
* skills share is still named when either is visible — the same
|
|
1717
|
+
* sole-owner rule the step hold-out uses, for the same reason: a shared name
|
|
1718
|
+
* is somebody's escape hatch. Names nobody declares from a skill — static
|
|
1719
|
+
* `.tool()` registrations, `read_skill`, `skip_step`, provider-delivered
|
|
1720
|
+
* tools — have no owning skill and are never filtered.
|
|
1721
|
+
*
|
|
1722
|
+
* DISPATCH IS UNTOUCHED. `lookupTool` still resolves every name in the map:
|
|
1723
|
+
* a narrowing may take a name out of a sentence, never out of the dispatch
|
|
1724
|
+
* map. This filter governs only what the model is TOLD.
|
|
1725
|
+
*/
|
|
1726
|
+
const dispatchRoster = (scope) => {
|
|
1727
|
+
const names = [...registryByName.keys()];
|
|
1728
|
+
const seen = new Set(names);
|
|
1729
|
+
for (const t of externalToolProvider ? providerToolCache?.current ?? [] : []) {
|
|
1730
|
+
if (seen.has(t.schema.name))
|
|
1731
|
+
continue;
|
|
1732
|
+
seen.add(t.schema.name);
|
|
1733
|
+
names.push(t.schema.name);
|
|
1734
|
+
}
|
|
1735
|
+
const hidden = new Set(scope.hiddenSkillIds ?? []);
|
|
1736
|
+
if (hidden.size === 0)
|
|
1737
|
+
return { named: names, held: names.length > 0 };
|
|
1738
|
+
const declaredBy = deps.toolDeclaringSkills;
|
|
1739
|
+
return (0, spokenIds_js_1.spoken)(names, (name) => {
|
|
1740
|
+
const owners = declaredBy?.get(name);
|
|
1741
|
+
return owners === undefined || owners.some((id) => !hidden.has(id));
|
|
1742
|
+
});
|
|
1743
|
+
};
|
|
1430
1744
|
/**
|
|
1431
1745
|
* The after-tool moment: the chain's last word, on a call that RAN.
|
|
1432
1746
|
*
|
|
@@ -1484,7 +1798,7 @@ function buildToolCallsHandler(deps) {
|
|
|
1484
1798
|
// resume can't re-pause (resume returns void), so that is surfaced as an error.
|
|
1485
1799
|
const resolveCredentialAndExecute = async (scope, tool, toolName, args, toolCallId, iteration, env) => {
|
|
1486
1800
|
if (!tool)
|
|
1487
|
-
return { result:
|
|
1801
|
+
return { result: unknownToolResult(toolName, dispatchRoster(scope)), error: true };
|
|
1488
1802
|
// Declared artifact arguments (9.22.0) — the same resolution the batch
|
|
1489
1803
|
// loop applies, at this door: a resumed call's refs are judged exactly
|
|
1490
1804
|
// as an inline call's, BEFORE credentials, and a refusal means the tool
|
|
@@ -2117,7 +2431,37 @@ function buildToolCallsHandler(deps) {
|
|
|
2117
2431
|
(0, types_js_1.checkerGoverns)(permissionChecker, 'skill_read')) {
|
|
2118
2432
|
const requestedSkillId = callArgs.id;
|
|
2119
2433
|
if (typeof requestedSkillId === 'string' && requestedSkillId.length > 0) {
|
|
2120
|
-
|
|
2434
|
+
// ── A SELF-CALL AT A MOUNTED CURSOR EXERCISES NO CAPABILITY ───
|
|
2435
|
+
// (9.86.0.) A MOUNTED cursor's own skill is already served: its
|
|
2436
|
+
// body is in that call's system prompt and its tools are on that
|
|
2437
|
+
// call's wire. Asking to read it activates nothing, moves nothing,
|
|
2438
|
+
// and reveals nothing the request did not already carry — so there
|
|
2439
|
+
// is no capability here for a policy to grant or deny, and denying
|
|
2440
|
+
// it told the model its current skill was "not available in this
|
|
2441
|
+
// context" while it was standing in it. That is the exact sentence
|
|
2442
|
+
// the self-call arm exists to stop the model reading. The gate's
|
|
2443
|
+
// self-call arm answers instead.
|
|
2444
|
+
//
|
|
2445
|
+
// A PARKED cursor is the one case where the same id is not a
|
|
2446
|
+
// no-op, and the skip is scoped to exclude it (9.86.0 fix pass):
|
|
2447
|
+
// parking suppressed the map's contribution, so the dispatch gate
|
|
2448
|
+
// 480 lines below reads that call as a RE-ENGAGEMENT and serves the
|
|
2449
|
+
// body and tools again on the next pass. That moves something, so
|
|
2450
|
+
// the policy keeps the question. `atMountedCursor` is where that
|
|
2451
|
+
// rule lives; the dispatch gate writes the same rule as two ordered
|
|
2452
|
+
// arms over the same `parkedNow` set. Every OTHER id still goes
|
|
2453
|
+
// through the policy, unchanged — this narrows nothing a role could
|
|
2454
|
+
// see.
|
|
2455
|
+
const stay = atMountedCursor({
|
|
2456
|
+
...(typeof scope.currentSkillId === 'string' && {
|
|
2457
|
+
cursor: scope.currentSkillId,
|
|
2458
|
+
}),
|
|
2459
|
+
target: requestedSkillId,
|
|
2460
|
+
parked: parkedNow(scope),
|
|
2461
|
+
});
|
|
2462
|
+
if (!stay) {
|
|
2463
|
+
await askCapability('skill_read', (0, skillTarget_js_1.skillTarget)(requestedSkillId), `Skill '${requestedSkillId}' is not available in this context.`);
|
|
2464
|
+
}
|
|
2121
2465
|
}
|
|
2122
2466
|
}
|
|
2123
2467
|
// ── Check-in gate (evidence-carrying human consent) ──────────────
|
|
@@ -2321,7 +2665,7 @@ function buildToolCallsHandler(deps) {
|
|
|
2321
2665
|
if (!credentialBlocked && !wantsBlocked) {
|
|
2322
2666
|
try {
|
|
2323
2667
|
if (!tool)
|
|
2324
|
-
throw new Error(
|
|
2668
|
+
throw new Error(unknownToolResult(tc.name, dispatchRoster(scope)));
|
|
2325
2669
|
// Set BEFORE the await: a tool that throws has still run, and a
|
|
2326
2670
|
// tool that does not exist has not. This flag is the entire
|
|
2327
2671
|
// precondition of the after-tool moment below.
|
|
@@ -2493,9 +2837,53 @@ function buildToolCallsHandler(deps) {
|
|
|
2493
2837
|
const reqId = callArgs.id;
|
|
2494
2838
|
if (typeof reqId === 'string' && reqId.length > 0) {
|
|
2495
2839
|
const currentSkillId = scope.currentSkillId;
|
|
2840
|
+
// ── ADMISSION reads the graph; every SENTENCE reads the filter ──
|
|
2841
|
+
// Two sets, deliberately (9.86.0). `admissible` is the routing law:
|
|
2842
|
+
// what the graph and the mount actually grant, unfiltered, because a
|
|
2843
|
+
// narrowing may take a schema off the wire but never a name out of
|
|
2844
|
+
// the dispatch map — role visibility governs what the model is TOLD,
|
|
2845
|
+
// not what the gate admits. `hopsNamed` / `openNamed` are what any
|
|
2846
|
+
// sentence, and the `skill.rejected` payload, may NAME: the same
|
|
2847
|
+
// hidden set the description filtered itself with, resolved once by
|
|
2848
|
+
// the tools slot and carried here on scope. Without it the refusals
|
|
2849
|
+
// read the raw sets and could name a skill this role may never see —
|
|
2850
|
+
// the leak the description closed, one stage downstream. (A hidden
|
|
2851
|
+
// skill cannot reach this gate anyway: the `skill_read` permission
|
|
2852
|
+
// gate above denies it first, on the same checker. The filter is
|
|
2853
|
+
// what keeps that true by construction rather than by coincidence.)
|
|
2854
|
+
const hiddenIds = new Set(scope.hiddenSkillIds ?? []);
|
|
2855
|
+
const mayName = (id) => !hiddenIds.has(id);
|
|
2496
2856
|
const hops = deps.allowedSkillIds(currentSkillId);
|
|
2497
|
-
|
|
2498
|
-
|
|
2857
|
+
const openAll = deps.openSkillIds ?? [];
|
|
2858
|
+
// `spoken` keeps BOTH halves — what may be named, and whether the
|
|
2859
|
+
// graph held anything before the filter ran. A composer handed only
|
|
2860
|
+
// the filtered array cannot tell "nothing was reachable" from
|
|
2861
|
+
// "nothing reachable may be named", and it used to assert the first.
|
|
2862
|
+
const hopsSpoken = (0, spokenIds_js_1.spoken)(hops, mayName);
|
|
2863
|
+
const openSpoken = (0, spokenIds_js_1.spoken)(openAll, mayName);
|
|
2864
|
+
// The cursor through the SAME filter (9.86.1). The description
|
|
2865
|
+
// withholds a hidden cursor's name and every refusal below printed
|
|
2866
|
+
// it raw in two clauses; `cursorWithheld` keeps the sentence's
|
|
2867
|
+
// anchor honest ("the skill the cursor stood in") without the id.
|
|
2868
|
+
const cursorSpoken = currentSkillId === undefined
|
|
2869
|
+
? {}
|
|
2870
|
+
: mayName(currentSkillId)
|
|
2871
|
+
? { cursorId: currentSkillId }
|
|
2872
|
+
: { cursorWithheld: true };
|
|
2873
|
+
const hopsNamed = hopsSpoken.named;
|
|
2874
|
+
const openNamed = openSpoken.named;
|
|
2875
|
+
// The ONE owner of "is this target the cursor?" — the same function
|
|
2876
|
+
// the description, the tool-effects judge and the permission gate
|
|
2877
|
+
// switch on, so a stay means the same thing in all four.
|
|
2878
|
+
const targetClass = (0, skillGraph_js_1.classifySkillTarget)({
|
|
2879
|
+
...(currentSkillId !== undefined && { cursor: currentSkillId }),
|
|
2880
|
+
target: reqId,
|
|
2881
|
+
hops,
|
|
2882
|
+
open: openAll,
|
|
2883
|
+
});
|
|
2884
|
+
skillHop = targetClass === 'hop';
|
|
2885
|
+
const admissible = dedupeIds([...hops, ...openAll]);
|
|
2886
|
+
const allowed = dedupeIds([...hopsNamed, ...openNamed]);
|
|
2499
2887
|
// ── The RE-ENGAGEMENT arm (9.59.0) — a third admission class ──
|
|
2500
2888
|
// A pick must be routed by INTENT. The reachable set deliberately
|
|
2501
2889
|
// excludes the node the cursor already occupies (`makeReachableSkills`
|
|
@@ -2513,15 +2901,13 @@ function buildToolCallsHandler(deps) {
|
|
|
2513
2901
|
// false, `pendingSkillPick` stays unwritten, and the pick lands on
|
|
2514
2902
|
// `acceptedSkillPicks`, which the kernel's renewal feed reads as
|
|
2515
2903
|
// explicit evidence and re-engages on the very next pass.
|
|
2516
|
-
const parked =
|
|
2517
|
-
? undefined
|
|
2518
|
-
: (0, types_js_2.parkedMemberIds)(deps.engagementPlan, scope.mapEngagement);
|
|
2904
|
+
const parked = parkedNow(scope);
|
|
2519
2905
|
const reengaging = parked?.has(reqId) === true;
|
|
2520
2906
|
if (reengaging) {
|
|
2521
2907
|
// Admitted. Nothing to refuse and no posture to apply: this is
|
|
2522
2908
|
// not a hop, and a posture governs routing, not engagement.
|
|
2523
2909
|
}
|
|
2524
|
-
else if (
|
|
2910
|
+
else if (targetClass === 'self') {
|
|
2525
2911
|
// ── The SELF-CALL arm (9.84.0) — BEFORE reachability ─────────
|
|
2526
2912
|
// The cursor is in neither half of `allowed`, by construction:
|
|
2527
2913
|
// `makeReachableSkills` filters it out of its own successor set
|
|
@@ -2596,9 +2982,16 @@ function buildToolCallsHandler(deps) {
|
|
|
2596
2982
|
});
|
|
2597
2983
|
noteSkillRefusal(scope, iteration);
|
|
2598
2984
|
}
|
|
2599
|
-
else if (!
|
|
2985
|
+
else if (!admissible.includes(reqId)) {
|
|
2600
2986
|
skillRejected = true;
|
|
2601
|
-
result =
|
|
2987
|
+
result = composeReadSkillRefusal({
|
|
2988
|
+
requestedId: reqId,
|
|
2989
|
+
targetClass,
|
|
2990
|
+
...cursorSpoken,
|
|
2991
|
+
hops: hopsSpoken,
|
|
2992
|
+
openIds: openSpoken,
|
|
2993
|
+
...(deps.skillGraphIsTree === true && { isTree: true }),
|
|
2994
|
+
});
|
|
2602
2995
|
(0, typedEmit_js_1.typedEmit)(scope, 'agentfootprint.skill.rejected', {
|
|
2603
2996
|
requestedId: reqId,
|
|
2604
2997
|
...(currentSkillId !== undefined && { currentSkillId }),
|
|
@@ -2620,7 +3013,30 @@ function buildToolCallsHandler(deps) {
|
|
|
2620
3013
|
if (refusedByPosture) {
|
|
2621
3014
|
skillRejected = true;
|
|
2622
3015
|
skillHop = false; // a refused pick must not move the cursor below
|
|
2623
|
-
|
|
3016
|
+
// The posture arm names NO hop — under 'rails' the model does
|
|
3017
|
+
// not route, and under 'guard' it routes only from a menu — so
|
|
3018
|
+
// the composer is handed the menu when there IS one and an
|
|
3019
|
+
// empty hop list either way. Open skills are still named:
|
|
3020
|
+
// they never reach this arm, so nothing here can decline them.
|
|
3021
|
+
const menuNamed = turnRoute?.offered !== undefined && (0, routingPolicy_js_1.menuOutstanding)(turnRoute, currentSkillId)
|
|
3022
|
+
? (0, spokenIds_js_1.spoken)(turnRoute.offered, mayName)
|
|
3023
|
+
: undefined;
|
|
3024
|
+
result = composeReadSkillRefusal({
|
|
3025
|
+
requestedId: reqId,
|
|
3026
|
+
targetClass,
|
|
3027
|
+
...cursorSpoken,
|
|
3028
|
+
// No hop is named under a posture, and the graph's hop set
|
|
3029
|
+
// is not this arm's subject — `held: false` is the honest
|
|
3030
|
+
// shape, not a claim that nothing was reachable (this arm
|
|
3031
|
+
// never composes the reachability clause at all).
|
|
3032
|
+
hops: { named: [], held: false },
|
|
3033
|
+
openIds: openSpoken,
|
|
3034
|
+
posture: deps.skillStrictness,
|
|
3035
|
+
...(menuNamed !== undefined && { menuOffered: menuNamed }),
|
|
3036
|
+
// The whole verdict, not a boolean derived from two of its
|
|
3037
|
+
// six values: the composer says one past fact per value.
|
|
3038
|
+
...(turnRoute !== undefined && { turnStartedBy: turnRoute.by }),
|
|
3039
|
+
});
|
|
2624
3040
|
(0, typedEmit_js_1.typedEmit)(scope, 'agentfootprint.skill.rejected', {
|
|
2625
3041
|
requestedId: reqId,
|
|
2626
3042
|
...(currentSkillId !== undefined && { currentSkillId }),
|
|
@@ -3065,10 +3481,14 @@ function buildToolCallsHandler(deps) {
|
|
|
3065
3481
|
// silently invents a decision, for the same reason it never silently
|
|
3066
3482
|
// drops one.
|
|
3067
3483
|
error = true;
|
|
3484
|
+
// A past fact about the resumed call, not a forecast about the
|
|
3485
|
+
// turn (9.86.1): "cannot be retried this turn … Answer without it,
|
|
3486
|
+
// or finish" was a prediction plus a standing order on a result
|
|
3487
|
+
// that is re-read on every later call.
|
|
3068
3488
|
result =
|
|
3069
|
-
`tool '${toolName}' was not executed
|
|
3070
|
-
`
|
|
3071
|
-
`
|
|
3489
|
+
`tool '${toolName}' was not executed on that call, and the resumed dispatch had ` +
|
|
3490
|
+
`no second checkpoint to retry it on: it declares its own checkIn consent gate, ` +
|
|
3491
|
+
`and that gate tripped for those arguments. (To ` +
|
|
3072
3492
|
`the agent's author: the middleware '${askedBy}' and the tool's checkIn ask ` +
|
|
3073
3493
|
`different questions — one is the rule's, one is the tool's with the evidence ` +
|
|
3074
3494
|
`pack attached — so approving one is not answering the other. Keep one gate for ` +
|