@caupulican/pi-adaptative 0.81.2 → 0.81.3

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.
Files changed (81) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/core/agent-session.d.ts +1 -23
  3. package/dist/core/agent-session.d.ts.map +1 -1
  4. package/dist/core/agent-session.js +117 -131
  5. package/dist/core/agent-session.js.map +1 -1
  6. package/dist/core/background-lane-controller.d.ts +2 -0
  7. package/dist/core/background-lane-controller.d.ts.map +1 -1
  8. package/dist/core/background-lane-controller.js +3 -0
  9. package/dist/core/background-lane-controller.js.map +1 -1
  10. package/dist/core/billing-failover-controller.d.ts +31 -0
  11. package/dist/core/billing-failover-controller.d.ts.map +1 -0
  12. package/dist/core/billing-failover-controller.js +72 -0
  13. package/dist/core/billing-failover-controller.js.map +1 -0
  14. package/dist/core/billing-failover.d.ts +25 -0
  15. package/dist/core/billing-failover.d.ts.map +1 -0
  16. package/dist/core/billing-failover.js +21 -0
  17. package/dist/core/billing-failover.js.map +1 -0
  18. package/dist/core/context-pipeline.d.ts +1 -0
  19. package/dist/core/context-pipeline.d.ts.map +1 -1
  20. package/dist/core/context-pipeline.js +51 -14
  21. package/dist/core/context-pipeline.js.map +1 -1
  22. package/dist/core/model-router/config-diagnostics.d.ts +1 -1
  23. package/dist/core/model-router/config-diagnostics.d.ts.map +1 -1
  24. package/dist/core/model-router/config-diagnostics.js +29 -1
  25. package/dist/core/model-router/config-diagnostics.js.map +1 -1
  26. package/dist/core/model-router/fitness-gate.d.ts +23 -0
  27. package/dist/core/model-router/fitness-gate.d.ts.map +1 -0
  28. package/dist/core/model-router/fitness-gate.js +33 -0
  29. package/dist/core/model-router/fitness-gate.js.map +1 -0
  30. package/dist/core/model-router/status.d.ts +25 -1
  31. package/dist/core/model-router/status.d.ts.map +1 -1
  32. package/dist/core/model-router/status.js +27 -4
  33. package/dist/core/model-router/status.js.map +1 -1
  34. package/dist/core/model-router-controller.d.ts +10 -1
  35. package/dist/core/model-router-controller.d.ts.map +1 -1
  36. package/dist/core/model-router-controller.js +108 -27
  37. package/dist/core/model-router-controller.js.map +1 -1
  38. package/dist/core/runtime-builder.d.ts +16 -0
  39. package/dist/core/runtime-builder.d.ts.map +1 -1
  40. package/dist/core/runtime-builder.js +88 -1
  41. package/dist/core/runtime-builder.js.map +1 -1
  42. package/dist/core/scout-controller.d.ts +44 -0
  43. package/dist/core/scout-controller.d.ts.map +1 -0
  44. package/dist/core/scout-controller.js +165 -0
  45. package/dist/core/scout-controller.js.map +1 -0
  46. package/dist/core/settings-manager.d.ts +16 -0
  47. package/dist/core/settings-manager.d.ts.map +1 -1
  48. package/dist/core/settings-manager.js +35 -3
  49. package/dist/core/settings-manager.js.map +1 -1
  50. package/dist/core/tools/context-scout.d.ts +19 -0
  51. package/dist/core/tools/context-scout.d.ts.map +1 -0
  52. package/dist/core/tools/context-scout.js +47 -0
  53. package/dist/core/tools/context-scout.js.map +1 -0
  54. package/dist/core/tools/index.d.ts +1 -0
  55. package/dist/core/tools/index.d.ts.map +1 -1
  56. package/dist/core/tools/index.js +1 -0
  57. package/dist/core/tools/index.js.map +1 -1
  58. package/dist/main.d.ts.map +1 -1
  59. package/dist/main.js +1 -1
  60. package/dist/main.js.map +1 -1
  61. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  62. package/dist/modes/interactive/components/settings-selector.js +77 -7
  63. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  64. package/dist/modes/interactive/local-model-commands.d.ts +5 -2
  65. package/dist/modes/interactive/local-model-commands.d.ts.map +1 -1
  66. package/dist/modes/interactive/local-model-commands.js +115 -1
  67. package/dist/modes/interactive/local-model-commands.js.map +1 -1
  68. package/docs/index.md +1 -0
  69. package/docs/providers.md +2 -0
  70. package/docs/scout.md +71 -0
  71. package/docs/settings.md +14 -3
  72. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  73. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  74. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  75. package/examples/extensions/sandbox/package-lock.json +2 -2
  76. package/examples/extensions/sandbox/package.json +1 -1
  77. package/examples/extensions/with-deps/package-lock.json +2 -2
  78. package/examples/extensions/with-deps/package.json +1 -1
  79. package/examples/sdk/12-full-control.ts +1 -1
  80. package/npm-shrinkwrap.json +12 -12
  81. package/package.json +4 -4
@@ -0,0 +1,165 @@
1
+ import { Agent } from "@caupulican/pi-agent-core";
2
+ export const SCOUT_SYSTEM_PROMPT = `You are a repository scout. You explore a codebase with read-only tools and return compact evidence. You do NOT solve tasks, write code, or modify anything.
3
+
4
+ TOOLS: read (file contents), grep (search file contents), find (locate files by glob/name).
5
+ - Issue INDEPENDENT tool calls in parallel in the same turn whenever possible.
6
+ - Prefer grep/find to narrow, then read only the decisive regions.
7
+
8
+ OUTPUT CONTRACT: when you have enough evidence (or hit your turn budget), reply with:
9
+ <final_answer>
10
+ <one-to-three-sentence summary of what you found>
11
+ path/to/file.ts:START-END
12
+ path/to/other.ts:START-END
13
+ </final_answer>
14
+ - Every path MUST be one you actually observed via a tool result this run.
15
+ - Line ranges point at the decisive code, not whole files.
16
+ - If you found nothing relevant, say so inside <final_answer> with zero citations.
17
+ Turn budget: {MAX_TURNS} turns. Be fast; be precise; cite, don't paste.`;
18
+ const MAX_TURNS_DEFAULT = 8;
19
+ const MAX_TURNS_HARD_CAP = 12;
20
+ const MAX_OUTPUT_TOKENS = 30_000;
21
+ const READ_ONLY_TOOL_NAMES = new Set(["read", "grep", "find"]);
22
+ export class ScoutController {
23
+ deps;
24
+ constructor(deps) {
25
+ this.deps = deps;
26
+ }
27
+ async run(query, maxTurns = MAX_TURNS_DEFAULT) {
28
+ const turnLimit = clampTurns(maxTurns);
29
+ const unavailable = emptyResult();
30
+ if (this.deps.signal?.aborted) {
31
+ return { ...unavailable, failure: "aborted" };
32
+ }
33
+ let modelResolution;
34
+ try {
35
+ modelResolution = await this.deps.resolveScoutModel();
36
+ }
37
+ catch (error) {
38
+ return { ...unavailable, failure: error instanceof Error ? error.message : String(error) };
39
+ }
40
+ if ("failure" in modelResolution) {
41
+ return { ...unavailable, failure: modelResolution.failure };
42
+ }
43
+ let turnsUsed = 0;
44
+ let truncated = false;
45
+ let lastAssistantText = "";
46
+ let outputTokens = 0;
47
+ const agent = new Agent({
48
+ initialState: {
49
+ model: modelResolution.model,
50
+ systemPrompt: SCOUT_SYSTEM_PROMPT.replace("{MAX_TURNS}", String(turnLimit)),
51
+ tools: this.deps
52
+ .buildReadOnlyTools(this.deps.getCwd())
53
+ .filter((tool) => READ_ONLY_TOOL_NAMES.has(tool.name)),
54
+ },
55
+ streamFn: this.deps.streamFn,
56
+ getApiKey: () => modelResolution.apiKey,
57
+ maxStallTurns: turnLimit,
58
+ });
59
+ const unsubscribe = agent.subscribe((event) => {
60
+ if (event.type !== "message_end" || event.message.role !== "assistant") {
61
+ return;
62
+ }
63
+ turnsUsed += 1;
64
+ lastAssistantText = assistantText(event.message);
65
+ outputTokens += event.message.usage?.totalTokens ?? 0;
66
+ this.deps.onEvent?.({ type: "scout_turn", detail: `turn ${turnsUsed}` });
67
+ if (hasFinalAnswer(lastAssistantText)) {
68
+ return;
69
+ }
70
+ if (turnsUsed >= turnLimit || outputTokens >= MAX_OUTPUT_TOKENS) {
71
+ truncated = true;
72
+ agent.abort();
73
+ }
74
+ });
75
+ const abortScout = () => {
76
+ truncated = true;
77
+ agent.abort();
78
+ };
79
+ this.deps.signal?.addEventListener("abort", abortScout, { once: true });
80
+ let runFailure;
81
+ try {
82
+ await agent.prompt(query);
83
+ }
84
+ catch (error) {
85
+ if (!truncated && !this.deps.signal?.aborted) {
86
+ runFailure = error instanceof Error ? error.message : String(error);
87
+ }
88
+ }
89
+ finally {
90
+ unsubscribe();
91
+ this.deps.signal?.removeEventListener("abort", abortScout);
92
+ }
93
+ const result = parseScoutAnswer(lastAssistantText, (path) => this.deps.fileExists(path), (path) => this.deps.countLines(path));
94
+ const finalResult = {
95
+ ...result,
96
+ truncated: truncated || !hasFinalAnswer(lastAssistantText),
97
+ turnsUsed,
98
+ failure: this.deps.signal?.aborted ? "aborted" : runFailure,
99
+ };
100
+ this.deps.onEvent?.({ type: "scout_end", detail: finalResult.failure ?? "ok" });
101
+ return finalResult;
102
+ }
103
+ }
104
+ export function parseScoutAnswer(text, fileExists, countLines) {
105
+ const finalAnswer = /<final_answer>([\s\S]*?)<\/final_answer>/i.exec(text)?.[1] ?? text;
106
+ const summaryLines = [];
107
+ const citations = [];
108
+ for (const rawLine of finalAnswer.split(/\r?\n/)) {
109
+ const line = rawLine.trim();
110
+ if (!line)
111
+ continue;
112
+ const citation = parseCitationLine(line, fileExists, countLines);
113
+ if (citation) {
114
+ citations.push(citation);
115
+ continue;
116
+ }
117
+ summaryLines.push(line);
118
+ }
119
+ const droppedCitations = citations.filter((citation) => !citation.valid).length;
120
+ return {
121
+ summary: summaryLines.join("\n"),
122
+ citations,
123
+ droppedCitations,
124
+ unreliable: citations.length > 0 && droppedCitations / citations.length > 0.5,
125
+ };
126
+ }
127
+ function parseCitationLine(line, fileExists, countLines) {
128
+ const match = /^(.+?):(\d+)(?:-(\d+))?$/.exec(line);
129
+ if (!match)
130
+ return undefined;
131
+ const path = match[1].trim();
132
+ const start = Number.parseInt(match[2], 10);
133
+ const end = Number.parseInt(match[3] ?? match[2], 10);
134
+ const lineCount = countLines(path);
135
+ const valid = fileExists(path) &&
136
+ Number.isFinite(start) &&
137
+ Number.isFinite(end) &&
138
+ start >= 1 &&
139
+ end >= start &&
140
+ (lineCount === undefined || end <= lineCount);
141
+ return { path, start, end, valid };
142
+ }
143
+ function clampTurns(maxTurns) {
144
+ if (!Number.isFinite(maxTurns))
145
+ return MAX_TURNS_DEFAULT;
146
+ return Math.min(MAX_TURNS_HARD_CAP, Math.max(1, Math.trunc(maxTurns)));
147
+ }
148
+ function emptyResult() {
149
+ return { summary: "", citations: [], droppedCitations: 0, unreliable: false, truncated: false, turnsUsed: 0 };
150
+ }
151
+ function hasFinalAnswer(text) {
152
+ return /<final_answer>[\s\S]*?<\/final_answer>/i.test(text);
153
+ }
154
+ function assistantText(message) {
155
+ if (!Array.isArray(message.content))
156
+ return "";
157
+ return message.content
158
+ .filter((content) => Boolean(content &&
159
+ typeof content === "object" &&
160
+ content.type === "text" &&
161
+ typeof content.text === "string"))
162
+ .map((content) => content.text)
163
+ .join("\n");
164
+ }
165
+ //# sourceMappingURL=scout-controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scout-controller.js","sourceRoot":"","sources":["../../src/core/scout-controller.ts"],"names":[],"mappings":"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","sourcesContent":["import { Agent, type AgentTool, type StreamFn } from \"@caupulican/pi-agent-core\";\nimport type { Model } from \"@caupulican/pi-ai\";\n\nexport const SCOUT_SYSTEM_PROMPT = `You are a repository scout. You explore a codebase with read-only tools and return compact evidence. You do NOT solve tasks, write code, or modify anything.\n\nTOOLS: read (file contents), grep (search file contents), find (locate files by glob/name).\n- Issue INDEPENDENT tool calls in parallel in the same turn whenever possible.\n- Prefer grep/find to narrow, then read only the decisive regions.\n\nOUTPUT CONTRACT: when you have enough evidence (or hit your turn budget), reply with:\n<final_answer>\n<one-to-three-sentence summary of what you found>\npath/to/file.ts:START-END\npath/to/other.ts:START-END\n</final_answer>\n- Every path MUST be one you actually observed via a tool result this run.\n- Line ranges point at the decisive code, not whole files.\n- If you found nothing relevant, say so inside <final_answer> with zero citations.\nTurn budget: {MAX_TURNS} turns. Be fast; be precise; cite, don't paste.`;\n\nexport interface ScoutCitation {\n\tpath: string;\n\tstart: number;\n\tend: number;\n\tvalid: boolean;\n}\n\nexport interface ScoutRunResult {\n\tsummary: string;\n\tcitations: ScoutCitation[];\n\tdroppedCitations: number;\n\tunreliable: boolean;\n\ttruncated: boolean;\n\tturnsUsed: number;\n\tfailure?: string;\n}\n\nexport interface ScoutControllerDeps {\n\tresolveScoutModel(): Promise<\n\t\t{ model: Model<any>; apiKey?: string; headers?: Record<string, string> } | { failure: string }\n\t>;\n\tgetCwd(): string;\n\tbuildReadOnlyTools(cwd: string): AgentTool<any>[];\n\tstreamFn?: StreamFn;\n\tfileExists(path: string): boolean;\n\tcountLines(path: string): number | undefined;\n\tonEvent?(event: { type: \"scout_turn\" | \"scout_end\"; detail: string }): void;\n\tsignal?: AbortSignal;\n}\n\nconst MAX_TURNS_DEFAULT = 8;\nconst MAX_TURNS_HARD_CAP = 12;\nconst MAX_OUTPUT_TOKENS = 30_000;\nconst READ_ONLY_TOOL_NAMES = new Set([\"read\", \"grep\", \"find\"]);\n\nexport class ScoutController {\n\tprivate readonly deps: ScoutControllerDeps;\n\n\tconstructor(deps: ScoutControllerDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\tasync run(query: string, maxTurns = MAX_TURNS_DEFAULT): Promise<ScoutRunResult> {\n\t\tconst turnLimit = clampTurns(maxTurns);\n\t\tconst unavailable = emptyResult();\n\t\tif (this.deps.signal?.aborted) {\n\t\t\treturn { ...unavailable, failure: \"aborted\" };\n\t\t}\n\n\t\tlet modelResolution: Awaited<ReturnType<ScoutControllerDeps[\"resolveScoutModel\"]>>;\n\t\ttry {\n\t\t\tmodelResolution = await this.deps.resolveScoutModel();\n\t\t} catch (error) {\n\t\t\treturn { ...unavailable, failure: error instanceof Error ? error.message : String(error) };\n\t\t}\n\t\tif (\"failure\" in modelResolution) {\n\t\t\treturn { ...unavailable, failure: modelResolution.failure };\n\t\t}\n\n\t\tlet turnsUsed = 0;\n\t\tlet truncated = false;\n\t\tlet lastAssistantText = \"\";\n\t\tlet outputTokens = 0;\n\t\tconst agent = new Agent({\n\t\t\tinitialState: {\n\t\t\t\tmodel: modelResolution.model,\n\t\t\t\tsystemPrompt: SCOUT_SYSTEM_PROMPT.replace(\"{MAX_TURNS}\", String(turnLimit)),\n\t\t\t\ttools: this.deps\n\t\t\t\t\t.buildReadOnlyTools(this.deps.getCwd())\n\t\t\t\t\t.filter((tool) => READ_ONLY_TOOL_NAMES.has(tool.name)),\n\t\t\t},\n\t\t\tstreamFn: this.deps.streamFn,\n\t\t\tgetApiKey: () => modelResolution.apiKey,\n\t\t\tmaxStallTurns: turnLimit,\n\t\t});\n\n\t\tconst unsubscribe = agent.subscribe((event) => {\n\t\t\tif (event.type !== \"message_end\" || event.message.role !== \"assistant\") {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tturnsUsed += 1;\n\t\t\tlastAssistantText = assistantText(event.message);\n\t\t\toutputTokens += event.message.usage?.totalTokens ?? 0;\n\t\t\tthis.deps.onEvent?.({ type: \"scout_turn\", detail: `turn ${turnsUsed}` });\n\t\t\tif (hasFinalAnswer(lastAssistantText)) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (turnsUsed >= turnLimit || outputTokens >= MAX_OUTPUT_TOKENS) {\n\t\t\t\ttruncated = true;\n\t\t\t\tagent.abort();\n\t\t\t}\n\t\t});\n\n\t\tconst abortScout = (): void => {\n\t\t\ttruncated = true;\n\t\t\tagent.abort();\n\t\t};\n\t\tthis.deps.signal?.addEventListener(\"abort\", abortScout, { once: true });\n\t\tlet runFailure: string | undefined;\n\t\ttry {\n\t\t\tawait agent.prompt(query);\n\t\t} catch (error) {\n\t\t\tif (!truncated && !this.deps.signal?.aborted) {\n\t\t\t\trunFailure = error instanceof Error ? error.message : String(error);\n\t\t\t}\n\t\t} finally {\n\t\t\tunsubscribe();\n\t\t\tthis.deps.signal?.removeEventListener(\"abort\", abortScout);\n\t\t}\n\n\t\tconst result = parseScoutAnswer(\n\t\t\tlastAssistantText,\n\t\t\t(path) => this.deps.fileExists(path),\n\t\t\t(path) => this.deps.countLines(path),\n\t\t);\n\t\tconst finalResult = {\n\t\t\t...result,\n\t\t\ttruncated: truncated || !hasFinalAnswer(lastAssistantText),\n\t\t\tturnsUsed,\n\t\t\tfailure: this.deps.signal?.aborted ? \"aborted\" : runFailure,\n\t\t};\n\t\tthis.deps.onEvent?.({ type: \"scout_end\", detail: finalResult.failure ?? \"ok\" });\n\t\treturn finalResult;\n\t}\n}\n\nexport function parseScoutAnswer(\n\ttext: string,\n\tfileExists: (path: string) => boolean,\n\tcountLines: (path: string) => number | undefined,\n): Omit<ScoutRunResult, \"truncated\" | \"turnsUsed\" | \"failure\"> {\n\tconst finalAnswer = /<final_answer>([\\s\\S]*?)<\\/final_answer>/i.exec(text)?.[1] ?? text;\n\tconst summaryLines: string[] = [];\n\tconst citations: ScoutCitation[] = [];\n\n\tfor (const rawLine of finalAnswer.split(/\\r?\\n/)) {\n\t\tconst line = rawLine.trim();\n\t\tif (!line) continue;\n\t\tconst citation = parseCitationLine(line, fileExists, countLines);\n\t\tif (citation) {\n\t\t\tcitations.push(citation);\n\t\t\tcontinue;\n\t\t}\n\t\tsummaryLines.push(line);\n\t}\n\n\tconst droppedCitations = citations.filter((citation) => !citation.valid).length;\n\treturn {\n\t\tsummary: summaryLines.join(\"\\n\"),\n\t\tcitations,\n\t\tdroppedCitations,\n\t\tunreliable: citations.length > 0 && droppedCitations / citations.length > 0.5,\n\t};\n}\n\nfunction parseCitationLine(\n\tline: string,\n\tfileExists: (path: string) => boolean,\n\tcountLines: (path: string) => number | undefined,\n): ScoutCitation | undefined {\n\tconst match = /^(.+?):(\\d+)(?:-(\\d+))?$/.exec(line);\n\tif (!match) return undefined;\n\tconst path = match[1].trim();\n\tconst start = Number.parseInt(match[2], 10);\n\tconst end = Number.parseInt(match[3] ?? match[2], 10);\n\tconst lineCount = countLines(path);\n\tconst valid =\n\t\tfileExists(path) &&\n\t\tNumber.isFinite(start) &&\n\t\tNumber.isFinite(end) &&\n\t\tstart >= 1 &&\n\t\tend >= start &&\n\t\t(lineCount === undefined || end <= lineCount);\n\treturn { path, start, end, valid };\n}\n\nfunction clampTurns(maxTurns: number): number {\n\tif (!Number.isFinite(maxTurns)) return MAX_TURNS_DEFAULT;\n\treturn Math.min(MAX_TURNS_HARD_CAP, Math.max(1, Math.trunc(maxTurns)));\n}\n\nfunction emptyResult(): ScoutRunResult {\n\treturn { summary: \"\", citations: [], droppedCitations: 0, unreliable: false, truncated: false, turnsUsed: 0 };\n}\n\nfunction hasFinalAnswer(text: string): boolean {\n\treturn /<final_answer>[\\s\\S]*?<\\/final_answer>/i.test(text);\n}\n\nfunction assistantText(message: { content: unknown }): string {\n\tif (!Array.isArray(message.content)) return \"\";\n\treturn message.content\n\t\t.filter((content): content is { type: \"text\"; text: string } =>\n\t\t\tBoolean(\n\t\t\t\tcontent &&\n\t\t\t\t\ttypeof content === \"object\" &&\n\t\t\t\t\t(content as { type?: unknown }).type === \"text\" &&\n\t\t\t\t\ttypeof (content as { text?: unknown }).text === \"string\",\n\t\t\t),\n\t\t)\n\t\t.map((content) => content.text)\n\t\t.join(\"\\n\");\n}\n"]}
@@ -8,6 +8,10 @@ export interface CompactionSettings {
8
8
  triggerPercent?: number;
9
9
  model?: string;
10
10
  }
11
+ export interface ScoutSettings {
12
+ enabled?: boolean;
13
+ model?: string;
14
+ }
11
15
  export interface SemanticMemoryGcSettings {
12
16
  enabled?: boolean;
13
17
  preserveRecentPages?: number;
@@ -139,6 +143,9 @@ export interface AutonomySettings {
139
143
  goalAutoContinue?: boolean;
140
144
  goalAutoContinueDelayMs?: number;
141
145
  }
146
+ export interface FailoverSettings {
147
+ subscriptionHop?: boolean;
148
+ }
142
149
  export interface ModelRouterSettings {
143
150
  enabled?: boolean;
144
151
  cheapModel?: string;
@@ -146,6 +153,7 @@ export interface ModelRouterSettings {
146
153
  expensiveModel?: string;
147
154
  learningModel?: string;
148
155
  judgeEnabled?: boolean;
156
+ fitnessGate?: boolean;
149
157
  judgeModel?: string;
150
158
  executorModel?: string;
151
159
  cheapThinking?: ThinkingLevel;
@@ -270,6 +278,7 @@ export interface Settings {
270
278
  /** Resource catalog directory (round resource management): the folder pi installs/updates/backs up from. */
271
279
  catalogDir?: string;
272
280
  compaction?: CompactionSettings;
281
+ scout?: ScoutSettings;
273
282
  /** Proactive per-turn cost guard (#34). */
274
283
  costGuard?: {
275
284
  maxTurnUsd?: number;
@@ -322,6 +331,7 @@ export interface Settings {
322
331
  modelCapability?: ModelCapabilitySettings;
323
332
  toolkit?: ToolkitSettings;
324
333
  modelRouter?: ModelRouterSettings;
334
+ failover?: FailoverSettings;
325
335
  autoLearn?: AutoLearnSettings;
326
336
  sessionDir?: string;
327
337
  httpIdleTimeoutMs?: number;
@@ -550,6 +560,7 @@ export declare class SettingsManager {
550
560
  maxTurnUsd: number;
551
561
  action: "warn" | "downgrade";
552
562
  };
563
+ getFailoverSettings(): Required<FailoverSettings>;
553
564
  private getProfileModelRouterSettings;
554
565
  getModelRouterSettings(): {
555
566
  enabled: boolean;
@@ -560,6 +571,7 @@ export declare class SettingsManager {
560
571
  judgeEnabled: boolean;
561
572
  judgeModel?: string;
562
573
  executorModel?: string;
574
+ fitnessGate: boolean;
563
575
  cheapThinking?: ThinkingLevel;
564
576
  mediumThinking?: ThinkingLevel;
565
577
  expensiveThinking?: ThinkingLevel;
@@ -575,6 +587,10 @@ export declare class SettingsManager {
575
587
  keepRecentTokens: number;
576
588
  triggerPercent: number;
577
589
  };
590
+ getScoutSettings(): {
591
+ enabled: boolean;
592
+ model: string;
593
+ };
578
594
  getContextGcSettings(): {
579
595
  enabled: boolean;
580
596
  preserveRecentMessages: number;