@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
@@ -1,9 +1,11 @@
1
+ import { annotateSources } from "../session-sources/provenance.js";
1
2
  import { randomUUID } from "node:crypto";
2
- import { Sources, textRef, isTaskEvidence, type Source } from "../session-sources/sources.js";
3
+ import { Sources, textRef, type Source } from "../session-sources/sources.js";
3
4
  import {
4
5
  canonical,
5
6
  emptySelection,
6
7
  idFor,
8
+ isWorkerProfile,
7
9
  requireCondition as check,
8
10
  type Pair,
9
11
  type Problem,
@@ -136,7 +138,7 @@ export function prepareView(options: {
136
138
  if (!item.source) continue;
137
139
  if (
138
140
  !selective ||
139
- item.source.producer !== "executor" ||
141
+ !isWorkerProfile(item.source.producer) ||
140
142
  admitted.has(item.source.ref) ||
141
143
  (item.source.reportHandoff && state.handoffs.has(item.source.reportHandoff))
142
144
  )
@@ -189,7 +191,7 @@ export function prepareView(options: {
189
191
  if (full.has(source.ref) || source.message.role !== "toolResult") return message;
190
192
  return {
191
193
  ...structuredClone(source.message),
192
- content: [{ type: "text", text: "[Executor result omitted from this view]" }],
194
+ content: [{ type: "text", text: "[Worker result omitted from this view]" }],
193
195
  details: undefined,
194
196
  };
195
197
  };
@@ -226,39 +228,28 @@ export function prepareView(options: {
226
228
  }
227
229
  while (cursor < historical.length) emit(historical[cursor++]);
228
230
  const fullSources = [...full.values()];
229
- // Stable provenance belongs with every represented occurrence, not a rolling
230
- // catalog. Insert before whole exchanges, never between native calls/results.
231
- const annotated: any[] = [];
232
- for (let i = 0; i < messages.length;) {
233
- const group = [messages[i++]];
234
- while (i < messages.length && messages[i].role === "toolResult") group.push(messages[i++]);
235
- const rows = group.flatMap((message) => {
236
- const s = renderedSources.get(message);
237
- if (!s) return [];
238
- const representation = full.has(s.ref) ? "full" : "structural only; omitted result bodies are not evidence";
239
- const selection =
240
- s.producer === "executor" && isTaskEvidence(s) && full.has(s.ref)
241
- ? "task evidence; selection checks apply"
242
- : "not offered for new evidence selection";
243
- return [
244
- `${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)}` : ""}`,
245
- ];
246
- });
247
- if (rows.length)
248
- annotated.push({
249
- role: "custom",
250
- customType: "freeflow-routing-v2-refs",
251
- display: false,
252
- content: `Source provenance for the following message/exchange:\n${rows.join("\n")}`,
253
- details: { routingInstance: options.instance },
254
- timestamp: 0,
255
- });
256
- annotated.push(...group);
257
- }
258
- messages = annotated;
231
+ messages = annotateSources(messages, renderedSources, new Set(full.keys()), sources, options.instance);
259
232
  // Restore exact current communication only when its accepted occurrence is absent.
260
233
  const a = state.assignmentId ? state.assignments.get(state.assignmentId) : undefined;
261
- const report = handoff?.kind === "return" ? handoff : undefined;
234
+ const baseReport = assessment
235
+ ? state.handoffs.get(assessment.handoffId)
236
+ : handoff?.kind === "return"
237
+ ? handoff
238
+ : undefined;
239
+ const recovery = state.recoveryId
240
+ ? state.recoveries.get(state.recoveryId)
241
+ : [...state.recoveries.values()]
242
+ .reverse()
243
+ .find(
244
+ (candidate) => candidate.assessmentHandoffId === assessment?.handoffId && candidate.state === "completed",
245
+ );
246
+ const recoveryRequest = recovery ? state.handoffs.get(recovery.requestHandoffId) : undefined;
247
+ const supplement =
248
+ handoff?.kind === "recovery-return"
249
+ ? handoff
250
+ : recovery?.supplementHandoffId
251
+ ? state.handoffs.get(recovery.supplementHandoffId)
252
+ : undefined;
262
253
  const hasCommunication = (h: any, field: string, value: string, metadata: Record<string, any> = {}) =>
263
254
  messages.some((m) => {
264
255
  const matches = (payload: any) =>
@@ -290,22 +281,41 @@ export function prepareView(options: {
290
281
  if (accepted && !hasCommunication(accepted, "contract", a.contract))
291
282
  restore("Current exact assignment", a.id, a.contract);
292
283
  }
293
- if (report) {
294
- const metadata = { outcome: report.outcome, limitations: report.limitations };
295
- const hasBody = hasCommunication(report, "report", report.text);
284
+ if (baseReport) {
285
+ const metadata = { outcome: baseReport.outcome, limitations: baseReport.limitations };
286
+ const hasBody = hasCommunication(baseReport, "report", baseReport.text);
296
287
  // Tool arguments establish accepted content but do not carry harness-assigned
297
288
  // revision/lineage. Old receipts and partially retained calls need metadata too.
298
- const complete = hasCommunication(report, "report", report.text, {
289
+ const complete = hasCommunication(baseReport, "report", baseReport.text, {
299
290
  ...metadata,
300
- reportRevision: report.reportRevision,
301
- assignmentRef: `assignment:${report.assignmentId}`,
302
- reportRef: `report:${report.id}:${report.reportRevision}`,
291
+ reportRevision: baseReport.reportRevision,
292
+ assignmentRef: `assignment:${baseReport.assignmentId}`,
293
+ reportRef: `report:${baseReport.id}:${baseReport.reportRevision}`,
303
294
  });
304
295
  if (!complete)
305
296
  restore(
306
297
  "Saved report",
307
- report.id,
308
- `Producer: executor\nAssignment: assignment:${report.assignmentId}\nReport ref: report:${report.id}:${report.reportRevision}\nRevision: ${report.reportRevision}\nOutcome: ${report.outcome}\nLimitations: ${JSON.stringify(report.limitations)}${hasBody ? "\nReport text is present in its accepted native occurrence above." : `\nReport:\n${report.text}`}`,
298
+ baseReport.id,
299
+ `Producer: ${baseReport.from}\nAssignment: assignment:${baseReport.assignmentId}\nReport ref: report:${baseReport.id}:${baseReport.reportRevision}\nRevision: ${baseReport.reportRevision}\nOutcome: ${baseReport.outcome}\nLimitations: ${JSON.stringify(baseReport.limitations)}${hasBody ? "\nReport text is present in its accepted native occurrence above." : `\nReport:\n${baseReport.text}`}`,
300
+ );
301
+ }
302
+ if (recovery && recoveryRequest && !hasCommunication(recoveryRequest, "request", recovery.request))
303
+ restore(
304
+ "Recovery request",
305
+ recovery.id,
306
+ `Assignment: assignment:${recovery.assignmentId}\nParent report: report:${recovery.assessmentHandoffId}:${recovery.baseReportRevision}\nAllowed paths: ${JSON.stringify(recovery.paths)}\nRequest:\n${recovery.request}`,
307
+ );
308
+ if (recovery && supplement) {
309
+ const hasBody = hasCommunication(supplement, "report", supplement.text);
310
+ const complete = hasCommunication(supplement, "report", supplement.text, {
311
+ outcome: supplement.outcome,
312
+ limitations: supplement.limitations,
313
+ });
314
+ if (!complete)
315
+ restore(
316
+ "Recovery supplement",
317
+ recovery.id,
318
+ `Assignment: assignment:${recovery.assignmentId}\nParent report: report:${recovery.assessmentHandoffId}:${recovery.baseReportRevision}\nSupplement revision: ${supplement.reportRevision}\nOutcome: ${supplement.outcome}\nLimitations: ${JSON.stringify(supplement.limitations)}${hasBody ? "\nSupplement text is present in its accepted native occurrence above." : `\nSupplement:\n${supplement.text}`}`,
309
319
  );
310
320
  }
311
321
  messages.push(options.runtimeMessage);
@@ -12,10 +12,20 @@ const value = (v: unknown) => (typeof v === "string" ? v : v === undefined ? ""
12
12
  const text = (v: unknown) => (typeof v === "string" ? v : "");
13
13
  const count = (n: number, word: string) => `${n} ${word}${n === 1 ? "" : "s"}`;
14
14
  const capital = (v: string) => (v ? v[0].toUpperCase() + v.slice(1) : "");
15
+ const locatorText = (locator: any) => {
16
+ if (!locator) return "";
17
+ const identity = `${locator.toolCallId ?? "unknown call"} (${locator.callRef ?? "unknown source"})`;
18
+ return locator.label ? `${locator.label}${locator.truncated ? " [truncated]" : ""} · ${identity}` : identity;
19
+ };
15
20
 
16
21
  function title(name: string, operation?: string): string {
17
- if (name === "freeflow_delegate") return operation === "replace" ? "Replace assignment" : "Delegate to Executor";
18
- if (name === "freeflow_return") return operation === "retry" ? "Retry return" : "Return to Coordinator";
22
+ if (name === "freeflow_delegate") return operation === "replace" ? "Replace assignment" : "Delegate assignment";
23
+ if (name === "freeflow_return")
24
+ return operation === "retry"
25
+ ? "Retry return"
26
+ : operation === "supplement"
27
+ ? "Return recovery supplement"
28
+ : "Return to Coordinator";
19
29
  if (name === "freeflow_project")
20
30
  return (
21
31
  (
@@ -34,6 +44,8 @@ function title(name: string, operation?: string): string {
34
44
  inspect: "Inspect routing",
35
45
  close: "Close unit",
36
46
  assess: "Resume assessment",
47
+ recover: "Recover assessment evidence",
48
+ "cancel-recovery": "Cancel evidence recovery",
37
49
  status: "Routing status",
38
50
  history: "Routing history",
39
51
  } as Record<string, string>
@@ -47,6 +59,7 @@ function draft(name: string, args: any, expanded: boolean): string {
47
59
  if (name === "freeflow_delegate") sections.push(text(args.contract));
48
60
  if (name === "freeflow_return") sections.push(text(args.report));
49
61
  if (name === "freeflow_unit" && args.operation === "close") sections.push(text(args.assessment));
62
+ if (name === "freeflow_unit" && args.operation === "recover") sections.push(text(args.request));
50
63
  // Metadata can arrive before the main text and otherwise occupy the entire
51
64
  // trailing preview forever. Keep the live prose moving; expansion keeps all fields.
52
65
  if (!expanded && sections.length) return sections.filter(Boolean).join("\n\n");
@@ -71,7 +84,9 @@ export function renderRoutingCall(name: string, args: any = {}, context: RenderC
71
84
  name === "freeflow_delegate"
72
85
  ? "assignment"
73
86
  : name === "freeflow_return"
74
- ? "report"
87
+ ? args.operation === "supplement"
88
+ ? "supplement"
89
+ : "report"
75
90
  : name === "freeflow_unit" && args.operation === "close"
76
91
  ? "assessment"
77
92
  : undefined;
@@ -98,8 +113,8 @@ function summary(receipt: any, name: string, args: any): string {
98
113
  const issue = problems.length
99
114
  ? `${count(problems.length, "evidence issue")} ${problems.length === 1 ? "needs" : "need"} correction`
100
115
  : "Evidence needs correction";
101
- if (receipt.reportSaved)
102
- return `${operation === "retry" ? "Saved report retained" : "Report saved"}${receipt.ready === false ? ` · ${issue}` : ""}`;
116
+ if (receipt.reportSaved || receipt.supplementSaved)
117
+ return `${operation === "retry" ? (receipt.supplementSaved ? "Saved supplement retained" : "Saved report retained") : receipt.supplementSaved ? "Recovery supplement saved" : "Report saved"}${receipt.ready === false ? ` · ${issue}` : ""}`;
103
118
  if (receipt.status === "blocked" || receipt.status === "rejected") {
104
119
  const reasons: Record<string, string> = {
105
120
  wrong_profile: "This operation belongs to the other profile",
@@ -138,6 +153,8 @@ function summary(receipt: any, name: string, args: any): string {
138
153
  }
139
154
  if (name === "freeflow_unit") {
140
155
  if (operation === "close") return capital(receipt.outcome ?? "closed");
156
+ if (operation === "recover") return "Recovery request saved";
157
+ if (operation === "cancel-recovery") return "Evidence recovery cancelled";
141
158
  if (operation === "assess")
142
159
  return receipt.status === "unchanged" ? "Assessment already active" : "Evidence prepared";
143
160
  if (receipt.view === "history")
@@ -180,7 +197,7 @@ export function renderRoutingResult(
180
197
  `Evidence revision ${facts.revision ?? "unknown"}`,
181
198
  ...(facts.selected ?? []).map(
182
199
  (s: any) =>
183
- `${s.ref} · ${s.kind}${s.producer ? ` · ${s.producer}` : ""}${s.assignment ? ` · assignment ${s.assignment}` : ""}${s.toolName ? ` · ${s.toolName}` : ""}`,
200
+ `${s.ref} · ${s.kind}${s.producer ? ` · ${s.producer}` : ""}${s.assignment ? ` · assignment ${s.assignment}` : ""}${s.toolName ? ` · ${s.toolName}` : ""}${s.locator ? ` · ${locatorText(s.locator)}` : ""}`,
184
201
  ),
185
202
  ...(facts.withdrawals ?? []).map((w: any) => `Withdrawn ${w.ref}: ${w.reason}`),
186
203
  ...(facts.unresolved ?? []).map((p: any) => `Unresolved ${p.ref}: ${p.detail}`),
@@ -195,9 +212,11 @@ export function renderRoutingResult(
195
212
  : "",
196
213
  receipt.report
197
214
  ? `Saved report\n${receipt.report}`
198
- : args.report
199
- ? `${receipt.reportSaved ? "Saved" : "Submitted"} report\n${args.report}`
200
- : "",
215
+ : receipt.supplement
216
+ ? `Saved recovery supplement\n${receipt.supplement}`
217
+ : args.report
218
+ ? `${receipt.supplementSaved ? "Saved recovery supplement" : receipt.reportSaved ? "Saved report" : "Submitted report"}\n${args.report}`
219
+ : "",
201
220
  receipt.assessment ? `Assessment\n${value(receipt.assessment)}` : args.assessment,
202
221
  evidence,
203
222
  receipt.items
@@ -217,7 +236,7 @@ export function renderRoutingResult(
217
236
  receipt.candidates
218
237
  ?.map(
219
238
  (item: any) =>
220
- `${item.ref} · ${item.kind} · ${item.producer}${item.toolName ? ` · ${item.toolName}` : ""}${item.assignment ? ` · assignment ${item.assignment}` : ""}\n${item.selected ? "Selected" : "Not selected"} · ${item.active ? "Active" : "Historical"} · ${item.eligible ? "Eligible" : "Ineligible"} · ${item.targetReady ? "No known item target gap" : "Target needs attention"}${item.retainedSelection ? "\nPreviously selected routing source retained; unavailable for new selections." : ""}\n${item.limitations.map((p: any) => `${p.code}: ${p.detail}`).join("\n")}`,
239
+ `${item.ref} · ${item.kind} · ${item.producer}${item.toolName ? ` · ${item.toolName}` : ""}${item.assignment ? ` · assignment ${item.assignment}` : ""}${item.locator ? ` · ${locatorText(item.locator)}` : ""}\n${item.selected ? "Selected" : "Not selected"} · ${item.active ? "Active" : "Historical"} · ${item.eligible ? "Eligible" : "Ineligible"} · ${item.targetReady ? "No known item target gap" : "Target needs attention"}${item.retainedSelection ? "\nPreviously selected routing source retained; unavailable for new selections." : ""}\n${item.limitations.map((p: any) => `${p.code}: ${p.detail}`).join("\n")}`,
221
240
  )
222
241
  .join("\n\n"),
223
242
  receipt.ref ? `Work ref: ${receipt.ref}` : "",
@@ -233,9 +252,11 @@ export function renderRoutingResult(
233
252
  "unit",
234
253
  "assignment",
235
254
  "assignmentRef",
255
+ "recovery",
236
256
  "handoff",
237
257
  "revision",
238
258
  "reportRevision",
259
+ "supplementRevision",
239
260
  "outcome",
240
261
  "stage",
241
262
  "transition",