@vinhnt-sdk/step-executor 0.1.3
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/LICENSE +21 -0
- package/README.md +29 -0
- package/dist/approval-handler.d.ts +18 -0
- package/dist/approval-handler.d.ts.map +1 -0
- package/dist/approval-handler.js +39 -0
- package/dist/approval-handler.js.map +1 -0
- package/dist/circuit-breaker.d.ts +48 -0
- package/dist/circuit-breaker.d.ts.map +1 -0
- package/dist/circuit-breaker.js +171 -0
- package/dist/circuit-breaker.js.map +1 -0
- package/dist/hooks.d.ts +15 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +2 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/kernel-error.d.ts +10 -0
- package/dist/kernel-error.d.ts.map +1 -0
- package/dist/kernel-error.js +12 -0
- package/dist/kernel-error.js.map +1 -0
- package/dist/kernel-utils.d.ts +49 -0
- package/dist/kernel-utils.d.ts.map +1 -0
- package/dist/kernel-utils.js +117 -0
- package/dist/kernel-utils.js.map +1 -0
- package/dist/path-policy.d.ts +18 -0
- package/dist/path-policy.d.ts.map +1 -0
- package/dist/path-policy.js +38 -0
- package/dist/path-policy.js.map +1 -0
- package/dist/permission-gate.d.ts +93 -0
- package/dist/permission-gate.d.ts.map +1 -0
- package/dist/permission-gate.js +303 -0
- package/dist/permission-gate.js.map +1 -0
- package/dist/run-context.d.ts +26 -0
- package/dist/run-context.d.ts.map +1 -0
- package/dist/run-context.js +13 -0
- package/dist/run-context.js.map +1 -0
- package/dist/run-state.d.ts +27 -0
- package/dist/run-state.d.ts.map +1 -0
- package/dist/run-state.js +88 -0
- package/dist/run-state.js.map +1 -0
- package/dist/self-correction.d.ts +29 -0
- package/dist/self-correction.d.ts.map +1 -0
- package/dist/self-correction.js +89 -0
- package/dist/self-correction.js.map +1 -0
- package/dist/step-executor.d.ts +81 -0
- package/dist/step-executor.d.ts.map +1 -0
- package/dist/step-executor.js +314 -0
- package/dist/step-executor.js.map +1 -0
- package/dist/termination.d.ts +87 -0
- package/dist/termination.d.ts.map +1 -0
- package/dist/termination.js +89 -0
- package/dist/termination.js.map +1 -0
- package/dist/tool-context-builder.d.ts +22 -0
- package/dist/tool-context-builder.d.ts.map +1 -0
- package/dist/tool-context-builder.js +42 -0
- package/dist/tool-context-builder.js.map +1 -0
- package/dist/tool-error-router.d.ts +26 -0
- package/dist/tool-error-router.d.ts.map +1 -0
- package/dist/tool-error-router.js +85 -0
- package/dist/tool-error-router.js.map +1 -0
- package/dist/tool-result-processor.d.ts +24 -0
- package/dist/tool-result-processor.d.ts.map +1 -0
- package/dist/tool-result-processor.js +47 -0
- package/dist/tool-result-processor.js.map +1 -0
- package/package.json +63 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nguyen Thanh Vinh
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# @vinhnt-sdk/step-executor
|
|
2
|
+
|
|
3
|
+
Step execution kernel for VNT Agent — tool lifecycle, per-tool timeouts, permission gating, doom-loop detection, self-correction and run state machine.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @vinhnt-sdk/step-executor
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { StepExecutor, PermissionGate, runLoop, RunStateMachine } from "@vinhnt-sdk/step-executor";
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Contents
|
|
18
|
+
|
|
19
|
+
- `StepExecutor` — per-step tool execution: invoke → permission re-check → run with timeout → result processing, with pluggable hooks/logging
|
|
20
|
+
- `PermissionGate` — permission evaluation against saved patterns, rules and risk defaults (consumes `@vinhnt-sdk/permission`)
|
|
21
|
+
- `tool-context-builder` / `approval-handler` / `tool-error-router` / `self-correction` / `tool-result-processor` — step lifecycle helpers
|
|
22
|
+
- `termination` — stop-condition evaluation and judge-based loop termination
|
|
23
|
+
- `kernel-utils` — doom-loop detection, arg hashing, timeouts, tool domains, defaults
|
|
24
|
+
- `run-state` / `run-context` — per-run state machine and context
|
|
25
|
+
- `circuit-breaker` / `kernel-error` — failure isolation and typed errors
|
|
26
|
+
|
|
27
|
+
## Hooks
|
|
28
|
+
|
|
29
|
+
The package exposes a minimal structural `StepExecutorPluginHooks` interface. Hosts (e.g. core's `PluginManager`) only need to implement `fireHook` for the tool-lifecycle / permission hook names.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { RunId, RequestContext } from "@vinhnt-sdk/schema";
|
|
2
|
+
import type { ChatMessage } from "@vinhnt-sdk/schema";
|
|
3
|
+
import type { ToolContext } from "@vinhnt-sdk/tools";
|
|
4
|
+
import type { StepExecutorPluginHooks } from "./hooks.js";
|
|
5
|
+
import type { PermissionGate, PermissionCheckResult } from "./permission-gate.js";
|
|
6
|
+
import type { ToolExecutionPlan } from "./step-executor.js";
|
|
7
|
+
import type { AgentConfig } from "@vinhnt-sdk/schema";
|
|
8
|
+
/** Dependencies required by {@link handleApproval}. */
|
|
9
|
+
export interface ApprovalHandlerDeps {
|
|
10
|
+
readonly store: {
|
|
11
|
+
emitEvent(event: Omit<import("@vinhnt-sdk/schema").KnownRunEvent, "sequence">, persist?: boolean): Promise<void>;
|
|
12
|
+
};
|
|
13
|
+
readonly permissionGate: PermissionGate;
|
|
14
|
+
readonly pluginManager: StepExecutorPluginHooks | undefined;
|
|
15
|
+
currentAgent: AgentConfig | undefined;
|
|
16
|
+
}
|
|
17
|
+
export declare function handleApproval(permResult: PermissionCheckResult, tc: ToolExecutionPlan, toolCtx: ToolContext, runId: RunId, ctx: RequestContext, _sessionId: string | undefined, messages: ChatMessage[], deps: ApprovalHandlerDeps, selfApproving?: boolean): Promise<boolean>;
|
|
18
|
+
//# sourceMappingURL=approval-handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval-handler.d.ts","sourceRoot":"","sources":["../src/approval-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAClF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAGtD,uDAAuD;AACvD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,KAAK,EAAE;QAAE,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,OAAO,oBAAoB,EAAE,aAAa,EAAE,UAAU,CAAC,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;KAAE,CAAC;IACrI,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC,QAAQ,CAAC,aAAa,EAAE,uBAAuB,GAAG,SAAS,CAAC;IAC5D,YAAY,EAAE,WAAW,GAAG,SAAS,CAAC;CACvC;AAED,wBAAsB,cAAc,CAClC,UAAU,EAAE,qBAAqB,EACjC,EAAE,EAAE,iBAAiB,EACrB,OAAO,EAAE,WAAW,EACpB,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,cAAc,EACnB,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,QAAQ,EAAE,WAAW,EAAE,EACvB,IAAI,EAAE,mBAAmB,EACzB,aAAa,CAAC,EAAE,OAAO,GACtB,OAAO,CAAC,OAAO,CAAC,CAsClB"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { toolDomain } from "./kernel-utils.js";
|
|
2
|
+
export async function handleApproval(permResult, tc, toolCtx, runId, ctx, _sessionId, messages, deps, selfApproving) {
|
|
3
|
+
// Allowed or needs-approval → proceed. A hard deny (!allowed, no approval
|
|
4
|
+
// needed) must surface as a rejection — a caller who skips this check would
|
|
5
|
+
// otherwise hand a denied tool straight through to execution.
|
|
6
|
+
if (permResult.allowed)
|
|
7
|
+
return true;
|
|
8
|
+
if (!permResult.needsApproval) {
|
|
9
|
+
messages.push({ role: "tool", toolCallId: tc.toolId, content: `Error: Tool "${tc.toolName}" denied by permission rules` });
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
// Single approval path: self-approving tools ask via their own `ctx.ask`
|
|
13
|
+
// (which persists savePatterns on "always"). Defer the gate-level ask.
|
|
14
|
+
if (selfApproving)
|
|
15
|
+
return true;
|
|
16
|
+
const savedOk = deps.permissionGate.checkSavedApproval(tc.toolName, tc.args, deps.currentAgent?.id);
|
|
17
|
+
if (savedOk)
|
|
18
|
+
return true;
|
|
19
|
+
const reply = await toolCtx.ask({
|
|
20
|
+
permission: `tool.${tc.toolName}`,
|
|
21
|
+
resource: tc.toolName,
|
|
22
|
+
reason: permResult.reason,
|
|
23
|
+
});
|
|
24
|
+
if (reply === "reject") {
|
|
25
|
+
deps.permissionGate.saveRejection(tc.toolName, tc.args, deps.currentAgent?.id);
|
|
26
|
+
await deps.store.emitEvent({
|
|
27
|
+
id: crypto.randomUUID(), runId, type: "tool.failed",
|
|
28
|
+
occurredAt: new Date().toISOString(), traceId: ctx.traceId,
|
|
29
|
+
data: { toolId: tc.toolId, toolName: tc.toolName, domain: toolDomain(tc.toolName), decision: "deny", error: `Tool "${tc.toolName}" rejected by user` },
|
|
30
|
+
});
|
|
31
|
+
messages.push({ role: "tool", toolCallId: tc.toolId, content: `Error: Tool "${tc.toolName}" rejected by user` });
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
if (reply === "always") {
|
|
35
|
+
deps.permissionGate.saveApproval(tc.toolName, tc.args, deps.currentAgent?.id);
|
|
36
|
+
}
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=approval-handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval-handler.js","sourceRoot":"","sources":["../src/approval-handler.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAU/C,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,UAAiC,EACjC,EAAqB,EACrB,OAAoB,EACpB,KAAY,EACZ,GAAmB,EACnB,UAA8B,EAC9B,QAAuB,EACvB,IAAyB,EACzB,aAAuB;IAEvB,0EAA0E;IAC1E,4EAA4E;IAC5E,8DAA8D;IAC9D,IAAI,UAAU,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IACpC,IAAI,CAAC,UAAU,CAAC,aAAa,EAAE,CAAC;QAC9B,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,gBAAgB,EAAE,CAAC,QAAQ,8BAA8B,EAAE,CAAC,CAAC;QAC3H,OAAO,KAAK,CAAC;IACf,CAAC;IAED,yEAAyE;IACzE,uEAAuE;IACvE,IAAI,aAAa;QAAE,OAAO,IAAI,CAAC;IAE/B,MAAM,OAAO,GAAG,IAAI,CAAC,cAAc,CAAC,kBAAkB,CAAC,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,IAA+B,EAAE,IAAI,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;IAC/H,IAAI,OAAO;QAAE,OAAO,IAAI,CAAC;IAEzB,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QAC9B,UAAU,EAAE,QAAQ,EAAE,CAAC,QAAQ,EAAE;QACjC,QAAQ,EAAE,EAAE,CAAC,QAAQ;QACrB,MAAM,EAAE,UAAU,CAAC,MAAO;KAC3B,CAAC,CAAC;IAEH,IAAI,KAAK,KAAK,QAAQ,EAAE,CAAC;QACvB,IAAI,CAAC,cAAc,CAAC,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,IAA+B,EAAE,IAAI,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;QAC1G,MAAM,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC;YACzB,EAAE,EAAE,MAAM,CAAC,UAAU,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,aAAa;YACnD,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO;YAC1D,IAAI,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,QAAQ,oBAAoB,EAAE;SACvJ,CAAC,CAAC;QACH,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,gBAAgB,EAAE,CAAC,QAAQ,oBAAoB,EAAE,CAAC,CAAC;QACjH,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,KAAK,KAAK,QAAQ,EAAE,CAAC;QACvB,IAAI,CAAC,cAAc,CAAC,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,IAA+B,EAAE,IAAI,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;IAC3G,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/** Current circuit breaker state. */
|
|
2
|
+
export type CircuitState = "closed" | "open" | "half_open";
|
|
3
|
+
/** Tuning for {@link CircuitBreaker}: failure/success thresholds and retry policy. */
|
|
4
|
+
export interface CircuitBreakerOptions {
|
|
5
|
+
failureThreshold?: number;
|
|
6
|
+
successThreshold?: number;
|
|
7
|
+
resetTimeoutMs?: number;
|
|
8
|
+
isFailure?: (err: unknown) => boolean;
|
|
9
|
+
/** Maximum number of retries for transient failures. Default: 3 */
|
|
10
|
+
maxRetries?: number;
|
|
11
|
+
/** Base delay for exponential backoff in ms. Default: 1000 */
|
|
12
|
+
backoffMs?: number;
|
|
13
|
+
/** Maximum delay for backoff in ms. Default: 30000 */
|
|
14
|
+
maxBackoffMs?: number;
|
|
15
|
+
}
|
|
16
|
+
/** Circuit breaker with failure thresholds, retry/backoff and half-open probing. */
|
|
17
|
+
export declare class CircuitBreaker {
|
|
18
|
+
private state;
|
|
19
|
+
private failureCount;
|
|
20
|
+
private lastFailureTime;
|
|
21
|
+
private halfOpenSuccesses;
|
|
22
|
+
private readonly options;
|
|
23
|
+
constructor(options?: CircuitBreakerOptions);
|
|
24
|
+
getState(): CircuitState;
|
|
25
|
+
/**
|
|
26
|
+
* Execute a function with circuit breaker and retry logic.
|
|
27
|
+
* Retries on transient failures with exponential backoff.
|
|
28
|
+
*
|
|
29
|
+
* @param fn - The async function to execute
|
|
30
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
31
|
+
* @returns The result of the function
|
|
32
|
+
* @throws CircuitBreakerOpenError if circuit is open
|
|
33
|
+
* @throws The original error if all retries fail
|
|
34
|
+
*/
|
|
35
|
+
call<T>(fn: () => Promise<T>, signal?: AbortSignal): Promise<T>;
|
|
36
|
+
private sleepAbortable;
|
|
37
|
+
private onSuccess;
|
|
38
|
+
private onFailure;
|
|
39
|
+
reset(): void;
|
|
40
|
+
/** Get the current configuration options. */
|
|
41
|
+
getOptions(): Readonly<Required<CircuitBreakerOptions>>;
|
|
42
|
+
}
|
|
43
|
+
/** Thrown when a call is rejected because the breaker is open. */
|
|
44
|
+
export declare class CircuitBreakerOpenError extends Error {
|
|
45
|
+
readonly remainingMs: number;
|
|
46
|
+
constructor(resetTimeoutMs: number);
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=circuit-breaker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"circuit-breaker.d.ts","sourceRoot":"","sources":["../src/circuit-breaker.ts"],"names":[],"mappings":"AAEA,qCAAqC;AACrC,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;AAE3D,sFAAsF;AACtF,MAAM,WAAW,qBAAqB;IACpC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC;IACtC,mEAAmE;IACnE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sDAAsD;IACtD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAyBD,oFAAoF;AACpF,qBAAa,cAAc;IACzB,OAAO,CAAC,KAAK,CAA0B;IACvC,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkC;gBAE9C,OAAO,CAAC,EAAE,qBAAqB;IAO3C,QAAQ,IAAI,YAAY;IAQxB;;;;;;;;;OASG;IACG,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC;YA0DvD,cAAc;IAsB5B,OAAO,CAAC,SAAS;IAajB,OAAO,CAAC,SAAS;IAcjB,KAAK,IAAI,IAAI;IAOb,6CAA6C;IAC7C,UAAU,IAAI,QAAQ,CAAC,QAAQ,CAAC,qBAAqB,CAAC,CAAC;CAGxD;AAED,kEAAkE;AAClE,qBAAa,uBAAwB,SAAQ,KAAK;IAChD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;gBAEjB,cAAc,EAAE,MAAM;CAKnC"}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { VntError } from "@vinhnt-sdk/schema";
|
|
2
|
+
const DEFAULT_OPTIONS = {
|
|
3
|
+
failureThreshold: 5,
|
|
4
|
+
successThreshold: 2,
|
|
5
|
+
resetTimeoutMs: 30_000,
|
|
6
|
+
maxRetries: 3,
|
|
7
|
+
backoffMs: 1000,
|
|
8
|
+
maxBackoffMs: 30_000,
|
|
9
|
+
isFailure: (err) => {
|
|
10
|
+
if (err instanceof DOMException && err.name === "AbortError")
|
|
11
|
+
return false;
|
|
12
|
+
if (err instanceof Error && err.name === "AbortError")
|
|
13
|
+
return false;
|
|
14
|
+
// Structured SDK errors carry an explicit retryable flag — upstream auth
|
|
15
|
+
// (401/403) and validation failures are marked non-retryable and must never
|
|
16
|
+
// count toward the breaker or be retried, even if their message text does
|
|
17
|
+
// not match the substring heuristics below.
|
|
18
|
+
if (err instanceof VntError && err.retryable === false)
|
|
19
|
+
return false;
|
|
20
|
+
const msg = err instanceof Error ? err.message.toLowerCase() : String(err).toLowerCase();
|
|
21
|
+
if (msg.includes("auth") || msg.includes("unauthorized") || msg.includes("invalid api key"))
|
|
22
|
+
return false;
|
|
23
|
+
if (msg.includes("context") && msg.includes("token"))
|
|
24
|
+
return false;
|
|
25
|
+
if (msg.includes("not found") || msg.includes("not_found"))
|
|
26
|
+
return false;
|
|
27
|
+
return true;
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
/** Circuit breaker with failure thresholds, retry/backoff and half-open probing. */
|
|
31
|
+
export class CircuitBreaker {
|
|
32
|
+
state = "closed";
|
|
33
|
+
failureCount = 0;
|
|
34
|
+
lastFailureTime = 0;
|
|
35
|
+
halfOpenSuccesses = 0;
|
|
36
|
+
options;
|
|
37
|
+
constructor(options) {
|
|
38
|
+
this.options = {
|
|
39
|
+
...DEFAULT_OPTIONS,
|
|
40
|
+
...Object.fromEntries(Object.entries(options ?? {}).filter(([, v]) => v !== undefined)),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
getState() {
|
|
44
|
+
if (this.state === "open" && Date.now() - this.lastFailureTime >= this.options.resetTimeoutMs) {
|
|
45
|
+
this.state = "half_open";
|
|
46
|
+
this.halfOpenSuccesses = 0;
|
|
47
|
+
}
|
|
48
|
+
return this.state;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Execute a function with circuit breaker and retry logic.
|
|
52
|
+
* Retries on transient failures with exponential backoff.
|
|
53
|
+
*
|
|
54
|
+
* @param fn - The async function to execute
|
|
55
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
56
|
+
* @returns The result of the function
|
|
57
|
+
* @throws CircuitBreakerOpenError if circuit is open
|
|
58
|
+
* @throws The original error if all retries fail
|
|
59
|
+
*/
|
|
60
|
+
async call(fn, signal) {
|
|
61
|
+
const state = this.getState();
|
|
62
|
+
if (state === "open") {
|
|
63
|
+
throw new CircuitBreakerOpenError(this.options.resetTimeoutMs);
|
|
64
|
+
}
|
|
65
|
+
let lastError;
|
|
66
|
+
let attempt = 0;
|
|
67
|
+
while (attempt <= this.options.maxRetries) {
|
|
68
|
+
if (signal?.aborted) {
|
|
69
|
+
throw new DOMException("Aborted", "AbortError");
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
const result = await fn();
|
|
73
|
+
this.onSuccess();
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
lastError = err;
|
|
78
|
+
// Cancellation is never a failure — rethrow without counting it so an
|
|
79
|
+
// aborted run cannot trip the breaker or get retried.
|
|
80
|
+
if (signal?.aborted || (err instanceof DOMException && err.name === "AbortError") || (err instanceof Error && err.name === "AbortError")) {
|
|
81
|
+
throw signal?.aborted ? new DOMException("Aborted", "AbortError") : err;
|
|
82
|
+
}
|
|
83
|
+
// Check if error is retryable
|
|
84
|
+
if (!this.options.isFailure(err)) {
|
|
85
|
+
throw err;
|
|
86
|
+
}
|
|
87
|
+
this.onFailure(err);
|
|
88
|
+
// Don't retry if we've hit the circuit breaker
|
|
89
|
+
if (this.getState() === "open") {
|
|
90
|
+
throw new CircuitBreakerOpenError(this.options.resetTimeoutMs);
|
|
91
|
+
}
|
|
92
|
+
// Don't retry on last attempt
|
|
93
|
+
if (attempt >= this.options.maxRetries) {
|
|
94
|
+
throw err;
|
|
95
|
+
}
|
|
96
|
+
// Calculate delay with exponential backoff
|
|
97
|
+
const delay = Math.min(this.options.backoffMs * Math.pow(2, attempt), this.options.maxBackoffMs);
|
|
98
|
+
await this.sleepAbortable(delay, signal);
|
|
99
|
+
attempt++;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
throw lastError;
|
|
103
|
+
}
|
|
104
|
+
async sleepAbortable(ms, signal) {
|
|
105
|
+
if (!signal) {
|
|
106
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
await new Promise((resolve, reject) => {
|
|
110
|
+
const timer = setTimeout(() => {
|
|
111
|
+
signal.removeEventListener("abort", onAbort);
|
|
112
|
+
resolve();
|
|
113
|
+
}, ms);
|
|
114
|
+
const onAbort = () => {
|
|
115
|
+
clearTimeout(timer);
|
|
116
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
117
|
+
};
|
|
118
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
119
|
+
if (signal.aborted) {
|
|
120
|
+
clearTimeout(timer);
|
|
121
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
onSuccess() {
|
|
126
|
+
if (this.state === "half_open") {
|
|
127
|
+
this.halfOpenSuccesses++;
|
|
128
|
+
if (this.halfOpenSuccesses >= this.options.successThreshold) {
|
|
129
|
+
this.state = "closed";
|
|
130
|
+
this.failureCount = 0;
|
|
131
|
+
this.halfOpenSuccesses = 0;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
else if (this.state === "closed") {
|
|
135
|
+
this.failureCount = 0;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
onFailure(err) {
|
|
139
|
+
if (!this.options.isFailure(err))
|
|
140
|
+
return;
|
|
141
|
+
this.failureCount++;
|
|
142
|
+
this.lastFailureTime = Date.now();
|
|
143
|
+
if (this.failureCount >= this.options.failureThreshold) {
|
|
144
|
+
this.state = "open";
|
|
145
|
+
}
|
|
146
|
+
else if (this.state === "half_open") {
|
|
147
|
+
this.state = "open";
|
|
148
|
+
this.halfOpenSuccesses = 0;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
reset() {
|
|
152
|
+
this.state = "closed";
|
|
153
|
+
this.failureCount = 0;
|
|
154
|
+
this.lastFailureTime = 0;
|
|
155
|
+
this.halfOpenSuccesses = 0;
|
|
156
|
+
}
|
|
157
|
+
/** Get the current configuration options. */
|
|
158
|
+
getOptions() {
|
|
159
|
+
return this.options;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/** Thrown when a call is rejected because the breaker is open. */
|
|
163
|
+
export class CircuitBreakerOpenError extends Error {
|
|
164
|
+
remainingMs;
|
|
165
|
+
constructor(resetTimeoutMs) {
|
|
166
|
+
super(`Circuit breaker is open — too many consecutive failures. Will retry in ${resetTimeoutMs}ms`);
|
|
167
|
+
this.name = "CircuitBreakerOpenError";
|
|
168
|
+
this.remainingMs = resetTimeoutMs;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=circuit-breaker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"circuit-breaker.js","sourceRoot":"","sources":["../src/circuit-breaker.ts"],"names":[],"mappings":"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"}
|
package/dist/hooks.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal structural plugin-hook surface used by the step executor.
|
|
3
|
+
*
|
|
4
|
+
* Hosts (e.g. core's `PluginManager`) only need to implement `fireHook` for
|
|
5
|
+
* the tool-lifecycle / permission hook names; no direct dependency on the
|
|
6
|
+
* full plugin contract.
|
|
7
|
+
*/
|
|
8
|
+
export interface StepExecutorPluginHooks {
|
|
9
|
+
fireHook(name: "onToolInvoked" | "onBeforeToolExecution" | "onAfterToolExecution" | "onToolCompleted" | "onToolFailed" | "onShellEnv" | "onPermissionAsk", data: Record<string, unknown>): Promise<{
|
|
10
|
+
modified: Record<string, unknown>;
|
|
11
|
+
} | null | void> | {
|
|
12
|
+
modified: Record<string, unknown>;
|
|
13
|
+
} | null | void;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=hooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CACN,IAAI,EACA,eAAe,GACf,uBAAuB,GACvB,sBAAsB,GACtB,iBAAiB,GACjB,cAAc,GACd,YAAY,GACZ,iBAAiB,EACrB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAE3B,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,GAAG,IAAI,GAAG,IAAI,CAAC,GAC5D;QAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,GACrC,IAAI,GACJ,IAAI,CAAC;CACV"}
|
package/dist/hooks.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.js","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":""}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @vinhnt-sdk/step-executor
|
|
3
|
+
* Step execution kernel: tool lifecycle, per-tool timeouts, permission gating,
|
|
4
|
+
* doom-loop detection, self-correction and run state machine.
|
|
5
|
+
*/
|
|
6
|
+
export type { StepExecutorPluginHooks } from "./hooks.js";
|
|
7
|
+
export { StepExecutor } from "./step-executor.js";
|
|
8
|
+
export type { StepExecutorDeps, ToolExecutionPlan } from "./step-executor.js";
|
|
9
|
+
export { DEFAULT_MAX_STEPS, DEFAULT_MAX_TOOL_CALLS_PER_STEP, DOOM_LOOP_THRESHOLD, toolDomain, } from "./kernel-utils.js";
|
|
10
|
+
export { evaluateStopConditions, buildJudgeMessages, parseJudgeVerdict, toToolCallOutcome } from "./termination.js";
|
|
11
|
+
export type { ToolCallOutcome, StepVerificationContext, StopCondition, TerminationPolicy } from "./termination.js";
|
|
12
|
+
export { PermissionGate } from "./permission-gate.js";
|
|
13
|
+
export type { ApprovalDecision, DynamicRule, PermissionCheckResult } from "./permission-gate.js";
|
|
14
|
+
export { createRunContext } from "./run-context.js";
|
|
15
|
+
export type { RunContext } from "./run-context.js";
|
|
16
|
+
export { KernelError } from "./kernel-error.js";
|
|
17
|
+
export type { KernelErrorCode } from "./kernel-error.js";
|
|
18
|
+
export { CircuitBreaker, CircuitBreakerOpenError } from "./circuit-breaker.js";
|
|
19
|
+
export type { CircuitState, CircuitBreakerOptions } from "./circuit-breaker.js";
|
|
20
|
+
export { RunStateMachine } from "./run-state.js";
|
|
21
|
+
export type { RunState } from "./run-state.js";
|
|
22
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,YAAY,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAI1D,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAI9E,OAAO,EACL,iBAAiB,EACjB,+BAA+B,EAC/B,mBAAmB,EACnB,UAAU,GACX,MAAM,mBAAmB,CAAC;AAI3B,OAAO,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACpH,YAAY,EAAE,eAAe,EAAE,uBAAuB,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAInH,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,YAAY,EAAE,gBAAgB,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAIjG,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,YAAY,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAInD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,YAAY,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAIzD,OAAO,EAAE,cAAc,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAC/E,YAAY,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAIhF,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,YAAY,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @vinhnt-sdk/step-executor
|
|
3
|
+
* Step execution kernel: tool lifecycle, per-tool timeouts, permission gating,
|
|
4
|
+
* doom-loop detection, self-correction and run state machine.
|
|
5
|
+
*/
|
|
6
|
+
// === Step Executor ===
|
|
7
|
+
export { StepExecutor } from "./step-executor.js";
|
|
8
|
+
// === Kernel Utils (public subset) ===
|
|
9
|
+
export { DEFAULT_MAX_STEPS, DEFAULT_MAX_TOOL_CALLS_PER_STEP, DOOM_LOOP_THRESHOLD, toolDomain, } from "./kernel-utils.js";
|
|
10
|
+
// === Termination ===
|
|
11
|
+
export { evaluateStopConditions, buildJudgeMessages, parseJudgeVerdict, toToolCallOutcome } from "./termination.js";
|
|
12
|
+
// === Permission Gate ===
|
|
13
|
+
export { PermissionGate } from "./permission-gate.js";
|
|
14
|
+
// === Run Context ===
|
|
15
|
+
export { createRunContext } from "./run-context.js";
|
|
16
|
+
// === Kernel Error ===
|
|
17
|
+
export { KernelError } from "./kernel-error.js";
|
|
18
|
+
// === Circuit Breaker ===
|
|
19
|
+
export { CircuitBreaker, CircuitBreakerOpenError } from "./circuit-breaker.js";
|
|
20
|
+
// === Run State ===
|
|
21
|
+
export { RunStateMachine } from "./run-state.js";
|
|
22
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,wBAAwB;AAExB,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGlD,uCAAuC;AAEvC,OAAO,EACL,iBAAiB,EACjB,+BAA+B,EAC/B,mBAAmB,EACnB,UAAU,GACX,MAAM,mBAAmB,CAAC;AAE3B,sBAAsB;AAEtB,OAAO,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAGpH,0BAA0B;AAE1B,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAGtD,sBAAsB;AAEtB,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAGpD,uBAAuB;AAEvB,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGhD,0BAA0B;AAE1B,OAAO,EAAE,cAAc,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAG/E,oBAAoB;AAEpB,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { VntError } from "@vinhnt-sdk/schema";
|
|
2
|
+
/** Kernel-level failure codes for {@link KernelError}. */
|
|
3
|
+
export type KernelErrorCode = "session_busy" | "cancelled" | "max_steps_exceeded" | "max_tokens_exceeded" | "tool_failed" | "model_failed" | "session_store_failed" | "internal_error" | "timeout" | "model_unavailable";
|
|
4
|
+
/** Kernel-level failure with a typed {@link KernelErrorCode}. */
|
|
5
|
+
export declare class KernelError extends VntError {
|
|
6
|
+
readonly code: string;
|
|
7
|
+
readonly retryable = false;
|
|
8
|
+
constructor(kernelCode: KernelErrorCode, message: string, cause?: unknown);
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=kernel-error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kernel-error.d.ts","sourceRoot":"","sources":["../src/kernel-error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE9C,0DAA0D;AAC1D,MAAM,MAAM,eAAe,GACvB,cAAc,GACd,WAAW,GACX,oBAAoB,GACpB,qBAAqB,GACrB,aAAa,GACb,cAAc,GACd,sBAAsB,GACtB,gBAAgB,GAChB,SAAS,GACT,mBAAmB,CAAC;AAExB,iEAAiE;AACjE,qBAAa,WAAY,SAAQ,QAAQ;IACvC,SAAyB,IAAI,EAAE,MAAM,CAAC;IACtC,SAAyB,SAAS,SAAS;gBAE/B,UAAU,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAK1E"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { VntError } from "@vinhnt-sdk/schema";
|
|
2
|
+
/** Kernel-level failure with a typed {@link KernelErrorCode}. */
|
|
3
|
+
export class KernelError extends VntError {
|
|
4
|
+
code;
|
|
5
|
+
retryable = false;
|
|
6
|
+
constructor(kernelCode, message, cause) {
|
|
7
|
+
super(message, { cause });
|
|
8
|
+
this.code = kernelCode;
|
|
9
|
+
this.name = `KernelError.${kernelCode}`;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=kernel-error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kernel-error.js","sourceRoot":"","sources":["../src/kernel-error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAe9C,iEAAiE;AACjE,MAAM,OAAO,WAAY,SAAQ,QAAQ;IACd,IAAI,CAAS;IACb,SAAS,GAAG,KAAK,CAAC;IAE3C,YAAY,UAA2B,EAAE,OAAe,EAAE,KAAe;QACvE,KAAK,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QAC1B,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;QACvB,IAAI,CAAC,IAAI,GAAG,eAAe,UAAU,EAAE,CAAC;IAC1C,CAAC;CACF"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { RunId } from "@vinhnt-sdk/schema";
|
|
2
|
+
/** Race a promise against an abort signal — rejects with RunAbortedError if aborted. @internal */
|
|
3
|
+
export declare function raceWithAbort<T>(promise: Promise<T>, signal: AbortSignal, runId: RunId): Promise<T>;
|
|
4
|
+
/**
|
|
5
|
+
* Run a tool execution with a cooperative timeout (RV-19). The caller's
|
|
6
|
+
* `run` receives a local `AbortSignal` that fires when EITHER the parent
|
|
7
|
+
* (run) signal aborts OR the tool exceeds `timeoutMs`. Well-behaved tools
|
|
8
|
+
* that watch their context signal stop their side effects at the deadline
|
|
9
|
+
* instead of continuing past it (a plain `Promise.race` abandons the tool
|
|
10
|
+
* promise and its effects keep running). The returned promise still
|
|
11
|
+
* hard-rejects at `timeoutMs`, so a non-cooperative tool can never hang the
|
|
12
|
+
* caller longer than the deadline. Parent aborts are NOT synthesised here —
|
|
13
|
+
* the caller's `raceWithAbort` rejects with `RunAbortedError`.
|
|
14
|
+
* @internal
|
|
15
|
+
*/
|
|
16
|
+
export declare function withToolTimeout<T>(run: (signal: AbortSignal) => Promise<T>, timeoutMs: number, parent: AbortSignal, timeoutMessage: string): Promise<T>;
|
|
17
|
+
/** Default cap on steps per run. */
|
|
18
|
+
export declare const DEFAULT_MAX_STEPS = 25;
|
|
19
|
+
/** Default cap on tool calls per step. */
|
|
20
|
+
export declare const DEFAULT_MAX_TOOL_CALLS_PER_STEP = 20;
|
|
21
|
+
/** Consecutive identical calls that trigger doom-loop detection. */
|
|
22
|
+
export declare const DOOM_LOOP_THRESHOLD = 3;
|
|
23
|
+
/** System prompt guiding the model to correct a failed tool call. @internal */
|
|
24
|
+
export declare const SELF_CORRECT_PROMPT = "A tool call just failed. Analyze the error and try a corrected approach.\n\nGuidelines:\n- If the error is about arguments, fix the arguments and retry.\n- If the tool is unavailable, suggest an alternative tool.\n- If the problem is permanent (e.g., invalid request), explain to the user.\n- Do NOT repeat the same failing call.";
|
|
25
|
+
/** A recorded tool invocation used for doom-loop detection. @internal */
|
|
26
|
+
export interface RecentCall {
|
|
27
|
+
id: string;
|
|
28
|
+
args: unknown;
|
|
29
|
+
/** Canonical hash of `args` — precomputed once for O(1) doom-loop comparison */
|
|
30
|
+
argsKey?: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Stable, order-independent hash of an arbitrary tool-arguments value.
|
|
34
|
+
* Object key order is normalized so `{a:1,b:2}` and `{b:2,a:1}` hash the same,
|
|
35
|
+
* and the hash is computed once per call (vs. repeated JSON.stringify in a
|
|
36
|
+
* doom-loop scan). Collisions are possible but astronomically unlikely for the
|
|
37
|
+
* small arg payloads agents produce.
|
|
38
|
+
* @internal
|
|
39
|
+
*/
|
|
40
|
+
export declare function hashArgs(value: unknown): string;
|
|
41
|
+
/** Detect whether the same tool+args repeated the last `threshold` calls. @internal */
|
|
42
|
+
export declare function detectDoomLoop(recentCalls: RecentCall[], name: string, args: unknown, threshold?: number): boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Derive the owning domain from a namespaced tool id:
|
|
45
|
+
* `mcp__<server>__<tool>` → "mcp:<server>", `coding.read_file` → "coding",
|
|
46
|
+
* bare ids → "core".
|
|
47
|
+
*/
|
|
48
|
+
export declare function toolDomain(toolName: string): string;
|
|
49
|
+
//# sourceMappingURL=kernel-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kernel-utils.d.ts","sourceRoot":"","sources":["../src/kernel-utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAEhD,kGAAkG;AAClG,wBAAgB,aAAa,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,CAAC,CAAC,CAanG;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAC/B,GAAG,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,EACxC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,WAAW,EACnB,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,CAAC,CAAC,CA4BZ;AAED,oCAAoC;AACpC,eAAO,MAAM,iBAAiB,KAAK,CAAC;AACpC,0CAA0C;AAC1C,eAAO,MAAM,+BAA+B,KAAK,CAAC;AAClD,oEAAoE;AACpE,eAAO,MAAM,mBAAmB,IAAI,CAAC;AAErC,+EAA+E;AAC/E,eAAO,MAAM,mBAAmB,8UAMO,CAAC;AAExC,yEAAyE;AACzE,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,OAAO,CAAC;IACd,gFAAgF;IAChF,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAE/C;AAoBD,uFAAuF;AACvF,wBAAgB,cAAc,CAAC,WAAW,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,SAAsB,GAAG,OAAO,CAK/H;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAOnD"}
|