@hassangameryt/freeflow 0.7.1 → 0.7.2

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 (122) 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 +28 -23
  6. package/capabilities/cognitive-routing/SKILL.md +94 -311
  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 +120 -0
  12. package/pi-extension/dist/cognitive-routing-v2/projection.js +321 -0
  13. package/pi-extension/dist/cognitive-routing-v2/render.js +231 -0
  14. package/pi-extension/dist/cognitive-routing-v2/runtime.js +1585 -0
  15. package/pi-extension/dist/cognitive-routing-v2/schemas.js +107 -0
  16. package/pi-extension/dist/cognitive-routing-v2/state.js +612 -0
  17. package/pi-extension/dist/cognitive-routing-v2/tools.js +91 -0
  18. package/pi-extension/dist/cognitive-routing-v2/types.js +44 -0
  19. package/pi-extension/dist/freeflow-context/tool.js +5 -0
  20. package/pi-extension/dist/index.js +279 -624
  21. package/pi-extension/dist/runtime/runtime-context.js +63 -13
  22. package/pi-extension/dist/session-sources/events.js +195 -0
  23. package/pi-extension/dist/session-sources/read-only-session.js +148 -0
  24. package/pi-extension/dist/session-sources/sources.js +233 -0
  25. package/pi-extension/dist/settings/settings-ui.js +44 -117
  26. package/pi-extension/src/cognitive-routing-v2/budget.ts +42 -0
  27. package/pi-extension/src/cognitive-routing-v2/config.ts +143 -0
  28. package/pi-extension/src/cognitive-routing-v2/projection.ts +365 -0
  29. package/pi-extension/src/cognitive-routing-v2/render.ts +256 -0
  30. package/pi-extension/src/cognitive-routing-v2/runtime.ts +1634 -0
  31. package/pi-extension/src/cognitive-routing-v2/schemas.ts +109 -0
  32. package/pi-extension/src/cognitive-routing-v2/state.ts +622 -0
  33. package/pi-extension/src/cognitive-routing-v2/tools.ts +93 -0
  34. package/pi-extension/src/cognitive-routing-v2/types.ts +222 -0
  35. package/pi-extension/src/freeflow-context/tool.ts +5 -0
  36. package/pi-extension/src/index.ts +285 -674
  37. package/pi-extension/src/runtime/runtime-context.ts +75 -14
  38. package/pi-extension/src/session-sources/events.ts +208 -0
  39. package/pi-extension/src/session-sources/read-only-session.ts +170 -0
  40. package/pi-extension/src/session-sources/sources.ts +263 -0
  41. package/pi-extension/src/settings/settings-ui.ts +54 -131
  42. package/pi-extension/tests/cognitive-routing-v2/communication.test.js +484 -0
  43. package/pi-extension/tests/cognitive-routing-v2/compaction-input.test.js +183 -0
  44. package/pi-extension/tests/cognitive-routing-v2/native.test.js +441 -0
  45. package/pi-extension/tests/cognitive-routing-v2/performance.test.js +87 -0
  46. package/pi-extension/tests/cognitive-routing-v2/persistence.test.js +245 -0
  47. package/pi-extension/tests/cognitive-routing-v2/projection.test.js +241 -0
  48. package/pi-extension/tests/cognitive-routing-v2/recovery.test.js +236 -0
  49. package/pi-extension/tests/cognitive-routing-v2/render.test.js +230 -0
  50. package/pi-extension/tests/cognitive-routing-v2/runtime.test.js +153 -0
  51. package/pi-extension/tests/cognitive-routing-v2/stabilization.test.js +521 -0
  52. package/pi-extension/tests/cognitive-routing-v2/state.test.js +275 -0
  53. package/pi-extension/tests/context-virtualization/extension.test.js +16 -0
  54. package/pi-extension/tests/fixtures/routing-native.js +188 -0
  55. package/pi-extension/tests/integration/mode-free-core.test.js +3 -2
  56. package/pi-extension/tests/integration/pi-extension.test.js +154 -37
  57. package/pi-extension/tests/integration/prompt-architecture.test.js +87 -6
  58. package/pi-extension/tests/integration/unaffected-regressions.test.js +77 -10
  59. package/plugin.json +1 -1
  60. package/runtime/prompts/cognitive-routing.md +4 -5
  61. package/runtime/prompts/core.md +51 -45
  62. package/runtime/prompts/interaction-contract.md +5 -5
  63. package/skills/action-selection/SKILL.md +57 -97
  64. package/skills/design-for-depth/SKILL.md +58 -193
  65. package/skills/design-for-depth/references/implementation-decisions.md +76 -0
  66. package/skills/diagnose-failure/SKILL.md +64 -196
  67. package/skills/discuss/SKILL.md +64 -123
  68. package/skills/discuss/references/discussion-continuity.md +7 -3
  69. package/skills/execute-work/SKILL.md +69 -88
  70. package/skills/execute-work/references/execute-work-edges.md +9 -3
  71. package/skills/handoff/SKILL.md +44 -54
  72. package/skills/handoff/references/handoff-templates.md +15 -6
  73. package/skills/review-artifact/SKILL.md +80 -127
  74. package/skills/review-artifact/references/independent-artifact-reviewer-contract.md +1 -1
  75. package/skills/review-work/SKILL.md +65 -115
  76. package/skills/review-work/references/independent-work-reviewer-contract.md +1 -1
  77. package/skills/simplify-code/SKILL.md +47 -100
  78. package/skills/track-work/SKILL.md +128 -119
  79. package/skills/track-work/references/working-record-format.md +14 -2
  80. package/skills/verify-work/SKILL.md +58 -62
  81. package/skills/verify-work/references/integration-evidence.md +4 -2
  82. package/skills/verify-work/references/test-design.md +89 -0
  83. package/skills/workflow/SKILL.md +73 -119
  84. package/skills/write-plan/SKILL.md +46 -77
  85. package/skills/write-plan/references/plan-shapes.md +5 -5
  86. package/skills/write-spec/SKILL.md +51 -63
  87. package/skills/write-spec/references/artifact-standards.md +2 -2
  88. package/skills/write-spec/references/decision-records.md +3 -2
  89. package/skills/write-spec/references/spec-shapes.md +15 -0
  90. package/pi-extension/dist/cognitive-routing/commands.js +0 -96
  91. package/pi-extension/dist/cognitive-routing/config.js +0 -232
  92. package/pi-extension/dist/cognitive-routing/controller.js +0 -1292
  93. package/pi-extension/dist/cognitive-routing/history.js +0 -451
  94. package/pi-extension/dist/cognitive-routing/host.js +0 -188
  95. package/pi-extension/dist/cognitive-routing/pi-routing-session.js +0 -414
  96. package/pi-extension/dist/cognitive-routing/pi-session-control.js +0 -320
  97. package/pi-extension/dist/cognitive-routing/runtime.js +0 -43
  98. package/pi-extension/dist/cognitive-routing/tool.js +0 -212
  99. package/pi-extension/dist/cognitive-routing/types.js +0 -6
  100. package/pi-extension/src/cognitive-routing/commands.ts +0 -129
  101. package/pi-extension/src/cognitive-routing/config.ts +0 -303
  102. package/pi-extension/src/cognitive-routing/controller.ts +0 -1628
  103. package/pi-extension/src/cognitive-routing/history.ts +0 -650
  104. package/pi-extension/src/cognitive-routing/host.ts +0 -266
  105. package/pi-extension/src/cognitive-routing/pi-routing-session.ts +0 -538
  106. package/pi-extension/src/cognitive-routing/pi-session-control.ts +0 -444
  107. package/pi-extension/src/cognitive-routing/runtime.ts +0 -57
  108. package/pi-extension/src/cognitive-routing/tool.ts +0 -273
  109. package/pi-extension/src/cognitive-routing/types.ts +0 -89
  110. package/pi-extension/tests/cognitive-routing/commands.test.js +0 -134
  111. package/pi-extension/tests/cognitive-routing/controller.test.js +0 -1128
  112. package/pi-extension/tests/cognitive-routing/extension-lifecycle.test.js +0 -1142
  113. package/pi-extension/tests/cognitive-routing/history.test.js +0 -274
  114. package/pi-extension/tests/cognitive-routing/host-identity.test.js +0 -63
  115. package/pi-extension/tests/cognitive-routing/pi-normal-lifecycle.test.js +0 -434
  116. package/pi-extension/tests/cognitive-routing/pi-session-control.test.js +0 -174
  117. package/pi-extension/tests/cognitive-routing/pi-session-history.test.js +0 -91
  118. package/pi-extension/tests/cognitive-routing/profile-resolution.test.js +0 -279
  119. package/pi-extension/tests/cognitive-routing/tool.test.js +0 -230
  120. package/skills/tdd/SKILL.md +0 -143
  121. package/skills/tdd/references/test-design.md +0 -96
  122. /package/pi-extension/tests/{cognitive-routing/host-fixture.js → fixtures/pi-host.js} +0 -0
@@ -0,0 +1,263 @@
1
+ import { createHash } from "node:crypto";
2
+ import { sessionEntryToContextMessages } from "@earendil-works/pi-coding-agent";
3
+ import {
4
+ canonical,
5
+ idFor,
6
+ refFor,
7
+ type NativeEntry,
8
+ type State,
9
+ type View,
10
+ type Problem,
11
+ } from "../cognitive-routing-v2/types.js";
12
+
13
+ export interface Source {
14
+ ref: string;
15
+ entry: NativeEntry;
16
+ message: any;
17
+ hash: string;
18
+ producer: View | "common";
19
+ executionId?: string;
20
+ assignmentId?: string;
21
+ active: boolean;
22
+ original?: Source;
23
+ reportHandoff?: string;
24
+ }
25
+ export interface Associated {
26
+ message: any;
27
+ source?: Source;
28
+ }
29
+ export const bodyHash = (value: unknown) => createHash("sha256").update(canonical(value)).digest("hex");
30
+ export const textRef = (ref: string) => `${ref}#text`;
31
+ const routingTools = new Set(["freeflow_delegate", "freeflow_return", "freeflow_project", "freeflow_unit"]);
32
+ // Discovery and new selections share this policy. Existing saved selections keep
33
+ // their original delivery semantics; mixed assistant messages offer visible text.
34
+ export function isTaskEvidence(source: Source): boolean {
35
+ if (source.original) return true;
36
+ if (source.message.role === "toolResult") return !routingTools.has(source.message.toolName);
37
+ return (
38
+ source.message.role === "assistant" &&
39
+ !source.message.content?.some((b: any) => b.type === "toolCall" && routingTools.has(b.name)) &&
40
+ source.message.content?.some((b: any) => b.type === "text" && b.text?.trim())
41
+ );
42
+ }
43
+ interface Exchange {
44
+ assistant: Source;
45
+ results: Map<string, Source[]>;
46
+ }
47
+ const callKey = (id: string, name: string) => JSON.stringify([id, name]);
48
+
49
+ export class Sources {
50
+ readonly byRef = new Map<string, Source>();
51
+ readonly ambiguous = new Set<string>();
52
+ private readonly byBody = new Map<string, Source[]>();
53
+ private readonly exchanges = new Map<string, Exchange>();
54
+ private readonly owners = new Map<string, Exchange>();
55
+ private tail?: Exchange;
56
+ private associated?: { messages: readonly any[]; items: Associated[] };
57
+ entries: readonly NativeEntry[] = [];
58
+ private activeIds?: ReadonlySet<string>;
59
+ constructor(entries: readonly NativeEntry[], state: State, activeIds?: ReadonlySet<string>) {
60
+ this.refresh(entries, state, activeIds);
61
+ }
62
+ refresh(entries: readonly NativeEntry[], state: State, activeIds?: ReadonlySet<string>): void {
63
+ const prefix = this.entries.length <= entries.length && this.entries.every((e, i) => e === entries[i]);
64
+ const start = prefix ? this.entries.length : 0;
65
+ this.associated = undefined;
66
+ if (!prefix) {
67
+ this.byRef.clear();
68
+ this.byBody.clear();
69
+ this.exchanges.clear();
70
+ this.owners.clear();
71
+ this.tail = undefined;
72
+ }
73
+ this.activeIds = activeIds;
74
+ for (const entry of entries.slice(start)) {
75
+ const messages = sessionEntryToContextMessages(entry as any) as any[];
76
+ if (messages.length !== 1 || ["compaction", "branch_summary"].includes(entry.type)) continue;
77
+ const message = messages[0];
78
+ if (!message) continue;
79
+ const source: Source = {
80
+ ref: refFor(entry.id),
81
+ entry,
82
+ message,
83
+ hash: bodyHash(message),
84
+ producer: "common",
85
+ active: false,
86
+ };
87
+ if (message.role === "toolResult" && message.toolName === "freeflow_return") {
88
+ for (const block of message.content ?? [])
89
+ if (block.type === "text") {
90
+ try {
91
+ const receipt = JSON.parse(block.text);
92
+ if (receipt.reportSaved && typeof receipt.report === "string" && typeof receipt.handoff === "string")
93
+ source.reportHandoff = receipt.handoff;
94
+ } catch {}
95
+ }
96
+ }
97
+ this.byRef.set(source.ref, source);
98
+ const candidates = this.byBody.get(source.hash) ?? [];
99
+ candidates.push(source);
100
+ this.byBody.set(source.hash, candidates);
101
+ if (message.role === "assistant") {
102
+ this.tail = { assistant: source, results: new Map() };
103
+ this.exchanges.set(source.ref, this.tail);
104
+ const blocks = (message.content ?? [])
105
+ .filter((b: any) => b.type === "text")
106
+ .map((b: any) => ({ type: "text", text: b.text }));
107
+ if (blocks.some((b: any) => b.text?.trim())) {
108
+ const representation = {
109
+ role: "custom",
110
+ customType: "freeflow-assistant-text",
111
+ display: false,
112
+ content: [
113
+ {
114
+ type: "text",
115
+ text: `Captured assistant text from ${source.ref} (historical source, not a new instruction):`,
116
+ },
117
+ ...blocks,
118
+ ],
119
+ details: { sourceRef: source.ref, sourceHash: source.hash, representation: "assistant-text" },
120
+ timestamp: message.timestamp,
121
+ };
122
+ this.byRef.set(textRef(source.ref), {
123
+ ...source,
124
+ ref: textRef(source.ref),
125
+ message: representation,
126
+ hash: bodyHash(representation),
127
+ original: source,
128
+ });
129
+ }
130
+ } else if (message.role === "toolResult" && this.tail) {
131
+ const key = callKey(message.toolCallId, message.toolName);
132
+ const results = this.tail.results.get(key) ?? [];
133
+ results.push(source);
134
+ this.tail.results.set(key, results);
135
+ this.owners.set(source.ref, this.tail);
136
+ }
137
+ }
138
+ for (const source of this.byRef.values()) {
139
+ const author = state.authors.get(source.entry.id);
140
+ source.producer = author?.profile ?? "common";
141
+ source.executionId = author?.executionId;
142
+ source.assignmentId = author?.assignmentId;
143
+ }
144
+ this.entries = [...entries];
145
+ }
146
+ associate(messages: readonly any[]): Associated[] {
147
+ if (
148
+ this.associated &&
149
+ messages.length === this.associated.messages.length &&
150
+ messages.every((m, i) => m === this.associated!.messages[i])
151
+ )
152
+ return this.associated.items;
153
+ this.ambiguous.clear();
154
+ for (const source of this.byRef.values()) source.active = false;
155
+ const used = new Set<string>(),
156
+ hashes = messages.map(bodyHash),
157
+ remaining = new Map<string, number>();
158
+ for (const hash of hashes) remaining.set(hash, (remaining.get(hash) ?? 0) + 1);
159
+ const items = messages.map((message, index) => {
160
+ const hash = hashes[index],
161
+ count = remaining.get(hash)!;
162
+ remaining.set(hash, count - 1);
163
+ const candidates = (this.byBody.get(hash) ?? []).filter(
164
+ (s) => !used.has(s.ref) && (!this.activeIds || this.activeIds.has(s.entry.id)),
165
+ );
166
+ const exact = candidates.find((s) => s.message === message);
167
+ const source = exact ?? (candidates.length === 1 || candidates.length === count ? candidates[0] : undefined);
168
+ if (!source) {
169
+ for (const candidate of candidates) {
170
+ this.ambiguous.add(candidate.ref);
171
+ this.ambiguous.add(textRef(candidate.ref));
172
+ }
173
+ return { message };
174
+ }
175
+ used.add(source.ref);
176
+ source.active = true;
177
+ const text = this.byRef.get(textRef(source.ref));
178
+ if (text) text.active = true;
179
+ return { message, source };
180
+ });
181
+ this.associated = { messages: [...messages], items };
182
+ return items;
183
+ }
184
+ eligible(ref: string, state: State): Problem | undefined {
185
+ const source = this.byRef.get(ref);
186
+ if (!idFor(ref) || !source)
187
+ return { ref, code: "source_unavailable", detail: "Exact source is unavailable on current native ancestry." };
188
+ const original = source.original ?? source;
189
+ if (bodyHash(original.message) !== original.hash || bodyHash(source.message) !== source.hash)
190
+ return {
191
+ ref,
192
+ code: "source_changed",
193
+ detail: "The captured source changed; reconcile evidence before delivery.",
194
+ };
195
+ if (source.producer !== "executor")
196
+ return { ref, code: "source_origin", detail: "Selection requires observed Executor attribution." };
197
+ if (state.exposure.get(original.ref) !== original.hash && state.exposure.get(source.ref) !== source.hash)
198
+ return {
199
+ ref,
200
+ code: "source_unexposed",
201
+ detail: "The captured body has not been fully exposed to Executor on this ancestry.",
202
+ };
203
+ return undefined;
204
+ }
205
+ selectionProblem(ref: string, state: State): Problem | undefined {
206
+ const problem = this.eligible(ref, state);
207
+ if (problem) return problem;
208
+ const source = this.byRef.get(ref)!;
209
+ if (!isTaskEvidence(source))
210
+ return {
211
+ ref,
212
+ code: "routing_source",
213
+ detail: this.byRef.has(textRef(ref))
214
+ ? `Routing control messages are not task evidence. Use ${textRef(ref)} if its visible assistant text is the intended evidence.`
215
+ : "Routing control receipts are not task evidence; saved communication is delivered separately.",
216
+ };
217
+ return undefined;
218
+ }
219
+ exchange(source: Source): { sources: Source[]; problems: Problem[] } {
220
+ if (source.original) return { sources: [source], problems: [] };
221
+ if (!["assistant", "toolResult"].includes(source.message.role)) return { sources: [source], problems: [] };
222
+ const group = source.message.role === "assistant" ? this.exchanges.get(source.ref) : this.owners.get(source.ref);
223
+ const calls = group?.assistant.message.content?.filter((b: any) => b.type === "toolCall") ?? [];
224
+ if (
225
+ !group ||
226
+ (source.message.role === "toolResult" &&
227
+ !calls.some((c: any) => c.id === source.message.toolCallId && c.name === source.message.toolName))
228
+ )
229
+ return {
230
+ sources: [],
231
+ problems: [
232
+ { ref: source.ref, code: "ambiguous_exchange", detail: "Result has no unique native assistant call." },
233
+ ],
234
+ };
235
+ const sources = [group.assistant],
236
+ problems: Problem[] = [];
237
+ if (new Set(calls.map((c: any) => c.id)).size !== calls.length)
238
+ problems.push({
239
+ ref: group.assistant.ref,
240
+ code: "duplicate_call_id",
241
+ detail: "One native assistant exchange repeats a tool-call identity.",
242
+ });
243
+ for (const call of calls) {
244
+ const results = group.results.get(callKey(call.id, call.name)) ?? [];
245
+ if (results.length !== 1)
246
+ problems.push({
247
+ ref: group.assistant.ref,
248
+ code: "incomplete_exchange",
249
+ detail: `Native call ${call.id} does not have exactly one captured result.`,
250
+ });
251
+ else sources.push(results[0]);
252
+ }
253
+ return { sources, problems };
254
+ }
255
+ ordered(sources: Iterable<Source>): Source[] {
256
+ const ids = new Set([...sources].map((s) => s.ref));
257
+ return this.entries.flatMap((entry) =>
258
+ [this.byRef.get(refFor(entry.id)), this.byRef.get(textRef(refFor(entry.id)))].filter(
259
+ (s): s is Source => !!s && ids.has(s.ref),
260
+ ),
261
+ );
262
+ }
263
+ }
@@ -19,14 +19,13 @@ 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,
26
25
  CognitiveRoutingProfile,
27
26
  CognitiveRoutingProfileName,
28
27
  CognitiveRoutingThinkingLevel,
29
- } from "../cognitive-routing/types.js";
28
+ } from "../cognitive-routing-v2/config.js";
30
29
 
31
30
  const DEFAULT_FREEFLOW_ENABLED = true;
32
31
  const DEFAULT_CONTEXT_VIRTUALIZATION_ENABLED = false;
@@ -70,7 +69,10 @@ type SettingsItem = {
70
69
 
71
70
  type CognitiveRoutingSettingsController = {
72
71
  state(): { effective: boolean; controlMode: string; activeProfile?: string };
73
- setManualProfile(profile: "standard" | "reasoning", mechanism?: string): Promise<{ status: string; reason?: string }>;
72
+ setManualProfile(
73
+ profile: "executor" | "coordinator",
74
+ mechanism?: string,
75
+ ): Promise<{ status: string; reason?: string }>;
74
76
  setAutomaticControl(mechanism?: string): Promise<{ status: string; reason?: string }>;
75
77
  };
76
78
 
@@ -184,7 +186,7 @@ function effectiveItemValue(item: SettingsItem): SettingsValue | undefined {
184
186
  function formatCoreValue(value: unknown): string {
185
187
  if (typeof value === "boolean") return booleanValue(value);
186
188
  if (isRecord(value) && typeof value.provider === "string" && typeof value.model === "string") {
187
- const effort = typeof value.thinkingLevel === "string" ? ` · ${value.thinkingLevel}` : "";
189
+ const effort = typeof value.thinking === "string" ? ` · ${value.thinking}` : "";
188
190
  return `${value.provider}/${value.model}${effort}`;
189
191
  }
190
192
  return String(value ?? "");
@@ -423,10 +425,10 @@ function sessionFreeflowItems(
423
425
 
424
426
  const cognitiveRoutingState = cognitiveRoutingController?.state();
425
427
  const cognitiveRoutingProfile =
426
- cognitiveRoutingState?.controlMode === "manual-standard"
427
- ? "standard"
428
- : cognitiveRoutingState?.controlMode === "manual-reasoning"
429
- ? "reasoning"
428
+ cognitiveRoutingState?.controlMode === "manual-executor"
429
+ ? "executor"
430
+ : cognitiveRoutingState?.controlMode === "manual-coordinator"
431
+ ? "coordinator"
430
432
  : "auto";
431
433
  const cognitiveRoutingItem: SettingsItem | undefined = state.cognitiveRouting
432
434
  ? {
@@ -435,16 +437,16 @@ function sessionFreeflowItems(
435
437
  description: "Hold a profile manually or release the hold for automatic model control.",
436
438
  kind: "enum",
437
439
  value: cognitiveRoutingProfile,
438
- values: ["auto", "standard", "reasoning"],
440
+ values: ["auto", "executor", "coordinator"],
439
441
  valueLabels: {
440
442
  auto: "automatic",
441
- standard: "manual · standard",
442
- reasoning: "manual · reasoning",
443
+ executor: "manual · executor",
444
+ coordinator: "manual · coordinator",
443
445
  },
444
446
  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.",
447
+ auto: "Release the manual hold and return to Coordinator reconciliation.",
448
+ executor: "Hold the executor profile until /freeflow profile auto.",
449
+ coordinator: "Hold the coordinator profile until /freeflow profile auto.",
448
450
  },
449
451
  inactive: freeflowInactive || !state.cognitiveRouting.effective || cognitiveRoutingController === undefined,
450
452
  runtimeInactive: state.cognitiveRouting.blockingReason?.code === "runtime_disabled",
@@ -517,8 +519,8 @@ function isCognitiveRoutingProfile(value: unknown): value is CognitiveRoutingPro
517
519
  isRecord(value) &&
518
520
  typeof value.provider === "string" &&
519
521
  typeof value.model === "string" &&
520
- typeof value.thinkingLevel === "string" &&
521
- COGNITIVE_ROUTING_THINKING_LEVELS.includes(value.thinkingLevel as CognitiveRoutingThinkingLevel)
522
+ typeof value.thinking === "string" &&
523
+ COGNITIVE_ROUTING_THINKING_LEVELS.includes(value.thinking as CognitiveRoutingThinkingLevel)
522
524
  );
523
525
  }
524
526
 
@@ -654,8 +656,7 @@ function createCognitiveRoutingProfileWizard(
654
656
  }
655
657
  const model = selected as CognitiveRoutingModelOption;
656
658
  if (!model?.key || model.thinkingLevels.length === 0) return undefined;
657
- const currentEffort =
658
- currentProfile && currentModelKey === model.key ? currentProfile.thinkingLevel : undefined;
659
+ const currentEffort = currentProfile && currentModelKey === model.key ? currentProfile.thinking : undefined;
659
660
  return {
660
661
  title: `Choose effort · ${model.label}`,
661
662
  choices: model.thinkingLevels.map((level) => ({
@@ -681,7 +682,7 @@ function createCognitiveRoutingProfileWizard(
681
682
  return {
682
683
  provider: model.provider,
683
684
  model: model.model,
684
- thinkingLevel: selectedValues[1] as CognitiveRoutingThinkingLevel,
685
+ thinking: selectedValues[1] as CognitiveRoutingThinkingLevel,
685
686
  } satisfies CognitiveRoutingProfile;
686
687
  },
687
688
  };
@@ -727,100 +728,13 @@ function cognitiveRoutingProfileItem(options: {
727
728
  };
728
729
  }
729
730
 
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
731
  function isCognitiveRoutingRuntimeAvailable(pi: any): boolean {
823
- return isPiFlowHost(pi?.host) || supportsPiSessionModelStateApi(pi);
732
+ return (
733
+ !isPiFlowHost(pi?.host) &&
734
+ typeof pi?.appendEntry === "function" &&
735
+ typeof pi?.setModel === "function" &&
736
+ typeof pi?.setThinkingLevel === "function"
737
+ );
824
738
  }
825
739
 
826
740
  function freeflowItems(
@@ -889,7 +803,7 @@ function freeflowItems(
889
803
  label: "Enabled",
890
804
  description: cognitiveRoutingRuntimeDisabled
891
805
  ? "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.",
806
+ : "Allow Cognitive Routing to manage explicit assignments and request native profile transitions.",
893
807
  path: ["cognitiveRouting", "enabled"],
894
808
  effectiveValue: cognitiveRoutingState?.enabled ?? false,
895
809
  effectiveSource: cognitiveRoutingSettingsSource(cognitiveRoutingState?.enabledSource),
@@ -898,7 +812,23 @@ function freeflowItems(
898
812
  cognitiveRoutingEnabledItem.inactive = freeflowInactive || cognitiveRoutingRuntimeDisabled;
899
813
  cognitiveRoutingEnabledItem.runtimeInactive = cognitiveRoutingRuntimeDisabled;
900
814
 
901
- const cognitiveRoutingProfiles = ["standard", "reasoning"].map((name) =>
815
+ const cognitiveRoutingProjectionItem = createScopedBooleanItem({
816
+ scope,
817
+ rawConfig,
818
+ localConfig,
819
+ id: "freeflow.cognitiveRouting.projection",
820
+ label: "Context projection",
821
+ description:
822
+ "Select Executor evidence for Coordinator; disable projection to keep ordinary Pi context in both profiles.",
823
+ path: ["cognitiveRouting", "projection"],
824
+ effectiveValue: cognitiveRoutingState?.projection ?? false,
825
+ effectiveSource: cognitiveRoutingSettingsSource(cognitiveRoutingState?.projectionSource),
826
+ defaultValue: false,
827
+ });
828
+ cognitiveRoutingProjectionItem.inactive = freeflowInactive || cognitiveRoutingRuntimeDisabled;
829
+ cognitiveRoutingProjectionItem.runtimeInactive = cognitiveRoutingRuntimeDisabled;
830
+
831
+ const cognitiveRoutingProfiles = ["executor", "coordinator"].map((name) =>
902
832
  cognitiveRoutingProfileItem({
903
833
  name: name as CognitiveRoutingProfileName,
904
834
  scope,
@@ -908,17 +838,7 @@ function freeflowItems(
908
838
  ctx: options.ctx,
909
839
  }),
910
840
  );
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]) {
841
+ for (const item of cognitiveRoutingProfiles) {
922
842
  item.inactive ||= freeflowInactive || cognitiveRoutingRuntimeDisabled;
923
843
  item.runtimeInactive = cognitiveRoutingRuntimeDisabled;
924
844
  }
@@ -936,12 +856,12 @@ function freeflowItems(
936
856
  label: "Cognitive Routing",
937
857
  description: cognitiveRoutingRuntimeDisabled
938
858
  ? "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.",
859
+ : "Configure the Executor and Coordinator profiles and choose whether Freeflow may manage model state for this repository.",
940
860
  kind: "group" as const,
941
861
  value: cognitiveRoutingRuntimeDisabled ? false : (cognitiveRoutingState?.enabled ?? false),
942
862
  inactive: freeflowInactive,
943
863
  displaySuffix: cognitiveRoutingStatus,
944
- children: [cognitiveRoutingEnabledItem, ...cognitiveRoutingProfiles, ...cognitiveRoutingSessionStart],
864
+ children: [cognitiveRoutingEnabledItem, ...cognitiveRoutingProfiles, cognitiveRoutingProjectionItem],
945
865
  } satisfies SettingsItem;
946
866
  })();
947
867
 
@@ -1051,7 +971,7 @@ async function updateConfig(
1051
971
  }
1052
972
 
1053
973
  function isCognitiveRoutingProfileItem(item: SettingsItem): boolean {
1054
- return item.id === "freeflow.cognitiveRouting.standard" || item.id === "freeflow.cognitiveRouting.reasoning";
974
+ return item.id === "freeflow.cognitiveRouting.executor" || item.id === "freeflow.cognitiveRouting.coordinator";
1055
975
  }
1056
976
 
1057
977
  function valueForDisplay(item: SettingsItem): string {
@@ -1096,8 +1016,8 @@ function refreshSettingsDerivedState(items: SettingsItem[]) {
1096
1016
  const cognitiveRoutingGroup = findSettingsItem(items, "freeflow.cognitiveRouting");
1097
1017
  const cognitiveRoutingEnabledItem = findSettingsItem(items, "freeflow.cognitiveRouting.enabled");
1098
1018
  const cognitiveRoutingProfiles = [
1099
- findSettingsItem(items, "freeflow.cognitiveRouting.standard"),
1100
- findSettingsItem(items, "freeflow.cognitiveRouting.reasoning"),
1019
+ findSettingsItem(items, "freeflow.cognitiveRouting.executor"),
1020
+ findSettingsItem(items, "freeflow.cognitiveRouting.coordinator"),
1101
1021
  ];
1102
1022
 
1103
1023
  if (cognitiveRoutingGroup && cognitiveRoutingEnabledItem) {
@@ -1447,7 +1367,10 @@ export async function handleFreeflowCommand(
1447
1367
  const result =
1448
1368
  value === "auto"
1449
1369
  ? await cognitiveRoutingController.setAutomaticControl("profile-settings")
1450
- : await cognitiveRoutingController.setManualProfile(value as "standard" | "reasoning", "profile-settings");
1370
+ : await cognitiveRoutingController.setManualProfile(
1371
+ value as "executor" | "coordinator",
1372
+ "profile-settings",
1373
+ );
1451
1374
  if ((result.status !== "automatic" && result.status !== "active") || result.reason) {
1452
1375
  ctx.ui.notify(
1453
1376
  `Cognitive Routing settings could not be applied: ${result.reason ?? result.status}.`,