@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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/README.md +147 -127
- package/capabilities/cognitive-routing/SKILL.md +162 -93
- package/gemini-extension.json +1 -1
- package/package.json +1 -1
- package/pi-extension/dist/cognitive-routing-v2/config.js +35 -19
- package/pi-extension/dist/cognitive-routing-v2/projection.js +53 -44
- package/pi-extension/dist/cognitive-routing-v2/render.js +31 -10
- package/pi-extension/dist/cognitive-routing-v2/runtime.js +701 -105
- package/pi-extension/dist/cognitive-routing-v2/schemas.js +90 -24
- package/pi-extension/dist/cognitive-routing-v2/state.js +243 -27
- package/pi-extension/dist/cognitive-routing-v2/tools.js +19 -50
- package/pi-extension/dist/cognitive-routing-v2/types.js +5 -1
- package/pi-extension/dist/context-virtualization/runtime.js +29 -7
- package/pi-extension/dist/freeflow-context/tool.js +7 -6
- package/pi-extension/dist/index.js +36 -15
- package/pi-extension/dist/provider-support/astra/adapter.js +110 -0
- package/pi-extension/dist/provider-support/astra/history.js +125 -0
- package/pi-extension/dist/provider-support/astra/index.js +11 -0
- package/pi-extension/dist/provider-support/astra/session-state.js +114 -0
- package/pi-extension/dist/provider-support/index.js +4 -0
- package/pi-extension/dist/runtime/request-history.js +216 -0
- package/pi-extension/dist/runtime/runtime-context.js +29 -10
- package/pi-extension/dist/session-sources/events.js +22 -0
- package/pi-extension/dist/session-sources/provenance.js +32 -0
- package/pi-extension/dist/session-sources/sources.js +51 -7
- package/pi-extension/dist/settings/settings-ui.js +247 -47
- package/pi-extension/src/cognitive-routing-v2/config.ts +49 -19
- package/pi-extension/src/cognitive-routing-v2/projection.ts +53 -43
- package/pi-extension/src/cognitive-routing-v2/render.ts +31 -10
- package/pi-extension/src/cognitive-routing-v2/runtime.ts +712 -105
- package/pi-extension/src/cognitive-routing-v2/schemas.ts +95 -24
- package/pi-extension/src/cognitive-routing-v2/state.ts +244 -27
- package/pi-extension/src/cognitive-routing-v2/tools.ts +19 -50
- package/pi-extension/src/cognitive-routing-v2/types.ts +35 -3
- package/pi-extension/src/context-virtualization/runtime.ts +30 -8
- package/pi-extension/src/freeflow-context/tool.ts +7 -6
- package/pi-extension/src/index.ts +36 -14
- package/pi-extension/src/provider-support/astra/adapter.ts +108 -0
- package/pi-extension/src/provider-support/astra/history.ts +145 -0
- package/pi-extension/src/provider-support/astra/index.ts +12 -0
- package/pi-extension/src/provider-support/astra/session-state.ts +113 -0
- package/pi-extension/src/provider-support/index.ts +5 -0
- package/pi-extension/src/runtime/request-history.ts +243 -0
- package/pi-extension/src/runtime/runtime-context.ts +32 -10
- package/pi-extension/src/session-sources/events.ts +22 -0
- package/pi-extension/src/session-sources/provenance.ts +39 -0
- package/pi-extension/src/session-sources/sources.ts +77 -6
- package/pi-extension/src/settings/settings-ui.ts +276 -46
- package/pi-extension/tests/cache-reuse/history.test.js +117 -0
- package/pi-extension/tests/cache-reuse/native.test.js +91 -0
- package/pi-extension/tests/cache-reuse/settings.test.js +211 -0
- package/pi-extension/tests/cognitive-routing-v2/communication.test.js +94 -1
- package/pi-extension/tests/cognitive-routing-v2/delegation.test.js +557 -0
- package/pi-extension/tests/cognitive-routing-v2/native.test.js +34 -1
- package/pi-extension/tests/cognitive-routing-v2/persistence.test.js +55 -0
- package/pi-extension/tests/cognitive-routing-v2/recovery.test.js +800 -2
- package/pi-extension/tests/cognitive-routing-v2/render.test.js +15 -1
- package/pi-extension/tests/cognitive-routing-v2/runtime.test.js +273 -3
- package/pi-extension/tests/cognitive-routing-v2/state.test.js +312 -1
- package/pi-extension/tests/context-virtualization/extension.test.js +20 -10
- package/pi-extension/tests/context-virtualization/runtime.test.js +17 -3
- package/pi-extension/tests/fixtures/routing-native.js +4 -1
- package/pi-extension/tests/integration/mode-free-core.test.js +4 -3
- package/pi-extension/tests/integration/pi-extension.test.js +508 -29
- package/pi-extension/tests/integration/prompt-architecture.test.js +56 -40
- package/pi-extension/tests/integration/unaffected-regressions.test.js +3 -3
- package/pi-extension/tests/provider-support/astra-boundaries.test.js +75 -0
- package/pi-extension/tests/provider-support/astra.test.js +306 -0
- package/pi-extension/tests/provider-support/cli.test.js +92 -0
- package/pi-extension/tests/provider-support/native.test.js +183 -0
- package/plugin.json +1 -1
- package/runtime/prompts/cognitive-routing.md +4 -4
- package/skills/design-for-depth/SKILL.md +1 -1
- package/skills/design-for-depth/references/implementation-decisions.md +3 -3
- 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,
|
|
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
|
|
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: "[
|
|
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
|
-
|
|
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
|
|
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 (
|
|
294
|
-
const metadata = { outcome:
|
|
295
|
-
const hasBody = hasCommunication(
|
|
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(
|
|
289
|
+
const complete = hasCommunication(baseReport, "report", baseReport.text, {
|
|
299
290
|
...metadata,
|
|
300
|
-
reportRevision:
|
|
301
|
-
assignmentRef: `assignment:${
|
|
302
|
-
reportRef: `report:${
|
|
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
|
-
|
|
308
|
-
`Producer:
|
|
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
|
|
18
|
-
if (name === "freeflow_return")
|
|
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
|
-
? "
|
|
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
|
-
:
|
|
199
|
-
?
|
|
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",
|