@hassangameryt/freeflow 0.7.0 → 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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +2 -1
- package/.codex-plugin/plugin.json +2 -2
- package/.cursor-plugin/plugin.json +25 -0
- package/README.md +57 -26
- package/capabilities/cognitive-routing/SKILL.md +94 -311
- package/com.github.copilot/hooks/hooks.json +11 -0
- package/command-surface.json +7 -1
- package/gemini-extension.json +5 -0
- package/hooks/adapters/claude-session-start.mjs +16 -0
- package/hooks/adapters/codex-session-start.mjs +16 -0
- package/hooks/adapters/copilot-session-start.mjs +13 -0
- package/hooks/adapters/cursor-session-start.mjs +13 -0
- package/hooks/adapters/gemini-session-start.mjs +15 -0
- package/hooks/claude/hooks.json +18 -0
- package/hooks/codex/hooks.json +18 -0
- package/hooks/cursor/hooks.json +9 -0
- package/hooks/freeflow-runtime-context.mjs +3 -215
- package/hooks/hooks.json +4 -5
- package/hooks/shared/runtime-context.mjs +210 -0
- package/hooks/tests/check-host-adapters.test.mjs +82 -0
- package/hooks/tests/check-runtime-context-hook.sh +34 -3
- package/package.json +20 -6
- package/pi-extension/dist/cognitive-routing-v2/budget.js +42 -0
- package/pi-extension/dist/cognitive-routing-v2/config.js +120 -0
- package/pi-extension/dist/cognitive-routing-v2/projection.js +321 -0
- package/pi-extension/dist/cognitive-routing-v2/render.js +231 -0
- package/pi-extension/dist/cognitive-routing-v2/runtime.js +1585 -0
- package/pi-extension/dist/cognitive-routing-v2/schemas.js +107 -0
- package/pi-extension/dist/cognitive-routing-v2/state.js +612 -0
- package/pi-extension/dist/cognitive-routing-v2/tools.js +91 -0
- package/pi-extension/dist/cognitive-routing-v2/types.js +44 -0
- package/pi-extension/dist/freeflow-context/tool.js +5 -0
- package/pi-extension/dist/index.js +279 -624
- package/pi-extension/dist/runtime/runtime-context.js +63 -13
- package/pi-extension/dist/session-sources/events.js +195 -0
- package/pi-extension/dist/session-sources/read-only-session.js +148 -0
- package/pi-extension/dist/session-sources/sources.js +233 -0
- package/pi-extension/dist/settings/settings-ui.js +44 -117
- package/pi-extension/src/cognitive-routing-v2/budget.ts +42 -0
- package/pi-extension/src/cognitive-routing-v2/config.ts +143 -0
- package/pi-extension/src/cognitive-routing-v2/projection.ts +365 -0
- package/pi-extension/src/cognitive-routing-v2/render.ts +256 -0
- package/pi-extension/src/cognitive-routing-v2/runtime.ts +1634 -0
- package/pi-extension/src/cognitive-routing-v2/schemas.ts +109 -0
- package/pi-extension/src/cognitive-routing-v2/state.ts +622 -0
- package/pi-extension/src/cognitive-routing-v2/tools.ts +93 -0
- package/pi-extension/src/cognitive-routing-v2/types.ts +222 -0
- package/pi-extension/src/freeflow-context/tool.ts +5 -0
- package/pi-extension/src/index.ts +285 -674
- package/pi-extension/src/runtime/runtime-context.ts +75 -14
- package/pi-extension/src/session-sources/events.ts +208 -0
- package/pi-extension/src/session-sources/read-only-session.ts +170 -0
- package/pi-extension/src/session-sources/sources.ts +263 -0
- package/pi-extension/src/settings/settings-ui.ts +54 -131
- package/pi-extension/tests/cognitive-routing-v2/communication.test.js +484 -0
- package/pi-extension/tests/cognitive-routing-v2/compaction-input.test.js +183 -0
- package/pi-extension/tests/cognitive-routing-v2/native.test.js +441 -0
- package/pi-extension/tests/cognitive-routing-v2/performance.test.js +87 -0
- package/pi-extension/tests/cognitive-routing-v2/persistence.test.js +245 -0
- package/pi-extension/tests/cognitive-routing-v2/projection.test.js +241 -0
- package/pi-extension/tests/cognitive-routing-v2/recovery.test.js +236 -0
- package/pi-extension/tests/cognitive-routing-v2/render.test.js +230 -0
- package/pi-extension/tests/cognitive-routing-v2/runtime.test.js +153 -0
- package/pi-extension/tests/cognitive-routing-v2/stabilization.test.js +521 -0
- package/pi-extension/tests/cognitive-routing-v2/state.test.js +275 -0
- package/pi-extension/tests/context-virtualization/extension.test.js +16 -0
- package/pi-extension/tests/fixtures/routing-native.js +188 -0
- package/pi-extension/tests/integration/mode-free-core.test.js +3 -2
- package/pi-extension/tests/integration/pi-extension.test.js +154 -37
- package/pi-extension/tests/integration/prompt-architecture.test.js +87 -6
- package/pi-extension/tests/integration/unaffected-regressions.test.js +77 -10
- package/plugin.json +33 -0
- package/runtime/prompts/cognitive-routing.md +4 -5
- package/runtime/prompts/core.md +51 -45
- package/runtime/prompts/interaction-contract.md +5 -5
- package/skills/action-selection/SKILL.md +57 -97
- package/skills/design-for-depth/SKILL.md +58 -193
- package/skills/design-for-depth/references/implementation-decisions.md +76 -0
- package/skills/diagnose-failure/SKILL.md +64 -196
- package/skills/discuss/SKILL.md +64 -123
- package/skills/discuss/references/discussion-continuity.md +7 -3
- package/skills/execute-work/SKILL.md +69 -88
- package/skills/execute-work/references/execute-work-edges.md +9 -3
- package/skills/handoff/SKILL.md +44 -54
- package/skills/handoff/references/handoff-templates.md +15 -6
- package/skills/review-artifact/SKILL.md +80 -127
- package/skills/review-artifact/references/independent-artifact-reviewer-contract.md +1 -1
- package/skills/review-work/SKILL.md +65 -115
- package/skills/review-work/references/independent-work-reviewer-contract.md +1 -1
- package/skills/simplify-code/SKILL.md +47 -100
- package/skills/track-work/SKILL.md +128 -119
- package/skills/track-work/references/working-record-format.md +14 -2
- package/skills/verify-work/SKILL.md +58 -62
- package/skills/verify-work/references/integration-evidence.md +4 -2
- package/skills/verify-work/references/test-design.md +89 -0
- package/skills/workflow/SKILL.md +73 -119
- package/skills/write-plan/SKILL.md +46 -77
- package/skills/write-plan/references/plan-shapes.md +5 -5
- package/skills/write-spec/SKILL.md +51 -63
- package/skills/write-spec/references/artifact-standards.md +2 -2
- package/skills/write-spec/references/decision-records.md +3 -2
- package/skills/write-spec/references/spec-shapes.md +15 -0
- package/pi-extension/dist/cognitive-routing/commands.js +0 -96
- package/pi-extension/dist/cognitive-routing/config.js +0 -232
- package/pi-extension/dist/cognitive-routing/controller.js +0 -1292
- package/pi-extension/dist/cognitive-routing/history.js +0 -451
- package/pi-extension/dist/cognitive-routing/host.js +0 -188
- package/pi-extension/dist/cognitive-routing/pi-routing-session.js +0 -414
- package/pi-extension/dist/cognitive-routing/pi-session-control.js +0 -320
- package/pi-extension/dist/cognitive-routing/runtime.js +0 -43
- package/pi-extension/dist/cognitive-routing/tool.js +0 -212
- package/pi-extension/dist/cognitive-routing/types.js +0 -6
- package/pi-extension/src/cognitive-routing/commands.ts +0 -129
- package/pi-extension/src/cognitive-routing/config.ts +0 -303
- package/pi-extension/src/cognitive-routing/controller.ts +0 -1628
- package/pi-extension/src/cognitive-routing/history.ts +0 -650
- package/pi-extension/src/cognitive-routing/host.ts +0 -266
- package/pi-extension/src/cognitive-routing/pi-routing-session.ts +0 -538
- package/pi-extension/src/cognitive-routing/pi-session-control.ts +0 -444
- package/pi-extension/src/cognitive-routing/runtime.ts +0 -57
- package/pi-extension/src/cognitive-routing/tool.ts +0 -273
- package/pi-extension/src/cognitive-routing/types.ts +0 -89
- package/pi-extension/tests/cognitive-routing/commands.test.js +0 -134
- package/pi-extension/tests/cognitive-routing/controller.test.js +0 -1128
- package/pi-extension/tests/cognitive-routing/extension-lifecycle.test.js +0 -1142
- package/pi-extension/tests/cognitive-routing/history.test.js +0 -274
- package/pi-extension/tests/cognitive-routing/host-identity.test.js +0 -63
- package/pi-extension/tests/cognitive-routing/pi-normal-lifecycle.test.js +0 -434
- package/pi-extension/tests/cognitive-routing/pi-session-control.test.js +0 -174
- package/pi-extension/tests/cognitive-routing/pi-session-history.test.js +0 -91
- package/pi-extension/tests/cognitive-routing/profile-resolution.test.js +0 -279
- package/pi-extension/tests/cognitive-routing/tool.test.js +0 -230
- package/skills/tdd/SKILL.md +0 -143
- package/skills/tdd/references/test-design.md +0 -96
- /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/
|
|
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(
|
|
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.
|
|
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-
|
|
427
|
-
? "
|
|
428
|
-
: cognitiveRoutingState?.controlMode === "manual-
|
|
429
|
-
? "
|
|
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", "
|
|
440
|
+
values: ["auto", "executor", "coordinator"],
|
|
439
441
|
valueLabels: {
|
|
440
442
|
auto: "automatic",
|
|
441
|
-
|
|
442
|
-
|
|
443
|
+
executor: "manual · executor",
|
|
444
|
+
coordinator: "manual · coordinator",
|
|
443
445
|
},
|
|
444
446
|
valueDescriptions: {
|
|
445
|
-
auto: "Release the manual hold
|
|
446
|
-
|
|
447
|
-
|
|
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.
|
|
521
|
-
COGNITIVE_ROUTING_THINKING_LEVELS.includes(value.
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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,
|
|
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.
|
|
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.
|
|
1100
|
-
findSettingsItem(items, "freeflow.cognitiveRouting.
|
|
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(
|
|
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}.`,
|