@nexrall/code-core 1.4.63 → 1.4.65
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/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 +51 -1
- package/dist/agent/agentTypes.d.ts.map +1 -1
- package/dist/agent/agentTypes.js +194 -11
- 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 +43 -6
- package/dist/agent/loop.d.ts.map +1 -1
- package/dist/agent/loop.js +942 -259
- 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/readDedupe.d.ts +15 -0
- package/dist/agent/readDedupe.d.ts.map +1 -0
- package/dist/agent/readDedupe.js +146 -0
- 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 +14 -0
- 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/modePolicy.d.ts +10 -0
- package/dist/permissions/modePolicy.d.ts.map +1 -1
- package/dist/permissions/modePolicy.js +11 -0
- 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 +99 -5
- 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 +8 -17
|
@@ -44,9 +44,17 @@ export interface StoredAgent {
|
|
|
44
44
|
seq: number;
|
|
45
45
|
/** Rough JSON size, cached so the byte cap doesn't re-serialise on every write. */
|
|
46
46
|
bytes: number;
|
|
47
|
+
/**
|
|
48
|
+
* The conversation (AgentLoopOptions.sessionId) that created it. Every main agent's
|
|
49
|
+
* scope is 'root', so without this any chat in a shared process (VS Code panels,
|
|
50
|
+
* desktop tasks) could resume another chat's sub-agents.
|
|
51
|
+
*/
|
|
52
|
+
sessionKey?: string;
|
|
47
53
|
}
|
|
54
|
+
export declare function enableAgentTranscriptPersistence(dir?: string): void;
|
|
55
|
+
export declare function disableAgentTranscriptPersistence(): void;
|
|
48
56
|
/** Store a finished sub-agent's transcript. Returns its resumable id. */
|
|
49
|
-
export declare function rememberAgent(agentName: string | null, description: string, messages: Message[], ownerScope?: string): string;
|
|
57
|
+
export declare function rememberAgent(agentName: string | null, description: string, messages: Message[], ownerScope?: string, sessionKey?: string): string;
|
|
50
58
|
/**
|
|
51
59
|
* May `scope` resume/modify the agent stored under `id`?
|
|
52
60
|
*
|
|
@@ -55,10 +63,13 @@ export declare function rememberAgent(agentName: string | null, description: str
|
|
|
55
63
|
* and inventing a prefix convention to fake it would be a second guessable capability.
|
|
56
64
|
* The main agent is included because it owns the session and already sees every result.
|
|
57
65
|
*/
|
|
58
|
-
export declare function canResume(stored: StoredAgent, scope: string): boolean;
|
|
66
|
+
export declare function canResume(stored: StoredAgent, scope: string, sessionKey?: string): boolean;
|
|
59
67
|
/** Append a resumed run's new messages to an existing agent. */
|
|
60
|
-
export declare function updateAgent(id: string, messages: Message[], scope?: string): void;
|
|
68
|
+
export declare function updateAgent(id: string, messages: Message[], scope?: string, sessionKey?: string): void;
|
|
61
69
|
export declare function getAgent(id: string): StoredAgent | undefined;
|
|
70
|
+
/** Mark an agent as being resumed. False if another run already holds it. */
|
|
71
|
+
export declare function claimAgentForResume(id: string): boolean;
|
|
72
|
+
export declare function releaseAgentForResume(id: string): void;
|
|
62
73
|
/** Newest first — the order a "which agents can I resume?" list wants. */
|
|
63
74
|
export declare function listAgents(): StoredAgent[];
|
|
64
75
|
/** Test seam. Not exported through index.ts's public surface by intent. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agentRegistry.d.ts","sourceRoot":"","sources":["../../src/agent/agentRegistry.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"agentRegistry.d.ts","sourceRoot":"","sources":["../../src/agent/agentRegistry.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AA8CxC,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX;;;;;;OAMG;IACH,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB;;;;;;;;;;;;;;OAcG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB,2EAA2E;IAC3E,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,iFAAiF;IACjF,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;;;;;OASG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ,mFAAmF;IACnF,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAsDD,wBAAgB,gCAAgC,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAInE;AAED,wBAAgB,iCAAiC,IAAI,IAAI,CAAwB;AAoCjF,yEAAyE;AACzE,wBAAgB,aAAa,CAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,OAAO,EAAE,EACnB,UAAU,SAAS,EACnB,UAAU,CAAC,EAAE,MAAM,GAClB,MAAM,CAoBR;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAG1F;AAED,gEAAgE;AAChE,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,KAAK,SAAS,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAiBtG;AAED,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAU5D;AAMD,6EAA6E;AAC7E,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAIvD;AACD,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAuB;AAE9E,0EAA0E;AAC1E,wBAAgB,UAAU,IAAI,WAAW,EAAE,CAE1C;AAED,2EAA2E;AAC3E,wBAAgB,mBAAmB,IAAI,IAAI,CAI1C;AAED,eAAO,MAAM,OAAO;;;CAAkC,CAAC"}
|
|
@@ -1,13 +1,53 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports._limits = void 0;
|
|
37
|
+
exports.enableAgentTranscriptPersistence = enableAgentTranscriptPersistence;
|
|
38
|
+
exports.disableAgentTranscriptPersistence = disableAgentTranscriptPersistence;
|
|
4
39
|
exports.rememberAgent = rememberAgent;
|
|
5
40
|
exports.canResume = canResume;
|
|
6
41
|
exports.updateAgent = updateAgent;
|
|
7
42
|
exports.getAgent = getAgent;
|
|
43
|
+
exports.claimAgentForResume = claimAgentForResume;
|
|
44
|
+
exports.releaseAgentForResume = releaseAgentForResume;
|
|
8
45
|
exports.listAgents = listAgents;
|
|
9
46
|
exports._resetAgentRegistry = _resetAgentRegistry;
|
|
10
47
|
const node_crypto_1 = require("node:crypto");
|
|
48
|
+
const fs = __importStar(require("node:fs"));
|
|
49
|
+
const os = __importStar(require("node:os"));
|
|
50
|
+
const path = __importStar(require("node:path"));
|
|
11
51
|
// ─── Resumable sub-agents ────────────────────────────────────────────────────
|
|
12
52
|
//
|
|
13
53
|
// A sub-agent normally starts with a blank slate: fresh context, no memory of a
|
|
@@ -91,8 +131,72 @@ function sizeOf(messages) {
|
|
|
91
131
|
return MAX_TOTAL_BYTES;
|
|
92
132
|
}
|
|
93
133
|
}
|
|
134
|
+
// ── Disk persistence (opt-in, per host) ─────────────────────────────────────────
|
|
135
|
+
//
|
|
136
|
+
// Claude Code keeps sub-agent transcripts on disk, so "continue that agent" survives a
|
|
137
|
+
// restart. Hosts opt in with enableAgentTranscriptPersistence() at startup; library
|
|
138
|
+
// callers and tests stay purely in-memory. Ids carry 32 random bits, so knowing one is
|
|
139
|
+
// the capability (the model only ever learns ids from its own tool results).
|
|
140
|
+
let _persistDir = null;
|
|
141
|
+
const PERSIST_MAX_FILES = 100;
|
|
142
|
+
const PERSIST_MAX_AGE_MS = 14 * 24 * 60 * 60 * 1000;
|
|
143
|
+
function enableAgentTranscriptPersistence(dir) {
|
|
144
|
+
_persistDir = dir ?? process.env.NEXRALL_AGENT_TRANSCRIPTS_DIR ?? path.join(os.homedir(), '.nexrall', 'agent-transcripts');
|
|
145
|
+
try {
|
|
146
|
+
fs.mkdirSync(_persistDir, { recursive: true, mode: 0o700 });
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
_persistDir = null;
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
prunePersisted();
|
|
153
|
+
}
|
|
154
|
+
function disableAgentTranscriptPersistence() { _persistDir = null; }
|
|
155
|
+
function persistedPath(id) {
|
|
156
|
+
if (!_persistDir || !/^agent_\d+_[0-9a-f]{8}$/.test(id))
|
|
157
|
+
return null;
|
|
158
|
+
return path.join(_persistDir, `${id}.json`);
|
|
159
|
+
}
|
|
160
|
+
function persist(agent) {
|
|
161
|
+
const p = persistedPath(agent.id);
|
|
162
|
+
if (!p)
|
|
163
|
+
return;
|
|
164
|
+
try {
|
|
165
|
+
fs.writeFileSync(p, JSON.stringify(agent), { mode: 0o600 });
|
|
166
|
+
}
|
|
167
|
+
catch { /* best effort */ }
|
|
168
|
+
}
|
|
169
|
+
function loadPersisted(id) {
|
|
170
|
+
const p = persistedPath(id);
|
|
171
|
+
if (!p)
|
|
172
|
+
return undefined;
|
|
173
|
+
try {
|
|
174
|
+
const a = JSON.parse(fs.readFileSync(p, 'utf-8'));
|
|
175
|
+
if (a?.id !== id || !Array.isArray(a.messages))
|
|
176
|
+
return undefined;
|
|
177
|
+
// A restarted process has fresh scope counters, so only the main agent ('root')
|
|
178
|
+
// may resume a transcript from a previous process.
|
|
179
|
+
return { ...a, ownerScope: 'root', seq: ++_seq };
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function prunePersisted() {
|
|
186
|
+
if (!_persistDir)
|
|
187
|
+
return;
|
|
188
|
+
try {
|
|
189
|
+
const files = fs.readdirSync(_persistDir).filter((f) => f.endsWith('.json'))
|
|
190
|
+
.map((f) => { const full = path.join(_persistDir, f); return { full, t: fs.statSync(full).mtimeMs }; })
|
|
191
|
+
.sort((a, b) => b.t - a.t);
|
|
192
|
+
const now = Date.now();
|
|
193
|
+
files.forEach((f, i) => { if (i >= PERSIST_MAX_FILES || now - f.t > PERSIST_MAX_AGE_MS)
|
|
194
|
+
fs.rmSync(f.full, { force: true }); });
|
|
195
|
+
}
|
|
196
|
+
catch { /* best effort */ }
|
|
197
|
+
}
|
|
94
198
|
/** Store a finished sub-agent's transcript. Returns its resumable id. */
|
|
95
|
-
function rememberAgent(agentName, description, messages, ownerScope = 'root') {
|
|
199
|
+
function rememberAgent(agentName, description, messages, ownerScope = 'root', sessionKey) {
|
|
96
200
|
// Unguessable, so an id cannot be ENUMERATED even before the ownership check runs.
|
|
97
201
|
// Defence in depth only — randomness is not authorisation, `canResume` is.
|
|
98
202
|
const id = `agent_${++_counter}_${(0, node_crypto_1.randomUUID)().slice(0, 8)}`;
|
|
@@ -107,7 +211,9 @@ function rememberAgent(agentName, description, messages, ownerScope = 'root') {
|
|
|
107
211
|
updatedAt: now,
|
|
108
212
|
seq: ++_seq,
|
|
109
213
|
bytes: sizeOf(messages),
|
|
214
|
+
...(sessionKey ? { sessionKey } : {}),
|
|
110
215
|
});
|
|
216
|
+
persist(_agents.get(id));
|
|
111
217
|
evictUntilWithinLimits();
|
|
112
218
|
return id;
|
|
113
219
|
}
|
|
@@ -119,18 +225,20 @@ function rememberAgent(agentName, description, messages, ownerScope = 'root') {
|
|
|
119
225
|
* and inventing a prefix convention to fake it would be a second guessable capability.
|
|
120
226
|
* The main agent is included because it owns the session and already sees every result.
|
|
121
227
|
*/
|
|
122
|
-
function canResume(stored, scope) {
|
|
228
|
+
function canResume(stored, scope, sessionKey) {
|
|
229
|
+
if (stored.sessionKey && sessionKey && stored.sessionKey !== sessionKey)
|
|
230
|
+
return false;
|
|
123
231
|
return stored.ownerScope === scope || scope === 'root';
|
|
124
232
|
}
|
|
125
233
|
/** Append a resumed run's new messages to an existing agent. */
|
|
126
|
-
function updateAgent(id, messages, scope = 'root') {
|
|
127
|
-
const existing =
|
|
234
|
+
function updateAgent(id, messages, scope = 'root', sessionKey) {
|
|
235
|
+
const existing = getAgent(id);
|
|
128
236
|
if (!existing)
|
|
129
237
|
return;
|
|
130
238
|
// Same authorisation as reading. Without it a low-privilege descendant could overwrite a
|
|
131
239
|
// privileged agent's transcript while its `agentName` stayed unchanged — indirect prompt
|
|
132
240
|
// injection into whatever frame resumes it next.
|
|
133
|
-
if (!canResume(existing, scope))
|
|
241
|
+
if (!canResume(existing, scope, sessionKey))
|
|
134
242
|
return;
|
|
135
243
|
existing.messages = messages;
|
|
136
244
|
existing.updatedAt = Date.now();
|
|
@@ -140,11 +248,33 @@ function updateAgent(id, messages, scope = 'root') {
|
|
|
140
248
|
// actively-continued agent from being evicted by a burst of new one-shots.
|
|
141
249
|
_agents.delete(id);
|
|
142
250
|
_agents.set(id, existing);
|
|
251
|
+
persist(existing);
|
|
143
252
|
evictUntilWithinLimits();
|
|
144
253
|
}
|
|
145
254
|
function getAgent(id) {
|
|
146
|
-
|
|
255
|
+
const live = _agents.get(id);
|
|
256
|
+
if (live)
|
|
257
|
+
return live;
|
|
258
|
+
// Evicted from memory or from a previous process — fall back to disk.
|
|
259
|
+
const fromDisk = loadPersisted(id);
|
|
260
|
+
if (fromDisk) {
|
|
261
|
+
_agents.set(id, fromDisk);
|
|
262
|
+
evictUntilWithinLimits();
|
|
263
|
+
}
|
|
264
|
+
return fromDisk;
|
|
265
|
+
}
|
|
266
|
+
// Ids currently being resumed. Two `task` calls in one message resuming the SAME agent
|
|
267
|
+
// would both start from the same transcript and the last to finish would silently
|
|
268
|
+
// overwrite the other's work (updateAgent is last-write-wins).
|
|
269
|
+
const _inUse = new Set();
|
|
270
|
+
/** Mark an agent as being resumed. False if another run already holds it. */
|
|
271
|
+
function claimAgentForResume(id) {
|
|
272
|
+
if (_inUse.has(id))
|
|
273
|
+
return false;
|
|
274
|
+
_inUse.add(id);
|
|
275
|
+
return true;
|
|
147
276
|
}
|
|
277
|
+
function releaseAgentForResume(id) { _inUse.delete(id); }
|
|
148
278
|
/** Newest first — the order a "which agents can I resume?" list wants. */
|
|
149
279
|
function listAgents() {
|
|
150
280
|
return [..._agents.values()].sort((a, b) => b.seq - a.seq);
|
|
@@ -56,10 +56,50 @@ export interface AgentType {
|
|
|
56
56
|
* — a reviewer or test-writer needs the project's rules, an explorer does not.
|
|
57
57
|
*/
|
|
58
58
|
lightPrompt?: boolean;
|
|
59
|
+
/** Tools this agent may NOT use, on top of (or instead of) an allowlist. Claude Code's `disallowedTools`. */
|
|
60
|
+
disallowedTools?: string[];
|
|
61
|
+
/** Model round-trips before the run stops and reports back (Claude Code's `maxTurns`). */
|
|
62
|
+
maxTurns?: number;
|
|
63
|
+
/** Reasoning effort for this agent's requests (low | medium | high | xhigh | max). Default: the session's. */
|
|
64
|
+
effort?: string;
|
|
65
|
+
/** Always run this agent in the background (the `task` call returns immediately). */
|
|
66
|
+
background?: boolean;
|
|
67
|
+
/** `worktree` — run in a fresh isolated worktree; removed afterwards if it made no changes. */
|
|
68
|
+
isolation?: 'worktree';
|
|
69
|
+
/** `plan` — read-only lock for this agent regardless of the session mode. Only narrowing modes are honoured. */
|
|
70
|
+
permissionMode?: 'plan';
|
|
71
|
+
/** Display colour hint for UIs. */
|
|
72
|
+
color?: string;
|
|
73
|
+
/** Skills whose instructions are preloaded into this agent's prompt (Claude Code's `skills`). */
|
|
74
|
+
skills?: string[];
|
|
75
|
+
/** MCP servers this agent may use; others' tools are neither sent nor allowed (Claude Code's `mcpServers`). */
|
|
76
|
+
mcpServers?: string[];
|
|
77
|
+
/**
|
|
78
|
+
* Hooks active only while THIS agent runs (Claude Code's agent-scoped `hooks`).
|
|
79
|
+
* Same execution model and trust gate as settings.json hooks — see trust.ts, which
|
|
80
|
+
* lists an agent file declaring hooks as a trust-sensitive file.
|
|
81
|
+
*/
|
|
82
|
+
hooks?: AgentHooks;
|
|
83
|
+
}
|
|
84
|
+
export interface AgentHookDef {
|
|
85
|
+
type: 'command';
|
|
86
|
+
command: string;
|
|
87
|
+
timeout_ms?: number;
|
|
88
|
+
}
|
|
89
|
+
export interface AgentHookEntry {
|
|
90
|
+
matcher?: string;
|
|
91
|
+
hooks: AgentHookDef[];
|
|
92
|
+
}
|
|
93
|
+
export interface AgentHooks {
|
|
94
|
+
PreToolUse?: AgentHookEntry[];
|
|
95
|
+
PostToolUse?: AgentHookEntry[];
|
|
96
|
+
/** When this agent finishes (Claude Code: `Stop` in an agent file == SubagentStop). */
|
|
97
|
+
Stop?: AgentHookDef[];
|
|
59
98
|
}
|
|
60
99
|
/** Where a sub-agent's own persistent notes live. */
|
|
61
100
|
export type AgentMemoryScope = 'project' | 'user' | 'local';
|
|
62
|
-
export declare
|
|
101
|
+
export declare function normaliseToolName(t: string): string;
|
|
102
|
+
export declare const VALID_MODELS: readonly ["claude-sonnet-5-5", "claude-opus-5-5", "claude-fable-5-1", "gpt-5.4", "gpt-5.4-mini", "gpt-4.1", "turbo", "pro", "ultra", "fast"];
|
|
63
103
|
/** The four tier aliases specifically (a subset of VALID_MODELS) — the only values `AgentType.model`/`Skill.model` narrow to when recognised; a real model id passes through as a plain string instead. */
|
|
64
104
|
export declare const MODEL_TIER_ALIASES: readonly ["turbo", "pro", "ultra", "fast"];
|
|
65
105
|
export type ModelTier = (typeof MODEL_TIER_ALIASES)[number];
|
|
@@ -94,6 +134,16 @@ export interface AgentWarning {
|
|
|
94
134
|
* happen to contain uppercase.
|
|
95
135
|
*/
|
|
96
136
|
export declare function parseModel(v: string | undefined): string | undefined;
|
|
137
|
+
/**
|
|
138
|
+
* Read and validate an agent file's `hooks:` block. Accepts Claude Code's shape —
|
|
139
|
+
* `{ PreToolUse|PostToolUse: [{ matcher?, hooks: [{ type: command, command, timeout? }] }],
|
|
140
|
+
* Stop|SubagentStop: [{ hooks: [...] }] }` — with `timeout` in SECONDS (Claude Code)
|
|
141
|
+
* or `timeout_ms`. Anything else is an error, never a partial guess.
|
|
142
|
+
*/
|
|
143
|
+
export declare function parseAgentHooks(raw: string): {
|
|
144
|
+
hooks?: AgentHooks;
|
|
145
|
+
error?: string;
|
|
146
|
+
};
|
|
97
147
|
/**
|
|
98
148
|
* Discover all agent types. Precedence: project > global > plugin > builtin.
|
|
99
149
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agentTypes.d.ts","sourceRoot":"","sources":["../../src/agent/agentTypes.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"agentTypes.d.ts","sourceRoot":"","sources":["../../src/agent/agentTypes.ts"],"names":[],"mappings":"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"}
|
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"));
|
|
@@ -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,7 +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
|
-
|
|
113
|
+
// web_search is a client-side tool now (executor.ts webSearch), so it must be granted
|
|
114
|
+
// explicitly — planner/devops-advisor were told to use it and then refused.
|
|
115
|
+
const RESEARCH_TOOLS = [...READ_ONLY_TOOLS, 'fetch_url', 'web_search'];
|
|
111
116
|
/** Read-only + the write tools, for agents that produce code. */
|
|
112
117
|
const WRITE_TOOLS = [
|
|
113
118
|
...READ_ONLY_TOOLS,
|
|
@@ -231,10 +236,13 @@ const BUILTIN_AGENTS = [
|
|
|
231
236
|
lightPrompt: true,
|
|
232
237
|
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
238
|
tools: READ_ONLY_TOOLS,
|
|
234
|
-
//
|
|
235
|
-
//
|
|
236
|
-
//
|
|
237
|
-
//
|
|
239
|
+
// The cheapest model of the SESSION'S OWN provider (Opus → Sonnet, GPT-6 Sol →
|
|
240
|
+
// Luna), like Claude Code's Explore on Haiku. A previous version forced 'turbo'
|
|
241
|
+
// (Claude Sonnet 5), which billed Anthropic even for DeepSeek/OpenAI sessions;
|
|
242
|
+
// 'cheapest' never leaves the provider and falls back to inheriting.
|
|
243
|
+
model: 'cheapest',
|
|
244
|
+
// Search-and-report needs breadth, not deep reasoning (Claude Code's Explore runs fast).
|
|
245
|
+
effort: 'low',
|
|
238
246
|
source: 'builtin',
|
|
239
247
|
prompt: [
|
|
240
248
|
'You map code. You NEVER modify anything.',
|
|
@@ -383,7 +391,37 @@ const KNOWN_TOOL_NAMES = new Set([
|
|
|
383
391
|
const KNOWN_META_KEYS = new Set([
|
|
384
392
|
'name', 'description', 'tools', 'model', 'test_files_only', 'testfilesonly',
|
|
385
393
|
'memory',
|
|
394
|
+
// Claude Code-compatible fields (keys arrive lowercased from parseFrontmatter).
|
|
395
|
+
'disallowedtools', 'disallowed_tools', 'maxturns', 'max_turns', 'effort', 'background',
|
|
396
|
+
'isolation', 'permissionmode', 'permission_mode', 'color',
|
|
397
|
+
'skills', 'mcpservers', 'mcp_servers', 'hooks',
|
|
386
398
|
]);
|
|
399
|
+
/** Claude Code fields Nexrall recognises but does not implement — warned about by name. */
|
|
400
|
+
/** Claude Code fields Nexrall recognises but does not implement — warned about by name. */
|
|
401
|
+
const UNSUPPORTED_CC_KEYS = new Set([]);
|
|
402
|
+
const VALID_EFFORTS = ['low', 'medium', 'high', 'xhigh', 'max'];
|
|
403
|
+
/**
|
|
404
|
+
* Claude Code tool names → Nexrall's, so an agent file written for Claude Code works
|
|
405
|
+
* when dropped into .nexrall/agents/ (`tools: Read, Grep, Glob` instead of a typo warning
|
|
406
|
+
* and an agent that silently cannot read). `mcp__server__tool` → `server__tool`.
|
|
407
|
+
*/
|
|
408
|
+
const CLAUDE_CODE_TOOL_ALIASES = {
|
|
409
|
+
read: 'read_file', write: 'write_file', edit: 'edit_file', multiedit: 'multi_edit',
|
|
410
|
+
grep: 'search_files', glob: 'glob', ls: 'list_directory', bash: 'bash',
|
|
411
|
+
bashoutput: 'bash_output', killshell: 'kill_shell', killbash: 'kill_shell',
|
|
412
|
+
webfetch: 'fetch_url', websearch: 'web_search', todowrite: 'todo_write',
|
|
413
|
+
notebookread: 'notebook_read', notebookedit: 'notebook_edit', task: 'task', agent: 'task',
|
|
414
|
+
};
|
|
415
|
+
function normaliseToolName(t) {
|
|
416
|
+
if (t.startsWith('mcp__'))
|
|
417
|
+
return t.slice('mcp__'.length);
|
|
418
|
+
if (/[A-Z]/.test(t)) {
|
|
419
|
+
const alias = CLAUDE_CODE_TOOL_ALIASES[t.toLowerCase()];
|
|
420
|
+
if (alias)
|
|
421
|
+
return alias;
|
|
422
|
+
}
|
|
423
|
+
return t;
|
|
424
|
+
}
|
|
387
425
|
// Models this build knows how to NAME. Used only for the frontmatter warning
|
|
388
426
|
// below, never to reject a value — see parseModel for why an allowlist must not
|
|
389
427
|
// gate what is sent.
|
|
@@ -399,7 +437,7 @@ const KNOWN_META_KEYS = new Set([
|
|
|
399
437
|
// at all. Two independently-maintained model lists are exactly the kind of
|
|
400
438
|
// drift the shared parseFrontmatter migration was meant to end.
|
|
401
439
|
exports.VALID_MODELS = [
|
|
402
|
-
'claude-sonnet-5', 'claude-opus-5-5', 'claude-fable-5-1',
|
|
440
|
+
'claude-sonnet-5-5', 'claude-opus-5-5', 'claude-fable-5-1',
|
|
403
441
|
'gpt-5.4', 'gpt-5.4-mini', 'gpt-4.1',
|
|
404
442
|
// Legacy tier aliases — still accepted, still resolved by the backend.
|
|
405
443
|
'turbo', 'pro', 'ultra', 'fast',
|
|
@@ -433,6 +471,11 @@ function parseModel(v) {
|
|
|
433
471
|
const lower = raw.toLowerCase();
|
|
434
472
|
if (exports.MODEL_TIER_ALIASES.includes(lower))
|
|
435
473
|
return lower;
|
|
474
|
+
// Resolved at dispatch against the parent's model (modelCatalogue.resolveSubAgentModel).
|
|
475
|
+
if (lower === 'inherit')
|
|
476
|
+
return undefined;
|
|
477
|
+
if (lower === 'cheapest')
|
|
478
|
+
return lower;
|
|
436
479
|
return raw;
|
|
437
480
|
}
|
|
438
481
|
/** Parse a frontmatter boolean, accepting the usual truthy spellings. */
|
|
@@ -445,9 +488,19 @@ function parseToolList(v) {
|
|
|
445
488
|
const tools = v
|
|
446
489
|
.replace(/^\[|\]$/g, '') // tolerate [a, b] style
|
|
447
490
|
.split(/[,\s]+/)
|
|
448
|
-
.map((t) => t.trim())
|
|
491
|
+
.map((t) => normaliseToolName(t.trim()))
|
|
449
492
|
.filter(Boolean);
|
|
450
|
-
return tools.length ? tools : undefined;
|
|
493
|
+
return tools.length ? [...new Set(tools)] : undefined;
|
|
494
|
+
}
|
|
495
|
+
function parseNameList(v) {
|
|
496
|
+
if (!v)
|
|
497
|
+
return undefined;
|
|
498
|
+
const names = v.replace(/^\[|\]$/g, '').split(',').map((x) => x.trim().replace(/^['"]|['"]$/g, '')).filter(Boolean);
|
|
499
|
+
return names.length ? [...new Set(names)] : undefined;
|
|
500
|
+
}
|
|
501
|
+
function parsePositiveInt(v) {
|
|
502
|
+
const n = Number((v ?? '').trim());
|
|
503
|
+
return Number.isInteger(n) && n > 0 ? n : undefined;
|
|
451
504
|
}
|
|
452
505
|
function loadDir(dir, source, into, warnings) {
|
|
453
506
|
let entries;
|
|
@@ -487,6 +540,7 @@ function loadDir(dir, source, into, warnings) {
|
|
|
487
540
|
continue;
|
|
488
541
|
const tools = parseToolList(meta.tools);
|
|
489
542
|
let effectiveTools = tools;
|
|
543
|
+
let agentHooks;
|
|
490
544
|
// ── FAIL CLOSED on a definition we could not parse ────────────────────────
|
|
491
545
|
//
|
|
492
546
|
// A file with no `---` frontmatter block used to yield tools === undefined,
|
|
@@ -568,7 +622,44 @@ function loadDir(dir, source, into, warnings) {
|
|
|
568
622
|
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
623
|
});
|
|
570
624
|
}
|
|
571
|
-
const
|
|
625
|
+
const unsupported = Object.keys(meta).filter((k) => UNSUPPORTED_CC_KEYS.has(k));
|
|
626
|
+
if (unsupported.length) {
|
|
627
|
+
warnings.push({
|
|
628
|
+
file: full,
|
|
629
|
+
agent: name,
|
|
630
|
+
message: `uses Claude Code field(s) Nexrall does not support yet: ${unsupported.join(', ')} — ignored.`,
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
const pm = (meta.permissionmode ?? meta.permission_mode)?.trim();
|
|
634
|
+
if (pm && pm !== 'plan' && pm !== 'default') {
|
|
635
|
+
warnings.push({
|
|
636
|
+
file: full,
|
|
637
|
+
agent: name,
|
|
638
|
+
message: `has permissionMode: "${pm}" — only "plan" (read-only) is honoured; a definition file cannot widen the session's permissions. Ignored.`,
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
const eff = meta.effort?.trim().toLowerCase();
|
|
642
|
+
if (eff && !VALID_EFFORTS.includes(eff)) {
|
|
643
|
+
warnings.push({ file: full, agent: name, message: `has effort: "${meta.effort}" — expected one of ${VALID_EFFORTS.join(', ')}. Ignored.` });
|
|
644
|
+
}
|
|
645
|
+
const mt = meta.maxturns ?? meta.max_turns;
|
|
646
|
+
if (mt !== undefined && parsePositiveInt(mt) === undefined) {
|
|
647
|
+
warnings.push({ file: full, agent: name, message: `has maxTurns: "${mt}" — expected a positive integer. Ignored.` });
|
|
648
|
+
}
|
|
649
|
+
const iso = meta.isolation?.trim().toLowerCase();
|
|
650
|
+
if (iso && iso !== 'worktree') {
|
|
651
|
+
warnings.push({ file: full, agent: name, message: `has isolation: "${meta.isolation}" — only "worktree" is supported. Ignored.` });
|
|
652
|
+
}
|
|
653
|
+
if (meta.hooks !== undefined) {
|
|
654
|
+
const parsed = parseAgentHooks(raw);
|
|
655
|
+
if (parsed.error) {
|
|
656
|
+
warnings.push({ file: full, agent: name, message: `has an unusable hooks: block (${parsed.error}) — no hooks will run for this agent.` });
|
|
657
|
+
}
|
|
658
|
+
else {
|
|
659
|
+
agentHooks = parsed.hooks;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
const strayKeys = Object.keys(meta).filter((k) => !KNOWN_META_KEYS.has(k) && !UNSUPPORTED_CC_KEYS.has(k));
|
|
572
663
|
if (strayKeys.length) {
|
|
573
664
|
warnings.push({
|
|
574
665
|
file: full,
|
|
@@ -602,8 +693,100 @@ function loadDir(dir, source, into, warnings) {
|
|
|
602
693
|
// definition never receives a writable store — should survive parseFrontmatter
|
|
603
694
|
// being changed to salvage partial metadata, which is a plausible future edit.
|
|
604
695
|
...(ok && parseMemoryScope(meta.memory) ? { memory: parseMemoryScope(meta.memory) } : {}),
|
|
696
|
+
// Claude Code-compatible fields. Every one only NARROWS or tunes the agent — none
|
|
697
|
+
// can grant a tool or relax a permission, so an unreadable file (ok=false) simply
|
|
698
|
+
// has none of them.
|
|
699
|
+
...(ok ? parseCcFields(meta) : {}),
|
|
700
|
+
...(ok && agentHooks ? { hooks: agentHooks } : {}),
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Read and validate an agent file's `hooks:` block. Accepts Claude Code's shape —
|
|
706
|
+
* `{ PreToolUse|PostToolUse: [{ matcher?, hooks: [{ type: command, command, timeout? }] }],
|
|
707
|
+
* Stop|SubagentStop: [{ hooks: [...] }] }` — with `timeout` in SECONDS (Claude Code)
|
|
708
|
+
* or `timeout_ms`. Anything else is an error, never a partial guess.
|
|
709
|
+
*/
|
|
710
|
+
function parseAgentHooks(raw) {
|
|
711
|
+
let v;
|
|
712
|
+
try {
|
|
713
|
+
v = (0, miniYaml_1.frontmatterBlock)(raw, 'hooks');
|
|
714
|
+
}
|
|
715
|
+
catch (e) {
|
|
716
|
+
return { error: e.message };
|
|
717
|
+
}
|
|
718
|
+
if (v == null)
|
|
719
|
+
return {};
|
|
720
|
+
if (typeof v !== 'object' || Array.isArray(v))
|
|
721
|
+
return { error: 'expected a map of events' };
|
|
722
|
+
const defs = (list, where) => {
|
|
723
|
+
if (!Array.isArray(list))
|
|
724
|
+
throw new Error(`${where}: expected a list of hooks`);
|
|
725
|
+
return list.map((h, i) => {
|
|
726
|
+
const o = h;
|
|
727
|
+
if (!o || o.type !== 'command' || typeof o.command !== 'string' || !o.command.trim()) {
|
|
728
|
+
throw new Error(`${where}[${i}]: each hook needs type: command and a command`);
|
|
729
|
+
}
|
|
730
|
+
const ms = typeof o.timeout_ms === 'number' ? o.timeout_ms
|
|
731
|
+
: typeof o.timeout === 'number' ? o.timeout * 1000 : undefined;
|
|
732
|
+
return { type: 'command', command: o.command, ...(ms && ms > 0 ? { timeout_ms: ms } : {}) };
|
|
733
|
+
});
|
|
734
|
+
};
|
|
735
|
+
const entries = (list, ev) => {
|
|
736
|
+
if (!Array.isArray(list))
|
|
737
|
+
throw new Error(`${ev}: expected a list`);
|
|
738
|
+
return list.map((e, i) => {
|
|
739
|
+
const o = e;
|
|
740
|
+
if (!o || typeof o !== 'object')
|
|
741
|
+
throw new Error(`${ev}[${i}]: expected { matcher, hooks }`);
|
|
742
|
+
return {
|
|
743
|
+
...(typeof o.matcher === 'string' && o.matcher ? { matcher: o.matcher } : {}),
|
|
744
|
+
hooks: defs(o.hooks, `${ev}[${i}].hooks`),
|
|
745
|
+
};
|
|
605
746
|
});
|
|
747
|
+
};
|
|
748
|
+
try {
|
|
749
|
+
const out = {};
|
|
750
|
+
for (const [ev, val] of Object.entries(v)) {
|
|
751
|
+
if (ev === 'PreToolUse' || ev === 'PostToolUse')
|
|
752
|
+
out[ev] = entries(val, ev);
|
|
753
|
+
else if (ev === 'Stop' || ev === 'SubagentStop')
|
|
754
|
+
out.Stop = [...(out.Stop ?? []), ...entries(val, ev).flatMap((e) => e.hooks)];
|
|
755
|
+
else
|
|
756
|
+
throw new Error(`unsupported event "${ev}" (supported: PreToolUse, PostToolUse, Stop)`);
|
|
757
|
+
}
|
|
758
|
+
return { hooks: out };
|
|
606
759
|
}
|
|
760
|
+
catch (e) {
|
|
761
|
+
return { error: e.message };
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
function parseCcFields(meta) {
|
|
765
|
+
const out = {};
|
|
766
|
+
const disallowed = parseToolList(meta.disallowedtools ?? meta.disallowed_tools);
|
|
767
|
+
if (disallowed)
|
|
768
|
+
out.disallowedTools = disallowed;
|
|
769
|
+
const mt = parsePositiveInt(meta.maxturns ?? meta.max_turns);
|
|
770
|
+
if (mt)
|
|
771
|
+
out.maxTurns = mt;
|
|
772
|
+
const eff = meta.effort?.trim().toLowerCase();
|
|
773
|
+
if (eff && VALID_EFFORTS.includes(eff))
|
|
774
|
+
out.effort = eff;
|
|
775
|
+
if (parseBool(meta.background))
|
|
776
|
+
out.background = true;
|
|
777
|
+
if (meta.isolation?.trim().toLowerCase() === 'worktree')
|
|
778
|
+
out.isolation = 'worktree';
|
|
779
|
+
if ((meta.permissionmode ?? meta.permission_mode)?.trim() === 'plan')
|
|
780
|
+
out.permissionMode = 'plan';
|
|
781
|
+
if (meta.color?.trim())
|
|
782
|
+
out.color = meta.color.trim();
|
|
783
|
+
const skills = parseNameList(meta.skills);
|
|
784
|
+
if (skills)
|
|
785
|
+
out.skills = skills;
|
|
786
|
+
const servers = parseNameList(meta.mcpservers ?? meta.mcp_servers);
|
|
787
|
+
if (servers)
|
|
788
|
+
out.mcpServers = servers;
|
|
789
|
+
return out;
|
|
607
790
|
}
|
|
608
791
|
/**
|
|
609
792
|
* Discover all agent types. Precedence: project > global > plugin > builtin.
|
|
@@ -697,7 +880,7 @@ function summariseAgents(types) {
|
|
|
697
880
|
const access = t.testFilesOnly
|
|
698
881
|
? 'writes TEST files only'
|
|
699
882
|
: canWrite ? 'can modify files' : 'read-only';
|
|
700
|
-
const model = t.model ? `, ${t.model} model` : '';
|
|
883
|
+
const model = t.model === 'cheapest' ? ', fast low-cost model' : t.model ? `, ${t.model} model` : '';
|
|
701
884
|
return `- ${t.name} (${access}${model}): ${t.description}`;
|
|
702
885
|
})
|
|
703
886
|
.join('\n');
|