@nexrall/code-core 1.4.12 → 1.4.14
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/loop.d.ts +13 -0
- package/dist/agent/loop.d.ts.map +1 -1
- package/dist/agent/loop.js +204 -16
- package/dist/agent/memory.d.ts +45 -0
- package/dist/agent/memory.d.ts.map +1 -0
- package/dist/agent/memory.js +243 -0
- package/dist/agent/skills.d.ts +35 -0
- package/dist/agent/skills.d.ts.map +1 -0
- package/dist/agent/skills.js +253 -0
- package/dist/api/client.d.ts +33 -1
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +559 -57
- package/dist/commands/loader.d.ts +7 -3
- package/dist/commands/loader.d.ts.map +1 -1
- package/dist/commands/loader.js +13 -6
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/plugins/index.d.ts +1 -1
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/tools/executor.d.ts.map +1 -1
- package/dist/tools/executor.js +46 -23
- package/dist/types.d.ts +111 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +44 -0
- package/package.json +1 -1
|
@@ -0,0 +1,243 @@
|
|
|
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
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.MEMORY_HARD_CAP_BYTES = exports.MEMORY_COMPACT_TRIGGER_BYTES = exports.MEMORY_MAX_BYTES = exports.MEMORY_ENTRY_MAX_CHARS = void 0;
|
|
37
|
+
exports.memoryFilePath = memoryFilePath;
|
|
38
|
+
exports.writeMemory = writeMemory;
|
|
39
|
+
exports.readMemory = readMemory;
|
|
40
|
+
exports.readAllMemory = readAllMemory;
|
|
41
|
+
exports.clearMemory = clearMemory;
|
|
42
|
+
exports.memoryStats = memoryStats;
|
|
43
|
+
exports.compactMemoryIfNeeded = compactMemoryIfNeeded;
|
|
44
|
+
const fs = __importStar(require("fs"));
|
|
45
|
+
const os = __importStar(require("os"));
|
|
46
|
+
const path = __importStar(require("path"));
|
|
47
|
+
const crypto = __importStar(require("crypto"));
|
|
48
|
+
// ─── Persistent agent memory ───────────────────────────────────────────────
|
|
49
|
+
//
|
|
50
|
+
// Two scopes, mirroring nexrall.md's own global/project split:
|
|
51
|
+
// - 'project' (default) — facts specific to the repo currently open
|
|
52
|
+
// (tech stack, build/deploy commands, project-specific conventions).
|
|
53
|
+
// Stored per-workDir so an unrelated project never sees it.
|
|
54
|
+
// - 'global' — facts that hold across EVERY project (communication-style
|
|
55
|
+
// preferences, cross-cutting habits like "always confirm before force-push").
|
|
56
|
+
//
|
|
57
|
+
// Previously everything lived in one un-scoped ~/.nexrall/memory.md, read into
|
|
58
|
+
// EVERY session regardless of which project was open — a fact about one repo's
|
|
59
|
+
// deploy command showed up as context noise in a totally unrelated project, and
|
|
60
|
+
// the single file grew without bound (a real one measured 312KB / ~245 entries
|
|
61
|
+
// after a few weeks, entries averaging ~1000 chars despite the tool's own
|
|
62
|
+
// instruction to keep each one to "1-2 sentences").
|
|
63
|
+
//
|
|
64
|
+
// Fixes applied here (see MEMORY_MAX_BYTES / MEMORY_ENTRY_MAX_CHARS / compaction
|
|
65
|
+
// below): hard per-entry length cap (not just a prompt instruction), hard total
|
|
66
|
+
// file-size cap with LLM-summarizing compaction of the oldest half instead of
|
|
67
|
+
// blind truncation (which would silently discard facts instead of condensing
|
|
68
|
+
// them), and a real file lock around the read-dedupe-write sequence so two
|
|
69
|
+
// concurrent CLI/VS Code sessions can't race into a duplicate entry.
|
|
70
|
+
// NEXRALL_MEMORY_DIR overrides the store root — used by tests to avoid touching
|
|
71
|
+
// the real ~/.nexrall/memory (mirrors CheckpointManager's NEXRALL_CHECKPOINT_DIR).
|
|
72
|
+
const MEMORY_ROOT = process.env.NEXRALL_MEMORY_DIR || path.join(os.homedir(), '.nexrall', 'memory');
|
|
73
|
+
const GLOBAL_FILE = path.join(MEMORY_ROOT, 'global.md');
|
|
74
|
+
/** Per-workDir file, keyed the same way CheckpointManager keys its store
|
|
75
|
+
* (sha1 of the resolved absolute path) so two differently-named checkouts of
|
|
76
|
+
* the same repo don't collide, and the same repo always maps to the same file
|
|
77
|
+
* regardless of cwd casing/trailing-slash quirks. */
|
|
78
|
+
function memoryFilePath(scope, workDir) {
|
|
79
|
+
if (scope === 'global' || !workDir)
|
|
80
|
+
return GLOBAL_FILE;
|
|
81
|
+
const key = crypto.createHash('sha1').update(path.resolve(workDir)).digest('hex').slice(0, 16);
|
|
82
|
+
return path.join(MEMORY_ROOT, `project-${key}.md`);
|
|
83
|
+
}
|
|
84
|
+
exports.MEMORY_ENTRY_MAX_CHARS = 400; // hard cap per entry — the "1-2 sentences" instruction is a request, this is the backstop
|
|
85
|
+
exports.MEMORY_MAX_BYTES = 12000; // ~3k tokens — target size compaction summarizes DOWN TO
|
|
86
|
+
exports.MEMORY_COMPACT_TRIGGER_BYTES = 16000; // LLM compaction (see compactMemoryIfNeeded) fires once past this
|
|
87
|
+
// Emergency-only synchronous fallback inside writeMemory itself, MUCH higher than the compact
|
|
88
|
+
// trigger — gives compactMemoryIfNeeded plenty of headroom to run (it's called opportunistically,
|
|
89
|
+
// fire-and-forget, from loop.ts right after a memory_write) before this ever kicks in. Only bites
|
|
90
|
+
// if compaction has been unavailable/failing for a while (e.g. offline, API errors), so the file
|
|
91
|
+
// still can't grow completely unbounded even in that worst case.
|
|
92
|
+
exports.MEMORY_HARD_CAP_BYTES = 4 * exports.MEMORY_COMPACT_TRIGGER_BYTES;
|
|
93
|
+
const FINGERPRINT_LEN = 60; // near-duplicate heuristic: same as before, first N chars, case-insensitive
|
|
94
|
+
// ─── Simple per-file async lock ────────────────────────────────────────────
|
|
95
|
+
// Fixes the read→dedupe-check→append race: two concurrent memoryWrite() calls
|
|
96
|
+
// (two CLI processes, or a sub-agent + the main agent) could both read "fact
|
|
97
|
+
// not present yet" before either had written, and both append duplicates.
|
|
98
|
+
// In-process locking is enough for the common case (same CLI/VS Code process);
|
|
99
|
+
// cross-process races are rarer and, worst case, produce a duplicate line that
|
|
100
|
+
// the NEXT memory_write's dedupe check or a compaction pass will clean up.
|
|
101
|
+
const _locks = new Map();
|
|
102
|
+
async function withLock(key, fn) {
|
|
103
|
+
const prev = _locks.get(key) ?? Promise.resolve();
|
|
104
|
+
let release;
|
|
105
|
+
const next = new Promise((res) => { release = res; });
|
|
106
|
+
_locks.set(key, prev.then(() => next));
|
|
107
|
+
try {
|
|
108
|
+
await prev;
|
|
109
|
+
return await fn();
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
release();
|
|
113
|
+
if (_locks.get(key) === next)
|
|
114
|
+
_locks.delete(key);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function readMemoryFile(file) {
|
|
118
|
+
try {
|
|
119
|
+
return fs.readFileSync(file, 'utf-8');
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return '';
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function writeMemoryFile(file, content) {
|
|
126
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
127
|
+
fs.writeFileSync(file, content, 'utf-8');
|
|
128
|
+
}
|
|
129
|
+
/** Evict the OLDEST entries (top of file) until under the byte cap. Used as a
|
|
130
|
+
* cheap fallback when LLM compaction is unavailable/fails — see compactMemory
|
|
131
|
+
* below for the preferred summarizing path. */
|
|
132
|
+
function evictOldest(content, maxBytes) {
|
|
133
|
+
if (Buffer.byteLength(content, 'utf-8') <= maxBytes)
|
|
134
|
+
return content;
|
|
135
|
+
const lines = content.split('\n').filter((l) => l.trim().length > 0);
|
|
136
|
+
while (lines.length > 1 && Buffer.byteLength(lines.join('\n'), 'utf-8') > maxBytes)
|
|
137
|
+
lines.shift();
|
|
138
|
+
return '\n' + lines.join('\n');
|
|
139
|
+
}
|
|
140
|
+
async function writeMemory(content, scope, workDir) {
|
|
141
|
+
let trimmed = content.trim();
|
|
142
|
+
if (!trimmed)
|
|
143
|
+
return { ok: false, already: false, scope, file: '' };
|
|
144
|
+
if (trimmed.length > exports.MEMORY_ENTRY_MAX_CHARS)
|
|
145
|
+
trimmed = trimmed.slice(0, exports.MEMORY_ENTRY_MAX_CHARS - 1).trimEnd() + '…';
|
|
146
|
+
const file = memoryFilePath(scope, workDir);
|
|
147
|
+
return withLock(file, async () => {
|
|
148
|
+
const existing = readMemoryFile(file);
|
|
149
|
+
const fingerprint = trimmed.toLowerCase().slice(0, FINGERPRINT_LEN);
|
|
150
|
+
if (existing.toLowerCase().includes(fingerprint)) {
|
|
151
|
+
return { ok: true, already: true, scope, file };
|
|
152
|
+
}
|
|
153
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
154
|
+
let next = existing + `\n- [${today}] ${trimmed}`;
|
|
155
|
+
// Emergency-only backstop — see MEMORY_HARD_CAP_BYTES. The EXPECTED bounding
|
|
156
|
+
// mechanism is compactMemoryIfNeeded (LLM summarization, called opportunistically
|
|
157
|
+
// right after this from loop.ts); this only fires if compaction hasn't kept up.
|
|
158
|
+
if (Buffer.byteLength(next, 'utf-8') > exports.MEMORY_HARD_CAP_BYTES) {
|
|
159
|
+
next = evictOldest(next, exports.MEMORY_MAX_BYTES);
|
|
160
|
+
}
|
|
161
|
+
writeMemoryFile(file, next);
|
|
162
|
+
return { ok: true, already: false, scope, file };
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
function readMemory(scope, workDir) {
|
|
166
|
+
const file = memoryFilePath(scope, workDir);
|
|
167
|
+
return readMemoryFile(file).trim();
|
|
168
|
+
}
|
|
169
|
+
/** Merge BOTH scopes for prompt injection — project-specific first (most
|
|
170
|
+
* relevant to what the agent is doing right now), global preferences after. */
|
|
171
|
+
function readAllMemory(workDir) {
|
|
172
|
+
const parts = [];
|
|
173
|
+
const globalMem = readMemory('global');
|
|
174
|
+
if (globalMem)
|
|
175
|
+
parts.push(`[Global memories — apply to every project]\n${globalMem}`);
|
|
176
|
+
if (workDir) {
|
|
177
|
+
const projMem = readMemory('project', workDir);
|
|
178
|
+
if (projMem)
|
|
179
|
+
parts.push(`[Project memories — ${path.basename(path.resolve(workDir))}]\n${projMem}`);
|
|
180
|
+
}
|
|
181
|
+
return parts.join('\n\n');
|
|
182
|
+
}
|
|
183
|
+
function clearMemory(scope, workDir) {
|
|
184
|
+
const file = memoryFilePath(scope, workDir);
|
|
185
|
+
try {
|
|
186
|
+
fs.rmSync(file, { force: true });
|
|
187
|
+
}
|
|
188
|
+
catch { /* ignore */ }
|
|
189
|
+
}
|
|
190
|
+
function memoryStats(scope, workDir) {
|
|
191
|
+
const file = memoryFilePath(scope, workDir);
|
|
192
|
+
const content = readMemoryFile(file);
|
|
193
|
+
const entries = content.split('\n').filter((l) => /^-\s\[\d{4}-\d{2}-\d{2}\]/.test(l)).length;
|
|
194
|
+
return { file, bytes: Buffer.byteLength(content, 'utf-8'), entries };
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Summarize the OLDEST half of a memory file into a few consolidated bullets via
|
|
198
|
+
* an LLM call, keeping the most recent entries verbatim — same "compact, don't
|
|
199
|
+
* silently drop" instinct as the conversation-history auto-compactor, applied to
|
|
200
|
+
* memory instead of a transcript. This is the EXPECTED bounding mechanism for
|
|
201
|
+
* memory file size — called opportunistically (fire-and-forget) right after a
|
|
202
|
+
* successful memory_write (see maybeCompactMemory in loop.ts) once a file crosses
|
|
203
|
+
* MEMORY_COMPACT_TRIGGER_BYTES. writeMemory's own emergency synchronous eviction
|
|
204
|
+
* (MEMORY_HARD_CAP_BYTES) sits well above this threshold specifically so this
|
|
205
|
+
* summarizing pass gets the chance to run first in the common case; the blunt
|
|
206
|
+
* eviction is a last resort for if compaction has been failing/unavailable.
|
|
207
|
+
*
|
|
208
|
+
* `summarize` is injected (rather than importing streamChat directly) to avoid
|
|
209
|
+
* pulling api/client.ts's network/auth machinery into every consumer of this
|
|
210
|
+
* module and to keep this file trivially unit-testable.
|
|
211
|
+
*/
|
|
212
|
+
async function compactMemoryIfNeeded(scope, workDir, summarize) {
|
|
213
|
+
const file = memoryFilePath(scope, workDir);
|
|
214
|
+
return withLock(file, async () => {
|
|
215
|
+
const content = readMemoryFile(file);
|
|
216
|
+
if (Buffer.byteLength(content, 'utf-8') <= exports.MEMORY_COMPACT_TRIGGER_BYTES)
|
|
217
|
+
return false;
|
|
218
|
+
const lines = content.split('\n').filter((l) => l.trim().length > 0);
|
|
219
|
+
if (lines.length < 8)
|
|
220
|
+
return false; // too few entries to bother — evictOldest already keeps it bounded
|
|
221
|
+
const cut = Math.floor(lines.length / 2);
|
|
222
|
+
const older = lines.slice(0, cut);
|
|
223
|
+
const recent = lines.slice(cut);
|
|
224
|
+
const prompt = `Consolidate these persistent-memory bullet entries into a shorter set of bullets, ` +
|
|
225
|
+
`merging duplicates/near-duplicates and dropping anything clearly stale or superseded ` +
|
|
226
|
+
`by a later entry. Keep each resulting bullet under ${exports.MEMORY_ENTRY_MAX_CHARS} characters, ` +
|
|
227
|
+
`one fact per line, prefixed "- [YYYY-MM-DD] " using the LATEST date among the entries it ` +
|
|
228
|
+
`draws from. Output ONLY the bullet lines, nothing else.\n\n${older.join('\n')}`;
|
|
229
|
+
let summarized;
|
|
230
|
+
try {
|
|
231
|
+
summarized = (await summarize(prompt)).trim();
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
return false; // summarization failed — leave the file as-is; the sync evictOldest cap still bounds it
|
|
235
|
+
}
|
|
236
|
+
if (!summarized)
|
|
237
|
+
return false;
|
|
238
|
+
const next = summarized + '\n' + recent.join('\n');
|
|
239
|
+
writeMemoryFile(file, Buffer.byteLength(next, 'utf-8') > exports.MEMORY_MAX_BYTES ? evictOldest(next, exports.MEMORY_MAX_BYTES) : next);
|
|
240
|
+
return true;
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
//# sourceMappingURL=memory.js.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export interface Skill {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
model?: 'turbo' | 'pro' | 'ultra';
|
|
5
|
+
mode?: string;
|
|
6
|
+
body: string;
|
|
7
|
+
source: 'project' | 'global' | 'builtin' | 'plugin';
|
|
8
|
+
/** Absolute path to the skill's own directory, for resolving supporting files. Undefined for flat command-style skills (no directory). */
|
|
9
|
+
dir?: string;
|
|
10
|
+
/** true → only `/name` invokes it; the model must not auto-trigger it (side-effecting workflows like /deploy). */
|
|
11
|
+
disableModelInvocation: boolean;
|
|
12
|
+
/** false → only the model may invoke it (auto-load only, not meant to be typed as a command). */
|
|
13
|
+
userInvocable: boolean;
|
|
14
|
+
}
|
|
15
|
+
/** Discover all skills (project overrides global overrides plugin overrides builtin).
|
|
16
|
+
* Directory-style `.nexrall/skills/<name>/SKILL.md` beats a flat `.nexrall/commands/<name>.md`
|
|
17
|
+
* of the same name within the same precedence tier. */
|
|
18
|
+
export declare function loadSkills(workDir: string): Skill[];
|
|
19
|
+
export declare function findSkill(skills: Skill[], name: string): Skill | undefined;
|
|
20
|
+
/** Skills the MODEL may auto-invoke via the use_skill tool (excludes disable-model-invocation:true ones). */
|
|
21
|
+
export declare function autoInvokableSkills(skills: Skill[]): Skill[];
|
|
22
|
+
/** Skills the USER may invoke via /name (excludes user-invocable:false ones). */
|
|
23
|
+
export declare function userInvokableSkills(skills: Skill[]): Skill[];
|
|
24
|
+
/** A compact catalogue injected into the system prompt so the model knows what it can auto-invoke via use_skill. */
|
|
25
|
+
export declare function summariseSkills(skills: Skill[]): string;
|
|
26
|
+
/**
|
|
27
|
+
* Expand a skill body into a final prompt (same $ARGUMENTS / @file / !`cmd`
|
|
28
|
+
* substitution as slash commands — see commands/loader.ts#expandBody), then
|
|
29
|
+
* prepend a short header pointing at the skill's own directory (if any) so
|
|
30
|
+
* the model knows where to find supporting files it may need to read_file
|
|
31
|
+
* on demand, mirroring Claude Code's "reference files from your SKILL.md"
|
|
32
|
+
* guidance without loading them into context up front.
|
|
33
|
+
*/
|
|
34
|
+
export declare function expandSkill(skill: Skill, argString: string, workDir: string): string;
|
|
35
|
+
//# sourceMappingURL=skills.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../../src/agent/skills.ts"],"names":[],"mappings":"AA2CA,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,GAAG,KAAK,GAAG,OAAO,CAAC;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC;IACpD,0IAA0I;IAC1I,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,kHAAkH;IAClH,sBAAsB,EAAE,OAAO,CAAC;IAChC,iGAAiG;IACjG,aAAa,EAAE,OAAO,CAAC;CACxB;AAyID;;wDAEwD;AACxD,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,KAAK,EAAE,CAanD;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS,CAG1E;AAED,6GAA6G;AAC7G,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,EAAE,CAE5D;AAED,iFAAiF;AACjF,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,EAAE,CAE5D;AAED,oHAAoH;AACpH,wBAAgB,eAAe,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,CAIvD;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAcpF"}
|
|
@@ -0,0 +1,253 @@
|
|
|
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
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.loadSkills = loadSkills;
|
|
37
|
+
exports.findSkill = findSkill;
|
|
38
|
+
exports.autoInvokableSkills = autoInvokableSkills;
|
|
39
|
+
exports.userInvokableSkills = userInvokableSkills;
|
|
40
|
+
exports.summariseSkills = summariseSkills;
|
|
41
|
+
exports.expandSkill = expandSkill;
|
|
42
|
+
const fs = __importStar(require("fs"));
|
|
43
|
+
const path = __importStar(require("path"));
|
|
44
|
+
const os = __importStar(require("os"));
|
|
45
|
+
const index_1 = require("../plugins/index");
|
|
46
|
+
const loader_1 = require("../commands/loader");
|
|
47
|
+
function parseFrontmatter(raw) {
|
|
48
|
+
const m = /^\s*---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/.exec(raw);
|
|
49
|
+
if (!m)
|
|
50
|
+
return { meta: {}, body: raw.trim() };
|
|
51
|
+
const meta = {};
|
|
52
|
+
for (const line of m[1].split(/\r?\n/)) {
|
|
53
|
+
const kv = /^([A-Za-z0-9_-]+)\s*:\s*(.*)$/.exec(line.trim());
|
|
54
|
+
if (kv)
|
|
55
|
+
meta[kv[1].toLowerCase()] = kv[2].trim().replace(/^["']|["']$/g, '');
|
|
56
|
+
}
|
|
57
|
+
return { meta, body: (m[2] ?? '').trim() };
|
|
58
|
+
}
|
|
59
|
+
function parseBool(v, fallback) {
|
|
60
|
+
if (v === undefined)
|
|
61
|
+
return fallback;
|
|
62
|
+
const s = v.trim().toLowerCase();
|
|
63
|
+
if (s === 'true' || s === '1' || s === 'yes')
|
|
64
|
+
return true;
|
|
65
|
+
if (s === 'false' || s === '0' || s === 'no')
|
|
66
|
+
return false;
|
|
67
|
+
return fallback;
|
|
68
|
+
}
|
|
69
|
+
function toSkill(meta, body, name, source, dir) {
|
|
70
|
+
const model = ['turbo', 'pro', 'ultra'].find((x) => x === (meta.model ?? '').toLowerCase());
|
|
71
|
+
return {
|
|
72
|
+
name,
|
|
73
|
+
description: meta.description || `Custom /${name} skill`,
|
|
74
|
+
model,
|
|
75
|
+
mode: meta.mode || undefined,
|
|
76
|
+
body,
|
|
77
|
+
source,
|
|
78
|
+
dir,
|
|
79
|
+
disableModelInvocation: parseBool(meta['disable-model-invocation'], false),
|
|
80
|
+
userInvocable: parseBool(meta['user-invocable'], true),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
// Legacy flat-file skills: .nexrall/commands/<name>.md (and plugin commands/).
|
|
84
|
+
// Kept as its own loader (rather than merged into loadSkillDir below) since the
|
|
85
|
+
// naming/precedence rule ("skill directory wins over a same-name flat command")
|
|
86
|
+
// requires knowing about both shapes before the caller merges them.
|
|
87
|
+
function loadFlatCommandDir(dir, source, into) {
|
|
88
|
+
let files;
|
|
89
|
+
try {
|
|
90
|
+
files = fs.readdirSync(dir).filter((f) => f.endsWith('.md'));
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
for (const file of files) {
|
|
96
|
+
try {
|
|
97
|
+
const raw = fs.readFileSync(path.join(dir, file), 'utf-8');
|
|
98
|
+
const { meta, body } = parseFrontmatter(raw);
|
|
99
|
+
const name = (meta.name || path.basename(file, '.md')).trim().toLowerCase();
|
|
100
|
+
if (!name)
|
|
101
|
+
continue;
|
|
102
|
+
if (source !== 'project' && into.has(name))
|
|
103
|
+
continue; // earlier tiers win
|
|
104
|
+
into.set(name, toSkill(meta, body, name, source));
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
/* skip malformed */
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// Directory-style skills: .nexrall/skills/<name>/SKILL.md (+ optional supporting files).
|
|
112
|
+
function loadSkillDir(root, source, into) {
|
|
113
|
+
let entries;
|
|
114
|
+
try {
|
|
115
|
+
entries = fs.readdirSync(root, { withFileTypes: true });
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
for (const entry of entries) {
|
|
121
|
+
if (!entry.isDirectory() && !entry.isSymbolicLink())
|
|
122
|
+
continue;
|
|
123
|
+
const skillDir = path.join(root, entry.name);
|
|
124
|
+
const skillFile = path.join(skillDir, 'SKILL.md');
|
|
125
|
+
try {
|
|
126
|
+
if (!fs.statSync(skillFile).isFile())
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
const raw = fs.readFileSync(skillFile, 'utf-8');
|
|
134
|
+
const { meta, body } = parseFrontmatter(raw);
|
|
135
|
+
const name = (meta.name || entry.name).trim().toLowerCase();
|
|
136
|
+
if (!name)
|
|
137
|
+
continue;
|
|
138
|
+
// A directory-style skill always wins over an earlier flat command of
|
|
139
|
+
// the same name (from the SAME precedence tier), and — like commands —
|
|
140
|
+
// project tier always wins outright regardless of load order.
|
|
141
|
+
if (source !== 'project' && into.has(name))
|
|
142
|
+
continue;
|
|
143
|
+
into.set(name, toSkill(meta, body, name, source, skillDir));
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
/* skip malformed */
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// ── Built-in skills ───────────────────────────────────────────────────────────
|
|
151
|
+
// Shipped defaults; lowest precedence (project > global > plugin > builtin), so
|
|
152
|
+
// a user can override any of them with a same-name skill or command file.
|
|
153
|
+
const BUILTIN_SKILLS = [
|
|
154
|
+
{
|
|
155
|
+
name: 'review',
|
|
156
|
+
description: 'Review uncommitted changes (or a PR/branch diff) for correctness bugs, edge cases, and security issues. ' +
|
|
157
|
+
'Use when the user asks to review a diff, check their changes before committing, or audit a PR.',
|
|
158
|
+
source: 'builtin',
|
|
159
|
+
disableModelInvocation: false,
|
|
160
|
+
userInvocable: true,
|
|
161
|
+
body: [
|
|
162
|
+
'Review the following diff like a meticulous senior engineer. Target: $ARGUMENTS',
|
|
163
|
+
'(If no target given, review the uncommitted working-tree changes below. If a branch or PR',
|
|
164
|
+
'number is given, run the appropriate `git diff <base>...` or `gh pr diff <n>` yourself first.)',
|
|
165
|
+
'',
|
|
166
|
+
'Branch: !`git branch --show-current`',
|
|
167
|
+
'Status: !`git status --short`',
|
|
168
|
+
'',
|
|
169
|
+
'Diff (uncommitted):',
|
|
170
|
+
'```diff',
|
|
171
|
+
'!`git diff HEAD --unified=5 --no-color | head -4000`',
|
|
172
|
+
'```',
|
|
173
|
+
'',
|
|
174
|
+
'Review methodology:',
|
|
175
|
+
'1. Read the surrounding code of every changed hunk (read_file with offset/limit) — never judge a hunk in isolation.',
|
|
176
|
+
'2. Look for: correctness bugs, edge cases (empty/null/unicode/concurrency), security issues',
|
|
177
|
+
' (injection, path traversal, secrets), breaking API changes (find_references / search callers),',
|
|
178
|
+
' silent behaviour changes, and missing error handling.',
|
|
179
|
+
'3. Check tests: do existing tests cover the change? Are assertions weakened?',
|
|
180
|
+
'',
|
|
181
|
+
'Output format:',
|
|
182
|
+
'- 🔴 Critical (must fix before merge) — with file:line and a concrete fix',
|
|
183
|
+
'- 🟡 Warning (should fix) — with file:line',
|
|
184
|
+
'- 🟢 Suggestion (nice to have)',
|
|
185
|
+
'- Verdict: APPROVE / REQUEST CHANGES with a one-paragraph summary.',
|
|
186
|
+
'Do NOT modify any files — this is a read-only review.',
|
|
187
|
+
].join('\n'),
|
|
188
|
+
},
|
|
189
|
+
];
|
|
190
|
+
/** Discover all skills (project overrides global overrides plugin overrides builtin).
|
|
191
|
+
* Directory-style `.nexrall/skills/<name>/SKILL.md` beats a flat `.nexrall/commands/<name>.md`
|
|
192
|
+
* of the same name within the same precedence tier. */
|
|
193
|
+
function loadSkills(workDir) {
|
|
194
|
+
const out = new Map();
|
|
195
|
+
// Flat commands first (so a same-tier skill directory below can override them).
|
|
196
|
+
loadFlatCommandDir(path.join(workDir, '.nexrall', 'commands'), 'project', out);
|
|
197
|
+
loadSkillDir(path.join(workDir, '.nexrall', 'skills'), 'project', out);
|
|
198
|
+
loadFlatCommandDir(path.join(os.homedir(), '.nexrall', 'commands'), 'global', out);
|
|
199
|
+
loadSkillDir(path.join(os.homedir(), '.nexrall', 'skills'), 'global', out);
|
|
200
|
+
for (const dir of (0, index_1.pluginAssetDirs)(workDir, 'commands'))
|
|
201
|
+
loadFlatCommandDir(dir, 'plugin', out);
|
|
202
|
+
for (const dir of (0, index_1.pluginAssetDirs)(workDir, 'skills'))
|
|
203
|
+
loadSkillDir(dir, 'plugin', out);
|
|
204
|
+
for (const skill of BUILTIN_SKILLS) {
|
|
205
|
+
if (!out.has(skill.name))
|
|
206
|
+
out.set(skill.name, skill);
|
|
207
|
+
}
|
|
208
|
+
return [...out.values()];
|
|
209
|
+
}
|
|
210
|
+
function findSkill(skills, name) {
|
|
211
|
+
const want = name.replace(/^\//, '').trim().toLowerCase();
|
|
212
|
+
return skills.find((s) => s.name === want);
|
|
213
|
+
}
|
|
214
|
+
/** Skills the MODEL may auto-invoke via the use_skill tool (excludes disable-model-invocation:true ones). */
|
|
215
|
+
function autoInvokableSkills(skills) {
|
|
216
|
+
return skills.filter((s) => !s.disableModelInvocation);
|
|
217
|
+
}
|
|
218
|
+
/** Skills the USER may invoke via /name (excludes user-invocable:false ones). */
|
|
219
|
+
function userInvokableSkills(skills) {
|
|
220
|
+
return skills.filter((s) => s.userInvocable);
|
|
221
|
+
}
|
|
222
|
+
/** A compact catalogue injected into the system prompt so the model knows what it can auto-invoke via use_skill. */
|
|
223
|
+
function summariseSkills(skills) {
|
|
224
|
+
const list = autoInvokableSkills(skills);
|
|
225
|
+
if (!list.length)
|
|
226
|
+
return '';
|
|
227
|
+
return list.map((s) => `- ${s.name}: ${s.description}`).join('\n');
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Expand a skill body into a final prompt (same $ARGUMENTS / @file / !`cmd`
|
|
231
|
+
* substitution as slash commands — see commands/loader.ts#expandBody), then
|
|
232
|
+
* prepend a short header pointing at the skill's own directory (if any) so
|
|
233
|
+
* the model knows where to find supporting files it may need to read_file
|
|
234
|
+
* on demand, mirroring Claude Code's "reference files from your SKILL.md"
|
|
235
|
+
* guidance without loading them into context up front.
|
|
236
|
+
*/
|
|
237
|
+
function expandSkill(skill, argString, workDir) {
|
|
238
|
+
const expanded = (0, loader_1.expandBody)(skill.body, argString, workDir);
|
|
239
|
+
if (!skill.dir)
|
|
240
|
+
return expanded;
|
|
241
|
+
let siblings = [];
|
|
242
|
+
try {
|
|
243
|
+
siblings = fs.readdirSync(skill.dir).filter((f) => f !== 'SKILL.md');
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
/* directory unreadable — proceed without the file listing */
|
|
247
|
+
}
|
|
248
|
+
if (!siblings.length)
|
|
249
|
+
return expanded;
|
|
250
|
+
return (`[Skill "${skill.name}" — supporting files available in ${skill.dir} (use read_file with an absolute path to load ` +
|
|
251
|
+
`any of these only if the instructions below need them): ${siblings.join(', ')}]\n\n${expanded}`);
|
|
252
|
+
}
|
|
253
|
+
//# sourceMappingURL=skills.js.map
|
package/dist/api/client.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Message, SSEEvent, AuthConfig, EnvContext, EditorContext } from '../types';
|
|
2
|
-
export declare const API_BASE
|
|
2
|
+
export declare const API_BASE: string;
|
|
3
3
|
/**
|
|
4
4
|
* Decide the final assistant `content` array to store in history for a completed
|
|
5
5
|
* turn. Pure + exported so it can be unit-tested without a live SSE stream.
|
|
@@ -42,8 +42,40 @@ export interface StreamChatOptions {
|
|
|
42
42
|
}>;
|
|
43
43
|
/** Catalogue of custom sub-agent types — injected into the system prompt. */
|
|
44
44
|
agents?: string;
|
|
45
|
+
/** Catalogue of auto-invokable skills (description only) — injected into the system prompt so the model can call use_skill when relevant. */
|
|
46
|
+
skills?: string;
|
|
47
|
+
/**
|
|
48
|
+
* Opt in to restarting a turn that died AFTER partial output was rendered.
|
|
49
|
+
*
|
|
50
|
+
* Historically any transport failure past the first token was fatal: retrying
|
|
51
|
+
* would have re-streamed text the user could already see, duplicating it. That
|
|
52
|
+
* rule is correct but it protects almost nothing in practice — a long turn emits
|
|
53
|
+
* a thinking delta within seconds, so the retry machinery was effectively dead
|
|
54
|
+
* code exactly when it mattered most (deep into an expensive agentic run).
|
|
55
|
+
*
|
|
56
|
+
* When the caller can UNDO its rendered output for the current turn, it sets
|
|
57
|
+
* this and handles the `stream_restart` event by discarding that output. Then a
|
|
58
|
+
* mid-render disconnect becomes recoverable instead of turn-fatal. This is safe
|
|
59
|
+
* for side effects: tool calls are dispatched by the agent loop only after
|
|
60
|
+
* `message_complete`, and a restart by definition means that never arrived — so
|
|
61
|
+
* no tool ran and nothing but rendered characters is rolled back.
|
|
62
|
+
*/
|
|
63
|
+
allowRestartAfterRender?: boolean;
|
|
45
64
|
}
|
|
46
65
|
export declare function streamChat(messages: Message[], options: StreamChatOptions, onEvent: (e: SSEEvent) => void): Promise<Message>;
|
|
66
|
+
/**
|
|
67
|
+
* Ask the server to stop generating a turn.
|
|
68
|
+
*
|
|
69
|
+
* This is REQUIRED for Stop to work, not an optimisation. A dropped socket used to abort
|
|
70
|
+
* the model; now it detaches and keeps generating so a network blip doesn't discard the
|
|
71
|
+
* turn (see the resume machinery above). Since a deliberate Stop looks identical to a
|
|
72
|
+
* blip from the server's side, it has to be signalled explicitly — otherwise the model
|
|
73
|
+
* would run to completion and bill in full after the user pressed Stop.
|
|
74
|
+
*
|
|
75
|
+
* Best-effort and never throws: Stop must feel instant, and the server's detach grace
|
|
76
|
+
* bounds the cost if this request never lands.
|
|
77
|
+
*/
|
|
78
|
+
export declare function cancelTurn(turnId: string): Promise<void>;
|
|
47
79
|
export declare function getBalance(): Promise<number>;
|
|
48
80
|
export declare function exchangeVscodeCode(code: string): Promise<AuthConfig>;
|
|
49
81
|
export declare function login(email: string, password: string): Promise<AuthConfig>;
|
package/dist/api/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAA8B,UAAU,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAiErH,eAAO,MAAM,QAAQ,QAAmB,CAAC;AAIzC;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,EAC5D,OAAO,EAAE,CAAC,EAAE,EACZ,UAAU,EAAE,KAAK,CAAC;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,IAAI,GAAG,SAAS,GACtD,CAAC,EAAE,CAeL;AAeD,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,UAAU,CAAC;IACjB,aAAa,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IACnC,oFAAoF;IACpF,UAAU,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC,CAAC;IACjG,6EAA6E;IAC7E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6IAA6I;IAC7I,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;;;;;;;;;;OAeG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAC;CACnC;AAiDD,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,OAAO,EAAE,EACnB,OAAO,EAAE,iBAAiB,EAC1B,OAAO,EAAE,CAAC,CAAC,EAAE,QAAQ,KAAK,IAAI,GAC7B,OAAO,CAAC,OAAO,CAAC,CA80BlB;AAID;;;;;;;;;;;GAWG;AACH,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAU9D;AAID,wBAAsB,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,CAalD;AAID,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAgB1E;AAID,wBAAsB,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAkBhF"}
|