@gajae-code/agent-core 0.14.0 → 0.14.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 +8 -0
- package/dist/types/agent-loop.d.ts +1 -1
- package/dist/types/types.d.ts +22 -0
- package/package.json +4 -4
- package/src/agent-loop.ts +92 -21
- package/src/compaction/pruning.ts +29 -9
- package/src/types.ts +34 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,10 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.14.1] - 2026-08-18
|
|
6
|
+
- Compaction pruning no longer kills the turn when a persisted `toolCall.arguments` is `null`. Sessions written by an earlier cold-spill eviction path store `null` where the spill sentinel belongs, and the staleness index dereferenced that payload unguarded, so reloading such a session threw `null is not an object (evaluating 'args.path')` as a turn-fatal error instead of skipping the one unusable call. `ToolCall.arguments` is typed non-nullable, so no type check flagged the gap. Every read of a persisted argument bag — path extraction, `apply_patch` header parsing, idempotent-bash keys, and search target keys — now treats a non-object payload as absent. The original arguments are not lost: the eviction marker still names the blob and rehydration restores them.
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- `toolFailureEnvelope` / `isToolFailureEnvelope` / `ToolFailureEnvelope` name the result details the loop attaches when a tool call fails without the tool returning details of its own. The guard matches only that envelope, so a consumer can tell it apart from a tool that reports a `failureKind` alongside its own details before dereferencing a tool-owned detail shape.
|
|
11
|
+
|
|
5
12
|
## [0.14.0] - 2026-08-17
|
|
6
13
|
|
|
7
14
|
### Fixed
|
|
8
15
|
|
|
16
|
+
- Managed fallback no longer kills a long turn with `Managed fallback attempt exceeded the provisional event buffer limit`. Every staged streaming frame carries the whole accumulated partial (once as `message`, once as `assistantMessageEvent.partial`), so staged bytes grew quadratically with the response length and a reasoning-heavy turn of a few thousand tokens crossed the 16 MiB cap even though no single event came close to it. Reaching the cap now first reclaims the staged `*_delta` increments, whose complete value is re-published by the retained `*_end` and terminal `message_end`/`done` frames, and only a batch that still cannot fit fails. Attempt atomicity is unchanged: nothing is published early, so a discarded attempt stays unobservable, and a single oversized event keeps its pre-clone rejection with no provider-fallback authority.
|
|
9
17
|
- Non-managed lossless response staging now commits its buffered lifecycle and switches to ordinary pass-through publication when the provisional event cap is reached, instead of turning a large reasoning-only response into a fatal `local_snapshot_failure`. Managed fallback attempts keep the strict bounded-buffer rejection required for atomic retry and provider-fallback isolation.
|
|
10
18
|
- Managed snapshot machinery no longer fails runs on benign payload-class or readable-proxy roots: an assistant message or stream event whose fields live on prototype getters (which `structuredClone` drops — it copies only own enumerable properties) or behind a proxy whose gets are readable is repaired through the existing guarded-read path instead of throwing a deterministic `shell.role`/`event.unknownType`/`event.snapshot` local snapshot failure. The run-loop message_update replay also builds its event through the managed event snapshot instead of a naive `{ ...event }` spread, which silently dropped prototype-carried fields before the snapshot boundary could see them. Hostile shapes (throwing get traps, sentinel-marked degraded content, malformed non-string event types) keep their named fail-fast diagnostics with no retry authority.
|
|
11
19
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { type AssistantMessage, type AssistantMessageEvent, type Context, EventStream } from "@gajae-code/ai";
|
|
6
6
|
import type { AttemptScope } from "./attempt-scope";
|
|
7
7
|
import { type AgentRunCoverage, type AgentRunSummary } from "./run-collector";
|
|
8
|
-
import type
|
|
8
|
+
import { type AgentContext, type AgentEvent, type AgentLoopConfig, type AgentMessage, type StreamFn } from "./types";
|
|
9
9
|
/** Sentinel returned by the abort race in `streamAssistantResponse`. */
|
|
10
10
|
/**
|
|
11
11
|
* Defensive caps for a provisional managed attempt. These are intentionally
|
package/dist/types/types.d.ts
CHANGED
|
@@ -693,3 +693,25 @@ export type AgentEvent = {
|
|
|
693
693
|
isError?: boolean;
|
|
694
694
|
scope?: AttemptScope;
|
|
695
695
|
};
|
|
696
|
+
/**
|
|
697
|
+
* Why a tool call failed when the loop — not the tool — produced the result.
|
|
698
|
+
*
|
|
699
|
+
* `argument_validation` means the call never dispatched: the arguments were
|
|
700
|
+
* rejected before `execute` ran. `execution` means `execute` threw.
|
|
701
|
+
*/
|
|
702
|
+
export type ToolFailureKind = "argument_validation" | "execution";
|
|
703
|
+
/**
|
|
704
|
+
* The result details the loop attaches when a tool call fails without the tool
|
|
705
|
+
* returning its own details. It carries no tool-owned field, so a consumer that
|
|
706
|
+
* dereferences a tool's own detail shape must recognise it first.
|
|
707
|
+
*/
|
|
708
|
+
export interface ToolFailureEnvelope {
|
|
709
|
+
failureKind: ToolFailureKind;
|
|
710
|
+
}
|
|
711
|
+
export declare function toolFailureEnvelope(kind: ToolFailureKind): ToolFailureEnvelope;
|
|
712
|
+
/**
|
|
713
|
+
* True only for the loop's own envelope. Tools that report a `failureKind`
|
|
714
|
+
* alongside their own details (`todo_write`, todo persistence) keep those fields,
|
|
715
|
+
* so their renderers still own the result and are left alone.
|
|
716
|
+
*/
|
|
717
|
+
export declare function isToolFailureEnvelope(value: unknown): value is ToolFailureEnvelope;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@gajae-code/agent-core",
|
|
4
|
-
"version": "0.14.
|
|
4
|
+
"version": "0.14.1",
|
|
5
5
|
"description": "General-purpose agent with transport abstraction, state management, and attachment support",
|
|
6
6
|
"homepage": "https://gajae-code.com",
|
|
7
7
|
"author": "Yeachan-Heo and Gajae Code Contributors",
|
|
@@ -32,9 +32,9 @@
|
|
|
32
32
|
"fmt": "biome format --write ."
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@gajae-code/ai": "0.14.
|
|
36
|
-
"@gajae-code/natives": "0.14.
|
|
37
|
-
"@gajae-code/utils": "0.14.
|
|
35
|
+
"@gajae-code/ai": "0.14.1",
|
|
36
|
+
"@gajae-code/natives": "0.14.1",
|
|
37
|
+
"@gajae-code/utils": "0.14.1",
|
|
38
38
|
"@opentelemetry/api": "^1.9.0"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
package/src/agent-loop.ts
CHANGED
|
@@ -68,17 +68,18 @@ import {
|
|
|
68
68
|
bindDispatchedToolIdentity,
|
|
69
69
|
markNonDispatchedToolEvent,
|
|
70
70
|
} from "./tool-dispatch-identity";
|
|
71
|
-
import
|
|
72
|
-
AgentContext,
|
|
73
|
-
AgentEvent,
|
|
74
|
-
AgentLoopConfig,
|
|
75
|
-
AgentMessage,
|
|
76
|
-
AgentTool,
|
|
77
|
-
AgentToolContext,
|
|
78
|
-
AgentToolResult,
|
|
79
|
-
ManagedAttemptOutcome,
|
|
80
|
-
StandaloneRunOwnership,
|
|
81
|
-
StreamFn,
|
|
71
|
+
import {
|
|
72
|
+
type AgentContext,
|
|
73
|
+
type AgentEvent,
|
|
74
|
+
type AgentLoopConfig,
|
|
75
|
+
type AgentMessage,
|
|
76
|
+
type AgentTool,
|
|
77
|
+
type AgentToolContext,
|
|
78
|
+
type AgentToolResult,
|
|
79
|
+
type ManagedAttemptOutcome,
|
|
80
|
+
type StandaloneRunOwnership,
|
|
81
|
+
type StreamFn,
|
|
82
|
+
toolFailureEnvelope,
|
|
82
83
|
} from "./types";
|
|
83
84
|
|
|
84
85
|
// Capture the intrinsic before any tool/hook can replace `Reflect.apply`. Calling this
|
|
@@ -1204,9 +1205,28 @@ function warnManagedSnapshotFailure(
|
|
|
1204
1205
|
* commits the transaction.
|
|
1205
1206
|
*/
|
|
1206
1207
|
type ManagedAttemptBatchItem =
|
|
1207
|
-
| { type: "event"; event: AgentEvent }
|
|
1208
|
+
| { type: "event"; event: AgentEvent; bytes?: number }
|
|
1208
1209
|
| { type: "assistant_event"; message: AssistantMessage; event: AssistantMessageEvent };
|
|
1209
1210
|
|
|
1211
|
+
/**
|
|
1212
|
+
* Streaming increments whose complete value is re-published by the block's own
|
|
1213
|
+
* `*_end` frame and by the terminal `message_end` / `done` frames. Those
|
|
1214
|
+
* terminal frames are never reclaimed, so dropping the increments loses no
|
|
1215
|
+
* content — only the intermediate frames that carried it on the way there.
|
|
1216
|
+
*/
|
|
1217
|
+
const MANAGED_SUPERSEDED_DELTA_EVENT_TYPES: ReadonlySet<string> = new Set([
|
|
1218
|
+
"text_delta",
|
|
1219
|
+
"thinking_delta",
|
|
1220
|
+
"reasoning_summary_delta",
|
|
1221
|
+
"toolcall_delta",
|
|
1222
|
+
]);
|
|
1223
|
+
|
|
1224
|
+
function isSupersededStreamingDelta(item: ManagedAttemptBatchItem): boolean {
|
|
1225
|
+
if (item.type === "assistant_event") return MANAGED_SUPERSEDED_DELTA_EVENT_TYPES.has(item.event.type);
|
|
1226
|
+
if (item.event.type !== "message_update") return false;
|
|
1227
|
+
return MANAGED_SUPERSEDED_DELTA_EVENT_TYPES.has(item.event.assistantMessageEvent.type);
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1210
1230
|
class ManagedAttemptTransaction {
|
|
1211
1231
|
#batch: ManagedAttemptBatchItem[] = [];
|
|
1212
1232
|
#stagedEventCount = 0;
|
|
@@ -1369,6 +1389,48 @@ class ManagedAttemptTransaction {
|
|
|
1369
1389
|
);
|
|
1370
1390
|
}
|
|
1371
1391
|
|
|
1392
|
+
/**
|
|
1393
|
+
* Reclaim staged frames that later staged frames already supersede.
|
|
1394
|
+
*
|
|
1395
|
+
* Every staged streaming frame carries the WHOLE accumulated partial (once as
|
|
1396
|
+
* `message`, once as `assistantMessageEvent.partial`), so a turn that streams
|
|
1397
|
+
* N increments stages ~N * length bytes: quadratic in the response length. A
|
|
1398
|
+
* reasoning-heavy turn of a few thousand tokens therefore used to exhaust the
|
|
1399
|
+
* provisional cap and kill the whole run, even though the attempt itself was
|
|
1400
|
+
* healthy and the cap exists only to bound memory.
|
|
1401
|
+
*
|
|
1402
|
+
* Each `*_delta` increment is re-published in full by its block's `*_end`
|
|
1403
|
+
* frame and by the terminal `message_end` / `done` frames, and those are
|
|
1404
|
+
* retained, so dropping the increments reclaims the growth without inventing
|
|
1405
|
+
* or losing content. Nothing is published here: the batch stays
|
|
1406
|
+
* all-or-nothing, so a discarded attempt remains unobservable and the
|
|
1407
|
+
* fallback chain is still untouched.
|
|
1408
|
+
*
|
|
1409
|
+
* Returns whether anything was reclaimed, so the caller can re-test the cap
|
|
1410
|
+
* and keep failing fast on a single payload that cannot fit on its own.
|
|
1411
|
+
*/
|
|
1412
|
+
#compactSupersededFrames(): boolean {
|
|
1413
|
+
if (this.#batch.length === 0) return false;
|
|
1414
|
+
const retained: ManagedAttemptBatchItem[] = [];
|
|
1415
|
+
let reclaimedBytes = 0;
|
|
1416
|
+
let reclaimedEvents = 0;
|
|
1417
|
+
for (const item of this.#batch) {
|
|
1418
|
+
if (!isSupersededStreamingDelta(item)) {
|
|
1419
|
+
retained.push(item);
|
|
1420
|
+
continue;
|
|
1421
|
+
}
|
|
1422
|
+
if (item.type === "event") {
|
|
1423
|
+
reclaimedBytes += item.bytes ?? 0;
|
|
1424
|
+
reclaimedEvents += 1;
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
if (retained.length === this.#batch.length) return false;
|
|
1428
|
+
this.#batch = retained;
|
|
1429
|
+
this.#stagedBytes -= reclaimedBytes;
|
|
1430
|
+
this.#stagedEventCount -= reclaimedEvents;
|
|
1431
|
+
return true;
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1372
1434
|
#stage(event: AgentEvent): void {
|
|
1373
1435
|
if (this.snapshotMode === "lossless") {
|
|
1374
1436
|
const snapshot = this.#repairAssistantEvent(event);
|
|
@@ -1402,7 +1464,7 @@ class ManagedAttemptTransaction {
|
|
|
1402
1464
|
this.push(detached);
|
|
1403
1465
|
return;
|
|
1404
1466
|
}
|
|
1405
|
-
this.#batch.push({ type: "event", event: detached });
|
|
1467
|
+
this.#batch.push({ type: "event", event: detached, bytes: detachedBytes });
|
|
1406
1468
|
this.#stagedEventCount++;
|
|
1407
1469
|
this.#stagedBytes += detachedBytes;
|
|
1408
1470
|
return;
|
|
@@ -1420,8 +1482,14 @@ class ManagedAttemptTransaction {
|
|
|
1420
1482
|
bytes = undefined;
|
|
1421
1483
|
}
|
|
1422
1484
|
if (bytes !== undefined && this.#wouldOverflow(bytes)) {
|
|
1423
|
-
|
|
1424
|
-
|
|
1485
|
+
// A long turn reaches the cap through accumulated streaming increments,
|
|
1486
|
+
// not through one oversized payload. Reclaim the superseded increments
|
|
1487
|
+
// first; only a batch that still cannot fit is a real local overflow.
|
|
1488
|
+
this.#compactSupersededFrames();
|
|
1489
|
+
if (this.#wouldOverflow(bytes)) {
|
|
1490
|
+
this.discard();
|
|
1491
|
+
throw new ManagedAttemptBufferOverflowError("overflow.preMeasure");
|
|
1492
|
+
}
|
|
1425
1493
|
}
|
|
1426
1494
|
const repaired = this.#repairAssistantEvent(event);
|
|
1427
1495
|
const detailed = managedAttemptSnapshotDetailed(repaired);
|
|
@@ -1441,10 +1509,15 @@ class ManagedAttemptTransaction {
|
|
|
1441
1509
|
throw new ManagedAttemptSnapshotError("staging.sanitize");
|
|
1442
1510
|
}
|
|
1443
1511
|
if (this.#wouldOverflow(bytes)) {
|
|
1444
|
-
this
|
|
1445
|
-
|
|
1512
|
+
this.#compactSupersededFrames();
|
|
1513
|
+
if (this.#wouldOverflow(bytes)) {
|
|
1514
|
+
this.discard();
|
|
1515
|
+
throw new ManagedAttemptBufferOverflowError("overflow.staged");
|
|
1516
|
+
}
|
|
1446
1517
|
}
|
|
1447
|
-
|
|
1518
|
+
// Retain each frame's accounted size so compaction can debit exactly what
|
|
1519
|
+
// it reclaims instead of re-measuring the whole batch.
|
|
1520
|
+
this.#batch.push({ type: "event", event: snapshot, bytes });
|
|
1448
1521
|
this.#stagedEventCount += 1;
|
|
1449
1522
|
|
|
1450
1523
|
this.#stagedBytes += bytes;
|
|
@@ -3518,9 +3591,7 @@ async function executeToolCalls(
|
|
|
3518
3591
|
caughtError = e;
|
|
3519
3592
|
result = {
|
|
3520
3593
|
content: [{ type: "text", text: e instanceof Error ? e.message : String(e) }],
|
|
3521
|
-
details:
|
|
3522
|
-
failureKind: record.argumentValidationFailed ? "argument_validation" : "execution",
|
|
3523
|
-
},
|
|
3594
|
+
details: toolFailureEnvelope(record.argumentValidationFailed ? "argument_validation" : "execution"),
|
|
3524
3595
|
};
|
|
3525
3596
|
isError = true;
|
|
3526
3597
|
}
|
|
@@ -233,9 +233,25 @@ interface PrunedToolArgumentsSentinel {
|
|
|
233
233
|
|
|
234
234
|
const EDIT_TOOL_NAMES = new Set(["edit", "write", "apply_patch", "ast_edit"]);
|
|
235
235
|
|
|
236
|
+
/**
|
|
237
|
+
* A tool call's arguments, or `undefined` when the persisted payload is not an
|
|
238
|
+
* object.
|
|
239
|
+
*
|
|
240
|
+
* `ToolCall.arguments` is typed non-nullable, but sessions written by an older
|
|
241
|
+
* cold-spill eviction path carry `arguments: null` where the spill sentinel
|
|
242
|
+
* should be. Reading `.path` off that null threw a TypeError that surfaced as
|
|
243
|
+
* `null is not an object (evaluating 'args.path')` and killed the turn, so
|
|
244
|
+
* every reader of persisted arguments must treat them as untrusted.
|
|
245
|
+
*/
|
|
246
|
+
function toolArguments(call: ToolCall): Record<string, unknown> | undefined {
|
|
247
|
+
const args = call.arguments;
|
|
248
|
+
return typeof args === "object" && args !== null ? args : undefined;
|
|
249
|
+
}
|
|
250
|
+
|
|
236
251
|
/** Extract the file-path argument from a tool call, when the tool has one. */
|
|
237
252
|
function toolCallPath(call: ToolCall): string | undefined {
|
|
238
|
-
const args = call
|
|
253
|
+
const args = toolArguments(call);
|
|
254
|
+
if (!args) return undefined;
|
|
239
255
|
const path = args.path ?? args.file_path ?? args.filePath;
|
|
240
256
|
return typeof path === "string" && path.length > 0 ? path : undefined;
|
|
241
257
|
}
|
|
@@ -260,7 +276,7 @@ const APPLY_PATCH_HEADER = /^\*\*\* (?:((?:Add|Update|Delete) File)|(Move to)):
|
|
|
260
276
|
function editToolPathGroups(call: ToolCall): string[][] {
|
|
261
277
|
const path = toolCallPath(call);
|
|
262
278
|
if (path !== undefined) return [[path]];
|
|
263
|
-
const input = call
|
|
279
|
+
const input = toolArguments(call)?.input;
|
|
264
280
|
if (typeof input !== "string") return [];
|
|
265
281
|
const groups: string[][] = [];
|
|
266
282
|
for (const match of input.matchAll(APPLY_PATCH_HEADER)) {
|
|
@@ -422,11 +438,13 @@ const IDEMPOTENT_BASH_COMMAND =
|
|
|
422
438
|
|
|
423
439
|
function normalizedIdempotentBashCommand(call: ToolCall): string | undefined {
|
|
424
440
|
if (call.name !== "bash") return undefined;
|
|
425
|
-
const
|
|
441
|
+
const args = toolArguments(call);
|
|
442
|
+
if (!args) return undefined;
|
|
443
|
+
const command = args.command;
|
|
426
444
|
if (typeof command !== "string") return undefined;
|
|
427
445
|
const normalized = command.trim().replace(/\s+/g, " ");
|
|
428
446
|
if (/[;&|]/.test(normalized) || !IDEMPOTENT_BASH_COMMAND.test(normalized)) return undefined;
|
|
429
|
-
return JSON.stringify([normalized, typeof
|
|
447
|
+
return JSON.stringify([normalized, typeof args.cwd === "string" ? args.cwd : undefined]);
|
|
430
448
|
}
|
|
431
449
|
|
|
432
450
|
function toolTargetKey(call: ToolCall): string | undefined {
|
|
@@ -434,13 +452,15 @@ function toolTargetKey(call: ToolCall): string | undefined {
|
|
|
434
452
|
if (path !== undefined) return JSON.stringify([call.name, "path", path]);
|
|
435
453
|
const command = normalizedIdempotentBashCommand(call);
|
|
436
454
|
if (command !== undefined) return JSON.stringify([call.name, "command", command]);
|
|
437
|
-
const
|
|
455
|
+
const args = toolArguments(call);
|
|
456
|
+
if (!args) return undefined;
|
|
457
|
+
const pattern = args.pattern;
|
|
438
458
|
if (typeof pattern === "string" && pattern.length > 0) {
|
|
439
|
-
const paths =
|
|
459
|
+
const paths = args.paths;
|
|
440
460
|
const pathList = Array.isArray(paths) ? paths.filter((p): p is string => typeof p === "string") : [];
|
|
441
|
-
const skip = typeof
|
|
442
|
-
const caseInsensitive =
|
|
443
|
-
const gitignore =
|
|
461
|
+
const skip = typeof args.skip === "number" ? args.skip : 0;
|
|
462
|
+
const caseInsensitive = args.i === true;
|
|
463
|
+
const gitignore = args.gitignore !== false;
|
|
444
464
|
return JSON.stringify([call.name, "pattern", pattern, pathList, skip, caseInsensitive, gitignore]);
|
|
445
465
|
}
|
|
446
466
|
return undefined;
|
package/src/types.ts
CHANGED
|
@@ -777,3 +777,37 @@ export type AgentEvent =
|
|
|
777
777
|
isError?: boolean;
|
|
778
778
|
scope?: AttemptScope;
|
|
779
779
|
};
|
|
780
|
+
|
|
781
|
+
/**
|
|
782
|
+
* Why a tool call failed when the loop — not the tool — produced the result.
|
|
783
|
+
*
|
|
784
|
+
* `argument_validation` means the call never dispatched: the arguments were
|
|
785
|
+
* rejected before `execute` ran. `execution` means `execute` threw.
|
|
786
|
+
*/
|
|
787
|
+
export type ToolFailureKind = "argument_validation" | "execution";
|
|
788
|
+
|
|
789
|
+
/**
|
|
790
|
+
* The result details the loop attaches when a tool call fails without the tool
|
|
791
|
+
* returning its own details. It carries no tool-owned field, so a consumer that
|
|
792
|
+
* dereferences a tool's own detail shape must recognise it first.
|
|
793
|
+
*/
|
|
794
|
+
export interface ToolFailureEnvelope {
|
|
795
|
+
failureKind: ToolFailureKind;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
export function toolFailureEnvelope(kind: ToolFailureKind): ToolFailureEnvelope {
|
|
799
|
+
return { failureKind: kind };
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
/**
|
|
803
|
+
* True only for the loop's own envelope. Tools that report a `failureKind`
|
|
804
|
+
* alongside their own details (`todo_write`, todo persistence) keep those fields,
|
|
805
|
+
* so their renderers still own the result and are left alone.
|
|
806
|
+
*/
|
|
807
|
+
export function isToolFailureEnvelope(value: unknown): value is ToolFailureEnvelope {
|
|
808
|
+
if (!value || typeof value !== "object") return false;
|
|
809
|
+
const keys = Object.keys(value);
|
|
810
|
+
if (keys.length !== 1 || keys[0] !== "failureKind") return false;
|
|
811
|
+
const kind = (value as ToolFailureEnvelope).failureKind;
|
|
812
|
+
return kind === "argument_validation" || kind === "execution";
|
|
813
|
+
}
|