@theokit/agents 8.5.2 → 8.7.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/CHANGELOG.md +1637 -0
- package/README.md +94 -0
- package/dist/auth.d.ts +84 -2
- package/dist/auth.js +152 -3
- package/dist/auth.js.map +1 -1
- package/dist/{bridge-entry-BEniSXWE.d.ts → bridge-entry-51lU7LQw.d.ts} +15 -0
- package/dist/bridge.d.ts +1 -1
- package/dist/bridge.js +2 -1
- package/dist/chunk-KMJVKPKH.js +72 -0
- package/dist/chunk-KMJVKPKH.js.map +1 -0
- package/dist/chunk-RKWCXVYG.js +100 -0
- package/dist/chunk-RKWCXVYG.js.map +1 -0
- package/dist/{chunk-C7UXZWVY.js → chunk-RZCNKKOG.js} +19 -2
- package/dist/chunk-RZCNKKOG.js.map +1 -0
- package/dist/config.d.ts +534 -0
- package/dist/config.js +655 -0
- package/dist/config.js.map +1 -0
- package/dist/hooks.d.ts +90 -1
- package/dist/hooks.js +119 -28
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/session.d.ts +33 -10
- package/dist/session.js +34 -3
- package/dist/session.js.map +1 -1
- package/package.json +6 -1
- package/dist/chunk-C7UXZWVY.js.map +0 -1
package/dist/config.js
ADDED
|
@@ -0,0 +1,655 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__name
|
|
3
|
+
} from "./chunk-Z4QWC7IK.js";
|
|
4
|
+
|
|
5
|
+
// src/config/layered-config.ts
|
|
6
|
+
import { foldLayers, verifyLayerOrdering } from "@theokit/sdk";
|
|
7
|
+
import { TheokitAgentError } from "@theokit/sdk/errors";
|
|
8
|
+
var LayerOutOfOrderError = class extends TheokitAgentError {
|
|
9
|
+
static {
|
|
10
|
+
__name(this, "LayerOutOfOrderError");
|
|
11
|
+
}
|
|
12
|
+
name = "LayerOutOfOrderError";
|
|
13
|
+
constructor(message) {
|
|
14
|
+
super(message, {
|
|
15
|
+
code: "config_layer_out_of_order",
|
|
16
|
+
isRetryable: false
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
var LayeredConfig = {
|
|
21
|
+
/**
|
|
22
|
+
* Fold the chain, validate the result, and report where everything came from.
|
|
23
|
+
*
|
|
24
|
+
* The schema is applied AFTER folding, never per layer. Validating each layer separately would
|
|
25
|
+
* force every file to be complete, which defeats layering: a project override that sets one key
|
|
26
|
+
* would have to restate the whole config.
|
|
27
|
+
*
|
|
28
|
+
* @throws {LayerOutOfOrderError} when a layer does not outrank the one before it.
|
|
29
|
+
*/
|
|
30
|
+
resolve(input) {
|
|
31
|
+
assertOrdering(input.layers);
|
|
32
|
+
const accumulating = input.accumulatingKeys ?? [];
|
|
33
|
+
const folded = foldLayers(input.layers, accumulating);
|
|
34
|
+
const value = input.schema.parse(folded);
|
|
35
|
+
return {
|
|
36
|
+
value,
|
|
37
|
+
provenancePerKey: buildProvenance(input.layers, accumulating),
|
|
38
|
+
precedenceReport: buildReport(input.layers)
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
function assertOrdering(layers) {
|
|
43
|
+
try {
|
|
44
|
+
verifyLayerOrdering(layers);
|
|
45
|
+
} catch (error) {
|
|
46
|
+
throw new LayerOutOfOrderError(error.message);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
__name(assertOrdering, "assertOrdering");
|
|
50
|
+
function contributorsOf(layers, key) {
|
|
51
|
+
return layers.filter((layer) => key in layer.values).map((layer) => layer.layer);
|
|
52
|
+
}
|
|
53
|
+
__name(contributorsOf, "contributorsOf");
|
|
54
|
+
function buildProvenance(layers, accumulating) {
|
|
55
|
+
const keys = new Set(layers.flatMap((layer) => Object.keys(layer.values)));
|
|
56
|
+
const provenance = {};
|
|
57
|
+
for (const key of keys) {
|
|
58
|
+
const contributors = contributorsOf(layers, key);
|
|
59
|
+
const winner = contributors.at(-1);
|
|
60
|
+
if (winner === void 0) continue;
|
|
61
|
+
provenance[key] = accumulating.includes(key) ? contributors.join(", ") : winner;
|
|
62
|
+
}
|
|
63
|
+
return provenance;
|
|
64
|
+
}
|
|
65
|
+
__name(buildProvenance, "buildProvenance");
|
|
66
|
+
function buildReport(layers) {
|
|
67
|
+
const declared = layers.map((layer) => layer.layer);
|
|
68
|
+
const contributed = new Set(layers.filter((layer) => Object.keys(layer.values).length > 0).map((layer) => layer.layer));
|
|
69
|
+
const measured = declared.filter((name) => contributed.has(name));
|
|
70
|
+
const declaredButSilent = declared.filter((name) => !contributed.has(name));
|
|
71
|
+
return {
|
|
72
|
+
declared,
|
|
73
|
+
measured,
|
|
74
|
+
declaredButSilent,
|
|
75
|
+
diverges: declaredButSilent.length > 0
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
__name(buildReport, "buildReport");
|
|
79
|
+
|
|
80
|
+
// src/config/trust-store.ts
|
|
81
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, realpathSync, statSync } from "fs";
|
|
82
|
+
import { dirname, resolve } from "path";
|
|
83
|
+
import { resolveTrustPosture } from "@theokit/sdk";
|
|
84
|
+
import { TheokitAgentError as TheokitAgentError2 } from "@theokit/sdk/errors";
|
|
85
|
+
import { atomicWriteJson, withFileLock } from "@theokit/sdk/persistence";
|
|
86
|
+
var TrustStorePermissionsError = class extends TheokitAgentError2 {
|
|
87
|
+
static {
|
|
88
|
+
__name(this, "TrustStorePermissionsError");
|
|
89
|
+
}
|
|
90
|
+
file;
|
|
91
|
+
mode;
|
|
92
|
+
name = "TrustStorePermissionsError";
|
|
93
|
+
constructor(file, mode) {
|
|
94
|
+
super(
|
|
95
|
+
`trust store ${file} is mode ${mode.toString(8)} \u2014 group or world writable. This file decides which directories may run shell hooks, so a writable store is a way to grant that to yourself. Refused rather than repaired: tightening it silently would hide that something changed the mode. Fix with \`chmod 600 ${file}\`.`,
|
|
96
|
+
// Refusing, not repairing: a permission that another user can set is not a transient fault.
|
|
97
|
+
{
|
|
98
|
+
code: "trust_store_insecure_mode",
|
|
99
|
+
isRetryable: false
|
|
100
|
+
}
|
|
101
|
+
), this.file = file, this.mode = mode;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
var FORBIDDEN_WRITE_BITS = 18;
|
|
105
|
+
function canonicalDir(path) {
|
|
106
|
+
try {
|
|
107
|
+
return realpathSync(path);
|
|
108
|
+
} catch {
|
|
109
|
+
return resolve(path);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
__name(canonicalDir, "canonicalDir");
|
|
113
|
+
var TrustStore = class {
|
|
114
|
+
static {
|
|
115
|
+
__name(this, "TrustStore");
|
|
116
|
+
}
|
|
117
|
+
file;
|
|
118
|
+
constructor(file) {
|
|
119
|
+
this.file = file;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Whether `path` carries a recorded decision to trust it.
|
|
123
|
+
*
|
|
124
|
+
* Denies on anything else — never recorded, recorded as refused, or unresolvable. A refusal on
|
|
125
|
+
* record (`trusted: false`) is a different fact from "never asked", and neither is trust.
|
|
126
|
+
*/
|
|
127
|
+
isTrusted(path) {
|
|
128
|
+
const key = canonicalDir(path);
|
|
129
|
+
return this.read().some((r) => canonicalDir(r.path) === key && r.trusted);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Read the store, refusing a file other users can write.
|
|
133
|
+
*
|
|
134
|
+
* A missing store is not an error — it is a machine that has trusted nothing yet, which is the
|
|
135
|
+
* correct starting state and the safe one.
|
|
136
|
+
*/
|
|
137
|
+
read() {
|
|
138
|
+
if (!existsSync(this.file)) return [];
|
|
139
|
+
this.assertSafePermissions();
|
|
140
|
+
const parsed = JSON.parse(readFileSync(this.file, "utf8"));
|
|
141
|
+
return parsed.records;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Record a decision about `path`, replacing any previous one for it.
|
|
145
|
+
*
|
|
146
|
+
* `decidedAt` and `decidedBy` are ARGUMENTS, not derived here (DIP): the clock and the identity
|
|
147
|
+
* belong to the caller, and baking `new Date()` in would make every assertion about the record
|
|
148
|
+
* depend on when the test ran.
|
|
149
|
+
*
|
|
150
|
+
* ASYNC because both `withFileLock` and `atomicWriteJson` are. Measured, not assumed: the first
|
|
151
|
+
* draft called them synchronously and `trust()` returned before the bytes landed, so an immediate
|
|
152
|
+
* `read()` saw an empty store. Same shape as the M71 pointer bug, and same cause — the SDK's
|
|
153
|
+
* `.d.ts` does not declare these, so nothing at compile time says they return a Promise
|
|
154
|
+
* (usetheodev/theokit-sdk#280).
|
|
155
|
+
*/
|
|
156
|
+
async trust(record) {
|
|
157
|
+
mkdirSync(dirname(this.file), {
|
|
158
|
+
recursive: true
|
|
159
|
+
});
|
|
160
|
+
await withFileLock(this.file, async () => {
|
|
161
|
+
const existing = existsSync(this.file) ? this.read() : [];
|
|
162
|
+
const key = canonicalDir(record.path);
|
|
163
|
+
const records = [
|
|
164
|
+
...existing.filter((r) => canonicalDir(r.path) !== key),
|
|
165
|
+
record
|
|
166
|
+
];
|
|
167
|
+
await atomicWriteJson(this.file, {
|
|
168
|
+
version: 1,
|
|
169
|
+
records
|
|
170
|
+
});
|
|
171
|
+
chmodSync(this.file, 384);
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* The recorded posture for `path`, or an UNTRUSTED posture when nothing was recorded.
|
|
176
|
+
*
|
|
177
|
+
* Absence resolves to untrusted, never to "unknown, proceed". A store that answered "I do not
|
|
178
|
+
* know" would push the decision back to the caller, and the caller asking is what the store
|
|
179
|
+
* exists to answer.
|
|
180
|
+
*/
|
|
181
|
+
postureFor(path, capabilities) {
|
|
182
|
+
const record = this.read().find((r) => r.path === path);
|
|
183
|
+
return resolveTrustPosture({
|
|
184
|
+
capabilities: [
|
|
185
|
+
...capabilities
|
|
186
|
+
],
|
|
187
|
+
isTrusted: /* @__PURE__ */ __name(() => record?.trusted === true, "isTrusted")
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
assertSafePermissions() {
|
|
191
|
+
const mode = statSync(this.file).mode & 511;
|
|
192
|
+
if ((mode & FORBIDDEN_WRITE_BITS) !== 0) {
|
|
193
|
+
throw new TrustStorePermissionsError(this.file, mode);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
// src/config/instruction-imports.ts
|
|
199
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, realpathSync as realpathSync2 } from "fs";
|
|
200
|
+
import { dirname as dirname2, resolve as resolve2 } from "path";
|
|
201
|
+
var MAX_IMPORT_DEPTH = 4;
|
|
202
|
+
var IMPORT_REGEX = /(?<![\w`])@([\w~./-]+\.md)\b/g;
|
|
203
|
+
function maskCodeSpans(text) {
|
|
204
|
+
return text.replace(/```[\s\S]*?(```|$)/g, (m) => m.replace(/[^\n]/g, " ")).replace(/`[^`\n]*`/g, (m) => " ".repeat(m.length));
|
|
205
|
+
}
|
|
206
|
+
__name(maskCodeSpans, "maskCodeSpans");
|
|
207
|
+
function insideRoot(target, rootDir) {
|
|
208
|
+
try {
|
|
209
|
+
const real = realpathSync2(target);
|
|
210
|
+
const realRoot = realpathSync2(rootDir);
|
|
211
|
+
return real === realRoot || real.startsWith(`${realRoot}/`);
|
|
212
|
+
} catch {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
__name(insideRoot, "insideRoot");
|
|
217
|
+
function importTarget(name, filePath, rootDir, visited, warn) {
|
|
218
|
+
if (name.startsWith("~/")) {
|
|
219
|
+
warn(`instruction import @${name} in ${filePath} is outside the project root \u2014 kept literal`);
|
|
220
|
+
return void 0;
|
|
221
|
+
}
|
|
222
|
+
const target = resolve2(dirname2(filePath), name);
|
|
223
|
+
if (!existsSync2(target)) {
|
|
224
|
+
warn(`instruction import @${name} in ${filePath} was not found \u2014 kept literal`);
|
|
225
|
+
return void 0;
|
|
226
|
+
}
|
|
227
|
+
if (!insideRoot(target, rootDir)) {
|
|
228
|
+
warn(`instruction import @${name} in ${filePath} is outside the project root \u2014 kept literal`);
|
|
229
|
+
return void 0;
|
|
230
|
+
}
|
|
231
|
+
return visited.has(target) ? void 0 : target;
|
|
232
|
+
}
|
|
233
|
+
__name(importTarget, "importTarget");
|
|
234
|
+
function expandInstructionImports(input) {
|
|
235
|
+
return expand(input.text, input.filePath, {
|
|
236
|
+
rootDir: input.rootDir,
|
|
237
|
+
warn: input.onWarn,
|
|
238
|
+
// Seeded with what the caller already read, so a file its walk loaded is not inlined a second
|
|
239
|
+
// time by an import that names it.
|
|
240
|
+
visited: new Set(input.alreadyLoaded ?? []),
|
|
241
|
+
depth: 0,
|
|
242
|
+
...input.wrap === void 0 ? {} : {
|
|
243
|
+
wrap: input.wrap
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
__name(expandInstructionImports, "expandInstructionImports");
|
|
248
|
+
function expand(text, filePath, ctx) {
|
|
249
|
+
const masked = maskCodeSpans(text);
|
|
250
|
+
const matches = [
|
|
251
|
+
...masked.matchAll(IMPORT_REGEX)
|
|
252
|
+
];
|
|
253
|
+
if (matches.length === 0) return text;
|
|
254
|
+
if (ctx.depth >= MAX_IMPORT_DEPTH) {
|
|
255
|
+
ctx.warn(`instruction import depth cap (${String(MAX_IMPORT_DEPTH)}) reached in ${filePath} \u2014 deeper imports kept literal`);
|
|
256
|
+
return text;
|
|
257
|
+
}
|
|
258
|
+
const out = [];
|
|
259
|
+
let cursor = 0;
|
|
260
|
+
for (const match of matches) {
|
|
261
|
+
const name = match[1];
|
|
262
|
+
out.push(text.slice(cursor, match.index));
|
|
263
|
+
cursor = match.index + match[0].length;
|
|
264
|
+
const target = importTarget(name, filePath, ctx.rootDir, ctx.visited, ctx.warn);
|
|
265
|
+
if (target === void 0) {
|
|
266
|
+
out.push(match[0]);
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
ctx.visited.add(target);
|
|
270
|
+
let content;
|
|
271
|
+
try {
|
|
272
|
+
content = readFileSync2(target, "utf8");
|
|
273
|
+
} catch {
|
|
274
|
+
ctx.warn(`instruction import @${name} in ${filePath} could not be read \u2014 kept literal`);
|
|
275
|
+
out.push(match[0]);
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
const expanded = expand(content, target, {
|
|
279
|
+
...ctx,
|
|
280
|
+
depth: ctx.depth + 1
|
|
281
|
+
});
|
|
282
|
+
out.push(ctx.wrap === void 0 ? expanded : ctx.wrap(name, expanded));
|
|
283
|
+
}
|
|
284
|
+
out.push(text.slice(cursor));
|
|
285
|
+
return out.join("");
|
|
286
|
+
}
|
|
287
|
+
__name(expand, "expand");
|
|
288
|
+
|
|
289
|
+
// src/config/instruction-tree.ts
|
|
290
|
+
import { readFileSync as readFileSync3, readdirSync, statSync as statSync2 } from "fs";
|
|
291
|
+
import { join, relative, resolve as resolve3 } from "path";
|
|
292
|
+
import { assertNoSymlinkEscape } from "@theokit/sdk/path-safety";
|
|
293
|
+
|
|
294
|
+
// src/config/frontmatter.ts
|
|
295
|
+
var FENCE = "---";
|
|
296
|
+
function splitFrontmatter(raw) {
|
|
297
|
+
const lines = raw.split("\n");
|
|
298
|
+
if (lines[0]?.trim() !== FENCE) return {
|
|
299
|
+
frontmatter: [],
|
|
300
|
+
body: raw
|
|
301
|
+
};
|
|
302
|
+
const closing = lines.indexOf(FENCE, 1);
|
|
303
|
+
if (closing === -1) return void 0;
|
|
304
|
+
return {
|
|
305
|
+
frontmatter: lines.slice(1, closing),
|
|
306
|
+
body: lines.slice(closing + 1).join("\n")
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
__name(splitFrontmatter, "splitFrontmatter");
|
|
310
|
+
function frontmatterValue(frontmatter, key) {
|
|
311
|
+
const prefix = `${key}:`;
|
|
312
|
+
for (const line of frontmatter) {
|
|
313
|
+
if (!line.startsWith(prefix)) continue;
|
|
314
|
+
const value = line.slice(prefix.length).trim();
|
|
315
|
+
const unquoted = /^(["'])(.*)\1$/.exec(value);
|
|
316
|
+
return unquoted?.[2] ?? value;
|
|
317
|
+
}
|
|
318
|
+
return void 0;
|
|
319
|
+
}
|
|
320
|
+
__name(frontmatterValue, "frontmatterValue");
|
|
321
|
+
|
|
322
|
+
// src/config/instruction-tree.ts
|
|
323
|
+
var DEFAULT_FILE_NAMES = [
|
|
324
|
+
"THEO.md",
|
|
325
|
+
"AGENTS.md"
|
|
326
|
+
];
|
|
327
|
+
var IGNORE_WARNING = /* @__PURE__ */ __name(() => void 0, "IGNORE_WARNING");
|
|
328
|
+
function loadInstructionTree(input) {
|
|
329
|
+
const warn = input.onWarn ?? IGNORE_WARNING;
|
|
330
|
+
const fileNames = input.fileNames ?? DEFAULT_FILE_NAMES;
|
|
331
|
+
const cwd = resolve3(input.cwd);
|
|
332
|
+
const blocks = [];
|
|
333
|
+
const seenInodes = /* @__PURE__ */ new Set();
|
|
334
|
+
let chars = 0;
|
|
335
|
+
const visit = /* @__PURE__ */ __name((dir, depth) => {
|
|
336
|
+
if (depth > input.budget.maxDepth) return false;
|
|
337
|
+
let entries;
|
|
338
|
+
try {
|
|
339
|
+
entries = readdirSync(dir);
|
|
340
|
+
} catch {
|
|
341
|
+
return false;
|
|
342
|
+
}
|
|
343
|
+
for (const { entry, path, stats } of filesBeforeDirectories(dir, entries)) {
|
|
344
|
+
const inode = `${String(stats.dev)}:${String(stats.ino)}`;
|
|
345
|
+
if (seenInodes.has(inode)) continue;
|
|
346
|
+
seenInodes.add(inode);
|
|
347
|
+
if (stats.isDirectory()) {
|
|
348
|
+
if (visit(path, depth + 1)) return true;
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
if (!fileNames.includes(entry)) continue;
|
|
352
|
+
try {
|
|
353
|
+
;
|
|
354
|
+
assertNoSymlinkEscape(path, cwd);
|
|
355
|
+
} catch {
|
|
356
|
+
warn(`instruction file escapes the project root and was skipped: ${relative(cwd, path)}`);
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
if (blocks.length >= input.budget.maxFiles) {
|
|
360
|
+
warn(`instruction budget: stopped at ${String(input.budget.maxFiles)} files`);
|
|
361
|
+
return true;
|
|
362
|
+
}
|
|
363
|
+
const parsed = parseInstructionFile(path, cwd, warn);
|
|
364
|
+
if (parsed === void 0) continue;
|
|
365
|
+
if (chars + parsed.content.length > input.budget.maxChars) {
|
|
366
|
+
warn(`instruction budget: stopped at ${String(input.budget.maxChars)} characters`);
|
|
367
|
+
return true;
|
|
368
|
+
}
|
|
369
|
+
chars += parsed.content.length;
|
|
370
|
+
blocks.push(parsed);
|
|
371
|
+
}
|
|
372
|
+
return false;
|
|
373
|
+
}, "visit");
|
|
374
|
+
let truncated = false;
|
|
375
|
+
for (const root of input.roots) {
|
|
376
|
+
truncated = visit(resolve3(cwd, root), 0);
|
|
377
|
+
if (truncated) break;
|
|
378
|
+
}
|
|
379
|
+
return {
|
|
380
|
+
blocks,
|
|
381
|
+
truncated,
|
|
382
|
+
count: blocks.length
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
__name(loadInstructionTree, "loadInstructionTree");
|
|
386
|
+
function filesBeforeDirectories(dir, entries) {
|
|
387
|
+
const found = [];
|
|
388
|
+
for (const entry of [
|
|
389
|
+
...entries
|
|
390
|
+
].sort((a, b) => a.localeCompare(b))) {
|
|
391
|
+
const path = join(dir, entry);
|
|
392
|
+
try {
|
|
393
|
+
found.push({
|
|
394
|
+
entry,
|
|
395
|
+
path,
|
|
396
|
+
stats: statSync2(path)
|
|
397
|
+
});
|
|
398
|
+
} catch {
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return [
|
|
403
|
+
...found.filter((item) => !item.stats.isDirectory()),
|
|
404
|
+
...found.filter((item) => item.stats.isDirectory())
|
|
405
|
+
];
|
|
406
|
+
}
|
|
407
|
+
__name(filesBeforeDirectories, "filesBeforeDirectories");
|
|
408
|
+
function parseInstructionFile(path, cwd, warn) {
|
|
409
|
+
let raw;
|
|
410
|
+
try {
|
|
411
|
+
raw = readFileSync3(path, "utf8");
|
|
412
|
+
} catch {
|
|
413
|
+
return void 0;
|
|
414
|
+
}
|
|
415
|
+
const rel = relative(cwd, path);
|
|
416
|
+
const parsed = splitFrontmatter(raw);
|
|
417
|
+
if (parsed === void 0) {
|
|
418
|
+
warn(`instruction frontmatter never closes, file skipped: ${rel}`);
|
|
419
|
+
return void 0;
|
|
420
|
+
}
|
|
421
|
+
return {
|
|
422
|
+
path: rel,
|
|
423
|
+
// Imports expand AFTER the frontmatter split, so an `@ref` inside a frontmatter block is not one
|
|
424
|
+
// — that block is metadata about the file, not content of the prompt.
|
|
425
|
+
content: expandInstructionImports({
|
|
426
|
+
text: parsed.body,
|
|
427
|
+
filePath: path,
|
|
428
|
+
rootDir: cwd,
|
|
429
|
+
onWarn: warn
|
|
430
|
+
}),
|
|
431
|
+
scopes: parsePathsScope(parsed.frontmatter)
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
__name(parseInstructionFile, "parseInstructionFile");
|
|
435
|
+
function parsePathsScope(frontmatter) {
|
|
436
|
+
const scopes = [];
|
|
437
|
+
let inPaths = false;
|
|
438
|
+
for (const line of frontmatter) {
|
|
439
|
+
if (/^paths\s*:/.test(line)) {
|
|
440
|
+
inPaths = true;
|
|
441
|
+
const inline = line.slice(line.indexOf(":") + 1).trim();
|
|
442
|
+
if (inline.startsWith("[")) {
|
|
443
|
+
return inline.slice(1, inline.lastIndexOf("]")).split(",").map((item2) => item2.trim().replaceAll(/^["']|["']$/g, "")).filter((item2) => item2.length > 0);
|
|
444
|
+
}
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
if (!inPaths) continue;
|
|
448
|
+
const item = /^ {0,32}- {0,32}(\S.*)$/.exec(line);
|
|
449
|
+
if (item?.[1] === void 0) break;
|
|
450
|
+
scopes.push(item[1].trim().replaceAll(/^["']|["']$/g, ""));
|
|
451
|
+
}
|
|
452
|
+
return scopes;
|
|
453
|
+
}
|
|
454
|
+
__name(parsePathsScope, "parsePathsScope");
|
|
455
|
+
|
|
456
|
+
// src/config/compose-instructions.ts
|
|
457
|
+
var DEFAULT_SEPARATOR = "\n\n";
|
|
458
|
+
var IGNORE_WARNING2 = /* @__PURE__ */ __name(() => void 0, "IGNORE_WARNING");
|
|
459
|
+
function composeInstructions(base, sources, options) {
|
|
460
|
+
const warn = options.onWarn ?? IGNORE_WARNING2;
|
|
461
|
+
const separator = options.separator ?? DEFAULT_SEPARATOR;
|
|
462
|
+
if (base.length >= options.maxChars) {
|
|
463
|
+
warn(`instruction budget: the base prompt alone is ${String(base.length)} characters against a ceiling of ${String(options.maxChars)}. It was truncated and every source was dropped.`);
|
|
464
|
+
return {
|
|
465
|
+
text: base.slice(0, options.maxChars),
|
|
466
|
+
dropped: sources.map((source) => source.name),
|
|
467
|
+
trimmed: "base"
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
const kept = [
|
|
471
|
+
...sources
|
|
472
|
+
];
|
|
473
|
+
const dropped = [];
|
|
474
|
+
let trimmed;
|
|
475
|
+
const lengthOf = /* @__PURE__ */ __name((list) => [
|
|
476
|
+
base,
|
|
477
|
+
...list.map((source) => source.content)
|
|
478
|
+
].join(separator).length, "lengthOf");
|
|
479
|
+
while (kept.length > 0 && lengthOf(kept) > options.maxChars) {
|
|
480
|
+
const last = kept.at(-1);
|
|
481
|
+
if (last === void 0) break;
|
|
482
|
+
const withoutLast = kept.slice(0, -1);
|
|
483
|
+
const room = options.maxChars - lengthOf(withoutLast) - separator.length;
|
|
484
|
+
if (room > 0 && trimmed === void 0) {
|
|
485
|
+
kept[kept.length - 1] = {
|
|
486
|
+
...last,
|
|
487
|
+
content: last.content.slice(0, room)
|
|
488
|
+
};
|
|
489
|
+
trimmed = last.name;
|
|
490
|
+
warn(`instruction budget: "${last.name}" was trimmed to ${String(room)} characters to fit the ${String(options.maxChars)}-character ceiling.`);
|
|
491
|
+
break;
|
|
492
|
+
}
|
|
493
|
+
kept.pop();
|
|
494
|
+
dropped.push(last.name);
|
|
495
|
+
warn(`instruction budget: "${last.name}" was dropped to fit the ceiling.`);
|
|
496
|
+
}
|
|
497
|
+
return {
|
|
498
|
+
text: [
|
|
499
|
+
base,
|
|
500
|
+
...kept.map((source) => source.content)
|
|
501
|
+
].join(separator),
|
|
502
|
+
dropped,
|
|
503
|
+
...trimmed !== void 0 && {
|
|
504
|
+
trimmed
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
__name(composeInstructions, "composeInstructions");
|
|
509
|
+
|
|
510
|
+
// src/config/custom-commands.ts
|
|
511
|
+
import { readFileSync as readFileSync4, readdirSync as readdirSync2, statSync as statSync3 } from "fs";
|
|
512
|
+
import { basename, extname, join as join2 } from "path";
|
|
513
|
+
var COMMANDS_DIR = join2(".theokit", "commands");
|
|
514
|
+
var IGNORE_WARNING3 = /* @__PURE__ */ __name(() => void 0, "IGNORE_WARNING");
|
|
515
|
+
function loadCustomCommands(input) {
|
|
516
|
+
const warn = input.onWarn ?? IGNORE_WARNING3;
|
|
517
|
+
const loaded = /* @__PURE__ */ new Map();
|
|
518
|
+
if (input.homeDir !== void 0) {
|
|
519
|
+
for (const command of readCommandsDir(join2(input.homeDir, COMMANDS_DIR), "user", warn)) {
|
|
520
|
+
loaded.set(command.name, command);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
if (input.projectDir !== void 0) {
|
|
524
|
+
if (!input.projectTrusted) {
|
|
525
|
+
const pending = readCommandsDir(join2(input.projectDir, COMMANDS_DIR), "project", IGNORE_WARNING3);
|
|
526
|
+
if (pending.length > 0) {
|
|
527
|
+
warn(`${String(pending.length)} project command(s) found but the directory is not trusted \u2014 none were loaded. A command is a prompt that runs on your behalf.`);
|
|
528
|
+
}
|
|
529
|
+
} else {
|
|
530
|
+
for (const command of readCommandsDir(join2(input.projectDir, COMMANDS_DIR), "project", warn)) {
|
|
531
|
+
const shadowed = loaded.get(command.name);
|
|
532
|
+
if (shadowed !== void 0) {
|
|
533
|
+
warn(`project command "${command.name}" overrides the user-level one at ${shadowed.path}.`);
|
|
534
|
+
}
|
|
535
|
+
loaded.set(command.name, command);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
const builtins = new Set(input.builtinNames ?? []);
|
|
540
|
+
const shadowedBuiltins = [
|
|
541
|
+
...loaded.keys()
|
|
542
|
+
].filter((name) => builtins.has(name));
|
|
543
|
+
for (const name of shadowedBuiltins) {
|
|
544
|
+
warn(`custom command "${name}" has the same name as a builtin. This loader does not choose between them \u2014 the router decides, because only it knows what its builtins do.`);
|
|
545
|
+
}
|
|
546
|
+
return {
|
|
547
|
+
// Project first in the returned order, matching the precedence a reader would expect.
|
|
548
|
+
commands: [
|
|
549
|
+
...loaded.values()
|
|
550
|
+
].sort(byProjectThenName),
|
|
551
|
+
shadowedBuiltins
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
__name(loadCustomCommands, "loadCustomCommands");
|
|
555
|
+
function byProjectThenName(a, b) {
|
|
556
|
+
if (a.source !== b.source) return a.source === "project" ? -1 : 1;
|
|
557
|
+
return a.name.localeCompare(b.name);
|
|
558
|
+
}
|
|
559
|
+
__name(byProjectThenName, "byProjectThenName");
|
|
560
|
+
function readCommandsDir(dir, source, warn) {
|
|
561
|
+
let entries;
|
|
562
|
+
try {
|
|
563
|
+
entries = readdirSync2(dir);
|
|
564
|
+
} catch {
|
|
565
|
+
return [];
|
|
566
|
+
}
|
|
567
|
+
entries.sort((a, b) => a.localeCompare(b));
|
|
568
|
+
const commands = [];
|
|
569
|
+
for (const entry of entries) {
|
|
570
|
+
if (extname(entry) !== ".md") continue;
|
|
571
|
+
const path = join2(dir, entry);
|
|
572
|
+
try {
|
|
573
|
+
if (!statSync3(path).isFile()) continue;
|
|
574
|
+
} catch {
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
577
|
+
const command = parseCommandFile(path, entry, source, warn);
|
|
578
|
+
if (command !== void 0) commands.push(command);
|
|
579
|
+
}
|
|
580
|
+
return commands;
|
|
581
|
+
}
|
|
582
|
+
__name(readCommandsDir, "readCommandsDir");
|
|
583
|
+
function parseCommandFile(path, entry, source, warn) {
|
|
584
|
+
let raw;
|
|
585
|
+
try {
|
|
586
|
+
raw = readFileSync4(path, "utf8");
|
|
587
|
+
} catch {
|
|
588
|
+
return void 0;
|
|
589
|
+
}
|
|
590
|
+
const parsed = splitFrontmatter(raw);
|
|
591
|
+
if (parsed === void 0) {
|
|
592
|
+
warn(`command frontmatter never closes, file skipped: ${path}`);
|
|
593
|
+
return void 0;
|
|
594
|
+
}
|
|
595
|
+
const description = frontmatterValue(parsed.frontmatter, "description");
|
|
596
|
+
return {
|
|
597
|
+
name: basename(entry, ".md"),
|
|
598
|
+
...description !== void 0 && {
|
|
599
|
+
description
|
|
600
|
+
},
|
|
601
|
+
body: parsed.body,
|
|
602
|
+
source,
|
|
603
|
+
path
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
__name(parseCommandFile, "parseCommandFile");
|
|
607
|
+
|
|
608
|
+
// src/config/context-pressure.ts
|
|
609
|
+
import { resolveEffectiveContextWindow } from "@theokit/sdk/compaction";
|
|
610
|
+
import { TheokitAgentError as TheokitAgentError3 } from "@theokit/sdk/errors";
|
|
611
|
+
var DEFAULT_CONTEXT_PRESSURE_THRESHOLDS = {
|
|
612
|
+
warn: 0.75,
|
|
613
|
+
critical: 0.9
|
|
614
|
+
};
|
|
615
|
+
var ContextPressureThresholdError = class extends TheokitAgentError3 {
|
|
616
|
+
static {
|
|
617
|
+
__name(this, "ContextPressureThresholdError");
|
|
618
|
+
}
|
|
619
|
+
name = "ContextPressureThresholdError";
|
|
620
|
+
constructor(message) {
|
|
621
|
+
super(message, {
|
|
622
|
+
code: "context_pressure_thresholds_unordered",
|
|
623
|
+
isRetryable: false
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
function contextPressure(usedTokens, effectiveWindow, thresholds = DEFAULT_CONTEXT_PRESSURE_THRESHOLDS) {
|
|
628
|
+
if (thresholds.warn >= thresholds.critical) {
|
|
629
|
+
throw new ContextPressureThresholdError(`context pressure: \`warn\` (${String(thresholds.warn)}) must be below \`critical\` (${String(thresholds.critical)}), otherwise one of the two levels can never be reached.`);
|
|
630
|
+
}
|
|
631
|
+
if (!Number.isFinite(effectiveWindow) || effectiveWindow <= 0) return "ok";
|
|
632
|
+
if (!Number.isFinite(usedTokens) || usedTokens <= 0) return "ok";
|
|
633
|
+
const ratio = usedTokens / effectiveWindow;
|
|
634
|
+
if (ratio >= thresholds.critical) return "critical";
|
|
635
|
+
if (ratio >= thresholds.warn) return "warn";
|
|
636
|
+
return "ok";
|
|
637
|
+
}
|
|
638
|
+
__name(contextPressure, "contextPressure");
|
|
639
|
+
export {
|
|
640
|
+
ContextPressureThresholdError,
|
|
641
|
+
DEFAULT_CONTEXT_PRESSURE_THRESHOLDS,
|
|
642
|
+
LayerOutOfOrderError,
|
|
643
|
+
LayeredConfig,
|
|
644
|
+
TrustStore,
|
|
645
|
+
TrustStorePermissionsError,
|
|
646
|
+
composeInstructions,
|
|
647
|
+
contextPressure,
|
|
648
|
+
resolveEffectiveContextWindow as effectiveContextWindow,
|
|
649
|
+
expandInstructionImports,
|
|
650
|
+
frontmatterValue,
|
|
651
|
+
loadCustomCommands,
|
|
652
|
+
loadInstructionTree,
|
|
653
|
+
splitFrontmatter
|
|
654
|
+
};
|
|
655
|
+
//# sourceMappingURL=config.js.map
|