@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
@@ -11,8 +11,8 @@ import {
11
11
  setSessionCoreOverride,
12
12
  } from "../runtime/runtime-context.js";
13
13
  import { PiSettingsComponent } from "./settings-tui.js";
14
- import { supportsPiSessionModelStateApi } from "../cognitive-routing/pi-session-control.js";
15
14
  import { isPiFlowHost } from "../runtime/runtime-identity.js";
15
+ import { isWorkerProfile, workersForDelegation } from "../cognitive-routing-v2/types.js";
16
16
  const DEFAULT_FREEFLOW_ENABLED = true;
17
17
  const DEFAULT_CONTEXT_VIRTUALIZATION_ENABLED = false;
18
18
  const DEFAULT_CONVERSATION_HISTORY_ENABLED = false;
@@ -102,7 +102,7 @@ function effectiveItemValue(item) {
102
102
  function formatCoreValue(value) {
103
103
  if (typeof value === "boolean") return booleanValue(value);
104
104
  if (isRecord(value) && typeof value.provider === "string" && typeof value.model === "string") {
105
- const effort = typeof value.thinkingLevel === "string" ? ` · ${value.thinkingLevel}` : "";
105
+ const effort = typeof value.thinking === "string" ? ` · ${value.thinking}` : "";
106
106
  return `${value.provider}/${value.model}${effort}`;
107
107
  }
108
108
  return String(value ?? "");
@@ -192,6 +192,68 @@ function createScopedBooleanItem(options) {
192
192
  item.displaySuffix = coreDisplaySuffix(item);
193
193
  return item;
194
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
+ }
195
257
  function resolveSettingsCoreView(rawConfig, layers) {
196
258
  const localConfig = layers?.local.valid && isRecord(layers.local.parsed) ? layers.local.parsed : {};
197
259
  const fallbackCore = {
@@ -248,7 +310,7 @@ function createSessionBooleanItem(options) {
248
310
  item.displaySuffix = coreDisplaySuffix(item);
249
311
  return item;
250
312
  }
251
- function sessionFreeflowItems(state, cognitiveRoutingController) {
313
+ function sessionFreeflowItems(state, cognitiveRoutingController, ctx) {
252
314
  const sessionOverrides = state.sessionOverrides;
253
315
  const configured = state.configuredCoreConfig;
254
316
  const configuredSources = state.configuredSources;
@@ -290,47 +352,76 @@ function sessionFreeflowItems(state, cognitiveRoutingController) {
290
352
  contextVirtualizationItem.inactive = freeflowInactive;
291
353
  conversationHistoryItem.inactive = freeflowInactive;
292
354
  const cognitiveRoutingState = cognitiveRoutingController?.state();
293
- const cognitiveRoutingProfile =
294
- cognitiveRoutingState?.controlMode === "manual-standard"
295
- ? "standard"
296
- : cognitiveRoutingState?.controlMode === "manual-reasoning"
297
- ? "reasoning"
298
- : "auto";
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";
299
363
  const cognitiveRoutingItem = state.cognitiveRouting
300
364
  ? {
301
365
  id: "freeflow.cognitiveRouting.profile",
302
366
  label: "Cognitive Routing",
303
- description: "Hold a profile manually or release the hold for automatic model control.",
367
+ description: "Hold an enabled profile manually or release the hold for automatic model control.",
304
368
  kind: "enum",
305
369
  value: cognitiveRoutingProfile,
306
- values: ["auto", "standard", "reasoning"],
307
- valueLabels: {
308
- auto: "automatic",
309
- standard: "manual · standard",
310
- reasoning: "manual · reasoning",
311
- },
312
- valueDescriptions: {
313
- auto: "Release the manual hold without forcing a model transition.",
314
- standard: "Hold the standard profile until /freeflow profile auto.",
315
- reasoning: "Hold the reasoning profile until /freeflow profile auto.",
316
- },
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
+ ]),
317
379
  inactive: freeflowInactive || !state.cognitiveRouting.effective || cognitiveRoutingController === undefined,
318
380
  runtimeInactive: state.cognitiveRouting.blockingReason?.code === "runtime_disabled",
319
381
  displaySuffix: cognitiveRoutingState?.effective ? cognitiveRoutingProfile : "unavailable",
320
382
  }
321
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;
322
411
  return [
323
412
  freeflowItem,
324
413
  ...(cognitiveRoutingItem ? [cognitiveRoutingItem] : []),
414
+ ...(cognitiveRoutingPresets ? [cognitiveRoutingPresets] : []),
325
415
  {
326
416
  id: "freeflow.session.reset",
327
417
  label: "Reset session overrides",
328
- description: "Clear Freeflow, Context Virtualization, and Conversation History overrides for this Pi session.",
418
+ description:
419
+ "Clear Freeflow, Context Virtualization, Conversation History, and routing profile overrides for this Pi session.",
329
420
  kind: "enum",
330
421
  value: "available",
331
422
  values: ["reset"],
332
423
  valueLabels: { reset: "Reset all session overrides" },
333
- valueDescriptions: { reset: "Return every session setting to its configured value." },
424
+ valueDescriptions: { reset: "Return every session setting and routing preset to its configured value." },
334
425
  format: () => "available",
335
426
  transient: true,
336
427
  },
@@ -363,8 +454,8 @@ function isCognitiveRoutingProfile(value) {
363
454
  isRecord(value) &&
364
455
  typeof value.provider === "string" &&
365
456
  typeof value.model === "string" &&
366
- typeof value.thinkingLevel === "string" &&
367
- COGNITIVE_ROUTING_THINKING_LEVELS.includes(value.thinkingLevel)
457
+ typeof value.thinking === "string" &&
458
+ COGNITIVE_ROUTING_THINKING_LEVELS.includes(value.thinking)
368
459
  );
369
460
  }
370
461
  function cognitiveRoutingThinkingLevels(model, registry) {
@@ -413,12 +504,14 @@ function cognitiveRoutingProfileDisplay(value) {
413
504
  return isCognitiveRoutingProfile(value) ? formatCoreValue(value) : "not configured";
414
505
  }
415
506
  function cognitiveRoutingSettingsSource(source) {
507
+ if (source === "session") return "session";
416
508
  if (source === "repository") return "repository";
417
509
  if (source === "personal" || source === "local") return "local";
418
510
  return "builtin";
419
511
  }
420
512
  function cognitiveRoutingProfileDisplaySuffix(source, scope) {
421
513
  if (!source) return undefined;
514
+ if (source === "session") return "(session override)";
422
515
  if (source === "personal" && scope === "repository") return "(effective personal)";
423
516
  return `(${source})`;
424
517
  }
@@ -437,7 +530,16 @@ function cognitiveRoutingConfirmStep(summary) {
437
530
  selectedKey: "__save__",
438
531
  };
439
532
  }
440
- function createCognitiveRoutingProfileWizard(currentValue, models, allowInherit) {
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
+ ) {
441
543
  const currentProfile = isCognitiveRoutingProfile(currentValue) ? currentValue : undefined;
442
544
  const currentModelKey = currentProfile
443
545
  ? cognitiveRoutingModelKey(currentProfile.provider, currentProfile.model)
@@ -448,8 +550,8 @@ function createCognitiveRoutingProfileWizard(currentValue, models, allowInherit)
448
550
  {
449
551
  key: LOCAL_INHERIT,
450
552
  value: LOCAL_INHERIT,
451
- label: "Inherit repository preset",
452
- description: "Remove the personal override without changing the repository preset.",
553
+ label: inheritChoice.label,
554
+ description: inheritChoice.description,
453
555
  },
454
556
  ]
455
557
  : []),
@@ -483,12 +585,11 @@ function createCognitiveRoutingProfileWizard(currentValue, models, allowInherit)
483
585
  if (selectedValues.length === 1) {
484
586
  const selected = selectedValues[0];
485
587
  if (selected === LOCAL_INHERIT) {
486
- return cognitiveRoutingConfirmStep("inherit repository preset");
588
+ return cognitiveRoutingConfirmStep(inheritChoice.summary);
487
589
  }
488
590
  const model = selected;
489
591
  if (!model?.key || model.thinkingLevels.length === 0) return undefined;
490
- const currentEffort =
491
- currentProfile && currentModelKey === model.key ? currentProfile.thinkingLevel : undefined;
592
+ const currentEffort = currentProfile && currentModelKey === model.key ? currentProfile.thinking : undefined;
492
593
  return {
493
594
  title: `Choose effort · ${model.label}`,
494
595
  choices: model.thinkingLevels.map((level) => ({
@@ -514,7 +615,7 @@ function createCognitiveRoutingProfileWizard(currentValue, models, allowInherit)
514
615
  return {
515
616
  provider: model.provider,
516
617
  model: model.model,
517
- thinkingLevel: selectedValues[1],
618
+ thinking: selectedValues[1],
518
619
  };
519
620
  },
520
621
  };
@@ -523,120 +624,71 @@ function cognitiveRoutingProfileItem(options) {
523
624
  const path = ["cognitiveRouting", "profiles", options.name];
524
625
  const repositoryProfile = getPath(options.rawConfig, path);
525
626
  const localProfile = getPath(options.localConfig, path);
526
- const effectiveProfile = options.capabilityState?.profiles?.[options.name] ?? repositoryProfile;
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;
527
633
  const localValue = isCognitiveRoutingProfile(localProfile) ? localProfile : LOCAL_INHERIT;
528
- const value = options.scope === "local" ? localValue : repositoryProfile;
529
- const source =
634
+ const value =
635
+ options.scope === "session"
636
+ ? (sessionProfile ?? LOCAL_INHERIT)
637
+ : options.scope === "local"
638
+ ? localValue
639
+ : repositoryProfile;
640
+ const configuredSource =
530
641
  options.capabilityState?.profileSources?.[options.name] ??
531
642
  (isCognitiveRoutingProfile(repositoryProfile) ? "repository" : undefined);
643
+ const source = options.scope === "session" ? (sessionProfile ? "session" : configuredSource) : configuredSource;
532
644
  const models = cognitiveRoutingModelOptions(options.ctx);
533
- const inactive = options.scope === "local" && models.length === 0 && localValue !== LOCAL_INHERIT;
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;
534
654
  return {
535
655
  id: `freeflow.cognitiveRouting.${options.name}`,
536
656
  label: `${options.name.charAt(0).toUpperCase()}${options.name.slice(1)} preset`,
537
657
  description:
538
- "Choose an authenticated available model and one effort supported by that model. Confirming writes the complete preset; cancel leaves it unchanged.",
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.",
539
661
  path,
540
662
  kind: "string",
541
663
  value,
542
- format: cognitiveRoutingProfileDisplay,
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
+ },
543
672
  configScope: options.scope,
544
673
  effectiveValue: effectiveProfile,
545
674
  effectiveSource: cognitiveRoutingSettingsSource(source),
546
- inheritedValue: repositoryProfile,
547
- inheritedSource: "repository",
675
+ inheritedValue: options.scope === "session" ? configuredProfile : repositoryProfile,
676
+ inheritedSource: cognitiveRoutingSettingsSource(configuredSource ?? "builtin"),
548
677
  inactive,
549
678
  displaySuffix: cognitiveRoutingProfileDisplaySuffix(source, options.scope),
550
- wizard: () => createCognitiveRoutingProfileWizard(value, models, options.scope === "local"),
679
+ wizard: () => createCognitiveRoutingProfileWizard(value, models, options.scope !== "repository", inheritChoice),
551
680
  };
552
681
  }
553
- function cognitiveRoutingSessionStartItem(options) {
554
- const path = ["cognitiveRouting", "sessionStart", options.setting];
555
- const defaultValue = options.setting === "control" ? "automatic" : "reasoning";
556
- const values = options.setting === "control" ? ["automatic", "manual"] : ["standard", "reasoning"];
557
- const labels = Object.fromEntries(values.map((value) => [value, value]));
558
- const descriptions =
559
- options.setting === "control"
560
- ? {
561
- automatic: "Start the new session in Reasoning; automatic delegation may use Standard during execution.",
562
- manual: "Hold the selected profile for the new session until the user changes it.",
563
- }
564
- : {
565
- standard: "Start a new manual session with the configured Standard preset.",
566
- reasoning: "Start a new manual session with the configured Reasoning preset.",
567
- };
568
- const repositoryValue = getPath(options.rawConfig, path);
569
- const localValue = getPath(options.localConfig, path);
570
- const isValidValue = (value) => values.includes(value);
571
- const repositorySetting = isValidValue(repositoryValue) ? repositoryValue : undefined;
572
- const localSetting = isValidValue(localValue) ? localValue : undefined;
573
- const inheritedValue = repositorySetting ?? defaultValue;
574
- const inheritedSource = repositorySetting ? "repository" : "builtin";
575
- const configuredSource =
576
- options.capabilityState?.sessionStartSources?.[options.setting] ?? (repositorySetting ? "repository" : undefined);
577
- const effectiveSessionStartControl =
578
- options.capabilityState?.sessionStart?.control ??
579
- getPath(options.localConfig, ["cognitiveRouting", "sessionStart", "control"]) ??
580
- getPath(options.rawConfig, ["cognitiveRouting", "sessionStart", "control"]) ??
581
- "automatic";
582
- const effectiveValue =
583
- options.setting === "profile" && effectiveSessionStartControl === "automatic"
584
- ? "reasoning"
585
- : (options.capabilityState?.sessionStart?.[options.setting] ?? inheritedValue);
586
- const effectiveSource = cognitiveRoutingSettingsSource(configuredSource);
587
- const label = options.setting === "control" ? "Session start control" : "Manual session-start profile";
588
- const description =
589
- options.setting === "control"
590
- ? "Choose automatic or manual control for new sessions only; changing this does not alter the active session."
591
- : "Choose the profile for manual session starts; automatic sessions always start in Reasoning.";
592
- const item =
593
- options.scope === "local"
594
- ? {
595
- id: `freeflow.cognitiveRouting.sessionStart.${options.setting}`,
596
- label,
597
- description: `${description} Choose inherit to use the repository value; use /freeflow settings repo to edit shared defaults.`,
598
- path,
599
- kind: "enum",
600
- value: localSetting ?? LOCAL_INHERIT,
601
- values: [LOCAL_INHERIT, ...values],
602
- valueLabels: { inherit: "Inherit repository", ...labels },
603
- valueDescriptions: {
604
- inherit: `Use ${inheritedValue} from ${inheritedSource}.`,
605
- ...descriptions,
606
- },
607
- format: (value) => String(value),
608
- configScope: "local",
609
- configValues: { inherit: undefined },
610
- effectiveValue,
611
- effectiveSource,
612
- inheritedValue,
613
- inheritedSource,
614
- }
615
- : {
616
- id: `freeflow.cognitiveRouting.sessionStart.${options.setting}`,
617
- label,
618
- description: `${description} This edits shared .freeflow/config.json.`,
619
- path,
620
- kind: "enum",
621
- value: inheritedValue,
622
- values,
623
- valueLabels: labels,
624
- valueDescriptions: descriptions,
625
- format: (value) => String(value),
626
- defaultValue,
627
- configScope: "repository",
628
- effectiveValue,
629
- effectiveSource,
630
- localOverrideValue: localSetting,
631
- };
632
- item.displaySuffix = coreDisplaySuffix(item);
633
- return item;
634
- }
635
682
  function isCognitiveRoutingRuntimeAvailable(pi) {
636
- return isPiFlowHost(pi?.host) || supportsPiSessionModelStateApi(pi);
683
+ return (
684
+ !isPiFlowHost(pi?.host) &&
685
+ typeof pi?.appendEntry === "function" &&
686
+ typeof pi?.setModel === "function" &&
687
+ typeof pi?.setThinkingLevel === "function"
688
+ );
637
689
  }
638
690
  function freeflowItems(rawConfig, options = {}) {
639
- const scope = options.scope ?? "repository";
691
+ const scope = options.scope === "local" ? "local" : "repository";
640
692
  const layers = options.layers;
641
693
  const { localConfig, core, sources } = resolveSettingsCoreView(rawConfig, layers);
642
694
  const freeflowItem = createScopedBooleanItem({
@@ -689,7 +741,7 @@ function freeflowItems(rawConfig, options = {}) {
689
741
  label: "Enabled",
690
742
  description: cognitiveRoutingRuntimeDisabled
691
743
  ? "Requires a host model-state control API; configuration is read-only on this host."
692
- : "Allow Cognitive Routing to own the session model lease and switch the configured profiles.",
744
+ : "Allow Cognitive Routing to manage explicit assignments and request native profile transitions.",
693
745
  path: ["cognitiveRouting", "enabled"],
694
746
  effectiveValue: cognitiveRoutingState?.enabled ?? false,
695
747
  effectiveSource: cognitiveRoutingSettingsSource(cognitiveRoutingState?.enabledSource),
@@ -697,7 +749,31 @@ function freeflowItems(rawConfig, options = {}) {
697
749
  });
698
750
  cognitiveRoutingEnabledItem.inactive = freeflowInactive || cognitiveRoutingRuntimeDisabled;
699
751
  cognitiveRoutingEnabledItem.runtimeInactive = cognitiveRoutingRuntimeDisabled;
700
- const cognitiveRoutingProfiles = ["standard", "reasoning"].map((name) =>
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;
761
+ const cognitiveRoutingProjectionItem = createScopedBooleanItem({
762
+ scope,
763
+ rawConfig,
764
+ localConfig,
765
+ id: "freeflow.cognitiveRouting.projection",
766
+ label: "Context projection",
767
+ description:
768
+ "Select worker evidence for Coordinator; disable projection to keep ordinary Pi context in all enabled profiles.",
769
+ path: ["cognitiveRouting", "projection"],
770
+ effectiveValue: cognitiveRoutingState?.projection ?? false,
771
+ effectiveSource: cognitiveRoutingSettingsSource(cognitiveRoutingState?.projectionSource),
772
+ defaultValue: false,
773
+ });
774
+ cognitiveRoutingProjectionItem.inactive = freeflowInactive || cognitiveRoutingRuntimeDisabled;
775
+ cognitiveRoutingProjectionItem.runtimeInactive = cognitiveRoutingRuntimeDisabled;
776
+ const cognitiveRoutingProfiles = ["coordinator", "helper", "executor"].map((name) =>
701
777
  cognitiveRoutingProfileItem({
702
778
  name: name,
703
779
  scope,
@@ -707,16 +783,7 @@ function freeflowItems(rawConfig, options = {}) {
707
783
  ctx: options.ctx,
708
784
  }),
709
785
  );
710
- const cognitiveRoutingSessionStart = ["control", "profile"].map((setting) =>
711
- cognitiveRoutingSessionStartItem({
712
- setting,
713
- scope,
714
- rawConfig,
715
- localConfig,
716
- capabilityState: cognitiveRoutingState,
717
- }),
718
- );
719
- for (const item of [...cognitiveRoutingProfiles, ...cognitiveRoutingSessionStart]) {
786
+ for (const item of cognitiveRoutingProfiles) {
720
787
  item.inactive ||= freeflowInactive || cognitiveRoutingRuntimeDisabled;
721
788
  item.runtimeInactive = cognitiveRoutingRuntimeDisabled;
722
789
  }
@@ -734,12 +801,17 @@ function freeflowItems(rawConfig, options = {}) {
734
801
  label: "Cognitive Routing",
735
802
  description: cognitiveRoutingRuntimeDisabled
736
803
  ? "Cognitive Routing configuration is visible for inspection but requires a host model-state control API."
737
- : "Configure the Standard and Reasoning profiles and choose whether Freeflow may manage model state for this repository.",
804
+ : "Configure Coordinator, Helper, and Executor profiles plus the workers available for delegation.",
738
805
  kind: "group",
739
806
  value: cognitiveRoutingRuntimeDisabled ? false : (cognitiveRoutingState?.enabled ?? false),
740
807
  inactive: freeflowInactive,
741
808
  displaySuffix: cognitiveRoutingStatus,
742
- children: [cognitiveRoutingEnabledItem, ...cognitiveRoutingProfiles, ...cognitiveRoutingSessionStart],
809
+ children: [
810
+ cognitiveRoutingEnabledItem,
811
+ cognitiveRoutingDelegationItem,
812
+ ...cognitiveRoutingProfiles,
813
+ cognitiveRoutingProjectionItem,
814
+ ],
743
815
  };
744
816
  })();
745
817
  return [
@@ -761,6 +833,7 @@ function pruneKnownDefaults(config) {
761
833
  { path: ["enabled"], value: DEFAULT_FREEFLOW_ENABLED },
762
834
  { path: ["contextVirtualization"], value: DEFAULT_CONTEXT_VIRTUALIZATION_ENABLED },
763
835
  { path: ["conversationHistory"], value: DEFAULT_CONVERSATION_HISTORY_ENABLED },
836
+ { path: ["cognitiveRouting", "delegation"], value: "executor" },
764
837
  ];
765
838
  for (const item of defaultPaths) {
766
839
  if (valuesEqual(getPath(config, item.path), item.value)) {
@@ -834,7 +907,7 @@ async function updateConfig(cwd, item, value, scope = item.configScope ?? "repos
834
907
  await writeFile(join(cwd, ".freeflow/config.json"), `${JSON.stringify(next, null, 2)}\n`, "utf8");
835
908
  }
836
909
  function isCognitiveRoutingProfileItem(item) {
837
- return item.id === "freeflow.cognitiveRouting.standard" || item.id === "freeflow.cognitiveRouting.reasoning";
910
+ return ["coordinator", "helper", "executor"].some((profile) => item.id === `freeflow.cognitiveRouting.${profile}`);
838
911
  }
839
912
  function valueForDisplay(item) {
840
913
  let value;
@@ -874,10 +947,11 @@ function refreshSettingsDerivedState(items) {
874
947
  const freeflowInactive = freeflowItem ? effectiveItemValue(freeflowItem) !== true : false;
875
948
  const cognitiveRoutingGroup = findSettingsItem(items, "freeflow.cognitiveRouting");
876
949
  const cognitiveRoutingEnabledItem = findSettingsItem(items, "freeflow.cognitiveRouting.enabled");
877
- const cognitiveRoutingProfiles = [
878
- findSettingsItem(items, "freeflow.cognitiveRouting.standard"),
879
- findSettingsItem(items, "freeflow.cognitiveRouting.reasoning"),
880
- ];
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
+ );
881
955
  if (cognitiveRoutingGroup && cognitiveRoutingEnabledItem) {
882
956
  const enabled = effectiveItemValue(cognitiveRoutingEnabledItem) === true;
883
957
  const profilesConfigured = cognitiveRoutingProfiles.every((item) =>
@@ -1152,7 +1226,7 @@ export async function handleFreeflowCommand(args, ctx, afterChange, pi, cognitiv
1152
1226
  let reconcileCognitiveRouting = settingsScope === "session";
1153
1227
  const items =
1154
1228
  settingsScope === "session"
1155
- ? sessionFreeflowItems(state, cognitiveRoutingController)
1229
+ ? sessionFreeflowItems(state, cognitiveRoutingController, ctx)
1156
1230
  : freeflowItems(raw, {
1157
1231
  scope: settingsScope,
1158
1232
  layers,
@@ -1195,8 +1269,61 @@ export async function handleFreeflowCommand(args, ctx, afterChange, pi, cognitiv
1195
1269
  return { changed: true, reloadRequired: false };
1196
1270
  }
1197
1271
  if (item.id === "freeflow.session.reset") {
1198
- const result = await resetSessionOverrides(ctx, pi);
1199
- return { changed: result.changed, reloadRequired: result.reloadRequired };
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 };
1200
1327
  }
1201
1328
  if (item.configScope === "session") {
1202
1329
  const keyById = {
@@ -0,0 +1,42 @@
1
+ // A local planning estimate, not a provider token count or an output allocation.
2
+ // In particular, base64 and native usage/metadata are not model text.
3
+ export function estimateRequest(systemPrompt: string, tools: readonly any[], messages: readonly any[], model: any) {
4
+ const text = (value: string) => Math.ceil(value.length / 3);
5
+ const content = (value: any): number =>
6
+ typeof value === "string"
7
+ ? text(value)
8
+ : Array.isArray(value)
9
+ ? value.reduce(
10
+ (sum, b) =>
11
+ sum +
12
+ (b.type === "image"
13
+ ? 1200
14
+ : b.type === "toolCall"
15
+ ? text(b.name + JSON.stringify(b.arguments ?? {}))
16
+ : b.type === "thinking"
17
+ ? text(b.thinking ?? "")
18
+ : text(b.text ?? "")),
19
+ 0,
20
+ )
21
+ : 0;
22
+ const estimatedTokens =
23
+ text(systemPrompt) +
24
+ text(JSON.stringify(tools.map((t) => ({ name: t.name, description: t.description, parameters: t.parameters })))) +
25
+ messages.reduce((sum, m) => sum + 8 + content(m.content), 0);
26
+ const window = model?.contextWindow ?? 0;
27
+ const outputReserve = Math.min(4096, Math.max(1, Math.floor(window / 10)));
28
+ const maximumInputTokens = Math.max(0, window - outputReserve);
29
+ const estimateMethod =
30
+ "Planning estimate: text characters / 3, 1200 tokens/image, schemas and message framing; output headroom is not the provider allocation";
31
+ const warnings =
32
+ estimatedTokens > maximumInputTokens
33
+ ? [
34
+ {
35
+ ref: "",
36
+ code: "budget_estimate",
37
+ detail: `Estimated input ${estimatedTokens} exceeds planning allowance ${maximumInputTokens}. Actual output allocation and image tokenization are provider-dependent; fit is unconfirmed. Use native compaction or revise explicitly if the provider rejects the request.`,
38
+ },
39
+ ]
40
+ : [];
41
+ return { estimatedTokens, maximumInputTokens, outputReserve, estimateMethod, warnings };
42
+ }