@hassangameryt/freeflow 0.7.2 → 0.7.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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/README.md +147 -127
- package/capabilities/cognitive-routing/SKILL.md +162 -93
- package/gemini-extension.json +1 -1
- package/package.json +1 -1
- package/pi-extension/dist/cognitive-routing-v2/config.js +35 -19
- package/pi-extension/dist/cognitive-routing-v2/projection.js +53 -44
- package/pi-extension/dist/cognitive-routing-v2/render.js +31 -10
- package/pi-extension/dist/cognitive-routing-v2/runtime.js +701 -105
- package/pi-extension/dist/cognitive-routing-v2/schemas.js +90 -24
- package/pi-extension/dist/cognitive-routing-v2/state.js +243 -27
- package/pi-extension/dist/cognitive-routing-v2/tools.js +19 -50
- package/pi-extension/dist/cognitive-routing-v2/types.js +5 -1
- package/pi-extension/dist/context-virtualization/runtime.js +29 -7
- package/pi-extension/dist/freeflow-context/tool.js +7 -6
- package/pi-extension/dist/index.js +36 -15
- package/pi-extension/dist/provider-support/astra/adapter.js +110 -0
- package/pi-extension/dist/provider-support/astra/history.js +125 -0
- package/pi-extension/dist/provider-support/astra/index.js +11 -0
- package/pi-extension/dist/provider-support/astra/session-state.js +114 -0
- package/pi-extension/dist/provider-support/index.js +4 -0
- package/pi-extension/dist/runtime/request-history.js +216 -0
- package/pi-extension/dist/runtime/runtime-context.js +29 -10
- package/pi-extension/dist/session-sources/events.js +22 -0
- package/pi-extension/dist/session-sources/provenance.js +32 -0
- package/pi-extension/dist/session-sources/sources.js +51 -7
- package/pi-extension/dist/settings/settings-ui.js +247 -47
- package/pi-extension/src/cognitive-routing-v2/config.ts +49 -19
- package/pi-extension/src/cognitive-routing-v2/projection.ts +53 -43
- package/pi-extension/src/cognitive-routing-v2/render.ts +31 -10
- package/pi-extension/src/cognitive-routing-v2/runtime.ts +712 -105
- package/pi-extension/src/cognitive-routing-v2/schemas.ts +95 -24
- package/pi-extension/src/cognitive-routing-v2/state.ts +244 -27
- package/pi-extension/src/cognitive-routing-v2/tools.ts +19 -50
- package/pi-extension/src/cognitive-routing-v2/types.ts +35 -3
- package/pi-extension/src/context-virtualization/runtime.ts +30 -8
- package/pi-extension/src/freeflow-context/tool.ts +7 -6
- package/pi-extension/src/index.ts +36 -14
- package/pi-extension/src/provider-support/astra/adapter.ts +108 -0
- package/pi-extension/src/provider-support/astra/history.ts +145 -0
- package/pi-extension/src/provider-support/astra/index.ts +12 -0
- package/pi-extension/src/provider-support/astra/session-state.ts +113 -0
- package/pi-extension/src/provider-support/index.ts +5 -0
- package/pi-extension/src/runtime/request-history.ts +243 -0
- package/pi-extension/src/runtime/runtime-context.ts +32 -10
- package/pi-extension/src/session-sources/events.ts +22 -0
- package/pi-extension/src/session-sources/provenance.ts +39 -0
- package/pi-extension/src/session-sources/sources.ts +77 -6
- package/pi-extension/src/settings/settings-ui.ts +276 -46
- package/pi-extension/tests/cache-reuse/history.test.js +117 -0
- package/pi-extension/tests/cache-reuse/native.test.js +91 -0
- package/pi-extension/tests/cache-reuse/settings.test.js +211 -0
- package/pi-extension/tests/cognitive-routing-v2/communication.test.js +94 -1
- package/pi-extension/tests/cognitive-routing-v2/delegation.test.js +557 -0
- package/pi-extension/tests/cognitive-routing-v2/native.test.js +34 -1
- package/pi-extension/tests/cognitive-routing-v2/persistence.test.js +55 -0
- package/pi-extension/tests/cognitive-routing-v2/recovery.test.js +800 -2
- package/pi-extension/tests/cognitive-routing-v2/render.test.js +15 -1
- package/pi-extension/tests/cognitive-routing-v2/runtime.test.js +273 -3
- package/pi-extension/tests/cognitive-routing-v2/state.test.js +312 -1
- package/pi-extension/tests/context-virtualization/extension.test.js +20 -10
- package/pi-extension/tests/context-virtualization/runtime.test.js +17 -3
- package/pi-extension/tests/fixtures/routing-native.js +4 -1
- package/pi-extension/tests/integration/mode-free-core.test.js +4 -3
- package/pi-extension/tests/integration/pi-extension.test.js +508 -29
- package/pi-extension/tests/integration/prompt-architecture.test.js +56 -40
- package/pi-extension/tests/integration/unaffected-regressions.test.js +3 -3
- package/pi-extension/tests/provider-support/astra-boundaries.test.js +75 -0
- package/pi-extension/tests/provider-support/astra.test.js +306 -0
- package/pi-extension/tests/provider-support/cli.test.js +92 -0
- package/pi-extension/tests/provider-support/native.test.js +183 -0
- package/plugin.json +1 -1
- package/runtime/prompts/cognitive-routing.md +4 -4
- package/skills/design-for-depth/SKILL.md +1 -1
- package/skills/design-for-depth/references/implementation-decisions.md +3 -3
- package/skills/discuss/SKILL.md +2 -2
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
} from "../runtime/runtime-context.js";
|
|
13
13
|
import { PiSettingsComponent } from "./settings-tui.js";
|
|
14
14
|
import { isPiFlowHost } from "../runtime/runtime-identity.js";
|
|
15
|
+
import { isWorkerProfile, workersForDelegation } from "../cognitive-routing-v2/types.js";
|
|
15
16
|
const DEFAULT_FREEFLOW_ENABLED = true;
|
|
16
17
|
const DEFAULT_CONTEXT_VIRTUALIZATION_ENABLED = false;
|
|
17
18
|
const DEFAULT_CONVERSATION_HISTORY_ENABLED = false;
|
|
@@ -191,6 +192,68 @@ function createScopedBooleanItem(options) {
|
|
|
191
192
|
item.displaySuffix = coreDisplaySuffix(item);
|
|
192
193
|
return item;
|
|
193
194
|
}
|
|
195
|
+
function createScopedDelegationItem(options) {
|
|
196
|
+
const path = ["cognitiveRouting", "delegation"];
|
|
197
|
+
const repositoryValue = getPath(options.rawConfig, path);
|
|
198
|
+
const inheritedValue = ["executor", "helper", "both"].includes(repositoryValue) ? repositoryValue : "executor";
|
|
199
|
+
const inheritedSource = repositoryValue === undefined ? "builtin" : "repository";
|
|
200
|
+
const localValue = getPath(options.localConfig, path);
|
|
201
|
+
const descriptions = {
|
|
202
|
+
executor: "Coordinator delegates assignments to Executor.",
|
|
203
|
+
helper: "Coordinator delegates supporting assignments to Helper and keeps production implementation.",
|
|
204
|
+
both: "Coordinator chooses Helper or Executor for each assignment.",
|
|
205
|
+
};
|
|
206
|
+
const item =
|
|
207
|
+
options.scope === "local"
|
|
208
|
+
? {
|
|
209
|
+
id: "freeflow.cognitiveRouting.delegation",
|
|
210
|
+
label: "Delegation method",
|
|
211
|
+
description:
|
|
212
|
+
"Choose enabled workers for automatic routing. Inherit uses the repository value; this setting does not rank model presets.",
|
|
213
|
+
path,
|
|
214
|
+
kind: "enum",
|
|
215
|
+
value: ["executor", "helper", "both"].includes(localValue) ? localValue : LOCAL_INHERIT,
|
|
216
|
+
values: [LOCAL_INHERIT, "executor", "helper", "both"],
|
|
217
|
+
valueLabels: {
|
|
218
|
+
inherit: "Inherit repository",
|
|
219
|
+
executor: "Executor only",
|
|
220
|
+
helper: "Helper only",
|
|
221
|
+
both: "Helper and Executor",
|
|
222
|
+
},
|
|
223
|
+
valueDescriptions: {
|
|
224
|
+
inherit: `Use ${inheritedValue} from ${inheritedSource}.`,
|
|
225
|
+
...descriptions,
|
|
226
|
+
},
|
|
227
|
+
configScope: "local",
|
|
228
|
+
configValues: { inherit: undefined, executor: "executor", helper: "helper", both: "both" },
|
|
229
|
+
effectiveValue: options.effectiveValue,
|
|
230
|
+
effectiveSource: options.effectiveSource,
|
|
231
|
+
inheritedValue,
|
|
232
|
+
inheritedSource,
|
|
233
|
+
}
|
|
234
|
+
: {
|
|
235
|
+
id: "freeflow.cognitiveRouting.delegation",
|
|
236
|
+
label: "Delegation method",
|
|
237
|
+
description: "Choose enabled workers for automatic routing. This edits shared .freeflow/config.json.",
|
|
238
|
+
path,
|
|
239
|
+
kind: "enum",
|
|
240
|
+
value: inheritedValue,
|
|
241
|
+
values: ["executor", "helper", "both"],
|
|
242
|
+
valueLabels: {
|
|
243
|
+
executor: "Executor only",
|
|
244
|
+
helper: "Helper only",
|
|
245
|
+
both: "Helper and Executor",
|
|
246
|
+
},
|
|
247
|
+
valueDescriptions: descriptions,
|
|
248
|
+
defaultValue: "executor",
|
|
249
|
+
configScope: "repository",
|
|
250
|
+
effectiveValue: options.effectiveValue,
|
|
251
|
+
effectiveSource: options.effectiveSource,
|
|
252
|
+
localOverrideValue: ["executor", "helper", "both"].includes(localValue) ? localValue : undefined,
|
|
253
|
+
};
|
|
254
|
+
item.displaySuffix = coreDisplaySuffix(item);
|
|
255
|
+
return item;
|
|
256
|
+
}
|
|
194
257
|
function resolveSettingsCoreView(rawConfig, layers) {
|
|
195
258
|
const localConfig = layers?.local.valid && isRecord(layers.local.parsed) ? layers.local.parsed : {};
|
|
196
259
|
const fallbackCore = {
|
|
@@ -247,7 +310,7 @@ function createSessionBooleanItem(options) {
|
|
|
247
310
|
item.displaySuffix = coreDisplaySuffix(item);
|
|
248
311
|
return item;
|
|
249
312
|
}
|
|
250
|
-
function sessionFreeflowItems(state, cognitiveRoutingController) {
|
|
313
|
+
function sessionFreeflowItems(state, cognitiveRoutingController, ctx) {
|
|
251
314
|
const sessionOverrides = state.sessionOverrides;
|
|
252
315
|
const configured = state.configuredCoreConfig;
|
|
253
316
|
const configuredSources = state.configuredSources;
|
|
@@ -289,47 +352,76 @@ function sessionFreeflowItems(state, cognitiveRoutingController) {
|
|
|
289
352
|
contextVirtualizationItem.inactive = freeflowInactive;
|
|
290
353
|
conversationHistoryItem.inactive = freeflowInactive;
|
|
291
354
|
const cognitiveRoutingState = cognitiveRoutingController?.state();
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
355
|
+
const delegation = state.cognitiveRouting?.delegation ?? "executor";
|
|
356
|
+
const heldProfile = cognitiveRoutingState?.controlMode.startsWith("manual-")
|
|
357
|
+
? cognitiveRoutingState.activeProfile
|
|
358
|
+
: undefined;
|
|
359
|
+
const availableProfiles = ["coordinator", ...workersForDelegation(delegation)];
|
|
360
|
+
if (heldProfile === "coordinator" || isWorkerProfile(heldProfile)) availableProfiles.push(heldProfile);
|
|
361
|
+
const manualProfiles = [...new Set(availableProfiles)];
|
|
362
|
+
const cognitiveRoutingProfile = heldProfile && manualProfiles.includes(heldProfile) ? heldProfile : "auto";
|
|
298
363
|
const cognitiveRoutingItem = state.cognitiveRouting
|
|
299
364
|
? {
|
|
300
365
|
id: "freeflow.cognitiveRouting.profile",
|
|
301
366
|
label: "Cognitive Routing",
|
|
302
|
-
description: "Hold
|
|
367
|
+
description: "Hold an enabled profile manually or release the hold for automatic model control.",
|
|
303
368
|
kind: "enum",
|
|
304
369
|
value: cognitiveRoutingProfile,
|
|
305
|
-
values: ["auto",
|
|
306
|
-
valueLabels:
|
|
307
|
-
auto
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
coordinator: "Hold the coordinator profile until /freeflow profile auto.",
|
|
315
|
-
},
|
|
370
|
+
values: ["auto", ...manualProfiles],
|
|
371
|
+
valueLabels: Object.fromEntries([
|
|
372
|
+
["auto", "automatic"],
|
|
373
|
+
...manualProfiles.map((profile) => [profile, `manual · ${profile}`]),
|
|
374
|
+
]),
|
|
375
|
+
valueDescriptions: Object.fromEntries([
|
|
376
|
+
["auto", "Release the manual hold and return to Coordinator reconciliation."],
|
|
377
|
+
...manualProfiles.map((profile) => [profile, `Hold the ${profile} profile until /freeflow profile auto.`]),
|
|
378
|
+
]),
|
|
316
379
|
inactive: freeflowInactive || !state.cognitiveRouting.effective || cognitiveRoutingController === undefined,
|
|
317
380
|
runtimeInactive: state.cognitiveRouting.blockingReason?.code === "runtime_disabled",
|
|
318
381
|
displaySuffix: cognitiveRoutingState?.effective ? cognitiveRoutingProfile : "unavailable",
|
|
319
382
|
}
|
|
320
383
|
: undefined;
|
|
384
|
+
const sessionProfiles = cognitiveRoutingController?.sessionProfileOverrides();
|
|
385
|
+
const profileItems =
|
|
386
|
+
state.cognitiveRouting && cognitiveRoutingController
|
|
387
|
+
? manualProfiles.map((name) =>
|
|
388
|
+
cognitiveRoutingProfileItem({
|
|
389
|
+
name,
|
|
390
|
+
scope: "session",
|
|
391
|
+
rawConfig: {},
|
|
392
|
+
localConfig: {},
|
|
393
|
+
capabilityState: state.cognitiveRouting,
|
|
394
|
+
ctx,
|
|
395
|
+
sessionProfiles,
|
|
396
|
+
}),
|
|
397
|
+
)
|
|
398
|
+
: [];
|
|
399
|
+
const cognitiveRoutingPresets = profileItems.length
|
|
400
|
+
? {
|
|
401
|
+
id: "freeflow.cognitiveRouting.presets",
|
|
402
|
+
label: "Cognitive Routing presets",
|
|
403
|
+
description: "Temporarily choose complete model/effort pairs for profiles enabled in this Pi session.",
|
|
404
|
+
kind: "group",
|
|
405
|
+
value: profileItems.some((item) => item.value !== LOCAL_INHERIT && item.value !== undefined),
|
|
406
|
+
displaySuffix: `${profileItems.filter((item) => item.value !== LOCAL_INHERIT && item.value !== undefined).length}/${profileItems.length} session overrides`,
|
|
407
|
+
inactive: freeflowInactive || !state.cognitiveRouting?.effective || cognitiveRoutingController === undefined,
|
|
408
|
+
children: profileItems,
|
|
409
|
+
}
|
|
410
|
+
: undefined;
|
|
321
411
|
return [
|
|
322
412
|
freeflowItem,
|
|
323
413
|
...(cognitiveRoutingItem ? [cognitiveRoutingItem] : []),
|
|
414
|
+
...(cognitiveRoutingPresets ? [cognitiveRoutingPresets] : []),
|
|
324
415
|
{
|
|
325
416
|
id: "freeflow.session.reset",
|
|
326
417
|
label: "Reset session overrides",
|
|
327
|
-
description:
|
|
418
|
+
description:
|
|
419
|
+
"Clear Freeflow, Context Virtualization, Conversation History, and routing profile overrides for this Pi session.",
|
|
328
420
|
kind: "enum",
|
|
329
421
|
value: "available",
|
|
330
422
|
values: ["reset"],
|
|
331
423
|
valueLabels: { reset: "Reset all session overrides" },
|
|
332
|
-
valueDescriptions: { reset: "Return every session setting to its configured value." },
|
|
424
|
+
valueDescriptions: { reset: "Return every session setting and routing preset to its configured value." },
|
|
333
425
|
format: () => "available",
|
|
334
426
|
transient: true,
|
|
335
427
|
},
|
|
@@ -412,12 +504,14 @@ function cognitiveRoutingProfileDisplay(value) {
|
|
|
412
504
|
return isCognitiveRoutingProfile(value) ? formatCoreValue(value) : "not configured";
|
|
413
505
|
}
|
|
414
506
|
function cognitiveRoutingSettingsSource(source) {
|
|
507
|
+
if (source === "session") return "session";
|
|
415
508
|
if (source === "repository") return "repository";
|
|
416
509
|
if (source === "personal" || source === "local") return "local";
|
|
417
510
|
return "builtin";
|
|
418
511
|
}
|
|
419
512
|
function cognitiveRoutingProfileDisplaySuffix(source, scope) {
|
|
420
513
|
if (!source) return undefined;
|
|
514
|
+
if (source === "session") return "(session override)";
|
|
421
515
|
if (source === "personal" && scope === "repository") return "(effective personal)";
|
|
422
516
|
return `(${source})`;
|
|
423
517
|
}
|
|
@@ -436,7 +530,16 @@ function cognitiveRoutingConfirmStep(summary) {
|
|
|
436
530
|
selectedKey: "__save__",
|
|
437
531
|
};
|
|
438
532
|
}
|
|
439
|
-
function createCognitiveRoutingProfileWizard(
|
|
533
|
+
function createCognitiveRoutingProfileWizard(
|
|
534
|
+
currentValue,
|
|
535
|
+
models,
|
|
536
|
+
allowInherit,
|
|
537
|
+
inheritChoice = {
|
|
538
|
+
label: "Inherit repository preset",
|
|
539
|
+
description: "Remove the personal override without changing the repository preset.",
|
|
540
|
+
summary: "inherit repository preset",
|
|
541
|
+
},
|
|
542
|
+
) {
|
|
440
543
|
const currentProfile = isCognitiveRoutingProfile(currentValue) ? currentValue : undefined;
|
|
441
544
|
const currentModelKey = currentProfile
|
|
442
545
|
? cognitiveRoutingModelKey(currentProfile.provider, currentProfile.model)
|
|
@@ -447,8 +550,8 @@ function createCognitiveRoutingProfileWizard(currentValue, models, allowInherit)
|
|
|
447
550
|
{
|
|
448
551
|
key: LOCAL_INHERIT,
|
|
449
552
|
value: LOCAL_INHERIT,
|
|
450
|
-
label:
|
|
451
|
-
description:
|
|
553
|
+
label: inheritChoice.label,
|
|
554
|
+
description: inheritChoice.description,
|
|
452
555
|
},
|
|
453
556
|
]
|
|
454
557
|
: []),
|
|
@@ -482,7 +585,7 @@ function createCognitiveRoutingProfileWizard(currentValue, models, allowInherit)
|
|
|
482
585
|
if (selectedValues.length === 1) {
|
|
483
586
|
const selected = selectedValues[0];
|
|
484
587
|
if (selected === LOCAL_INHERIT) {
|
|
485
|
-
return cognitiveRoutingConfirmStep(
|
|
588
|
+
return cognitiveRoutingConfirmStep(inheritChoice.summary);
|
|
486
589
|
}
|
|
487
590
|
const model = selected;
|
|
488
591
|
if (!model?.key || model.thinkingLevels.length === 0) return undefined;
|
|
@@ -521,31 +624,59 @@ function cognitiveRoutingProfileItem(options) {
|
|
|
521
624
|
const path = ["cognitiveRouting", "profiles", options.name];
|
|
522
625
|
const repositoryProfile = getPath(options.rawConfig, path);
|
|
523
626
|
const localProfile = getPath(options.localConfig, path);
|
|
524
|
-
const
|
|
627
|
+
const configuredProfile = options.capabilityState?.profiles?.[options.name] ?? repositoryProfile;
|
|
628
|
+
const sessionPair = options.sessionProfiles?.[options.name];
|
|
629
|
+
const sessionProfile = sessionPair
|
|
630
|
+
? { provider: sessionPair.provider, model: sessionPair.modelId, thinking: sessionPair.thinking }
|
|
631
|
+
: undefined;
|
|
632
|
+
const effectiveProfile = sessionProfile ?? configuredProfile;
|
|
525
633
|
const localValue = isCognitiveRoutingProfile(localProfile) ? localProfile : LOCAL_INHERIT;
|
|
526
|
-
const value =
|
|
527
|
-
|
|
634
|
+
const value =
|
|
635
|
+
options.scope === "session"
|
|
636
|
+
? (sessionProfile ?? LOCAL_INHERIT)
|
|
637
|
+
: options.scope === "local"
|
|
638
|
+
? localValue
|
|
639
|
+
: repositoryProfile;
|
|
640
|
+
const configuredSource =
|
|
528
641
|
options.capabilityState?.profileSources?.[options.name] ??
|
|
529
642
|
(isCognitiveRoutingProfile(repositoryProfile) ? "repository" : undefined);
|
|
643
|
+
const source = options.scope === "session" ? (sessionProfile ? "session" : configuredSource) : configuredSource;
|
|
530
644
|
const models = cognitiveRoutingModelOptions(options.ctx);
|
|
531
|
-
const inactive = options.scope
|
|
645
|
+
const inactive = options.scope !== "repository" && models.length === 0 && value !== LOCAL_INHERIT;
|
|
646
|
+
const inheritChoice =
|
|
647
|
+
options.scope === "session"
|
|
648
|
+
? {
|
|
649
|
+
label: "Inherit configured preset",
|
|
650
|
+
description: "Remove the session override without changing local or repository configuration.",
|
|
651
|
+
summary: "inherit configured preset",
|
|
652
|
+
}
|
|
653
|
+
: undefined;
|
|
532
654
|
return {
|
|
533
655
|
id: `freeflow.cognitiveRouting.${options.name}`,
|
|
534
656
|
label: `${options.name.charAt(0).toUpperCase()}${options.name.slice(1)} preset`,
|
|
535
657
|
description:
|
|
536
|
-
|
|
658
|
+
options.scope === "session"
|
|
659
|
+
? "Temporarily choose the model and effort for this profile in the current Pi session only."
|
|
660
|
+
: "Choose an authenticated available model and one effort supported by that model. Confirming writes the complete preset; cancel leaves it unchanged.",
|
|
537
661
|
path,
|
|
538
662
|
kind: "string",
|
|
539
663
|
value,
|
|
540
|
-
format:
|
|
664
|
+
format: (current) => {
|
|
665
|
+
const inherits = current === LOCAL_INHERIT || (options.scope === "session" && current === undefined);
|
|
666
|
+
return inherits
|
|
667
|
+
? options.scope === "session"
|
|
668
|
+
? "inherit configured preset"
|
|
669
|
+
: "inherit repository preset"
|
|
670
|
+
: cognitiveRoutingProfileDisplay(current);
|
|
671
|
+
},
|
|
541
672
|
configScope: options.scope,
|
|
542
673
|
effectiveValue: effectiveProfile,
|
|
543
674
|
effectiveSource: cognitiveRoutingSettingsSource(source),
|
|
544
|
-
inheritedValue: repositoryProfile,
|
|
545
|
-
inheritedSource: "
|
|
675
|
+
inheritedValue: options.scope === "session" ? configuredProfile : repositoryProfile,
|
|
676
|
+
inheritedSource: cognitiveRoutingSettingsSource(configuredSource ?? "builtin"),
|
|
546
677
|
inactive,
|
|
547
678
|
displaySuffix: cognitiveRoutingProfileDisplaySuffix(source, options.scope),
|
|
548
|
-
wizard: () => createCognitiveRoutingProfileWizard(value, models, options.scope
|
|
679
|
+
wizard: () => createCognitiveRoutingProfileWizard(value, models, options.scope !== "repository", inheritChoice),
|
|
549
680
|
};
|
|
550
681
|
}
|
|
551
682
|
function isCognitiveRoutingRuntimeAvailable(pi) {
|
|
@@ -557,7 +688,7 @@ function isCognitiveRoutingRuntimeAvailable(pi) {
|
|
|
557
688
|
);
|
|
558
689
|
}
|
|
559
690
|
function freeflowItems(rawConfig, options = {}) {
|
|
560
|
-
const scope = options.scope
|
|
691
|
+
const scope = options.scope === "local" ? "local" : "repository";
|
|
561
692
|
const layers = options.layers;
|
|
562
693
|
const { localConfig, core, sources } = resolveSettingsCoreView(rawConfig, layers);
|
|
563
694
|
const freeflowItem = createScopedBooleanItem({
|
|
@@ -618,6 +749,15 @@ function freeflowItems(rawConfig, options = {}) {
|
|
|
618
749
|
});
|
|
619
750
|
cognitiveRoutingEnabledItem.inactive = freeflowInactive || cognitiveRoutingRuntimeDisabled;
|
|
620
751
|
cognitiveRoutingEnabledItem.runtimeInactive = cognitiveRoutingRuntimeDisabled;
|
|
752
|
+
const cognitiveRoutingDelegationItem = createScopedDelegationItem({
|
|
753
|
+
scope,
|
|
754
|
+
rawConfig,
|
|
755
|
+
localConfig,
|
|
756
|
+
effectiveValue: cognitiveRoutingState?.delegation ?? "executor",
|
|
757
|
+
effectiveSource: cognitiveRoutingSettingsSource(cognitiveRoutingState?.delegationSource),
|
|
758
|
+
});
|
|
759
|
+
cognitiveRoutingDelegationItem.inactive = freeflowInactive || cognitiveRoutingRuntimeDisabled;
|
|
760
|
+
cognitiveRoutingDelegationItem.runtimeInactive = cognitiveRoutingRuntimeDisabled;
|
|
621
761
|
const cognitiveRoutingProjectionItem = createScopedBooleanItem({
|
|
622
762
|
scope,
|
|
623
763
|
rawConfig,
|
|
@@ -625,7 +765,7 @@ function freeflowItems(rawConfig, options = {}) {
|
|
|
625
765
|
id: "freeflow.cognitiveRouting.projection",
|
|
626
766
|
label: "Context projection",
|
|
627
767
|
description:
|
|
628
|
-
"Select
|
|
768
|
+
"Select worker evidence for Coordinator; disable projection to keep ordinary Pi context in all enabled profiles.",
|
|
629
769
|
path: ["cognitiveRouting", "projection"],
|
|
630
770
|
effectiveValue: cognitiveRoutingState?.projection ?? false,
|
|
631
771
|
effectiveSource: cognitiveRoutingSettingsSource(cognitiveRoutingState?.projectionSource),
|
|
@@ -633,7 +773,7 @@ function freeflowItems(rawConfig, options = {}) {
|
|
|
633
773
|
});
|
|
634
774
|
cognitiveRoutingProjectionItem.inactive = freeflowInactive || cognitiveRoutingRuntimeDisabled;
|
|
635
775
|
cognitiveRoutingProjectionItem.runtimeInactive = cognitiveRoutingRuntimeDisabled;
|
|
636
|
-
const cognitiveRoutingProfiles = ["
|
|
776
|
+
const cognitiveRoutingProfiles = ["coordinator", "helper", "executor"].map((name) =>
|
|
637
777
|
cognitiveRoutingProfileItem({
|
|
638
778
|
name: name,
|
|
639
779
|
scope,
|
|
@@ -661,12 +801,17 @@ function freeflowItems(rawConfig, options = {}) {
|
|
|
661
801
|
label: "Cognitive Routing",
|
|
662
802
|
description: cognitiveRoutingRuntimeDisabled
|
|
663
803
|
? "Cognitive Routing configuration is visible for inspection but requires a host model-state control API."
|
|
664
|
-
: "Configure
|
|
804
|
+
: "Configure Coordinator, Helper, and Executor profiles plus the workers available for delegation.",
|
|
665
805
|
kind: "group",
|
|
666
806
|
value: cognitiveRoutingRuntimeDisabled ? false : (cognitiveRoutingState?.enabled ?? false),
|
|
667
807
|
inactive: freeflowInactive,
|
|
668
808
|
displaySuffix: cognitiveRoutingStatus,
|
|
669
|
-
children: [
|
|
809
|
+
children: [
|
|
810
|
+
cognitiveRoutingEnabledItem,
|
|
811
|
+
cognitiveRoutingDelegationItem,
|
|
812
|
+
...cognitiveRoutingProfiles,
|
|
813
|
+
cognitiveRoutingProjectionItem,
|
|
814
|
+
],
|
|
670
815
|
};
|
|
671
816
|
})();
|
|
672
817
|
return [
|
|
@@ -688,6 +833,7 @@ function pruneKnownDefaults(config) {
|
|
|
688
833
|
{ path: ["enabled"], value: DEFAULT_FREEFLOW_ENABLED },
|
|
689
834
|
{ path: ["contextVirtualization"], value: DEFAULT_CONTEXT_VIRTUALIZATION_ENABLED },
|
|
690
835
|
{ path: ["conversationHistory"], value: DEFAULT_CONVERSATION_HISTORY_ENABLED },
|
|
836
|
+
{ path: ["cognitiveRouting", "delegation"], value: "executor" },
|
|
691
837
|
];
|
|
692
838
|
for (const item of defaultPaths) {
|
|
693
839
|
if (valuesEqual(getPath(config, item.path), item.value)) {
|
|
@@ -761,7 +907,7 @@ async function updateConfig(cwd, item, value, scope = item.configScope ?? "repos
|
|
|
761
907
|
await writeFile(join(cwd, ".freeflow/config.json"), `${JSON.stringify(next, null, 2)}\n`, "utf8");
|
|
762
908
|
}
|
|
763
909
|
function isCognitiveRoutingProfileItem(item) {
|
|
764
|
-
return
|
|
910
|
+
return ["coordinator", "helper", "executor"].some((profile) => item.id === `freeflow.cognitiveRouting.${profile}`);
|
|
765
911
|
}
|
|
766
912
|
function valueForDisplay(item) {
|
|
767
913
|
let value;
|
|
@@ -801,10 +947,11 @@ function refreshSettingsDerivedState(items) {
|
|
|
801
947
|
const freeflowInactive = freeflowItem ? effectiveItemValue(freeflowItem) !== true : false;
|
|
802
948
|
const cognitiveRoutingGroup = findSettingsItem(items, "freeflow.cognitiveRouting");
|
|
803
949
|
const cognitiveRoutingEnabledItem = findSettingsItem(items, "freeflow.cognitiveRouting.enabled");
|
|
804
|
-
const
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
950
|
+
const cognitiveRoutingDelegationItem = findSettingsItem(items, "freeflow.cognitiveRouting.delegation");
|
|
951
|
+
const delegation = cognitiveRoutingDelegationItem ? effectiveItemValue(cognitiveRoutingDelegationItem) : "executor";
|
|
952
|
+
const cognitiveRoutingProfiles = ["coordinator", ...workersForDelegation(delegation)].map((profile) =>
|
|
953
|
+
findSettingsItem(items, `freeflow.cognitiveRouting.${profile}`),
|
|
954
|
+
);
|
|
808
955
|
if (cognitiveRoutingGroup && cognitiveRoutingEnabledItem) {
|
|
809
956
|
const enabled = effectiveItemValue(cognitiveRoutingEnabledItem) === true;
|
|
810
957
|
const profilesConfigured = cognitiveRoutingProfiles.every((item) =>
|
|
@@ -1079,7 +1226,7 @@ export async function handleFreeflowCommand(args, ctx, afterChange, pi, cognitiv
|
|
|
1079
1226
|
let reconcileCognitiveRouting = settingsScope === "session";
|
|
1080
1227
|
const items =
|
|
1081
1228
|
settingsScope === "session"
|
|
1082
|
-
? sessionFreeflowItems(state, cognitiveRoutingController)
|
|
1229
|
+
? sessionFreeflowItems(state, cognitiveRoutingController, ctx)
|
|
1083
1230
|
: freeflowItems(raw, {
|
|
1084
1231
|
scope: settingsScope,
|
|
1085
1232
|
layers,
|
|
@@ -1122,8 +1269,61 @@ export async function handleFreeflowCommand(args, ctx, afterChange, pi, cognitiv
|
|
|
1122
1269
|
return { changed: true, reloadRequired: false };
|
|
1123
1270
|
}
|
|
1124
1271
|
if (item.id === "freeflow.session.reset") {
|
|
1125
|
-
const
|
|
1126
|
-
|
|
1272
|
+
const routingResult = cognitiveRoutingController
|
|
1273
|
+
? await cognitiveRoutingController.resetSessionProfileOverrides("Reset session overrides")
|
|
1274
|
+
: { status: "unchanged" };
|
|
1275
|
+
if (routingResult.status === "blocked" || routingResult.reason) {
|
|
1276
|
+
ctx.ui.notify(
|
|
1277
|
+
`Cognitive Routing session presets could not be reset: ${routingResult.reason ?? routingResult.status}.`,
|
|
1278
|
+
"warning",
|
|
1279
|
+
);
|
|
1280
|
+
return { changed: false, reloadRequired: false };
|
|
1281
|
+
}
|
|
1282
|
+
try {
|
|
1283
|
+
const result = await resetSessionOverrides(ctx, pi);
|
|
1284
|
+
return {
|
|
1285
|
+
changed: result.changed || routingResult.status !== "unchanged",
|
|
1286
|
+
reloadRequired: result.reloadRequired,
|
|
1287
|
+
};
|
|
1288
|
+
} catch (error) {
|
|
1289
|
+
ctx.ui.notify(
|
|
1290
|
+
`Routing presets were reset, but core/context session overrides could not be reset: ${error instanceof Error ? error.message : String(error)}`,
|
|
1291
|
+
"warning",
|
|
1292
|
+
);
|
|
1293
|
+
return {
|
|
1294
|
+
changed: routingResult.status !== "unchanged",
|
|
1295
|
+
reloadRequired: false,
|
|
1296
|
+
};
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
if (item.configScope === "session" && isCognitiveRoutingProfileItem(item)) {
|
|
1300
|
+
if (!cognitiveRoutingController) {
|
|
1301
|
+
ctx.ui.notify("Cognitive Routing is unavailable for this session.", "warning");
|
|
1302
|
+
return { changed: false, reloadRequired: false };
|
|
1303
|
+
}
|
|
1304
|
+
const profile = item.id.split(".").at(-1);
|
|
1305
|
+
const override =
|
|
1306
|
+
value === undefined || value === LOCAL_INHERIT
|
|
1307
|
+
? null
|
|
1308
|
+
: {
|
|
1309
|
+
provider: value.provider,
|
|
1310
|
+
modelId: value.model,
|
|
1311
|
+
thinking: value.thinking,
|
|
1312
|
+
};
|
|
1313
|
+
const result = await cognitiveRoutingController.setSessionProfileOverride(
|
|
1314
|
+
profile,
|
|
1315
|
+
override,
|
|
1316
|
+
"Session profile settings",
|
|
1317
|
+
);
|
|
1318
|
+
if (result.status === "blocked" || result.reason) {
|
|
1319
|
+
ctx.ui.notify(
|
|
1320
|
+
`Cognitive Routing session preset could not be applied: ${result.reason ?? result.status}.`,
|
|
1321
|
+
"warning",
|
|
1322
|
+
);
|
|
1323
|
+
return { changed: false, reloadRequired: false };
|
|
1324
|
+
}
|
|
1325
|
+
await afterChange(false);
|
|
1326
|
+
return { changed: result.status !== "unchanged", reloadRequired: false };
|
|
1127
1327
|
}
|
|
1128
1328
|
if (item.configScope === "session") {
|
|
1129
1329
|
const keyById = {
|
|
@@ -1,6 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
DELEGATION_MODES,
|
|
3
|
+
EFFORTS,
|
|
4
|
+
PROFILES,
|
|
5
|
+
canonical,
|
|
6
|
+
isObject,
|
|
7
|
+
workersForDelegation,
|
|
8
|
+
type DelegationMode,
|
|
9
|
+
type Effort,
|
|
10
|
+
type Pair,
|
|
11
|
+
type Profile,
|
|
12
|
+
} from "./types.js";
|
|
2
13
|
export type CognitiveRoutingProfileName = Profile;
|
|
3
14
|
export type CognitiveRoutingThinkingLevel = Effort;
|
|
15
|
+
export type CognitiveRoutingDelegationMode = DelegationMode;
|
|
4
16
|
export interface CognitiveRoutingProfile {
|
|
5
17
|
provider: string;
|
|
6
18
|
model: string;
|
|
@@ -12,6 +24,8 @@ export interface CognitiveRoutingCapabilityState {
|
|
|
12
24
|
enabled: boolean;
|
|
13
25
|
effective: boolean;
|
|
14
26
|
enabledSource: string;
|
|
27
|
+
delegation: DelegationMode;
|
|
28
|
+
delegationSource: string;
|
|
15
29
|
projection: boolean;
|
|
16
30
|
projectionSource: string;
|
|
17
31
|
profiles: Partial<Record<Profile, CognitiveRoutingProfile>>;
|
|
@@ -27,24 +41,29 @@ export const pairFromProfile = (profile: CognitiveRoutingProfile): Pair => ({
|
|
|
27
41
|
});
|
|
28
42
|
function parseLayer(raw: any): any {
|
|
29
43
|
if (raw === undefined) return {};
|
|
30
|
-
if (
|
|
44
|
+
if (
|
|
45
|
+
!isObject(raw) ||
|
|
46
|
+
Object.keys(raw).some((key) => !["enabled", "delegation", "projection", "profiles"].includes(key))
|
|
47
|
+
)
|
|
31
48
|
throw new Error(
|
|
32
|
-
"Routing requires the
|
|
49
|
+
"Routing requires the enabled/delegation/projection/profiles schema; experimental legacy configuration is not migrated.",
|
|
33
50
|
);
|
|
34
51
|
for (const key of ["enabled", "projection"])
|
|
35
52
|
if (raw[key] !== undefined && typeof raw[key] !== "boolean") throw new Error(`${key} must be boolean.`);
|
|
53
|
+
if (raw.delegation !== undefined && !DELEGATION_MODES.includes(raw.delegation))
|
|
54
|
+
throw new Error("delegation must be executor, helper, or both.");
|
|
36
55
|
if (raw.profiles !== undefined) {
|
|
37
|
-
if (!isObject(raw.profiles) || Object.keys(raw.profiles).some((
|
|
38
|
-
throw new Error("Profiles must be coordinator
|
|
39
|
-
for (const
|
|
56
|
+
if (!isObject(raw.profiles) || Object.keys(raw.profiles).some((key) => !PROFILES.includes(key as Profile)))
|
|
57
|
+
throw new Error("Profiles must be coordinator, helper, or executor.");
|
|
58
|
+
for (const profile of Object.values(raw.profiles) as any[]) {
|
|
40
59
|
if (
|
|
41
|
-
!isObject(
|
|
42
|
-
Object.keys(
|
|
43
|
-
typeof
|
|
44
|
-
!
|
|
45
|
-
typeof
|
|
46
|
-
!
|
|
47
|
-
!EFFORTS.includes(
|
|
60
|
+
!isObject(profile) ||
|
|
61
|
+
Object.keys(profile).some((key) => !["provider", "model", "thinking"].includes(key)) ||
|
|
62
|
+
typeof profile.provider !== "string" ||
|
|
63
|
+
!profile.provider.trim() ||
|
|
64
|
+
typeof profile.model !== "string" ||
|
|
65
|
+
!profile.model.trim() ||
|
|
66
|
+
!EFFORTS.includes(profile.thinking)
|
|
48
67
|
)
|
|
49
68
|
throw new Error("Each profile requires provider, model, and supported thinking.");
|
|
50
69
|
}
|
|
@@ -62,6 +81,8 @@ export async function resolveCognitiveRoutingState(
|
|
|
62
81
|
enabled: false,
|
|
63
82
|
effective: false,
|
|
64
83
|
enabledSource: "default",
|
|
84
|
+
delegation: "executor",
|
|
85
|
+
delegationSource: "default",
|
|
65
86
|
projection: false,
|
|
66
87
|
projectionSource: "default",
|
|
67
88
|
profiles: {},
|
|
@@ -78,6 +99,10 @@ export async function resolveCognitiveRoutingState(
|
|
|
78
99
|
state.enabled = layer.enabled;
|
|
79
100
|
state.enabledSource = source;
|
|
80
101
|
}
|
|
102
|
+
if (layer.delegation !== undefined) {
|
|
103
|
+
state.delegation = layer.delegation;
|
|
104
|
+
state.delegationSource = source;
|
|
105
|
+
}
|
|
81
106
|
if (layer.projection !== undefined) {
|
|
82
107
|
state.projection = layer.projection;
|
|
83
108
|
state.projectionSource = source;
|
|
@@ -101,7 +126,8 @@ export async function resolveCognitiveRoutingState(
|
|
|
101
126
|
...state,
|
|
102
127
|
blockingReason: { code: "host_unsupported", message: "Required public Pi model registry is unavailable." },
|
|
103
128
|
};
|
|
104
|
-
|
|
129
|
+
const requiredProfiles: Profile[] = ["coordinator", ...workersForDelegation(state.delegation)];
|
|
130
|
+
for (const name of requiredProfiles) {
|
|
105
131
|
const profile = state.profiles[name];
|
|
106
132
|
if (!profile)
|
|
107
133
|
return { ...state, blockingReason: { code: "profile_missing", message: `Configure the ${name} profile.` } };
|
|
@@ -134,10 +160,14 @@ export async function resolveCognitiveRoutingState(
|
|
|
134
160
|
};
|
|
135
161
|
}
|
|
136
162
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
163
|
+
for (const worker of workersForDelegation(state.delegation))
|
|
164
|
+
if (canonical(state.profiles.coordinator) === canonical(state.profiles[worker]))
|
|
165
|
+
return {
|
|
166
|
+
...state,
|
|
167
|
+
blockingReason: {
|
|
168
|
+
code: "profiles_identical",
|
|
169
|
+
message: `Configure distinct Coordinator and ${worker} model/effort pairs.`,
|
|
170
|
+
},
|
|
171
|
+
};
|
|
142
172
|
return { ...state, effective: true, blockingReason: { code: "", message: "" } };
|
|
143
173
|
}
|