@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.
Files changed (74) hide show
  1. package/CHANGELOG.md +126 -0
  2. package/LICENSE +21 -0
  3. package/README.md +147 -0
  4. package/dist/engine/agent-loop.d.ts +52 -0
  5. package/dist/engine/agent-loop.js +260 -0
  6. package/dist/engine/agent.d.ts +129 -0
  7. package/dist/engine/agent.js +283 -0
  8. package/dist/engine/message-manager.d.ts +26 -0
  9. package/dist/engine/message-manager.js +39 -0
  10. package/dist/engine/steering.d.ts +27 -0
  11. package/dist/engine/steering.js +51 -0
  12. package/dist/engine/watchdog.d.ts +24 -0
  13. package/dist/engine/watchdog.js +50 -0
  14. package/dist/index.d.ts +46 -0
  15. package/dist/index.js +63 -0
  16. package/dist/llm/model-registry.d.ts +51 -0
  17. package/dist/llm/model-registry.js +277 -0
  18. package/dist/llm/provider.d.ts +72 -0
  19. package/dist/llm/provider.js +77 -0
  20. package/dist/llm/providers/anthropic.d.ts +23 -0
  21. package/dist/llm/providers/anthropic.js +420 -0
  22. package/dist/llm/providers/google.d.ts +22 -0
  23. package/dist/llm/providers/google.js +336 -0
  24. package/dist/llm/providers/index.d.ts +67 -0
  25. package/dist/llm/providers/index.js +127 -0
  26. package/dist/llm/providers/openai.d.ts +22 -0
  27. package/dist/llm/providers/openai.js +353 -0
  28. package/dist/llm/stream-utils.d.ts +33 -0
  29. package/dist/llm/stream-utils.js +132 -0
  30. package/dist/llm/types.d.ts +129 -0
  31. package/dist/llm/types.js +17 -0
  32. package/dist/sandbox/console-capture.d.ts +29 -0
  33. package/dist/sandbox/console-capture.js +56 -0
  34. package/dist/sandbox/http-bridge.d.ts +47 -0
  35. package/dist/sandbox/http-bridge.js +143 -0
  36. package/dist/sandbox/isolated-vm-sandbox.d.ts +50 -0
  37. package/dist/sandbox/isolated-vm-sandbox.js +226 -0
  38. package/dist/sandbox/tool-bridge.d.ts +42 -0
  39. package/dist/sandbox/tool-bridge.js +98 -0
  40. package/dist/sandbox/types.d.ts +84 -0
  41. package/dist/sandbox/types.js +8 -0
  42. package/dist/skills/constants.d.ts +122 -0
  43. package/dist/skills/constants.js +179 -0
  44. package/dist/skills/disclosure.d.ts +146 -0
  45. package/dist/skills/disclosure.js +788 -0
  46. package/dist/skills/frontmatter.d.ts +27 -0
  47. package/dist/skills/frontmatter.js +177 -0
  48. package/dist/skills/index.d.ts +21 -0
  49. package/dist/skills/index.js +18 -0
  50. package/dist/skills/skill-find-tool.d.ts +41 -0
  51. package/dist/skills/skill-find-tool.js +117 -0
  52. package/dist/skills/skill-registry.d.ts +96 -0
  53. package/dist/skills/skill-registry.js +208 -0
  54. package/dist/skills/skill-tool.d.ts +44 -0
  55. package/dist/skills/skill-tool.js +96 -0
  56. package/dist/skills/tool-policy.d.ts +91 -0
  57. package/dist/skills/tool-policy.js +272 -0
  58. package/dist/skills/types.d.ts +271 -0
  59. package/dist/skills/types.js +8 -0
  60. package/dist/skills/validate.d.ts +37 -0
  61. package/dist/skills/validate.js +264 -0
  62. package/dist/tools/executor.d.ts +54 -0
  63. package/dist/tools/executor.js +206 -0
  64. package/dist/tools/helpers.d.ts +69 -0
  65. package/dist/tools/helpers.js +87 -0
  66. package/dist/tools/registry.d.ts +58 -0
  67. package/dist/tools/registry.js +119 -0
  68. package/dist/tools/types.d.ts +140 -0
  69. package/dist/tools/types.js +7 -0
  70. package/dist/tools/validator.d.ts +46 -0
  71. package/dist/tools/validator.js +210 -0
  72. package/dist/types.d.ts +56 -0
  73. package/dist/types.js +7 -0
  74. 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
+ * `&lt;` 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[];