@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,6 +1,7 @@
1
+ import { annotateSources } from "../session-sources/provenance.js";
1
2
  import { randomUUID } from "node:crypto";
2
- import { textRef, isTaskEvidence } from "../session-sources/sources.js";
3
- import { canonical, emptySelection, requireCondition as check } from "./types.js";
3
+ import { textRef } from "../session-sources/sources.js";
4
+ import { canonical, emptySelection, isWorkerProfile, requireCondition as check } from "./types.js";
4
5
  import { estimateRequest } from "./budget.js";
5
6
  export function changeSelection(prior, input, sources, state) {
6
7
  check(
@@ -92,7 +93,7 @@ export function prepareView(options) {
92
93
  if (!item.source) continue;
93
94
  if (
94
95
  !selective ||
95
- item.source.producer !== "executor" ||
96
+ !isWorkerProfile(item.source.producer) ||
96
97
  admitted.has(item.source.ref) ||
97
98
  (item.source.reportHandoff && state.handoffs.has(item.source.reportHandoff))
98
99
  )
@@ -145,7 +146,7 @@ export function prepareView(options) {
145
146
  if (full.has(source.ref) || source.message.role !== "toolResult") return message;
146
147
  return {
147
148
  ...structuredClone(source.message),
148
- content: [{ type: "text", text: "[Executor result omitted from this view]" }],
149
+ content: [{ type: "text", text: "[Worker result omitted from this view]" }],
149
150
  details: undefined,
150
151
  };
151
152
  };
@@ -182,39 +183,28 @@ export function prepareView(options) {
182
183
  }
183
184
  while (cursor < historical.length) emit(historical[cursor++]);
184
185
  const fullSources = [...full.values()];
185
- // Stable provenance belongs with every represented occurrence, not a rolling
186
- // catalog. Insert before whole exchanges, never between native calls/results.
187
- const annotated = [];
188
- for (let i = 0; i < messages.length;) {
189
- const group = [messages[i++]];
190
- while (i < messages.length && messages[i].role === "toolResult") group.push(messages[i++]);
191
- const rows = group.flatMap((message) => {
192
- const s = renderedSources.get(message);
193
- if (!s) return [];
194
- const representation = full.has(s.ref) ? "full" : "structural only; omitted result bodies are not evidence";
195
- const selection =
196
- s.producer === "executor" && isTaskEvidence(s) && full.has(s.ref)
197
- ? "task evidence; selection checks apply"
198
- : "not offered for new evidence selection";
199
- return [
200
- `${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)}` : ""}`,
201
- ];
202
- });
203
- if (rows.length)
204
- annotated.push({
205
- role: "custom",
206
- customType: "freeflow-routing-v2-refs",
207
- display: false,
208
- content: `Source provenance for the following message/exchange:\n${rows.join("\n")}`,
209
- details: { routingInstance: options.instance },
210
- timestamp: 0,
211
- });
212
- annotated.push(...group);
213
- }
214
- messages = annotated;
186
+ messages = annotateSources(messages, renderedSources, new Set(full.keys()), sources, options.instance);
215
187
  // Restore exact current communication only when its accepted occurrence is absent.
216
188
  const a = state.assignmentId ? state.assignments.get(state.assignmentId) : undefined;
217
- const report = handoff?.kind === "return" ? handoff : undefined;
189
+ const baseReport = assessment
190
+ ? state.handoffs.get(assessment.handoffId)
191
+ : handoff?.kind === "return"
192
+ ? handoff
193
+ : undefined;
194
+ const recovery = state.recoveryId
195
+ ? state.recoveries.get(state.recoveryId)
196
+ : [...state.recoveries.values()]
197
+ .reverse()
198
+ .find(
199
+ (candidate) => candidate.assessmentHandoffId === assessment?.handoffId && candidate.state === "completed",
200
+ );
201
+ const recoveryRequest = recovery ? state.handoffs.get(recovery.requestHandoffId) : undefined;
202
+ const supplement =
203
+ handoff?.kind === "recovery-return"
204
+ ? handoff
205
+ : recovery?.supplementHandoffId
206
+ ? state.handoffs.get(recovery.supplementHandoffId)
207
+ : undefined;
218
208
  const hasCommunication = (h, field, value, metadata = {}) =>
219
209
  messages.some((m) => {
220
210
  const matches = (payload) =>
@@ -246,22 +236,41 @@ export function prepareView(options) {
246
236
  if (accepted && !hasCommunication(accepted, "contract", a.contract))
247
237
  restore("Current exact assignment", a.id, a.contract);
248
238
  }
249
- if (report) {
250
- const metadata = { outcome: report.outcome, limitations: report.limitations };
251
- const hasBody = hasCommunication(report, "report", report.text);
239
+ if (baseReport) {
240
+ const metadata = { outcome: baseReport.outcome, limitations: baseReport.limitations };
241
+ const hasBody = hasCommunication(baseReport, "report", baseReport.text);
252
242
  // Tool arguments establish accepted content but do not carry harness-assigned
253
243
  // revision/lineage. Old receipts and partially retained calls need metadata too.
254
- const complete = hasCommunication(report, "report", report.text, {
244
+ const complete = hasCommunication(baseReport, "report", baseReport.text, {
255
245
  ...metadata,
256
- reportRevision: report.reportRevision,
257
- assignmentRef: `assignment:${report.assignmentId}`,
258
- reportRef: `report:${report.id}:${report.reportRevision}`,
246
+ reportRevision: baseReport.reportRevision,
247
+ assignmentRef: `assignment:${baseReport.assignmentId}`,
248
+ reportRef: `report:${baseReport.id}:${baseReport.reportRevision}`,
259
249
  });
260
250
  if (!complete)
261
251
  restore(
262
252
  "Saved report",
263
- report.id,
264
- `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}`}`,
253
+ baseReport.id,
254
+ `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}`}`,
255
+ );
256
+ }
257
+ if (recovery && recoveryRequest && !hasCommunication(recoveryRequest, "request", recovery.request))
258
+ restore(
259
+ "Recovery request",
260
+ recovery.id,
261
+ `Assignment: assignment:${recovery.assignmentId}\nParent report: report:${recovery.assessmentHandoffId}:${recovery.baseReportRevision}\nAllowed paths: ${JSON.stringify(recovery.paths)}\nRequest:\n${recovery.request}`,
262
+ );
263
+ if (recovery && supplement) {
264
+ const hasBody = hasCommunication(supplement, "report", supplement.text);
265
+ const complete = hasCommunication(supplement, "report", supplement.text, {
266
+ outcome: supplement.outcome,
267
+ limitations: supplement.limitations,
268
+ });
269
+ if (!complete)
270
+ restore(
271
+ "Recovery supplement",
272
+ recovery.id,
273
+ `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}`}`,
265
274
  );
266
275
  }
267
276
  messages.push(options.runtimeMessage);
@@ -3,9 +3,19 @@ const value = (v) => (typeof v === "string" ? v : v === undefined ? "" : JSON.st
3
3
  const text = (v) => (typeof v === "string" ? v : "");
4
4
  const count = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`;
5
5
  const capital = (v) => (v ? v[0].toUpperCase() + v.slice(1) : "");
6
+ const locatorText = (locator) => {
7
+ if (!locator) return "";
8
+ const identity = `${locator.toolCallId ?? "unknown call"} (${locator.callRef ?? "unknown source"})`;
9
+ return locator.label ? `${locator.label}${locator.truncated ? " [truncated]" : ""} · ${identity}` : identity;
10
+ };
6
11
  function title(name, operation) {
7
- if (name === "freeflow_delegate") return operation === "replace" ? "Replace assignment" : "Delegate to Executor";
8
- if (name === "freeflow_return") return operation === "retry" ? "Retry return" : "Return to Coordinator";
12
+ if (name === "freeflow_delegate") return operation === "replace" ? "Replace assignment" : "Delegate assignment";
13
+ if (name === "freeflow_return")
14
+ return operation === "retry"
15
+ ? "Retry return"
16
+ : operation === "supplement"
17
+ ? "Return recovery supplement"
18
+ : "Return to Coordinator";
9
19
  if (name === "freeflow_project")
10
20
  return (
11
21
  {
@@ -21,6 +31,8 @@ function title(name, operation) {
21
31
  inspect: "Inspect routing",
22
32
  close: "Close unit",
23
33
  assess: "Resume assessment",
34
+ recover: "Recover assessment evidence",
35
+ "cancel-recovery": "Cancel evidence recovery",
24
36
  status: "Routing status",
25
37
  history: "Routing history",
26
38
  }[operation ?? ""] ?? "Routing unit"
@@ -32,6 +44,7 @@ function draft(name, args, expanded) {
32
44
  if (name === "freeflow_delegate") sections.push(text(args.contract));
33
45
  if (name === "freeflow_return") sections.push(text(args.report));
34
46
  if (name === "freeflow_unit" && args.operation === "close") sections.push(text(args.assessment));
47
+ if (name === "freeflow_unit" && args.operation === "recover") sections.push(text(args.request));
35
48
  // Metadata can arrive before the main text and otherwise occupy the entire
36
49
  // trailing preview forever. Keep the live prose moving; expansion keeps all fields.
37
50
  if (!expanded && sections.length) return sections.filter(Boolean).join("\n\n");
@@ -53,7 +66,9 @@ export function renderRoutingCall(name, args = {}, context = {}) {
53
66
  name === "freeflow_delegate"
54
67
  ? "assignment"
55
68
  : name === "freeflow_return"
56
- ? "report"
69
+ ? args.operation === "supplement"
70
+ ? "supplement"
71
+ : "report"
57
72
  : name === "freeflow_unit" && args.operation === "close"
58
73
  ? "assessment"
59
74
  : undefined;
@@ -79,8 +94,8 @@ function summary(receipt, name, args) {
79
94
  const issue = problems.length
80
95
  ? `${count(problems.length, "evidence issue")} ${problems.length === 1 ? "needs" : "need"} correction`
81
96
  : "Evidence needs correction";
82
- if (receipt.reportSaved)
83
- return `${operation === "retry" ? "Saved report retained" : "Report saved"}${receipt.ready === false ? ` · ${issue}` : ""}`;
97
+ if (receipt.reportSaved || receipt.supplementSaved)
98
+ return `${operation === "retry" ? (receipt.supplementSaved ? "Saved supplement retained" : "Saved report retained") : receipt.supplementSaved ? "Recovery supplement saved" : "Report saved"}${receipt.ready === false ? ` · ${issue}` : ""}`;
84
99
  if (receipt.status === "blocked" || receipt.status === "rejected") {
85
100
  const reasons = {
86
101
  wrong_profile: "This operation belongs to the other profile",
@@ -119,6 +134,8 @@ function summary(receipt, name, args) {
119
134
  }
120
135
  if (name === "freeflow_unit") {
121
136
  if (operation === "close") return capital(receipt.outcome ?? "closed");
137
+ if (operation === "recover") return "Recovery request saved";
138
+ if (operation === "cancel-recovery") return "Evidence recovery cancelled";
122
139
  if (operation === "assess")
123
140
  return receipt.status === "unchanged" ? "Assessment already active" : "Evidence prepared";
124
141
  if (receipt.view === "history")
@@ -155,7 +172,7 @@ export function renderRoutingResult(result, options = {}, name = "", context = {
155
172
  `Evidence revision ${facts.revision ?? "unknown"}`,
156
173
  ...(facts.selected ?? []).map(
157
174
  (s) =>
158
- `${s.ref} · ${s.kind}${s.producer ? ` · ${s.producer}` : ""}${s.assignment ? ` · assignment ${s.assignment}` : ""}${s.toolName ? ` · ${s.toolName}` : ""}`,
175
+ `${s.ref} · ${s.kind}${s.producer ? ` · ${s.producer}` : ""}${s.assignment ? ` · assignment ${s.assignment}` : ""}${s.toolName ? ` · ${s.toolName}` : ""}${s.locator ? ` · ${locatorText(s.locator)}` : ""}`,
159
176
  ),
160
177
  ...(facts.withdrawals ?? []).map((w) => `Withdrawn ${w.ref}: ${w.reason}`),
161
178
  ...(facts.unresolved ?? []).map((p) => `Unresolved ${p.ref}: ${p.detail}`),
@@ -170,9 +187,11 @@ export function renderRoutingResult(result, options = {}, name = "", context = {
170
187
  : "",
171
188
  receipt.report
172
189
  ? `Saved report\n${receipt.report}`
173
- : args.report
174
- ? `${receipt.reportSaved ? "Saved" : "Submitted"} report\n${args.report}`
175
- : "",
190
+ : receipt.supplement
191
+ ? `Saved recovery supplement\n${receipt.supplement}`
192
+ : args.report
193
+ ? `${receipt.supplementSaved ? "Saved recovery supplement" : receipt.reportSaved ? "Saved report" : "Submitted report"}\n${args.report}`
194
+ : "",
176
195
  receipt.assessment ? `Assessment\n${value(receipt.assessment)}` : args.assessment,
177
196
  evidence,
178
197
  receipt.items
@@ -192,7 +211,7 @@ export function renderRoutingResult(result, options = {}, name = "", context = {
192
211
  receipt.candidates
193
212
  ?.map(
194
213
  (item) =>
195
- `${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) => `${p.code}: ${p.detail}`).join("\n")}`,
214
+ `${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) => `${p.code}: ${p.detail}`).join("\n")}`,
196
215
  )
197
216
  .join("\n\n"),
198
217
  receipt.ref ? `Work ref: ${receipt.ref}` : "",
@@ -208,9 +227,11 @@ export function renderRoutingResult(result, options = {}, name = "", context = {
208
227
  "unit",
209
228
  "assignment",
210
229
  "assignmentRef",
230
+ "recovery",
211
231
  "handoff",
212
232
  "revision",
213
233
  "reportRevision",
234
+ "supplementRevision",
214
235
  "outcome",
215
236
  "stage",
216
237
  "transition",