@nexrall/code-core 1.4.63 → 1.4.66
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/README.md +2 -2
- package/dist/agent/agentRegistry.d.ts +14 -3
- package/dist/agent/agentRegistry.d.ts.map +1 -1
- package/dist/agent/agentRegistry.js +136 -6
- package/dist/agent/agentTypes.d.ts +53 -3
- package/dist/agent/agentTypes.d.ts.map +1 -1
- package/dist/agent/agentTypes.js +200 -153
- package/dist/agent/backgroundAgents.d.ts +66 -0
- package/dist/agent/backgroundAgents.d.ts.map +1 -0
- package/dist/agent/backgroundAgents.js +145 -0
- package/dist/agent/loop.d.ts +44 -7
- package/dist/agent/loop.d.ts.map +1 -1
- package/dist/agent/loop.js +946 -263
- package/dist/agent/memory.d.ts +11 -0
- package/dist/agent/memory.d.ts.map +1 -1
- package/dist/agent/memory.js +23 -3
- package/dist/agent/modelCatalogue.d.ts +15 -0
- package/dist/agent/modelCatalogue.d.ts.map +1 -1
- package/dist/agent/modelCatalogue.js +46 -0
- package/dist/agent/planMode.d.ts +14 -0
- package/dist/agent/planMode.d.ts.map +1 -1
- package/dist/agent/planMode.js +145 -17
- package/dist/agent/readDedupe.d.ts +15 -0
- package/dist/agent/readDedupe.d.ts.map +1 -0
- package/dist/agent/readDedupe.js +146 -0
- package/dist/agent/securityLint.js +2 -2
- package/dist/agent/toolPrefetch.d.ts +44 -0
- package/dist/agent/toolPrefetch.d.ts.map +1 -0
- package/dist/agent/toolPrefetch.js +101 -0
- package/dist/agent/trust.d.ts +0 -5
- package/dist/agent/trust.d.ts.map +1 -1
- package/dist/agent/trust.js +41 -0
- package/dist/api/client.d.ts +15 -1
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +88 -8
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/permissions/destructive.d.ts +9 -7
- package/dist/permissions/destructive.d.ts.map +1 -1
- package/dist/permissions/destructive.js +55 -8
- package/dist/permissions/destructiveTokens.d.ts +29 -0
- package/dist/permissions/destructiveTokens.d.ts.map +1 -0
- package/dist/permissions/destructiveTokens.js +469 -0
- package/dist/permissions/modePolicy.d.ts +22 -8
- package/dist/permissions/modePolicy.d.ts.map +1 -1
- package/dist/permissions/modePolicy.js +25 -10
- package/dist/tools/executor.d.ts +36 -0
- package/dist/tools/executor.d.ts.map +1 -1
- package/dist/tools/executor.js +408 -85
- package/dist/tools/tsLangService.d.ts.map +1 -1
- package/dist/tools/tsLangService.js +107 -21
- package/dist/types.d.ts +101 -7
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +12 -1
- package/dist/util/miniYaml.d.ts +10 -0
- package/dist/util/miniYaml.d.ts.map +1 -0
- package/dist/util/miniYaml.js +149 -0
- package/package.json +2 -10
package/dist/agent/agentTypes.js
CHANGED
|
@@ -34,13 +34,16 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.MODEL_TIER_ALIASES = exports.VALID_MODELS = void 0;
|
|
37
|
+
exports.normaliseToolName = normaliseToolName;
|
|
37
38
|
exports.parseModel = parseModel;
|
|
39
|
+
exports.parseAgentHooks = parseAgentHooks;
|
|
38
40
|
exports.loadAgentTypes = loadAgentTypes;
|
|
39
41
|
exports.loadAgentTypesWithWarnings = loadAgentTypesWithWarnings;
|
|
40
42
|
exports.knownToolNames = knownToolNames;
|
|
41
43
|
exports.builtinAgents = builtinAgents;
|
|
42
44
|
exports.summariseAgents = summariseAgents;
|
|
43
45
|
exports.findAgentType = findAgentType;
|
|
46
|
+
const miniYaml_1 = require("../util/miniYaml");
|
|
44
47
|
const fs = __importStar(require("fs"));
|
|
45
48
|
const path = __importStar(require("path"));
|
|
46
49
|
const os = __importStar(require("os"));
|
|
@@ -62,7 +65,7 @@ function parseMemoryScope(v) {
|
|
|
62
65
|
// capability on whichever client it runs.
|
|
63
66
|
//
|
|
64
67
|
// This exists because it was previously inlined per agent, and the one agent that
|
|
65
|
-
// had it
|
|
68
|
+
// had it listed five VS Code-only tools, leaving that agent with just five
|
|
66
69
|
// usable tools when run from the CLI — a silent capability gap that is very easy
|
|
67
70
|
// to reintroduce by copy-pasting a list.
|
|
68
71
|
const READ_ONLY_TOOLS = [
|
|
@@ -79,7 +82,7 @@ const READ_ONLY_TOOLS = [
|
|
|
79
82
|
// edit Z directly" does better work, and withholding it meant every sub-agent
|
|
80
83
|
// rediscovered project conventions from scratch. Writing is a different thing
|
|
81
84
|
// entirely — memory is durable, cross-session, SHARED state, while sub-agents run
|
|
82
|
-
// up to
|
|
85
|
+
// up to 10-wide (configurable) in parallel with their reasoning hidden from the user. A wrong fact
|
|
83
86
|
// written there is invisible and permanent, and it competes for a capped, shared
|
|
84
87
|
// budget that gets periodically condensed.
|
|
85
88
|
//
|
|
@@ -107,19 +110,9 @@ const READ_ONLY_TOOLS = [
|
|
|
107
110
|
* without it can still search) and denies nothing. Naming the absence is the
|
|
108
111
|
* only way to stop it being "helpfully" re-added.
|
|
109
112
|
*/
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const
|
|
113
|
-
...READ_ONLY_TOOLS,
|
|
114
|
-
'write_file', 'edit_file', 'multi_edit', 'create_directory', 'move_file', 'copy_file',
|
|
115
|
-
// notebook_edit is a write tool like any other, and allowsTestOnlyWrite already
|
|
116
|
-
// knows its shape (`source` is cell CONTENT, not a path). Omitting it just meant
|
|
117
|
-
// test-writer silently could not touch notebooks.
|
|
118
|
-
'notebook_edit',
|
|
119
|
-
// Office document generation — same write-access tier as write_file, just a
|
|
120
|
-
// structured content shape instead of raw text (see tools/executor.ts).
|
|
121
|
-
'write_docx', 'write_xlsx', 'write_pptx',
|
|
122
|
-
];
|
|
113
|
+
// web_search is a client-side tool now (executor.ts webSearch), so it must be granted
|
|
114
|
+
// explicitly — planner was told to use it and then refused.
|
|
115
|
+
const RESEARCH_TOOLS = [...READ_ONLY_TOOLS, 'fetch_url', 'web_search'];
|
|
123
116
|
const BUILTIN_AGENTS = [
|
|
124
117
|
{
|
|
125
118
|
// The catch-all, matching Claude Code's `general-purpose`.
|
|
@@ -160,66 +153,6 @@ const BUILTIN_AGENTS = [
|
|
|
160
153
|
'(with file paths), what you ran and its outcome, and anything you deliberately left undone.',
|
|
161
154
|
source: 'builtin',
|
|
162
155
|
},
|
|
163
|
-
{
|
|
164
|
-
name: 'reviewer',
|
|
165
|
-
description: 'Read-only code reviewer — finds correctness bugs, edge cases, and security issues in a diff or file set. Cannot modify files.',
|
|
166
|
-
tools: READ_ONLY_TOOLS,
|
|
167
|
-
source: 'builtin',
|
|
168
|
-
prompt: [
|
|
169
|
-
'You are a meticulous senior code reviewer. You NEVER modify files — you only read, search, and report.',
|
|
170
|
-
'',
|
|
171
|
-
'Method:',
|
|
172
|
-
'1. Read the full context around every change you are asked to review; never judge a hunk in isolation.',
|
|
173
|
-
'2. Hunt specifically for: correctness bugs, unhandled edge cases (empty/null/unicode/concurrency/timezone),',
|
|
174
|
-
' security issues (injection, path traversal, secrets in code, unsafe deserialization), breaking API',
|
|
175
|
-
' changes (search for callers first), silent behaviour changes, and swallowed errors.',
|
|
176
|
-
'3. Verify test coverage: are the changed paths tested? Were assertions weakened or tests deleted?',
|
|
177
|
-
'4. Only use bash for read-only commands (git diff/log/show, grep, test runs). Never run mutating commands.',
|
|
178
|
-
'',
|
|
179
|
-
'Report format: 🔴 Critical / 🟡 Warning / 🟢 Suggestion, each with file:line and a concrete fix,',
|
|
180
|
-
'then a final verdict (APPROVE or REQUEST CHANGES) with a one-paragraph rationale.',
|
|
181
|
-
].join('\n'),
|
|
182
|
-
},
|
|
183
|
-
// Promoted from the security-audit plugin to a builtin.
|
|
184
|
-
//
|
|
185
|
-
// Leaving it plugin-only was indefensible next to `reviewer` being builtin:
|
|
186
|
-
// reviewer's own prompt already tells it to look for security issues, so
|
|
187
|
-
// security IS treated as default work — yet the specialist agent for it was
|
|
188
|
-
// invisible unless the user happened to know the plugin existed. For an agent
|
|
189
|
-
// that WRITES code, "you only get a security review if you knew to install
|
|
190
|
-
// something" is the wrong default.
|
|
191
|
-
{
|
|
192
|
-
name: 'security-auditor',
|
|
193
|
-
description: 'Read-only security auditor — hunts injection, authz, secrets, and validation flaws in a path or diff. Cannot modify files.',
|
|
194
|
-
tools: READ_ONLY_TOOLS,
|
|
195
|
-
// No `model` override — inherits the session's, same as general-purpose.
|
|
196
|
-
// A previous version forced 'pro' (Claude Opus 5) here, which silently
|
|
197
|
-
// billed Anthropic even for a session running entirely on OpenAI/DeepSeek/
|
|
198
|
-
// Qwen. Consistency with the main conversation's provider matters more
|
|
199
|
-
// than defaulting every specialist to a fixed tier.
|
|
200
|
-
source: 'builtin',
|
|
201
|
-
prompt: [
|
|
202
|
-
'You are a security auditor. You find real, exploitable flaws — not style issues.',
|
|
203
|
-
'',
|
|
204
|
-
'Method:',
|
|
205
|
-
'1. Map the attack surface FIRST: entry points (HTTP routes, message handlers, CLI args, file/network',
|
|
206
|
-
' input, deserialization), then trace user-controlled data inward to where it is used.',
|
|
207
|
-
'2. For each finding: file:line, the flaw class, a one-line exploit scenario, and the concrete fix.',
|
|
208
|
-
'3. Grade severity honestly: Critical = remote compromise or data breach; High = auth bypass/IDOR;',
|
|
209
|
-
' Medium = needs unusual preconditions; Low = hardening.',
|
|
210
|
-
'',
|
|
211
|
-
'Classes worth the most attention, in order: injection (SQL/command/template/prototype), broken',
|
|
212
|
-
'authz (missing ownership checks, IDOR, trusting client-supplied ids), secrets committed to source,',
|
|
213
|
-
'path traversal, SSRF, unsafe deserialization, missing rate limits on expensive or auth endpoints,',
|
|
214
|
-
'and crypto misuse (hand-rolled comparison, predictable randomness).',
|
|
215
|
-
'',
|
|
216
|
-
'Hard rules:',
|
|
217
|
-
'- READ-ONLY: never modify, create or delete files. bash only for read-only inspection.',
|
|
218
|
-
'- NEVER print a discovered secret\'s value. Report its location and advise rotation.',
|
|
219
|
-
'- Distinguish EXPLOITABLE from theoretical, and say which one each finding is.',
|
|
220
|
-
'- "No issues found in scope X" is a valid, useful result. Do not pad the report to look thorough.',
|
|
221
|
-
].join('\n'),
|
|
222
|
-
},
|
|
223
156
|
// The gap Claude Code fills with its built-in `Explore`: read-heavy codebase
|
|
224
157
|
// search that would otherwise flood the parent's context. Defaults to the
|
|
225
158
|
// cheapest model on purpose — "find every caller of X" has no need of a
|
|
@@ -231,10 +164,13 @@ const BUILTIN_AGENTS = [
|
|
|
231
164
|
lightPrompt: true,
|
|
232
165
|
description: 'Fast read-only codebase explorer — locates files, symbols, and call sites and reports concise findings. Use to keep bulk searching out of the main context. Cannot modify files.',
|
|
233
166
|
tools: READ_ONLY_TOOLS,
|
|
234
|
-
//
|
|
235
|
-
//
|
|
236
|
-
//
|
|
237
|
-
//
|
|
167
|
+
// The cheapest model of the SESSION'S OWN provider (Opus → Sonnet, GPT-6 Sol →
|
|
168
|
+
// Luna), like Claude Code's Explore on Haiku. A previous version forced 'turbo'
|
|
169
|
+
// (Claude Sonnet 5), which billed Anthropic even for DeepSeek/OpenAI sessions;
|
|
170
|
+
// 'cheapest' never leaves the provider and falls back to inheriting.
|
|
171
|
+
model: 'cheapest',
|
|
172
|
+
// Search-and-report needs breadth, not deep reasoning (Claude Code's Explore runs fast).
|
|
173
|
+
effort: 'low',
|
|
238
174
|
source: 'builtin',
|
|
239
175
|
prompt: [
|
|
240
176
|
'You map code. You NEVER modify anything.',
|
|
@@ -257,10 +193,14 @@ const BUILTIN_AGENTS = [
|
|
|
257
193
|
// quietly become "start editing".
|
|
258
194
|
{
|
|
259
195
|
name: 'planner',
|
|
196
|
+
// Lean prompt, as Claude Code's Plan builtin: it researches and reports, and the MAIN
|
|
197
|
+
// agent (which has nexrall.md and the conversation) judges the plan against project
|
|
198
|
+
// rules. Conventions that must bind the plan belong in the delegation prompt.
|
|
199
|
+
lightPrompt: true,
|
|
260
200
|
description: 'Read-only planning agent — researches a change and returns a concrete step-by-step implementation plan with risks and affected files. Cannot modify files.',
|
|
261
201
|
tools: RESEARCH_TOOLS,
|
|
262
202
|
// No `model` override — inherits the session's, for the same reason as
|
|
263
|
-
// explorer
|
|
203
|
+
// explorer above: a fixed alias silently billed Anthropic
|
|
264
204
|
// regardless of the user's chosen provider.
|
|
265
205
|
source: 'builtin',
|
|
266
206
|
prompt: [
|
|
@@ -281,74 +221,6 @@ const BUILTIN_AGENTS = [
|
|
|
281
221
|
'- Anything genuinely ambiguous, stated as an open question rather than a silent assumption.',
|
|
282
222
|
].join('\n'),
|
|
283
223
|
},
|
|
284
|
-
// Promoted from the test-gen plugin. Needs write access — it produces test
|
|
285
|
-
// files — but is deliberately forbidden from touching source, because "make the
|
|
286
|
-
// tests pass" is the single most common way an agent destroys signal.
|
|
287
|
-
{
|
|
288
|
-
name: 'test-writer',
|
|
289
|
-
description: 'Writes tests that follow the project\'s existing conventions. May create/edit TEST files only — never production source.',
|
|
290
|
-
tools: WRITE_TOOLS,
|
|
291
|
-
// Enforced, not merely requested: the permission gate refuses a write whose
|
|
292
|
-
// path is not a test file. Without this the allowlist would grant edit_file
|
|
293
|
-
// for every path and the rule below would be a suggestion the model is free
|
|
294
|
-
// to rationalise its way past.
|
|
295
|
-
testFilesOnly: true,
|
|
296
|
-
source: 'builtin',
|
|
297
|
-
prompt: [
|
|
298
|
-
'You write tests. You may create and edit TEST files only.',
|
|
299
|
-
'',
|
|
300
|
-
'Hard rules — these are the ways test-writing agents destroy value, so they are non-negotiable:',
|
|
301
|
-
'- NEVER modify production source to make a test pass. If the code looks wrong, REPORT it and stop.',
|
|
302
|
-
'- NEVER weaken, delete or skip an existing assertion or test.',
|
|
303
|
-
'- A test that cannot fail is worse than no test. Every test must be able to fail for one clear reason.',
|
|
304
|
-
'',
|
|
305
|
-
'Method:',
|
|
306
|
-
'1. Read the existing tests FIRST and copy their conventions exactly — runner, file naming, layout,',
|
|
307
|
-
' assertion style, fixture/helper patterns. Never introduce a new framework.',
|
|
308
|
-
'2. Test observable behaviour and the contract, not private internals.',
|
|
309
|
-
'3. Cover the boring-but-real cases: empty input, null/undefined, unicode and non-BMP characters,',
|
|
310
|
-
' boundaries, error paths, concurrency where it applies.',
|
|
311
|
-
'4. No sleeps or wall-clock dependence — those produce the flaky tests that get deleted later.',
|
|
312
|
-
'5. RUN the tests you wrote and report the real output. Never claim a test passes without running it.',
|
|
313
|
-
].join('\n'),
|
|
314
|
-
},
|
|
315
|
-
// The DevOps gap — answered with a READ-ONLY advisor, not an operator.
|
|
316
|
-
//
|
|
317
|
-
// A "DevOps agent" with write/apply access is a genuinely different risk class
|
|
318
|
-
// from the others here: its mistakes are `kubectl delete`, a bad `terraform
|
|
319
|
-
// apply`, a broken deploy pipeline — often not revertible and affecting
|
|
320
|
-
// production rather than a working tree. So this one diagnoses and proposes a
|
|
321
|
-
// diff; a human applies it. That asymmetry is the whole design.
|
|
322
|
-
{
|
|
323
|
-
name: 'devops-advisor',
|
|
324
|
-
description: 'Read-only CI/CD, container, and infrastructure advisor — diagnoses pipelines, Dockerfiles, and k8s manifests and proposes concrete fixes as a diff. Never applies changes.',
|
|
325
|
-
tools: RESEARCH_TOOLS,
|
|
326
|
-
// No `model` override — inherits the session's, for the same reason as the
|
|
327
|
-
// other specialists above.
|
|
328
|
-
source: 'builtin',
|
|
329
|
-
prompt: [
|
|
330
|
-
'You are an infrastructure and delivery advisor. You DIAGNOSE and PROPOSE. You never apply changes.',
|
|
331
|
-
'',
|
|
332
|
-
'Hard rules:',
|
|
333
|
-
'- READ-ONLY, and stricter than the other read-only agents: bash is for INSPECTION only',
|
|
334
|
-
' (git log/diff, cat, grep, `kubectl get/describe`, `docker images`, `terraform plan`).',
|
|
335
|
-
' NEVER run anything that mutates infrastructure — no apply/delete/scale/rollout/restart/push,',
|
|
336
|
-
' no `terraform apply`, no `helm upgrade`. If a fix needs such a command, WRITE IT OUT for a human.',
|
|
337
|
-
'- Never print secret values from env files, k8s Secrets or CI variables. Reference them by name.',
|
|
338
|
-
'',
|
|
339
|
-
'Method:',
|
|
340
|
-
'1. Read what actually exists — workflow files, Dockerfiles, manifests, kustomize overlays, the',
|
|
341
|
-
' deploy scripts — before drawing any conclusion. Never reason from what a stack "usually" looks like.',
|
|
342
|
-
'2. Follow the real path a change takes to production, and name the step that is broken or missing.',
|
|
343
|
-
'3. Check the failure modes that bite hardest: CI path filters that skip files a workload actually',
|
|
344
|
-
' needs, image tags that do not match what is deployed, missing health probes, absent resource',
|
|
345
|
-
' limits, secrets baked into images, ports/timeouts inconsistent between proxy and app, and',
|
|
346
|
-
' migrations that must run before the new image is live.',
|
|
347
|
-
'',
|
|
348
|
-
'Output: the diagnosis, the evidence (file:line or command output), the proposed change as a diff or',
|
|
349
|
-
'exact file content, and the command a human should run to apply and verify it.',
|
|
350
|
-
].join('\n'),
|
|
351
|
-
},
|
|
352
224
|
];
|
|
353
225
|
/**
|
|
354
226
|
* Every tool name a client may offer, for validating a hand-written allowlist.
|
|
@@ -383,7 +255,37 @@ const KNOWN_TOOL_NAMES = new Set([
|
|
|
383
255
|
const KNOWN_META_KEYS = new Set([
|
|
384
256
|
'name', 'description', 'tools', 'model', 'test_files_only', 'testfilesonly',
|
|
385
257
|
'memory',
|
|
258
|
+
// Claude Code-compatible fields (keys arrive lowercased from parseFrontmatter).
|
|
259
|
+
'disallowedtools', 'disallowed_tools', 'maxturns', 'max_turns', 'effort', 'background',
|
|
260
|
+
'isolation', 'permissionmode', 'permission_mode', 'color',
|
|
261
|
+
'skills', 'mcpservers', 'mcp_servers', 'hooks',
|
|
386
262
|
]);
|
|
263
|
+
/** Claude Code fields Nexrall recognises but does not implement — warned about by name. */
|
|
264
|
+
/** Claude Code fields Nexrall recognises but does not implement — warned about by name. */
|
|
265
|
+
const UNSUPPORTED_CC_KEYS = new Set([]);
|
|
266
|
+
const VALID_EFFORTS = ['low', 'medium', 'high', 'xhigh', 'max'];
|
|
267
|
+
/**
|
|
268
|
+
* Claude Code tool names → Nexrall's, so an agent file written for Claude Code works
|
|
269
|
+
* when dropped into .nexrall/agents/ (`tools: Read, Grep, Glob` instead of a typo warning
|
|
270
|
+
* and an agent that silently cannot read). `mcp__server__tool` → `server__tool`.
|
|
271
|
+
*/
|
|
272
|
+
const CLAUDE_CODE_TOOL_ALIASES = {
|
|
273
|
+
read: 'read_file', write: 'write_file', edit: 'edit_file', multiedit: 'multi_edit',
|
|
274
|
+
grep: 'search_files', glob: 'glob', ls: 'list_directory', bash: 'bash',
|
|
275
|
+
bashoutput: 'bash_output', killshell: 'kill_shell', killbash: 'kill_shell',
|
|
276
|
+
webfetch: 'fetch_url', websearch: 'web_search', todowrite: 'todo_write',
|
|
277
|
+
notebookread: 'notebook_read', notebookedit: 'notebook_edit', task: 'task', agent: 'task',
|
|
278
|
+
};
|
|
279
|
+
function normaliseToolName(t) {
|
|
280
|
+
if (t.startsWith('mcp__'))
|
|
281
|
+
return t.slice('mcp__'.length);
|
|
282
|
+
if (/[A-Z]/.test(t)) {
|
|
283
|
+
const alias = CLAUDE_CODE_TOOL_ALIASES[t.toLowerCase()];
|
|
284
|
+
if (alias)
|
|
285
|
+
return alias;
|
|
286
|
+
}
|
|
287
|
+
return t;
|
|
288
|
+
}
|
|
387
289
|
// Models this build knows how to NAME. Used only for the frontmatter warning
|
|
388
290
|
// below, never to reject a value — see parseModel for why an allowlist must not
|
|
389
291
|
// gate what is sent.
|
|
@@ -399,7 +301,7 @@ const KNOWN_META_KEYS = new Set([
|
|
|
399
301
|
// at all. Two independently-maintained model lists are exactly the kind of
|
|
400
302
|
// drift the shared parseFrontmatter migration was meant to end.
|
|
401
303
|
exports.VALID_MODELS = [
|
|
402
|
-
'claude-sonnet-5', 'claude-opus-5-5', 'claude-fable-5-1',
|
|
304
|
+
'claude-sonnet-5-5', 'claude-opus-5-5', 'claude-fable-5-1',
|
|
403
305
|
'gpt-5.4', 'gpt-5.4-mini', 'gpt-4.1',
|
|
404
306
|
// Legacy tier aliases — still accepted, still resolved by the backend.
|
|
405
307
|
'turbo', 'pro', 'ultra', 'fast',
|
|
@@ -433,6 +335,11 @@ function parseModel(v) {
|
|
|
433
335
|
const lower = raw.toLowerCase();
|
|
434
336
|
if (exports.MODEL_TIER_ALIASES.includes(lower))
|
|
435
337
|
return lower;
|
|
338
|
+
// Resolved at dispatch against the parent's model (modelCatalogue.resolveSubAgentModel).
|
|
339
|
+
if (lower === 'inherit')
|
|
340
|
+
return undefined;
|
|
341
|
+
if (lower === 'cheapest')
|
|
342
|
+
return lower;
|
|
436
343
|
return raw;
|
|
437
344
|
}
|
|
438
345
|
/** Parse a frontmatter boolean, accepting the usual truthy spellings. */
|
|
@@ -445,9 +352,19 @@ function parseToolList(v) {
|
|
|
445
352
|
const tools = v
|
|
446
353
|
.replace(/^\[|\]$/g, '') // tolerate [a, b] style
|
|
447
354
|
.split(/[,\s]+/)
|
|
448
|
-
.map((t) => t.trim())
|
|
355
|
+
.map((t) => normaliseToolName(t.trim()))
|
|
449
356
|
.filter(Boolean);
|
|
450
|
-
return tools.length ? tools : undefined;
|
|
357
|
+
return tools.length ? [...new Set(tools)] : undefined;
|
|
358
|
+
}
|
|
359
|
+
function parseNameList(v) {
|
|
360
|
+
if (!v)
|
|
361
|
+
return undefined;
|
|
362
|
+
const names = v.replace(/^\[|\]$/g, '').split(',').map((x) => x.trim().replace(/^['"]|['"]$/g, '')).filter(Boolean);
|
|
363
|
+
return names.length ? [...new Set(names)] : undefined;
|
|
364
|
+
}
|
|
365
|
+
function parsePositiveInt(v) {
|
|
366
|
+
const n = Number((v ?? '').trim());
|
|
367
|
+
return Number.isInteger(n) && n > 0 ? n : undefined;
|
|
451
368
|
}
|
|
452
369
|
function loadDir(dir, source, into, warnings) {
|
|
453
370
|
let entries;
|
|
@@ -487,6 +404,7 @@ function loadDir(dir, source, into, warnings) {
|
|
|
487
404
|
continue;
|
|
488
405
|
const tools = parseToolList(meta.tools);
|
|
489
406
|
let effectiveTools = tools;
|
|
407
|
+
let agentHooks;
|
|
490
408
|
// ── FAIL CLOSED on a definition we could not parse ────────────────────────
|
|
491
409
|
//
|
|
492
410
|
// A file with no `---` frontmatter block used to yield tools === undefined,
|
|
@@ -568,7 +486,44 @@ function loadDir(dir, source, into, warnings) {
|
|
|
568
486
|
message: 'lists `task` as its ONLY tool, so it can delegate but cannot read, write or run anything itself — it has no way to do or verify work. Add the tools it needs, or drop the `tools:` line to inherit its parent\'s.',
|
|
569
487
|
});
|
|
570
488
|
}
|
|
571
|
-
const
|
|
489
|
+
const unsupported = Object.keys(meta).filter((k) => UNSUPPORTED_CC_KEYS.has(k));
|
|
490
|
+
if (unsupported.length) {
|
|
491
|
+
warnings.push({
|
|
492
|
+
file: full,
|
|
493
|
+
agent: name,
|
|
494
|
+
message: `uses Claude Code field(s) Nexrall does not support yet: ${unsupported.join(', ')} — ignored.`,
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
const pm = (meta.permissionmode ?? meta.permission_mode)?.trim();
|
|
498
|
+
if (pm && pm !== 'plan' && pm !== 'default') {
|
|
499
|
+
warnings.push({
|
|
500
|
+
file: full,
|
|
501
|
+
agent: name,
|
|
502
|
+
message: `has permissionMode: "${pm}" — only "plan" (read-only) is honoured; a definition file cannot widen the session's permissions. Ignored.`,
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
const eff = meta.effort?.trim().toLowerCase();
|
|
506
|
+
if (eff && !VALID_EFFORTS.includes(eff)) {
|
|
507
|
+
warnings.push({ file: full, agent: name, message: `has effort: "${meta.effort}" — expected one of ${VALID_EFFORTS.join(', ')}. Ignored.` });
|
|
508
|
+
}
|
|
509
|
+
const mt = meta.maxturns ?? meta.max_turns;
|
|
510
|
+
if (mt !== undefined && parsePositiveInt(mt) === undefined) {
|
|
511
|
+
warnings.push({ file: full, agent: name, message: `has maxTurns: "${mt}" — expected a positive integer. Ignored.` });
|
|
512
|
+
}
|
|
513
|
+
const iso = meta.isolation?.trim().toLowerCase();
|
|
514
|
+
if (iso && iso !== 'worktree') {
|
|
515
|
+
warnings.push({ file: full, agent: name, message: `has isolation: "${meta.isolation}" — only "worktree" is supported. Ignored.` });
|
|
516
|
+
}
|
|
517
|
+
if (meta.hooks !== undefined) {
|
|
518
|
+
const parsed = parseAgentHooks(raw);
|
|
519
|
+
if (parsed.error) {
|
|
520
|
+
warnings.push({ file: full, agent: name, message: `has an unusable hooks: block (${parsed.error}) — no hooks will run for this agent.` });
|
|
521
|
+
}
|
|
522
|
+
else {
|
|
523
|
+
agentHooks = parsed.hooks;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
const strayKeys = Object.keys(meta).filter((k) => !KNOWN_META_KEYS.has(k) && !UNSUPPORTED_CC_KEYS.has(k));
|
|
572
527
|
if (strayKeys.length) {
|
|
573
528
|
warnings.push({
|
|
574
529
|
file: full,
|
|
@@ -602,8 +557,100 @@ function loadDir(dir, source, into, warnings) {
|
|
|
602
557
|
// definition never receives a writable store — should survive parseFrontmatter
|
|
603
558
|
// being changed to salvage partial metadata, which is a plausible future edit.
|
|
604
559
|
...(ok && parseMemoryScope(meta.memory) ? { memory: parseMemoryScope(meta.memory) } : {}),
|
|
560
|
+
// Claude Code-compatible fields. Every one only NARROWS or tunes the agent — none
|
|
561
|
+
// can grant a tool or relax a permission, so an unreadable file (ok=false) simply
|
|
562
|
+
// has none of them.
|
|
563
|
+
...(ok ? parseCcFields(meta) : {}),
|
|
564
|
+
...(ok && agentHooks ? { hooks: agentHooks } : {}),
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Read and validate an agent file's `hooks:` block. Accepts Claude Code's shape —
|
|
570
|
+
* `{ PreToolUse|PostToolUse: [{ matcher?, hooks: [{ type: command, command, timeout? }] }],
|
|
571
|
+
* Stop|SubagentStop: [{ hooks: [...] }] }` — with `timeout` in SECONDS (Claude Code)
|
|
572
|
+
* or `timeout_ms`. Anything else is an error, never a partial guess.
|
|
573
|
+
*/
|
|
574
|
+
function parseAgentHooks(raw) {
|
|
575
|
+
let v;
|
|
576
|
+
try {
|
|
577
|
+
v = (0, miniYaml_1.frontmatterBlock)(raw, 'hooks');
|
|
578
|
+
}
|
|
579
|
+
catch (e) {
|
|
580
|
+
return { error: e.message };
|
|
581
|
+
}
|
|
582
|
+
if (v == null)
|
|
583
|
+
return {};
|
|
584
|
+
if (typeof v !== 'object' || Array.isArray(v))
|
|
585
|
+
return { error: 'expected a map of events' };
|
|
586
|
+
const defs = (list, where) => {
|
|
587
|
+
if (!Array.isArray(list))
|
|
588
|
+
throw new Error(`${where}: expected a list of hooks`);
|
|
589
|
+
return list.map((h, i) => {
|
|
590
|
+
const o = h;
|
|
591
|
+
if (!o || o.type !== 'command' || typeof o.command !== 'string' || !o.command.trim()) {
|
|
592
|
+
throw new Error(`${where}[${i}]: each hook needs type: command and a command`);
|
|
593
|
+
}
|
|
594
|
+
const ms = typeof o.timeout_ms === 'number' ? o.timeout_ms
|
|
595
|
+
: typeof o.timeout === 'number' ? o.timeout * 1000 : undefined;
|
|
596
|
+
return { type: 'command', command: o.command, ...(ms && ms > 0 ? { timeout_ms: ms } : {}) };
|
|
597
|
+
});
|
|
598
|
+
};
|
|
599
|
+
const entries = (list, ev) => {
|
|
600
|
+
if (!Array.isArray(list))
|
|
601
|
+
throw new Error(`${ev}: expected a list`);
|
|
602
|
+
return list.map((e, i) => {
|
|
603
|
+
const o = e;
|
|
604
|
+
if (!o || typeof o !== 'object')
|
|
605
|
+
throw new Error(`${ev}[${i}]: expected { matcher, hooks }`);
|
|
606
|
+
return {
|
|
607
|
+
...(typeof o.matcher === 'string' && o.matcher ? { matcher: o.matcher } : {}),
|
|
608
|
+
hooks: defs(o.hooks, `${ev}[${i}].hooks`),
|
|
609
|
+
};
|
|
605
610
|
});
|
|
611
|
+
};
|
|
612
|
+
try {
|
|
613
|
+
const out = {};
|
|
614
|
+
for (const [ev, val] of Object.entries(v)) {
|
|
615
|
+
if (ev === 'PreToolUse' || ev === 'PostToolUse')
|
|
616
|
+
out[ev] = entries(val, ev);
|
|
617
|
+
else if (ev === 'Stop' || ev === 'SubagentStop')
|
|
618
|
+
out.Stop = [...(out.Stop ?? []), ...entries(val, ev).flatMap((e) => e.hooks)];
|
|
619
|
+
else
|
|
620
|
+
throw new Error(`unsupported event "${ev}" (supported: PreToolUse, PostToolUse, Stop)`);
|
|
621
|
+
}
|
|
622
|
+
return { hooks: out };
|
|
606
623
|
}
|
|
624
|
+
catch (e) {
|
|
625
|
+
return { error: e.message };
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
function parseCcFields(meta) {
|
|
629
|
+
const out = {};
|
|
630
|
+
const disallowed = parseToolList(meta.disallowedtools ?? meta.disallowed_tools);
|
|
631
|
+
if (disallowed)
|
|
632
|
+
out.disallowedTools = disallowed;
|
|
633
|
+
const mt = parsePositiveInt(meta.maxturns ?? meta.max_turns);
|
|
634
|
+
if (mt)
|
|
635
|
+
out.maxTurns = mt;
|
|
636
|
+
const eff = meta.effort?.trim().toLowerCase();
|
|
637
|
+
if (eff && VALID_EFFORTS.includes(eff))
|
|
638
|
+
out.effort = eff;
|
|
639
|
+
if (parseBool(meta.background))
|
|
640
|
+
out.background = true;
|
|
641
|
+
if (meta.isolation?.trim().toLowerCase() === 'worktree')
|
|
642
|
+
out.isolation = 'worktree';
|
|
643
|
+
if ((meta.permissionmode ?? meta.permission_mode)?.trim() === 'plan')
|
|
644
|
+
out.permissionMode = 'plan';
|
|
645
|
+
if (meta.color?.trim())
|
|
646
|
+
out.color = meta.color.trim();
|
|
647
|
+
const skills = parseNameList(meta.skills);
|
|
648
|
+
if (skills)
|
|
649
|
+
out.skills = skills;
|
|
650
|
+
const servers = parseNameList(meta.mcpservers ?? meta.mcp_servers);
|
|
651
|
+
if (servers)
|
|
652
|
+
out.mcpServers = servers;
|
|
653
|
+
return out;
|
|
607
654
|
}
|
|
608
655
|
/**
|
|
609
656
|
* Discover all agent types. Precedence: project > global > plugin > builtin.
|
|
@@ -697,7 +744,7 @@ function summariseAgents(types) {
|
|
|
697
744
|
const access = t.testFilesOnly
|
|
698
745
|
? 'writes TEST files only'
|
|
699
746
|
: canWrite ? 'can modify files' : 'read-only';
|
|
700
|
-
const model = t.model ? `, ${t.model} model` : '';
|
|
747
|
+
const model = t.model === 'cheapest' ? ', fast low-cost model' : t.model ? `, ${t.model} model` : '';
|
|
701
748
|
return `- ${t.name} (${access}${model}): ${t.description}`;
|
|
702
749
|
})
|
|
703
750
|
.join('\n');
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { ToolResult } from '../types';
|
|
2
|
+
export type BackgroundStatus = 'running' | 'completed' | 'failed' | 'stopped';
|
|
3
|
+
export interface BackgroundAgentInfo {
|
|
4
|
+
id: string;
|
|
5
|
+
description: string;
|
|
6
|
+
agentType: string;
|
|
7
|
+
status: BackgroundStatus;
|
|
8
|
+
startedAt: number;
|
|
9
|
+
finishedAt?: number;
|
|
10
|
+
toolCalls: number;
|
|
11
|
+
lastTool?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface BackgroundNotification {
|
|
14
|
+
id: string;
|
|
15
|
+
description: string;
|
|
16
|
+
agentType: string;
|
|
17
|
+
status: Exclude<BackgroundStatus, 'running'>;
|
|
18
|
+
durationMs: number;
|
|
19
|
+
toolCalls: number;
|
|
20
|
+
/** The sub-agent's final report (or error + salvaged partial work). */
|
|
21
|
+
text: string;
|
|
22
|
+
}
|
|
23
|
+
/** Hard ceiling on simultaneously running background agents per session. */
|
|
24
|
+
export declare const MAX_BACKGROUND_AGENTS = 8;
|
|
25
|
+
export declare class BackgroundAgentHub {
|
|
26
|
+
private readonly entries;
|
|
27
|
+
private readonly pending;
|
|
28
|
+
private readonly listeners;
|
|
29
|
+
private counter;
|
|
30
|
+
/**
|
|
31
|
+
* Start `run` detached. `run` receives this agent's own abort signal and progress
|
|
32
|
+
* hooks, and must resolve (never reject) with the sub-agent's ToolResult.
|
|
33
|
+
*/
|
|
34
|
+
start(meta: {
|
|
35
|
+
description: string;
|
|
36
|
+
agentType: string;
|
|
37
|
+
}, run: (ctx: {
|
|
38
|
+
abort: {
|
|
39
|
+
aborted: boolean;
|
|
40
|
+
};
|
|
41
|
+
onToolUse: (name: string) => void;
|
|
42
|
+
}) => Promise<ToolResult>): {
|
|
43
|
+
id: string;
|
|
44
|
+
} | {
|
|
45
|
+
error: string;
|
|
46
|
+
};
|
|
47
|
+
private finish;
|
|
48
|
+
/** Running agents, oldest first. */
|
|
49
|
+
list(): BackgroundAgentInfo[];
|
|
50
|
+
runningCount(): number;
|
|
51
|
+
hasPending(): boolean;
|
|
52
|
+
/** Remove and return every undelivered completion. */
|
|
53
|
+
takeNotifications(): BackgroundNotification[];
|
|
54
|
+
/** Ask one agent to stop. Its (partial) result is still delivered as a notification. */
|
|
55
|
+
stop(id: string): boolean;
|
|
56
|
+
/** Stop everything and drop undelivered notifications (session cleared / closed). */
|
|
57
|
+
stopAll(opts?: {
|
|
58
|
+
dropPending?: boolean;
|
|
59
|
+
}): void;
|
|
60
|
+
/** Subscribe to any change (start, progress, finish, delivery). Returns an unsubscribe. */
|
|
61
|
+
onChange(cb: () => void): () => void;
|
|
62
|
+
private emit;
|
|
63
|
+
}
|
|
64
|
+
/** The text block a notification becomes in the model's context. */
|
|
65
|
+
export declare function formatBackgroundNotifications(list: BackgroundNotification[]): string;
|
|
66
|
+
//# sourceMappingURL=backgroundAgents.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backgroundAgents.d.ts","sourceRoot":"","sources":["../../src/agent/backgroundAgents.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3C,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE9E,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,gBAAgB,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC,gBAAgB,EAAE,SAAS,CAAC,CAAC;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,uEAAuE;IACvE,IAAI,EAAE,MAAM,CAAC;CACd;AAMD,4EAA4E;AAC5E,eAAO,MAAM,qBAAqB,IAAI,CAAC;AAEvC,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA4B;IACpD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAgC;IACxD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAyB;IACnD,OAAO,CAAC,OAAO,CAAK;IAEpB;;;OAGG;IACH,KAAK,CACH,IAAI,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,EAChD,GAAG,EAAE,CAAC,GAAG,EAAE;QAAE,KAAK,EAAE;YAAE,OAAO,EAAE,OAAO,CAAA;SAAE,CAAC;QAAC,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,KAAK,OAAO,CAAC,UAAU,CAAC,GACpG;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE;IA4BrC,OAAO,CAAC,MAAM;IAqBd,oCAAoC;IACpC,IAAI,IAAI,mBAAmB,EAAE;IAM7B,YAAY,IAAI,MAAM;IAMtB,UAAU,IAAI,OAAO;IAErB,sDAAsD;IACtD,iBAAiB,IAAI,sBAAsB,EAAE;IAM7C,wFAAwF;IACxF,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAOzB,qFAAqF;IACrF,OAAO,CAAC,IAAI,GAAE;QAAE,WAAW,CAAC,EAAE,OAAO,CAAA;KAAO,GAAG,IAAI;IAMnD,2FAA2F;IAC3F,QAAQ,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI;IAKpC,OAAO,CAAC,IAAI;CAKb;AAOD,oEAAoE;AACpE,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,sBAAsB,EAAE,GAAG,MAAM,CAYpF"}
|