wholestack 0.4.0
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 +60 -0
- package/dist/chunk-7DJJXUV4.js +4012 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +673 -0
- package/dist/index.d.ts +1394 -0
- package/dist/index.js +297 -0
- package/package.json +75 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,1394 @@
|
|
|
1
|
+
import * as ai from 'ai';
|
|
2
|
+
import { ToolSet, LanguageModel, ModelMessage } from 'ai';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Two ways to drive the ZETA build pipeline:
|
|
6
|
+
*
|
|
7
|
+
* http — POST /api/zeta/build on a running Next app. Zero local toolchain,
|
|
8
|
+
* but the engine must be up (and reachable).
|
|
9
|
+
* local — spawn the SAME worker the route spawns
|
|
10
|
+
* (scripts/zeta-build-worker.mts via `node --import tsx`), skipping
|
|
11
|
+
* HTTP, Turbopack, and the docker sandbox entirely. No server needed,
|
|
12
|
+
* but you must be inside the monorepo with tsx + build env present.
|
|
13
|
+
*
|
|
14
|
+
* Both speak the identical NDJSON event protocol, so we normalize once.
|
|
15
|
+
*/
|
|
16
|
+
interface BuildResult {
|
|
17
|
+
ok: boolean;
|
|
18
|
+
error?: string;
|
|
19
|
+
buildId?: unknown;
|
|
20
|
+
verdict?: "SHIP" | "NO_SHIP";
|
|
21
|
+
themeId?: unknown;
|
|
22
|
+
fileCount?: number;
|
|
23
|
+
spec?: string | null;
|
|
24
|
+
verifyErrors?: string[];
|
|
25
|
+
fileList?: (string | null | undefined)[];
|
|
26
|
+
/** True when the engine returned a preview only (no code) — needs membership. */
|
|
27
|
+
paywalled?: boolean;
|
|
28
|
+
/** Where to subscribe when paywalled. */
|
|
29
|
+
upgradeUrl?: string;
|
|
30
|
+
}
|
|
31
|
+
interface DeliverResult {
|
|
32
|
+
ok: boolean;
|
|
33
|
+
written?: number;
|
|
34
|
+
dir?: string;
|
|
35
|
+
error?: string;
|
|
36
|
+
paywalled?: boolean;
|
|
37
|
+
upgradeUrl?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
type TodoStatus = "pending" | "in_progress" | "completed";
|
|
41
|
+
interface Todo {
|
|
42
|
+
content: string;
|
|
43
|
+
status: TodoStatus;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Permission modes — the safety dial:
|
|
48
|
+
*
|
|
49
|
+
* default — ask before every edit and shell command (with an "always"
|
|
50
|
+
* escape hatch per command and a "yes to all edits" toggle).
|
|
51
|
+
* acceptEdits — file edits apply without asking; shell commands still gated.
|
|
52
|
+
* plan — read-only. Edits, writes, builds, and commands are refused so
|
|
53
|
+
* the agent researches and proposes a plan instead of acting.
|
|
54
|
+
* yolo — nothing is gated (the old `--yes`). For trusted, scripted runs.
|
|
55
|
+
*/
|
|
56
|
+
type PermissionMode = "default" | "acceptEdits" | "plan" | "yolo";
|
|
57
|
+
declare const PERMISSION_MODES: PermissionMode[];
|
|
58
|
+
declare function isPermissionMode(v: unknown): v is PermissionMode;
|
|
59
|
+
interface ConfirmChoice {
|
|
60
|
+
key: string;
|
|
61
|
+
label: string;
|
|
62
|
+
}
|
|
63
|
+
/** Prompt the user to pick one of `choices`; resolves to the chosen `key`. */
|
|
64
|
+
type ConfirmFn = (prompt: string, choices: ConfirmChoice[]) => Promise<string>;
|
|
65
|
+
type Decision = "allow" | "deny";
|
|
66
|
+
declare class Permissions {
|
|
67
|
+
mode: PermissionMode;
|
|
68
|
+
private readonly confirm?;
|
|
69
|
+
private alwaysCommands;
|
|
70
|
+
private acceptAllEdits;
|
|
71
|
+
constructor(mode: PermissionMode, confirm?: ConfirmFn | undefined);
|
|
72
|
+
isPlan(): boolean;
|
|
73
|
+
setMode(mode: PermissionMode): void;
|
|
74
|
+
/** Message a tool returns when an action is blocked by plan mode. */
|
|
75
|
+
planRefusal(): string;
|
|
76
|
+
/** Gate a file mutation. The caller has already rendered the diff. */
|
|
77
|
+
requestEdit(path: string): Promise<Decision>;
|
|
78
|
+
/** Gate a shell command. `display` is the command line shown to the user. */
|
|
79
|
+
requestCommand(display: string): Promise<Decision>;
|
|
80
|
+
/** Gate a heavier action (e.g. generate_app) — allowed unless plan mode. */
|
|
81
|
+
guardMutation(): Decision;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Edit checkpoints — the safety net under the agent's hands. Every file mutation
|
|
86
|
+
* (write_file / edit_file / multi_edit) snapshots the prior on-disk state before
|
|
87
|
+
* touching the file, grouped per tool call into one undoable checkpoint. `/undo`
|
|
88
|
+
* restores the previous state (re-creating or deleting files as needed) and pushes
|
|
89
|
+
* the just-undone state onto a redo stack; `/redo` replays it.
|
|
90
|
+
*
|
|
91
|
+
* In-memory, scoped to the running session — the point is fast, trustworthy
|
|
92
|
+
* rollback of agent edits while you work, not a durable VCS (that's git's job).
|
|
93
|
+
*/
|
|
94
|
+
interface FileSnap {
|
|
95
|
+
path: string;
|
|
96
|
+
/** Content before the change; null means the file did not exist. */
|
|
97
|
+
before: string | null;
|
|
98
|
+
}
|
|
99
|
+
interface Checkpoint {
|
|
100
|
+
id: number;
|
|
101
|
+
label: string;
|
|
102
|
+
at: number;
|
|
103
|
+
snaps: FileSnap[];
|
|
104
|
+
}
|
|
105
|
+
/** Outcome of an undo/redo, for the REPL to report. */
|
|
106
|
+
interface RestoreResult {
|
|
107
|
+
ok: boolean;
|
|
108
|
+
label?: string;
|
|
109
|
+
restored?: string[];
|
|
110
|
+
deleted?: string[];
|
|
111
|
+
error?: string;
|
|
112
|
+
}
|
|
113
|
+
declare class CheckpointStore {
|
|
114
|
+
private undoStack;
|
|
115
|
+
private redoStack;
|
|
116
|
+
private seq;
|
|
117
|
+
/** Snaps accumulated for the in-flight tool call (multi_edit groups several). */
|
|
118
|
+
private pending;
|
|
119
|
+
/** Begin a new group; call before a mutating tool touches any file. */
|
|
120
|
+
begin(): void;
|
|
121
|
+
/** Record a file's pre-edit state once per path within the current group. */
|
|
122
|
+
capture(path: string, before: string | null): void;
|
|
123
|
+
/** Seal the group into an undoable checkpoint. A redo stack is invalidated by new edits. */
|
|
124
|
+
commit(label: string): void;
|
|
125
|
+
get canUndo(): boolean;
|
|
126
|
+
get canRedo(): boolean;
|
|
127
|
+
list(limit?: number): Checkpoint[];
|
|
128
|
+
/** Restore the most recent checkpoint; the displaced state becomes redoable. */
|
|
129
|
+
undo(): Promise<RestoreResult>;
|
|
130
|
+
/** Re-apply the most recently undone checkpoint. */
|
|
131
|
+
redo(): Promise<RestoreResult>;
|
|
132
|
+
/** Read the current on-disk state of each path so it can be re-applied later. */
|
|
133
|
+
private captureCurrent;
|
|
134
|
+
/** Write each snap's `before` back to disk (or delete when it was absent). */
|
|
135
|
+
private apply;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* The agent's hands. Three families:
|
|
140
|
+
*
|
|
141
|
+
* ZETA verbs — generate_app / verify_contract — the reason this CLI exists.
|
|
142
|
+
* They drive the real engine, not a mock.
|
|
143
|
+
* Code edits — read / write / edit / grep / glob — a real coding agent's
|
|
144
|
+
* toolset, with diff previews and permission gates on writes.
|
|
145
|
+
* Shell — run_command — gated through the permission layer.
|
|
146
|
+
*
|
|
147
|
+
* Every tool keeps its side effects honest: failures come back as
|
|
148
|
+
* { ok:false, error } the model can read and react to, never a silent success.
|
|
149
|
+
*/
|
|
150
|
+
interface ToolContext {
|
|
151
|
+
/** Where file ops are rooted + sandboxed. */
|
|
152
|
+
cwd: string;
|
|
153
|
+
/** ZETA build engine base URL (the running Next app). */
|
|
154
|
+
zetaApiUrl: string;
|
|
155
|
+
/** How generate_app drives the pipeline: auto | local | http. */
|
|
156
|
+
buildMode: "auto" | "local" | "http";
|
|
157
|
+
/** The permission gate — diffs, command confirms, plan-mode refusals. */
|
|
158
|
+
permissions: Permissions;
|
|
159
|
+
/** Snapshots taken before each file mutation, for /undo + /redo. */
|
|
160
|
+
checkpoints?: CheckpointStore;
|
|
161
|
+
}
|
|
162
|
+
declare function buildTools(ctx: ToolContext): {
|
|
163
|
+
todo_write: ai.Tool<{
|
|
164
|
+
todos: {
|
|
165
|
+
status: "pending" | "in_progress" | "completed";
|
|
166
|
+
content: string;
|
|
167
|
+
}[];
|
|
168
|
+
}, {
|
|
169
|
+
ok: boolean;
|
|
170
|
+
remaining: number;
|
|
171
|
+
total: number;
|
|
172
|
+
}>;
|
|
173
|
+
todo_read: ai.Tool<{}, {
|
|
174
|
+
ok: boolean;
|
|
175
|
+
todos: Todo[];
|
|
176
|
+
}>;
|
|
177
|
+
generate_app: ai.Tool<{
|
|
178
|
+
idea: string;
|
|
179
|
+
scope: "full" | "static" | "component" | "page";
|
|
180
|
+
buildModel: "zeta-g1" | "zeta-g1-max";
|
|
181
|
+
install: boolean;
|
|
182
|
+
keep: boolean;
|
|
183
|
+
}, BuildResult | {
|
|
184
|
+
ok: boolean;
|
|
185
|
+
liveUrl: string | undefined;
|
|
186
|
+
buildId: string | undefined;
|
|
187
|
+
files: number | undefined;
|
|
188
|
+
loc: number | undefined;
|
|
189
|
+
verdict: string | undefined;
|
|
190
|
+
trust: number | undefined;
|
|
191
|
+
note: string;
|
|
192
|
+
} | {
|
|
193
|
+
delivered: DeliverResult;
|
|
194
|
+
ok: boolean;
|
|
195
|
+
error?: string;
|
|
196
|
+
buildId?: unknown;
|
|
197
|
+
verdict?: "SHIP" | "NO_SHIP";
|
|
198
|
+
themeId?: unknown;
|
|
199
|
+
fileCount?: number;
|
|
200
|
+
spec?: string | null;
|
|
201
|
+
verifyErrors?: string[];
|
|
202
|
+
fileList?: (string | null | undefined)[];
|
|
203
|
+
paywalled?: boolean;
|
|
204
|
+
upgradeUrl?: string;
|
|
205
|
+
liveUrl?: undefined;
|
|
206
|
+
files?: undefined;
|
|
207
|
+
loc?: undefined;
|
|
208
|
+
trust?: undefined;
|
|
209
|
+
note?: undefined;
|
|
210
|
+
}>;
|
|
211
|
+
verify_contract: ai.Tool<{
|
|
212
|
+
projectDir: string;
|
|
213
|
+
contract: string;
|
|
214
|
+
property?: "conservation" | "no-value-extraction" | "reentrancy-safety" | "asset-conservation" | "monotonic-supply" | "access-control" | "initialization-safety" | "pausability" | "supply-cap" | "allowance-correctness" | undefined;
|
|
215
|
+
certPath?: string | undefined;
|
|
216
|
+
address?: string | undefined;
|
|
217
|
+
rpc?: string | undefined;
|
|
218
|
+
}, {
|
|
219
|
+
ok: boolean;
|
|
220
|
+
error: string;
|
|
221
|
+
verified?: undefined;
|
|
222
|
+
property?: undefined;
|
|
223
|
+
exitCode?: undefined;
|
|
224
|
+
output?: undefined;
|
|
225
|
+
} | {
|
|
226
|
+
ok: boolean;
|
|
227
|
+
verified: boolean;
|
|
228
|
+
property: string;
|
|
229
|
+
exitCode: number;
|
|
230
|
+
output: string;
|
|
231
|
+
error?: undefined;
|
|
232
|
+
}>;
|
|
233
|
+
read_file: ai.Tool<{
|
|
234
|
+
path: string;
|
|
235
|
+
offset?: number | undefined;
|
|
236
|
+
limit?: number | undefined;
|
|
237
|
+
}, {
|
|
238
|
+
ok: boolean;
|
|
239
|
+
path: string;
|
|
240
|
+
content: string;
|
|
241
|
+
startLine?: undefined;
|
|
242
|
+
error?: undefined;
|
|
243
|
+
} | {
|
|
244
|
+
ok: boolean;
|
|
245
|
+
path: string;
|
|
246
|
+
content: string;
|
|
247
|
+
startLine: number;
|
|
248
|
+
error?: undefined;
|
|
249
|
+
} | {
|
|
250
|
+
ok: boolean;
|
|
251
|
+
error: string;
|
|
252
|
+
path?: undefined;
|
|
253
|
+
content?: undefined;
|
|
254
|
+
startLine?: undefined;
|
|
255
|
+
}>;
|
|
256
|
+
write_file: ai.Tool<{
|
|
257
|
+
path: string;
|
|
258
|
+
content: string;
|
|
259
|
+
}, {
|
|
260
|
+
ok: boolean;
|
|
261
|
+
declined: boolean;
|
|
262
|
+
error: string;
|
|
263
|
+
path?: undefined;
|
|
264
|
+
bytes?: undefined;
|
|
265
|
+
} | {
|
|
266
|
+
ok: boolean;
|
|
267
|
+
path: string;
|
|
268
|
+
bytes: number;
|
|
269
|
+
declined?: undefined;
|
|
270
|
+
error?: undefined;
|
|
271
|
+
} | {
|
|
272
|
+
ok: boolean;
|
|
273
|
+
error: string;
|
|
274
|
+
declined?: undefined;
|
|
275
|
+
path?: undefined;
|
|
276
|
+
bytes?: undefined;
|
|
277
|
+
}>;
|
|
278
|
+
edit_file: ai.Tool<{
|
|
279
|
+
path: string;
|
|
280
|
+
old_string: string;
|
|
281
|
+
new_string: string;
|
|
282
|
+
replace_all: boolean;
|
|
283
|
+
}, {
|
|
284
|
+
ok: boolean;
|
|
285
|
+
error: string;
|
|
286
|
+
declined?: undefined;
|
|
287
|
+
path?: undefined;
|
|
288
|
+
added?: undefined;
|
|
289
|
+
removed?: undefined;
|
|
290
|
+
} | {
|
|
291
|
+
ok: boolean;
|
|
292
|
+
declined: boolean;
|
|
293
|
+
error: string;
|
|
294
|
+
path?: undefined;
|
|
295
|
+
added?: undefined;
|
|
296
|
+
removed?: undefined;
|
|
297
|
+
} | {
|
|
298
|
+
ok: boolean;
|
|
299
|
+
path: string;
|
|
300
|
+
added: number;
|
|
301
|
+
removed: number;
|
|
302
|
+
error?: undefined;
|
|
303
|
+
declined?: undefined;
|
|
304
|
+
}>;
|
|
305
|
+
multi_edit: ai.Tool<{
|
|
306
|
+
edits: {
|
|
307
|
+
path: string;
|
|
308
|
+
old_string: string;
|
|
309
|
+
new_string: string;
|
|
310
|
+
replace_all: boolean;
|
|
311
|
+
}[];
|
|
312
|
+
}, {
|
|
313
|
+
ok: boolean;
|
|
314
|
+
error: string;
|
|
315
|
+
declined?: undefined;
|
|
316
|
+
files?: undefined;
|
|
317
|
+
added?: undefined;
|
|
318
|
+
removed?: undefined;
|
|
319
|
+
} | {
|
|
320
|
+
ok: boolean;
|
|
321
|
+
declined: boolean;
|
|
322
|
+
error: string;
|
|
323
|
+
files?: undefined;
|
|
324
|
+
added?: undefined;
|
|
325
|
+
removed?: undefined;
|
|
326
|
+
} | {
|
|
327
|
+
ok: boolean;
|
|
328
|
+
files: string[];
|
|
329
|
+
added: number;
|
|
330
|
+
removed: number;
|
|
331
|
+
error?: undefined;
|
|
332
|
+
declined?: undefined;
|
|
333
|
+
}>;
|
|
334
|
+
list_dir: ai.Tool<{
|
|
335
|
+
path: string;
|
|
336
|
+
}, {
|
|
337
|
+
ok: boolean;
|
|
338
|
+
path: string;
|
|
339
|
+
entries: {
|
|
340
|
+
name: string;
|
|
341
|
+
dir: boolean;
|
|
342
|
+
}[];
|
|
343
|
+
error?: undefined;
|
|
344
|
+
} | {
|
|
345
|
+
ok: boolean;
|
|
346
|
+
error: string;
|
|
347
|
+
path?: undefined;
|
|
348
|
+
entries?: undefined;
|
|
349
|
+
}>;
|
|
350
|
+
glob: ai.Tool<{
|
|
351
|
+
path: string;
|
|
352
|
+
pattern: string;
|
|
353
|
+
}, {
|
|
354
|
+
ok: boolean;
|
|
355
|
+
count: number;
|
|
356
|
+
files: string[];
|
|
357
|
+
error?: undefined;
|
|
358
|
+
} | {
|
|
359
|
+
ok: boolean;
|
|
360
|
+
error: string;
|
|
361
|
+
count?: undefined;
|
|
362
|
+
files?: undefined;
|
|
363
|
+
}>;
|
|
364
|
+
grep: ai.Tool<{
|
|
365
|
+
path: string;
|
|
366
|
+
pattern: string;
|
|
367
|
+
ignoreCase: boolean;
|
|
368
|
+
maxResults: number;
|
|
369
|
+
glob?: string | undefined;
|
|
370
|
+
}, {
|
|
371
|
+
ok: boolean;
|
|
372
|
+
count: number;
|
|
373
|
+
matches: {
|
|
374
|
+
file: string;
|
|
375
|
+
line: number;
|
|
376
|
+
text: string;
|
|
377
|
+
}[];
|
|
378
|
+
error?: undefined;
|
|
379
|
+
} | {
|
|
380
|
+
ok: boolean;
|
|
381
|
+
error: string;
|
|
382
|
+
count?: undefined;
|
|
383
|
+
matches?: undefined;
|
|
384
|
+
}>;
|
|
385
|
+
run_command: ai.Tool<{
|
|
386
|
+
args: string[];
|
|
387
|
+
command: string;
|
|
388
|
+
}, {
|
|
389
|
+
ok: boolean;
|
|
390
|
+
declined: boolean;
|
|
391
|
+
error: string;
|
|
392
|
+
exitCode?: undefined;
|
|
393
|
+
output?: undefined;
|
|
394
|
+
} | {
|
|
395
|
+
ok: boolean;
|
|
396
|
+
exitCode: number;
|
|
397
|
+
output: string;
|
|
398
|
+
declined?: undefined;
|
|
399
|
+
error?: undefined;
|
|
400
|
+
}>;
|
|
401
|
+
run_app: ai.Tool<{
|
|
402
|
+
dir: string;
|
|
403
|
+
timeoutSeconds: number;
|
|
404
|
+
script?: string | undefined;
|
|
405
|
+
}, {
|
|
406
|
+
ok: boolean;
|
|
407
|
+
error: string;
|
|
408
|
+
declined?: undefined;
|
|
409
|
+
url?: undefined;
|
|
410
|
+
serving?: undefined;
|
|
411
|
+
log?: undefined;
|
|
412
|
+
} | {
|
|
413
|
+
ok: boolean;
|
|
414
|
+
declined: boolean;
|
|
415
|
+
error: string;
|
|
416
|
+
url?: undefined;
|
|
417
|
+
serving?: undefined;
|
|
418
|
+
log?: undefined;
|
|
419
|
+
} | {
|
|
420
|
+
ok: boolean;
|
|
421
|
+
url: string | undefined;
|
|
422
|
+
serving: boolean;
|
|
423
|
+
log: string;
|
|
424
|
+
error?: undefined;
|
|
425
|
+
declined?: undefined;
|
|
426
|
+
} | {
|
|
427
|
+
ok: boolean;
|
|
428
|
+
error: string | undefined;
|
|
429
|
+
log: string;
|
|
430
|
+
declined?: undefined;
|
|
431
|
+
url?: undefined;
|
|
432
|
+
serving?: undefined;
|
|
433
|
+
}>;
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Hooks — shell commands the user (or a plugin) wires to agent events, the
|
|
438
|
+
* familiar event-hook idea. A PreToolUse hook can BLOCK a tool call (a
|
|
439
|
+
* conscience/guardrail); PostToolUse and UserPromptSubmit hooks inject extra
|
|
440
|
+
* context. Each hook receives a JSON event on stdin and may answer with JSON or
|
|
441
|
+
* an exit code.
|
|
442
|
+
*/
|
|
443
|
+
type HookEvent = "PreToolUse" | "PostToolUse" | "UserPromptSubmit" | "Stop";
|
|
444
|
+
interface HookDef {
|
|
445
|
+
/** Regex tested against the tool name (tool events only). */
|
|
446
|
+
matcher?: string;
|
|
447
|
+
command: string;
|
|
448
|
+
source?: string;
|
|
449
|
+
}
|
|
450
|
+
type HookSet = Partial<Record<HookEvent, HookDef[]>>;
|
|
451
|
+
interface HookPayload {
|
|
452
|
+
toolName?: string;
|
|
453
|
+
toolInput?: unknown;
|
|
454
|
+
toolResult?: unknown;
|
|
455
|
+
prompt?: string;
|
|
456
|
+
}
|
|
457
|
+
interface HookOutcome {
|
|
458
|
+
/** Set on a PreToolUse block — the reason fed back to the model. */
|
|
459
|
+
block?: string;
|
|
460
|
+
/** Extra context strings to surface (PostToolUse / UserPromptSubmit). */
|
|
461
|
+
context: string[];
|
|
462
|
+
}
|
|
463
|
+
declare class HookRunner {
|
|
464
|
+
private readonly hooks;
|
|
465
|
+
private readonly cwd;
|
|
466
|
+
constructor(hooks: HookSet, cwd: string);
|
|
467
|
+
any(): boolean;
|
|
468
|
+
has(event: HookEvent): boolean;
|
|
469
|
+
run(event: HookEvent, payload: HookPayload): Promise<HookOutcome>;
|
|
470
|
+
}
|
|
471
|
+
/** Merge hook sets (later wins on nothing — they all concatenate). */
|
|
472
|
+
declare function mergeHookSets(...sets: HookSet[]): HookSet;
|
|
473
|
+
/** Load hooks.json from the global config dir and the project's .zeta-g/. */
|
|
474
|
+
declare function loadHookFiles(cwd: string): HookSet;
|
|
475
|
+
/**
|
|
476
|
+
* Wrap every tool's execute so PreToolUse hooks can block it and PostToolUse
|
|
477
|
+
* hooks can append context. No-op when there are no tool hooks.
|
|
478
|
+
*/
|
|
479
|
+
declare function applyHooks(tools: ToolSet, runner: HookRunner): ToolSet;
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Zeta-G's brain — one family, three tiers, all branded Zeta:
|
|
483
|
+
*
|
|
484
|
+
* Zeta-G1.0 Lite — light & cheap
|
|
485
|
+
* Zeta-G1.0 — the default
|
|
486
|
+
* Zeta-G1.0 MAX — the most capable
|
|
487
|
+
*
|
|
488
|
+
* The transport underneath is an implementation detail and never surfaced to
|
|
489
|
+
* the user — only the Zeta tier names are shown. (The ZETA build engine writes
|
|
490
|
+
* ISL on its own fast lane; that's separate from the conversational brain here.)
|
|
491
|
+
* We resolve lazily so the CLI boots even with no key; it errors the moment you
|
|
492
|
+
* actually ask it to think.
|
|
493
|
+
*/
|
|
494
|
+
type ModelKey = string;
|
|
495
|
+
declare function resolveModelKey(raw: string | undefined): ModelKey;
|
|
496
|
+
declare function modelLabel(key: ModelKey): string;
|
|
497
|
+
declare function modelId(key: ModelKey): string;
|
|
498
|
+
declare function modelContextWindow(key: ModelKey): number;
|
|
499
|
+
declare function supportsThinking(key: ModelKey): boolean;
|
|
500
|
+
declare function listModels(): {
|
|
501
|
+
key: ModelKey;
|
|
502
|
+
label: string;
|
|
503
|
+
thinking: boolean;
|
|
504
|
+
}[];
|
|
505
|
+
/**
|
|
506
|
+
* Provider-specific reasoning options. Every tier runs on Cerebras' OpenAI-
|
|
507
|
+
* compatible endpoint, which honors `reasoning_effort` (the @ai-sdk/openai
|
|
508
|
+
* provider maps `openai.reasoningEffort` → that body field). We keep effort
|
|
509
|
+
* "low" by default — the measured-safe setting that stops gpt-oss from starving
|
|
510
|
+
* its `content` budget — and only raise it to "high" when the user opts into
|
|
511
|
+
* thinking via --think / /think. Custom (OpenRouter) lanes get no override.
|
|
512
|
+
*/
|
|
513
|
+
declare function buildProviderOptions(key: ModelKey, thinkingOn: boolean): Record<string, unknown> | undefined;
|
|
514
|
+
declare function resolveModel(key: ModelKey): LanguageModel;
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Token + cost accounting. The model reports usage after every turn; we keep a
|
|
518
|
+
* running total so `/cost` can answer "what has this session cost me?" honestly.
|
|
519
|
+
*
|
|
520
|
+
* Prices are approximate list rates ($ per 1M tokens). The fast Zeta-g1 lanes
|
|
521
|
+
* are billed on a flat plan, so we surface 0 rather than invent a per-token
|
|
522
|
+
* number. Costs are internal estimates only — the user never sees an upstream id.
|
|
523
|
+
*/
|
|
524
|
+
interface TokenUsage {
|
|
525
|
+
inputTokens?: number;
|
|
526
|
+
outputTokens?: number;
|
|
527
|
+
totalTokens?: number;
|
|
528
|
+
cachedInputTokens?: number;
|
|
529
|
+
reasoningTokens?: number;
|
|
530
|
+
}
|
|
531
|
+
declare function estimateCost(modelId: string, u: TokenUsage): number;
|
|
532
|
+
/** Running totals across a session, keyed for a clean `/cost` readout. */
|
|
533
|
+
declare class UsageMeter {
|
|
534
|
+
private input;
|
|
535
|
+
private output;
|
|
536
|
+
private cached;
|
|
537
|
+
private reasoning;
|
|
538
|
+
private turns;
|
|
539
|
+
private cost;
|
|
540
|
+
add(modelId: string, u: TokenUsage): void;
|
|
541
|
+
get totalTokens(): number;
|
|
542
|
+
get totalCost(): number;
|
|
543
|
+
snapshot(): {
|
|
544
|
+
input: number;
|
|
545
|
+
output: number;
|
|
546
|
+
cached: number;
|
|
547
|
+
reasoning: number;
|
|
548
|
+
total: number;
|
|
549
|
+
turns: number;
|
|
550
|
+
cost: number;
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
interface SessionMeta {
|
|
555
|
+
id: string;
|
|
556
|
+
cwd: string;
|
|
557
|
+
model: string;
|
|
558
|
+
startedAt: string;
|
|
559
|
+
}
|
|
560
|
+
interface SessionSummary extends SessionMeta {
|
|
561
|
+
preview: string;
|
|
562
|
+
turns: number;
|
|
563
|
+
updatedAt: number;
|
|
564
|
+
}
|
|
565
|
+
declare class Session {
|
|
566
|
+
readonly meta: SessionMeta;
|
|
567
|
+
private readonly path;
|
|
568
|
+
private constructor();
|
|
569
|
+
/** Start a fresh session and write its meta header. */
|
|
570
|
+
static create(cwd: string, model: string): Session;
|
|
571
|
+
/** Reopen an existing session and replay its messages. */
|
|
572
|
+
static resume(id: string): {
|
|
573
|
+
session: Session;
|
|
574
|
+
messages: ModelMessage[];
|
|
575
|
+
} | null;
|
|
576
|
+
/** The most recent session (optionally scoped to a working directory). */
|
|
577
|
+
static latestId(cwd?: string): string | null;
|
|
578
|
+
/** List sessions newest-first, with a preview of the first user message. */
|
|
579
|
+
static list(limit?: number, cwd?: string): SessionSummary[];
|
|
580
|
+
appendUser(text: string): void;
|
|
581
|
+
appendMessages(messages: ModelMessage[]): void;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* SP-21 — Prompt / Policy Runtime: the shared contract.
|
|
586
|
+
*
|
|
587
|
+
* The single source of truth for the prompt-governance layer. Every module in
|
|
588
|
+
* src/prompts, src/policy, and src/security implements against THESE types so
|
|
589
|
+
* the pieces compose without interface drift.
|
|
590
|
+
*
|
|
591
|
+
* Core idea: a turn's context is split into isolated, authority-stamped
|
|
592
|
+
* CHANNELS. SYSTEM/DEVELOPER/USER carry instruction authority (descending);
|
|
593
|
+
* RETRIEVED/TOOL/MCP/WEB are UNTRUSTED data and can never issue instructions —
|
|
594
|
+
* "ignore previous instructions" buried in a tool result or a fetched page is
|
|
595
|
+
* neutralized and fenced as data before it ever reaches the provider.
|
|
596
|
+
*/
|
|
597
|
+
/** Authority tiers — higher binds harder; a lower tier NEVER overrides a higher one. */
|
|
598
|
+
type Authority = "system" | "developer" | "user" | "untrusted";
|
|
599
|
+
/** The isolated channels that make up a turn's context. */
|
|
600
|
+
type ChannelKind = "system" | "developer" | "user" | "retrieved" | "tool" | "mcp" | "memory" | "web";
|
|
601
|
+
interface Channel {
|
|
602
|
+
kind: ChannelKind;
|
|
603
|
+
authority: Authority;
|
|
604
|
+
/** Provenance for linting + display (tool name, url, file path, server key). */
|
|
605
|
+
source?: string;
|
|
606
|
+
content: string;
|
|
607
|
+
/** Untrusted channels are firewall-scanned and fenced as data, never as instructions. */
|
|
608
|
+
trusted: boolean;
|
|
609
|
+
}
|
|
610
|
+
declare function channelAuthority(kind: ChannelKind): Authority;
|
|
611
|
+
declare function isUntrusted(kind: ChannelKind): boolean;
|
|
612
|
+
/** Channels whose content is untrusted external input — derived from the one
|
|
613
|
+
* source of truth (AUTHORITY_OF) so it can never drift from isUntrusted(). */
|
|
614
|
+
declare const UNTRUSTED_CHANNELS: ChannelKind[];
|
|
615
|
+
type FsAccess = "none" | "read" | "write";
|
|
616
|
+
/** What a tool/plugin/MCP server is permitted to touch. */
|
|
617
|
+
interface CapabilitySet {
|
|
618
|
+
fs: FsAccess;
|
|
619
|
+
network: boolean;
|
|
620
|
+
shell: boolean;
|
|
621
|
+
secrets: boolean;
|
|
622
|
+
}
|
|
623
|
+
interface CapabilityManifest {
|
|
624
|
+
/** Tool / plugin / MCP id this manifest governs (e.g. "edit_file", "mcp__github__*"). */
|
|
625
|
+
name: string;
|
|
626
|
+
capabilities: CapabilitySet;
|
|
627
|
+
/** Where it came from: "builtin" | "plugin:<name>" | "mcp:<server>" | "web". */
|
|
628
|
+
origin: string;
|
|
629
|
+
}
|
|
630
|
+
declare const NO_CAPS: CapabilitySet;
|
|
631
|
+
/** True iff `have` covers everything in `need`. */
|
|
632
|
+
declare function capsAllow(have: CapabilitySet, need: Partial<CapabilitySet>): boolean;
|
|
633
|
+
type PolicyAction = "allow" | "warn" | "strip" | "block";
|
|
634
|
+
interface PolicyDecision {
|
|
635
|
+
action: PolicyAction;
|
|
636
|
+
reason?: string;
|
|
637
|
+
/** Sanitized content when action is "strip" (or "allow" with redactions). */
|
|
638
|
+
sanitized?: string;
|
|
639
|
+
}
|
|
640
|
+
type Severity = "none" | "low" | "medium" | "high";
|
|
641
|
+
declare function maxSeverity(a: Severity, b: Severity): Severity;
|
|
642
|
+
declare function severityRank(s: Severity): number;
|
|
643
|
+
interface Detection {
|
|
644
|
+
/** Stable rule id, e.g. "instruction-override". */
|
|
645
|
+
rule: string;
|
|
646
|
+
severity: Severity;
|
|
647
|
+
/** The offending snippet (bounded ≤200 chars). */
|
|
648
|
+
match: string;
|
|
649
|
+
note: string;
|
|
650
|
+
}
|
|
651
|
+
interface ScanResult {
|
|
652
|
+
detections: Detection[];
|
|
653
|
+
/** Max severity across all detections (or "none"). */
|
|
654
|
+
severity: Severity;
|
|
655
|
+
}
|
|
656
|
+
interface FirewallVerdict {
|
|
657
|
+
/** What the policy decided to do with this channel. */
|
|
658
|
+
action: PolicyAction;
|
|
659
|
+
scan: ScanResult;
|
|
660
|
+
/** Content after neutralization (instructions defanged, secrets redacted, fenced). */
|
|
661
|
+
safe: string;
|
|
662
|
+
source?: string;
|
|
663
|
+
kind?: ChannelKind;
|
|
664
|
+
}
|
|
665
|
+
/** A detector inspects untrusted text and returns any detections it finds. */
|
|
666
|
+
type Detector = (text: string, ctx?: {
|
|
667
|
+
source?: string;
|
|
668
|
+
kind?: ChannelKind;
|
|
669
|
+
}) => Detection[];
|
|
670
|
+
|
|
671
|
+
interface AgentOptions {
|
|
672
|
+
model: LanguageModel;
|
|
673
|
+
modelKey: ModelKey;
|
|
674
|
+
ctx: ToolContext;
|
|
675
|
+
/** Web + MCP tools merged alongside the built-in toolset. */
|
|
676
|
+
extraTools?: ToolSet;
|
|
677
|
+
/** Hooks applied to the merged toolset + run on UserPromptSubmit. */
|
|
678
|
+
hooks?: HookRunner;
|
|
679
|
+
/** Project memory + plugin system text, appended to the system prompt. */
|
|
680
|
+
memoryText?: string;
|
|
681
|
+
thinking?: boolean;
|
|
682
|
+
maxSteps?: number;
|
|
683
|
+
contextWindow?: number;
|
|
684
|
+
session?: Session | null;
|
|
685
|
+
usage?: UsageMeter;
|
|
686
|
+
onUsage?: (u: TokenUsage) => void;
|
|
687
|
+
}
|
|
688
|
+
interface TurnResult {
|
|
689
|
+
aborted: boolean;
|
|
690
|
+
usage: TokenUsage | null;
|
|
691
|
+
}
|
|
692
|
+
declare class Agent {
|
|
693
|
+
private readonly opts;
|
|
694
|
+
private history;
|
|
695
|
+
private tools;
|
|
696
|
+
private thinking;
|
|
697
|
+
private modelKey;
|
|
698
|
+
private model;
|
|
699
|
+
private session;
|
|
700
|
+
private contextWindow;
|
|
701
|
+
/** Real input-token count the provider reported last turn (beats the char estimate). */
|
|
702
|
+
private lastInputTokens;
|
|
703
|
+
/** Output throughput of the last turn (tokens/sec, first token → stream end). */
|
|
704
|
+
lastTps: number;
|
|
705
|
+
/** SP-21 prompt/policy runtime: authority preamble + injection firewall + capability model. */
|
|
706
|
+
private readonly policy;
|
|
707
|
+
private readonly caps;
|
|
708
|
+
readonly usage: UsageMeter;
|
|
709
|
+
constructor(opts: AgentOptions);
|
|
710
|
+
get toolNames(): string[];
|
|
711
|
+
/** The declared capability manifests (for /caps + /doctor). */
|
|
712
|
+
capabilities(): CapabilityManifest[];
|
|
713
|
+
get thinkingOn(): boolean;
|
|
714
|
+
setThinking(on: boolean): void;
|
|
715
|
+
setModel(model: LanguageModel, key: ModelKey, contextWindow?: number): void;
|
|
716
|
+
setSession(session: Session | null): void;
|
|
717
|
+
reset(): void;
|
|
718
|
+
replaceHistory(messages: ModelMessage[]): void;
|
|
719
|
+
/** % of the model's context window the current history occupies. */
|
|
720
|
+
contextPct(): number;
|
|
721
|
+
private system;
|
|
722
|
+
/**
|
|
723
|
+
* Auto-compact when the history nears the context window. We budget against
|
|
724
|
+
* the MAX of the provider's last real input-token count and a char estimate
|
|
725
|
+
* that includes the system prompt (tool list, memory) — so tool/JSON-dense
|
|
726
|
+
* histories that the naive estimate under-counts still trigger in time.
|
|
727
|
+
*/
|
|
728
|
+
private maybeCompact;
|
|
729
|
+
/** Force or auto compaction; prints a one-line note. Returns true if it ran. */
|
|
730
|
+
runCompaction(announce: boolean): Promise<boolean>;
|
|
731
|
+
/** Run one user turn end-to-end; streams to stdout, updates history. */
|
|
732
|
+
send(userInput: string, signal?: AbortSignal): Promise<TurnResult>;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
interface MemorySource {
|
|
736
|
+
path: string;
|
|
737
|
+
bytes: number;
|
|
738
|
+
truncated: boolean;
|
|
739
|
+
}
|
|
740
|
+
interface ProjectMemory {
|
|
741
|
+
sources: MemorySource[];
|
|
742
|
+
text: string;
|
|
743
|
+
}
|
|
744
|
+
/**
|
|
745
|
+
* Collect memory files from the global config dir and every directory between
|
|
746
|
+
* the repo root and the cwd (root first, so cwd-local files win on conflict).
|
|
747
|
+
*/
|
|
748
|
+
declare function loadProjectMemory(cwd: string): Promise<ProjectMemory>;
|
|
749
|
+
|
|
750
|
+
/**
|
|
751
|
+
* A slim MCP client — the extensibility layer. It reads the standard `.mcp.json`
|
|
752
|
+
* format (so a project's servers Just Work), connects to each,
|
|
753
|
+
* and exposes their tools to the agent namespaced `mcp__<server>__<tool>`.
|
|
754
|
+
*
|
|
755
|
+
* Fail-soft by design: a server that won't start is reported and skipped, never
|
|
756
|
+
* fatal. We support both stdio servers ({command,args,env}) and HTTP servers
|
|
757
|
+
* ({url,headers}). No tier policy here — a CLI on your own machine trusts the
|
|
758
|
+
* servers you configured.
|
|
759
|
+
*/
|
|
760
|
+
interface StdioServerConfig {
|
|
761
|
+
command: string;
|
|
762
|
+
args?: string[];
|
|
763
|
+
env?: Record<string, string>;
|
|
764
|
+
}
|
|
765
|
+
interface HttpServerConfig {
|
|
766
|
+
url: string;
|
|
767
|
+
headers?: Record<string, string>;
|
|
768
|
+
}
|
|
769
|
+
type ServerConfig = StdioServerConfig | HttpServerConfig;
|
|
770
|
+
interface LoadedMcp {
|
|
771
|
+
tools: ToolSet;
|
|
772
|
+
mounted: {
|
|
773
|
+
key: string;
|
|
774
|
+
tools: number;
|
|
775
|
+
}[];
|
|
776
|
+
failed: {
|
|
777
|
+
key: string;
|
|
778
|
+
error: string;
|
|
779
|
+
}[];
|
|
780
|
+
close: () => Promise<void>;
|
|
781
|
+
}
|
|
782
|
+
interface LoadMcpOptions {
|
|
783
|
+
cwd: string;
|
|
784
|
+
/** Only mount these server keys (from --mcp). Empty/undefined → all configured. */
|
|
785
|
+
only?: string[];
|
|
786
|
+
/** Skip MCP entirely. */
|
|
787
|
+
disabled?: boolean;
|
|
788
|
+
/** Extra servers (e.g. contributed by plugins). File configs override these. */
|
|
789
|
+
extraServers?: Record<string, ServerConfig>;
|
|
790
|
+
connectTimeoutMs?: number;
|
|
791
|
+
/**
|
|
792
|
+
* Deferred mode (DEFAULT). Instead of binding every MCP tool's full JSON schema
|
|
793
|
+
* to the model on every turn — which can cost tens of thousands of tokens before
|
|
794
|
+
* the user types anything — expose just two tiny tools: `mcp_list_tools` (a
|
|
795
|
+
* compact catalog) and `mcp_call_tool` (the dispatcher). The model discovers and
|
|
796
|
+
* calls MCP tools on demand. Set `defer: false` to bind all schemas eagerly.
|
|
797
|
+
*/
|
|
798
|
+
defer?: boolean;
|
|
799
|
+
}
|
|
800
|
+
declare function loadMcpTools(opts: LoadMcpOptions): Promise<LoadedMcp>;
|
|
801
|
+
|
|
802
|
+
/**
|
|
803
|
+
* Slash commands — the in-session control surface. An extensible registry, not
|
|
804
|
+
* a hardcoded if/else: built-ins live here, plugins and ~/.zeta-g/commands/*.md
|
|
805
|
+
* register more. A command returns a CommandResult the REPL acts on, so the
|
|
806
|
+
* registry stays decoupled from the agent it drives.
|
|
807
|
+
*/
|
|
808
|
+
type CommandResult = {
|
|
809
|
+
type: "handled";
|
|
810
|
+
} | {
|
|
811
|
+
type: "exit";
|
|
812
|
+
} | {
|
|
813
|
+
type: "clear";
|
|
814
|
+
} | {
|
|
815
|
+
type: "compact";
|
|
816
|
+
} | {
|
|
817
|
+
type: "prompt";
|
|
818
|
+
text: string;
|
|
819
|
+
} | {
|
|
820
|
+
type: "switchModel";
|
|
821
|
+
modelKey: ModelKey;
|
|
822
|
+
} | {
|
|
823
|
+
type: "setMode";
|
|
824
|
+
mode: PermissionMode;
|
|
825
|
+
} | {
|
|
826
|
+
type: "toggleThinking";
|
|
827
|
+
on: boolean;
|
|
828
|
+
} | {
|
|
829
|
+
type: "resume";
|
|
830
|
+
sessionId: string;
|
|
831
|
+
};
|
|
832
|
+
interface CommandContext {
|
|
833
|
+
args: string;
|
|
834
|
+
cwd: string;
|
|
835
|
+
modelKey: ModelKey;
|
|
836
|
+
modelLabel: string;
|
|
837
|
+
thinkingOn: boolean;
|
|
838
|
+
permissions: Permissions;
|
|
839
|
+
usage: UsageMeter;
|
|
840
|
+
memory: ProjectMemory;
|
|
841
|
+
mcp: LoadedMcp;
|
|
842
|
+
toolNames: string[];
|
|
843
|
+
capabilities: CapabilityManifest[];
|
|
844
|
+
checkpoints: CheckpointStore;
|
|
845
|
+
commands: SlashCommand[];
|
|
846
|
+
print: (s?: string) => void;
|
|
847
|
+
}
|
|
848
|
+
interface SlashCommand {
|
|
849
|
+
name: string;
|
|
850
|
+
aliases?: string[];
|
|
851
|
+
summary: string;
|
|
852
|
+
hidden?: boolean;
|
|
853
|
+
/** Where it came from — "builtin", "custom", or a plugin name. */
|
|
854
|
+
source?: string;
|
|
855
|
+
run: (ctx: CommandContext) => Promise<CommandResult> | CommandResult;
|
|
856
|
+
}
|
|
857
|
+
/** Parse every *.md in `dir` into slash commands tagged with `source`. */
|
|
858
|
+
declare function loadMdCommands(dir: string, source: string): SlashCommand[];
|
|
859
|
+
declare class CommandRegistry {
|
|
860
|
+
private map;
|
|
861
|
+
private ordered;
|
|
862
|
+
constructor();
|
|
863
|
+
register(cmd: SlashCommand): void;
|
|
864
|
+
get(name: string): SlashCommand | undefined;
|
|
865
|
+
list(): SlashCommand[];
|
|
866
|
+
names(): string[];
|
|
867
|
+
/** Load *.md commands from ~/.zeta-g/commands and <cwd>/.zeta-g/commands. */
|
|
868
|
+
loadCustom(cwd: string): void;
|
|
869
|
+
dispatch(input: string, base: Omit<CommandContext, "args" | "commands">): Promise<CommandResult | null>;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
interface LoadedPlugins {
|
|
873
|
+
plugins: {
|
|
874
|
+
name: string;
|
|
875
|
+
description?: string;
|
|
876
|
+
version?: string;
|
|
877
|
+
dir: string;
|
|
878
|
+
}[];
|
|
879
|
+
systemText: string;
|
|
880
|
+
commands: SlashCommand[];
|
|
881
|
+
mcpServers: Record<string, ServerConfig>;
|
|
882
|
+
hooks: HookSet;
|
|
883
|
+
failed: {
|
|
884
|
+
dir: string;
|
|
885
|
+
error: string;
|
|
886
|
+
}[];
|
|
887
|
+
}
|
|
888
|
+
declare function loadPlugins(cwd: string): LoadedPlugins;
|
|
889
|
+
|
|
890
|
+
interface SearchHit {
|
|
891
|
+
title: string;
|
|
892
|
+
url: string;
|
|
893
|
+
snippet: string;
|
|
894
|
+
}
|
|
895
|
+
declare function buildWebTools(): {
|
|
896
|
+
web_fetch: ai.Tool<{
|
|
897
|
+
url: string;
|
|
898
|
+
}, {
|
|
899
|
+
ok: boolean;
|
|
900
|
+
error: string;
|
|
901
|
+
status?: undefined;
|
|
902
|
+
url?: undefined;
|
|
903
|
+
contentType?: undefined;
|
|
904
|
+
content?: undefined;
|
|
905
|
+
truncated?: undefined;
|
|
906
|
+
} | {
|
|
907
|
+
ok: boolean;
|
|
908
|
+
status: number;
|
|
909
|
+
url: string;
|
|
910
|
+
contentType: string;
|
|
911
|
+
content: string;
|
|
912
|
+
truncated: boolean;
|
|
913
|
+
error?: undefined;
|
|
914
|
+
}>;
|
|
915
|
+
web_search: ai.Tool<{
|
|
916
|
+
query: string;
|
|
917
|
+
}, {
|
|
918
|
+
ok: boolean;
|
|
919
|
+
note: string;
|
|
920
|
+
query?: undefined;
|
|
921
|
+
results?: undefined;
|
|
922
|
+
error?: undefined;
|
|
923
|
+
} | {
|
|
924
|
+
ok: boolean;
|
|
925
|
+
query: string;
|
|
926
|
+
results: SearchHit[];
|
|
927
|
+
note?: undefined;
|
|
928
|
+
error?: undefined;
|
|
929
|
+
} | {
|
|
930
|
+
ok: boolean;
|
|
931
|
+
error: string;
|
|
932
|
+
note?: undefined;
|
|
933
|
+
query?: undefined;
|
|
934
|
+
results?: undefined;
|
|
935
|
+
}>;
|
|
936
|
+
};
|
|
937
|
+
|
|
938
|
+
/** Rough token estimate — ~4 chars/token over the serialized messages. */
|
|
939
|
+
declare function estimateTokens(messages: ModelMessage[]): number;
|
|
940
|
+
interface CompactResult {
|
|
941
|
+
messages: ModelMessage[];
|
|
942
|
+
summary: string;
|
|
943
|
+
before: number;
|
|
944
|
+
after: number;
|
|
945
|
+
degraded: boolean;
|
|
946
|
+
}
|
|
947
|
+
interface CompactOptions {
|
|
948
|
+
/** How many trailing user turns to keep verbatim. */
|
|
949
|
+
keepUserTurns?: number;
|
|
950
|
+
/** Max chars of transcript fed to the summarizer. */
|
|
951
|
+
maxTranscript?: number;
|
|
952
|
+
}
|
|
953
|
+
/**
|
|
954
|
+
* Compact `messages`. Returns a new history: [summary, ack, ...recent turns].
|
|
955
|
+
* Falls back to a plain recency window (with `degraded: true`) if the model
|
|
956
|
+
* call to summarize fails — never silently claims success it didn't get.
|
|
957
|
+
*/
|
|
958
|
+
declare function compact(messages: ModelMessage[], model: LanguageModel, opts?: CompactOptions): Promise<CompactResult>;
|
|
959
|
+
|
|
960
|
+
/**
|
|
961
|
+
* SP-21 — System-prompt REGISTRY: identity, role layering, and safety overlays.
|
|
962
|
+
*
|
|
963
|
+
* One deterministic place to keep the agent's system prompts. A prompt is
|
|
964
|
+
* assembled from three layers, in a fixed order so the same inputs always
|
|
965
|
+
* yield the same bytes:
|
|
966
|
+
*
|
|
967
|
+
* 1. a BASE system body (versioned per id — register a new version, the old
|
|
968
|
+
* one stays reachable for replay/audit),
|
|
969
|
+
* 2. an optional ROLE layer (e.g. "coder" / "planner") that sharpens behavior,
|
|
970
|
+
* 3. zero or more SAFETY overlays appended last (the safety + verify reflexes),
|
|
971
|
+
* so they read as the final, binding word.
|
|
972
|
+
*
|
|
973
|
+
* Nothing here reaches for wall-clock time or randomness: composition is a pure
|
|
974
|
+
* function of what was registered. The single source of truth for shared shapes
|
|
975
|
+
* lives in ./types.js — this module only adds the registry-specific structures.
|
|
976
|
+
*/
|
|
977
|
+
/** A behavior layer for a named role, layered on top of the base system body. */
|
|
978
|
+
interface RoleLayer {
|
|
979
|
+
role: string;
|
|
980
|
+
body: string;
|
|
981
|
+
}
|
|
982
|
+
/** A safety rule appended after the base + role, so it reads as the last word. */
|
|
983
|
+
interface SafetyOverlay {
|
|
984
|
+
id: string;
|
|
985
|
+
body: string;
|
|
986
|
+
}
|
|
987
|
+
/**
|
|
988
|
+
* The registry. Holds versioned system bodies, role layers, and safety
|
|
989
|
+
* overlays, and composes them deterministically. Registration order is
|
|
990
|
+
* preserved (insertion-ordered Maps) so "latest" and "all overlays" are stable.
|
|
991
|
+
*/
|
|
992
|
+
declare class PromptRegistry {
|
|
993
|
+
/** id → list of versions, in registration order (last = latest). */
|
|
994
|
+
private readonly systems;
|
|
995
|
+
/** role name → its layer body. */
|
|
996
|
+
private readonly roles;
|
|
997
|
+
/** overlay id → overlay, in registration order. */
|
|
998
|
+
private readonly safety;
|
|
999
|
+
/**
|
|
1000
|
+
* Register a system body under `id`. If `version` is omitted, an auto label
|
|
1001
|
+
* `v<N>` is assigned from the count already stored for that id. Re-registering
|
|
1002
|
+
* the same explicit version replaces that revision in place (it keeps its
|
|
1003
|
+
* original slot); a new version is appended and becomes the latest.
|
|
1004
|
+
*/
|
|
1005
|
+
registerSystem(id: string, body: string, version?: string): void;
|
|
1006
|
+
/**
|
|
1007
|
+
* Fetch a system body. With no `version`, returns the latest (last
|
|
1008
|
+
* registered). Throws if the id — or the requested version — is unknown.
|
|
1009
|
+
*/
|
|
1010
|
+
getSystem(id: string, version?: string): string;
|
|
1011
|
+
/** The versions stored for `id`, in registration order. Empty if unknown. */
|
|
1012
|
+
versions(id: string): string[];
|
|
1013
|
+
/** Register (or replace) the layer body for a role. */
|
|
1014
|
+
registerRole(layer: RoleLayer): void;
|
|
1015
|
+
/** The body for a role, or undefined if the role is unknown. */
|
|
1016
|
+
getRole(role: string): string | undefined;
|
|
1017
|
+
/** Register (or replace) a safety overlay by its id. */
|
|
1018
|
+
registerOverlay(o: SafetyOverlay): void;
|
|
1019
|
+
/** All registered safety overlays, in registration order. */
|
|
1020
|
+
overlays(): SafetyOverlay[];
|
|
1021
|
+
/**
|
|
1022
|
+
* Compose a full system prompt: base body, then the role layer (when a known
|
|
1023
|
+
* role is given), then the safety overlays — joined by a blank line.
|
|
1024
|
+
*
|
|
1025
|
+
* Overlay selection: if `overlayIds` is provided, those overlays are appended
|
|
1026
|
+
* in the order listed (unknown ids are skipped); if omitted, ALL registered
|
|
1027
|
+
* overlays are appended in registration order.
|
|
1028
|
+
*/
|
|
1029
|
+
composeSystem(opts: {
|
|
1030
|
+
id: string;
|
|
1031
|
+
version?: string;
|
|
1032
|
+
role?: string;
|
|
1033
|
+
overlayIds?: string[];
|
|
1034
|
+
}): string;
|
|
1035
|
+
}
|
|
1036
|
+
/**
|
|
1037
|
+
* The default registry, seeded with Zeta-G's core identity, the safety and
|
|
1038
|
+
* verify overlays, and the coder/planner roles. Importers get a ready-to-use
|
|
1039
|
+
* registry; they can register additional versions, roles, or overlays on top.
|
|
1040
|
+
*/
|
|
1041
|
+
declare const DEFAULT_REGISTRY: PromptRegistry;
|
|
1042
|
+
|
|
1043
|
+
/**
|
|
1044
|
+
* Deterministic template rendering + token budgeting.
|
|
1045
|
+
*
|
|
1046
|
+
* Two small, pure jobs. First, {{key}} substitution that treats values as
|
|
1047
|
+
* literal text — a value containing "$1" or "$&" must never be reinterpreted
|
|
1048
|
+
* as a regex replacement pattern, so we substitute through a FUNCTION replacer.
|
|
1049
|
+
* Second, fitting a turn's CHANNELS inside a token budget by trimming the
|
|
1050
|
+
* lowest-authority content first: a SYSTEM channel is sacred and is never
|
|
1051
|
+
* touched, untrusted data goes first, then user, then developer.
|
|
1052
|
+
*
|
|
1053
|
+
* Everything here is deterministic — no clock, no randomness — so the same
|
|
1054
|
+
* inputs always produce the same prompt.
|
|
1055
|
+
*/
|
|
1056
|
+
|
|
1057
|
+
/**
|
|
1058
|
+
* Replace every {{key}} in `tpl` with `vars[key]`. The replacer is a function,
|
|
1059
|
+
* so any "$" sequences inside a value stay literal rather than being treated as
|
|
1060
|
+
* replacement patterns. Unknown keys: throw under `strict`, else collapse to "".
|
|
1061
|
+
*/
|
|
1062
|
+
declare function renderTemplate(tpl: string, vars: Record<string, string>, opts?: {
|
|
1063
|
+
strict?: boolean;
|
|
1064
|
+
}): string;
|
|
1065
|
+
/** Cheap, provider-agnostic token estimate: ~4 chars per token. */
|
|
1066
|
+
declare function estimateTextTokens(text: string): number;
|
|
1067
|
+
interface BudgetInput {
|
|
1068
|
+
channels: Channel[];
|
|
1069
|
+
maxTokens: number;
|
|
1070
|
+
/** Tokens to hold back for the response / scaffolding (default 0). */
|
|
1071
|
+
reserve?: number;
|
|
1072
|
+
}
|
|
1073
|
+
interface BudgetResult {
|
|
1074
|
+
channels: Channel[];
|
|
1075
|
+
/** Per-kind tally of tokens removed by trimming. */
|
|
1076
|
+
trimmed: {
|
|
1077
|
+
kind: ChannelKind;
|
|
1078
|
+
droppedTokens: number;
|
|
1079
|
+
}[];
|
|
1080
|
+
total: number;
|
|
1081
|
+
}
|
|
1082
|
+
/**
|
|
1083
|
+
* Fit `channels` within (maxTokens - reserve) by truncating the lowest-priority
|
|
1084
|
+
* channels first. Truncation keeps the HEAD of the content and appends a trim
|
|
1085
|
+
* marker. Returns fresh channel objects, the per-kind dropped-token counts, and
|
|
1086
|
+
* the final total estimate. Never trims a "system" channel.
|
|
1087
|
+
*/
|
|
1088
|
+
declare function budgetChannels(input: BudgetInput): BudgetResult;
|
|
1089
|
+
|
|
1090
|
+
/**
|
|
1091
|
+
* Prompt linting — catch "string soup" mistakes before a turn is sent.
|
|
1092
|
+
*
|
|
1093
|
+
* The channel model (see ./types.ts) keeps authority tiers separated, but it
|
|
1094
|
+
* can't stop a caller from assembling a malformed prompt: an empty system
|
|
1095
|
+
* message, a leaked API key pasted into a developer note, an untrusted tool
|
|
1096
|
+
* result mislabeled as trusted, or a template that never got its variables
|
|
1097
|
+
* filled in. These are cheap to detect and expensive to ship, so we surface
|
|
1098
|
+
* them as plain findings the CLI can print (or refuse on) before the provider
|
|
1099
|
+
* ever sees the text.
|
|
1100
|
+
*
|
|
1101
|
+
* Everything here is pure and deterministic — same channels in, same findings
|
|
1102
|
+
* out, no clock and no randomness.
|
|
1103
|
+
*/
|
|
1104
|
+
|
|
1105
|
+
/** How loudly a finding should be surfaced. Mirrors the firewall's tone, not its scale. */
|
|
1106
|
+
type LintSeverity = "error" | "warning" | "info";
|
|
1107
|
+
interface LintFinding {
|
|
1108
|
+
/** Stable rule id, e.g. "empty-body" or "leaked-secret". */
|
|
1109
|
+
rule: string;
|
|
1110
|
+
severity: LintSeverity;
|
|
1111
|
+
message: string;
|
|
1112
|
+
/** Which channel kind the finding belongs to, when it came from a specific channel. */
|
|
1113
|
+
channel?: ChannelKind;
|
|
1114
|
+
}
|
|
1115
|
+
/**
|
|
1116
|
+
* Lint a single system- or developer-class body. These channels carry
|
|
1117
|
+
* instruction authority, so mistakes here are the most damaging: an empty
|
|
1118
|
+
* system prompt silently neuters the agent, and a secret pasted into one rides
|
|
1119
|
+
* straight to the provider with full trust.
|
|
1120
|
+
*/
|
|
1121
|
+
declare function lintSystemText(body: string): LintFinding[];
|
|
1122
|
+
/**
|
|
1123
|
+
* Lint a fully assembled prompt — the ordered list of channels for one turn.
|
|
1124
|
+
* Checks the shape of the prompt as a whole (a system channel must exist, the
|
|
1125
|
+
* trust labels must match each channel's kind) and folds in per-body linting
|
|
1126
|
+
* for every instruction-bearing channel.
|
|
1127
|
+
*/
|
|
1128
|
+
declare function lintPrompt(channels: Channel[]): LintFinding[];
|
|
1129
|
+
|
|
1130
|
+
/**
|
|
1131
|
+
* The POLICY ENGINE — the layer that turns a firewall SCAN into an ACTION.
|
|
1132
|
+
*
|
|
1133
|
+
* A detector tells us *what* it saw; the policy decides *what to do about it*.
|
|
1134
|
+
* Severity maps to one of four actions (allow / warn / strip / block) per a
|
|
1135
|
+
* small, overridable config, so an operator can tighten or loosen the firewall
|
|
1136
|
+
* without touching detection code. The engine also owns the AUTHORITY PREAMBLE:
|
|
1137
|
+
* the SYSTEM-prompt block that teaches the model the prompt-authority tiers and
|
|
1138
|
+
* the one inviolable rule — text inside fenced UNTRUSTED blocks is DATA, never
|
|
1139
|
+
* an instruction. Pure and deterministic: same scan in, same decision out.
|
|
1140
|
+
*/
|
|
1141
|
+
|
|
1142
|
+
/** How the firewall behaves: the per-severity action table plus two hard switches. */
|
|
1143
|
+
interface PolicyConfig {
|
|
1144
|
+
/** Action to take for each injection severity the scanner reports. */
|
|
1145
|
+
onInjection: Record<Severity, PolicyAction>;
|
|
1146
|
+
/** Redact detected secrets/credentials before content reaches the provider. */
|
|
1147
|
+
blockSecrets: boolean;
|
|
1148
|
+
/** Neutralize exfiltration attempts (data-routed-outward) found in untrusted data. */
|
|
1149
|
+
stripExfil: boolean;
|
|
1150
|
+
}
|
|
1151
|
+
/** The safe default: clean passes through, suspicion is stripped, blatant injection is blocked. */
|
|
1152
|
+
declare const DEFAULT_POLICY: PolicyConfig;
|
|
1153
|
+
/**
|
|
1154
|
+
* Maps firewall severity to a policy action and frames the prompt-authority
|
|
1155
|
+
* contract. Stateless aside from its merged config — construct once, reuse.
|
|
1156
|
+
*/
|
|
1157
|
+
declare class PolicyEngine {
|
|
1158
|
+
readonly config: PolicyConfig;
|
|
1159
|
+
constructor(config?: Partial<PolicyConfig>);
|
|
1160
|
+
/**
|
|
1161
|
+
* Decide what to do with a scanned channel. The action is read straight off
|
|
1162
|
+
* the severity table; the reason summarizes the detections that drove it so
|
|
1163
|
+
* the choice is legible in logs and to the user.
|
|
1164
|
+
*/
|
|
1165
|
+
decideChannel(scan: ScanResult): PolicyDecision;
|
|
1166
|
+
/**
|
|
1167
|
+
* The SYSTEM-prompt block that establishes prompt authority and the
|
|
1168
|
+
* untrusted-data rule. Prepended ahead of any external content so the model
|
|
1169
|
+
* carries the contract into every turn.
|
|
1170
|
+
*/
|
|
1171
|
+
authorityPreamble(): string;
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
/**
|
|
1175
|
+
* SP-21 — The anti-prompt-injection FIREWALL.
|
|
1176
|
+
*
|
|
1177
|
+
* Untrusted channels (tool output, RAG hits, MCP results, fetched pages) are
|
|
1178
|
+
* data, not instructions. This module is the chokepoint they pass through
|
|
1179
|
+
* before reaching the provider: every detector runs over the text, the policy
|
|
1180
|
+
* engine decides what to do, and whatever survives is fenced inside a clearly
|
|
1181
|
+
* labelled DATA-ONLY block so the model treats it as content to reason ABOUT,
|
|
1182
|
+
* never as commands to obey.
|
|
1183
|
+
*
|
|
1184
|
+
* Pure and deterministic: same text in, same verdict out. No clock, no random.
|
|
1185
|
+
*/
|
|
1186
|
+
|
|
1187
|
+
declare class PromptFirewall {
|
|
1188
|
+
private readonly detectors;
|
|
1189
|
+
constructor(detectors?: Detector[]);
|
|
1190
|
+
/**
|
|
1191
|
+
* Run every detector over the (bounded) text and fold their findings into a
|
|
1192
|
+
* single ScanResult. Severity starts at "none" and climbs via maxSeverity.
|
|
1193
|
+
*/
|
|
1194
|
+
scan(text: string, ctx?: {
|
|
1195
|
+
source?: string;
|
|
1196
|
+
kind?: ChannelKind;
|
|
1197
|
+
}): ScanResult;
|
|
1198
|
+
/**
|
|
1199
|
+
* Wrap content in the labelled DATA-ONLY fence. Credential material and the
|
|
1200
|
+
* override imperative itself are ALWAYS scrubbed first via live regex over the
|
|
1201
|
+
* body (redactSensitive) — not by string-matching a lossy clipped snippet — so
|
|
1202
|
+
* collapsed-whitespace or oversized spans can't slip through. `opts` is kept
|
|
1203
|
+
* for API compatibility; redaction is unconditional now.
|
|
1204
|
+
*/
|
|
1205
|
+
neutralize(text: string, _scan: ScanResult, _opts?: {
|
|
1206
|
+
redactSecrets?: boolean;
|
|
1207
|
+
}): string;
|
|
1208
|
+
/**
|
|
1209
|
+
* The full pipeline for one untrusted channel: normalize (defang homoglyph /
|
|
1210
|
+
* zero-width evasion), scan, ask the policy what to do, then produce the safe
|
|
1211
|
+
* rendering. We scan AND forward the SAME normalized+bounded body, so nothing
|
|
1212
|
+
* unscanned ever reaches the model.
|
|
1213
|
+
* block → withhold entirely (just a breadcrumb of what was dropped)
|
|
1214
|
+
* strip / warn / allow → fenced + redacted untrusted data
|
|
1215
|
+
*/
|
|
1216
|
+
guard(channel: Channel, policy: PolicyEngine): FirewallVerdict;
|
|
1217
|
+
}
|
|
1218
|
+
/** Shared default firewall wired to the full detector suite. */
|
|
1219
|
+
declare const DEFAULT_FIREWALL: PromptFirewall;
|
|
1220
|
+
|
|
1221
|
+
/**
|
|
1222
|
+
* ChannelStack — the assembler that keeps prompt authority tiers isolated.
|
|
1223
|
+
*
|
|
1224
|
+
* You add content as typed channels; on render() the trusted channels
|
|
1225
|
+
* (system/developer/user) are concatenated as authoritative instructions, while
|
|
1226
|
+
* every UNTRUSTED channel (tool/mcp/web/retrieved/memory) is run through the
|
|
1227
|
+
* firewall and emitted as fenced DATA — so "ignore previous instructions" inside
|
|
1228
|
+
* a tool result or a fetched page can't seize control. Optionally budgeted so a
|
|
1229
|
+
* giant retrieved blob can't blow the context window (system is never trimmed).
|
|
1230
|
+
*/
|
|
1231
|
+
declare class ChannelStack {
|
|
1232
|
+
private list;
|
|
1233
|
+
add(kind: ChannelKind, content: string, opts?: {
|
|
1234
|
+
source?: string;
|
|
1235
|
+
}): this;
|
|
1236
|
+
/** Convenience for the untrusted families — always firewall-fenced on render. */
|
|
1237
|
+
addUntrusted(kind: Extract<ChannelKind, "retrieved" | "tool" | "mcp" | "web" | "memory">, content: string, source?: string): this;
|
|
1238
|
+
channels(): Channel[];
|
|
1239
|
+
/**
|
|
1240
|
+
* Render the stack into provider-ready text, with hard tier isolation:
|
|
1241
|
+
* · `system` — system + developer channels (authoritative)
|
|
1242
|
+
* · `user` — user channels (the human's literal input)
|
|
1243
|
+
* · `dataBlocks` — every untrusted channel, firewalled + fenced as data
|
|
1244
|
+
* Returns the firewall verdicts so the caller can warn on detections.
|
|
1245
|
+
*/
|
|
1246
|
+
render(opts: {
|
|
1247
|
+
policy: PolicyEngine;
|
|
1248
|
+
firewall: PromptFirewall;
|
|
1249
|
+
budget?: number;
|
|
1250
|
+
}): {
|
|
1251
|
+
system: string;
|
|
1252
|
+
user: string;
|
|
1253
|
+
dataBlocks: string;
|
|
1254
|
+
findings: FirewallVerdict[];
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
/**
|
|
1259
|
+
* SP-21 — Capability security model.
|
|
1260
|
+
*
|
|
1261
|
+
* Every tool, plugin, and MCP server runs with the LEAST authority it needs:
|
|
1262
|
+
* a small, declared set of { fs, network, shell, secrets } capabilities. This
|
|
1263
|
+
* module is the single chokepoint where those grants are declared and checked.
|
|
1264
|
+
*
|
|
1265
|
+
* The flow: on registration a caller `declare`s a manifest (or asks
|
|
1266
|
+
* `defaultManifestFor` to infer one from a known tool name); `check` resolves a
|
|
1267
|
+
* requirement against it. Anything we don't recognise gets NO_CAPS — grant
|
|
1268
|
+
* nothing until someone declares otherwise.
|
|
1269
|
+
*
|
|
1270
|
+
* STATUS: this layer is ADVISORY this pass — manifests are declared and shown
|
|
1271
|
+
* (`/caps`), and runtime side-effects are still gated by the permission layer
|
|
1272
|
+
* (shell/writes/commands) and the prompt firewall. `check()` is the hook a
|
|
1273
|
+
* future sandbox (SP-24) calls to hard-enforce caps before a syscall; it is not
|
|
1274
|
+
* yet on the execute path, so treat the manifest as a declared contract, not an
|
|
1275
|
+
* enforced boundary.
|
|
1276
|
+
*/
|
|
1277
|
+
|
|
1278
|
+
/** Infer a conservative capability set from a known tool name + its origin. */
|
|
1279
|
+
declare function defaultManifestFor(toolName: string, origin: string): CapabilityManifest;
|
|
1280
|
+
/**
|
|
1281
|
+
* The central capability registry + gatekeeper. Manifests are declared once;
|
|
1282
|
+
* every privileged action is checked against the resolved grant. Pure and
|
|
1283
|
+
* deterministic — the only state is the declared manifest map.
|
|
1284
|
+
*/
|
|
1285
|
+
declare class CapabilityEnforcer {
|
|
1286
|
+
private readonly manifests;
|
|
1287
|
+
/** Register (or overwrite) the manifest governing a tool/plugin/MCP id. */
|
|
1288
|
+
declare(manifest: CapabilityManifest): void;
|
|
1289
|
+
/**
|
|
1290
|
+
* Resolve the manifest for a name. Prefers an exact match; otherwise falls
|
|
1291
|
+
* back to a declared wildcard ("mcp__github__*" governs "mcp__github__x").
|
|
1292
|
+
* Returns undefined when nothing covers the name.
|
|
1293
|
+
*/
|
|
1294
|
+
get(name: string): CapabilityManifest | undefined;
|
|
1295
|
+
/**
|
|
1296
|
+
* Decide whether `name` may exercise the requested capabilities. The grant
|
|
1297
|
+
* is the resolved manifest, or NO_CAPS when nothing is declared — so an
|
|
1298
|
+
* unknown tool is denied anything beyond the empty set.
|
|
1299
|
+
*/
|
|
1300
|
+
check(name: string, need: Partial<CapabilitySet>): PolicyDecision;
|
|
1301
|
+
/** All declared manifests, in declaration order. */
|
|
1302
|
+
list(): CapabilityManifest[];
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
/**
|
|
1306
|
+
* SP-21 — Prompt-injection DETECTORS.
|
|
1307
|
+
*
|
|
1308
|
+
* A detector reads a slice of UNTRUSTED text (a tool result, a fetched page, a
|
|
1309
|
+
* RAG chunk) and reports anything that looks like it is trying to seize the
|
|
1310
|
+
* conversation, smuggle secrets out, or hijack a tool. Detectors NEVER mutate
|
|
1311
|
+
* the text — they only describe what they saw. The firewall layer decides what
|
|
1312
|
+
* to do (warn / strip / block) with the verdicts these produce.
|
|
1313
|
+
*
|
|
1314
|
+
* Two house rules keep this file safe and predictable:
|
|
1315
|
+
* 1. Every regex is LINEAR-TIME. No nested quantifiers over open text
|
|
1316
|
+
* (`(a+)+`, `(.*)*`), so a hostile input can never trigger ReDoS — the
|
|
1317
|
+
* worst case is a single linear pass. Where we'd want "anything", we use a
|
|
1318
|
+
* bounded character run (`[^\n]{0,80}`) instead of an unbounded `.*`.
|
|
1319
|
+
* 2. Every reported `match` is clamped to ≤200 chars so a giant payload can't
|
|
1320
|
+
* blow up logs or the verdict it rides in.
|
|
1321
|
+
*
|
|
1322
|
+
* Pure and deterministic: no clock, no randomness, no I/O.
|
|
1323
|
+
*/
|
|
1324
|
+
|
|
1325
|
+
/** The full battery, in the order the firewall runs them. */
|
|
1326
|
+
declare const ALL_DETECTORS: Detector[];
|
|
1327
|
+
|
|
1328
|
+
declare function isUntrustedTool(name: string): boolean;
|
|
1329
|
+
interface FirewallToolOptions {
|
|
1330
|
+
firewall: PromptFirewall;
|
|
1331
|
+
policy: PolicyEngine;
|
|
1332
|
+
/** Notified for every untrusted output processed (for terminal warnings). */
|
|
1333
|
+
onFinding?: (verdict: FirewallVerdict, tool: string) => void;
|
|
1334
|
+
}
|
|
1335
|
+
declare function applyFirewall(tools: ToolSet, opts: FirewallToolOptions): ToolSet;
|
|
1336
|
+
|
|
1337
|
+
/**
|
|
1338
|
+
* Colored unified-diff rendering — the inline edit preview. We show the
|
|
1339
|
+
* user exactly what an edit changes before it touches disk, so an approval is
|
|
1340
|
+
* an informed one. Built on the `diff` package's line differ, then painted with
|
|
1341
|
+
* our calm ANSI palette: green +, red -, dim context, with collapsed runs of
|
|
1342
|
+
* unchanged lines so a one-line edit doesn't print the whole file.
|
|
1343
|
+
*/
|
|
1344
|
+
interface DiffStat {
|
|
1345
|
+
added: number;
|
|
1346
|
+
removed: number;
|
|
1347
|
+
}
|
|
1348
|
+
/** Count added / removed lines between two texts. */
|
|
1349
|
+
declare function diffStat(before: string, after: string): DiffStat;
|
|
1350
|
+
/** Print a colored diff for an edit (before → after) under a path header. */
|
|
1351
|
+
declare function renderEditDiff(path: string, before: string, after: string, opts?: {
|
|
1352
|
+
context?: number;
|
|
1353
|
+
maxLines?: number;
|
|
1354
|
+
}): void;
|
|
1355
|
+
/** Print a new-file preview (first N lines, all green). */
|
|
1356
|
+
declare function renderNewFileDiff(path: string, content: string, opts?: {
|
|
1357
|
+
maxLines?: number;
|
|
1358
|
+
}): void;
|
|
1359
|
+
|
|
1360
|
+
interface InputOptions {
|
|
1361
|
+
/** Live list of slash-command names (without the leading /), for completion. */
|
|
1362
|
+
commandNames: () => string[];
|
|
1363
|
+
/** Called when the user asks to quit (empty line + Ctrl-C, or twice). */
|
|
1364
|
+
onExit: () => void;
|
|
1365
|
+
}
|
|
1366
|
+
declare class InputController {
|
|
1367
|
+
private readonly opts;
|
|
1368
|
+
private rl;
|
|
1369
|
+
constructor(opts: InputOptions);
|
|
1370
|
+
/** Tab completion: /commands when the line starts with /, @paths otherwise. */
|
|
1371
|
+
private complete;
|
|
1372
|
+
/** Read one logical line, joining trailing-backslash continuations. */
|
|
1373
|
+
readLine(promptStr: string): Promise<string>;
|
|
1374
|
+
private saveHistory;
|
|
1375
|
+
/**
|
|
1376
|
+
* Ask a y/n/a-style question; resolves to the chosen choice key. Safe to call
|
|
1377
|
+
* mid-turn: if an interrupt-watch is active (raw mode) we suspend it, ask in
|
|
1378
|
+
* cooked mode, then re-arm — so ESC works before AND after a confirm, and the
|
|
1379
|
+
* prompt itself reads a normal line.
|
|
1380
|
+
*/
|
|
1381
|
+
confirm(question: string, choices: ConfirmChoice[]): Promise<string>;
|
|
1382
|
+
private activeWatch;
|
|
1383
|
+
private onKey?;
|
|
1384
|
+
private startWatch;
|
|
1385
|
+
private stopWatch;
|
|
1386
|
+
/**
|
|
1387
|
+
* Run `fn` with an AbortSignal that fires on ESC / Ctrl-C — a streaming turn
|
|
1388
|
+
* can be interrupted cleanly without killing the process.
|
|
1389
|
+
*/
|
|
1390
|
+
runInterruptible<T>(fn: (signal: AbortSignal) => Promise<T>): Promise<T>;
|
|
1391
|
+
close(): void;
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
export { ALL_DETECTORS, Agent, type AgentOptions, type Authority, CapabilityEnforcer, type CapabilityManifest, type CapabilitySet, type Channel, type ChannelKind, ChannelStack, type CommandContext, CommandRegistry, type CommandResult, type ConfirmFn, DEFAULT_FIREWALL, DEFAULT_POLICY, DEFAULT_REGISTRY, type Detection, type Detector, type FirewallVerdict, type FsAccess, type HookEvent, HookRunner, type HookSet, InputController, type LintFinding, type LoadedMcp, type LoadedPlugins, type ModelKey, NO_CAPS, PERMISSION_MODES, type PermissionMode, Permissions, type PolicyAction, type PolicyConfig, type PolicyDecision, PolicyEngine, type ProjectMemory, PromptFirewall, PromptRegistry, type RoleLayer, type SafetyOverlay, type ScanResult, type ServerConfig, Session, type SessionMeta, type SessionSummary, type Severity, type SlashCommand, type TokenUsage, type ToolContext, type TurnResult, UNTRUSTED_CHANNELS, UsageMeter, applyFirewall, applyHooks, budgetChannels, buildProviderOptions, buildTools, buildWebTools, capsAllow, channelAuthority, compact, defaultManifestFor, diffStat, estimateCost, estimateTextTokens, estimateTokens, isPermissionMode, isUntrusted, isUntrustedTool, lintPrompt, lintSystemText, listModels, loadHookFiles, loadMcpTools, loadMdCommands, loadPlugins, loadProjectMemory, maxSeverity, mergeHookSets, modelContextWindow, modelId, modelLabel, renderEditDiff, renderNewFileDiff, renderTemplate, resolveModel, resolveModelKey, severityRank, supportsThinking };
|