@hassangameryt/freeflow 0.7.2 → 0.8.0
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/NOTICE +14 -0
- package/README.md +151 -136
- package/capabilities/cognitive-routing/SKILL.md +110 -96
- package/capabilities/cognitive-routing/references/both-mode.md +36 -0
- package/capabilities/cognitive-routing/references/executor-mode.md +48 -0
- package/capabilities/cognitive-routing/references/helper-mode.md +47 -0
- package/capabilities/compaction/SKILL.md +49 -0
- package/capabilities/compaction/references/summary-format.md +239 -0
- package/command-surface.json +4 -0
- package/gemini-extension.json +1 -1
- package/hooks/shared/runtime-context.mjs +28 -30
- package/licenses/Apache-2.0.txt +55 -0
- package/package.json +22 -9
- package/pi-extension/README.md +41 -0
- package/pi-extension/dist/cognitive-routing/assembler.js +393 -0
- package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/config.js +40 -21
- package/pi-extension/dist/cognitive-routing/economics.js +45 -0
- package/pi-extension/dist/cognitive-routing/event-schema.js +388 -0
- package/pi-extension/dist/cognitive-routing/event-store.js +280 -0
- package/pi-extension/dist/cognitive-routing/gate.js +241 -0
- package/pi-extension/dist/cognitive-routing/handlers.js +899 -0
- package/pi-extension/dist/cognitive-routing/handoffs.js +350 -0
- package/pi-extension/dist/cognitive-routing/model-control.js +467 -0
- package/pi-extension/dist/cognitive-routing/projection.js +503 -0
- package/pi-extension/dist/cognitive-routing/provenance.js +81 -0
- package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/render.js +31 -10
- package/pi-extension/dist/cognitive-routing/runtime.js +396 -0
- package/pi-extension/dist/cognitive-routing/schemas.js +209 -0
- package/pi-extension/dist/cognitive-routing/session.js +221 -0
- package/pi-extension/dist/cognitive-routing/sources.js +291 -0
- package/pi-extension/dist/cognitive-routing/state.js +487 -0
- package/pi-extension/dist/cognitive-routing/status.js +80 -0
- package/pi-extension/dist/cognitive-routing/tools.js +60 -0
- package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/types.js +10 -12
- package/pi-extension/dist/compaction/carry.js +191 -0
- package/pi-extension/dist/compaction/config.js +22 -0
- package/pi-extension/dist/compaction/controller.js +471 -0
- package/pi-extension/dist/compaction/harness.js +77 -0
- package/pi-extension/dist/compaction/results.js +158 -0
- package/pi-extension/dist/compaction/thresholds.js +35 -0
- package/pi-extension/dist/compaction/tool.js +57 -0
- package/pi-extension/dist/host/branch.js +13 -0
- package/pi-extension/dist/host/canonical.js +12 -0
- package/pi-extension/dist/host/catalog.js +77 -0
- package/pi-extension/dist/host/config.js +374 -0
- package/pi-extension/dist/host/projection-tags.js +60 -0
- package/pi-extension/dist/host/prompts.js +82 -0
- package/pi-extension/dist/{session-sources → host}/read-only-session.js +112 -1
- package/pi-extension/dist/host/request-history.js +298 -0
- package/pi-extension/dist/host/runtime-state.js +189 -0
- package/pi-extension/dist/host/settings/freeflow-command.js +1570 -0
- package/pi-extension/dist/{settings/settings-tui.js → host/settings/settings-view.js} +186 -73
- package/pi-extension/dist/host/staging.js +28 -0
- package/pi-extension/dist/host/status.js +101 -0
- package/pi-extension/dist/index.js +441 -170
- package/pi-extension/dist/provider-support/cache/anchor.js +21 -0
- package/pi-extension/dist/provider-support/cache/anthropic.js +102 -0
- package/pi-extension/dist/provider-support/cache/health.js +73 -0
- package/pi-extension/dist/provider-support/cache/index.js +58 -0
- package/pi-extension/dist/provider-support/cache/keepalive.js +210 -0
- package/pi-extension/dist/provider-support/cache/monitor.js +17 -0
- package/pi-extension/dist/provider-support/cache/openai-responses.js +48 -0
- package/pi-extension/dist/provider-support/effort.js +13 -0
- package/pi-extension/dist/provider-support/index.js +45 -0
- package/pi-extension/dist/provider-support/openai/adapter.js +127 -0
- package/pi-extension/dist/provider-support/openai/history.js +127 -0
- package/pi-extension/dist/provider-support/openai/index.js +15 -0
- package/pi-extension/dist/provider-support/openai/session-state.js +105 -0
- package/pi-extension/dist/provider-support/routes.js +17 -0
- package/pi-extension/dist/tool-execution/apply-patch/grammar.js +23 -0
- package/pi-extension/dist/tool-execution/apply-patch/match.js +38 -0
- package/pi-extension/dist/tool-execution/apply-patch/parser.js +177 -0
- package/pi-extension/dist/tool-execution/apply-patch/plan.js +201 -0
- package/pi-extension/dist/tool-execution/apply-patch/tool.js +84 -0
- package/pi-extension/dist/tool-execution/background.js +238 -0
- package/pi-extension/dist/tool-execution/bash-guard.js +23 -0
- package/pi-extension/dist/tool-execution/config.js +22 -0
- package/pi-extension/dist/tool-execution/edit-messages.js +120 -0
- package/pi-extension/dist/tool-execution/file-state.js +138 -0
- package/pi-extension/dist/tool-execution/file-tracking.js +143 -0
- package/pi-extension/dist/tool-execution/prompt.js +54 -0
- package/pi-extension/dist/tool-execution/tools.js +16 -0
- package/pi-extension/freeflow/README.md +9 -0
- package/plugin.json +1 -1
- package/runtime/prompts/cognitive-routing.md +4 -4
- package/runtime/prompts/core.md +22 -75
- package/runtime/prompts/interaction-contract.md +15 -8
- package/runtime/prompts/tool-execution.md +38 -0
- package/runtime/prompts/working-method.md +108 -0
- package/skills/action-selection/SKILL.md +39 -47
- package/skills/action-selection/references/trajectory-stalls.md +2 -0
- package/skills/bypass/SKILL.md +2 -2
- package/skills/commit-work/SKILL.md +31 -83
- package/skills/commit-work/references/git-state-edges.md +1 -1
- package/skills/commit-work/references/simple-push.md +2 -0
- package/skills/commit-work/references/staging-decisions.md +1 -1
- package/skills/decision-gate/SKILL.md +31 -50
- package/skills/design-for-depth/SKILL.md +44 -53
- package/skills/design-for-depth/references/design-pressure-signals.md +46 -144
- package/skills/design-for-depth/references/implementation-decisions.md +4 -4
- package/skills/design-for-depth/references/interface-design-loop.md +5 -36
- package/skills/design-for-depth/references/module-and-dependency-design.md +2 -32
- package/skills/design-for-depth/references/state-and-failure-boundaries.md +2 -13
- package/skills/diagnose-failure/SKILL.md +39 -24
- package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +2 -0
- package/skills/diagnose-failure/references/flaky-and-performance-diagnosis.md +2 -2
- package/skills/discuss/SKILL.md +37 -13
- package/skills/discuss/references/discussion-continuity.md +7 -27
- package/skills/evaluate-skill/SKILL.md +49 -80
- package/skills/evaluate-skill/references/evaluation-definition-schema.md +35 -5
- package/skills/evaluate-skill/references/evaluation-design.md +14 -14
- package/skills/evaluate-skill/references/execution-and-evidence.md +26 -5
- package/skills/evaluate-skill/references/review-and-revision.md +3 -1
- package/skills/evaluate-skill/scripts/lib/definitions.mjs +49 -8
- package/skills/evaluate-skill/scripts/lib/pi.mjs +14 -7
- package/skills/evaluate-skill/scripts/lib/review.mjs +196 -0
- package/skills/evaluate-skill/scripts/lib/runner.mjs +45 -8
- package/skills/evaluate-skill/scripts/lib/sandbox.mjs +2 -0
- package/skills/evaluate-skill/scripts/lib/trials.mjs +103 -0
- package/skills/evaluate-skill/scripts/lib/view.mjs +67 -1
- package/skills/evaluate-skill/scripts/pi-command-tool.mjs +144 -0
- package/skills/evaluate-skill/scripts/pi-guard.mjs +30 -13
- package/skills/evaluate-skill/scripts/skill-eval.mjs +114 -34
- package/skills/execute-work/SKILL.md +41 -9
- package/skills/execute-work/references/code-practices.md +2 -0
- package/skills/execute-work/references/execute-work-edges.md +11 -1
- package/skills/handoff/SKILL.md +1 -1
- package/skills/handoff/references/handoff-templates.md +1 -1
- package/skills/review-artifact/SKILL.md +42 -41
- package/skills/review-artifact/references/adjudicate-artifact-review.md +1 -1
- package/skills/review-artifact/references/independent-artifact-reviewer-contract.md +1 -1
- package/skills/review-work/SKILL.md +38 -32
- package/skills/review-work/references/adjudicate-work-review.md +1 -1
- package/skills/review-work/references/independent-work-reviewer-contract.md +1 -1
- package/skills/review-work/references/security-risk-lens.md +2 -4
- package/skills/setup-freeflow/SKILL.md +3 -1
- package/skills/setup-freeflow/references/activation-contract.md +7 -13
- package/skills/setup-freeflow/references/host-setup.md +3 -3
- package/skills/track-work/SKILL.md +113 -161
- package/skills/track-work/references/working-record-format.md +189 -350
- package/skills/track-work/scripts/lib/document.mjs +62 -6
- package/skills/track-work/scripts/lib/format.mjs +16 -1
- package/skills/track-work/scripts/lib/store.mjs +9 -8
- package/skills/track-work/scripts/lib/transitions.mjs +79 -4
- package/skills/track-work/scripts/working-record.mjs +34 -12
- package/skills/verify-work/SKILL.md +29 -20
- package/skills/verify-work/references/browser-runtime-evidence.md +1 -5
- package/skills/verify-work/references/integration-evidence.md +2 -0
- package/skills/verify-work/references/performance-evidence.md +3 -1
- package/skills/verify-work/references/test-design.md +3 -1
- package/skills/workflow/SKILL.md +65 -21
- package/skills/workflow/references/domain-skill-composition.md +5 -3
- package/skills/write-docs/SKILL.md +83 -0
- package/skills/write-docs/references/documentation-shapes.md +119 -0
- package/skills/write-docs/scripts/docs-check.mjs +139 -0
- package/skills/write-plan/SKILL.md +40 -40
- package/skills/write-plan/references/plan-shapes.md +1 -1
- package/skills/write-skill/SKILL.md +107 -147
- package/skills/write-skill/references/activation-boundaries.md +2 -0
- package/skills/write-skill/references/agent-first-instructions.md +22 -10
- package/skills/write-skill/references/progressive-disclosure.md +4 -2
- package/skills/write-skill/references/skill-dependency-graphs.md +2 -0
- package/skills/write-skill/scripts/lib/similarity.mjs +56 -0
- package/skills/write-skill/scripts/skill-author.mjs +18 -2
- package/skills/write-spec/SKILL.md +34 -41
- package/skills/write-spec/references/artifact-standards.md +1 -1
- package/skills/write-spec/references/decision-records.md +2 -0
- package/skills/write-spec/references/spec-shapes.md +1 -1
- package/capabilities/context-virtualization/SKILL.md +0 -103
- package/capabilities/conversation-history/SKILL.md +0 -53
- package/hooks/tests/check-host-adapters.test.mjs +0 -82
- package/hooks/tests/check-runtime-context-hook.sh +0 -212
- package/pi-extension/dist/cognitive-routing-v2/projection.js +0 -321
- package/pi-extension/dist/cognitive-routing-v2/runtime.js +0 -1585
- package/pi-extension/dist/cognitive-routing-v2/schemas.js +0 -107
- package/pi-extension/dist/cognitive-routing-v2/state.js +0 -612
- package/pi-extension/dist/cognitive-routing-v2/tools.js +0 -91
- package/pi-extension/dist/context-virtualization/commands.js +0 -89
- package/pi-extension/dist/context-virtualization/projector.js +0 -42
- package/pi-extension/dist/context-virtualization/runtime.js +0 -356
- package/pi-extension/dist/context-virtualization/state.js +0 -57
- package/pi-extension/dist/context-virtualization/types.js +0 -10
- package/pi-extension/dist/conversation-history/passages.js +0 -66
- package/pi-extension/dist/conversation-history/ranking.js +0 -105
- package/pi-extension/dist/conversation-history/runtime.js +0 -226
- package/pi-extension/dist/conversation-history/search.js +0 -56
- package/pi-extension/dist/conversation-history/types.js +0 -1
- package/pi-extension/dist/freeflow-context/resolver.js +0 -120
- package/pi-extension/dist/freeflow-context/runtime.js +0 -80
- package/pi-extension/dist/freeflow-context/source-projector.js +0 -95
- package/pi-extension/dist/freeflow-context/tool.js +0 -313
- package/pi-extension/dist/freeflow-context/types.js +0 -12
- package/pi-extension/dist/freeflow-context/visibility.js +0 -28
- package/pi-extension/dist/integrations/mcp-config.js +0 -29
- package/pi-extension/dist/runtime/runtime-context.js +0 -735
- package/pi-extension/dist/runtime/runtime-identity.js +0 -5
- package/pi-extension/dist/session-sources/events.js +0 -195
- package/pi-extension/dist/session-sources/sources.js +0 -233
- package/pi-extension/dist/settings/settings-ui.js +0 -1160
- package/pi-extension/dist/ui/text-component.js +0 -57
- package/pi-extension/src/cognitive-routing-v2/budget.ts +0 -42
- package/pi-extension/src/cognitive-routing-v2/config.ts +0 -143
- package/pi-extension/src/cognitive-routing-v2/projection.ts +0 -365
- package/pi-extension/src/cognitive-routing-v2/render.ts +0 -256
- package/pi-extension/src/cognitive-routing-v2/runtime.ts +0 -1634
- package/pi-extension/src/cognitive-routing-v2/schemas.ts +0 -109
- package/pi-extension/src/cognitive-routing-v2/state.ts +0 -622
- package/pi-extension/src/cognitive-routing-v2/tools.ts +0 -93
- package/pi-extension/src/cognitive-routing-v2/types.ts +0 -222
- package/pi-extension/src/context-virtualization/commands.ts +0 -111
- package/pi-extension/src/context-virtualization/projector.ts +0 -62
- package/pi-extension/src/context-virtualization/runtime.ts +0 -406
- package/pi-extension/src/context-virtualization/state.ts +0 -79
- package/pi-extension/src/context-virtualization/types.ts +0 -92
- package/pi-extension/src/conversation-history/passages.ts +0 -81
- package/pi-extension/src/conversation-history/ranking.ts +0 -155
- package/pi-extension/src/conversation-history/runtime.ts +0 -250
- package/pi-extension/src/conversation-history/search.ts +0 -79
- package/pi-extension/src/conversation-history/types.ts +0 -31
- package/pi-extension/src/freeflow-context/resolver.ts +0 -139
- package/pi-extension/src/freeflow-context/runtime.ts +0 -100
- package/pi-extension/src/freeflow-context/source-projector.ts +0 -106
- package/pi-extension/src/freeflow-context/tool.ts +0 -365
- package/pi-extension/src/freeflow-context/types.ts +0 -70
- package/pi-extension/src/freeflow-context/visibility.ts +0 -53
- package/pi-extension/src/index.ts +0 -368
- package/pi-extension/src/integrations/mcp-config.ts +0 -32
- package/pi-extension/src/runtime/runtime-context.ts +0 -838
- package/pi-extension/src/runtime/runtime-identity.ts +0 -8
- package/pi-extension/src/session-sources/events.ts +0 -208
- package/pi-extension/src/session-sources/read-only-session.ts +0 -170
- package/pi-extension/src/session-sources/sources.ts +0 -263
- package/pi-extension/src/settings/settings-tui.ts +0 -501
- package/pi-extension/src/settings/settings-ui.ts +0 -1422
- package/pi-extension/src/ui/text-component.ts +0 -66
- package/pi-extension/tests/cognitive-routing-v2/communication.test.js +0 -484
- package/pi-extension/tests/cognitive-routing-v2/compaction-input.test.js +0 -183
- package/pi-extension/tests/cognitive-routing-v2/native.test.js +0 -441
- package/pi-extension/tests/cognitive-routing-v2/performance.test.js +0 -87
- package/pi-extension/tests/cognitive-routing-v2/persistence.test.js +0 -245
- package/pi-extension/tests/cognitive-routing-v2/projection.test.js +0 -241
- package/pi-extension/tests/cognitive-routing-v2/recovery.test.js +0 -236
- package/pi-extension/tests/cognitive-routing-v2/render.test.js +0 -230
- package/pi-extension/tests/cognitive-routing-v2/runtime.test.js +0 -153
- package/pi-extension/tests/cognitive-routing-v2/stabilization.test.js +0 -521
- package/pi-extension/tests/cognitive-routing-v2/state.test.js +0 -275
- package/pi-extension/tests/context-virtualization/extension.test.js +0 -765
- package/pi-extension/tests/context-virtualization/projector.test.js +0 -110
- package/pi-extension/tests/context-virtualization/runtime.test.js +0 -216
- package/pi-extension/tests/conversation-history/runtime.test.js +0 -98
- package/pi-extension/tests/conversation-history/search.test.js +0 -97
- package/pi-extension/tests/fixtures/pi-host.js +0 -4
- package/pi-extension/tests/fixtures/routing-native.js +0 -188
- package/pi-extension/tests/integration/mode-free-core.test.js +0 -219
- package/pi-extension/tests/integration/pi-extension.test.js +0 -968
- package/pi-extension/tests/integration/prompt-architecture.test.js +0 -431
- package/pi-extension/tests/integration/unaffected-regressions.test.js +0 -478
- package/pi-extension/tests/settings/settings-tui.test.js +0 -243
- package/pi-extension/tsconfig.json +0 -16
- package/runtime/prompts/context-virtualization.md +0 -3
- package/runtime/prompts/conversation-history.md +0 -3
- package/skills/design-for-depth/references/software-design-philosophy.md +0 -202
- /package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/budget.js +0 -0
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PROFILES,
|
|
3
|
+
ROUTING_ENTRY,
|
|
4
|
+
canonical,
|
|
5
|
+
eventKey,
|
|
6
|
+
eventValue,
|
|
7
|
+
isWorkerProfile,
|
|
8
|
+
requireCondition as check,
|
|
9
|
+
} from "./types.js";
|
|
10
|
+
import { parseRoutingEvent } from "./event-schema.js";
|
|
11
|
+
export function initialState() {
|
|
12
|
+
return {
|
|
13
|
+
control: "inactive",
|
|
14
|
+
units: new Map(),
|
|
15
|
+
assignments: new Map(),
|
|
16
|
+
handoffs: new Map(),
|
|
17
|
+
recoveries: new Map(),
|
|
18
|
+
executions: new Map(),
|
|
19
|
+
selections: new Map(),
|
|
20
|
+
reservations: new Map(),
|
|
21
|
+
attempts: new Map(),
|
|
22
|
+
exposure: new Map(),
|
|
23
|
+
authors: new Map(),
|
|
24
|
+
resumeBasis: new Map(),
|
|
25
|
+
profileOverrides: new Map(),
|
|
26
|
+
events: new Map(),
|
|
27
|
+
eventIds: new Map(),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function applyEvent(state, event, owned = false) {
|
|
31
|
+
const e = parseRoutingEvent(event),
|
|
32
|
+
key = eventKey(e),
|
|
33
|
+
value = eventValue(e);
|
|
34
|
+
const previous = state.events.get(key),
|
|
35
|
+
priorId = state.eventIds.get(e.eventId);
|
|
36
|
+
check(!priorId || priorId === value, "event_identity_conflict");
|
|
37
|
+
if (previous) {
|
|
38
|
+
check(eventValue(previous) === value, "operation_conflict");
|
|
39
|
+
return state;
|
|
40
|
+
}
|
|
41
|
+
const s = owned
|
|
42
|
+
? state
|
|
43
|
+
: {
|
|
44
|
+
...state,
|
|
45
|
+
units: new Map([...state.units].map(([k, v]) => [k, { ...v }])),
|
|
46
|
+
assignments: new Map([...state.assignments].map(([k, v]) => [k, { ...v }])),
|
|
47
|
+
handoffs: new Map([...state.handoffs].map(([k, v]) => [k, { ...v }])),
|
|
48
|
+
recoveries: new Map(
|
|
49
|
+
[...state.recoveries].map(([k, v]) => [
|
|
50
|
+
k,
|
|
51
|
+
{
|
|
52
|
+
...v,
|
|
53
|
+
paths: [...v.paths],
|
|
54
|
+
requestedResults: [...(v.requestedResults ?? [])],
|
|
55
|
+
results: (v.results ?? []).map((grant) => ({ ...grant })),
|
|
56
|
+
},
|
|
57
|
+
]),
|
|
58
|
+
),
|
|
59
|
+
executions: new Map([...state.executions].map(([k, v]) => [k, { ...v }])),
|
|
60
|
+
selections: new Map(state.selections),
|
|
61
|
+
reservations: new Map(state.reservations),
|
|
62
|
+
attempts: new Map(state.attempts),
|
|
63
|
+
exposure: new Map(state.exposure),
|
|
64
|
+
authors: new Map(state.authors),
|
|
65
|
+
resumeBasis: new Map(state.resumeBasis),
|
|
66
|
+
profileOverrides: new Map(state.profileOverrides),
|
|
67
|
+
events: new Map(state.events),
|
|
68
|
+
eventIds: new Map(state.eventIds),
|
|
69
|
+
assessment: state.assessment ? { ...state.assessment } : undefined,
|
|
70
|
+
},
|
|
71
|
+
d = e.data;
|
|
72
|
+
const assignment = () => (s.assignmentId ? s.assignments.get(s.assignmentId) : undefined);
|
|
73
|
+
const assignmentWorker = () => {
|
|
74
|
+
const a = assignment();
|
|
75
|
+
const h = a ? s.handoffs.get(a.delegateHandoffId) : undefined;
|
|
76
|
+
check(h?.kind === "delegate" && h.assignmentId === a?.id && isWorkerProfile(h.to), "assignment_worker_missing");
|
|
77
|
+
return h.to;
|
|
78
|
+
};
|
|
79
|
+
// A worker response that started but was neither bound nor retired may still be producing effects.
|
|
80
|
+
const workerRunning = (belongs) =>
|
|
81
|
+
[...s.executions.values()].some(
|
|
82
|
+
(x) => belongs(x) && isWorkerProfile(x.profile) && !x.assistantEntryId && !x.interrupted,
|
|
83
|
+
);
|
|
84
|
+
const pending = () => (s.pendingId ? s.handoffs.get(s.pendingId) : undefined);
|
|
85
|
+
const isPending = () => pending() && ["pending", "blocked"].includes(pending().state);
|
|
86
|
+
const currentReturn = (id) => {
|
|
87
|
+
const h = s.handoffs.get(id);
|
|
88
|
+
check(
|
|
89
|
+
h && ["return", "recovery-return"].includes(h.kind) && h.assignmentId === s.assignmentId,
|
|
90
|
+
"not_current_return",
|
|
91
|
+
);
|
|
92
|
+
return h;
|
|
93
|
+
};
|
|
94
|
+
switch (d.type) {
|
|
95
|
+
case "execution-interrupted": {
|
|
96
|
+
const x = s.executions.get(d.executionId);
|
|
97
|
+
check(x && !x.assistantEntryId, "execution_not_unbound");
|
|
98
|
+
x.interrupted = d.reason;
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
case "assignment-resumed": {
|
|
102
|
+
const a = assignment();
|
|
103
|
+
check(
|
|
104
|
+
s.control === "automatic" && a?.id === d.assignmentId && a.state === "outstanding" && !isPending(),
|
|
105
|
+
"assignment_not_resumable",
|
|
106
|
+
);
|
|
107
|
+
s.resumeBasis.set(a.id, d.basisUserEntryId);
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
case "control":
|
|
111
|
+
s.control = d.control;
|
|
112
|
+
s.profile = d.profile;
|
|
113
|
+
break;
|
|
114
|
+
case "profile-overrides":
|
|
115
|
+
for (const profile of PROFILES) {
|
|
116
|
+
if (!Object.hasOwn(d.overrides, profile)) continue;
|
|
117
|
+
const override = d.overrides[profile];
|
|
118
|
+
if (override === null) s.profileOverrides.delete(profile);
|
|
119
|
+
else s.profileOverrides.set(profile, override);
|
|
120
|
+
}
|
|
121
|
+
break;
|
|
122
|
+
case "delegation-override":
|
|
123
|
+
s.delegationOverride = d.delegation ?? undefined;
|
|
124
|
+
break;
|
|
125
|
+
case "execution-opened":
|
|
126
|
+
check(!s.executions.has(d.execution.id), "duplicate_execution");
|
|
127
|
+
s.executions.set(d.execution.id, d.execution);
|
|
128
|
+
break;
|
|
129
|
+
case "execution-bound": {
|
|
130
|
+
const x = s.executions.get(d.executionId);
|
|
131
|
+
check(x, "execution_missing");
|
|
132
|
+
if (x.assistantEntryId)
|
|
133
|
+
check(
|
|
134
|
+
x.assistantEntryId === d.assistantEntryId &&
|
|
135
|
+
canonical(x.resultEntryIds) === canonical(d.resultEntryIds) &&
|
|
136
|
+
x.outcome === d.outcome,
|
|
137
|
+
"binding_conflict",
|
|
138
|
+
);
|
|
139
|
+
x.assistantEntryId = d.assistantEntryId;
|
|
140
|
+
x.resultEntryIds = d.resultEntryIds;
|
|
141
|
+
x.outcome = d.outcome;
|
|
142
|
+
for (const id of [d.assistantEntryId, ...d.resultEntryIds]) {
|
|
143
|
+
const prev = s.authors.get(id);
|
|
144
|
+
check(!prev || prev.executionId === x.id, "source_authorship_conflict");
|
|
145
|
+
s.authors.set(id, { profile: x.profile, executionId: x.id, assignmentId: x.assignmentId });
|
|
146
|
+
}
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
case "delegate-accepted": {
|
|
150
|
+
check(s.control === "automatic" && s.profile === "coordinator", "wrong_control");
|
|
151
|
+
const a = assignment(),
|
|
152
|
+
h = d.handoff,
|
|
153
|
+
u = s.unitId ? s.units.get(s.unitId) : undefined;
|
|
154
|
+
check(!s.assignments.has(d.assignment.id) && !s.handoffs.has(h.id), "reused_work_identity");
|
|
155
|
+
check(
|
|
156
|
+
h.kind === "delegate" &&
|
|
157
|
+
h.from === "coordinator" &&
|
|
158
|
+
isWorkerProfile(h.to) &&
|
|
159
|
+
h.state === "pending" &&
|
|
160
|
+
h.text === d.assignment.contract &&
|
|
161
|
+
h.assignmentId === d.assignment.id &&
|
|
162
|
+
d.assignment.delegateHandoffId === h.id &&
|
|
163
|
+
d.assignment.unitId === d.unit.id,
|
|
164
|
+
"delegate_identity_mismatch",
|
|
165
|
+
);
|
|
166
|
+
if (u) check(u.state === "open" && u.id === d.unit.id && u.objective === d.unit.objective, "unit_conflict");
|
|
167
|
+
else check(!s.units.has(d.unit.id), "closed_unit_reused");
|
|
168
|
+
check(
|
|
169
|
+
canonical(d.unit.assignmentIds) === canonical([...(u?.assignmentIds ?? []), d.assignment.id]),
|
|
170
|
+
"assignment_order",
|
|
171
|
+
);
|
|
172
|
+
if (d.replacement) {
|
|
173
|
+
check(a?.state === "outstanding" && a.id === d.replacement.assignmentId && u, "no_replaceable_assignment");
|
|
174
|
+
check(!workerRunning((x) => x.assignmentId === a.id), "unresolved_worker_execution");
|
|
175
|
+
check(
|
|
176
|
+
!isPending() || (pending().kind === "delegate" && d.replacement.supersededHandoffId === pending().id),
|
|
177
|
+
"handoff_pending",
|
|
178
|
+
);
|
|
179
|
+
a.state = "superseded";
|
|
180
|
+
s.handoffs.get(a.delegateHandoffId).state = "superseded";
|
|
181
|
+
} else {
|
|
182
|
+
check(a?.state !== "outstanding" && !isPending(), "assignment_outstanding");
|
|
183
|
+
check(!s.recoveryId, "recovery_outstanding");
|
|
184
|
+
}
|
|
185
|
+
s.units.set(d.unit.id, d.unit);
|
|
186
|
+
s.unitId = d.unit.id;
|
|
187
|
+
s.assignments.set(d.assignment.id, d.assignment);
|
|
188
|
+
s.assignmentId = d.assignment.id;
|
|
189
|
+
s.handoffs.set(h.id, h);
|
|
190
|
+
s.pendingId = h.id;
|
|
191
|
+
s.assessment = undefined;
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
case "return-accepted": {
|
|
195
|
+
check(s.control === "automatic" && s.profile === assignmentWorker(), "wrong_control");
|
|
196
|
+
const a = assignment(),
|
|
197
|
+
h = d.handoff;
|
|
198
|
+
check(
|
|
199
|
+
a &&
|
|
200
|
+
h.assignmentId === a.id &&
|
|
201
|
+
h.kind === "return" &&
|
|
202
|
+
h.from === assignmentWorker() &&
|
|
203
|
+
h.to === "coordinator" &&
|
|
204
|
+
h.state === "pending",
|
|
205
|
+
"return_identity_mismatch",
|
|
206
|
+
);
|
|
207
|
+
const previousReturn = a.returnHandoffId ? s.handoffs.get(a.returnHandoffId) : undefined;
|
|
208
|
+
if (a.state === "outstanding")
|
|
209
|
+
check(!isPending() && !s.handoffs.has(h.id) && h.reportRevision === 1, "handoff_pending");
|
|
210
|
+
else
|
|
211
|
+
check(
|
|
212
|
+
a.state === "returned" &&
|
|
213
|
+
previousReturn &&
|
|
214
|
+
previousReturn.id === h.id &&
|
|
215
|
+
["pending", "blocked"].includes(previousReturn.state) &&
|
|
216
|
+
h.reportRevision === previousReturn.reportRevision + 1,
|
|
217
|
+
"invalid_report_revision",
|
|
218
|
+
);
|
|
219
|
+
a.state = "returned";
|
|
220
|
+
a.returnHandoffId = h.id;
|
|
221
|
+
s.handoffs.set(h.id, h);
|
|
222
|
+
s.pendingId = h.id;
|
|
223
|
+
s.assessment = {
|
|
224
|
+
handoffId: h.id,
|
|
225
|
+
assignmentId: a.id,
|
|
226
|
+
view: "active",
|
|
227
|
+
basisUserEntryId: h.basisUserEntryId,
|
|
228
|
+
problems: [],
|
|
229
|
+
};
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
case "recovery-request-accepted": {
|
|
233
|
+
const a = assignment(),
|
|
234
|
+
base = s.handoffs.get(d.recovery.assessmentHandoffId),
|
|
235
|
+
h = d.handoff;
|
|
236
|
+
check(
|
|
237
|
+
s.control === "automatic" &&
|
|
238
|
+
s.profile === "coordinator" &&
|
|
239
|
+
a?.state === "returned" &&
|
|
240
|
+
d.recovery.assignmentId === a.id &&
|
|
241
|
+
d.recovery.state === "requested" &&
|
|
242
|
+
d.recovery.supplementRevision === 0 &&
|
|
243
|
+
d.recovery.supplementHandoffId === undefined &&
|
|
244
|
+
d.recovery.cancellationReason === undefined &&
|
|
245
|
+
s.assessment?.handoffId === base?.id &&
|
|
246
|
+
base?.kind === "return" &&
|
|
247
|
+
a.returnHandoffId === base.id &&
|
|
248
|
+
base.state === "configured" &&
|
|
249
|
+
base.reportRevision === d.recovery.baseReportRevision &&
|
|
250
|
+
!s.recoveryId &&
|
|
251
|
+
!isPending() &&
|
|
252
|
+
h.kind === "recovery-request" &&
|
|
253
|
+
h.assignmentId === a.id &&
|
|
254
|
+
h.from === "coordinator" &&
|
|
255
|
+
h.to === assignmentWorker() &&
|
|
256
|
+
h.state === "pending" &&
|
|
257
|
+
!s.handoffs.has(h.id) &&
|
|
258
|
+
!s.recoveries.has(d.recovery.id),
|
|
259
|
+
"recovery_not_available",
|
|
260
|
+
);
|
|
261
|
+
s.recoveries.set(d.recovery.id, {
|
|
262
|
+
...d.recovery,
|
|
263
|
+
requestedResults: [...(d.recovery.requestedResults ?? [])],
|
|
264
|
+
results: (d.recovery.results ?? []).map((grant) => ({ ...grant })),
|
|
265
|
+
});
|
|
266
|
+
s.recoveryId = d.recovery.id;
|
|
267
|
+
s.handoffs.set(h.id, h);
|
|
268
|
+
s.pendingId = h.id;
|
|
269
|
+
s.assessment.view = "suspended";
|
|
270
|
+
s.assessment.suspensionReason = "recovery";
|
|
271
|
+
s.assessment.basisUserEntryId = h.basisUserEntryId;
|
|
272
|
+
s.assessment.problems = [];
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
case "recovery-supplement-accepted": {
|
|
276
|
+
const a = assignment(),
|
|
277
|
+
r = s.recoveries.get(d.recoveryId),
|
|
278
|
+
h = d.handoff,
|
|
279
|
+
previous = r?.supplementHandoffId ? s.handoffs.get(r.supplementHandoffId) : undefined;
|
|
280
|
+
check(
|
|
281
|
+
s.control === "automatic" &&
|
|
282
|
+
s.profile === assignmentWorker() &&
|
|
283
|
+
a?.state === "returned" &&
|
|
284
|
+
r?.id === s.recoveryId &&
|
|
285
|
+
r.assignmentId === a.id &&
|
|
286
|
+
s.assessment?.handoffId === r.assessmentHandoffId &&
|
|
287
|
+
s.handoffs.get(r.assessmentHandoffId)?.reportRevision === r.baseReportRevision &&
|
|
288
|
+
["reading", "returning"].includes(r.state) &&
|
|
289
|
+
h.kind === "recovery-return" &&
|
|
290
|
+
h.assignmentId === a.id &&
|
|
291
|
+
h.from === assignmentWorker() &&
|
|
292
|
+
h.to === "coordinator" &&
|
|
293
|
+
h.state === "pending" &&
|
|
294
|
+
(!previous ? !s.handoffs.has(h.id) : ["pending", "blocked"].includes(previous.state) && previous.id === h.id),
|
|
295
|
+
"recovery_not_returnable",
|
|
296
|
+
);
|
|
297
|
+
const expectedRevision = (previous?.reportRevision ?? 0) + 1;
|
|
298
|
+
check(h.reportRevision === expectedRevision, "invalid_report_revision");
|
|
299
|
+
r.state = "returning";
|
|
300
|
+
r.supplementHandoffId = h.id;
|
|
301
|
+
r.supplementRevision = h.reportRevision;
|
|
302
|
+
s.handoffs.set(h.id, h);
|
|
303
|
+
s.pendingId = h.id;
|
|
304
|
+
break;
|
|
305
|
+
}
|
|
306
|
+
case "recovery-cancelled": {
|
|
307
|
+
const r = s.recoveries.get(d.recoveryId);
|
|
308
|
+
check(
|
|
309
|
+
s.control === "automatic" &&
|
|
310
|
+
s.profile === "coordinator" &&
|
|
311
|
+
r?.id === s.recoveryId &&
|
|
312
|
+
["requested", "reading", "returning"].includes(r.state),
|
|
313
|
+
"recovery_not_cancellable",
|
|
314
|
+
);
|
|
315
|
+
check(!workerRunning((x) => x.recoveryId === r.id), "unresolved_worker_execution");
|
|
316
|
+
const p = pending();
|
|
317
|
+
if (p) {
|
|
318
|
+
check([r.requestHandoffId, r.supplementHandoffId].includes(p.id), "handoff_pending");
|
|
319
|
+
p.state = "superseded";
|
|
320
|
+
s.pendingId = undefined;
|
|
321
|
+
}
|
|
322
|
+
r.state = "cancelled";
|
|
323
|
+
r.cancellationReason = d.reason;
|
|
324
|
+
s.recoveryId = undefined;
|
|
325
|
+
break;
|
|
326
|
+
}
|
|
327
|
+
case "handoff-retry-requested": {
|
|
328
|
+
const h = currentReturn(d.handoffId);
|
|
329
|
+
check(
|
|
330
|
+
s.control === "automatic" &&
|
|
331
|
+
s.profile === assignmentWorker() &&
|
|
332
|
+
["blocked", "pending"].includes(h.state) &&
|
|
333
|
+
h.reportRevision === d.reportRevision &&
|
|
334
|
+
(s.selections.get(h.assignmentId)?.revision ?? 0) === d.selectionRevision,
|
|
335
|
+
"invalid_return_retry",
|
|
336
|
+
);
|
|
337
|
+
s.attempts.set(h.id, {
|
|
338
|
+
executionId: d.executionId,
|
|
339
|
+
reportRevision: d.reportRevision,
|
|
340
|
+
selectionRevision: d.selectionRevision,
|
|
341
|
+
});
|
|
342
|
+
h.state = "pending";
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
case "handoff-prepared": {
|
|
346
|
+
const h = currentReturn(d.handoffId);
|
|
347
|
+
check(["pending", "blocked"].includes(h.state), "handoff_terminal");
|
|
348
|
+
check(d.reservation.selectionRevision === (s.selections.get(h.assignmentId)?.revision ?? 0), "stale_selection");
|
|
349
|
+
s.reservations.set(h.id, d.reservation);
|
|
350
|
+
if (s.assessment?.handoffId === h.id) s.assessment.reservation = d.reservation;
|
|
351
|
+
if (h.kind === "recovery-return") {
|
|
352
|
+
const recovery = [...s.recoveries.values()].find((candidate) => candidate.supplementHandoffId === h.id);
|
|
353
|
+
if (recovery && s.assessment?.handoffId === recovery.assessmentHandoffId)
|
|
354
|
+
s.assessment.reservation = d.reservation;
|
|
355
|
+
}
|
|
356
|
+
break;
|
|
357
|
+
}
|
|
358
|
+
case "handoff-state": {
|
|
359
|
+
const h = s.handoffs.get(d.handoffId);
|
|
360
|
+
check(h && s.pendingId === h.id && ["pending", "blocked"].includes(h.state), "handoff_terminal");
|
|
361
|
+
h.state = d.state;
|
|
362
|
+
h.reason = d.reason;
|
|
363
|
+
if (d.state === "configured") {
|
|
364
|
+
check(d.observedPair, "configuration_unobserved");
|
|
365
|
+
s.profile = h.to;
|
|
366
|
+
s.pendingId = undefined;
|
|
367
|
+
const r = s.recoveryId ? s.recoveries.get(s.recoveryId) : undefined;
|
|
368
|
+
if (r && h.id === r.requestHandoffId) r.state = "reading";
|
|
369
|
+
if (r && h.id === r.supplementHandoffId) {
|
|
370
|
+
r.state = "completed";
|
|
371
|
+
s.recoveryId = undefined;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
if (d.state === "superseded") s.pendingId = undefined;
|
|
375
|
+
break;
|
|
376
|
+
}
|
|
377
|
+
case "selection-changed": {
|
|
378
|
+
const a = assignment();
|
|
379
|
+
check(
|
|
380
|
+
a?.id === d.assignmentId &&
|
|
381
|
+
s.control === "automatic" &&
|
|
382
|
+
s.profile === assignmentWorker() &&
|
|
383
|
+
(a.state === "outstanding" ||
|
|
384
|
+
(a.state === "returned" &&
|
|
385
|
+
(isPending() || (s.recoveryId !== undefined && s.recoveries.get(s.recoveryId)?.state === "reading")))),
|
|
386
|
+
"selection_wrong_phase",
|
|
387
|
+
);
|
|
388
|
+
check(d.selection.revision === (s.selections.get(a.id)?.revision ?? 0) + 1, "selection_revision");
|
|
389
|
+
s.selections.set(a.id, d.selection);
|
|
390
|
+
break;
|
|
391
|
+
}
|
|
392
|
+
case "assessment-suspended": {
|
|
393
|
+
check(s.assessment?.handoffId === d.handoffId, "assessment_missing");
|
|
394
|
+
s.assessment.view = "suspended";
|
|
395
|
+
s.assessment.suspensionReason = d.reason;
|
|
396
|
+
s.assessment.basisUserEntryId = d.basisUserEntryId;
|
|
397
|
+
s.assessment.problems = d.problems;
|
|
398
|
+
break;
|
|
399
|
+
}
|
|
400
|
+
case "assessment-resumed": {
|
|
401
|
+
check(s.control === "automatic" && s.profile === "coordinator", "wrong_control");
|
|
402
|
+
check(!s.recoveryId, "recovery_outstanding");
|
|
403
|
+
check(s.assessment?.handoffId === d.handoffId, "assessment_missing");
|
|
404
|
+
check(s.assessment.view === "suspended", "assessment_not_suspended");
|
|
405
|
+
if (d.reservation)
|
|
406
|
+
check(
|
|
407
|
+
d.reservation.selectionRevision === (s.selections.get(s.assessment.assignmentId)?.revision ?? 0),
|
|
408
|
+
"stale_selection",
|
|
409
|
+
);
|
|
410
|
+
s.assessment.view = "active";
|
|
411
|
+
s.assessment.suspensionReason = undefined;
|
|
412
|
+
s.assessment.basisUserEntryId = d.basisUserEntryId;
|
|
413
|
+
if (d.reservation) {
|
|
414
|
+
s.assessment.reservation = d.reservation;
|
|
415
|
+
s.reservations.set(d.handoffId, d.reservation);
|
|
416
|
+
}
|
|
417
|
+
s.assessment.problems = [];
|
|
418
|
+
break;
|
|
419
|
+
}
|
|
420
|
+
case "unit-closed": {
|
|
421
|
+
const u = s.units.get(d.unitId),
|
|
422
|
+
a = assignment();
|
|
423
|
+
check(
|
|
424
|
+
s.control === "automatic" && s.profile === "coordinator" && u?.id === s.unitId && u.state === "open",
|
|
425
|
+
"unit_not_open",
|
|
426
|
+
);
|
|
427
|
+
if (d.outcome === "accepted")
|
|
428
|
+
check(a?.state !== "outstanding" && !isPending() && !s.recoveryId, "unfinished_work");
|
|
429
|
+
const recovery = s.recoveryId ? s.recoveries.get(s.recoveryId) : undefined;
|
|
430
|
+
if (recovery) {
|
|
431
|
+
check(
|
|
432
|
+
d.outcome !== "accepted" && ["requested", "reading", "returning"].includes(recovery.state),
|
|
433
|
+
"unfinished_work",
|
|
434
|
+
);
|
|
435
|
+
check(!workerRunning((x) => x.recoveryId === recovery.id), "unresolved_worker_execution");
|
|
436
|
+
const transfer = pending();
|
|
437
|
+
if (transfer) {
|
|
438
|
+
check([recovery.requestHandoffId, recovery.supplementHandoffId].includes(transfer.id), "handoff_pending");
|
|
439
|
+
transfer.state = "superseded";
|
|
440
|
+
s.pendingId = undefined;
|
|
441
|
+
}
|
|
442
|
+
recovery.state = "cancelled";
|
|
443
|
+
recovery.cancellationReason = d.assessment;
|
|
444
|
+
s.recoveryId = undefined;
|
|
445
|
+
}
|
|
446
|
+
if (a?.state === "outstanding") {
|
|
447
|
+
check(d.supersededAssignmentId === a.id && d.outcome !== "accepted", "supersession_required");
|
|
448
|
+
check(!workerRunning((x) => x.assignmentId === a.id), "unresolved_worker_execution");
|
|
449
|
+
a.state = "superseded";
|
|
450
|
+
}
|
|
451
|
+
if (pending()) pending().state = "superseded";
|
|
452
|
+
u.state = "closed";
|
|
453
|
+
u.disposition = d.outcome;
|
|
454
|
+
u.assessment = d.assessment;
|
|
455
|
+
s.unitId = undefined;
|
|
456
|
+
s.assignmentId = undefined;
|
|
457
|
+
s.pendingId = undefined;
|
|
458
|
+
s.assessment = undefined;
|
|
459
|
+
break;
|
|
460
|
+
}
|
|
461
|
+
case "request-observed": {
|
|
462
|
+
const x = s.executions.get(d.executionId);
|
|
463
|
+
check(x && x.profile === d.profile, "request_execution_missing");
|
|
464
|
+
if (d.handoffId) check(s.handoffs.has(d.handoffId), "request_handoff_missing");
|
|
465
|
+
break;
|
|
466
|
+
}
|
|
467
|
+
case "sources-exposed": {
|
|
468
|
+
const x = s.executions.get(d.executionId);
|
|
469
|
+
check(x && x.profile === d.view, "exposure_execution_missing");
|
|
470
|
+
if (isWorkerProfile(d.view)) for (const source of d.sources) s.exposure.set(source.ref, source.bodyHash);
|
|
471
|
+
break;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
s.events.set(key, e);
|
|
475
|
+
s.eventIds.set(e.eventId, value);
|
|
476
|
+
return s;
|
|
477
|
+
}
|
|
478
|
+
export function reduce(state, event) {
|
|
479
|
+
return applyEvent(state, event);
|
|
480
|
+
}
|
|
481
|
+
export function replay(entries) {
|
|
482
|
+
let state = initialState();
|
|
483
|
+
for (const entry of entries)
|
|
484
|
+
if (entry.type === "custom" && entry.customType === ROUTING_ENTRY)
|
|
485
|
+
state = applyEvent(state, parseRoutingEvent(entry.data), true);
|
|
486
|
+
return state;
|
|
487
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { initialState } from "./state.js";
|
|
2
|
+
/**
|
|
3
|
+
* What routing reports about itself: the summary behind Freeflow's status line and the Runtime State sent with each
|
|
4
|
+
* request (whether routing is active or blocked and why, the running profile, control mode, a switch waiting for the
|
|
5
|
+
* next prompt, preset warnings), and the detailed view with unit, assignment, handoff, recovery, and recent events
|
|
6
|
+
* used by unit status and diagnostics.
|
|
7
|
+
*/
|
|
8
|
+
export class RoutingStatus {
|
|
9
|
+
session;
|
|
10
|
+
models;
|
|
11
|
+
constructor(session, models) {
|
|
12
|
+
this.session = session;
|
|
13
|
+
this.models = models;
|
|
14
|
+
}
|
|
15
|
+
state() {
|
|
16
|
+
let state;
|
|
17
|
+
try {
|
|
18
|
+
state = this.session.stateData();
|
|
19
|
+
} catch {
|
|
20
|
+
state = initialState();
|
|
21
|
+
}
|
|
22
|
+
const blocked = this.session.error ?? this.session.store?.blocked;
|
|
23
|
+
return {
|
|
24
|
+
effective: this.session.supported() && state.control !== "inactive" && !blocked,
|
|
25
|
+
activeProfile: state.profile,
|
|
26
|
+
delegation: this.session.delegation(state),
|
|
27
|
+
controlMode: state.control === "manual" ? `manual-${state.profile}` : state.control,
|
|
28
|
+
runtimeStatus: blocked
|
|
29
|
+
? "blocked"
|
|
30
|
+
: this.session.supported() && state.control !== "inactive"
|
|
31
|
+
? "active"
|
|
32
|
+
: "inactive",
|
|
33
|
+
runtimeReason:
|
|
34
|
+
blocked ?? (this.session.suppressed ? "startup_selection" : this.session.capability?.blockingReason.message),
|
|
35
|
+
projectionFailure: this.session.projectionError,
|
|
36
|
+
...((pending) => (pending ? { pendingPair: `${pending.modelId}/${pending.thinking}` } : {}))(
|
|
37
|
+
this.models.pending(),
|
|
38
|
+
),
|
|
39
|
+
...(this.models.heldMismatch(state) ? { pairMismatch: true } : {}),
|
|
40
|
+
...((warnings) => (warnings.length ? { presetWarnings: warnings } : {}))(this.models.presetWarnings(state)),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
detail(limit = 0) {
|
|
44
|
+
const state = this.session.stateData();
|
|
45
|
+
const unit = state.unitId ? state.units.get(state.unitId) : undefined;
|
|
46
|
+
const a = state.assignmentId ? state.assignments.get(state.assignmentId) : undefined;
|
|
47
|
+
return {
|
|
48
|
+
...this.state(),
|
|
49
|
+
unit: unit ? { id: unit.id, state: unit.state, assignments: unit.assignmentIds.length } : null,
|
|
50
|
+
assignment: a ? { id: a.id, state: a.state, worker: this.session.assignedWorker(state, a.id) } : null,
|
|
51
|
+
pendingHandoff: state.pendingId ?? null,
|
|
52
|
+
recovery: state.recoveryId
|
|
53
|
+
? (() => {
|
|
54
|
+
const recovery = state.recoveries.get(state.recoveryId);
|
|
55
|
+
return recovery
|
|
56
|
+
? {
|
|
57
|
+
id: recovery.id,
|
|
58
|
+
state: recovery.state,
|
|
59
|
+
assignmentId: recovery.assignmentId,
|
|
60
|
+
assessmentHandoffId: recovery.assessmentHandoffId,
|
|
61
|
+
paths: recovery.paths,
|
|
62
|
+
results: recovery.results ?? [],
|
|
63
|
+
supplementRevision: recovery.supplementRevision,
|
|
64
|
+
}
|
|
65
|
+
: null;
|
|
66
|
+
})()
|
|
67
|
+
: null,
|
|
68
|
+
assessment: state.assessment
|
|
69
|
+
? { handoffId: state.assessment.handoffId, view: state.assessment.view, problems: state.assessment.problems }
|
|
70
|
+
: null,
|
|
71
|
+
...(limit
|
|
72
|
+
? {
|
|
73
|
+
history: [...state.events.values()]
|
|
74
|
+
.slice(-Math.min(100, Math.max(1, limit)))
|
|
75
|
+
.map((e) => ({ event: e.eventId, type: e.data.type, operation: e.operationId })),
|
|
76
|
+
}
|
|
77
|
+
: {}),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { ROUTING_TOOLS } from "./types.js";
|
|
2
|
+
import { ROUTING_SCHEMAS, explainRoutingArguments, matches } from "./schemas.js";
|
|
3
|
+
export { ROUTING_SCHEMAS } from "./schemas.js";
|
|
4
|
+
import { renderRoutingCall, renderRoutingResult } from "./render.js";
|
|
5
|
+
const descriptions = {
|
|
6
|
+
freeflow_delegate:
|
|
7
|
+
"Save a supported assignment contract and request enabled worker execution. Explicit replace supersedes a quiescent outstanding assignment in the same unit.",
|
|
8
|
+
freeflow_return:
|
|
9
|
+
"Save or revise the actual assignment report, save an attached recovery supplement, or retry the current saved return without resubmitting text. Returning does not accept or close the unit.",
|
|
10
|
+
freeflow_unit:
|
|
11
|
+
"Inspect current responsibility, paginated work history, or exact saved communication. Coordinator can restore an assessment, request or cancel attached evidence recovery, or close a unit without clearing history.",
|
|
12
|
+
freeflow_project:
|
|
13
|
+
"Inspect current selection and scoped candidates when needed, then add or remove exposed worker task-evidence refs. Strict shapes: inspect uses operation plus optional scope/cursor; add uses operation plus eligible refs only; remove uses operation plus currently selected or unresolved refs plus required reason. Reason is remove-only. Only tool results are evidence; put anything you would say in the report.",
|
|
14
|
+
};
|
|
15
|
+
const guidance = {
|
|
16
|
+
freeflow_delegate: [
|
|
17
|
+
"freeflow_delegate is Coordinator-only under Automatic control. Put the contract in the input. Choose worker when both Helper and Executor are enabled; the sole worker is inferred otherwise. Use replace only for explicit same-unit replacement; do not supply IDs.",
|
|
18
|
+
],
|
|
19
|
+
freeflow_return: [
|
|
20
|
+
"freeflow_return submit saves/revises the assignment report; supplement saves separate recovery communication; retry completes the current unchanged saved return without text. Stop ordinary task or recovery-read work after its communication is saved. Keep the handoff last; do not batch it with new task tools.",
|
|
21
|
+
],
|
|
22
|
+
freeflow_unit: [
|
|
23
|
+
"freeflow_unit inspect defaults to current state; view history returns work refs and detail reads saved communication. Coordinator recover requests bounded evidence work against the current returned assessment; cancel-recovery ends only that recovery; assess restores a suspended evidence obligation; close changes work status without clearing communication or completing a Working Record task.",
|
|
24
|
+
],
|
|
25
|
+
freeflow_project: [
|
|
26
|
+
"Use exactly one shape: inspect {operation, scope?, cursor?}; add {operation, refs} with no reason; remove {operation, refs, reason}. Reason is required only for remove and invalid for add/inspect. Add exact eligible visible refs directly when identity and eligibility are clear; inspect when identity, eligibility, representation or selection state is unclear. Never add refs marked not offered for new evidence selection. For remove, use currently selected or unresolved refs; a non-selectable ref may be named only to clear its unresolved request and requires a reason. Only tool results are evidence: select the result bodies behind your claims, not calls or your own messages, and say everything else in the report. Correct or explicitly withdraw unresolved items with a limitation. Known previously exposed bodies resolve automatically; target-representation gaps are not successful delivery.",
|
|
27
|
+
],
|
|
28
|
+
};
|
|
29
|
+
export function registerRoutingTools(pi, runtime) {
|
|
30
|
+
for (const name of ROUTING_TOOLS) {
|
|
31
|
+
const definition = {
|
|
32
|
+
name,
|
|
33
|
+
label: name.replace("freeflow_", "Routing "),
|
|
34
|
+
description: descriptions[name],
|
|
35
|
+
parameters: ROUTING_SCHEMAS[name],
|
|
36
|
+
// Handoffs and evidence selection are assistant-issued boundaries: codemode scripts must not call them.
|
|
37
|
+
exposure: "model-only",
|
|
38
|
+
executionMode: "sequential",
|
|
39
|
+
promptGuidelines: guidance[name],
|
|
40
|
+
renderCall: (args, _theme, context) => renderRoutingCall(name, args, context),
|
|
41
|
+
renderResult: (result, options, _theme, context) => renderRoutingResult(result, options, name, context),
|
|
42
|
+
async execute(id, input, signal, _update, ctx) {
|
|
43
|
+
if (!matches(input, ROUTING_SCHEMAS[name])) throw new Error(explainRoutingArguments(name, input));
|
|
44
|
+
return runtime.invoke(name, id, input, signal, ctx);
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
pi.registerTool(definition);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export function applyRoutingToolVisibility(pi, _runtime, _available = true) {
|
|
51
|
+
if (!pi.getActiveTools || !pi.setActiveTools) return;
|
|
52
|
+
const current = pi.getActiveTools();
|
|
53
|
+
// Missing routing tools are appended and present ones keep their place, so tools another extension adds later
|
|
54
|
+
// (tool_search, for one) are never reordered around them.
|
|
55
|
+
const kept = current.filter(
|
|
56
|
+
(name) => name !== "freeflow_switch_profile" && name !== "freeflow_cognitive_routing_history",
|
|
57
|
+
);
|
|
58
|
+
const next = [...kept, ...ROUTING_TOOLS.filter((name) => !kept.includes(name))];
|
|
59
|
+
if (JSON.stringify(next) !== JSON.stringify(current)) pi.setActiveTools(next);
|
|
60
|
+
}
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
+
import { canonical } from "../host/canonical.js";
|
|
1
2
|
export const ROUTING_ENTRY = "freeflow-routing-v2";
|
|
2
3
|
export const ROUTING_MESSAGE = "freeflow-routing-v2-state";
|
|
3
|
-
export const
|
|
4
|
+
export const ROUTING_ATTENTION_MESSAGE = "freeflow-routing-attention";
|
|
5
|
+
export const WORKER_PROFILES = ["helper", "executor"];
|
|
6
|
+
export const PROFILES = ["coordinator", ...WORKER_PROFILES];
|
|
7
|
+
export const DELEGATION_MODES = ["executor", "helper", "both"];
|
|
4
8
|
export const EFFORTS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
9
|
+
export const isWorkerProfile = (value) => WORKER_PROFILES.includes(value);
|
|
10
|
+
export const workersForDelegation = (mode) => (mode === "both" ? WORKER_PROFILES : [mode]);
|
|
5
11
|
export class RoutingError extends Error {
|
|
6
12
|
code;
|
|
7
13
|
problems;
|
|
@@ -21,16 +27,6 @@ export function requireCondition(condition, code, message = code) {
|
|
|
21
27
|
export function isObject(value) {
|
|
22
28
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
23
29
|
}
|
|
24
|
-
export function canonical(value) {
|
|
25
|
-
if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
|
|
26
|
-
if (isObject(value))
|
|
27
|
-
return `{${Object.keys(value)
|
|
28
|
-
.filter((k) => value[k] !== undefined)
|
|
29
|
-
.sort()
|
|
30
|
-
.map((k) => `${JSON.stringify(k)}:${canonical(value[k])}`)
|
|
31
|
-
.join(",")}}`;
|
|
32
|
-
return JSON.stringify(value) ?? "null";
|
|
33
|
-
}
|
|
34
30
|
export function samePair(left, right) {
|
|
35
31
|
return !!left && !!right && canonical(left) === canonical(right);
|
|
36
32
|
}
|
|
@@ -40,5 +36,7 @@ export const eventValue = (event) =>
|
|
|
40
36
|
export const emptySelection = () => ({ revision: 0, selected: [], unresolved: [], withdrawals: [] });
|
|
41
37
|
export const refFor = (id) => `ctx:${id}`;
|
|
42
38
|
export function idFor(ref) {
|
|
43
|
-
return /^ctx:([^\s:#]+)
|
|
39
|
+
return /^ctx:([^\s:#]+)$/.exec(ref)?.[1];
|
|
44
40
|
}
|
|
41
|
+
export { canonical };
|
|
42
|
+
export const ROUTING_TOOLS = ["freeflow_delegate", "freeflow_return", "freeflow_unit", "freeflow_project"];
|