@hassangameryt/freeflow 0.7.1 → 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.
Files changed (147) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/.cursor-plugin/plugin.json +1 -1
  5. package/README.md +153 -128
  6. package/capabilities/cognitive-routing/SKILL.md +126 -274
  7. package/command-surface.json +7 -1
  8. package/gemini-extension.json +1 -1
  9. package/package.json +4 -4
  10. package/pi-extension/dist/cognitive-routing-v2/budget.js +42 -0
  11. package/pi-extension/dist/cognitive-routing-v2/config.js +136 -0
  12. package/pi-extension/dist/cognitive-routing-v2/projection.js +330 -0
  13. package/pi-extension/dist/cognitive-routing-v2/render.js +252 -0
  14. package/pi-extension/dist/cognitive-routing-v2/runtime.js +2181 -0
  15. package/pi-extension/dist/cognitive-routing-v2/schemas.js +173 -0
  16. package/pi-extension/dist/cognitive-routing-v2/state.js +828 -0
  17. package/pi-extension/dist/cognitive-routing-v2/tools.js +60 -0
  18. package/pi-extension/dist/cognitive-routing-v2/types.js +48 -0
  19. package/pi-extension/dist/context-virtualization/runtime.js +29 -7
  20. package/pi-extension/dist/freeflow-context/tool.js +12 -6
  21. package/pi-extension/dist/index.js +302 -626
  22. package/pi-extension/dist/provider-support/astra/adapter.js +110 -0
  23. package/pi-extension/dist/provider-support/astra/history.js +125 -0
  24. package/pi-extension/dist/provider-support/astra/index.js +11 -0
  25. package/pi-extension/dist/provider-support/astra/session-state.js +114 -0
  26. package/pi-extension/dist/provider-support/index.js +4 -0
  27. package/pi-extension/dist/runtime/request-history.js +216 -0
  28. package/pi-extension/dist/runtime/runtime-context.js +85 -16
  29. package/pi-extension/dist/session-sources/events.js +217 -0
  30. package/pi-extension/dist/session-sources/provenance.js +32 -0
  31. package/pi-extension/dist/session-sources/read-only-session.js +148 -0
  32. package/pi-extension/dist/session-sources/sources.js +277 -0
  33. package/pi-extension/dist/settings/settings-ui.js +274 -147
  34. package/pi-extension/src/cognitive-routing-v2/budget.ts +42 -0
  35. package/pi-extension/src/cognitive-routing-v2/config.ts +173 -0
  36. package/pi-extension/src/cognitive-routing-v2/projection.ts +375 -0
  37. package/pi-extension/src/cognitive-routing-v2/render.ts +277 -0
  38. package/pi-extension/src/cognitive-routing-v2/runtime.ts +2241 -0
  39. package/pi-extension/src/cognitive-routing-v2/schemas.ts +180 -0
  40. package/pi-extension/src/cognitive-routing-v2/state.ts +839 -0
  41. package/pi-extension/src/cognitive-routing-v2/tools.ts +62 -0
  42. package/pi-extension/src/cognitive-routing-v2/types.ts +254 -0
  43. package/pi-extension/src/context-virtualization/runtime.ts +30 -8
  44. package/pi-extension/src/freeflow-context/tool.ts +12 -6
  45. package/pi-extension/src/index.ts +308 -675
  46. package/pi-extension/src/provider-support/astra/adapter.ts +108 -0
  47. package/pi-extension/src/provider-support/astra/history.ts +145 -0
  48. package/pi-extension/src/provider-support/astra/index.ts +12 -0
  49. package/pi-extension/src/provider-support/astra/session-state.ts +113 -0
  50. package/pi-extension/src/provider-support/index.ts +5 -0
  51. package/pi-extension/src/runtime/request-history.ts +243 -0
  52. package/pi-extension/src/runtime/runtime-context.ts +100 -17
  53. package/pi-extension/src/session-sources/events.ts +230 -0
  54. package/pi-extension/src/session-sources/provenance.ts +39 -0
  55. package/pi-extension/src/session-sources/read-only-session.ts +170 -0
  56. package/pi-extension/src/session-sources/sources.ts +334 -0
  57. package/pi-extension/src/settings/settings-ui.ts +311 -158
  58. package/pi-extension/tests/cache-reuse/history.test.js +117 -0
  59. package/pi-extension/tests/cache-reuse/native.test.js +91 -0
  60. package/pi-extension/tests/cache-reuse/settings.test.js +211 -0
  61. package/pi-extension/tests/cognitive-routing-v2/communication.test.js +577 -0
  62. package/pi-extension/tests/cognitive-routing-v2/compaction-input.test.js +183 -0
  63. package/pi-extension/tests/cognitive-routing-v2/delegation.test.js +557 -0
  64. package/pi-extension/tests/cognitive-routing-v2/native.test.js +474 -0
  65. package/pi-extension/tests/cognitive-routing-v2/performance.test.js +87 -0
  66. package/pi-extension/tests/cognitive-routing-v2/persistence.test.js +300 -0
  67. package/pi-extension/tests/cognitive-routing-v2/projection.test.js +241 -0
  68. package/pi-extension/tests/cognitive-routing-v2/recovery.test.js +1034 -0
  69. package/pi-extension/tests/cognitive-routing-v2/render.test.js +244 -0
  70. package/pi-extension/tests/cognitive-routing-v2/runtime.test.js +423 -0
  71. package/pi-extension/tests/cognitive-routing-v2/stabilization.test.js +521 -0
  72. package/pi-extension/tests/cognitive-routing-v2/state.test.js +586 -0
  73. package/pi-extension/tests/context-virtualization/extension.test.js +33 -7
  74. package/pi-extension/tests/context-virtualization/runtime.test.js +17 -3
  75. package/pi-extension/tests/fixtures/routing-native.js +191 -0
  76. package/pi-extension/tests/integration/mode-free-core.test.js +6 -4
  77. package/pi-extension/tests/integration/pi-extension.test.js +655 -59
  78. package/pi-extension/tests/integration/prompt-architecture.test.js +127 -30
  79. package/pi-extension/tests/integration/unaffected-regressions.test.js +78 -11
  80. package/pi-extension/tests/provider-support/astra-boundaries.test.js +75 -0
  81. package/pi-extension/tests/provider-support/astra.test.js +306 -0
  82. package/pi-extension/tests/provider-support/cli.test.js +92 -0
  83. package/pi-extension/tests/provider-support/native.test.js +183 -0
  84. package/plugin.json +1 -1
  85. package/runtime/prompts/cognitive-routing.md +4 -5
  86. package/runtime/prompts/core.md +51 -45
  87. package/runtime/prompts/interaction-contract.md +5 -5
  88. package/skills/action-selection/SKILL.md +57 -97
  89. package/skills/design-for-depth/SKILL.md +58 -193
  90. package/skills/design-for-depth/references/implementation-decisions.md +76 -0
  91. package/skills/diagnose-failure/SKILL.md +64 -196
  92. package/skills/discuss/SKILL.md +64 -123
  93. package/skills/discuss/references/discussion-continuity.md +7 -3
  94. package/skills/execute-work/SKILL.md +69 -88
  95. package/skills/execute-work/references/execute-work-edges.md +9 -3
  96. package/skills/handoff/SKILL.md +44 -54
  97. package/skills/handoff/references/handoff-templates.md +15 -6
  98. package/skills/review-artifact/SKILL.md +80 -127
  99. package/skills/review-artifact/references/independent-artifact-reviewer-contract.md +1 -1
  100. package/skills/review-work/SKILL.md +65 -115
  101. package/skills/review-work/references/independent-work-reviewer-contract.md +1 -1
  102. package/skills/simplify-code/SKILL.md +47 -100
  103. package/skills/track-work/SKILL.md +128 -119
  104. package/skills/track-work/references/working-record-format.md +14 -2
  105. package/skills/verify-work/SKILL.md +58 -62
  106. package/skills/verify-work/references/integration-evidence.md +4 -2
  107. package/skills/verify-work/references/test-design.md +89 -0
  108. package/skills/workflow/SKILL.md +73 -119
  109. package/skills/write-plan/SKILL.md +46 -77
  110. package/skills/write-plan/references/plan-shapes.md +5 -5
  111. package/skills/write-spec/SKILL.md +51 -63
  112. package/skills/write-spec/references/artifact-standards.md +2 -2
  113. package/skills/write-spec/references/decision-records.md +3 -2
  114. package/skills/write-spec/references/spec-shapes.md +15 -0
  115. package/pi-extension/dist/cognitive-routing/commands.js +0 -96
  116. package/pi-extension/dist/cognitive-routing/config.js +0 -232
  117. package/pi-extension/dist/cognitive-routing/controller.js +0 -1292
  118. package/pi-extension/dist/cognitive-routing/history.js +0 -451
  119. package/pi-extension/dist/cognitive-routing/host.js +0 -188
  120. package/pi-extension/dist/cognitive-routing/pi-routing-session.js +0 -414
  121. package/pi-extension/dist/cognitive-routing/pi-session-control.js +0 -320
  122. package/pi-extension/dist/cognitive-routing/runtime.js +0 -43
  123. package/pi-extension/dist/cognitive-routing/tool.js +0 -212
  124. package/pi-extension/dist/cognitive-routing/types.js +0 -6
  125. package/pi-extension/src/cognitive-routing/commands.ts +0 -129
  126. package/pi-extension/src/cognitive-routing/config.ts +0 -303
  127. package/pi-extension/src/cognitive-routing/controller.ts +0 -1628
  128. package/pi-extension/src/cognitive-routing/history.ts +0 -650
  129. package/pi-extension/src/cognitive-routing/host.ts +0 -266
  130. package/pi-extension/src/cognitive-routing/pi-routing-session.ts +0 -538
  131. package/pi-extension/src/cognitive-routing/pi-session-control.ts +0 -444
  132. package/pi-extension/src/cognitive-routing/runtime.ts +0 -57
  133. package/pi-extension/src/cognitive-routing/tool.ts +0 -273
  134. package/pi-extension/src/cognitive-routing/types.ts +0 -89
  135. package/pi-extension/tests/cognitive-routing/commands.test.js +0 -134
  136. package/pi-extension/tests/cognitive-routing/controller.test.js +0 -1128
  137. package/pi-extension/tests/cognitive-routing/extension-lifecycle.test.js +0 -1142
  138. package/pi-extension/tests/cognitive-routing/history.test.js +0 -274
  139. package/pi-extension/tests/cognitive-routing/host-identity.test.js +0 -63
  140. package/pi-extension/tests/cognitive-routing/pi-normal-lifecycle.test.js +0 -434
  141. package/pi-extension/tests/cognitive-routing/pi-session-control.test.js +0 -174
  142. package/pi-extension/tests/cognitive-routing/pi-session-history.test.js +0 -91
  143. package/pi-extension/tests/cognitive-routing/profile-resolution.test.js +0 -279
  144. package/pi-extension/tests/cognitive-routing/tool.test.js +0 -230
  145. package/skills/tdd/SKILL.md +0 -143
  146. package/skills/tdd/references/test-design.md +0 -96
  147. /package/pi-extension/tests/{cognitive-routing/host-fixture.js → fixtures/pi-host.js} +0 -0
@@ -19,14 +19,15 @@ import {
19
19
  type SettingsWizard,
20
20
  type SettingsWizardStep,
21
21
  } from "./settings-tui.js";
22
- import { supportsPiSessionModelStateApi } from "../cognitive-routing/pi-session-control.js";
23
22
  import { isPiFlowHost } from "../runtime/runtime-identity.js";
24
23
  import type {
25
24
  CognitiveRoutingCapabilityState,
25
+ CognitiveRoutingDelegationMode,
26
26
  CognitiveRoutingProfile,
27
27
  CognitiveRoutingProfileName,
28
28
  CognitiveRoutingThinkingLevel,
29
- } from "../cognitive-routing/types.js";
29
+ } from "../cognitive-routing-v2/config.js";
30
+ import { isWorkerProfile, workersForDelegation } from "../cognitive-routing-v2/types.js";
30
31
 
31
32
  const DEFAULT_FREEFLOW_ENABLED = true;
32
33
  const DEFAULT_CONTEXT_VIRTUALIZATION_ENABLED = false;
@@ -68,10 +69,26 @@ type SettingsItem = {
68
69
  transient?: boolean;
69
70
  };
70
71
 
72
+ type SessionRoutingPair = {
73
+ provider: string;
74
+ modelId: string;
75
+ thinking: CognitiveRoutingThinkingLevel;
76
+ };
77
+
71
78
  type CognitiveRoutingSettingsController = {
72
79
  state(): { effective: boolean; controlMode: string; activeProfile?: string };
73
- setManualProfile(profile: "standard" | "reasoning", mechanism?: string): Promise<{ status: string; reason?: string }>;
80
+ sessionProfileOverrides(): Partial<Record<CognitiveRoutingProfileName, SessionRoutingPair>>;
81
+ setManualProfile(
82
+ profile: CognitiveRoutingProfileName,
83
+ mechanism?: string,
84
+ ): Promise<{ status: string; reason?: string }>;
74
85
  setAutomaticControl(mechanism?: string): Promise<{ status: string; reason?: string }>;
86
+ setSessionProfileOverride(
87
+ profile: CognitiveRoutingProfileName,
88
+ override: SessionRoutingPair | null,
89
+ mechanism?: string,
90
+ ): Promise<{ status: string; reason?: string }>;
91
+ resetSessionProfileOverrides(mechanism?: string): Promise<{ status: string; reason?: string }>;
75
92
  };
76
93
 
77
94
  type AfterChangeOptions = {
@@ -184,7 +201,7 @@ function effectiveItemValue(item: SettingsItem): SettingsValue | undefined {
184
201
  function formatCoreValue(value: unknown): string {
185
202
  if (typeof value === "boolean") return booleanValue(value);
186
203
  if (isRecord(value) && typeof value.provider === "string" && typeof value.model === "string") {
187
- const effort = typeof value.thinkingLevel === "string" ? ` · ${value.thinkingLevel}` : "";
204
+ const effort = typeof value.thinking === "string" ? ` · ${value.thinking}` : "";
188
205
  return `${value.provider}/${value.model}${effort}`;
189
206
  }
190
207
  return String(value ?? "");
@@ -291,6 +308,79 @@ function createScopedBooleanItem(options: ScopedBooleanItemOptions): SettingsIte
291
308
  return item;
292
309
  }
293
310
 
311
+ function createScopedDelegationItem(options: {
312
+ scope: Exclude<ConfigScope, "session">;
313
+ rawConfig: Record<string, unknown>;
314
+ localConfig: Record<string, unknown>;
315
+ effectiveValue: CognitiveRoutingDelegationMode;
316
+ effectiveSource: ConfigSource;
317
+ }): SettingsItem {
318
+ const path = ["cognitiveRouting", "delegation"];
319
+ const repositoryValue = getPath(options.rawConfig, path);
320
+ const inheritedValue: CognitiveRoutingDelegationMode = ["executor", "helper", "both"].includes(
321
+ repositoryValue as string,
322
+ )
323
+ ? (repositoryValue as CognitiveRoutingDelegationMode)
324
+ : "executor";
325
+ const inheritedSource: ConfigSource = repositoryValue === undefined ? "builtin" : "repository";
326
+ const localValue = getPath(options.localConfig, path);
327
+ const descriptions = {
328
+ executor: "Coordinator delegates assignments to Executor.",
329
+ helper: "Coordinator delegates supporting assignments to Helper and keeps production implementation.",
330
+ both: "Coordinator chooses Helper or Executor for each assignment.",
331
+ };
332
+ const item: SettingsItem =
333
+ options.scope === "local"
334
+ ? {
335
+ id: "freeflow.cognitiveRouting.delegation",
336
+ label: "Delegation method",
337
+ description:
338
+ "Choose enabled workers for automatic routing. Inherit uses the repository value; this setting does not rank model presets.",
339
+ path,
340
+ kind: "enum",
341
+ value: ["executor", "helper", "both"].includes(localValue as string) ? localValue : LOCAL_INHERIT,
342
+ values: [LOCAL_INHERIT, "executor", "helper", "both"],
343
+ valueLabels: {
344
+ inherit: "Inherit repository",
345
+ executor: "Executor only",
346
+ helper: "Helper only",
347
+ both: "Helper and Executor",
348
+ },
349
+ valueDescriptions: {
350
+ inherit: `Use ${inheritedValue} from ${inheritedSource}.`,
351
+ ...descriptions,
352
+ },
353
+ configScope: "local",
354
+ configValues: { inherit: undefined, executor: "executor", helper: "helper", both: "both" },
355
+ effectiveValue: options.effectiveValue,
356
+ effectiveSource: options.effectiveSource,
357
+ inheritedValue,
358
+ inheritedSource,
359
+ }
360
+ : {
361
+ id: "freeflow.cognitiveRouting.delegation",
362
+ label: "Delegation method",
363
+ description: "Choose enabled workers for automatic routing. This edits shared .freeflow/config.json.",
364
+ path,
365
+ kind: "enum",
366
+ value: inheritedValue,
367
+ values: ["executor", "helper", "both"],
368
+ valueLabels: {
369
+ executor: "Executor only",
370
+ helper: "Helper only",
371
+ both: "Helper and Executor",
372
+ },
373
+ valueDescriptions: descriptions,
374
+ defaultValue: "executor",
375
+ configScope: "repository",
376
+ effectiveValue: options.effectiveValue,
377
+ effectiveSource: options.effectiveSource,
378
+ localOverrideValue: ["executor", "helper", "both"].includes(localValue as string) ? localValue : undefined,
379
+ };
380
+ item.displaySuffix = coreDisplaySuffix(item);
381
+ return item;
382
+ }
383
+
294
384
  function resolveSettingsCoreView(
295
385
  rawConfig: Record<string, unknown>,
296
386
  layers?: Awaited<ReturnType<typeof readFreeflowConfigLayers>>,
@@ -369,6 +459,7 @@ function createSessionBooleanItem(options: {
369
459
  function sessionFreeflowItems(
370
460
  state: Awaited<ReturnType<typeof readCapabilityState>>,
371
461
  cognitiveRoutingController?: CognitiveRoutingSettingsController,
462
+ ctx?: any,
372
463
  ): SettingsItem[] {
373
464
  const sessionOverrides = state.sessionOverrides as Record<string, boolean>;
374
465
  const configured = state.configuredCoreConfig;
@@ -422,48 +513,79 @@ function sessionFreeflowItems(
422
513
  conversationHistoryItem.inactive = freeflowInactive;
423
514
 
424
515
  const cognitiveRoutingState = cognitiveRoutingController?.state();
516
+ const delegation = state.cognitiveRouting?.delegation ?? "executor";
517
+ const heldProfile = cognitiveRoutingState?.controlMode.startsWith("manual-")
518
+ ? cognitiveRoutingState.activeProfile
519
+ : undefined;
520
+ const availableProfiles: CognitiveRoutingProfileName[] = ["coordinator", ...workersForDelegation(delegation)];
521
+ if (heldProfile === "coordinator" || isWorkerProfile(heldProfile)) availableProfiles.push(heldProfile);
522
+ const manualProfiles = [...new Set(availableProfiles)];
425
523
  const cognitiveRoutingProfile =
426
- cognitiveRoutingState?.controlMode === "manual-standard"
427
- ? "standard"
428
- : cognitiveRoutingState?.controlMode === "manual-reasoning"
429
- ? "reasoning"
430
- : "auto";
524
+ heldProfile && manualProfiles.includes(heldProfile as CognitiveRoutingProfileName) ? heldProfile : "auto";
431
525
  const cognitiveRoutingItem: SettingsItem | undefined = state.cognitiveRouting
432
526
  ? {
433
527
  id: "freeflow.cognitiveRouting.profile",
434
528
  label: "Cognitive Routing",
435
- description: "Hold a profile manually or release the hold for automatic model control.",
529
+ description: "Hold an enabled profile manually or release the hold for automatic model control.",
436
530
  kind: "enum",
437
531
  value: cognitiveRoutingProfile,
438
- values: ["auto", "standard", "reasoning"],
439
- valueLabels: {
440
- auto: "automatic",
441
- standard: "manual · standard",
442
- reasoning: "manual · reasoning",
443
- },
444
- valueDescriptions: {
445
- auto: "Release the manual hold without forcing a model transition.",
446
- standard: "Hold the standard profile until /freeflow profile auto.",
447
- reasoning: "Hold the reasoning profile until /freeflow profile auto.",
448
- },
532
+ values: ["auto", ...manualProfiles],
533
+ valueLabels: Object.fromEntries([
534
+ ["auto", "automatic"],
535
+ ...manualProfiles.map((profile) => [profile, `manual · ${profile}`]),
536
+ ]),
537
+ valueDescriptions: Object.fromEntries([
538
+ ["auto", "Release the manual hold and return to Coordinator reconciliation."],
539
+ ...manualProfiles.map((profile) => [profile, `Hold the ${profile} profile until /freeflow profile auto.`]),
540
+ ]),
449
541
  inactive: freeflowInactive || !state.cognitiveRouting.effective || cognitiveRoutingController === undefined,
450
542
  runtimeInactive: state.cognitiveRouting.blockingReason?.code === "runtime_disabled",
451
543
  displaySuffix: cognitiveRoutingState?.effective ? cognitiveRoutingProfile : "unavailable",
452
544
  }
453
545
  : undefined;
454
546
 
547
+ const sessionProfiles = cognitiveRoutingController?.sessionProfileOverrides();
548
+ const profileItems =
549
+ state.cognitiveRouting && cognitiveRoutingController
550
+ ? manualProfiles.map((name) =>
551
+ cognitiveRoutingProfileItem({
552
+ name,
553
+ scope: "session",
554
+ rawConfig: {},
555
+ localConfig: {},
556
+ capabilityState: state.cognitiveRouting as CognitiveRoutingCapabilityState,
557
+ ctx,
558
+ sessionProfiles,
559
+ }),
560
+ )
561
+ : [];
562
+ const cognitiveRoutingPresets: SettingsItem | undefined = profileItems.length
563
+ ? {
564
+ id: "freeflow.cognitiveRouting.presets",
565
+ label: "Cognitive Routing presets",
566
+ description: "Temporarily choose complete model/effort pairs for profiles enabled in this Pi session.",
567
+ kind: "group",
568
+ value: profileItems.some((item) => item.value !== LOCAL_INHERIT && item.value !== undefined),
569
+ displaySuffix: `${profileItems.filter((item) => item.value !== LOCAL_INHERIT && item.value !== undefined).length}/${profileItems.length} session overrides`,
570
+ inactive: freeflowInactive || !state.cognitiveRouting?.effective || cognitiveRoutingController === undefined,
571
+ children: profileItems,
572
+ }
573
+ : undefined;
574
+
455
575
  return [
456
576
  freeflowItem,
457
577
  ...(cognitiveRoutingItem ? [cognitiveRoutingItem] : []),
578
+ ...(cognitiveRoutingPresets ? [cognitiveRoutingPresets] : []),
458
579
  {
459
580
  id: "freeflow.session.reset",
460
581
  label: "Reset session overrides",
461
- description: "Clear Freeflow, Context Virtualization, and Conversation History overrides for this Pi session.",
582
+ description:
583
+ "Clear Freeflow, Context Virtualization, Conversation History, and routing profile overrides for this Pi session.",
462
584
  kind: "enum",
463
585
  value: "available",
464
586
  values: ["reset"],
465
587
  valueLabels: { reset: "Reset all session overrides" },
466
- valueDescriptions: { reset: "Return every session setting to its configured value." },
588
+ valueDescriptions: { reset: "Return every session setting and routing preset to its configured value." },
467
589
  format: () => "available",
468
590
  transient: true,
469
591
  },
@@ -517,8 +639,8 @@ function isCognitiveRoutingProfile(value: unknown): value is CognitiveRoutingPro
517
639
  isRecord(value) &&
518
640
  typeof value.provider === "string" &&
519
641
  typeof value.model === "string" &&
520
- typeof value.thinkingLevel === "string" &&
521
- COGNITIVE_ROUTING_THINKING_LEVELS.includes(value.thinkingLevel as CognitiveRoutingThinkingLevel)
642
+ typeof value.thinking === "string" &&
643
+ COGNITIVE_ROUTING_THINKING_LEVELS.includes(value.thinking as CognitiveRoutingThinkingLevel)
522
644
  );
523
645
  }
524
646
 
@@ -572,6 +694,7 @@ function cognitiveRoutingProfileDisplay(value: unknown): string {
572
694
  }
573
695
 
574
696
  function cognitiveRoutingSettingsSource(source: unknown): ConfigSource {
697
+ if (source === "session") return "session";
575
698
  if (source === "repository") return "repository";
576
699
  if (source === "personal" || source === "local") return "local";
577
700
  return "builtin";
@@ -579,6 +702,7 @@ function cognitiveRoutingSettingsSource(source: unknown): ConfigSource {
579
702
 
580
703
  function cognitiveRoutingProfileDisplaySuffix(source: unknown, scope: ConfigScope): string | undefined {
581
704
  if (!source) return undefined;
705
+ if (source === "session") return "(session override)";
582
706
  if (source === "personal" && scope === "repository") return "(effective personal)";
583
707
  return `(${source})`;
584
708
  }
@@ -603,6 +727,11 @@ function createCognitiveRoutingProfileWizard(
603
727
  currentValue: unknown,
604
728
  models: CognitiveRoutingModelOption[],
605
729
  allowInherit: boolean,
730
+ inheritChoice: { label: string; description: string; summary: string } = {
731
+ label: "Inherit repository preset",
732
+ description: "Remove the personal override without changing the repository preset.",
733
+ summary: "inherit repository preset",
734
+ },
606
735
  ): SettingsWizard {
607
736
  const currentProfile = isCognitiveRoutingProfile(currentValue) ? currentValue : undefined;
608
737
  const currentModelKey = currentProfile
@@ -614,8 +743,8 @@ function createCognitiveRoutingProfileWizard(
614
743
  {
615
744
  key: LOCAL_INHERIT,
616
745
  value: LOCAL_INHERIT,
617
- label: "Inherit repository preset",
618
- description: "Remove the personal override without changing the repository preset.",
746
+ label: inheritChoice.label,
747
+ description: inheritChoice.description,
619
748
  },
620
749
  ]
621
750
  : []),
@@ -650,12 +779,11 @@ function createCognitiveRoutingProfileWizard(
650
779
  if (selectedValues.length === 1) {
651
780
  const selected = selectedValues[0];
652
781
  if (selected === LOCAL_INHERIT) {
653
- return cognitiveRoutingConfirmStep("inherit repository preset");
782
+ return cognitiveRoutingConfirmStep(inheritChoice.summary);
654
783
  }
655
784
  const model = selected as CognitiveRoutingModelOption;
656
785
  if (!model?.key || model.thinkingLevels.length === 0) return undefined;
657
- const currentEffort =
658
- currentProfile && currentModelKey === model.key ? currentProfile.thinkingLevel : undefined;
786
+ const currentEffort = currentProfile && currentModelKey === model.key ? currentProfile.thinking : undefined;
659
787
  return {
660
788
  title: `Choose effort · ${model.label}`,
661
789
  choices: model.thinkingLevels.map((level) => ({
@@ -681,7 +809,7 @@ function createCognitiveRoutingProfileWizard(
681
809
  return {
682
810
  provider: model.provider,
683
811
  model: model.model,
684
- thinkingLevel: selectedValues[1] as CognitiveRoutingThinkingLevel,
812
+ thinking: selectedValues[1] as CognitiveRoutingThinkingLevel,
685
813
  } satisfies CognitiveRoutingProfile;
686
814
  },
687
815
  };
@@ -694,133 +822,75 @@ function cognitiveRoutingProfileItem(options: {
694
822
  localConfig: Record<string, unknown>;
695
823
  capabilityState: CognitiveRoutingCapabilityState | undefined;
696
824
  ctx: any;
825
+ sessionProfiles?: Partial<Record<CognitiveRoutingProfileName, SessionRoutingPair>>;
697
826
  }): SettingsItem {
698
827
  const path = ["cognitiveRouting", "profiles", options.name];
699
828
  const repositoryProfile = getPath(options.rawConfig, path);
700
829
  const localProfile = getPath(options.localConfig, path);
701
- const effectiveProfile = options.capabilityState?.profiles?.[options.name] ?? repositoryProfile;
830
+ const configuredProfile = options.capabilityState?.profiles?.[options.name] ?? repositoryProfile;
831
+ const sessionPair = options.sessionProfiles?.[options.name];
832
+ const sessionProfile = sessionPair
833
+ ? { provider: sessionPair.provider, model: sessionPair.modelId, thinking: sessionPair.thinking }
834
+ : undefined;
835
+ const effectiveProfile = sessionProfile ?? configuredProfile;
702
836
  const localValue = isCognitiveRoutingProfile(localProfile) ? localProfile : LOCAL_INHERIT;
703
- const value = options.scope === "local" ? localValue : repositoryProfile;
704
- const source =
837
+ const value =
838
+ options.scope === "session"
839
+ ? (sessionProfile ?? LOCAL_INHERIT)
840
+ : options.scope === "local"
841
+ ? localValue
842
+ : repositoryProfile;
843
+ const configuredSource =
705
844
  options.capabilityState?.profileSources?.[options.name] ??
706
845
  (isCognitiveRoutingProfile(repositoryProfile) ? "repository" : undefined);
846
+ const source = options.scope === "session" ? (sessionProfile ? "session" : configuredSource) : configuredSource;
707
847
  const models = cognitiveRoutingModelOptions(options.ctx);
708
- const inactive = options.scope === "local" && models.length === 0 && localValue !== LOCAL_INHERIT;
848
+ const inactive = options.scope !== "repository" && models.length === 0 && value !== LOCAL_INHERIT;
849
+ const inheritChoice =
850
+ options.scope === "session"
851
+ ? {
852
+ label: "Inherit configured preset",
853
+ description: "Remove the session override without changing local or repository configuration.",
854
+ summary: "inherit configured preset",
855
+ }
856
+ : undefined;
709
857
 
710
858
  return {
711
859
  id: `freeflow.cognitiveRouting.${options.name}`,
712
860
  label: `${options.name.charAt(0).toUpperCase()}${options.name.slice(1)} preset`,
713
861
  description:
714
- "Choose an authenticated available model and one effort supported by that model. Confirming writes the complete preset; cancel leaves it unchanged.",
862
+ options.scope === "session"
863
+ ? "Temporarily choose the model and effort for this profile in the current Pi session only."
864
+ : "Choose an authenticated available model and one effort supported by that model. Confirming writes the complete preset; cancel leaves it unchanged.",
715
865
  path,
716
866
  kind: "string",
717
867
  value,
718
- format: cognitiveRoutingProfileDisplay,
868
+ format: (current) => {
869
+ const inherits = current === LOCAL_INHERIT || (options.scope === "session" && current === undefined);
870
+ return inherits
871
+ ? options.scope === "session"
872
+ ? "inherit configured preset"
873
+ : "inherit repository preset"
874
+ : cognitiveRoutingProfileDisplay(current);
875
+ },
719
876
  configScope: options.scope,
720
877
  effectiveValue: effectiveProfile,
721
878
  effectiveSource: cognitiveRoutingSettingsSource(source),
722
- inheritedValue: repositoryProfile,
723
- inheritedSource: "repository",
879
+ inheritedValue: options.scope === "session" ? configuredProfile : repositoryProfile,
880
+ inheritedSource: cognitiveRoutingSettingsSource(configuredSource ?? "builtin"),
724
881
  inactive,
725
882
  displaySuffix: cognitiveRoutingProfileDisplaySuffix(source, options.scope),
726
- wizard: () => createCognitiveRoutingProfileWizard(value, models, options.scope === "local"),
883
+ wizard: () => createCognitiveRoutingProfileWizard(value, models, options.scope !== "repository", inheritChoice),
727
884
  };
728
885
  }
729
886
 
730
- type CognitiveRoutingSessionStartSetting = "control" | "profile";
731
-
732
- function cognitiveRoutingSessionStartItem(options: {
733
- setting: CognitiveRoutingSessionStartSetting;
734
- scope: ConfigScope;
735
- rawConfig: Record<string, unknown>;
736
- localConfig: Record<string, unknown>;
737
- capabilityState: CognitiveRoutingCapabilityState | undefined;
738
- }): SettingsItem {
739
- const path = ["cognitiveRouting", "sessionStart", options.setting];
740
- const defaultValue = options.setting === "control" ? "automatic" : "reasoning";
741
- const values = options.setting === "control" ? ["automatic", "manual"] : ["standard", "reasoning"];
742
- const labels = Object.fromEntries(values.map((value) => [value, value]));
743
- const descriptions =
744
- options.setting === "control"
745
- ? {
746
- automatic: "Start the new session in Reasoning; automatic delegation may use Standard during execution.",
747
- manual: "Hold the selected profile for the new session until the user changes it.",
748
- }
749
- : {
750
- standard: "Start a new manual session with the configured Standard preset.",
751
- reasoning: "Start a new manual session with the configured Reasoning preset.",
752
- };
753
- const repositoryValue = getPath(options.rawConfig, path);
754
- const localValue = getPath(options.localConfig, path);
755
- const isValidValue = (value: unknown): value is string => values.includes(value as string);
756
- const repositorySetting = isValidValue(repositoryValue) ? repositoryValue : undefined;
757
- const localSetting = isValidValue(localValue) ? localValue : undefined;
758
- const inheritedValue = repositorySetting ?? defaultValue;
759
- const inheritedSource: ConfigSource = repositorySetting ? "repository" : "builtin";
760
- const configuredSource =
761
- options.capabilityState?.sessionStartSources?.[options.setting] ?? (repositorySetting ? "repository" : undefined);
762
- const effectiveSessionStartControl =
763
- options.capabilityState?.sessionStart?.control ??
764
- getPath(options.localConfig, ["cognitiveRouting", "sessionStart", "control"]) ??
765
- getPath(options.rawConfig, ["cognitiveRouting", "sessionStart", "control"]) ??
766
- "automatic";
767
- const effectiveValue =
768
- options.setting === "profile" && effectiveSessionStartControl === "automatic"
769
- ? "reasoning"
770
- : (options.capabilityState?.sessionStart?.[options.setting] ?? inheritedValue);
771
- const effectiveSource = cognitiveRoutingSettingsSource(configuredSource);
772
- const label = options.setting === "control" ? "Session start control" : "Manual session-start profile";
773
- const description =
774
- options.setting === "control"
775
- ? "Choose automatic or manual control for new sessions only; changing this does not alter the active session."
776
- : "Choose the profile for manual session starts; automatic sessions always start in Reasoning.";
777
-
778
- const item: SettingsItem =
779
- options.scope === "local"
780
- ? {
781
- id: `freeflow.cognitiveRouting.sessionStart.${options.setting}`,
782
- label,
783
- description: `${description} Choose inherit to use the repository value; use /freeflow settings repo to edit shared defaults.`,
784
- path,
785
- kind: "enum",
786
- value: localSetting ?? LOCAL_INHERIT,
787
- values: [LOCAL_INHERIT, ...values],
788
- valueLabels: { inherit: "Inherit repository", ...labels },
789
- valueDescriptions: {
790
- inherit: `Use ${inheritedValue} from ${inheritedSource}.`,
791
- ...descriptions,
792
- },
793
- format: (value) => String(value),
794
- configScope: "local",
795
- configValues: { inherit: undefined },
796
- effectiveValue,
797
- effectiveSource,
798
- inheritedValue,
799
- inheritedSource,
800
- }
801
- : {
802
- id: `freeflow.cognitiveRouting.sessionStart.${options.setting}`,
803
- label,
804
- description: `${description} This edits shared .freeflow/config.json.`,
805
- path,
806
- kind: "enum",
807
- value: inheritedValue,
808
- values,
809
- valueLabels: labels,
810
- valueDescriptions: descriptions,
811
- format: (value) => String(value),
812
- defaultValue,
813
- configScope: "repository",
814
- effectiveValue,
815
- effectiveSource,
816
- localOverrideValue: localSetting,
817
- };
818
- item.displaySuffix = coreDisplaySuffix(item);
819
- return item;
820
- }
821
-
822
887
  function isCognitiveRoutingRuntimeAvailable(pi: any): boolean {
823
- return isPiFlowHost(pi?.host) || supportsPiSessionModelStateApi(pi);
888
+ return (
889
+ !isPiFlowHost(pi?.host) &&
890
+ typeof pi?.appendEntry === "function" &&
891
+ typeof pi?.setModel === "function" &&
892
+ typeof pi?.setThinkingLevel === "function"
893
+ );
824
894
  }
825
895
 
826
896
  function freeflowItems(
@@ -833,7 +903,7 @@ function freeflowItems(
833
903
  runtimeAvailable?: boolean;
834
904
  } = {},
835
905
  ): SettingsItem[] {
836
- const scope = options.scope ?? "repository";
906
+ const scope: Exclude<ConfigScope, "session"> = options.scope === "local" ? "local" : "repository";
837
907
  const layers = options.layers;
838
908
  const { localConfig, core, sources } = resolveSettingsCoreView(rawConfig, layers);
839
909
 
@@ -889,7 +959,7 @@ function freeflowItems(
889
959
  label: "Enabled",
890
960
  description: cognitiveRoutingRuntimeDisabled
891
961
  ? "Requires a host model-state control API; configuration is read-only on this host."
892
- : "Allow Cognitive Routing to own the session model lease and switch the configured profiles.",
962
+ : "Allow Cognitive Routing to manage explicit assignments and request native profile transitions.",
893
963
  path: ["cognitiveRouting", "enabled"],
894
964
  effectiveValue: cognitiveRoutingState?.enabled ?? false,
895
965
  effectiveSource: cognitiveRoutingSettingsSource(cognitiveRoutingState?.enabledSource),
@@ -898,7 +968,33 @@ function freeflowItems(
898
968
  cognitiveRoutingEnabledItem.inactive = freeflowInactive || cognitiveRoutingRuntimeDisabled;
899
969
  cognitiveRoutingEnabledItem.runtimeInactive = cognitiveRoutingRuntimeDisabled;
900
970
 
901
- const cognitiveRoutingProfiles = ["standard", "reasoning"].map((name) =>
971
+ const cognitiveRoutingDelegationItem = createScopedDelegationItem({
972
+ scope,
973
+ rawConfig,
974
+ localConfig,
975
+ effectiveValue: cognitiveRoutingState?.delegation ?? "executor",
976
+ effectiveSource: cognitiveRoutingSettingsSource(cognitiveRoutingState?.delegationSource),
977
+ });
978
+ cognitiveRoutingDelegationItem.inactive = freeflowInactive || cognitiveRoutingRuntimeDisabled;
979
+ cognitiveRoutingDelegationItem.runtimeInactive = cognitiveRoutingRuntimeDisabled;
980
+
981
+ const cognitiveRoutingProjectionItem = createScopedBooleanItem({
982
+ scope,
983
+ rawConfig,
984
+ localConfig,
985
+ id: "freeflow.cognitiveRouting.projection",
986
+ label: "Context projection",
987
+ description:
988
+ "Select worker evidence for Coordinator; disable projection to keep ordinary Pi context in all enabled profiles.",
989
+ path: ["cognitiveRouting", "projection"],
990
+ effectiveValue: cognitiveRoutingState?.projection ?? false,
991
+ effectiveSource: cognitiveRoutingSettingsSource(cognitiveRoutingState?.projectionSource),
992
+ defaultValue: false,
993
+ });
994
+ cognitiveRoutingProjectionItem.inactive = freeflowInactive || cognitiveRoutingRuntimeDisabled;
995
+ cognitiveRoutingProjectionItem.runtimeInactive = cognitiveRoutingRuntimeDisabled;
996
+
997
+ const cognitiveRoutingProfiles = ["coordinator", "helper", "executor"].map((name) =>
902
998
  cognitiveRoutingProfileItem({
903
999
  name: name as CognitiveRoutingProfileName,
904
1000
  scope,
@@ -908,17 +1004,7 @@ function freeflowItems(
908
1004
  ctx: options.ctx,
909
1005
  }),
910
1006
  );
911
- const cognitiveRoutingSessionStart = (["control", "profile"] as CognitiveRoutingSessionStartSetting[]).map(
912
- (setting) =>
913
- cognitiveRoutingSessionStartItem({
914
- setting,
915
- scope,
916
- rawConfig,
917
- localConfig,
918
- capabilityState: cognitiveRoutingState,
919
- }),
920
- );
921
- for (const item of [...cognitiveRoutingProfiles, ...cognitiveRoutingSessionStart]) {
1007
+ for (const item of cognitiveRoutingProfiles) {
922
1008
  item.inactive ||= freeflowInactive || cognitiveRoutingRuntimeDisabled;
923
1009
  item.runtimeInactive = cognitiveRoutingRuntimeDisabled;
924
1010
  }
@@ -936,12 +1022,17 @@ function freeflowItems(
936
1022
  label: "Cognitive Routing",
937
1023
  description: cognitiveRoutingRuntimeDisabled
938
1024
  ? "Cognitive Routing configuration is visible for inspection but requires a host model-state control API."
939
- : "Configure the Standard and Reasoning profiles and choose whether Freeflow may manage model state for this repository.",
1025
+ : "Configure Coordinator, Helper, and Executor profiles plus the workers available for delegation.",
940
1026
  kind: "group" as const,
941
1027
  value: cognitiveRoutingRuntimeDisabled ? false : (cognitiveRoutingState?.enabled ?? false),
942
1028
  inactive: freeflowInactive,
943
1029
  displaySuffix: cognitiveRoutingStatus,
944
- children: [cognitiveRoutingEnabledItem, ...cognitiveRoutingProfiles, ...cognitiveRoutingSessionStart],
1030
+ children: [
1031
+ cognitiveRoutingEnabledItem,
1032
+ cognitiveRoutingDelegationItem,
1033
+ ...cognitiveRoutingProfiles,
1034
+ cognitiveRoutingProjectionItem,
1035
+ ],
945
1036
  } satisfies SettingsItem;
946
1037
  })();
947
1038
 
@@ -965,6 +1056,7 @@ function pruneKnownDefaults(config: Record<string, unknown>) {
965
1056
  { path: ["enabled"], value: DEFAULT_FREEFLOW_ENABLED },
966
1057
  { path: ["contextVirtualization"], value: DEFAULT_CONTEXT_VIRTUALIZATION_ENABLED },
967
1058
  { path: ["conversationHistory"], value: DEFAULT_CONVERSATION_HISTORY_ENABLED },
1059
+ { path: ["cognitiveRouting", "delegation"], value: "executor" },
968
1060
  ];
969
1061
 
970
1062
  for (const item of defaultPaths) {
@@ -1051,7 +1143,7 @@ async function updateConfig(
1051
1143
  }
1052
1144
 
1053
1145
  function isCognitiveRoutingProfileItem(item: SettingsItem): boolean {
1054
- return item.id === "freeflow.cognitiveRouting.standard" || item.id === "freeflow.cognitiveRouting.reasoning";
1146
+ return ["coordinator", "helper", "executor"].some((profile) => item.id === `freeflow.cognitiveRouting.${profile}`);
1055
1147
  }
1056
1148
 
1057
1149
  function valueForDisplay(item: SettingsItem): string {
@@ -1095,10 +1187,13 @@ function refreshSettingsDerivedState(items: SettingsItem[]) {
1095
1187
  const freeflowInactive = freeflowItem ? effectiveItemValue(freeflowItem) !== true : false;
1096
1188
  const cognitiveRoutingGroup = findSettingsItem(items, "freeflow.cognitiveRouting");
1097
1189
  const cognitiveRoutingEnabledItem = findSettingsItem(items, "freeflow.cognitiveRouting.enabled");
1098
- const cognitiveRoutingProfiles = [
1099
- findSettingsItem(items, "freeflow.cognitiveRouting.standard"),
1100
- findSettingsItem(items, "freeflow.cognitiveRouting.reasoning"),
1101
- ];
1190
+ const cognitiveRoutingDelegationItem = findSettingsItem(items, "freeflow.cognitiveRouting.delegation");
1191
+ const delegation = (
1192
+ cognitiveRoutingDelegationItem ? effectiveItemValue(cognitiveRoutingDelegationItem) : "executor"
1193
+ ) as CognitiveRoutingDelegationMode;
1194
+ const cognitiveRoutingProfiles = ["coordinator", ...workersForDelegation(delegation)].map((profile) =>
1195
+ findSettingsItem(items, `freeflow.cognitiveRouting.${profile}`),
1196
+ );
1102
1197
 
1103
1198
  if (cognitiveRoutingGroup && cognitiveRoutingEnabledItem) {
1104
1199
  const enabled = effectiveItemValue(cognitiveRoutingEnabledItem) === true;
@@ -1416,7 +1511,7 @@ export async function handleFreeflowCommand(
1416
1511
  let reconcileCognitiveRouting = settingsScope === "session";
1417
1512
  const items =
1418
1513
  settingsScope === "session"
1419
- ? sessionFreeflowItems(state, cognitiveRoutingController)
1514
+ ? sessionFreeflowItems(state, cognitiveRoutingController, ctx)
1420
1515
  : freeflowItems(raw, {
1421
1516
  scope: settingsScope,
1422
1517
  layers,
@@ -1447,7 +1542,10 @@ export async function handleFreeflowCommand(
1447
1542
  const result =
1448
1543
  value === "auto"
1449
1544
  ? await cognitiveRoutingController.setAutomaticControl("profile-settings")
1450
- : await cognitiveRoutingController.setManualProfile(value as "standard" | "reasoning", "profile-settings");
1545
+ : await cognitiveRoutingController.setManualProfile(
1546
+ value as CognitiveRoutingProfileName,
1547
+ "profile-settings",
1548
+ );
1451
1549
  if ((result.status !== "automatic" && result.status !== "active") || result.reason) {
1452
1550
  ctx.ui.notify(
1453
1551
  `Cognitive Routing settings could not be applied: ${result.reason ?? result.status}.`,
@@ -1459,8 +1557,63 @@ export async function handleFreeflowCommand(
1459
1557
  return { changed: true, reloadRequired: false };
1460
1558
  }
1461
1559
  if (item.id === "freeflow.session.reset") {
1462
- const result = await resetSessionOverrides(ctx, pi);
1463
- return { changed: result.changed, reloadRequired: result.reloadRequired };
1560
+ const routingResult = cognitiveRoutingController
1561
+ ? await cognitiveRoutingController.resetSessionProfileOverrides("Reset session overrides")
1562
+ : { status: "unchanged" };
1563
+ if (routingResult.status === "blocked" || routingResult.reason) {
1564
+ ctx.ui.notify(
1565
+ `Cognitive Routing session presets could not be reset: ${routingResult.reason ?? routingResult.status}.`,
1566
+ "warning",
1567
+ );
1568
+ return { changed: false, reloadRequired: false };
1569
+ }
1570
+ try {
1571
+ const result = await resetSessionOverrides(ctx, pi);
1572
+ return {
1573
+ changed: result.changed || routingResult.status !== "unchanged",
1574
+ reloadRequired: result.reloadRequired,
1575
+ };
1576
+ } catch (error) {
1577
+ ctx.ui.notify(
1578
+ `Routing presets were reset, but core/context session overrides could not be reset: ${
1579
+ error instanceof Error ? error.message : String(error)
1580
+ }`,
1581
+ "warning",
1582
+ );
1583
+ return {
1584
+ changed: routingResult.status !== "unchanged",
1585
+ reloadRequired: false,
1586
+ };
1587
+ }
1588
+ }
1589
+ if (item.configScope === "session" && isCognitiveRoutingProfileItem(item)) {
1590
+ if (!cognitiveRoutingController) {
1591
+ ctx.ui.notify("Cognitive Routing is unavailable for this session.", "warning");
1592
+ return { changed: false, reloadRequired: false };
1593
+ }
1594
+ const profile = item.id.split(".").at(-1) as CognitiveRoutingProfileName;
1595
+ const override =
1596
+ value === undefined || value === LOCAL_INHERIT
1597
+ ? null
1598
+ : {
1599
+ provider: (value as CognitiveRoutingProfile).provider,
1600
+ modelId: (value as CognitiveRoutingProfile).model,
1601
+ thinking: (value as CognitiveRoutingProfile).thinking,
1602
+ };
1603
+ const result = await cognitiveRoutingController.setSessionProfileOverride(
1604
+ profile,
1605
+ override,
1606
+ "Session profile settings",
1607
+ );
1608
+ if (result.status === "blocked" || result.reason) {
1609
+ ctx.ui.notify(
1610
+ `Cognitive Routing session preset could not be applied: ${result.reason ?? result.status}.`,
1611
+ "warning",
1612
+ );
1613
+ return { changed: false, reloadRequired: false };
1614
+ }
1615
+ await afterChange(false);
1616
+ return { changed: result.status !== "unchanged", reloadRequired: false };
1464
1617
  }
1465
1618
  if (item.configScope === "session") {
1466
1619
  const keyById = {