@matthewfl/pi-contemplator 0.1.9 → 0.1.11
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/package.json +1 -1
- package/src/agents/summarizer/agent.ts +2 -2
- package/src/agents/summarizer/prompts.ts +7 -3
- package/src/commands/settings.ts +33 -7
- package/src/commands/status.ts +6 -0
- package/src/config.ts +9 -0
- package/src/hooks/consolidation-trigger.ts +16 -3
- package/src/runtime.ts +17 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@matthewfl/pi-contemplator",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"description": "A Pi extension that keeps long-running agentic sessions on track with background memory, contemplation, and structural review.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -27,7 +27,7 @@ import { estimateStringTokens } from "../../tokens.js";
|
|
|
27
27
|
import { createRecallAgentTool } from "../../tools/recall-observation.js";
|
|
28
28
|
import { createSearchMemoriesAgentTool } from "../../tools/search-memories.js";
|
|
29
29
|
import { logAgentStreamError } from "../stream-errors.js";
|
|
30
|
-
import {
|
|
30
|
+
import { summarizerContinue, SUMMARIZER_SYSTEM } from "./prompts.js";
|
|
31
31
|
import {
|
|
32
32
|
renderSummarizerMemory,
|
|
33
33
|
sampleSummarizerMemories,
|
|
@@ -568,7 +568,7 @@ export async function runSummarizer(args: RunSummarizerArgs): Promise<Summarizer
|
|
|
568
568
|
|
|
569
569
|
try {
|
|
570
570
|
await runOnce("The preceding summarize call and receipt are an illustrative example only. Its placeholder ids are not real and it did not create a summary. Now inspect the actual records and use tools to register safe compression, or call done if none is warranted.", false);
|
|
571
|
-
for (let invocation = 1; !completedWithDone && invocation < SUMMARIZER_MAX_INVOCATIONS; invocation++) await runOnce(
|
|
571
|
+
for (let invocation = 1; !completedWithDone && invocation < SUMMARIZER_MAX_INVOCATIONS; invocation++) await runOnce(summarizerContinue(drafts.size, invocation), true);
|
|
572
572
|
} catch (error) {
|
|
573
573
|
debugLog("summarizer.error", { error: error instanceof Error ? error.message : String(error), acceptedSummaries: drafts.size });
|
|
574
574
|
if (drafts.size === 0) return { completed: false, reviewedUpToId: coversUpToId, sample };
|
|
@@ -24,7 +24,7 @@ Citations and retrieval:
|
|
|
24
24
|
- Cite sources inline with square brackets: [aaaaaaaaaaaa, bbbbbbbbbbbb]. Square brackets are only for citations.
|
|
25
25
|
- A future agent can recall citations for full paths, commands, errors, logs, and intermediate results. Keep those details inline only when they are needed to understand or use the summary; otherwise preserve the conclusion and a useful retrieval cue.
|
|
26
26
|
- A summary must stand alone and cite at least two newly consumable provided memories.
|
|
27
|
-
- Do not count tokens
|
|
27
|
+
- Do not count tokens, laboriously audit ids, or draft summaries in prose. Call summarize directly with in-progress summaries: the tool validates ids and compression and explains any rejection. If a recorded summary needs revision, correct it afterward with fix_summary.
|
|
28
28
|
|
|
29
29
|
Examples:
|
|
30
30
|
- BAD: "The test command was run several times [aaaaaaaaaaaa, bbbbbbbbbbbb]."
|
|
@@ -38,9 +38,13 @@ Tools:
|
|
|
38
38
|
- summarize records one or more summaries and can mark inspected memories keep_verbatim for this run. Read its receipt: it identifies every source removed from the visible pool. A rejected candidate changes nothing; correct it or leave the sources verbatim.
|
|
39
39
|
- fix_summary corrects or removes only a summary created in this run.
|
|
40
40
|
- search_memories and recall are for concrete evidence suggested by the provided records, not for hunting unrelated history to compress.
|
|
41
|
-
- Prose does not change memory.
|
|
41
|
+
- Prose does not change memory. DO NOT DRAFT summaries in text. Directly record in-progress summaries with summarize; use fix_summary afterward if they need revision.
|
|
42
42
|
- Call done alone after all safe work is recorded. If no safe summary is warranted, call done immediately.
|
|
43
43
|
|
|
44
44
|
Prefer faithful useful compression over both distortion and indefinite accumulation. Under pressure, make progress on old low-value clusters first; treat durable valuable records and user intent as the last things to compress.`;
|
|
45
45
|
|
|
46
|
-
export
|
|
46
|
+
export function summarizerContinue(recordedSummaries: number, reminderNumber: number): string {
|
|
47
|
+
const count = Math.max(0, Math.floor(recordedSummaries));
|
|
48
|
+
const thinkingMinutes = Math.max(1, Math.floor(reminderNumber)) * 20;
|
|
49
|
+
return `IMPORTANT!!!! YOU HAVE BEEN THINKING FOR ${thinkingMinutes} MINUTES. CALL A TOOL NOW. DO NOT DRAFT OR WRITE SUMMARIES IN THE MAIN TEXT. DIRECTLY RECORD IN-PROGRESS SUMMARIES USING summarize; IF THERE IS A PROBLEM, REVISE THEM LATER USING fix_summary. THERE ${count === 1 ? "IS" : "ARE"} CURRENTLY ${count} RECORDED ${count === 1 ? "SUMMARY" : "SUMMARIES"}${count === 0 ? "; NOTHING HAS BEEN SUMMARIZED YET" : ""}. IF YOU ALREADY WROTE SUMMARIES IN THE MAIN TEXT, RECORD THEM USING summarize NOW. IF NO SAFE SUMMARY IS WARRANTED, CALL done.`;
|
|
50
|
+
}
|
package/src/commands/settings.ts
CHANGED
|
@@ -58,8 +58,16 @@ export function observerInputCapLabel(runtime: Runtime, contextWindow: number |
|
|
|
58
58
|
return `${cap.toLocaleString()} tokens (fallback default; model context unavailable)`;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
function memoryWorkerModelSettingLabel(runtime: Runtime, worker: "observer" | "summarizer"): string {
|
|
62
|
+
const key = worker === "observer" ? "observerModel" : "summarizerModel";
|
|
63
|
+
const settings = runtime.getSessionSettings();
|
|
64
|
+
if (hasOverride(settings, key)) return modelLabel(runtime.config[key]);
|
|
65
|
+
const inherited = runtime.config[key] ?? runtime.config.model;
|
|
66
|
+
return `${modelLabel(inherited)} (default)`;
|
|
67
|
+
}
|
|
68
|
+
|
|
61
69
|
function observerContextWindow(runtime: Runtime, ctx: ExtensionContext): number | undefined {
|
|
62
|
-
const configured = runtime.
|
|
70
|
+
const configured = runtime.configuredMemoryWorkerModel("observer");
|
|
63
71
|
const registry = ctx.modelRegistry as unknown as ModelRegistryLike;
|
|
64
72
|
const model = configured ? registry.find?.(configured.provider, configured.id) ?? ctx.model : ctx.model;
|
|
65
73
|
return (model as { contextWindow?: number } | undefined)?.contextWindow;
|
|
@@ -140,11 +148,25 @@ class FilterableModelSelector extends Container implements Focusable {
|
|
|
140
148
|
}
|
|
141
149
|
}
|
|
142
150
|
|
|
143
|
-
async function
|
|
151
|
+
export async function modelsForSettingsSelector(ctx: ExtensionContext): Promise<ConfiguredModel[]> {
|
|
152
|
+
// Pi resolves enabledModels (and --models) into this session-scoped list.
|
|
153
|
+
// Mirroring it keeps our worker pickers consistent with the built-in model
|
|
154
|
+
// picker instead of unexpectedly exposing the entire provider catalogue.
|
|
155
|
+
if (ctx.scopedModels.length > 0) {
|
|
156
|
+
return ctx.scopedModels.map(({ model, thinkingLevel }) => ({
|
|
157
|
+
provider: model.provider,
|
|
158
|
+
id: model.id,
|
|
159
|
+
...(thinkingLevel === undefined ? {} : { thinking: thinkingLevel }),
|
|
160
|
+
}));
|
|
161
|
+
}
|
|
144
162
|
const registry = ctx.modelRegistry as unknown as ModelRegistryLike;
|
|
145
163
|
await registry.refresh?.();
|
|
146
164
|
const available = registry.getAvailable();
|
|
147
|
-
|
|
165
|
+
return (available.length > 0 ? available : registry.getAll()).map(({ provider, id }) => ({ provider, id }));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function chooseModel(ctx: ExtensionContext, current: ConfiguredModel | undefined, title: string): Promise<ConfiguredModel | null | undefined> {
|
|
169
|
+
const models = await modelsForSettingsSelector(ctx);
|
|
148
170
|
if (models.length === 0) {
|
|
149
171
|
ctx.ui.notify("No configured models are available.", "warning");
|
|
150
172
|
return undefined;
|
|
@@ -218,6 +240,7 @@ export function registerSettingsCommand(pi: ExtensionAPI, runtime: Runtime): voi
|
|
|
218
240
|
`Contemplator new-observation trigger (count): ${scalarLabel(runtime, "contemplatorMinNewObservations")}`,
|
|
219
241
|
`Contemplator response spacing (count): ${scalarLabel(runtime, "contemplatorMinTurns")}`,
|
|
220
242
|
`Summarizer enabled: ${scalarLabel(runtime, "summarizerEnabled")}`,
|
|
243
|
+
`Summarizer model: ${memoryWorkerModelSettingLabel(runtime, "summarizer")}`,
|
|
221
244
|
`New memory pool protection budget (tokens): ${scalarLabel(runtime, "newMemoryPoolMaxTokens")}`,
|
|
222
245
|
`Old memory pool target (tokens, advisory): ${scalarLabel(runtime, "oldMemoryPoolTargetTokens")}`,
|
|
223
246
|
`Summarizer old-pool retrigger growth (tokens): ${scalarLabel(runtime, "summarizerRetriggerTokens")}`,
|
|
@@ -225,7 +248,7 @@ export function registerSettingsCommand(pi: ExtensionAPI, runtime: Runtime): voi
|
|
|
225
248
|
`Structural reviewer enabled: ${scalarLabel(runtime, "reviewerEnabled")}`,
|
|
226
249
|
`Structural reviewer model: ${hasOverride(settings, "reviewerModel") ? modelLabel(runtime.config.reviewerModel) : `${modelLabel(runtime.getDefaultConfig().reviewerModel)} (default)`}`,
|
|
227
250
|
`Observe source during compaction: ${scalarLabel(runtime, "compactionObserverEnabled")}`,
|
|
228
|
-
`Observer
|
|
251
|
+
`Observer model: ${memoryWorkerModelSettingLabel(runtime, "observer")}`,
|
|
229
252
|
`Observer source backlog trigger (tokens): ${scalarLabel(runtime, "observeAfterTokens")}`,
|
|
230
253
|
`Observer input cap: ${observerInputCapLabel(runtime, observerContextWindow(runtime, ctx))}`,
|
|
231
254
|
`Observer and summarizer max rounds: ${scalarLabel(runtime, "agentMaxTurns")}`,
|
|
@@ -251,9 +274,12 @@ export function registerSettingsCommand(pi: ExtensionAPI, runtime: Runtime): voi
|
|
|
251
274
|
} else if (choice.startsWith("Structural reviewer model:")) {
|
|
252
275
|
const model = await chooseModel(ctx, runtime.config.reviewerModel, "Structural reviewer model");
|
|
253
276
|
if (model !== undefined) appendSettings(pi, runtime, { reviewerModel: model });
|
|
254
|
-
} else if (choice.startsWith("Observer
|
|
255
|
-
const model = await chooseModel(ctx, runtime.config.model, "Observer
|
|
256
|
-
if (model !== undefined) appendSettings(pi, runtime, { model });
|
|
277
|
+
} else if (choice.startsWith("Observer model:")) {
|
|
278
|
+
const model = await chooseModel(ctx, runtime.config.observerModel ?? runtime.config.model, "Observer model");
|
|
279
|
+
if (model !== undefined) appendSettings(pi, runtime, { observerModel: model });
|
|
280
|
+
} else if (choice.startsWith("Summarizer model:")) {
|
|
281
|
+
const model = await chooseModel(ctx, runtime.config.summarizerModel ?? runtime.config.model, "Summarizer model");
|
|
282
|
+
if (model !== undefined) appendSettings(pi, runtime, { summarizerModel: model });
|
|
257
283
|
} else if (choice.startsWith("Automatic compaction threshold mode:")) {
|
|
258
284
|
const mode = await ctx.ui.select("Automatic compaction threshold mode", ["calibrated", "ratio"]);
|
|
259
285
|
if (mode === "calibrated" || mode === "ratio") appendSettings(pi, runtime, { compactAfterTokensMode: mode });
|
package/src/commands/status.ts
CHANGED
|
@@ -40,6 +40,10 @@ function formatRunAge(timestamp: number): string {
|
|
|
40
40
|
return `${new Date(timestamp).toISOString()} (${formatDuration(Math.max(0, Date.now() - timestamp))} ago)`;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
function configuredModelLabel(model: { provider: string; id: string } | null): string {
|
|
44
|
+
return model ? `${model.provider}/${model.id}` : "current session model";
|
|
45
|
+
}
|
|
46
|
+
|
|
43
47
|
function contemplatorWaitingLabel(waitingFor: Runtime["contemplatorState"]["waitingFor"]): string {
|
|
44
48
|
switch (waitingFor) {
|
|
45
49
|
case "observer": return "waiting for observer backlog";
|
|
@@ -127,6 +131,8 @@ export function registerStatusCommand(pi: ExtensionAPI, runtime: Runtime): void
|
|
|
127
131
|
`Old memory pool: ~${pools.oldTokens.toLocaleString()} / ${runtime.config.oldMemoryPoolTargetTokens.toLocaleString()} advisory target tokens (${pct(pools.oldTokens, runtime.config.oldMemoryPoolTargetTokens)}%)`,
|
|
128
132
|
`Summary pool: ~${visibleSummaryTokens.toLocaleString()} visible tokens`,
|
|
129
133
|
`Summarizer: ${runtime.config.summarizerEnabled === false ? "disabled" : "enabled"}; retrigger after +${runtime.config.summarizerRetriggerTokens.toLocaleString()} old-pool tokens / sample above ~${summarizerSamplingTokens.toLocaleString()} tokens`,
|
|
134
|
+
`Summarizer model: ${configuredModelLabel(runtime.configuredMemoryWorkerModel("summarizer"))}`,
|
|
135
|
+
`Observer model: ${configuredModelLabel(runtime.configuredMemoryWorkerModel("observer"))}`,
|
|
130
136
|
`Cumulative agent time: ${formatDuration(agentActiveTimeMs(entries))}`,
|
|
131
137
|
`Observe source during compaction: ${runtime.config.compactionObserverEnabled === false ? "disabled" : "enabled"}`,
|
|
132
138
|
`Contemplator: ${runtime.config.contemplatorEnabled ? "enabled" : "disabled"}`,
|
package/src/config.ts
CHANGED
|
@@ -46,7 +46,12 @@ export interface Config {
|
|
|
46
46
|
/** Advisory token target for older summarizer-eligible memory. */
|
|
47
47
|
oldMemoryPoolTargetTokens: number;
|
|
48
48
|
agentMaxTurns: number;
|
|
49
|
+
/** Legacy shared fallback for observer/summarizer model selection. */
|
|
49
50
|
model?: ConfiguredModel;
|
|
51
|
+
/** Optional model override used only by the observer. */
|
|
52
|
+
observerModel?: ConfiguredModel;
|
|
53
|
+
/** Optional model override used only by the summarizer. */
|
|
54
|
+
summarizerModel?: ConfiguredModel;
|
|
50
55
|
showWorkerNotifications: boolean;
|
|
51
56
|
passive: boolean;
|
|
52
57
|
/** Run the asynchronous observer when a compaction begins. */
|
|
@@ -231,6 +236,10 @@ function normalizeSettingsConfig(value: Record<string, unknown>): Partial<Config
|
|
|
231
236
|
if (typeof value.debugLog === "boolean") normalized.debugLog = value.debugLog;
|
|
232
237
|
const model = normalizeModel(value.model);
|
|
233
238
|
if (model) normalized.model = model;
|
|
239
|
+
const observerModel = normalizeModel(value.observerModel);
|
|
240
|
+
if (observerModel) normalized.observerModel = observerModel;
|
|
241
|
+
const summarizerModel = normalizeModel(value.summarizerModel);
|
|
242
|
+
if (summarizerModel) normalized.summarizerModel = summarizerModel;
|
|
234
243
|
const contemplatorModel = normalizeModel(value.contemplatorModel);
|
|
235
244
|
if (contemplatorModel) normalized.contemplatorModel = contemplatorModel;
|
|
236
245
|
const reviewerModel = normalizeModel(value.reviewerModel);
|
|
@@ -72,6 +72,12 @@ function shouldNotifyWorker(runtime: Runtime, ctx: ConsolidationCtx): boolean {
|
|
|
72
72
|
return runtime.config.showWorkerNotifications && ctx.hasUI;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
function configuredMemoryWorkerModel(runtime: Runtime, worker: "observer" | "summarizer") {
|
|
76
|
+
return typeof runtime.configuredMemoryWorkerModel === "function"
|
|
77
|
+
? runtime.configuredMemoryWorkerModel(worker)
|
|
78
|
+
: runtime.config[worker === "observer" ? "observerModel" : "summarizerModel"] ?? runtime.config.model ?? null;
|
|
79
|
+
}
|
|
80
|
+
|
|
75
81
|
function makeModelResolver(runtime: Runtime, ctx: ConsolidationCtx): (stage: "observer") => Promise<ResolvedModel | undefined> {
|
|
76
82
|
let cached: ResolveResult | undefined;
|
|
77
83
|
return async (stage) => {
|
|
@@ -80,6 +86,7 @@ function makeModelResolver(runtime: Runtime, ctx: ConsolidationCtx): (stage: "ob
|
|
|
80
86
|
modelRegistry: ctx.modelRegistry,
|
|
81
87
|
hasUI: ctx.hasUI,
|
|
82
88
|
ui: ctx.ui,
|
|
89
|
+
configuredModel: configuredMemoryWorkerModel(runtime, "observer"),
|
|
83
90
|
});
|
|
84
91
|
if (cached.ok) {
|
|
85
92
|
runtime.resolveFailureNotified = false;
|
|
@@ -368,7 +375,13 @@ export function scheduleSummarizer(pi: ExtensionAPI, runtime: Runtime, ctx: Cons
|
|
|
368
375
|
runtime.lastSummarizerStartedAt = startedAt;
|
|
369
376
|
runtime.lastSummarizerRun = { startedAt, status: "running", messages: [] };
|
|
370
377
|
try {
|
|
371
|
-
const resolved = await runtime.resolveModel({
|
|
378
|
+
const resolved = await runtime.resolveModel({
|
|
379
|
+
model: ctx.model,
|
|
380
|
+
modelRegistry: ctx.modelRegistry,
|
|
381
|
+
hasUI: ctx.hasUI,
|
|
382
|
+
ui: ctx.ui,
|
|
383
|
+
configuredModel: configuredMemoryWorkerModel(runtime, "summarizer"),
|
|
384
|
+
});
|
|
372
385
|
if (!resolved.ok) {
|
|
373
386
|
debugLog("summarizer.model_unavailable", { reason: resolved.reason });
|
|
374
387
|
runtime.lastSummarizerRun = { startedAt, status: "failed", messages: [], error: resolved.reason };
|
|
@@ -394,7 +407,7 @@ export function scheduleSummarizer(pi: ExtensionAPI, runtime: Runtime, ctx: Cons
|
|
|
394
407
|
newPoolMaxTokens: runtime.config.newMemoryPoolMaxTokens,
|
|
395
408
|
samplingThresholdTokens: runtime.config.summarizerSamplingThresholdTokens,
|
|
396
409
|
maxTurns: runtime.config.agentMaxTurns,
|
|
397
|
-
thinkingLevel: runtime.config.model?.thinking ?? "minimal",
|
|
410
|
+
thinkingLevel: runtime.config.summarizerModel?.thinking ?? runtime.config.model?.thinking ?? "minimal",
|
|
398
411
|
recordUsage: (usage) => runtime.recordAgentUsage(usage),
|
|
399
412
|
onMessages: (messages) => {
|
|
400
413
|
watchdog.progress();
|
|
@@ -561,7 +574,7 @@ async function runObserverStage(
|
|
|
561
574
|
chunk,
|
|
562
575
|
allowedSourceEntryIds: sourceEntryIds,
|
|
563
576
|
maxTurns: runtime.config.agentMaxTurns,
|
|
564
|
-
thinkingLevel: runtime.config.model?.thinking ?? "low",
|
|
577
|
+
thinkingLevel: runtime.config.observerModel?.thinking ?? runtime.config.model?.thinking ?? "low",
|
|
565
578
|
recordUsage: (usage) => runtime.recordAgentUsage(usage),
|
|
566
579
|
onProgress: observerWatchdog.progress,
|
|
567
580
|
onMessages: (messages) => {
|
package/src/runtime.ts
CHANGED
|
@@ -31,6 +31,8 @@ export type SessionSettings = Partial<Pick<Config,
|
|
|
31
31
|
>> & {
|
|
32
32
|
/** null explicitly means use the configured/session model. */
|
|
33
33
|
model?: ConfiguredModel | null;
|
|
34
|
+
observerModel?: ConfiguredModel | null;
|
|
35
|
+
summarizerModel?: ConfiguredModel | null;
|
|
34
36
|
contemplatorModel?: ConfiguredModel | null;
|
|
35
37
|
reviewerModel?: ConfiguredModel | null;
|
|
36
38
|
};
|
|
@@ -148,6 +150,10 @@ export function computeSessionSettings(entries: readonly unknown[]): SessionSett
|
|
|
148
150
|
if (typeof data.compactAfterTokensRatio === "number" && data.compactAfterTokensRatio > 0 && data.compactAfterTokensRatio < 1) restored.compactAfterTokensRatio = data.compactAfterTokensRatio;
|
|
149
151
|
if (data.model === null) restored.model = null;
|
|
150
152
|
else if (isConfiguredModel(data.model)) restored.model = data.model;
|
|
153
|
+
if (data.observerModel === null) restored.observerModel = null;
|
|
154
|
+
else if (isConfiguredModel(data.observerModel)) restored.observerModel = data.observerModel;
|
|
155
|
+
if (data.summarizerModel === null) restored.summarizerModel = null;
|
|
156
|
+
else if (isConfiguredModel(data.summarizerModel)) restored.summarizerModel = data.summarizerModel;
|
|
151
157
|
if (data.contemplatorModel === null) restored.contemplatorModel = null;
|
|
152
158
|
else if (isConfiguredModel(data.contemplatorModel)) restored.contemplatorModel = data.contemplatorModel;
|
|
153
159
|
if (data.reviewerModel === null) restored.reviewerModel = null;
|
|
@@ -240,11 +246,13 @@ export class Runtime {
|
|
|
240
246
|
}
|
|
241
247
|
|
|
242
248
|
private applySessionSettings(): void {
|
|
243
|
-
const { model, contemplatorModel, reviewerModel, ...scalarSettings } = this.sessionSettings;
|
|
249
|
+
const { model, observerModel, summarizerModel, contemplatorModel, reviewerModel, ...scalarSettings } = this.sessionSettings;
|
|
244
250
|
this.config = {
|
|
245
251
|
...this.baseConfig,
|
|
246
252
|
...scalarSettings,
|
|
247
253
|
...(model === undefined ? {} : { model: model ?? undefined }),
|
|
254
|
+
...(observerModel === undefined ? {} : { observerModel: observerModel ?? undefined }),
|
|
255
|
+
...(summarizerModel === undefined ? {} : { summarizerModel: summarizerModel ?? undefined }),
|
|
248
256
|
...(contemplatorModel === undefined ? {} : { contemplatorModel: contemplatorModel ?? undefined }),
|
|
249
257
|
...(reviewerModel === undefined ? {} : { reviewerModel: reviewerModel ?? undefined }),
|
|
250
258
|
};
|
|
@@ -263,6 +271,14 @@ export class Runtime {
|
|
|
263
271
|
return { ...this.baseConfig };
|
|
264
272
|
}
|
|
265
273
|
|
|
274
|
+
/** Resolve an observer/summarizer override, retaining explicit session-model selection. */
|
|
275
|
+
configuredMemoryWorkerModel(worker: "observer" | "summarizer"): ConfiguredModel | null {
|
|
276
|
+
const key = worker === "observer" ? "observerModel" : "summarizerModel";
|
|
277
|
+
const sessionValue = this.sessionSettings[key];
|
|
278
|
+
if (sessionValue === null) return null;
|
|
279
|
+
return this.config[key] ?? this.config.model ?? null;
|
|
280
|
+
}
|
|
281
|
+
|
|
266
282
|
advanceContextGeneration(): void {
|
|
267
283
|
this.contextGeneration++;
|
|
268
284
|
// A session switch (or reload/shutdown) invalidates any in-flight or pending
|