agentfootprint 9.19.0 → 9.20.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/AGENTS.md +1 -1
- package/CLAUDE.md +3 -1
- package/ai-instructions/claude-code/SKILL.md +1 -1
- package/dist/core/agent/resultCeiling.js +65 -0
- package/dist/core/agent/resultCeiling.js.map +1 -0
- package/dist/core/agent/stages/toolCalls.js +104 -8
- package/dist/core/agent/stages/toolCalls.js.map +1 -1
- package/dist/core/tools.js +34 -1
- package/dist/core/tools.js.map +1 -1
- package/dist/esm/core/agent/resultCeiling.d.ts +58 -0
- package/dist/esm/core/agent/resultCeiling.js +61 -0
- package/dist/esm/core/agent/resultCeiling.js.map +1 -0
- package/dist/esm/core/agent/stages/toolCalls.js +104 -8
- package/dist/esm/core/agent/stages/toolCalls.js.map +1 -1
- package/dist/esm/core/tools.d.ts +46 -0
- package/dist/esm/core/tools.js +32 -0
- package/dist/esm/core/tools.js.map +1 -1
- package/dist/esm/events/payloads.d.ts +29 -1
- package/dist/esm/events/payloads.js +3 -1
- package/dist/esm/events/payloads.js.map +1 -1
- package/dist/esm/events/registry.d.ts +3 -1
- package/dist/esm/events/registry.js +2 -0
- package/dist/esm/events/registry.js.map +1 -1
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/lib/injection-engine/skillGraph.js +7 -7
- package/dist/esm/lib/injection-engine/skillGraph.js.map +1 -1
- package/dist/esm/lib/injection-engine/skillGraphCheckup.js +9 -0
- package/dist/esm/lib/injection-engine/skillGraphCheckup.js.map +1 -1
- package/dist/esm/lib/injection-engine/skillMatch.d.ts +43 -3
- package/dist/esm/lib/injection-engine/skillMatch.js +177 -13
- package/dist/esm/lib/injection-engine/skillMatch.js.map +1 -1
- package/dist/events/payloads.js +3 -1
- package/dist/events/payloads.js.map +1 -1
- package/dist/events/registry.js +2 -0
- package/dist/events/registry.js.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/injection-engine/skillGraph.js +7 -7
- package/dist/lib/injection-engine/skillGraph.js.map +1 -1
- package/dist/lib/injection-engine/skillGraphCheckup.js +9 -0
- package/dist/lib/injection-engine/skillGraphCheckup.js.map +1 -1
- package/dist/lib/injection-engine/skillMatch.js +177 -13
- package/dist/lib/injection-engine/skillMatch.js.map +1 -1
- package/dist/types/core/agent/resultCeiling.d.ts +59 -0
- package/dist/types/core/agent/resultCeiling.d.ts.map +1 -0
- package/dist/types/core/agent/stages/toolCalls.d.ts.map +1 -1
- package/dist/types/core/tools.d.ts +46 -0
- package/dist/types/core/tools.d.ts.map +1 -1
- package/dist/types/events/payloads.d.ts +29 -1
- package/dist/types/events/payloads.d.ts.map +1 -1
- package/dist/types/events/registry.d.ts +3 -1
- package/dist/types/events/registry.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/lib/injection-engine/skillGraphCheckup.d.ts.map +1 -1
- package/dist/types/lib/injection-engine/skillMatch.d.ts +43 -3
- package/dist/types/lib/injection-engine/skillMatch.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/core/tools.js
CHANGED
|
@@ -8,8 +8,37 @@
|
|
|
8
8
|
* Emits: N/A (types only).
|
|
9
9
|
*/
|
|
10
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
-
exports.defineTool = exports.warnIfInvalidToolName = exports.assertValidToolName = void 0;
|
|
11
|
+
exports.defineTool = exports.warnIfInvalidToolName = exports.assertValidToolName = exports.assertResultCeiling = void 0;
|
|
12
12
|
const footprintjs_1 = require("footprintjs");
|
|
13
|
+
/**
|
|
14
|
+
* Refuse a `resultCeiling` this library cannot honor, at definition time —
|
|
15
|
+
* naming the tool and the fix, never failing at the first oversized result of
|
|
16
|
+
* the first run. Exported beside {@link assertValidToolName} for consumers
|
|
17
|
+
* assembling `Tool` objects by hand.
|
|
18
|
+
*/
|
|
19
|
+
function assertResultCeiling(toolName, ceiling) {
|
|
20
|
+
if (ceiling === undefined)
|
|
21
|
+
return;
|
|
22
|
+
const { maxChars, narrowBy } = ceiling;
|
|
23
|
+
if (!Number.isFinite(maxChars) || !Number.isInteger(maxChars) || maxChars <= 0) {
|
|
24
|
+
throw new Error(`defineTool: tool '${toolName}' declares resultCeiling.maxChars = ${String(maxChars)}, ` +
|
|
25
|
+
`which is not a positive whole number of characters. The ceiling is the size over ` +
|
|
26
|
+
`which the model reads a refusal instead of the result — to have no ceiling, omit ` +
|
|
27
|
+
`\`resultCeiling\` (absent means results are never measured, exactly as before).`);
|
|
28
|
+
}
|
|
29
|
+
if (narrowBy !== undefined) {
|
|
30
|
+
const usable = Array.isArray(narrowBy) &&
|
|
31
|
+
narrowBy.length > 0 &&
|
|
32
|
+
narrowBy.every((n) => typeof n === 'string' && n.trim().length > 0);
|
|
33
|
+
if (!usable) {
|
|
34
|
+
throw new Error(`defineTool: tool '${toolName}' declares resultCeiling.narrowBy = ` +
|
|
35
|
+
`${JSON.stringify(narrowBy)}, which names nothing the refusal could suggest. ` +
|
|
36
|
+
`List at least one parameter name (non-empty strings, e.g. ['limit', 'fields']), ` +
|
|
37
|
+
`or drop the field — omitting it is how "no suggestions" is said.`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
exports.assertResultCeiling = assertResultCeiling;
|
|
13
42
|
/**
|
|
14
43
|
* Ergonomic builder for `Tool`. Equivalent to constructing an object
|
|
15
44
|
* literal with `schema` + `execute`, but flatter and safer — the name
|
|
@@ -81,6 +110,9 @@ function warnIfInvalidToolName(name) {
|
|
|
81
110
|
exports.warnIfInvalidToolName = warnIfInvalidToolName;
|
|
82
111
|
function defineTool(options) {
|
|
83
112
|
warnIfInvalidToolName(options.name);
|
|
113
|
+
// A ceiling that cannot cap fails HERE, naming the tool — not at the first
|
|
114
|
+
// oversized result of the first run.
|
|
115
|
+
assertResultCeiling(options.name, options.resultCeiling);
|
|
84
116
|
return {
|
|
85
117
|
schema: {
|
|
86
118
|
name: options.name,
|
|
@@ -94,6 +126,7 @@ function defineTool(options) {
|
|
|
94
126
|
// Copied verbatim, never inferred — an empty array is a tool that declared
|
|
95
127
|
// it touches nothing, which is a different statement from saying nothing.
|
|
96
128
|
...(options.capabilities !== undefined && { capabilities: options.capabilities }),
|
|
129
|
+
...(options.resultCeiling !== undefined && { resultCeiling: options.resultCeiling }),
|
|
97
130
|
execute: options.execute,
|
|
98
131
|
};
|
|
99
132
|
}
|
package/dist/core/tools.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../../src/core/tools.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;AAEH,6CAAwC;
|
|
1
|
+
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../../src/core/tools.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* resultCeiling — the refusing ceiling a tool declares on its OWN result
|
|
3
|
+
* (9.20.0). One owner of the measurement and the refusal sentence; the
|
|
4
|
+
* tool-dispatch loop (`stages/toolCalls.ts`) calls it at every boundary where
|
|
5
|
+
* a handler's return becomes final, so a resumed call is refused exactly as an
|
|
6
|
+
* inline one.
|
|
7
|
+
*
|
|
8
|
+
* Pattern: measured refusal at the dispatch boundary — the sibling of
|
|
9
|
+
* `toolResultCap.ts`, with the OPPOSITE answer to overflow, because
|
|
10
|
+
* the two serve different owners:
|
|
11
|
+
*
|
|
12
|
+
* • `maxToolResultChars` (agent-level, 9.11.0) TRUNCATES: the operator caps
|
|
13
|
+
* tools they did not write, so the most honest deliverable is a marker
|
|
14
|
+
* plus a verbatim head of whatever came back.
|
|
15
|
+
* • `resultCeiling` (per-tool, this module) REFUSES: the AUTHOR declared
|
|
16
|
+
* what a sane result is and which parameters (`narrowBy`) make a retry
|
|
17
|
+
* smaller. A truncated result reads as a complete one — the model cannot
|
|
18
|
+
* tell the data ends where the cut happened and fabricates from the part
|
|
19
|
+
* it saw — while a refusal that says "No data was returned" and names the
|
|
20
|
+
* narrowing parameters produces a clean retry (field-verified on a
|
|
21
|
+
* ~191k-char return).
|
|
22
|
+
*
|
|
23
|
+
* The oversized payload never leaves the dispatch: not into history, not into
|
|
24
|
+
* `stream.tool_end`, not into any recorder. The RECORD keeps the truth as the
|
|
25
|
+
* typed `agentfootprint.tools.result_refused` event (true size, ceiling,
|
|
26
|
+
* suggestions), emitted by the caller beside the replacement; the delivered
|
|
27
|
+
* result carries status `'invalid'` — the closed-set member whose corrective
|
|
28
|
+
* action is "fix the call" — so `onToolStatus` edges can route on it.
|
|
29
|
+
*
|
|
30
|
+
* Emits: nothing itself (pure measurement); zero-cost-when-unused — no
|
|
31
|
+
* declared ceiling means one `undefined` check and byte-identical behavior.
|
|
32
|
+
*/
|
|
33
|
+
import type { ToolResultCeiling } from '../tools.js';
|
|
34
|
+
/** What an over-ceiling measurement hands the dispatch loop: the sentence the
|
|
35
|
+
* model reads and the true size for the record. `undefined` from
|
|
36
|
+
* {@link applyResultCeiling} means no ceiling or under it — untouched path. */
|
|
37
|
+
export interface ResultCeilingRefusal {
|
|
38
|
+
/** The teaching refusal — the ONLY thing the model (and every downstream
|
|
39
|
+
* channel) receives in place of the oversized payload. */
|
|
40
|
+
readonly refusal: string;
|
|
41
|
+
/** The stringified result's true length — the record's fact, carried by the
|
|
42
|
+
* `tools.result_refused` event, never by any content channel. */
|
|
43
|
+
readonly sizeChars: number;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Measure one handler result against the tool's declared ceiling.
|
|
47
|
+
*
|
|
48
|
+
* Returns `undefined` when there is no ceiling or the result fits — the caller
|
|
49
|
+
* keeps every byte of today's path. Over the ceiling, returns the refusal to
|
|
50
|
+
* deliver INSTEAD of the payload. What is measured is the string the model
|
|
51
|
+
* would have read (strings as-is, everything else `safeStringify`d) — the same
|
|
52
|
+
* rule `toolResultCap.ts` measures by, so the two ceilings always argue about
|
|
53
|
+
* the same number.
|
|
54
|
+
*/
|
|
55
|
+
export declare function applyResultCeiling(value: unknown, opts: {
|
|
56
|
+
readonly toolName: string;
|
|
57
|
+
readonly ceiling: ToolResultCeiling | undefined;
|
|
58
|
+
}): ResultCeilingRefusal | undefined;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* resultCeiling — the refusing ceiling a tool declares on its OWN result
|
|
3
|
+
* (9.20.0). One owner of the measurement and the refusal sentence; the
|
|
4
|
+
* tool-dispatch loop (`stages/toolCalls.ts`) calls it at every boundary where
|
|
5
|
+
* a handler's return becomes final, so a resumed call is refused exactly as an
|
|
6
|
+
* inline one.
|
|
7
|
+
*
|
|
8
|
+
* Pattern: measured refusal at the dispatch boundary — the sibling of
|
|
9
|
+
* `toolResultCap.ts`, with the OPPOSITE answer to overflow, because
|
|
10
|
+
* the two serve different owners:
|
|
11
|
+
*
|
|
12
|
+
* • `maxToolResultChars` (agent-level, 9.11.0) TRUNCATES: the operator caps
|
|
13
|
+
* tools they did not write, so the most honest deliverable is a marker
|
|
14
|
+
* plus a verbatim head of whatever came back.
|
|
15
|
+
* • `resultCeiling` (per-tool, this module) REFUSES: the AUTHOR declared
|
|
16
|
+
* what a sane result is and which parameters (`narrowBy`) make a retry
|
|
17
|
+
* smaller. A truncated result reads as a complete one — the model cannot
|
|
18
|
+
* tell the data ends where the cut happened and fabricates from the part
|
|
19
|
+
* it saw — while a refusal that says "No data was returned" and names the
|
|
20
|
+
* narrowing parameters produces a clean retry (field-verified on a
|
|
21
|
+
* ~191k-char return).
|
|
22
|
+
*
|
|
23
|
+
* The oversized payload never leaves the dispatch: not into history, not into
|
|
24
|
+
* `stream.tool_end`, not into any recorder. The RECORD keeps the truth as the
|
|
25
|
+
* typed `agentfootprint.tools.result_refused` event (true size, ceiling,
|
|
26
|
+
* suggestions), emitted by the caller beside the replacement; the delivered
|
|
27
|
+
* result carries status `'invalid'` — the closed-set member whose corrective
|
|
28
|
+
* action is "fix the call" — so `onToolStatus` edges can route on it.
|
|
29
|
+
*
|
|
30
|
+
* Emits: nothing itself (pure measurement); zero-cost-when-unused — no
|
|
31
|
+
* declared ceiling means one `undefined` check and byte-identical behavior.
|
|
32
|
+
*/
|
|
33
|
+
import { safeStringify } from './validators.js';
|
|
34
|
+
/**
|
|
35
|
+
* Measure one handler result against the tool's declared ceiling.
|
|
36
|
+
*
|
|
37
|
+
* Returns `undefined` when there is no ceiling or the result fits — the caller
|
|
38
|
+
* keeps every byte of today's path. Over the ceiling, returns the refusal to
|
|
39
|
+
* deliver INSTEAD of the payload. What is measured is the string the model
|
|
40
|
+
* would have read (strings as-is, everything else `safeStringify`d) — the same
|
|
41
|
+
* rule `toolResultCap.ts` measures by, so the two ceilings always argue about
|
|
42
|
+
* the same number.
|
|
43
|
+
*/
|
|
44
|
+
export function applyResultCeiling(value, opts) {
|
|
45
|
+
const ceiling = opts.ceiling;
|
|
46
|
+
if (ceiling === undefined)
|
|
47
|
+
return undefined;
|
|
48
|
+
const text = typeof value === 'string' ? value : safeStringify(value);
|
|
49
|
+
if (text.length <= ceiling.maxChars)
|
|
50
|
+
return undefined;
|
|
51
|
+
const narrow = ceiling.narrowBy !== undefined && ceiling.narrowBy.length > 0
|
|
52
|
+
? ` — e.g. pass ${ceiling.narrowBy.map((n) => `'${n}'`).join(', ')}`
|
|
53
|
+
: '';
|
|
54
|
+
return {
|
|
55
|
+
refusal: `Result too large: ${opts.toolName} returned ${text.length} chars, over its declared ` +
|
|
56
|
+
`${ceiling.maxChars}-char ceiling. Narrow the request and call again${narrow}. ` +
|
|
57
|
+
`No data was returned.`,
|
|
58
|
+
sizeChars: text.length,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=resultCeiling.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resultCeiling.js","sourceRoot":"","sources":["../../../../src/core/agent/resultCeiling.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAGH,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAchD;;;;;;;;;GASG;AACH,MAAM,UAAU,kBAAkB,CAChC,KAAc,EACd,IAAoF;IAEpF,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;IAC7B,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC5C,MAAM,IAAI,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;IACtE,IAAI,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC,QAAQ;QAAE,OAAO,SAAS,CAAC;IACtD,MAAM,MAAM,GACV,OAAO,CAAC,QAAQ,KAAK,SAAS,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC;QAC3D,CAAC,CAAC,gBAAgB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;QACpE,CAAC,CAAC,EAAE,CAAC;IACT,OAAO;QACL,OAAO,EACL,qBAAqB,IAAI,CAAC,QAAQ,aAAa,IAAI,CAAC,MAAM,4BAA4B;YACtF,GAAG,OAAO,CAAC,QAAQ,mDAAmD,MAAM,IAAI;YAChF,uBAAuB;QACzB,SAAS,EAAE,IAAI,CAAC,MAAM;KACvB,CAAC;AACJ,CAAC"}
|
|
@@ -57,6 +57,7 @@ import { recordDecisions } from '../middleware/ledger.js';
|
|
|
57
57
|
import { formatToolArgIssues, validateToolArgs, } from '../toolArgsValidation.js';
|
|
58
58
|
import { safeStringify } from '../validators.js';
|
|
59
59
|
import { capToolResult } from '../toolResultCap.js';
|
|
60
|
+
import { applyResultCeiling } from '../resultCeiling.js';
|
|
60
61
|
import { currentStepOf, pointerOf, readSkillStepIntro, skipAdvanceSentence, skipHoldSentence, skipNeedsReasonSentence, skipNothingActiveSentence, stepAdvanceSuffix, stepInProgress, SKIP_STEP_TOOL_NAME, } from '../../../lib/injection-engine/skillSteps.js';
|
|
61
62
|
import { explainStatusOnlyNearMiss, pruneLeases, readToolResultEnvelope, tenantOf, } from '../toolEffects.js';
|
|
62
63
|
/** Declaration order preserved, ids de-duplicated — the shape the gate's re-prompt
|
|
@@ -252,6 +253,38 @@ export function buildToolCallsHandler(deps) {
|
|
|
252
253
|
: capToolResult(values.modelResult, { toolName, maxChars: max }).value,
|
|
253
254
|
};
|
|
254
255
|
};
|
|
256
|
+
/**
|
|
257
|
+
* The tool's OWN refusing ceiling (9.20.0) — ONE implementation for every
|
|
258
|
+
* dispatch door, applied at the execute boundary the moment the handler's
|
|
259
|
+
* return lands (BEFORE the after-tool chain, the read_skill-gate precedent:
|
|
260
|
+
* governance composes over what the model will actually read).
|
|
261
|
+
*
|
|
262
|
+
* `undefined` = no ceiling declared or the result fits — the caller keeps
|
|
263
|
+
* today's path byte for byte. Otherwise: the typed `tools.result_refused`
|
|
264
|
+
* event records the true size (the only place it survives), and the returned
|
|
265
|
+
* TEACHING REFUSAL replaces the payload on every channel — history,
|
|
266
|
+
* `stream.tool_end`, recorders. Distinct from the agent-level
|
|
267
|
+
* `maxToolResultChars` (which truncates with a verbatim head, measured AFTER
|
|
268
|
+
* the chain): this is the tool author's own contract, and the two compose —
|
|
269
|
+
* the refusal sentence is far under any sane agent cap.
|
|
270
|
+
*/
|
|
271
|
+
const refuseOverCeiling = (scope, call, tool, value, declaredStatus) => {
|
|
272
|
+
const ceiling = tool?.resultCeiling;
|
|
273
|
+
const verdict = applyResultCeiling(value, { toolName: call.toolName, ceiling });
|
|
274
|
+
if (verdict === undefined || ceiling === undefined)
|
|
275
|
+
return undefined;
|
|
276
|
+
typedEmit(scope, 'agentfootprint.tools.result_refused', {
|
|
277
|
+
toolName: call.toolName,
|
|
278
|
+
toolCallId: call.toolCallId,
|
|
279
|
+
iteration: call.iteration,
|
|
280
|
+
sizeChars: verdict.sizeChars,
|
|
281
|
+
maxChars: ceiling.maxChars,
|
|
282
|
+
// Copied — event payloads are detached plain data, never shared refs.
|
|
283
|
+
...(ceiling.narrowBy !== undefined && { narrowBy: [...ceiling.narrowBy] }),
|
|
284
|
+
...(declaredStatus !== undefined && { declaredStatus }),
|
|
285
|
+
});
|
|
286
|
+
return verdict.refusal;
|
|
287
|
+
};
|
|
255
288
|
// ── Step-procedure boundary (9.18.0) — ONE implementation, five sites ──
|
|
256
289
|
// The batch loop and the four resume paths all finalize results; each
|
|
257
290
|
// calls these where its result becomes final, so a pointer can never miss
|
|
@@ -789,8 +822,37 @@ export function buildToolCallsHandler(deps) {
|
|
|
789
822
|
// judged exactly as an inline one's.
|
|
790
823
|
const envelope = readToolResultEnvelope(result);
|
|
791
824
|
if (envelope !== undefined) {
|
|
825
|
+
// The tool's own ceiling (9.20.0) measures the CONTENT — the channel
|
|
826
|
+
// that can overflow a context window. The DECLARED effects are small
|
|
827
|
+
// validated data and are still judged by the caller: a tool that
|
|
828
|
+
// proposed a transition and overflowed its payload does not lose the
|
|
829
|
+
// transition. The delivered status becomes 'invalid' (the declared
|
|
830
|
+
// status described a result the model never received — the
|
|
831
|
+
// `result_refused` event keeps what was declared).
|
|
832
|
+
const refusal = refuseOverCeiling(scope, { toolName, toolCallId, iteration }, tool, envelope.content, envelope.status);
|
|
833
|
+
if (refusal !== undefined) {
|
|
834
|
+
return {
|
|
835
|
+
result: refusal,
|
|
836
|
+
executed: true,
|
|
837
|
+
ceilingRefused: true,
|
|
838
|
+
envelope: { ...envelope, content: refusal, status: 'invalid' },
|
|
839
|
+
};
|
|
840
|
+
}
|
|
792
841
|
return { result: envelope.content, executed: true, envelope };
|
|
793
842
|
}
|
|
843
|
+
const refusal = refuseOverCeiling(scope, { toolName, toolCallId, iteration }, tool, result);
|
|
844
|
+
if (refusal !== undefined) {
|
|
845
|
+
// Synthesized empty-effects envelope: the resume paths read the
|
|
846
|
+
// routing status off `envelope.status`, and this is how a refused
|
|
847
|
+
// NON-envelope result carries `'invalid'` there without every path
|
|
848
|
+
// learning about ceilings. Empty effects/malformed judge to nothing.
|
|
849
|
+
return {
|
|
850
|
+
result: refusal,
|
|
851
|
+
executed: true,
|
|
852
|
+
ceilingRefused: true,
|
|
853
|
+
envelope: { content: refusal, effects: [], status: 'invalid', malformed: [] },
|
|
854
|
+
};
|
|
855
|
+
}
|
|
794
856
|
// A status-only shape missing its `effects: []` marker is DATA (bytes
|
|
795
857
|
// unchanged) — but never silently: name the dropped marker in dev mode.
|
|
796
858
|
const nearMiss = explainStatusOnlyNearMiss(result);
|
|
@@ -932,6 +994,10 @@ export function buildToolCallsHandler(deps) {
|
|
|
932
994
|
let denied = false;
|
|
933
995
|
/** True once `tool.execute` has been entered — see `afterMoment`. */
|
|
934
996
|
let executed = false;
|
|
997
|
+
/** The tool's own `resultCeiling` refused this call's result (9.20.0)
|
|
998
|
+
* — the call ran, but it must not complete a procedure step: the
|
|
999
|
+
* refusal's own instruction is to call again. */
|
|
1000
|
+
let ceilingRefused = false;
|
|
935
1001
|
let haltContext;
|
|
936
1002
|
if (permissionChecker) {
|
|
937
1003
|
try {
|
|
@@ -1395,6 +1461,21 @@ export function buildToolCallsHandler(deps) {
|
|
|
1395
1461
|
if (nearMiss !== undefined)
|
|
1396
1462
|
warnEffect(tc.name, nearMiss);
|
|
1397
1463
|
}
|
|
1464
|
+
// The tool's own refusing ceiling (9.20.0) — at the execute
|
|
1465
|
+
// boundary, BEFORE the gates and the after-tool chain, so
|
|
1466
|
+
// governance and the agent-level cap compose over what the
|
|
1467
|
+
// model will actually read (the read_skill-gate precedent).
|
|
1468
|
+
// A recognized envelope's DECLARED effects (`toolEnvelope`)
|
|
1469
|
+
// are still judged below — the effects channel is not the
|
|
1470
|
+
// channel that overflowed; the delivered status becomes
|
|
1471
|
+
// 'invalid' so `onToolStatus` edges can route the overflow
|
|
1472
|
+
// (the declared one rides the `result_refused` event).
|
|
1473
|
+
const overflowRefusal = refuseOverCeiling(scope, { toolName: tc.name, toolCallId: tc.id, iteration }, tool, result, toolStatus);
|
|
1474
|
+
if (overflowRefusal !== undefined) {
|
|
1475
|
+
ceilingRefused = true;
|
|
1476
|
+
result = overflowRefusal;
|
|
1477
|
+
toolStatus = 'invalid';
|
|
1478
|
+
}
|
|
1398
1479
|
await endCall(tc.id);
|
|
1399
1480
|
}
|
|
1400
1481
|
catch (err) {
|
|
@@ -1552,8 +1633,10 @@ export function buildToolCallsHandler(deps) {
|
|
|
1552
1633
|
// The advance: only a call that RAN clean completes a step — an
|
|
1553
1634
|
// error result never advances (the pointer holds; the model
|
|
1554
1635
|
// retries, skips with a reason, or routes around), and neither
|
|
1555
|
-
// does a permission denial or a
|
|
1556
|
-
|
|
1636
|
+
// does a permission denial, a gate refusal, or a ceiling refusal
|
|
1637
|
+
// (the model holds no data and was told to call again — advancing
|
|
1638
|
+
// would contradict the refusal's own instruction).
|
|
1639
|
+
if (executed && !error && !denied && !skillRejected && !ceilingRefused) {
|
|
1557
1640
|
resultStr += applyStepReturn(scope, {
|
|
1558
1641
|
toolName: tc.name,
|
|
1559
1642
|
toolCallId: tc.id,
|
|
@@ -1828,9 +1911,14 @@ export function buildToolCallsHandler(deps) {
|
|
|
1828
1911
|
const dispatched = await resolveCredentialAndExecute(scope, tool, toolName, rest.args, toolCallId, iteration, env);
|
|
1829
1912
|
result = dispatched.result;
|
|
1830
1913
|
error = dispatched.error;
|
|
1831
|
-
|
|
1832
|
-
|
|
1914
|
+
// A ceiling-refused call RAN but must not advance a step (9.20.0);
|
|
1915
|
+
// its envelope — status 'invalid' plus any surviving declared
|
|
1916
|
+
// effects — is still picked up and judged below.
|
|
1917
|
+
stepToolRan =
|
|
1918
|
+
dispatched.executed === true && error !== true && dispatched.ceilingRefused !== true;
|
|
1919
|
+
if (dispatched.executed === true && error !== true) {
|
|
1833
1920
|
resumeEnvelope = dispatched.envelope;
|
|
1921
|
+
}
|
|
1834
1922
|
// skip_step behind a middleware ask, approved (9.18.0): the
|
|
1835
1923
|
// placeholder just landed — replace it with the authoritative
|
|
1836
1924
|
// sentence BEFORE the chain's last word, the execute loop's
|
|
@@ -1954,9 +2042,12 @@ export function buildToolCallsHandler(deps) {
|
|
|
1954
2042
|
const dispatched = await resolveCredentialAndExecute(scope, tool, toolName, args, toolCallId, iteration, env);
|
|
1955
2043
|
result = dispatched.result;
|
|
1956
2044
|
error = dispatched.error;
|
|
1957
|
-
|
|
1958
|
-
|
|
2045
|
+
// Same ceiling law as the ask path: ran ≠ completed a step (9.20.0).
|
|
2046
|
+
stepToolRan =
|
|
2047
|
+
dispatched.executed === true && error !== true && dispatched.ceilingRefused !== true;
|
|
2048
|
+
if (dispatched.executed === true && error !== true) {
|
|
1959
2049
|
resumeEnvelope = dispatched.envelope;
|
|
2050
|
+
}
|
|
1960
2051
|
// Consent moved the execution to this side of the pause; the rules
|
|
1961
2052
|
// about results move with it.
|
|
1962
2053
|
if (dispatched.executed === true) {
|
|
@@ -2075,8 +2166,13 @@ export function buildToolCallsHandler(deps) {
|
|
|
2075
2166
|
modelResult = consentCapped.modelResult;
|
|
2076
2167
|
let consentResultStr = typeof modelResult === 'string' ? modelResult : safeStringify(modelResult);
|
|
2077
2168
|
// Step boundary (9.18.0) — a step tool that waited on 3LO consent
|
|
2078
|
-
// advances the moment its resumed dispatch really ran, clean.
|
|
2079
|
-
|
|
2169
|
+
// advances the moment its resumed dispatch really ran, clean. A
|
|
2170
|
+
// ceiling-refused result is not clean (9.20.0): the model holds no
|
|
2171
|
+
// data and was told to call again.
|
|
2172
|
+
if (deps.stepPlanFor &&
|
|
2173
|
+
dispatched.executed === true &&
|
|
2174
|
+
error !== true &&
|
|
2175
|
+
dispatched.ceilingRefused !== true) {
|
|
2080
2176
|
consentResultStr += applyStepReturn(scope, { toolName, toolCallId, iteration });
|
|
2081
2177
|
}
|
|
2082
2178
|
// Typed tool effects (9.19.0) — same judge as every other path.
|