@aragon-agent/core 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/CHANGELOG.md +126 -0
- package/LICENSE +21 -0
- package/README.md +147 -0
- package/dist/engine/agent-loop.d.ts +52 -0
- package/dist/engine/agent-loop.js +260 -0
- package/dist/engine/agent.d.ts +129 -0
- package/dist/engine/agent.js +283 -0
- package/dist/engine/message-manager.d.ts +26 -0
- package/dist/engine/message-manager.js +39 -0
- package/dist/engine/steering.d.ts +27 -0
- package/dist/engine/steering.js +51 -0
- package/dist/engine/watchdog.d.ts +24 -0
- package/dist/engine/watchdog.js +50 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.js +63 -0
- package/dist/llm/model-registry.d.ts +51 -0
- package/dist/llm/model-registry.js +277 -0
- package/dist/llm/provider.d.ts +72 -0
- package/dist/llm/provider.js +77 -0
- package/dist/llm/providers/anthropic.d.ts +23 -0
- package/dist/llm/providers/anthropic.js +420 -0
- package/dist/llm/providers/google.d.ts +22 -0
- package/dist/llm/providers/google.js +336 -0
- package/dist/llm/providers/index.d.ts +67 -0
- package/dist/llm/providers/index.js +127 -0
- package/dist/llm/providers/openai.d.ts +22 -0
- package/dist/llm/providers/openai.js +353 -0
- package/dist/llm/stream-utils.d.ts +33 -0
- package/dist/llm/stream-utils.js +132 -0
- package/dist/llm/types.d.ts +129 -0
- package/dist/llm/types.js +17 -0
- package/dist/sandbox/console-capture.d.ts +29 -0
- package/dist/sandbox/console-capture.js +56 -0
- package/dist/sandbox/http-bridge.d.ts +47 -0
- package/dist/sandbox/http-bridge.js +143 -0
- package/dist/sandbox/isolated-vm-sandbox.d.ts +50 -0
- package/dist/sandbox/isolated-vm-sandbox.js +226 -0
- package/dist/sandbox/tool-bridge.d.ts +42 -0
- package/dist/sandbox/tool-bridge.js +98 -0
- package/dist/sandbox/types.d.ts +84 -0
- package/dist/sandbox/types.js +8 -0
- package/dist/skills/constants.d.ts +122 -0
- package/dist/skills/constants.js +179 -0
- package/dist/skills/disclosure.d.ts +146 -0
- package/dist/skills/disclosure.js +788 -0
- package/dist/skills/frontmatter.d.ts +27 -0
- package/dist/skills/frontmatter.js +177 -0
- package/dist/skills/index.d.ts +21 -0
- package/dist/skills/index.js +18 -0
- package/dist/skills/skill-find-tool.d.ts +41 -0
- package/dist/skills/skill-find-tool.js +117 -0
- package/dist/skills/skill-registry.d.ts +96 -0
- package/dist/skills/skill-registry.js +208 -0
- package/dist/skills/skill-tool.d.ts +44 -0
- package/dist/skills/skill-tool.js +96 -0
- package/dist/skills/tool-policy.d.ts +91 -0
- package/dist/skills/tool-policy.js +272 -0
- package/dist/skills/types.d.ts +271 -0
- package/dist/skills/types.js +8 -0
- package/dist/skills/validate.d.ts +37 -0
- package/dist/skills/validate.js +264 -0
- package/dist/tools/executor.d.ts +54 -0
- package/dist/tools/executor.js +206 -0
- package/dist/tools/helpers.d.ts +69 -0
- package/dist/tools/helpers.js +87 -0
- package/dist/tools/registry.d.ts +58 -0
- package/dist/tools/registry.js +119 -0
- package/dist/tools/types.d.ts +140 -0
- package/dist/tools/types.js +7 -0
- package/dist/tools/validator.d.ts +46 -0
- package/dist/tools/validator.js +210 -0
- package/dist/types.d.ts +56 -0
- package/dist/types.js +7 -0
- package/package.json +115 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CodeAct Sandbox — Type Definitions
|
|
3
|
+
*
|
|
4
|
+
* Interfaces for the isolated-vm JavaScript sandbox execution environment.
|
|
5
|
+
* The sandbox enables CodeAct-style code execution: LLM-generated JavaScript
|
|
6
|
+
* runs in a V8 Isolate with bridged tool access, console capture, and HTTP.
|
|
7
|
+
*/
|
|
8
|
+
import type { AgentTool } from '../tools/types.js';
|
|
9
|
+
/** Configuration for creating an IsolatedVmSandbox instance. */
|
|
10
|
+
export interface SandboxConfig {
|
|
11
|
+
/** V8 Isolate memory limit in megabytes. Default: 128. */
|
|
12
|
+
memoryLimitMB?: number;
|
|
13
|
+
/** Default execution timeout in milliseconds. Default: 30_000. */
|
|
14
|
+
defaultTimeout?: number;
|
|
15
|
+
/** Tools to bridge into the sandbox as callable async functions. */
|
|
16
|
+
tools?: AgentTool[];
|
|
17
|
+
/**
|
|
18
|
+
* Allowed hostnames for the HTTP bridge (fetch).
|
|
19
|
+
* An empty array (default) means all HTTP requests are blocked.
|
|
20
|
+
*/
|
|
21
|
+
allowedHosts?: string[];
|
|
22
|
+
/** Pre-injected global variables available in the sandbox. */
|
|
23
|
+
globals?: Record<string, unknown>;
|
|
24
|
+
}
|
|
25
|
+
/** Per-invocation options for CodeActSandbox.execute(). */
|
|
26
|
+
export interface ExecuteOptions {
|
|
27
|
+
/** Execution timeout in milliseconds. Overrides SandboxConfig.defaultTimeout. */
|
|
28
|
+
timeout?: number;
|
|
29
|
+
/** AbortSignal for cooperative cancellation. */
|
|
30
|
+
signal?: AbortSignal;
|
|
31
|
+
}
|
|
32
|
+
/** The result of a single code execution in the sandbox. */
|
|
33
|
+
export interface ExecuteResult {
|
|
34
|
+
/** The serialized return value of the code (JSON string), or undefined on error / void. */
|
|
35
|
+
returnValue?: string;
|
|
36
|
+
/** Console output entries collected during execution, in chronological order. */
|
|
37
|
+
consoleOutput: ConsoleEntry[];
|
|
38
|
+
/** Error information if the execution failed. Absent on success. */
|
|
39
|
+
error?: {
|
|
40
|
+
name: string;
|
|
41
|
+
message: string;
|
|
42
|
+
stack?: string;
|
|
43
|
+
};
|
|
44
|
+
/** Wall-clock execution duration in milliseconds. */
|
|
45
|
+
duration: number;
|
|
46
|
+
/** Tool calls made during execution via the tool bridge. */
|
|
47
|
+
toolCalls: SandboxToolCall[];
|
|
48
|
+
}
|
|
49
|
+
/** A single captured console output entry. */
|
|
50
|
+
export interface ConsoleEntry {
|
|
51
|
+
level: 'log' | 'warn' | 'error' | 'info';
|
|
52
|
+
args: string[];
|
|
53
|
+
timestamp: number;
|
|
54
|
+
}
|
|
55
|
+
/** Record of a single tool invocation made from within the sandbox. */
|
|
56
|
+
export interface SandboxToolCall {
|
|
57
|
+
toolName: string;
|
|
58
|
+
args: Record<string, unknown>;
|
|
59
|
+
result?: string;
|
|
60
|
+
error?: string;
|
|
61
|
+
/** Duration of the tool execution in milliseconds. */
|
|
62
|
+
duration: number;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The primary sandbox interface for executing JavaScript code in an
|
|
66
|
+
* isolated V8 environment with bridged tool access.
|
|
67
|
+
*
|
|
68
|
+
* Lifecycle:
|
|
69
|
+
* 1. Construct with config (lazy — no Isolate created yet)
|
|
70
|
+
* 2. Call execute() — Isolate + Context created on first call
|
|
71
|
+
* 3. Variables persist across execute() calls within the same Context
|
|
72
|
+
* 4. Call reset() to clear all state (new Context, re-inject bridges)
|
|
73
|
+
* 5. Call dispose() when done to release the V8 Isolate
|
|
74
|
+
*/
|
|
75
|
+
export interface CodeActSandbox {
|
|
76
|
+
/** Execute JavaScript code in the sandbox. */
|
|
77
|
+
execute(code: string, options?: ExecuteOptions): Promise<ExecuteResult>;
|
|
78
|
+
/** Reset sandbox state: destroy current Context, create a fresh one, re-inject bridges. */
|
|
79
|
+
reset(): Promise<void>;
|
|
80
|
+
/** Dispose the sandbox and release the underlying V8 Isolate. */
|
|
81
|
+
dispose(): void;
|
|
82
|
+
/** Whether this sandbox has been disposed. All calls after disposal will throw. */
|
|
83
|
+
get isDisposed(): boolean;
|
|
84
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CodeAct Sandbox — Type Definitions
|
|
3
|
+
*
|
|
4
|
+
* Interfaces for the isolated-vm JavaScript sandbox execution environment.
|
|
5
|
+
* The sandbox enables CodeAct-style code execution: LLM-generated JavaScript
|
|
6
|
+
* runs in a V8 Isolate with bridged tool access, console capture, and HTTP.
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill budgets and format constants — the single source of truth (spec §11.1).
|
|
3
|
+
*
|
|
4
|
+
* UNIT DISCIPLINE (D19 / R16): every `*_BYTES` constant below is measured in
|
|
5
|
+
* UTF-8 BYTES, never in JS string length. `ToolExecutor`'s own ceiling
|
|
6
|
+
* (`DEFAULT_MAX_OUTPUT_SIZE = 100_000`) is a byte count and `truncateUtf8()`
|
|
7
|
+
* cuts on byte boundaries, so a "30 000 characters" budget silently becomes
|
|
8
|
+
* 90 000 bytes for CJK text and gets halved by the executor — taking the
|
|
9
|
+
* closing `</skill>` tag with it. Always measure with `Buffer.byteLength`.
|
|
10
|
+
*
|
|
11
|
+
* No magic numbers anywhere else in the skills subsystem.
|
|
12
|
+
*/
|
|
13
|
+
/** Whole `<available_skills>` block ceiling, UTF-8 bytes. */
|
|
14
|
+
export declare const SKILL_CATALOG_MAX_BYTES = 6000;
|
|
15
|
+
/** Per-entry description cap (characters — this one is a display width, not a budget). */
|
|
16
|
+
export declare const SKILL_DESC_LINE_MAX = 220;
|
|
17
|
+
/**
|
|
18
|
+
* INTEGER BUCKETS, NEVER FLOATS. The Level 1 catalog order is a hard
|
|
19
|
+
* determinism requirement (§5.1): a score computed from, say, a decayed
|
|
20
|
+
* timestamp would make truncation depend on when the test ran and leave the
|
|
21
|
+
* byte-budget suites unpinnable. Buckets keep the comparison exact.
|
|
22
|
+
*/
|
|
23
|
+
export declare const USAGE_RECENCY_HOUR_MS: number;
|
|
24
|
+
export declare const USAGE_RECENCY_DAY_MS: number;
|
|
25
|
+
export declare const USAGE_RECENCY_WEEK_MS: number;
|
|
26
|
+
/** `useCount` thresholds for the frequency bucket (2 / 1 / 0). */
|
|
27
|
+
export declare const USAGE_FREQ_HIGH = 10;
|
|
28
|
+
export declare const USAGE_FREQ_LOW = 3;
|
|
29
|
+
/**
|
|
30
|
+
* Scope outweighs every usage signal by construction: a `project` skill was put
|
|
31
|
+
* there FOR THIS REPOSITORY, so even an unused one is closer to the task at hand
|
|
32
|
+
* than a frequently used global. Usage only reorders within one scope.
|
|
33
|
+
*/
|
|
34
|
+
export declare const SCOPE_SCORE_WEIGHT = 1000;
|
|
35
|
+
export declare const RECENCY_SCORE_WEIGHT = 10;
|
|
36
|
+
/** Whole `<skill_search_results>` block ceiling, UTF-8 bytes. */
|
|
37
|
+
export declare const SKILL_FIND_MAX_BYTES = 8000;
|
|
38
|
+
/** Results returned when the model does not ask for a specific count. */
|
|
39
|
+
export declare const SKILL_FIND_DEFAULT_LIMIT = 10;
|
|
40
|
+
/** Hard ceiling on `limit`; a larger request is clamped, not rejected. */
|
|
41
|
+
export declare const SKILL_FIND_MAX_LIMIT = 25;
|
|
42
|
+
/** Query tokens honoured. Extra words are dropped rather than failing the call. */
|
|
43
|
+
export declare const SKILL_FIND_MAX_TOKENS = 8;
|
|
44
|
+
/** SKILL.md body ceiling inside a `skill` tool result, UTF-8 bytes. */
|
|
45
|
+
export declare const SKILL_BODY_MAX_BYTES = 30000;
|
|
46
|
+
/**
|
|
47
|
+
* Hard ceiling on the ENTIRE `skill` tool result, UTF-8 bytes.
|
|
48
|
+
*
|
|
49
|
+
* Deliberately 40 KB below `ToolExecutor`'s 100 000-byte cap so this tool can
|
|
50
|
+
* never trigger upstream truncation — which would append `... [truncated]`
|
|
51
|
+
* after eating the closing tag and hand the model a structurally broken block.
|
|
52
|
+
*/
|
|
53
|
+
export declare const SKILL_RESULT_MAX_BYTES = 60000;
|
|
54
|
+
/** Max bundled-file entries listed in `<skill_files>`. */
|
|
55
|
+
export declare const SKILL_FILES_MAX = 50;
|
|
56
|
+
/**
|
|
57
|
+
* Hard ceiling on the whole `/<skill-name>` invocation MESSAGE, UTF-8 bytes.
|
|
58
|
+
*
|
|
59
|
+
* Same number as `SKILL_RESULT_MAX_BYTES`, deliberately a SEPARATE name (D-G12).
|
|
60
|
+
* The two ceilings exist for DIFFERENT reasons: the tool-result one dodges
|
|
61
|
+
* `ToolExecutor`'s 100 000-byte cut, which would eat the closing tag; this one
|
|
62
|
+
* exists because one injected reference document must not swallow the context
|
|
63
|
+
* window. Sharing a constant would make "it is safe to raise one" read as "it is
|
|
64
|
+
* safe to raise the other".
|
|
65
|
+
*
|
|
66
|
+
* WHY 60 000 IS ALWAYS ENOUGH TODAY: `skills.bodyMaxBytes` is clamped to at most
|
|
67
|
+
* 50 000 (`cli/src/config/schema.ts::BODY_BYTES_RANGE.max`), which leaves 10 KB
|
|
68
|
+
* for tags, the file list and the guidance. THAT CLAMP IS THE PREMISE — if it is
|
|
69
|
+
* ever raised, this constant has to move with it, and nothing else would go red.
|
|
70
|
+
*/
|
|
71
|
+
export declare const SKILL_INVOCATION_MAX_BYTES = 60000;
|
|
72
|
+
/**
|
|
73
|
+
* Ceiling for the repeat-load digest (§7.2), UTF-8 bytes.
|
|
74
|
+
*
|
|
75
|
+
* The digest carries the open tag, the description line, the omitted-body note,
|
|
76
|
+
* `<skill_files>` and the guidance — everything except the body itself. When it
|
|
77
|
+
* still does not fit, `<skill_files>` rows are reclaimed; the structure is never
|
|
78
|
+
* sacrificed, exactly as in `renderSkillBody`.
|
|
79
|
+
*/
|
|
80
|
+
export declare const SKILL_DIGEST_MAX_BYTES = 4000;
|
|
81
|
+
/**
|
|
82
|
+
* Refusals of the SAME tool within one turn before the refusal escalates (§5.5).
|
|
83
|
+
*
|
|
84
|
+
* "Do not retry this tool" is a request, not a mechanism. Without a counter a
|
|
85
|
+
* single mis-declared skill can burn an entire turn on one blocked tool.
|
|
86
|
+
*/
|
|
87
|
+
export declare const TOOL_POLICY_DENY_ESCALATE_AT = 3;
|
|
88
|
+
/**
|
|
89
|
+
* Bundled files that are "something you RUN" rather than "something you READ"
|
|
90
|
+
* (§8.2). Used by `classifyBundledFile()`; a `scripts/` prefix counts too.
|
|
91
|
+
*/
|
|
92
|
+
export declare const SKILL_SCRIPT_EXTENSIONS: readonly [".py", ".js", ".mjs", ".cjs", ".ts", ".sh", ".ps1", ".bat", ".cmd"];
|
|
93
|
+
/** Combined ceiling for all always-on skill bodies in the system prompt, UTF-8 bytes. */
|
|
94
|
+
export declare const ALWAYS_SKILLS_MAX_BYTES = 12000;
|
|
95
|
+
/** Largest SKILL.md the scanner will read, UTF-8 bytes. */
|
|
96
|
+
export declare const SKILL_MD_MAX_BYTES: number;
|
|
97
|
+
/** Frontmatter block hard limits (§4.3 rule 8) — defence against malformed files. */
|
|
98
|
+
export declare const FRONTMATTER_MAX_BYTES: number;
|
|
99
|
+
export declare const FRONTMATTER_MAX_LINES = 200;
|
|
100
|
+
/** Directories scanned per root before the scanner gives up (§6.2 step 6). */
|
|
101
|
+
export declare const SKILL_SCAN_MAX_DIRS = 500;
|
|
102
|
+
/** Soft wall-clock ceiling for one full discovery pass, milliseconds. */
|
|
103
|
+
export declare const SKILL_SCAN_SOFT_TIMEOUT_MS = 1500;
|
|
104
|
+
/** Staging leftovers older than this are reclaimed on the next discover() (P2-10). */
|
|
105
|
+
export declare const STAGING_TTL_MS: number;
|
|
106
|
+
/** `name` must be kebab-case ASCII. Also the dynamic slash-command name. */
|
|
107
|
+
export declare const SKILL_NAME_PATTERN: RegExp;
|
|
108
|
+
export declare const SKILL_NAME_MAX = 64;
|
|
109
|
+
export declare const SKILL_DESC_MAX = 1024;
|
|
110
|
+
export declare const SKILL_META_MAX = 256;
|
|
111
|
+
export declare const SKILL_KEYWORDS_MAX = 16;
|
|
112
|
+
export declare const SKILL_KEYWORD_LEN_MAX = 32;
|
|
113
|
+
export declare const SKILL_ALLOWED_TOOLS_MAX = 32;
|
|
114
|
+
export declare const STAGED_MAX_FILE_BYTES: number;
|
|
115
|
+
export declare const STAGED_MAX_TOTAL_BYTES: number;
|
|
116
|
+
export declare const STAGED_MAX_FILES = 200;
|
|
117
|
+
export declare const STAGED_MAX_DEPTH = 5;
|
|
118
|
+
/** `skill_create` inline file limits (§8.4). */
|
|
119
|
+
export declare const CREATE_MAX_FILE_BYTES: number;
|
|
120
|
+
export declare const CREATE_MAX_FILES = 20;
|
|
121
|
+
/** Windows device names that can never be used as a path segment. */
|
|
122
|
+
export declare const WINDOWS_RESERVED_NAMES: Set<string>;
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill budgets and format constants — the single source of truth (spec §11.1).
|
|
3
|
+
*
|
|
4
|
+
* UNIT DISCIPLINE (D19 / R16): every `*_BYTES` constant below is measured in
|
|
5
|
+
* UTF-8 BYTES, never in JS string length. `ToolExecutor`'s own ceiling
|
|
6
|
+
* (`DEFAULT_MAX_OUTPUT_SIZE = 100_000`) is a byte count and `truncateUtf8()`
|
|
7
|
+
* cuts on byte boundaries, so a "30 000 characters" budget silently becomes
|
|
8
|
+
* 90 000 bytes for CJK text and gets halved by the executor — taking the
|
|
9
|
+
* closing `</skill>` tag with it. Always measure with `Buffer.byteLength`.
|
|
10
|
+
*
|
|
11
|
+
* No magic numbers anywhere else in the skills subsystem.
|
|
12
|
+
*/
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Level 1 — catalog block (system prompt)
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
/** Whole `<available_skills>` block ceiling, UTF-8 bytes. */
|
|
17
|
+
export const SKILL_CATALOG_MAX_BYTES = 6000;
|
|
18
|
+
/** Per-entry description cap (characters — this one is a display width, not a budget). */
|
|
19
|
+
export const SKILL_DESC_LINE_MAX = 220;
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Catalog ranking buckets (§5.2)
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
/**
|
|
24
|
+
* INTEGER BUCKETS, NEVER FLOATS. The Level 1 catalog order is a hard
|
|
25
|
+
* determinism requirement (§5.1): a score computed from, say, a decayed
|
|
26
|
+
* timestamp would make truncation depend on when the test ran and leave the
|
|
27
|
+
* byte-budget suites unpinnable. Buckets keep the comparison exact.
|
|
28
|
+
*/
|
|
29
|
+
export const USAGE_RECENCY_HOUR_MS = 60 * 60 * 1000;
|
|
30
|
+
export const USAGE_RECENCY_DAY_MS = 24 * USAGE_RECENCY_HOUR_MS;
|
|
31
|
+
export const USAGE_RECENCY_WEEK_MS = 7 * USAGE_RECENCY_DAY_MS;
|
|
32
|
+
/** `useCount` thresholds for the frequency bucket (2 / 1 / 0). */
|
|
33
|
+
export const USAGE_FREQ_HIGH = 10;
|
|
34
|
+
export const USAGE_FREQ_LOW = 3;
|
|
35
|
+
/**
|
|
36
|
+
* Scope outweighs every usage signal by construction: a `project` skill was put
|
|
37
|
+
* there FOR THIS REPOSITORY, so even an unused one is closer to the task at hand
|
|
38
|
+
* than a frequently used global. Usage only reorders within one scope.
|
|
39
|
+
*/
|
|
40
|
+
export const SCOPE_SCORE_WEIGHT = 1000;
|
|
41
|
+
export const RECENCY_SCORE_WEIGHT = 10;
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// `skill_find` — the escape hatch for a truncated catalog (§5.4)
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
/** Whole `<skill_search_results>` block ceiling, UTF-8 bytes. */
|
|
46
|
+
export const SKILL_FIND_MAX_BYTES = 8000;
|
|
47
|
+
/** Results returned when the model does not ask for a specific count. */
|
|
48
|
+
export const SKILL_FIND_DEFAULT_LIMIT = 10;
|
|
49
|
+
/** Hard ceiling on `limit`; a larger request is clamped, not rejected. */
|
|
50
|
+
export const SKILL_FIND_MAX_LIMIT = 25;
|
|
51
|
+
/** Query tokens honoured. Extra words are dropped rather than failing the call. */
|
|
52
|
+
export const SKILL_FIND_MAX_TOKENS = 8;
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// Level 2 — `skill` tool result
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
/** SKILL.md body ceiling inside a `skill` tool result, UTF-8 bytes. */
|
|
57
|
+
export const SKILL_BODY_MAX_BYTES = 30_000;
|
|
58
|
+
/**
|
|
59
|
+
* Hard ceiling on the ENTIRE `skill` tool result, UTF-8 bytes.
|
|
60
|
+
*
|
|
61
|
+
* Deliberately 40 KB below `ToolExecutor`'s 100 000-byte cap so this tool can
|
|
62
|
+
* never trigger upstream truncation — which would append `... [truncated]`
|
|
63
|
+
* after eating the closing tag and hand the model a structurally broken block.
|
|
64
|
+
*/
|
|
65
|
+
export const SKILL_RESULT_MAX_BYTES = 60_000;
|
|
66
|
+
/** Max bundled-file entries listed in `<skill_files>`. */
|
|
67
|
+
export const SKILL_FILES_MAX = 50;
|
|
68
|
+
/**
|
|
69
|
+
* Hard ceiling on the whole `/<skill-name>` invocation MESSAGE, UTF-8 bytes.
|
|
70
|
+
*
|
|
71
|
+
* Same number as `SKILL_RESULT_MAX_BYTES`, deliberately a SEPARATE name (D-G12).
|
|
72
|
+
* The two ceilings exist for DIFFERENT reasons: the tool-result one dodges
|
|
73
|
+
* `ToolExecutor`'s 100 000-byte cut, which would eat the closing tag; this one
|
|
74
|
+
* exists because one injected reference document must not swallow the context
|
|
75
|
+
* window. Sharing a constant would make "it is safe to raise one" read as "it is
|
|
76
|
+
* safe to raise the other".
|
|
77
|
+
*
|
|
78
|
+
* WHY 60 000 IS ALWAYS ENOUGH TODAY: `skills.bodyMaxBytes` is clamped to at most
|
|
79
|
+
* 50 000 (`cli/src/config/schema.ts::BODY_BYTES_RANGE.max`), which leaves 10 KB
|
|
80
|
+
* for tags, the file list and the guidance. THAT CLAMP IS THE PREMISE — if it is
|
|
81
|
+
* ever raised, this constant has to move with it, and nothing else would go red.
|
|
82
|
+
*/
|
|
83
|
+
export const SKILL_INVOCATION_MAX_BYTES = 60_000;
|
|
84
|
+
/**
|
|
85
|
+
* Ceiling for the repeat-load digest (§7.2), UTF-8 bytes.
|
|
86
|
+
*
|
|
87
|
+
* The digest carries the open tag, the description line, the omitted-body note,
|
|
88
|
+
* `<skill_files>` and the guidance — everything except the body itself. When it
|
|
89
|
+
* still does not fit, `<skill_files>` rows are reclaimed; the structure is never
|
|
90
|
+
* sacrificed, exactly as in `renderSkillBody`.
|
|
91
|
+
*/
|
|
92
|
+
export const SKILL_DIGEST_MAX_BYTES = 4_000;
|
|
93
|
+
/**
|
|
94
|
+
* Refusals of the SAME tool within one turn before the refusal escalates (§5.5).
|
|
95
|
+
*
|
|
96
|
+
* "Do not retry this tool" is a request, not a mechanism. Without a counter a
|
|
97
|
+
* single mis-declared skill can burn an entire turn on one blocked tool.
|
|
98
|
+
*/
|
|
99
|
+
export const TOOL_POLICY_DENY_ESCALATE_AT = 3;
|
|
100
|
+
/**
|
|
101
|
+
* Bundled files that are "something you RUN" rather than "something you READ"
|
|
102
|
+
* (§8.2). Used by `classifyBundledFile()`; a `scripts/` prefix counts too.
|
|
103
|
+
*/
|
|
104
|
+
export const SKILL_SCRIPT_EXTENSIONS = [
|
|
105
|
+
'.py',
|
|
106
|
+
'.js',
|
|
107
|
+
'.mjs',
|
|
108
|
+
'.cjs',
|
|
109
|
+
'.ts',
|
|
110
|
+
'.sh',
|
|
111
|
+
'.ps1',
|
|
112
|
+
'.bat',
|
|
113
|
+
'.cmd',
|
|
114
|
+
];
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
// `activation: always` block
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
/** Combined ceiling for all always-on skill bodies in the system prompt, UTF-8 bytes. */
|
|
119
|
+
export const ALWAYS_SKILLS_MAX_BYTES = 12_000;
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
// Disk / parsing limits
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
/** Largest SKILL.md the scanner will read, UTF-8 bytes. */
|
|
124
|
+
export const SKILL_MD_MAX_BYTES = 512 * 1024;
|
|
125
|
+
/** Frontmatter block hard limits (§4.3 rule 8) — defence against malformed files. */
|
|
126
|
+
export const FRONTMATTER_MAX_BYTES = 8 * 1024;
|
|
127
|
+
export const FRONTMATTER_MAX_LINES = 200;
|
|
128
|
+
/** Directories scanned per root before the scanner gives up (§6.2 step 6). */
|
|
129
|
+
export const SKILL_SCAN_MAX_DIRS = 500;
|
|
130
|
+
/** Soft wall-clock ceiling for one full discovery pass, milliseconds. */
|
|
131
|
+
export const SKILL_SCAN_SOFT_TIMEOUT_MS = 1500;
|
|
132
|
+
/** Staging leftovers older than this are reclaimed on the next discover() (P2-10). */
|
|
133
|
+
export const STAGING_TTL_MS = 24 * 60 * 60 * 1000;
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
// Frontmatter field limits (§4.2)
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
/** `name` must be kebab-case ASCII. Also the dynamic slash-command name. */
|
|
138
|
+
export const SKILL_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
139
|
+
export const SKILL_NAME_MAX = 64;
|
|
140
|
+
export const SKILL_DESC_MAX = 1024;
|
|
141
|
+
export const SKILL_META_MAX = 256;
|
|
142
|
+
export const SKILL_KEYWORDS_MAX = 16;
|
|
143
|
+
export const SKILL_KEYWORD_LEN_MAX = 32;
|
|
144
|
+
export const SKILL_ALLOWED_TOOLS_MAX = 32;
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
// Staged-install validation limits (§8.3 step 2) — enforced by validateStagedSkill
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
export const STAGED_MAX_FILE_BYTES = 5 * 1024 * 1024;
|
|
149
|
+
export const STAGED_MAX_TOTAL_BYTES = 20 * 1024 * 1024;
|
|
150
|
+
export const STAGED_MAX_FILES = 200;
|
|
151
|
+
export const STAGED_MAX_DEPTH = 5;
|
|
152
|
+
/** `skill_create` inline file limits (§8.4). */
|
|
153
|
+
export const CREATE_MAX_FILE_BYTES = 1024 * 1024;
|
|
154
|
+
export const CREATE_MAX_FILES = 20;
|
|
155
|
+
/** Windows device names that can never be used as a path segment. */
|
|
156
|
+
export const WINDOWS_RESERVED_NAMES = new Set([
|
|
157
|
+
'con',
|
|
158
|
+
'prn',
|
|
159
|
+
'aux',
|
|
160
|
+
'nul',
|
|
161
|
+
'com1',
|
|
162
|
+
'com2',
|
|
163
|
+
'com3',
|
|
164
|
+
'com4',
|
|
165
|
+
'com5',
|
|
166
|
+
'com6',
|
|
167
|
+
'com7',
|
|
168
|
+
'com8',
|
|
169
|
+
'com9',
|
|
170
|
+
'lpt1',
|
|
171
|
+
'lpt2',
|
|
172
|
+
'lpt3',
|
|
173
|
+
'lpt4',
|
|
174
|
+
'lpt5',
|
|
175
|
+
'lpt6',
|
|
176
|
+
'lpt7',
|
|
177
|
+
'lpt8',
|
|
178
|
+
'lpt9',
|
|
179
|
+
]);
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Progressive disclosure rendering (spec §5) — the exact text the model sees.
|
|
3
|
+
*
|
|
4
|
+
* Three levels:
|
|
5
|
+
* L1 `renderSkillCatalog()` → `<available_skills>` in the SYSTEM PROMPT.
|
|
6
|
+
* L2 `renderSkillBody()` → the `skill` tool RESULT (never the prompt, D2).
|
|
7
|
+
* L3 nothing — bundled files are read on demand with `read_file` / `bash`.
|
|
8
|
+
*
|
|
9
|
+
* TWO INVARIANTS THAT ARE EASY TO BREAK AND EXPENSIVE TO LOSE:
|
|
10
|
+
*
|
|
11
|
+
* 1. Every byte of third-party text is passed through
|
|
12
|
+
* `sanitizeForPromptBlock()` before it is concatenated into a tagged
|
|
13
|
+
* block. "Wrap it in <skill> and tell the model it is reference material"
|
|
14
|
+
* (§9.1 R1) only works if the content CANNOT close the tag. See D18.
|
|
15
|
+
*
|
|
16
|
+
* 2. Every budget is counted in UTF-8 BYTES, never `String.length` (D19).
|
|
17
|
+
* `ToolExecutor` truncates at 100 000 bytes and would eat the closing tag.
|
|
18
|
+
*/
|
|
19
|
+
import type { SkillBodyOptions, SkillCatalogOptions, SkillFindOptions, SkillInvocationOptions, SkillPlatform, SkillRecord, SkillUsageMap } from './types.js';
|
|
20
|
+
export declare function byteLength(text: string): number;
|
|
21
|
+
/** Cut to at most `maxBytes` UTF-8 bytes without splitting a code point. */
|
|
22
|
+
export declare function truncateToBytes(text: string, maxBytes: number): string;
|
|
23
|
+
/**
|
|
24
|
+
* Neutralize a span of untrusted text so it can be embedded inside a tagged
|
|
25
|
+
* prompt block (§9.5). Idempotent, total, and never throws.
|
|
26
|
+
*
|
|
27
|
+
* Angle brackets become their FULL-WIDTH twins rather than HTML entities:
|
|
28
|
+
* `<` reads as literal noise inside Markdown, whereas `<` keeps the text
|
|
29
|
+
* legible to a human and semantically obvious to the model while being
|
|
30
|
+
* structurally incapable of closing `</skill>` or `</available_skills>`.
|
|
31
|
+
* Q6 accepts the cost for HTML/JSX-themed skills — that content belongs in
|
|
32
|
+
* `reference/` where `read_file` returns it verbatim, unsanitized.
|
|
33
|
+
*/
|
|
34
|
+
export declare function sanitizeForPromptBlock(text: string, opts?: {
|
|
35
|
+
collapseNewlines?: boolean;
|
|
36
|
+
}): string;
|
|
37
|
+
/** Records eligible for the Level 1 catalog, in the canonical order. */
|
|
38
|
+
export declare function catalogRecords(records: SkillRecord[]): SkillRecord[];
|
|
39
|
+
/**
|
|
40
|
+
* Catalog order with usage folded in — the thing that decides what SURVIVES
|
|
41
|
+
* truncation (§5.2). Filtering stays in `catalogRecords()` so there is exactly
|
|
42
|
+
* one definition of "eligible".
|
|
43
|
+
*
|
|
44
|
+
* INVARIANT I-A1: with no usage data every recency/frequency bucket is 0, the
|
|
45
|
+
* score collapses to the scope term alone, and the output is element-for-element
|
|
46
|
+
* identical to `catalogRecords()`. That is what lets the pre-existing catalog
|
|
47
|
+
* snapshots keep passing unchanged, and it is the reason this feature can be
|
|
48
|
+
* reverted by simply not passing `usage`.
|
|
49
|
+
*/
|
|
50
|
+
export declare function rankCatalogRecords(records: SkillRecord[], opts?: {
|
|
51
|
+
usage?: SkillUsageMap;
|
|
52
|
+
now?: number;
|
|
53
|
+
}): SkillRecord[];
|
|
54
|
+
/** Public helper for D14: the single catalog line for a freshly installed skill. */
|
|
55
|
+
export declare function renderCatalogLine(record: SkillRecord, descLineMax?: number): string;
|
|
56
|
+
/**
|
|
57
|
+
* Render the `<available_skills>` block, or `''` when nothing is eligible.
|
|
58
|
+
*
|
|
59
|
+
* `''` is load-bearing: it is what keeps `buildSystemPrompt()` byte-identical
|
|
60
|
+
* to its pre-Skills output on the `--no-skills` / zero-skill / all-manual paths
|
|
61
|
+
* (invariant I-S1, AC-10).
|
|
62
|
+
*/
|
|
63
|
+
export declare function renderSkillCatalog(records: SkillRecord[], opts?: SkillCatalogOptions): string;
|
|
64
|
+
/**
|
|
65
|
+
* The empty-result text is load-bearing, not politeness (RA3).
|
|
66
|
+
*
|
|
67
|
+
* "No skill matched" is the exact moment a model is most tempted to go find one
|
|
68
|
+
* on the internet and install it — which would turn agent self-installation from
|
|
69
|
+
* a convenience the user authorised into an autonomous supply-chain decision
|
|
70
|
+
* (D-A4). The refusal has to be stated here, in the same breath as the miss,
|
|
71
|
+
* and it is deliberately worded like `skill_install`'s own description.
|
|
72
|
+
*/
|
|
73
|
+
export declare function renderSkillFindNoMatch(query: string, suggestions?: string[]): string;
|
|
74
|
+
/**
|
|
75
|
+
* Render the `skill_find` payload. Same two invariants as the catalog: every
|
|
76
|
+
* span of third-party text is sanitized (D18) and the budget is counted in
|
|
77
|
+
* UTF-8 bytes (D19).
|
|
78
|
+
*/
|
|
79
|
+
export declare function renderSkillFindResults(query: string, matched: SkillRecord[], opts?: SkillFindOptions): string;
|
|
80
|
+
/**
|
|
81
|
+
* Is this bundled file something you RUN or something you READ (§8.2)?
|
|
82
|
+
*
|
|
83
|
+
* A heuristic on purpose (QG6): a `scripts:` frontmatter key would answer this
|
|
84
|
+
* exactly, at the cost of forking the format contract with Claude Code for one
|
|
85
|
+
* line of guidance. The prefix + extension test covers the existing ecosystem.
|
|
86
|
+
*/
|
|
87
|
+
export declare function classifyBundledFile(path: string): 'script' | 'reference';
|
|
88
|
+
/**
|
|
89
|
+
* Render the Level 2 payload returned by the `skill` tool.
|
|
90
|
+
*
|
|
91
|
+
* `mode: 'digest'` drops the body and points at the earlier copy instead (§7.2).
|
|
92
|
+
* Repeating 30 KB of identical text is not merely expensive — a verbatim
|
|
93
|
+
* duplicate in the history is the input shape most likely to send a model into
|
|
94
|
+
* repetition, and the note that already accompanied it said "you do not need to
|
|
95
|
+
* re-read this" while handing over a second copy.
|
|
96
|
+
*/
|
|
97
|
+
export declare function renderSkillBody(record: SkillRecord, opts?: SkillBodyOptions): string;
|
|
98
|
+
/**
|
|
99
|
+
* Render always-on skills for the system prompt, or `''` when there are none.
|
|
100
|
+
*
|
|
101
|
+
* The overflow notice hangs off THIS block's own tail rather than the catalog's
|
|
102
|
+
* (P2-11): `renderSkillCatalog()` has no way to know what was dropped here, and
|
|
103
|
+
* a cross-function dependency for one advisory line is not worth the coupling.
|
|
104
|
+
*
|
|
105
|
+
* `platform` IS FORWARDED; `policy` IS NOT — and the absence of a `policy` field
|
|
106
|
+
* on these options is the enforcement, not a convention (P0-2 / D-G18). D-G9
|
|
107
|
+
* exempts always-on skills from the ceiling, so a policy line here would tell the
|
|
108
|
+
* model, on EVERY turn, that only certain tools are permitted — an invented
|
|
109
|
+
* restriction, applied for the whole session, traceable to nothing the user can
|
|
110
|
+
* see. `platform` is a fact about the host and is true regardless.
|
|
111
|
+
*/
|
|
112
|
+
export declare function renderAlwaysSkills(records: SkillRecord[], opts?: {
|
|
113
|
+
maxBytes?: number;
|
|
114
|
+
bodyMaxBytes?: number;
|
|
115
|
+
platform?: SkillPlatform;
|
|
116
|
+
}): string;
|
|
117
|
+
/** Split an argument string into shell-style words, honouring quotes. */
|
|
118
|
+
export declare function splitArguments(args: string): string[];
|
|
119
|
+
/**
|
|
120
|
+
* Substitute `$ARGUMENTS` / `$1..$9` in a skill body; `$$` is a literal `$`.
|
|
121
|
+
*
|
|
122
|
+
* When no placeholder appears at all and arguments were supplied, they are
|
|
123
|
+
* appended under an `## Arguments` heading so a skill that was written without
|
|
124
|
+
* placeholders still receives what the user typed.
|
|
125
|
+
*/
|
|
126
|
+
export declare function applySkillArguments(body: string, args: string): {
|
|
127
|
+
text: string;
|
|
128
|
+
substituted: boolean;
|
|
129
|
+
};
|
|
130
|
+
/**
|
|
131
|
+
* Build the user message a `/<skill-name>` command submits on the user's behalf.
|
|
132
|
+
*
|
|
133
|
+
* THIS PATH HAS A BUDGET NOW (FG3 / W2). It used to have none at all, while the
|
|
134
|
+
* scanner happily accepts a 512 KB `SKILL.md` — so `/big-skill` submitted half a
|
|
135
|
+
* megabyte as one user message, and `skills.bodyMaxBytes` was silently inert on
|
|
136
|
+
* the one delivery path a human triggers by hand. The user-visible symptom was
|
|
137
|
+
* "this skill errors every time I use it", with the provider's 400 as the only
|
|
138
|
+
* clue.
|
|
139
|
+
*
|
|
140
|
+
* STEP ORDER MATTERS: substitute arguments BEFORE truncating. The other way
|
|
141
|
+
* round, the bytes that get cut can be exactly where `$ARGUMENTS` was going to
|
|
142
|
+
* land, and the user sees their arguments quietly ignored.
|
|
143
|
+
*/
|
|
144
|
+
export declare function renderSkillInvocation(record: SkillRecord, args: string, opts?: SkillInvocationOptions): string;
|
|
145
|
+
/** Nearest skill names for an unknown lookup: prefix matches first, then edits ≤ 2. */
|
|
146
|
+
export declare function suggestSkillNames(query: string, names: string[], limit?: number): string[];
|