@hassangameryt/freeflow 0.7.2 → 0.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) 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 +147 -127
  6. package/capabilities/cognitive-routing/SKILL.md +162 -93
  7. package/gemini-extension.json +1 -1
  8. package/package.json +1 -1
  9. package/pi-extension/dist/cognitive-routing-v2/config.js +35 -19
  10. package/pi-extension/dist/cognitive-routing-v2/projection.js +53 -44
  11. package/pi-extension/dist/cognitive-routing-v2/render.js +31 -10
  12. package/pi-extension/dist/cognitive-routing-v2/runtime.js +701 -105
  13. package/pi-extension/dist/cognitive-routing-v2/schemas.js +90 -24
  14. package/pi-extension/dist/cognitive-routing-v2/state.js +243 -27
  15. package/pi-extension/dist/cognitive-routing-v2/tools.js +19 -50
  16. package/pi-extension/dist/cognitive-routing-v2/types.js +5 -1
  17. package/pi-extension/dist/context-virtualization/runtime.js +29 -7
  18. package/pi-extension/dist/freeflow-context/tool.js +7 -6
  19. package/pi-extension/dist/index.js +36 -15
  20. package/pi-extension/dist/provider-support/astra/adapter.js +110 -0
  21. package/pi-extension/dist/provider-support/astra/history.js +125 -0
  22. package/pi-extension/dist/provider-support/astra/index.js +11 -0
  23. package/pi-extension/dist/provider-support/astra/session-state.js +114 -0
  24. package/pi-extension/dist/provider-support/index.js +4 -0
  25. package/pi-extension/dist/runtime/request-history.js +216 -0
  26. package/pi-extension/dist/runtime/runtime-context.js +29 -10
  27. package/pi-extension/dist/session-sources/events.js +22 -0
  28. package/pi-extension/dist/session-sources/provenance.js +32 -0
  29. package/pi-extension/dist/session-sources/sources.js +51 -7
  30. package/pi-extension/dist/settings/settings-ui.js +247 -47
  31. package/pi-extension/src/cognitive-routing-v2/config.ts +49 -19
  32. package/pi-extension/src/cognitive-routing-v2/projection.ts +53 -43
  33. package/pi-extension/src/cognitive-routing-v2/render.ts +31 -10
  34. package/pi-extension/src/cognitive-routing-v2/runtime.ts +712 -105
  35. package/pi-extension/src/cognitive-routing-v2/schemas.ts +95 -24
  36. package/pi-extension/src/cognitive-routing-v2/state.ts +244 -27
  37. package/pi-extension/src/cognitive-routing-v2/tools.ts +19 -50
  38. package/pi-extension/src/cognitive-routing-v2/types.ts +35 -3
  39. package/pi-extension/src/context-virtualization/runtime.ts +30 -8
  40. package/pi-extension/src/freeflow-context/tool.ts +7 -6
  41. package/pi-extension/src/index.ts +36 -14
  42. package/pi-extension/src/provider-support/astra/adapter.ts +108 -0
  43. package/pi-extension/src/provider-support/astra/history.ts +145 -0
  44. package/pi-extension/src/provider-support/astra/index.ts +12 -0
  45. package/pi-extension/src/provider-support/astra/session-state.ts +113 -0
  46. package/pi-extension/src/provider-support/index.ts +5 -0
  47. package/pi-extension/src/runtime/request-history.ts +243 -0
  48. package/pi-extension/src/runtime/runtime-context.ts +32 -10
  49. package/pi-extension/src/session-sources/events.ts +22 -0
  50. package/pi-extension/src/session-sources/provenance.ts +39 -0
  51. package/pi-extension/src/session-sources/sources.ts +77 -6
  52. package/pi-extension/src/settings/settings-ui.ts +276 -46
  53. package/pi-extension/tests/cache-reuse/history.test.js +117 -0
  54. package/pi-extension/tests/cache-reuse/native.test.js +91 -0
  55. package/pi-extension/tests/cache-reuse/settings.test.js +211 -0
  56. package/pi-extension/tests/cognitive-routing-v2/communication.test.js +94 -1
  57. package/pi-extension/tests/cognitive-routing-v2/delegation.test.js +557 -0
  58. package/pi-extension/tests/cognitive-routing-v2/native.test.js +34 -1
  59. package/pi-extension/tests/cognitive-routing-v2/persistence.test.js +55 -0
  60. package/pi-extension/tests/cognitive-routing-v2/recovery.test.js +800 -2
  61. package/pi-extension/tests/cognitive-routing-v2/render.test.js +15 -1
  62. package/pi-extension/tests/cognitive-routing-v2/runtime.test.js +273 -3
  63. package/pi-extension/tests/cognitive-routing-v2/state.test.js +312 -1
  64. package/pi-extension/tests/context-virtualization/extension.test.js +20 -10
  65. package/pi-extension/tests/context-virtualization/runtime.test.js +17 -3
  66. package/pi-extension/tests/fixtures/routing-native.js +4 -1
  67. package/pi-extension/tests/integration/mode-free-core.test.js +4 -3
  68. package/pi-extension/tests/integration/pi-extension.test.js +508 -29
  69. package/pi-extension/tests/integration/prompt-architecture.test.js +56 -40
  70. package/pi-extension/tests/integration/unaffected-regressions.test.js +3 -3
  71. package/pi-extension/tests/provider-support/astra-boundaries.test.js +75 -0
  72. package/pi-extension/tests/provider-support/astra.test.js +306 -0
  73. package/pi-extension/tests/provider-support/cli.test.js +92 -0
  74. package/pi-extension/tests/provider-support/native.test.js +183 -0
  75. package/plugin.json +1 -1
  76. package/runtime/prompts/cognitive-routing.md +4 -4
  77. package/skills/design-for-depth/SKILL.md +1 -1
  78. package/skills/design-for-depth/references/implementation-decisions.md +3 -3
  79. package/skills/discuss/SKILL.md +2 -2
@@ -0,0 +1,39 @@
1
+ import { isWorkerProfile } from "../cognitive-routing-v2/types.js";
2
+ import { isTaskEvidence, textRef, type Sources, type Source } from "./sources.js";
3
+
4
+ export function annotateSources(
5
+ messages: any[],
6
+ renderedSources: Map<any, Source>,
7
+ full: Set<string>,
8
+ sources: Sources,
9
+ instance: string,
10
+ ): any[] {
11
+ const annotated: any[] = [];
12
+ for (let i = 0; i < messages.length;) {
13
+ const group = [messages[i++]];
14
+ while (i < messages.length && messages[i].role === "toolResult") group.push(messages[i++]);
15
+ const rows = group.flatMap((message) => {
16
+ const s = renderedSources.get(message);
17
+ if (!s) return [];
18
+ const representation = full.has(s.ref) ? "full" : "structural only; omitted result bodies are not evidence";
19
+ const selection =
20
+ isWorkerProfile(s.producer) && isTaskEvidence(s) && full.has(s.ref)
21
+ ? "task evidence; selection checks apply"
22
+ : "not offered for new evidence selection";
23
+ return [
24
+ `${s.ref} | producer: ${s.producer === "common" ? "unknown/common (no observed routing profile)" : s.producer} | ${s.original ? "assistant-text" : s.message.role}${s.message.toolName ? ` | ${s.message.toolName}` : ""}${s.assignmentId ? ` | assignment: ${s.assignmentId}` : ""} | ${representation} | ${selection}${!s.original && full.has(s.ref) && sources.byRef.has(textRef(s.ref)) ? ` | visible text: ${textRef(s.ref)}` : ""}`,
25
+ ];
26
+ });
27
+ annotated.push(...group);
28
+ if (rows.length)
29
+ annotated.push({
30
+ role: "custom",
31
+ customType: "freeflow-routing-v2-refs",
32
+ display: false,
33
+ content: `Source provenance for the preceding message/exchange:\n${rows.join("\n")}`,
34
+ details: { routingInstance: instance },
35
+ timestamp: 0,
36
+ });
37
+ }
38
+ return annotated;
39
+ }
@@ -3,6 +3,7 @@ import { sessionEntryToContextMessages } from "@earendil-works/pi-coding-agent";
3
3
  import {
4
4
  canonical,
5
5
  idFor,
6
+ isWorkerProfile,
6
7
  refFor,
7
8
  type NativeEntry,
8
9
  type State,
@@ -10,6 +11,12 @@ import {
10
11
  type Problem,
11
12
  } from "../cognitive-routing-v2/types.js";
12
13
 
14
+ export interface EvidenceLocator {
15
+ callRef: string;
16
+ toolCallId: string;
17
+ label?: string;
18
+ truncated?: boolean;
19
+ }
13
20
  export interface Source {
14
21
  ref: string;
15
22
  entry: NativeEntry;
@@ -22,6 +29,15 @@ export interface Source {
22
29
  original?: Source;
23
30
  reportHandoff?: string;
24
31
  }
32
+ export interface NativeToolCall {
33
+ id: string;
34
+ name: string;
35
+ arguments?: unknown;
36
+ }
37
+ export type EvidenceLocatorProvider = (
38
+ assistant: Source,
39
+ call: NativeToolCall,
40
+ ) => Pick<EvidenceLocator, "label" | "truncated"> | undefined;
25
41
  export interface Associated {
26
42
  message: any;
27
43
  source?: Source;
@@ -45,6 +61,31 @@ interface Exchange {
45
61
  results: Map<string, Source[]>;
46
62
  }
47
63
  const callKey = (id: string, name: string) => JSON.stringify([id, name]);
64
+ const locatorLimit = 160;
65
+
66
+ function normalizeLabel(value: unknown): Pick<EvidenceLocator, "label" | "truncated"> | undefined {
67
+ if (typeof value !== "string") return undefined;
68
+ const clean = value
69
+ .replace(/[\u0000-\u001f\u007f]/g, " ")
70
+ .replace(/\s+/g, " ")
71
+ .trim();
72
+ if (!clean) return undefined;
73
+ if (clean.length <= locatorLimit) return { label: clean, truncated: false };
74
+ return { label: `${clean.slice(0, locatorLimit - 3)}...`, truncated: true };
75
+ }
76
+
77
+ function locatorForCall(
78
+ assistant: Source,
79
+ call: NativeToolCall,
80
+ locatorProvider?: EvidenceLocatorProvider,
81
+ ): EvidenceLocator | undefined {
82
+ if (typeof assistant.ref !== "string" || typeof call.id !== "string" || typeof call.name !== "string")
83
+ return undefined;
84
+ const locator: EvidenceLocator = { callRef: assistant.ref, toolCallId: call.id };
85
+ const details = locatorProvider?.(assistant, call);
86
+ const label = normalizeLabel(details?.label);
87
+ return label ? { ...locator, ...label, truncated: details?.truncated === true || label.truncated } : locator;
88
+ }
48
89
 
49
90
  export class Sources {
50
91
  readonly byRef = new Map<string, Source>();
@@ -56,7 +97,14 @@ export class Sources {
56
97
  private associated?: { messages: readonly any[]; items: Associated[] };
57
98
  entries: readonly NativeEntry[] = [];
58
99
  private activeIds?: ReadonlySet<string>;
59
- constructor(entries: readonly NativeEntry[], state: State, activeIds?: ReadonlySet<string>) {
100
+ private readonly locatorProvider?: EvidenceLocatorProvider;
101
+ constructor(
102
+ entries: readonly NativeEntry[],
103
+ state: State,
104
+ activeIds?: ReadonlySet<string>,
105
+ locatorProvider?: EvidenceLocatorProvider,
106
+ ) {
107
+ this.locatorProvider = locatorProvider;
60
108
  this.refresh(entries, state, activeIds);
61
109
  }
62
110
  refresh(entries: readonly NativeEntry[], state: State, activeIds?: ReadonlySet<string>): void {
@@ -89,7 +137,11 @@ export class Sources {
89
137
  if (block.type === "text") {
90
138
  try {
91
139
  const receipt = JSON.parse(block.text);
92
- if (receipt.reportSaved && typeof receipt.report === "string" && typeof receipt.handoff === "string")
140
+ if (
141
+ ((receipt.reportSaved && typeof receipt.report === "string") ||
142
+ (receipt.supplementSaved && typeof receipt.supplement === "string")) &&
143
+ typeof receipt.handoff === "string"
144
+ )
93
145
  source.reportHandoff = receipt.handoff;
94
146
  } catch {}
95
147
  }
@@ -153,7 +205,15 @@ export class Sources {
153
205
  this.ambiguous.clear();
154
206
  for (const source of this.byRef.values()) source.active = false;
155
207
  const used = new Set<string>(),
156
- hashes = messages.map(bodyHash),
208
+ hashes = messages.map((message) => {
209
+ const direct = bodyHash(message);
210
+ if (this.byBody.has(direct) || message?.role !== "toolResult" || !Array.isArray(message.content)) return direct;
211
+ const last = message.content.at(-1);
212
+ if (last?.type !== "text") return direct;
213
+ const original = bodyHash({ ...message, content: message.content.slice(0, -1) });
214
+ // Freeflow's stable reference decoration does not change the native evidence body.
215
+ return this.byBody.get(original)?.some((s) => last.text === `[context-ref: ${s.ref}]`) ? original : direct;
216
+ }),
157
217
  remaining = new Map<string, number>();
158
218
  for (const hash of hashes) remaining.set(hash, (remaining.get(hash) ?? 0) + 1);
159
219
  const items = messages.map((message, index) => {
@@ -192,13 +252,13 @@ export class Sources {
192
252
  code: "source_changed",
193
253
  detail: "The captured source changed; reconcile evidence before delivery.",
194
254
  };
195
- if (source.producer !== "executor")
196
- return { ref, code: "source_origin", detail: "Selection requires observed Executor attribution." };
255
+ if (!isWorkerProfile(source.producer))
256
+ return { ref, code: "source_origin", detail: "Selection requires observed worker attribution." };
197
257
  if (state.exposure.get(original.ref) !== original.hash && state.exposure.get(source.ref) !== source.hash)
198
258
  return {
199
259
  ref,
200
260
  code: "source_unexposed",
201
- detail: "The captured body has not been fully exposed to Executor on this ancestry.",
261
+ detail: "The captured body has not been fully exposed to a worker on this ancestry.",
202
262
  };
203
263
  return undefined;
204
264
  }
@@ -252,6 +312,17 @@ export class Sources {
252
312
  }
253
313
  return { sources, problems };
254
314
  }
315
+ locator(ref: string): EvidenceLocator | undefined {
316
+ const source = this.byRef.get(ref);
317
+ if (!source || source.original || source.message.role !== "toolResult") return undefined;
318
+ const group = this.owners.get(source.ref);
319
+ if (!group) return undefined;
320
+ const calls = group.assistant.message.content?.filter((b: any) => b.type === "toolCall") ?? [];
321
+ const matches = calls.filter(
322
+ (call: any) => call.id === source.message.toolCallId && call.name === source.message.toolName,
323
+ );
324
+ return matches.length === 1 ? locatorForCall(group.assistant, matches[0], this.locatorProvider) : undefined;
325
+ }
255
326
  ordered(sources: Iterable<Source>): Source[] {
256
327
  const ids = new Set([...sources].map((s) => s.ref));
257
328
  return this.entries.flatMap((entry) =>