@abelxiaoxing/cadence 1.0.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.
@@ -0,0 +1,253 @@
1
+ import type { Model, Usage } from "@earendil-works/pi-ai";
2
+ import {
3
+ createAgentSession,
4
+ defineTool,
5
+ type ModelRuntime,
6
+ SessionManager,
7
+ SettingsManager,
8
+ } from "@earendil-works/pi-coding-agent";
9
+ import { Type } from "typebox";
10
+ import type { DiffResult, EvidenceResult } from "./contracts.ts";
11
+ import { EmptyResourceLoader } from "./empty-resource-loader.ts";
12
+ import { createScopedTools } from "./scoped-tools.ts";
13
+ import {
14
+ createSubmitTool,
15
+ type FinalCategory,
16
+ type SubmitClassification,
17
+ } from "./submit-tool.ts";
18
+
19
+ const ZERO_USAGE: Usage = {
20
+ input: 0,
21
+ output: 0,
22
+ cacheRead: 0,
23
+ cacheWrite: 0,
24
+ totalTokens: 0,
25
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
26
+ };
27
+
28
+ export class UsageAggregator {
29
+ private readonly ids = new Set<string>();
30
+ private usage: Usage = structuredClone(ZERO_USAGE);
31
+ add(id: string, value: Usage): boolean {
32
+ if (this.ids.has(id)) return false;
33
+ this.ids.add(id);
34
+ this.usage = {
35
+ input: this.usage.input + value.input,
36
+ output: this.usage.output + value.output,
37
+ cacheRead: this.usage.cacheRead + value.cacheRead,
38
+ cacheWrite: this.usage.cacheWrite + value.cacheWrite,
39
+ totalTokens: this.usage.totalTokens + value.totalTokens,
40
+ cost: {
41
+ input: this.usage.cost.input + value.cost.input,
42
+ output: this.usage.cost.output + value.cost.output,
43
+ cacheRead: this.usage.cost.cacheRead + value.cost.cacheRead,
44
+ cacheWrite: this.usage.cost.cacheWrite + value.cost.cacheWrite,
45
+ total: this.usage.cost.total + value.cost.total,
46
+ },
47
+ };
48
+ return true;
49
+ }
50
+ total(): Usage {
51
+ return structuredClone(this.usage);
52
+ }
53
+ }
54
+
55
+ function wrapScopedTools(roots: string[]) {
56
+ const order = ["read", "grep", "find", "ls"];
57
+ return createScopedTools({ roots })
58
+ .sort((a, b) => order.indexOf(a.name) - order.indexOf(b.name))
59
+ .map((scoped) =>
60
+ defineTool({
61
+ name: scoped.name,
62
+ label: scoped.name,
63
+ description: scoped.description,
64
+ parameters:
65
+ scoped.name === "grep"
66
+ ? Type.Object({ path: Type.String(), pattern: Type.String() })
67
+ : Type.Object({ path: Type.String() }),
68
+ async execute(_id, params) {
69
+ const result = await scoped.execute(
70
+ params as Record<string, unknown>,
71
+ );
72
+ return {
73
+ content: [{ type: "text" as const, text: JSON.stringify(result) }],
74
+ details: result,
75
+ };
76
+ },
77
+ }),
78
+ );
79
+ }
80
+
81
+ export async function runChildSession(input: {
82
+ cwd: string;
83
+ modelRuntime: ModelRuntime;
84
+ model: Model<string>;
85
+ systemPrompt: string;
86
+ requestId: string;
87
+ role: string;
88
+ phase?: string;
89
+ output: "evidence" | "diff";
90
+ roots: string[];
91
+ timeoutMs: number;
92
+ }): Promise<
93
+ | {
94
+ ok: true;
95
+ result: EvidenceResult | DiffResult;
96
+ toolNames: string[];
97
+ submitCount: number;
98
+ disposeCount: number;
99
+ usage: Usage;
100
+ classification: SubmitClassification;
101
+ }
102
+ | {
103
+ ok: false;
104
+ error: string;
105
+ disposeCount: number;
106
+ classification: SubmitClassification;
107
+ }
108
+ > {
109
+ const submit = createSubmitTool({
110
+ requestId: input.requestId,
111
+ role: input.role,
112
+ phase: input.phase ?? "red",
113
+ output: input.output,
114
+ });
115
+ const readTools = wrapScopedTools(input.roots);
116
+ const customTools = [...readTools, submit.tool];
117
+ const toolNames = customTools.map((tool) => tool.name);
118
+ let disposeCount = 0;
119
+ const usage = new UsageAggregator();
120
+ const abort = new AbortController();
121
+ const timer = setTimeout(
122
+ () => abort.abort(new Error("child phase timeout")),
123
+ input.timeoutMs,
124
+ );
125
+ let session:
126
+ | Awaited<ReturnType<typeof createAgentSession>>["session"]
127
+ | undefined;
128
+ let unsubscribe: (() => void) | undefined;
129
+ const disposeOnce = () => {
130
+ if (session) {
131
+ session.dispose();
132
+ disposeCount++;
133
+ session = undefined;
134
+ }
135
+ };
136
+ const classifySession = (): SubmitClassification => {
137
+ const assistants =
138
+ session?.messages.filter((m) => m.role === "assistant") ?? [];
139
+ const last = assistants.at(-1);
140
+ let finalCategory: FinalCategory;
141
+ if (!last) {
142
+ finalCategory = "no-final-assistant";
143
+ } else if (
144
+ last.content.length === 1 &&
145
+ last.content[0].type === "toolCall" &&
146
+ last.content[0].name === "abel_submit_result"
147
+ ) {
148
+ finalCategory =
149
+ submit.getAttempts() > 1 ? "multiple-submit" : "single-submit-only";
150
+ } else if (last.content.length === 1 && last.content[0].type === "text") {
151
+ finalCategory = "text-only";
152
+ } else {
153
+ finalCategory = "mixed";
154
+ }
155
+ return {
156
+ finalCategory,
157
+ attempts: submit.getAttempts(),
158
+ schema: submit.getSchema(),
159
+ identity: submit.getIdentity(),
160
+ };
161
+ };
162
+ try {
163
+ ({ session } = await createAgentSession({
164
+ cwd: input.cwd,
165
+ modelRuntime: input.modelRuntime,
166
+ model: input.model,
167
+ thinkingLevel: "off",
168
+ tools: toolNames,
169
+ customTools,
170
+ resourceLoader: new EmptyResourceLoader(input.systemPrompt),
171
+ sessionManager: SessionManager.inMemory(input.cwd),
172
+ settingsManager: SettingsManager.inMemory({
173
+ compaction: { enabled: false },
174
+ retry: { enabled: false, maxRetries: 0, provider: { maxRetries: 0 } },
175
+ }),
176
+ }));
177
+ unsubscribe = session.subscribe((event) => {
178
+ if (event.type === "message_end" && event.message.role === "assistant") {
179
+ usage.add(
180
+ `${event.message.provider}:${event.message.model}:${event.message.timestamp}`,
181
+ event.message.usage,
182
+ );
183
+ }
184
+ });
185
+ const onAbort = () => void session?.abort();
186
+ abort.signal.addEventListener("abort", onAbort, { once: true });
187
+ try {
188
+ await Promise.race([
189
+ session.prompt(input.systemPrompt, { expandPromptTemplates: false }),
190
+ new Promise<never>((_, reject) =>
191
+ abort.signal.addEventListener(
192
+ "abort",
193
+ () => reject(abort.signal.reason),
194
+ { once: true },
195
+ ),
196
+ ),
197
+ ]);
198
+ } finally {
199
+ abort.signal.removeEventListener("abort", onAbort);
200
+ }
201
+ const result = submit.getResult();
202
+ const attempts = submit.getAttempts();
203
+ const classification = classifySession();
204
+ if (!result || attempts !== 1) {
205
+ disposeOnce();
206
+ return {
207
+ ok: false,
208
+ error: "child did not retain exactly one structural submission",
209
+ disposeCount,
210
+ classification,
211
+ };
212
+ }
213
+ const assistants = session.messages.filter((m) => m.role === "assistant");
214
+ const final = assistants.at(-1);
215
+ if (
216
+ !final ||
217
+ final.role !== "assistant" ||
218
+ final.content.length !== 1 ||
219
+ final.content[0]?.type !== "toolCall" ||
220
+ final.content[0]?.name !== "abel_submit_result"
221
+ ) {
222
+ disposeOnce();
223
+ return {
224
+ ok: false,
225
+ error: "final assistant message is not one structural submit",
226
+ disposeCount,
227
+ classification,
228
+ };
229
+ }
230
+ disposeOnce();
231
+ return {
232
+ ok: true,
233
+ result,
234
+ toolNames,
235
+ submitCount: attempts,
236
+ disposeCount,
237
+ usage: usage.total(),
238
+ classification,
239
+ };
240
+ } catch (error) {
241
+ disposeOnce();
242
+ return {
243
+ ok: false,
244
+ error: (error as Error).message,
245
+ disposeCount,
246
+ classification: classifySession(),
247
+ };
248
+ } finally {
249
+ clearTimeout(timer);
250
+ unsubscribe?.();
251
+ disposeOnce();
252
+ }
253
+ }
@@ -0,0 +1,375 @@
1
+ // Strict request/result contracts for the private orchestration kernel.
2
+ // Structural validation only; no runtime dependency on a schema library.
3
+ export const STAGES = [
4
+ "abel-design",
5
+ "abel-implement",
6
+ "abel-diagnose",
7
+ ] as const;
8
+ export const ROLES = [
9
+ "design-explorer",
10
+ "contract-reviewer",
11
+ "implementation-worker",
12
+ "diagnosis-worker",
13
+ ] as const;
14
+ export const ACTIONS = ["run", "apply", "discard", "cancel", "finish"] as const;
15
+ export const OUTPUT_KINDS = ["evidence", "diff"] as const;
16
+ export const PHASES = [
17
+ "evidence",
18
+ "red",
19
+ "green",
20
+ "refactor",
21
+ "review",
22
+ ] as const;
23
+
24
+ export const LIMITS = {
25
+ maxActiveChildSessions: 4,
26
+ maxRequestsPerBatch: 8,
27
+ maxEnvelopeBytes: 64 * 1024,
28
+ phaseTimeoutMs: 20 * 60 * 1000,
29
+ maxCompleteResultBytes: 64 * 1024,
30
+ } as const;
31
+
32
+ const NONCANONICAL = /(^|\/)\.\.(\/|$)|(^|\/)\/|^\//;
33
+
34
+ export interface RequestEnvelope {
35
+ stage: string;
36
+ role: string;
37
+ id: string;
38
+ phase: string;
39
+ objective: string;
40
+ roots: string[];
41
+ context: { agents: string; contract: string };
42
+ declared: {
43
+ read: string[];
44
+ write: string[];
45
+ conflicts: string[];
46
+ resources: string[];
47
+ verificationLock?: string;
48
+ };
49
+ output: string;
50
+ snapshot?: unknown;
51
+ }
52
+
53
+ export function isValidRelativePath(p: unknown): p is string {
54
+ return (
55
+ typeof p === "string" &&
56
+ p.length > 0 &&
57
+ p.length <= 512 &&
58
+ !NONCANONICAL.test(p) &&
59
+ !p.startsWith("/") &&
60
+ !p.includes("..") &&
61
+ !p.includes("\\") &&
62
+ !p.includes("\u0000")
63
+ );
64
+ }
65
+
66
+ export function validateRequestEnvelope(
67
+ value: unknown,
68
+ ): { ok: true; value: RequestEnvelope } | { ok: false; reason: string } {
69
+ if (value === null || typeof value !== "object") {
70
+ return { ok: false, reason: "missing request envelope" };
71
+ }
72
+ const env = value as Record<string, unknown>;
73
+ for (const field of [
74
+ "stage",
75
+ "role",
76
+ "id",
77
+ "phase",
78
+ "objective",
79
+ "roots",
80
+ "context",
81
+ "declared",
82
+ "output",
83
+ ]) {
84
+ if (env[field] === undefined) {
85
+ return { ok: false, reason: `missing required field: ${field}` };
86
+ }
87
+ }
88
+ if (!(STAGES as readonly string[]).includes(env.stage as string)) {
89
+ return { ok: false, reason: `unknown stage: ${String(env.stage)}` };
90
+ }
91
+ if (!(ROLES as readonly string[]).includes(env.role as string)) {
92
+ return { ok: false, reason: `unknown role: ${String(env.role)}` };
93
+ }
94
+ if (
95
+ typeof env.id !== "string" ||
96
+ env.id.length === 0 ||
97
+ env.id.length > 128
98
+ ) {
99
+ return { ok: false, reason: "invalid request id" };
100
+ }
101
+ if (
102
+ typeof env.phase !== "string" ||
103
+ !(PHASES as readonly string[]).includes(env.phase)
104
+ ) {
105
+ return { ok: false, reason: `invalid phase: ${String(env.phase)}` };
106
+ }
107
+ const serialized = JSON.stringify(env);
108
+ if (serialized.length > LIMITS.maxEnvelopeBytes) {
109
+ return {
110
+ ok: false,
111
+ reason: `request envelope exceeds ${LIMITS.maxEnvelopeBytes / 1024} KiB`,
112
+ };
113
+ }
114
+ if (
115
+ typeof env.objective !== "string" ||
116
+ env.objective.length === 0 ||
117
+ env.objective.length > 4096
118
+ ) {
119
+ return { ok: false, reason: "invalid objective" };
120
+ }
121
+ if (
122
+ !Array.isArray(env.roots) ||
123
+ env.roots.length === 0 ||
124
+ !env.roots.every(isValidRelativePath)
125
+ ) {
126
+ return { ok: false, reason: "invalid path roots" };
127
+ }
128
+ const ctx = env.context as Record<string, unknown>;
129
+ if (
130
+ ctx === null ||
131
+ typeof ctx !== "object" ||
132
+ typeof ctx.agents !== "string" ||
133
+ typeof ctx.contract !== "string"
134
+ ) {
135
+ return { ok: false, reason: "invalid context" };
136
+ }
137
+ const decl = env.declared as Record<string, unknown>;
138
+ if (decl === null || typeof decl !== "object") {
139
+ return { ok: false, reason: "invalid declared sets" };
140
+ }
141
+ for (const key of ["read", "write", "conflicts", "resources"]) {
142
+ if (
143
+ !Array.isArray(decl[key]) ||
144
+ !(decl[key] as unknown[]).every(isValidRelativePath)
145
+ ) {
146
+ return { ok: false, reason: `invalid declared ${key} set` };
147
+ }
148
+ }
149
+ if (
150
+ decl.verificationLock !== undefined &&
151
+ !isValidRelativePath(decl.verificationLock)
152
+ ) {
153
+ return { ok: false, reason: "invalid verification lock" };
154
+ }
155
+ if (!(OUTPUT_KINDS as readonly string[]).includes(env.output as string)) {
156
+ return { ok: false, reason: `invalid output kind: ${String(env.output)}` };
157
+ }
158
+ if (
159
+ env.snapshot !== undefined &&
160
+ (env.snapshot === null || typeof env.snapshot !== "object")
161
+ ) {
162
+ return { ok: false, reason: "invalid snapshot" };
163
+ }
164
+ return { ok: true, value: env as unknown as RequestEnvelope };
165
+ }
166
+
167
+ /** Extract proposed write paths from ordinary unified diff header pairs. */
168
+ export function diffWritePaths(diffText: string): {
169
+ paths: string[];
170
+ kind: string;
171
+ } {
172
+ if (typeof diffText !== "string" || diffText.length === 0) {
173
+ throw new Error("empty diff");
174
+ }
175
+ if (/^GIT binary patch|^Binary files /.test(diffText)) {
176
+ throw new Error("binary diff is not supported");
177
+ }
178
+ const lines = diffText.split("\n");
179
+ if (lines.some((line) => /^copy (from|to) /.test(line))) {
180
+ throw new Error("copy records are not supported");
181
+ }
182
+ if (lines.some((line) => /^rename (from|to) /.test(line))) {
183
+ throw new Error("rename records are not supported");
184
+ }
185
+ const paths: string[] = [];
186
+ let sawHeader = false;
187
+ let pendingOld: string | null = null;
188
+ for (const line of lines) {
189
+ if (/^old mode /.test(line) || /^new mode /.test(line)) {
190
+ throw new Error("mode transitions are not supported");
191
+ }
192
+ if (/^(new|deleted) file mode /.test(line)) {
193
+ const mode = line.split(" ").pop();
194
+ if (mode === "160000") {
195
+ throw new Error("submodule mode is not supported");
196
+ }
197
+ if (mode !== "100644" && mode !== "100755") {
198
+ throw new Error(`non-regular file mode is not supported: ${mode}`);
199
+ }
200
+ continue;
201
+ }
202
+ if (line.startsWith("--- ") && !line.startsWith("--- /dev/null")) {
203
+ pendingOld = parseHeaderPath(line.slice(4));
204
+ continue;
205
+ }
206
+ if (line.startsWith("+++ ")) {
207
+ const newPath = parseHeaderPath(line.slice(4));
208
+ let target: string;
209
+ if (newPath === null) {
210
+ if (pendingOld === null) {
211
+ throw new Error("diff header pair is malformed");
212
+ }
213
+ target = pendingOld;
214
+ } else {
215
+ target = newPath;
216
+ if (pendingOld !== null && pendingOld !== newPath) {
217
+ throw new Error("mismatched old/new diff targets");
218
+ }
219
+ }
220
+ pendingOld = null;
221
+ if (paths.includes(target)) throw new Error("duplicate diff targets");
222
+ paths.push(target);
223
+ sawHeader = true;
224
+ }
225
+ }
226
+ if (!sawHeader || paths.length === 0) {
227
+ throw new Error("diff has no ordinary text headers");
228
+ }
229
+ return { paths, kind: "text" };
230
+ }
231
+
232
+ function parseHeaderPath(header: string): string | null {
233
+ if (header === "/dev/null") return null;
234
+ const path = header.replace(/^\s*(a|b)\//, "");
235
+ if (!isValidRelativePath(path)) {
236
+ throw new Error(`noncanonical or escaping diff path: ${header}`);
237
+ }
238
+ return path;
239
+ }
240
+
241
+ export interface EvidenceResult {
242
+ id: string;
243
+ role: string;
244
+ kind: "evidence";
245
+ conclusions: string[];
246
+ citations: { path: string; lines: string }[];
247
+ constraints: string[];
248
+ dependencies: string[];
249
+ risks: string[];
250
+ blockingQuestions: string[];
251
+ hints: { writeSet: string[]; verification: string; agentsImpact: string };
252
+ }
253
+
254
+ export interface DiffResult {
255
+ id: string;
256
+ role: string;
257
+ kind: "diff";
258
+ taskId: string;
259
+ phase: string;
260
+ summary: string;
261
+ diff: string;
262
+ expectedVerification: string;
263
+ risks: string[];
264
+ nextStep: string;
265
+ contractCompliant: boolean;
266
+ }
267
+
268
+ export function validateEvidenceResult(value: unknown): {
269
+ ok: boolean;
270
+ reason?: string;
271
+ } {
272
+ if (value === null || typeof value !== "object")
273
+ return { ok: false, reason: "missing result" };
274
+ const r = value as Record<string, unknown>;
275
+ if (r.kind !== "evidence") return { ok: false, reason: "wrong result kind" };
276
+ for (const field of [
277
+ "id",
278
+ "role",
279
+ "conclusions",
280
+ "citations",
281
+ "blockingQuestions",
282
+ ]) {
283
+ if (r[field] === undefined)
284
+ return { ok: false, reason: `missing field: ${field}` };
285
+ }
286
+ if (typeof r.id !== "string" || typeof r.role !== "string")
287
+ return { ok: false, reason: "invalid identity" };
288
+ if (!(ROLES as readonly string[]).includes(r.role))
289
+ return { ok: false, reason: "unknown role" };
290
+ const serialized = JSON.stringify(r);
291
+ if (serialized.length > LIMITS.maxCompleteResultBytes) {
292
+ return {
293
+ ok: false,
294
+ reason: `result exceeds ${LIMITS.maxCompleteResultBytes} bytes`,
295
+ };
296
+ }
297
+ return { ok: true };
298
+ }
299
+
300
+ export function validateDiffResult(value: unknown): {
301
+ ok: boolean;
302
+ reason?: string;
303
+ paths?: string[];
304
+ } {
305
+ if (value === null || typeof value !== "object")
306
+ return { ok: false, reason: "missing result" };
307
+ const r = value as Record<string, unknown>;
308
+ if (r.kind !== "diff") return { ok: false, reason: "wrong result kind" };
309
+ for (const field of [
310
+ "id",
311
+ "role",
312
+ "taskId",
313
+ "phase",
314
+ "summary",
315
+ "diff",
316
+ "expectedVerification",
317
+ "risks",
318
+ "nextStep",
319
+ "contractCompliant",
320
+ ]) {
321
+ if (r[field] === undefined)
322
+ return { ok: false, reason: `missing field: ${field}` };
323
+ }
324
+ if (
325
+ typeof r.id !== "string" ||
326
+ r.id.length === 0 ||
327
+ r.id.length > 128 ||
328
+ typeof r.role !== "string" ||
329
+ r.role.length === 0 ||
330
+ typeof r.taskId !== "string" ||
331
+ r.taskId.length === 0
332
+ ) {
333
+ return { ok: false, reason: "invalid identity" };
334
+ }
335
+ if (!(ROLES as readonly string[]).includes(r.role))
336
+ return { ok: false, reason: "unknown role" };
337
+ if (
338
+ typeof r.phase !== "string" ||
339
+ !(PHASES as readonly string[]).includes(r.phase)
340
+ ) {
341
+ return { ok: false, reason: "invalid phase" };
342
+ }
343
+ if (
344
+ typeof r.summary !== "string" ||
345
+ r.summary.length === 0 ||
346
+ typeof r.expectedVerification !== "string" ||
347
+ r.expectedVerification.length === 0 ||
348
+ typeof r.nextStep !== "string" ||
349
+ r.nextStep.length === 0
350
+ ) {
351
+ return { ok: false, reason: "invalid diff result text" };
352
+ }
353
+ if (
354
+ !Array.isArray(r.risks) ||
355
+ !(r.risks as unknown[]).every((x) => typeof x === "string")
356
+ ) {
357
+ return { ok: false, reason: "invalid risks" };
358
+ }
359
+ if (r.contractCompliant !== true)
360
+ return { ok: false, reason: "contract compliance not affirmed" };
361
+ let paths: string[];
362
+ try {
363
+ paths = diffWritePaths(r.diff as string).paths;
364
+ } catch (err) {
365
+ return { ok: false, reason: `invalid diff: ${(err as Error).message}` };
366
+ }
367
+ const serialized = JSON.stringify(r);
368
+ if (serialized.length > LIMITS.maxCompleteResultBytes) {
369
+ return {
370
+ ok: false,
371
+ reason: `result exceeds ${LIMITS.maxCompleteResultBytes} bytes`,
372
+ };
373
+ }
374
+ return { ok: true, paths };
375
+ }
package/src/drain.ts ADDED
@@ -0,0 +1,20 @@
1
+ // Unified idempotent drain (P-006). One drain step closes admission, erases
2
+ // retained diffs and Worker metadata, and removes only the dispatcher
3
+ // activation. No private files are created; child usage is returned exactly
4
+ // once by the child-session dispose path.
5
+ import type { Activation } from "./activation.ts";
6
+ import type { ResultStore } from "./result-store.ts";
7
+ import type { WorkerRegistry } from "./worker.ts";
8
+
9
+ export interface DrainTargets {
10
+ results: ResultStore;
11
+ registry: WorkerRegistry;
12
+ activation: Activation;
13
+ }
14
+
15
+ /** Close admission, erase retained diffs and Worker metadata, deactivate. */
16
+ export function drainStage(targets: DrainTargets): void {
17
+ targets.results.clear();
18
+ targets.registry.clear();
19
+ targets.activation.drain();
20
+ }
@@ -0,0 +1,38 @@
1
+ import {
2
+ createExtensionRuntime,
3
+ type ResourceLoader,
4
+ } from "@earendil-works/pi-coding-agent";
5
+
6
+ /** ResourceLoader that discovers nothing and supplies only the approved prompt. */
7
+ export class EmptyResourceLoader implements ResourceLoader {
8
+ constructor(private readonly systemPrompt?: string) {}
9
+ getExtensions() {
10
+ return { extensions: [], errors: [], runtime: createExtensionRuntime() };
11
+ }
12
+ getSkills() {
13
+ return { skills: [], diagnostics: [] };
14
+ }
15
+ getPrompts() {
16
+ return { prompts: [], diagnostics: [] };
17
+ }
18
+ getThemes() {
19
+ return { themes: [], diagnostics: [] };
20
+ }
21
+ getAgentsFiles() {
22
+ return { agentsFiles: [] };
23
+ }
24
+ getSystemPrompt() {
25
+ return this.systemPrompt;
26
+ }
27
+ getSystemPromptSource() {
28
+ return undefined;
29
+ }
30
+ getAppendSystemPrompt() {
31
+ return [];
32
+ }
33
+ getAppendSystemPromptSources() {
34
+ return [];
35
+ }
36
+ extendResources() {}
37
+ async reload() {}
38
+ }