@nexrall/code-core 1.4.66 → 1.4.67
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/agentTypes.d.ts +6 -2
- package/dist/agent/agentTypes.d.ts.map +1 -1
- package/dist/agent/agentTypes.js +13 -4
- package/dist/agent/askOnce.d.ts +12 -0
- package/dist/agent/askOnce.d.ts.map +1 -0
- package/dist/agent/askOnce.js +41 -0
- package/dist/agent/compaction.d.ts +244 -0
- package/dist/agent/compaction.d.ts.map +1 -0
- package/dist/agent/compaction.js +976 -0
- package/dist/agent/fileLocks.d.ts +34 -0
- package/dist/agent/fileLocks.d.ts.map +1 -0
- package/dist/agent/fileLocks.js +114 -0
- package/dist/agent/hooks.d.ts +324 -0
- package/dist/agent/hooks.d.ts.map +1 -0
- package/dist/agent/hooks.js +1228 -0
- package/dist/agent/iterationPolicy.d.ts +121 -0
- package/dist/agent/iterationPolicy.d.ts.map +1 -0
- package/dist/agent/iterationPolicy.js +297 -0
- package/dist/agent/lifecycleHost.d.ts +55 -0
- package/dist/agent/lifecycleHost.d.ts.map +1 -0
- package/dist/agent/lifecycleHost.js +294 -0
- package/dist/agent/loop.d.ts +11 -491
- package/dist/agent/loop.d.ts.map +1 -1
- package/dist/agent/loop.js +417 -3031
- package/dist/agent/planMode.d.ts.map +1 -1
- package/dist/agent/planMode.js +1 -0
- package/dist/agent/sharedTasks.d.ts +7 -0
- package/dist/agent/sharedTasks.d.ts.map +1 -1
- package/dist/agent/sharedTasks.js +16 -0
- package/dist/agent/subAgentBudget.d.ts +65 -0
- package/dist/agent/subAgentBudget.d.ts.map +1 -0
- package/dist/agent/subAgentBudget.js +269 -0
- package/dist/agent/subTask.d.ts +6 -0
- package/dist/agent/subTask.d.ts.map +1 -0
- package/dist/agent/subTask.js +713 -0
- package/dist/agent/subTaskSupport.d.ts +156 -0
- package/dist/agent/subTaskSupport.d.ts.map +1 -0
- package/dist/agent/subTaskSupport.js +409 -0
- package/dist/agent/toolDescriptions.d.ts +3 -0
- package/dist/agent/toolDescriptions.d.ts.map +1 -0
- package/dist/agent/toolDescriptions.js +116 -0
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +17 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/mcp/client.d.ts +104 -0
- package/dist/mcp/client.d.ts.map +1 -1
- package/dist/mcp/client.js +136 -2
- package/dist/mcp/httpClient.d.ts +17 -1
- package/dist/mcp/httpClient.d.ts.map +1 -1
- package/dist/mcp/httpClient.js +120 -19
- package/dist/mcp/manager.d.ts +77 -2
- package/dist/mcp/manager.d.ts.map +1 -1
- package/dist/mcp/manager.js +275 -9
- package/dist/mcp/sseClient.d.ts +7 -1
- package/dist/mcp/sseClient.d.ts.map +1 -1
- package/dist/mcp/sseClient.js +45 -1
- package/dist/mcp/stats.d.ts +41 -0
- package/dist/mcp/stats.d.ts.map +1 -0
- package/dist/mcp/stats.js +108 -0
- package/dist/permissions/destructive.d.ts +2 -0
- package/dist/permissions/destructive.d.ts.map +1 -1
- package/dist/permissions/destructive.js +6 -2
- package/dist/permissions/destructiveTokens.d.ts +5 -0
- package/dist/permissions/destructiveTokens.d.ts.map +1 -1
- package/dist/permissions/destructiveTokens.js +9 -3
- package/dist/permissions/modePolicy.d.ts.map +1 -1
- package/dist/permissions/modePolicy.js +5 -2
- package/dist/permissions/rules.d.ts +4 -1
- package/dist/permissions/rules.d.ts.map +1 -1
- package/dist/permissions/rules.js +29 -0
- package/dist/types.d.ts +45 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Host-driven lifecycle hooks — the two events the AGENT LOOP cannot fire itself:
|
|
3
|
+
//
|
|
4
|
+
// • FileChanged — a watched file in the working directory changed on disk.
|
|
5
|
+
// The loop has no watcher; the host (CLI / VS Code) starts one.
|
|
6
|
+
// • InstructionsLoaded — instruction files (nexrall.md, memory) were read. Only the
|
|
7
|
+
// host knows which files those were and why they were read.
|
|
8
|
+
//
|
|
9
|
+
// Both use the same config, payload and exit-code protocol as every other hook; see
|
|
10
|
+
// loop.ts. Both are OBSERVERS: a hook can inform the user, it cannot veto anything —
|
|
11
|
+
// the file already changed, the file is already loaded.
|
|
12
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
15
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
16
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
17
|
+
}
|
|
18
|
+
Object.defineProperty(o, k2, desc);
|
|
19
|
+
}) : (function(o, m, k, k2) {
|
|
20
|
+
if (k2 === undefined) k2 = k;
|
|
21
|
+
o[k2] = m[k];
|
|
22
|
+
}));
|
|
23
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
24
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
25
|
+
}) : function(o, v) {
|
|
26
|
+
o["default"] = v;
|
|
27
|
+
});
|
|
28
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
29
|
+
var ownKeys = function(o) {
|
|
30
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
31
|
+
var ar = [];
|
|
32
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
33
|
+
return ar;
|
|
34
|
+
};
|
|
35
|
+
return ownKeys(o);
|
|
36
|
+
};
|
|
37
|
+
return function (mod) {
|
|
38
|
+
if (mod && mod.__esModule) return mod;
|
|
39
|
+
var result = {};
|
|
40
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
41
|
+
__setModuleDefault(result, mod);
|
|
42
|
+
return result;
|
|
43
|
+
};
|
|
44
|
+
})();
|
|
45
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
+
exports.fireInstructionsLoadedHooks = fireInstructionsLoadedHooks;
|
|
47
|
+
exports.startFileChangedWatcher = startFileChangedWatcher;
|
|
48
|
+
exports.filesChangedSinceSnapshot = filesChangedSinceSnapshot;
|
|
49
|
+
const fs = __importStar(require("fs"));
|
|
50
|
+
const path = __importStar(require("path"));
|
|
51
|
+
const loop_1 = require("./loop");
|
|
52
|
+
/**
|
|
53
|
+
* Tell the InstructionsLoaded hooks which instruction files this session loaded.
|
|
54
|
+
* Observer: output and exit code are ignored (Claude Code says the same for this event).
|
|
55
|
+
*
|
|
56
|
+
* Called by the host at the moment the files are actually read — a `--bare` run reads
|
|
57
|
+
* none and therefore fires nothing, which is the honest behaviour.
|
|
58
|
+
*/
|
|
59
|
+
async function fireInstructionsLoadedHooks(workDir, sessionId, files) {
|
|
60
|
+
if (!files.length)
|
|
61
|
+
return;
|
|
62
|
+
try {
|
|
63
|
+
const items = (0, loop_1.loadHooks)(workDir).InstructionsLoaded;
|
|
64
|
+
if (!items?.length)
|
|
65
|
+
return;
|
|
66
|
+
for (const f of files) {
|
|
67
|
+
await (0, loop_1.runLifecycleHooks)(items, 'InstructionsLoaded', workDir, {
|
|
68
|
+
session_id: sessionId,
|
|
69
|
+
file_path: f.file_path,
|
|
70
|
+
memory_type: f.memory_type,
|
|
71
|
+
load_reason: f.load_reason,
|
|
72
|
+
...(f.globs ? { globs: f.globs } : {}),
|
|
73
|
+
}, f.load_reason).catch(() => undefined);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch { /* a hook must never break startup */ }
|
|
77
|
+
}
|
|
78
|
+
/** Only basenames in the working directory can be watched — a matcher is a filename, not a path. */
|
|
79
|
+
function isWatchableName(name) {
|
|
80
|
+
// Claude Code is narrower still (letters, digits, _, |); we allow the characters a real
|
|
81
|
+
// filename needs and drop anything that tries to be a path or a glob.
|
|
82
|
+
return !!name && !name.includes('/') && !name.includes('\\') && !name.includes('*') && !name.includes('?') && name !== '.' && name !== '..';
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Start watching the files named by the FileChanged hooks' matchers ("a.txt|b.txt").
|
|
86
|
+
*
|
|
87
|
+
* - Returns null when no FileChanged hook is configured, so callers can wire it
|
|
88
|
+
* unconditionally.
|
|
89
|
+
* - `fs.watch` on the WORKING DIRECTORY (not the files themselves: editors replace
|
|
90
|
+
* files, which kills a per-file watch on rename) filtered to the watched basenames,
|
|
91
|
+
* with a short per-file debounce — one save is several fs events.
|
|
92
|
+
* - A hook's `{"watchPaths": […]}` answer replaces the dynamic list; `{"systemMessage": "…"}`
|
|
93
|
+
* is handed to `onSystemMessage` for the host to display.
|
|
94
|
+
* - Never throws into the host: watching is best-effort (an OS without inotify, a
|
|
95
|
+
* directory that vanishes) and a broken watch must not break the session.
|
|
96
|
+
*/
|
|
97
|
+
function startFileChangedWatcher(workDir, sessionId, opts) {
|
|
98
|
+
let items;
|
|
99
|
+
try {
|
|
100
|
+
items = (0, loop_1.loadHooks)(workDir).FileChanged;
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
items = undefined;
|
|
104
|
+
}
|
|
105
|
+
if (!items?.length)
|
|
106
|
+
return null;
|
|
107
|
+
// Static names: every literal in every matcher. Flat handlers contribute none — a flat
|
|
108
|
+
// handler with no matcher watches nothing until some hook returns a watchPaths list.
|
|
109
|
+
const staticNames = new Set();
|
|
110
|
+
for (const it of items) {
|
|
111
|
+
if (it && Array.isArray(it.hooks)) {
|
|
112
|
+
const m = it.matcher;
|
|
113
|
+
if (!m || m === '*')
|
|
114
|
+
continue;
|
|
115
|
+
for (const raw of m.split('|')) {
|
|
116
|
+
const name = raw.trim();
|
|
117
|
+
if (isWatchableName(name))
|
|
118
|
+
staticNames.add(name);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (!staticNames.size)
|
|
123
|
+
return null;
|
|
124
|
+
const dynamicNames = new Set();
|
|
125
|
+
const watched = () => new Set([...staticNames, ...dynamicNames]);
|
|
126
|
+
// Who existed when we last looked. fs.watch's eventType is NOT an add/change oracle:
|
|
127
|
+
// on macOS a plain overwrite arrives as 'rename', so distinguishing by eventType
|
|
128
|
+
// labelled every save an "add". Existence across events is the honest signal, and it
|
|
129
|
+
// needs a seed — a file already present when the watcher starts is not "added" later.
|
|
130
|
+
const known = new Set();
|
|
131
|
+
const seedKnown = (names) => {
|
|
132
|
+
for (const n of names) {
|
|
133
|
+
try {
|
|
134
|
+
if (fs.existsSync(path.join(workDir, n)))
|
|
135
|
+
known.add(n);
|
|
136
|
+
}
|
|
137
|
+
catch { /* best-effort */ }
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
seedKnown(staticNames);
|
|
141
|
+
// A cheap identity for "this file changed": mtime + size. Used by the ARMING RECONCILE
|
|
142
|
+
// below, which exists because fs.watch arms ASYNCHRONOUSLY — the FSEvents/FSEventStream
|
|
143
|
+
// stream starts on the next tick, and an event that lands before it is armed is not
|
|
144
|
+
// queued, it is GONE. An editor that saves the instant the session starts is exactly
|
|
145
|
+
// that case (measured: under load the write was lost outright, the hook never ran).
|
|
146
|
+
const stamp = (name) => {
|
|
147
|
+
try {
|
|
148
|
+
const st = fs.statSync(path.join(workDir, name));
|
|
149
|
+
return `${st.mtimeMs}:${st.size}`;
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
return '';
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
const seen = new Map();
|
|
156
|
+
const snapshot = (names) => { for (const n of names)
|
|
157
|
+
seen.set(n, stamp(n)); };
|
|
158
|
+
snapshot(staticNames);
|
|
159
|
+
const pending = new Map();
|
|
160
|
+
let closed = false;
|
|
161
|
+
/** Arm the per-file debounce — shared by real fs events and by the arming reconcile. */
|
|
162
|
+
const scheduleFire = (name) => {
|
|
163
|
+
const prev = pending.get(name);
|
|
164
|
+
if (prev)
|
|
165
|
+
clearTimeout(prev);
|
|
166
|
+
pending.set(name, setTimeout(() => {
|
|
167
|
+
pending.delete(name);
|
|
168
|
+
let exists = true;
|
|
169
|
+
try {
|
|
170
|
+
exists = fs.existsSync(path.join(workDir, name));
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
exists = false;
|
|
174
|
+
}
|
|
175
|
+
const event = !exists ? 'unlink' : known.has(name) ? 'change' : 'add';
|
|
176
|
+
if (exists)
|
|
177
|
+
known.add(name);
|
|
178
|
+
else
|
|
179
|
+
known.delete(name);
|
|
180
|
+
seen.set(name, stamp(name));
|
|
181
|
+
void fireChanged(workDir, sessionId, name, event, items, dynamicNames.has(name)).then((o) => {
|
|
182
|
+
if (!o)
|
|
183
|
+
return;
|
|
184
|
+
if (o.systemMessage && opts?.onSystemMessage) {
|
|
185
|
+
try {
|
|
186
|
+
opts.onSystemMessage(o.systemMessage);
|
|
187
|
+
}
|
|
188
|
+
catch { /* host callback */ }
|
|
189
|
+
}
|
|
190
|
+
if (o.watchPaths) {
|
|
191
|
+
// watchPaths REPLACES the dynamic list (Claude Code semantics); the static
|
|
192
|
+
// matcher names always stay watched — they are what activated this watcher.
|
|
193
|
+
dynamicNames.clear();
|
|
194
|
+
for (const p of o.watchPaths) {
|
|
195
|
+
const n = isWatchableName(path.basename(p)) ? path.basename(p) : '';
|
|
196
|
+
if (n)
|
|
197
|
+
dynamicNames.add(n);
|
|
198
|
+
}
|
|
199
|
+
seedKnown(dynamicNames);
|
|
200
|
+
snapshot(dynamicNames);
|
|
201
|
+
}
|
|
202
|
+
}).catch(() => { });
|
|
203
|
+
}, 250));
|
|
204
|
+
};
|
|
205
|
+
let watcher = null;
|
|
206
|
+
try {
|
|
207
|
+
watcher = fs.watch(workDir, { persistent: false }, (_eventType, filename) => {
|
|
208
|
+
if (closed)
|
|
209
|
+
return;
|
|
210
|
+
const name = filename ? path.basename(String(filename)) : '';
|
|
211
|
+
if (!name || !watched().has(name))
|
|
212
|
+
return;
|
|
213
|
+
// Debounce per file: a single save reaches us as create+change+change on some
|
|
214
|
+
// platforms, and running the hook per raw event would fire it 3×.
|
|
215
|
+
scheduleFire(name);
|
|
216
|
+
});
|
|
217
|
+
// A watcher that failed to attach must not hold a dead object.
|
|
218
|
+
watcher.on('error', () => { });
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
// Arming reconcile (see `stamp` above): one look after the stream has had time to arm,
|
|
224
|
+
// to catch a write the stream was not yet listening for. It reuses scheduleFire, so a
|
|
225
|
+
// file caught by BOTH paths still fires exactly once (the pending timer is re-armed,
|
|
226
|
+
// not duplicated).
|
|
227
|
+
const armGraceMs = opts?.armGraceMs ?? 400;
|
|
228
|
+
const reconcileTimer = armGraceMs > 0
|
|
229
|
+
? setTimeout(() => {
|
|
230
|
+
if (closed)
|
|
231
|
+
return;
|
|
232
|
+
for (const n of filesChangedSinceSnapshot(watched(), seen, stamp))
|
|
233
|
+
scheduleFire(n);
|
|
234
|
+
}, armGraceMs)
|
|
235
|
+
: null;
|
|
236
|
+
return {
|
|
237
|
+
stop() {
|
|
238
|
+
closed = true;
|
|
239
|
+
if (reconcileTimer)
|
|
240
|
+
clearTimeout(reconcileTimer);
|
|
241
|
+
for (const t of pending.values())
|
|
242
|
+
clearTimeout(t);
|
|
243
|
+
pending.clear();
|
|
244
|
+
try {
|
|
245
|
+
watcher?.close();
|
|
246
|
+
}
|
|
247
|
+
catch { /* already closed */ }
|
|
248
|
+
watcher = null;
|
|
249
|
+
},
|
|
250
|
+
updateWatchPaths(paths) {
|
|
251
|
+
dynamicNames.clear();
|
|
252
|
+
for (const p of paths) {
|
|
253
|
+
const n = isWatchableName(path.basename(p)) ? path.basename(p) : '';
|
|
254
|
+
if (n)
|
|
255
|
+
dynamicNames.add(n);
|
|
256
|
+
}
|
|
257
|
+
seedKnown(dynamicNames);
|
|
258
|
+
snapshot(dynamicNames);
|
|
259
|
+
},
|
|
260
|
+
watchedFiles() { return [...watched()].sort(); },
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Which watched files differ from the snapshot taken when the watcher armed. This is the
|
|
265
|
+
* whole substance of the arming reconcile: fs.watch arms asynchronously, so a write that
|
|
266
|
+
* lands before the stream is listening is not queued — it is gone. Exported so the
|
|
267
|
+
* comparison can be tested without racing an OS event.
|
|
268
|
+
*/
|
|
269
|
+
function filesChangedSinceSnapshot(names, seen, stamp) {
|
|
270
|
+
const out = [];
|
|
271
|
+
for (const n of names)
|
|
272
|
+
if (stamp(n) !== (seen.get(n) ?? ''))
|
|
273
|
+
out.push(n);
|
|
274
|
+
return out;
|
|
275
|
+
}
|
|
276
|
+
/** Fire the FileChanged hooks for one file, returning the merged outcome (or null on any failure). */
|
|
277
|
+
async function fireChanged(workDir, sessionId, name, event, items, isDynamic) {
|
|
278
|
+
const abs = path.join(workDir, name);
|
|
279
|
+
try {
|
|
280
|
+
// A file admitted through watchPaths had no matcher naming it, so no matcher may
|
|
281
|
+
// filter it either — every FileChanged group hears about it. A static matcher file
|
|
282
|
+
// runs only the groups that named it.
|
|
283
|
+
const o = await (0, loop_1.runLifecycleHooks)(items, 'FileChanged', workDir, {
|
|
284
|
+
session_id: sessionId,
|
|
285
|
+
file_path: abs,
|
|
286
|
+
event,
|
|
287
|
+
}, isDynamic ? undefined : name);
|
|
288
|
+
return { watchPaths: o.watchPaths, systemMessage: o.systemMessage };
|
|
289
|
+
}
|
|
290
|
+
catch {
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
//# sourceMappingURL=lifecycleHost.js.map
|