@hassangameryt/freeflow 0.4.0 → 0.6.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 +2 -1
- package/.codex-plugin/plugin.json +2 -1
- package/README.md +150 -155
- package/capabilities/cognitive-routing/SKILL.md +229 -0
- package/capabilities/context-virtualization/SKILL.md +103 -0
- package/capabilities/conversation-history/SKILL.md +53 -0
- package/command-surface.json +17 -13
- package/hooks/freeflow-runtime-context.mjs +393 -159
- package/hooks/hooks.json +26 -1
- package/hooks/tests/check-runtime-context-hook.sh +223 -54
- package/package.json +25 -16
- package/pi-extension/dist/cognitive-routing/commands.js +96 -0
- package/pi-extension/dist/cognitive-routing/config.js +236 -0
- package/pi-extension/dist/cognitive-routing/controller.js +862 -0
- package/pi-extension/dist/cognitive-routing/history.js +439 -0
- package/pi-extension/dist/cognitive-routing/host.js +126 -0
- package/pi-extension/dist/cognitive-routing/runtime.js +43 -0
- package/pi-extension/dist/cognitive-routing/tool.js +211 -0
- package/pi-extension/dist/cognitive-routing/types.js +6 -0
- package/pi-extension/dist/context-virtualization/commands.js +89 -0
- package/pi-extension/dist/context-virtualization/projector.js +42 -0
- package/pi-extension/dist/context-virtualization/runtime.js +356 -0
- package/pi-extension/dist/context-virtualization/state.js +57 -0
- package/pi-extension/dist/context-virtualization/types.js +10 -0
- package/pi-extension/dist/conversation-history/passages.js +66 -0
- package/pi-extension/dist/conversation-history/ranking.js +105 -0
- package/pi-extension/dist/conversation-history/runtime.js +226 -0
- package/pi-extension/dist/conversation-history/search.js +56 -0
- package/pi-extension/dist/conversation-history/types.js +1 -0
- package/pi-extension/dist/freeflow-context/resolver.js +120 -0
- package/pi-extension/dist/freeflow-context/runtime.js +80 -0
- package/pi-extension/dist/freeflow-context/source-projector.js +95 -0
- package/pi-extension/dist/freeflow-context/tool.js +308 -0
- package/pi-extension/dist/freeflow-context/types.js +12 -0
- package/pi-extension/dist/freeflow-context/visibility.js +28 -0
- package/pi-extension/dist/index.js +516 -98
- package/pi-extension/dist/runtime/runtime-context.js +318 -271
- package/pi-extension/dist/runtime/runtime-identity.js +5 -0
- package/pi-extension/dist/settings/settings-tui.js +78 -0
- package/pi-extension/dist/settings/settings-ui.js +593 -563
- package/pi-extension/dist/ui/text-component.js +57 -0
- package/pi-extension/src/cognitive-routing/commands.ts +119 -0
- package/pi-extension/src/cognitive-routing/config.ts +307 -0
- package/pi-extension/src/cognitive-routing/controller.ts +1118 -0
- package/pi-extension/src/cognitive-routing/history.ts +638 -0
- package/pi-extension/src/cognitive-routing/host.ts +172 -0
- package/pi-extension/src/cognitive-routing/runtime.ts +57 -0
- package/pi-extension/src/cognitive-routing/tool.ts +272 -0
- package/pi-extension/src/cognitive-routing/types.ts +89 -0
- package/pi-extension/src/context-virtualization/commands.ts +111 -0
- package/pi-extension/src/context-virtualization/projector.ts +62 -0
- package/pi-extension/src/context-virtualization/runtime.ts +406 -0
- package/pi-extension/src/context-virtualization/state.ts +79 -0
- package/pi-extension/src/context-virtualization/types.ts +92 -0
- package/pi-extension/src/conversation-history/passages.ts +81 -0
- package/pi-extension/src/conversation-history/ranking.ts +155 -0
- package/pi-extension/src/conversation-history/runtime.ts +250 -0
- package/pi-extension/src/conversation-history/search.ts +79 -0
- package/pi-extension/src/conversation-history/types.ts +31 -0
- package/pi-extension/src/freeflow-context/resolver.ts +139 -0
- package/pi-extension/src/freeflow-context/runtime.ts +100 -0
- package/pi-extension/src/freeflow-context/source-projector.ts +106 -0
- package/pi-extension/src/freeflow-context/tool.ts +360 -0
- package/pi-extension/src/freeflow-context/types.ts +70 -0
- package/pi-extension/src/freeflow-context/visibility.ts +53 -0
- package/pi-extension/src/index.ts +553 -101
- package/pi-extension/src/runtime/runtime-context.ts +339 -281
- package/pi-extension/src/runtime/runtime-identity.ts +8 -0
- package/pi-extension/src/settings/settings-tui.ts +93 -0
- package/pi-extension/src/settings/settings-ui.ts +700 -613
- package/pi-extension/src/ui/text-component.ts +66 -0
- package/pi-extension/tests/cognitive-routing/commands.test.js +134 -0
- package/pi-extension/tests/cognitive-routing/controller.test.js +1099 -0
- package/pi-extension/tests/cognitive-routing/extension-lifecycle.test.js +1071 -0
- package/pi-extension/tests/cognitive-routing/history.test.js +274 -0
- package/pi-extension/tests/cognitive-routing/host-fixture.js +4 -0
- package/pi-extension/tests/cognitive-routing/host-identity.test.js +63 -0
- package/pi-extension/tests/cognitive-routing/profile-resolution.test.js +236 -0
- package/pi-extension/tests/cognitive-routing/tool.test.js +230 -0
- package/pi-extension/tests/context-virtualization/extension.test.js +749 -0
- package/pi-extension/tests/context-virtualization/projector.test.js +110 -0
- package/pi-extension/tests/context-virtualization/runtime.test.js +216 -0
- package/pi-extension/tests/conversation-history/runtime.test.js +98 -0
- package/pi-extension/tests/conversation-history/search.test.js +97 -0
- package/pi-extension/tests/integration/pi-extension.test.js +928 -2047
- package/pi-extension/tests/integration/prompt-architecture.test.js +305 -0
- package/runtime/prompts/cognitive-routing.md +9 -0
- package/runtime/prompts/context-virtualization.md +3 -0
- package/runtime/prompts/conversation-history.md +3 -0
- package/runtime/prompts/core.md +13 -0
- package/runtime/prompts/interaction-contract.md +7 -0
- package/runtime/prompts/skills.md +65 -0
- package/skills/action-selection/SKILL.md +91 -0
- package/skills/bypass/SKILL.md +3 -2
- package/skills/commit-work/SKILL.md +1 -1
- package/skills/commit-work/references/staging-decisions.md +1 -1
- package/skills/decision-gate/SKILL.md +1 -1
- package/skills/design-for-depth/SKILL.md +14 -33
- package/skills/design-for-depth/references/design-pressure-signals.md +1 -1
- package/skills/design-for-depth/references/interface-design-loop.md +1 -1
- package/skills/design-for-depth/references/software-design-philosophy.md +1 -1
- package/skills/diagnose-failure/SKILL.md +20 -14
- package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +1 -3
- package/skills/diagnose-failure/references/{flaky-and-performance.md → flaky-and-performance-diagnosis.md} +1 -1
- package/skills/discuss/SKILL.md +34 -22
- package/skills/discuss/references/discussion-continuity.md +71 -0
- package/skills/evaluate-skill/SKILL.md +5 -5
- package/skills/evaluate-skill/references/{definition-schema.md → evaluation-definition-schema.md} +42 -2
- package/skills/evaluate-skill/references/execution-and-evidence.md +8 -3
- package/skills/evaluate-skill/scripts/lib/definitions.mjs +114 -3
- package/skills/evaluate-skill/scripts/lib/grade-interaction.mjs +156 -1
- package/skills/evaluate-skill/scripts/lib/grade.mjs +9 -1
- package/skills/evaluate-skill/scripts/lib/pi.mjs +147 -32
- package/skills/evaluate-skill/scripts/lib/rpc.mjs +4 -4
- package/skills/evaluate-skill/scripts/lib/runner.mjs +36 -14
- package/skills/evaluate-skill/scripts/lib/sandbox.mjs +92 -0
- package/skills/evaluate-skill/scripts/pi-guard.mjs +1 -1
- package/skills/evaluate-skill/scripts/pi-observer.mjs +66 -0
- package/skills/evaluate-skill/scripts/skill-eval.mjs +6 -5
- package/skills/execute-work/SKILL.md +18 -15
- package/skills/execute-work/references/code-practices.md +1 -1
- package/skills/execute-work/references/execute-work-edges.md +42 -0
- package/skills/finish-branch/SKILL.md +2 -2
- package/skills/finish-branch/references/integration-options.md +1 -1
- package/skills/handoff/SKILL.md +3 -3
- package/skills/launch-work/SKILL.md +1 -1
- package/skills/launch-work/references/launch-readiness.md +2 -2
- package/skills/migration-work/SKILL.md +2 -2
- package/skills/migration-work/references/migration-lifecycle.md +1 -1
- package/skills/mode-contract/SKILL.md +32 -10
- package/skills/release-work/SKILL.md +1 -1
- package/skills/release-work/references/release-evidence.md +1 -1
- package/skills/review-artifact/SKILL.md +28 -26
- package/skills/review-work/SKILL.md +28 -26
- package/skills/review-work/references/{reviewer-prompt.md → independent-work-reviewer-contract.md} +1 -1
- package/skills/review-work/references/security-risk-lens.md +1 -1
- package/skills/setup-freeflow/SKILL.md +6 -10
- package/skills/setup-freeflow/references/activation-contract.md +5 -10
- package/skills/setup-freeflow/references/host-setup.md +3 -7
- package/skills/simplify-code/SKILL.md +1 -1
- package/skills/simplify-code/references/simplification-patterns.md +1 -1
- package/skills/tdd/SKILL.md +18 -38
- package/skills/tdd/references/test-design.md +2 -2
- package/skills/track-work/SKILL.md +362 -58
- package/skills/track-work/scripts/lib/codec.mjs +570 -0
- package/skills/track-work/scripts/lib/commands.mjs +568 -0
- package/skills/track-work/scripts/lib/contract.mjs +303 -0
- package/skills/track-work/scripts/lib/model.mjs +506 -0
- package/skills/track-work/scripts/lib/operations.mjs +965 -0
- package/skills/track-work/scripts/lib/record-store.mjs +264 -0
- package/skills/track-work/scripts/lib/result.mjs +37 -0
- package/skills/track-work/scripts/lib/rewrites.mjs +740 -0
- package/skills/track-work/scripts/lib/schema.mjs +277 -0
- package/skills/track-work/scripts/lib/source-inventory.mjs +162 -0
- package/skills/track-work/scripts/lib/views.mjs +443 -0
- package/skills/track-work/scripts/lib/workspace.mjs +134 -0
- package/skills/track-work/scripts/working-record.mjs +122 -0
- package/skills/verify-work/SKILL.md +14 -2
- package/skills/verify-work/references/browser-runtime-evidence.md +1 -1
- package/skills/verify-work/references/integration-evidence.md +1 -1
- package/skills/verify-work/references/performance-evidence.md +1 -1
- package/skills/workflow/SKILL.md +105 -53
- package/skills/workflow/references/domain-skill-composition.md +22 -42
- package/skills/write-plan/SKILL.md +1 -1
- package/skills/write-skill/SKILL.md +3 -2
- package/skills/write-skill/references/progressive-disclosure.md +17 -3
- package/pi-extension/dist/output-router/host-producer-identification.js +0 -232
- package/pi-extension/dist/output-router/native-safety-net.js +0 -216
- package/pi-extension/dist/output-router/observed-tool-routing.js +0 -122
- package/pi-extension/dist/output-router/renderers.js +0 -1023
- package/pi-extension/dist/output-router/router-tools.js +0 -554
- package/pi-extension/dist/output-router/schemas.js +0 -383
- package/pi-extension/dist/output-router/status.js +0 -744
- package/pi-extension/dist/output-router/utils.js +0 -502
- package/pi-extension/src/output-router/host-producer-identification.ts +0 -256
- package/pi-extension/src/output-router/native-safety-net.ts +0 -248
- package/pi-extension/src/output-router/observed-tool-routing.ts +0 -134
- package/pi-extension/src/output-router/renderers.ts +0 -1108
- package/pi-extension/src/output-router/router-tools.ts +0 -579
- package/pi-extension/src/output-router/schemas.ts +0 -387
- package/pi-extension/src/output-router/status.ts +0 -791
- package/pi-extension/src/output-router/utils.ts +0 -554
- package/pi-extension/tests/output-router/observed-producers.test.js +0 -132
- package/pi-extension/tests/output-router/search.test.js +0 -722
- package/router/dist/benchmarks/benchmark-harness.d.ts +0 -35
- package/router/dist/benchmarks/benchmark-harness.js +0 -92
- package/router/dist/benchmarks/benchmarks.d.ts +0 -109
- package/router/dist/benchmarks/benchmarks.js +0 -979
- package/router/dist/benchmarks/codex-qa-benchmarks.d.ts +0 -95
- package/router/dist/benchmarks/codex-qa-benchmarks.js +0 -500
- package/router/dist/benchmarks/command-benchmarks.d.ts +0 -143
- package/router/dist/benchmarks/command-benchmarks.js +0 -698
- package/router/dist/benchmarks/context-mode-normalized-benchmarks.d.ts +0 -82
- package/router/dist/benchmarks/context-mode-normalized-benchmarks.js +0 -773
- package/router/dist/benchmarks/context-mode-real-deep-benchmark.d.ts +0 -132
- package/router/dist/benchmarks/context-mode-real-deep-benchmark.js +0 -1380
- package/router/dist/benchmarks/context-saving-baseline.d.ts +0 -60
- package/router/dist/benchmarks/context-saving-baseline.js +0 -470
- package/router/dist/benchmarks/index-benchmarks.d.ts +0 -103
- package/router/dist/benchmarks/index-benchmarks.js +0 -862
- package/router/dist/benchmarks/storage-policy-benchmarks.d.ts +0 -101
- package/router/dist/benchmarks/storage-policy-benchmarks.js +0 -618
- package/router/dist/benchmarks/vault-index-benchmarks.d.ts +0 -56
- package/router/dist/benchmarks/vault-index-benchmarks.js +0 -322
- package/router/dist/config/config.d.ts +0 -103
- package/router/dist/config/config.js +0 -632
- package/router/dist/config/router-contract.d.ts +0 -10
- package/router/dist/config/router-contract.js +0 -54
- package/router/dist/config/schema.d.ts +0 -27
- package/router/dist/config/schema.js +0 -648
- package/router/dist/config/types.d.ts +0 -573
- package/router/dist/config/types.js +0 -41
- package/router/dist/evidence/bounded-evidence.d.ts +0 -40
- package/router/dist/evidence/bounded-evidence.js +0 -323
- package/router/dist/evidence/evidence-range-selector.d.ts +0 -24
- package/router/dist/evidence/evidence-range-selector.js +0 -232
- package/router/dist/evidence/evidence-search.d.ts +0 -27
- package/router/dist/evidence/evidence-search.js +0 -490
- package/router/dist/evidence/evidence.d.ts +0 -38
- package/router/dist/evidence/evidence.js +0 -200
- package/router/dist/evidence/failure-contracts.d.ts +0 -37
- package/router/dist/evidence/failure-contracts.js +0 -166
- package/router/dist/evidence/line-ranges.d.ts +0 -24
- package/router/dist/evidence/line-ranges.js +0 -22
- package/router/dist/experiments/local-index.d.ts +0 -64
- package/router/dist/experiments/local-index.js +0 -581
- package/router/dist/index.d.ts +0 -30
- package/router/dist/index.js +0 -32
- package/router/dist/local/local-traversal.d.ts +0 -18
- package/router/dist/local/local-traversal.js +0 -334
- package/router/dist/processing/engine.d.ts +0 -132
- package/router/dist/processing/engine.js +0 -533
- package/router/dist/processing/reducers.d.ts +0 -280
- package/router/dist/processing/reducers.js +0 -1673
- package/router/dist/processing/renderers.d.ts +0 -31
- package/router/dist/processing/renderers.js +0 -195
- package/router/dist/processing/scripts.d.ts +0 -91
- package/router/dist/processing/scripts.js +0 -386
- package/router/dist/repo/repo-traversal.d.ts +0 -18
- package/router/dist/repo/repo-traversal.js +0 -245
- package/router/dist/routing/observed-reducers.d.ts +0 -41
- package/router/dist/routing/observed-reducers.js +0 -661
- package/router/dist/routing/observed-routing.d.ts +0 -26
- package/router/dist/routing/observed-routing.js +0 -378
- package/router/dist/routing/parsers.d.ts +0 -16
- package/router/dist/routing/parsers.js +0 -521
- package/router/dist/routing/run-filters.d.ts +0 -60
- package/router/dist/routing/run-filters.js +0 -238
- package/router/dist/routing/run-reducers.d.ts +0 -49
- package/router/dist/routing/run-reducers.js +0 -122
- package/router/dist/sandbox/adapter-roots.d.ts +0 -46
- package/router/dist/sandbox/adapter-roots.js +0 -66
- package/router/dist/sandbox/eryx-python-adapter.d.ts +0 -13
- package/router/dist/sandbox/eryx-python-adapter.js +0 -868
- package/router/dist/sandbox/jq-wasm-adapter.d.ts +0 -9
- package/router/dist/sandbox/jq-wasm-adapter.js +0 -429
- package/router/dist/sandbox/quickjs-wasi-adapter.d.ts +0 -13
- package/router/dist/sandbox/quickjs-wasi-adapter.js +0 -384
- package/router/dist/sandbox/script-sandbox.d.ts +0 -134
- package/router/dist/sandbox/script-sandbox.js +0 -337
- package/router/dist/setup/script-transform-adapters.d.ts +0 -32
- package/router/dist/setup/script-transform-adapters.js +0 -278
- package/router/dist/tools/batch.d.ts +0 -33
- package/router/dist/tools/batch.js +0 -586
- package/router/dist/tools/run.d.ts +0 -62
- package/router/dist/tools/run.js +0 -1304
- package/router/dist/tools/search.d.ts +0 -66
- package/router/dist/tools/search.js +0 -1364
- package/router/dist/transform/engine.d.ts +0 -168
- package/router/dist/transform/engine.js +0 -2072
- package/router/dist/vault/vault-index.d.ts +0 -125
- package/router/dist/vault/vault-index.js +0 -416
- package/router/dist/vault/vault.d.ts +0 -138
- package/router/dist/vault/vault.js +0 -630
- package/runtime/interaction-contract.md +0 -16
- package/skills/discuss/references/checkpoints.md +0 -66
- package/skills/execute-work/references/execution-loop.md +0 -77
- package/skills/output-router/SKILL.md +0 -206
- package/skills/output-router/references/safety-policy.md +0 -40
- package/skills/setup-freeflow/references/output-router-setup.md +0 -110
- package/skills/track-work/references/working-record-schema.md +0 -378
- package/skills/workflow/references/workflow-loop.md +0 -92
- /package/skills/handoff/references/{templates.md → handoff-templates.md} +0 -0
- /package/skills/review-artifact/references/{reviewer-prompt.md → independent-artifact-reviewer-contract.md} +0 -0
|
@@ -2,17 +2,6 @@ import { execFile } from "node:child_process";
|
|
|
2
2
|
import { appendFile, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
4
4
|
import { promisify } from "node:util";
|
|
5
|
-
import {
|
|
6
|
-
DEFAULT_OBSERVED_ROUTING_CONFIG,
|
|
7
|
-
DEFAULT_OUTPUT_ROUTER_ENABLED,
|
|
8
|
-
DEFAULT_POST_TOOL_ROUTING,
|
|
9
|
-
DEFAULT_ROUTER_THRESHOLDS,
|
|
10
|
-
DEFAULT_SCRIPT_TRANSFORM_CONFIG,
|
|
11
|
-
DEFAULT_STORAGE_POLICY,
|
|
12
|
-
DEFAULT_VAULT_RETENTION,
|
|
13
|
-
DEFAULT_VAULT_ROOT,
|
|
14
|
-
normalizeFreeflowConfig,
|
|
15
|
-
} from "../../../router/dist/index.js";
|
|
16
5
|
import {
|
|
17
6
|
handleModeCommand,
|
|
18
7
|
readCapabilityState,
|
|
@@ -25,13 +14,12 @@ import {
|
|
|
25
14
|
VALID_MODES,
|
|
26
15
|
} from "../runtime/runtime-context.js";
|
|
27
16
|
import { PiSettingsComponent } from "./settings-tui.js";
|
|
28
|
-
|
|
29
|
-
const STORAGE_POLICY_VALUES = ["hybrid-dedupe", "store-everything"];
|
|
30
|
-
const OBSERVED_PERSISTENCE_VALUES = ["none", "metadata-only", "exact"];
|
|
31
|
-
const SCRIPT_LANGUAGES = ["javascript", "python", "jq"];
|
|
17
|
+
import { isPiFlowHost } from "../runtime/runtime-identity.js";
|
|
32
18
|
const DEFAULT_FREEFLOW_ENABLED = true;
|
|
33
19
|
const DEFAULT_INTERACTION_CONTRACT_ENABLED = true;
|
|
34
20
|
const DEFAULT_SKILLS_ENABLED = true;
|
|
21
|
+
const DEFAULT_CONTEXT_VIRTUALIZATION_ENABLED = false;
|
|
22
|
+
const DEFAULT_CONVERSATION_HISTORY_ENABLED = false;
|
|
35
23
|
const MODE_VALUES = ["conversation", "workflow", "strict-workflow"];
|
|
36
24
|
const MODE_LABELS = {
|
|
37
25
|
default: "Use configured default",
|
|
@@ -114,288 +102,9 @@ function setConfigValue(config, item, value) {
|
|
|
114
102
|
}
|
|
115
103
|
setPath(config, item.path, value);
|
|
116
104
|
}
|
|
117
|
-
function parseStringList(text) {
|
|
118
|
-
return text
|
|
119
|
-
.split(",")
|
|
120
|
-
.map((item) => item.trim())
|
|
121
|
-
.filter(Boolean);
|
|
122
|
-
}
|
|
123
|
-
function parsePositiveInteger(text) {
|
|
124
|
-
const value = Number(text.trim());
|
|
125
|
-
if (!Number.isInteger(value) || value <= 0) {
|
|
126
|
-
throw new Error("Expected a positive integer.");
|
|
127
|
-
}
|
|
128
|
-
return value;
|
|
129
|
-
}
|
|
130
|
-
function parseJsonObject(text) {
|
|
131
|
-
const trimmed = text.trim();
|
|
132
|
-
if (!trimmed) return {};
|
|
133
|
-
let parsed;
|
|
134
|
-
try {
|
|
135
|
-
parsed = JSON.parse(trimmed);
|
|
136
|
-
} catch (error) {
|
|
137
|
-
throw new Error(`Expected valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
138
|
-
}
|
|
139
|
-
if (!isRecord(parsed)) {
|
|
140
|
-
throw new Error("Expected a JSON object.");
|
|
141
|
-
}
|
|
142
|
-
return parsed;
|
|
143
|
-
}
|
|
144
|
-
function formatList(value) {
|
|
145
|
-
return Array.isArray(value) ? value.join(", ") : "";
|
|
146
|
-
}
|
|
147
|
-
function formatJson(value) {
|
|
148
|
-
return JSON.stringify(isRecord(value) ? value : {});
|
|
149
|
-
}
|
|
150
105
|
function booleanValue(value) {
|
|
151
106
|
return value === true ? "enabled" : "disabled";
|
|
152
107
|
}
|
|
153
|
-
function normalizedSettingsConfig(rawConfig) {
|
|
154
|
-
const rawRouter = isRecord(rawConfig.outputRouter) ? rawConfig.outputRouter : {};
|
|
155
|
-
const normalized = normalizeFreeflowConfig({
|
|
156
|
-
...rawConfig,
|
|
157
|
-
outputRouter: { ...rawRouter, enabled: true },
|
|
158
|
-
});
|
|
159
|
-
return normalized.config;
|
|
160
|
-
}
|
|
161
|
-
function rawValue(rawConfig, path, fallback) {
|
|
162
|
-
const value = getPath(rawConfig, path);
|
|
163
|
-
return value === undefined ? fallback : value;
|
|
164
|
-
}
|
|
165
|
-
function outputRouterItems(rawConfig, freeflowInactive = false) {
|
|
166
|
-
const effective = normalizedSettingsConfig(rawConfig);
|
|
167
|
-
const routerEnabled = normalizeFreeflowConfig(rawConfig).config.outputRouter.enabled;
|
|
168
|
-
const router = effective.outputRouter;
|
|
169
|
-
const scriptTransform = effective.scriptTransform;
|
|
170
|
-
const observedRouting = effective.observedRouting;
|
|
171
|
-
const inactive = freeflowInactive || !routerEnabled;
|
|
172
|
-
return [
|
|
173
|
-
{
|
|
174
|
-
id: "outputRouter.enabled",
|
|
175
|
-
label: "Output Router",
|
|
176
|
-
description: "Master switch for Freeflow routed evidence tools and router runtime guidance.",
|
|
177
|
-
path: ["outputRouter", "enabled"],
|
|
178
|
-
kind: "boolean",
|
|
179
|
-
value: routerEnabled,
|
|
180
|
-
defaultValue: DEFAULT_OUTPUT_ROUTER_ENABLED,
|
|
181
|
-
inactive: freeflowInactive,
|
|
182
|
-
},
|
|
183
|
-
{
|
|
184
|
-
id: "outputRouter.postToolRouting",
|
|
185
|
-
label: "Native safety net",
|
|
186
|
-
description:
|
|
187
|
-
"Post-process large/noisy native read/bash output. safety-net can replace oversized native output with vaulted routed evidence; strict is stronger.",
|
|
188
|
-
path: ["outputRouter", "postToolRouting"],
|
|
189
|
-
kind: "enum",
|
|
190
|
-
value: router.postToolRouting,
|
|
191
|
-
values: [...POST_TOOL_ROUTING_VALUES],
|
|
192
|
-
valueDescriptions: {
|
|
193
|
-
off: "Leave native tool output unchanged",
|
|
194
|
-
"safety-net": "Route oversized or noisy native output",
|
|
195
|
-
strict: "Apply stronger native output routing",
|
|
196
|
-
},
|
|
197
|
-
defaultValue: DEFAULT_POST_TOOL_ROUTING,
|
|
198
|
-
inactive,
|
|
199
|
-
},
|
|
200
|
-
{
|
|
201
|
-
id: "outputRouter.storagePolicy",
|
|
202
|
-
label: "Storage policy",
|
|
203
|
-
description: "Vault storage behavior for exactness-sensitive and noisy output.",
|
|
204
|
-
path: ["outputRouter", "storagePolicy"],
|
|
205
|
-
kind: "enum",
|
|
206
|
-
value: router.storagePolicy,
|
|
207
|
-
values: [...STORAGE_POLICY_VALUES],
|
|
208
|
-
defaultValue: DEFAULT_STORAGE_POLICY,
|
|
209
|
-
inactive,
|
|
210
|
-
},
|
|
211
|
-
{
|
|
212
|
-
id: "outputRouter.thresholds.largeOutputBytes",
|
|
213
|
-
label: "Large output bytes",
|
|
214
|
-
description: "Byte threshold for treating command/tool output as large.",
|
|
215
|
-
path: ["outputRouter", "thresholds", "largeOutputBytes"],
|
|
216
|
-
kind: "integer",
|
|
217
|
-
value: router.thresholds.largeOutputBytes,
|
|
218
|
-
defaultValue: DEFAULT_ROUTER_THRESHOLDS.largeOutputBytes,
|
|
219
|
-
parse: parsePositiveInteger,
|
|
220
|
-
inactive,
|
|
221
|
-
},
|
|
222
|
-
{
|
|
223
|
-
id: "outputRouter.thresholds.largeOutputLines",
|
|
224
|
-
label: "Large output lines",
|
|
225
|
-
description: "Line threshold for treating command/tool output as large.",
|
|
226
|
-
path: ["outputRouter", "thresholds", "largeOutputLines"],
|
|
227
|
-
kind: "integer",
|
|
228
|
-
value: router.thresholds.largeOutputLines,
|
|
229
|
-
defaultValue: DEFAULT_ROUTER_THRESHOLDS.largeOutputLines,
|
|
230
|
-
parse: parsePositiveInteger,
|
|
231
|
-
inactive,
|
|
232
|
-
},
|
|
233
|
-
{
|
|
234
|
-
id: "outputRouter.vault.root",
|
|
235
|
-
label: "Vault root",
|
|
236
|
-
description: "Root directory for recoverable routed output.",
|
|
237
|
-
path: ["outputRouter", "vault", "root"],
|
|
238
|
-
kind: "string",
|
|
239
|
-
value: router.vault.root,
|
|
240
|
-
defaultValue: DEFAULT_VAULT_ROOT,
|
|
241
|
-
inactive,
|
|
242
|
-
},
|
|
243
|
-
{
|
|
244
|
-
id: "outputRouter.vault.retention.ttlDays",
|
|
245
|
-
label: "Vault retention days",
|
|
246
|
-
description: "TTL retention for vaulted output. Set a positive integer; default is 7 days.",
|
|
247
|
-
path: ["outputRouter", "vault", "retention", "ttlDays"],
|
|
248
|
-
kind: "integer",
|
|
249
|
-
value:
|
|
250
|
-
router.vault.retention?.strategy === "ttl" ? router.vault.retention.ttlDays : DEFAULT_VAULT_RETENTION.ttlDays,
|
|
251
|
-
defaultValue: DEFAULT_VAULT_RETENTION.ttlDays,
|
|
252
|
-
parse: parsePositiveInteger,
|
|
253
|
-
inactive,
|
|
254
|
-
},
|
|
255
|
-
{
|
|
256
|
-
id: "outputRouter.generatedPaths",
|
|
257
|
-
label: "Generated paths",
|
|
258
|
-
description:
|
|
259
|
-
"Comma-separated globs for generated repo paths that should be treated as noisy or lower-priority evidence.",
|
|
260
|
-
path: ["outputRouter", "hints", "generatedPathGlobs"],
|
|
261
|
-
kind: "list",
|
|
262
|
-
value: rawValue(
|
|
263
|
-
rawConfig,
|
|
264
|
-
["outputRouter", "hints", "generatedPathGlobs"],
|
|
265
|
-
rawValue(rawConfig, ["outputRouter", "generatedPaths"], router.hints?.generatedPathGlobs ?? []),
|
|
266
|
-
),
|
|
267
|
-
format: formatList,
|
|
268
|
-
parse: parseStringList,
|
|
269
|
-
inactive,
|
|
270
|
-
},
|
|
271
|
-
{
|
|
272
|
-
id: "outputRouter.noisyCommandHints",
|
|
273
|
-
label: "Noisy command hints",
|
|
274
|
-
description: "Comma-separated command patterns that are likely to produce noisy output.",
|
|
275
|
-
path: ["outputRouter", "hints", "noisyCommandPatterns"],
|
|
276
|
-
kind: "list",
|
|
277
|
-
value: rawValue(
|
|
278
|
-
rawConfig,
|
|
279
|
-
["outputRouter", "hints", "noisyCommandPatterns"],
|
|
280
|
-
rawValue(rawConfig, ["outputRouter", "noisyCommandHints"], router.hints?.noisyCommandPatterns ?? []),
|
|
281
|
-
),
|
|
282
|
-
format: formatList,
|
|
283
|
-
parse: parseStringList,
|
|
284
|
-
inactive,
|
|
285
|
-
},
|
|
286
|
-
{
|
|
287
|
-
id: "outputRouter.scriptTransform.enabled",
|
|
288
|
-
label: "Script transform",
|
|
289
|
-
description:
|
|
290
|
-
"Enable sandboxed script transforms/producers. Requires proof-backed adapters; no unsandboxed fallback.",
|
|
291
|
-
path: ["outputRouter", "scriptTransform", "enabled"],
|
|
292
|
-
kind: "boolean",
|
|
293
|
-
value: scriptTransform.enabled,
|
|
294
|
-
defaultValue: DEFAULT_SCRIPT_TRANSFORM_CONFIG.enabled,
|
|
295
|
-
inactive,
|
|
296
|
-
},
|
|
297
|
-
{
|
|
298
|
-
id: "outputRouter.scriptTransform.languages",
|
|
299
|
-
label: "Script languages",
|
|
300
|
-
description: "Comma-separated sandbox languages to allow: javascript, python, jq.",
|
|
301
|
-
path: ["outputRouter", "scriptTransform", "languages"],
|
|
302
|
-
kind: "list",
|
|
303
|
-
value: scriptTransform.languages,
|
|
304
|
-
defaultValue: DEFAULT_SCRIPT_TRANSFORM_CONFIG.languages,
|
|
305
|
-
format: formatList,
|
|
306
|
-
parse: (text) => parseStringList(text).filter((value) => SCRIPT_LANGUAGES.includes(value)),
|
|
307
|
-
inactive,
|
|
308
|
-
},
|
|
309
|
-
{
|
|
310
|
-
id: "outputRouter.scriptTransform.limits.timeoutMs",
|
|
311
|
-
label: "Script timeout ms",
|
|
312
|
-
description: "Maximum sandboxed script execution time.",
|
|
313
|
-
path: ["outputRouter", "scriptTransform", "limits", "timeoutMs"],
|
|
314
|
-
kind: "integer",
|
|
315
|
-
value: scriptTransform.limits.timeoutMs,
|
|
316
|
-
defaultValue: DEFAULT_SCRIPT_TRANSFORM_CONFIG.limits.timeoutMs,
|
|
317
|
-
parse: parsePositiveInteger,
|
|
318
|
-
inactive,
|
|
319
|
-
},
|
|
320
|
-
{
|
|
321
|
-
id: "outputRouter.scriptTransform.limits.maxInputBytes",
|
|
322
|
-
label: "Script max input bytes",
|
|
323
|
-
description: "Maximum input size for sandboxed scripts.",
|
|
324
|
-
path: ["outputRouter", "scriptTransform", "limits", "maxInputBytes"],
|
|
325
|
-
kind: "integer",
|
|
326
|
-
value: scriptTransform.limits.maxInputBytes,
|
|
327
|
-
defaultValue: DEFAULT_SCRIPT_TRANSFORM_CONFIG.limits.maxInputBytes,
|
|
328
|
-
parse: parsePositiveInteger,
|
|
329
|
-
inactive,
|
|
330
|
-
},
|
|
331
|
-
{
|
|
332
|
-
id: "outputRouter.scriptTransform.limits.maxOutputBytes",
|
|
333
|
-
label: "Script max output bytes",
|
|
334
|
-
description: "Maximum output size for sandboxed scripts.",
|
|
335
|
-
path: ["outputRouter", "scriptTransform", "limits", "maxOutputBytes"],
|
|
336
|
-
kind: "integer",
|
|
337
|
-
value: scriptTransform.limits.maxOutputBytes,
|
|
338
|
-
defaultValue: DEFAULT_SCRIPT_TRANSFORM_CONFIG.limits.maxOutputBytes,
|
|
339
|
-
parse: parsePositiveInteger,
|
|
340
|
-
inactive,
|
|
341
|
-
},
|
|
342
|
-
{
|
|
343
|
-
id: "outputRouter.observedRouting.enabled",
|
|
344
|
-
label: "Observed routing",
|
|
345
|
-
description: "Route configured host tool results after they run, e.g. MCP/web/fetch/code-search output.",
|
|
346
|
-
path: ["outputRouter", "observedRouting", "enabled"],
|
|
347
|
-
kind: "boolean",
|
|
348
|
-
value: observedRouting.enabled,
|
|
349
|
-
defaultValue: DEFAULT_OBSERVED_ROUTING_CONFIG.enabled,
|
|
350
|
-
inactive,
|
|
351
|
-
},
|
|
352
|
-
...observedProducerItems("web", "Web search", observedRouting.web, inactive),
|
|
353
|
-
...observedProducerItems("fetch", "Fetch content", observedRouting.fetch, inactive),
|
|
354
|
-
...observedProducerItems("codeSearch", "Code search", observedRouting.codeSearch, inactive),
|
|
355
|
-
{
|
|
356
|
-
id: "outputRouter.observedRouting.mcp.servers",
|
|
357
|
-
label: "MCP servers JSON",
|
|
358
|
-
description: 'JSON object keyed by MCP server id. Example: {"github":{"enabled":true,"persistence":"exact"}}',
|
|
359
|
-
path: ["outputRouter", "observedRouting", "mcp", "servers"],
|
|
360
|
-
kind: "json",
|
|
361
|
-
value: observedRouting.mcp?.servers ?? {},
|
|
362
|
-
defaultValue: DEFAULT_OBSERVED_ROUTING_CONFIG.mcp.servers,
|
|
363
|
-
format: formatJson,
|
|
364
|
-
parse: parseJsonObject,
|
|
365
|
-
inactive,
|
|
366
|
-
},
|
|
367
|
-
];
|
|
368
|
-
}
|
|
369
|
-
function observedProducerItems(id, label, value, inactive) {
|
|
370
|
-
return [
|
|
371
|
-
{
|
|
372
|
-
id: `outputRouter.observedRouting.${id}.enabled`,
|
|
373
|
-
label: `${label} observed`,
|
|
374
|
-
description: `Enable observed routing for ${label.toLowerCase()} output.`,
|
|
375
|
-
path: ["outputRouter", "observedRouting", id, "enabled"],
|
|
376
|
-
kind: "boolean",
|
|
377
|
-
value: value?.enabled === true,
|
|
378
|
-
defaultValue: false,
|
|
379
|
-
inactive,
|
|
380
|
-
},
|
|
381
|
-
{
|
|
382
|
-
id: `outputRouter.observedRouting.${id}.persistence`,
|
|
383
|
-
label: `${label} persistence`,
|
|
384
|
-
description: "Persistence mode for this observed producer: none, metadata-only, or exact.",
|
|
385
|
-
path: ["outputRouter", "observedRouting", id, "persistence"],
|
|
386
|
-
kind: "enum",
|
|
387
|
-
value: value?.persistence ?? "none",
|
|
388
|
-
values: [...OBSERVED_PERSISTENCE_VALUES],
|
|
389
|
-
valueDescriptions: {
|
|
390
|
-
none: "Do not persist observed output",
|
|
391
|
-
"metadata-only": "Persist metadata without raw output",
|
|
392
|
-
exact: "Persist exact recoverable output",
|
|
393
|
-
},
|
|
394
|
-
defaultValue: value?.enabled === true ? undefined : "none",
|
|
395
|
-
inactive,
|
|
396
|
-
},
|
|
397
|
-
];
|
|
398
|
-
}
|
|
399
108
|
function configValueForChoice(item, value) {
|
|
400
109
|
const key = String(value);
|
|
401
110
|
if (item.configValues && Object.hasOwn(item.configValues, key)) {
|
|
@@ -404,10 +113,15 @@ function configValueForChoice(item, value) {
|
|
|
404
113
|
return value;
|
|
405
114
|
}
|
|
406
115
|
function effectiveItemValue(item) {
|
|
407
|
-
return item.effectiveValue
|
|
116
|
+
return item.effectiveValue === undefined ? item.value : item.effectiveValue;
|
|
408
117
|
}
|
|
409
118
|
function formatCoreValue(value) {
|
|
410
|
-
|
|
119
|
+
if (typeof value === "boolean") return booleanValue(value);
|
|
120
|
+
if (isRecord(value) && typeof value.provider === "string" && typeof value.model === "string") {
|
|
121
|
+
const effort = typeof value.thinkingLevel === "string" ? ` · ${value.thinkingLevel}` : "";
|
|
122
|
+
return `${value.provider}/${value.model}${effort}`;
|
|
123
|
+
}
|
|
124
|
+
return String(value ?? "");
|
|
411
125
|
}
|
|
412
126
|
function coreDisplaySuffix(item, inactive = false) {
|
|
413
127
|
const source = item.effectiveSource ?? "builtin";
|
|
@@ -558,14 +272,19 @@ function resolveSettingsCoreView(rawConfig, layers) {
|
|
|
558
272
|
const fallbackCore = {
|
|
559
273
|
enabled: getPath(rawConfig, ["enabled"]) !== false,
|
|
560
274
|
interactionContract: getPath(rawConfig, ["interactionContract"]) !== false,
|
|
275
|
+
contextVirtualization: getPath(rawConfig, ["contextVirtualization"]) === true,
|
|
276
|
+
conversationHistory: getPath(rawConfig, ["conversationHistory"]) === true,
|
|
561
277
|
skills: { enabled: getPath(repositorySkills, ["enabled"]) !== false },
|
|
562
278
|
defaultMode: validModeOrUndefined(rawConfig.defaultMode) ?? "workflow",
|
|
563
279
|
};
|
|
564
280
|
const fallbackSources = {
|
|
565
281
|
enabled: typeof getPath(rawConfig, ["enabled"]) === "boolean" ? "repository" : "builtin",
|
|
566
282
|
interactionContract: typeof getPath(rawConfig, ["interactionContract"]) === "boolean" ? "repository" : "builtin",
|
|
283
|
+
contextVirtualization:
|
|
284
|
+
typeof getPath(rawConfig, ["contextVirtualization"]) === "boolean" ? "repository" : "builtin",
|
|
285
|
+
conversationHistory: typeof getPath(rawConfig, ["conversationHistory"]) === "boolean" ? "repository" : "builtin",
|
|
567
286
|
skillsEnabled: typeof getPath(repositorySkills, ["enabled"]) === "boolean" ? "repository" : "builtin",
|
|
568
|
-
defaultMode: validModeOrUndefined(rawConfig.defaultMode)
|
|
287
|
+
defaultMode: validModeOrUndefined(rawConfig.defaultMode) === undefined ? "builtin" : "repository",
|
|
569
288
|
};
|
|
570
289
|
return {
|
|
571
290
|
localConfig,
|
|
@@ -573,9 +292,6 @@ function resolveSettingsCoreView(rawConfig, layers) {
|
|
|
573
292
|
sources: layers?.sources ?? fallbackSources,
|
|
574
293
|
};
|
|
575
294
|
}
|
|
576
|
-
function groupEnabled(items, id) {
|
|
577
|
-
return items.find((item) => item.id === id)?.value === true;
|
|
578
|
-
}
|
|
579
295
|
function createSessionBooleanItem(options) {
|
|
580
296
|
const override = options.sessionOverrides[options.key];
|
|
581
297
|
const item = {
|
|
@@ -613,7 +329,7 @@ function createSessionBooleanItem(options) {
|
|
|
613
329
|
item.displaySuffix = coreDisplaySuffix(item);
|
|
614
330
|
return item;
|
|
615
331
|
}
|
|
616
|
-
function sessionFreeflowItems(state, modeState) {
|
|
332
|
+
function sessionFreeflowItems(state, modeState, cognitiveRoutingController) {
|
|
617
333
|
const sessionOverrides = state.sessionOverrides;
|
|
618
334
|
const configured = state.configuredCoreConfig;
|
|
619
335
|
const configuredSources = state.configuredSources;
|
|
@@ -651,10 +367,34 @@ function sessionFreeflowItems(state, modeState) {
|
|
|
651
367
|
effectiveSource: effectiveSources.skillsEnabled,
|
|
652
368
|
sessionOverrides,
|
|
653
369
|
});
|
|
370
|
+
const contextVirtualizationItem = createSessionBooleanItem({
|
|
371
|
+
id: "freeflow.contextVirtualization",
|
|
372
|
+
label: "Context Virtualization",
|
|
373
|
+
description: "Temporary Context Virtualization override for this Pi session.",
|
|
374
|
+
key: "contextVirtualization",
|
|
375
|
+
inheritedValue: configured.contextVirtualization,
|
|
376
|
+
inheritedSource: configuredSources.contextVirtualization,
|
|
377
|
+
effectiveValue: state.contextVirtualization.enabled,
|
|
378
|
+
effectiveSource: effectiveSources.contextVirtualization,
|
|
379
|
+
sessionOverrides,
|
|
380
|
+
});
|
|
381
|
+
const conversationHistoryItem = createSessionBooleanItem({
|
|
382
|
+
id: "freeflow.conversationHistory",
|
|
383
|
+
label: "Conversation History",
|
|
384
|
+
description: "Temporary Conversation History override for this Pi session.",
|
|
385
|
+
key: "conversationHistory",
|
|
386
|
+
inheritedValue: configured.conversationHistory,
|
|
387
|
+
inheritedSource: configuredSources.conversationHistory,
|
|
388
|
+
effectiveValue: state.conversationHistory.enabled,
|
|
389
|
+
effectiveSource: effectiveSources.conversationHistory,
|
|
390
|
+
sessionOverrides,
|
|
391
|
+
});
|
|
654
392
|
const freeflowInactive = !state.enabled;
|
|
655
393
|
const skillsEnabled = state.skills.enabled;
|
|
656
394
|
interactionItem.inactive = freeflowInactive;
|
|
657
395
|
skillsItem.inactive = freeflowInactive;
|
|
396
|
+
contextVirtualizationItem.inactive = freeflowInactive || !skillsEnabled;
|
|
397
|
+
conversationHistoryItem.inactive = freeflowInactive || !skillsEnabled;
|
|
658
398
|
const sessionMode = modeState.currentMode ?? "default";
|
|
659
399
|
const sessionModeItem = {
|
|
660
400
|
id: "freeflow.sessionMode",
|
|
@@ -681,15 +421,47 @@ function sessionFreeflowItems(state, modeState) {
|
|
|
681
421
|
inheritedValue: modeState.defaultMode,
|
|
682
422
|
inheritedSource: modeState.defaultModeSource,
|
|
683
423
|
};
|
|
424
|
+
const cognitiveRoutingState = cognitiveRoutingController?.state();
|
|
425
|
+
const cognitiveRoutingProfile =
|
|
426
|
+
cognitiveRoutingState?.controlMode === "manual-standard"
|
|
427
|
+
? "standard"
|
|
428
|
+
: cognitiveRoutingState?.controlMode === "manual-reasoning"
|
|
429
|
+
? "reasoning"
|
|
430
|
+
: "auto";
|
|
431
|
+
const cognitiveRoutingItem = state.cognitiveRouting
|
|
432
|
+
? {
|
|
433
|
+
id: "freeflow.cognitiveRouting.profile",
|
|
434
|
+
label: "Cognitive Routing",
|
|
435
|
+
description: "Hold a profile manually or release the hold for automatic model control.",
|
|
436
|
+
kind: "enum",
|
|
437
|
+
value: cognitiveRoutingProfile,
|
|
438
|
+
values: ["auto", "standard", "reasoning"],
|
|
439
|
+
valueLabels: {
|
|
440
|
+
auto: "automatic",
|
|
441
|
+
standard: "manual · standard",
|
|
442
|
+
reasoning: "manual · reasoning",
|
|
443
|
+
},
|
|
444
|
+
valueDescriptions: {
|
|
445
|
+
auto: "Release the manual hold without forcing a model transition.",
|
|
446
|
+
standard: "Hold the standard profile until /freeflow profile auto.",
|
|
447
|
+
reasoning: "Hold the reasoning profile until /freeflow profile auto.",
|
|
448
|
+
},
|
|
449
|
+
inactive: !state.cognitiveRouting.effective || cognitiveRoutingController === undefined,
|
|
450
|
+
runtimeInactive: state.cognitiveRouting.blockingReason?.code === "runtime_disabled",
|
|
451
|
+
displaySuffix: cognitiveRoutingState?.effective ? cognitiveRoutingProfile : "unavailable",
|
|
452
|
+
}
|
|
453
|
+
: undefined;
|
|
684
454
|
return [
|
|
685
455
|
freeflowItem,
|
|
686
456
|
interactionItem,
|
|
687
457
|
skillsItem,
|
|
688
458
|
sessionModeItem,
|
|
459
|
+
...(cognitiveRoutingItem ? [cognitiveRoutingItem] : []),
|
|
689
460
|
{
|
|
690
461
|
id: "freeflow.session.reset",
|
|
691
462
|
label: "Reset session overrides",
|
|
692
|
-
description:
|
|
463
|
+
description:
|
|
464
|
+
"Clear Freeflow, Interaction Contract, Skills, Context Virtualization, Conversation History, and mode overrides for this Pi session.",
|
|
693
465
|
kind: "enum",
|
|
694
466
|
value: "available",
|
|
695
467
|
values: ["reset"],
|
|
@@ -698,7 +470,295 @@ function sessionFreeflowItems(state, modeState) {
|
|
|
698
470
|
format: () => "available",
|
|
699
471
|
transient: true,
|
|
700
472
|
},
|
|
473
|
+
{
|
|
474
|
+
id: "freeflow.context",
|
|
475
|
+
label: "Freeflow Context",
|
|
476
|
+
description: "Choose which context projection and conversation-history operations are available to the model.",
|
|
477
|
+
kind: "group",
|
|
478
|
+
value: contextVirtualizationItem.effectiveValue === true || conversationHistoryItem.effectiveValue === true,
|
|
479
|
+
displaySuffix: `${[contextVirtualizationItem, conversationHistoryItem].filter((item) => item.effectiveValue === true).length}/2 enabled`,
|
|
480
|
+
children: [contextVirtualizationItem, conversationHistoryItem],
|
|
481
|
+
},
|
|
482
|
+
];
|
|
483
|
+
}
|
|
484
|
+
const COGNITIVE_ROUTING_THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
485
|
+
const COGNITIVE_ROUTING_EFFORT_DESCRIPTIONS = {
|
|
486
|
+
off: "No reasoning",
|
|
487
|
+
minimal: "Very brief reasoning",
|
|
488
|
+
low: "Light reasoning",
|
|
489
|
+
medium: "Moderate reasoning",
|
|
490
|
+
high: "Deep reasoning",
|
|
491
|
+
xhigh: "Extra-high reasoning",
|
|
492
|
+
max: "Maximum reasoning",
|
|
493
|
+
};
|
|
494
|
+
function cognitiveRoutingModelKey(provider, model) {
|
|
495
|
+
return `${provider}/${model}`;
|
|
496
|
+
}
|
|
497
|
+
function isCognitiveRoutingProfile(value) {
|
|
498
|
+
return (
|
|
499
|
+
isRecord(value) &&
|
|
500
|
+
typeof value.provider === "string" &&
|
|
501
|
+
typeof value.model === "string" &&
|
|
502
|
+
typeof value.thinkingLevel === "string" &&
|
|
503
|
+
COGNITIVE_ROUTING_THINKING_LEVELS.includes(value.thinkingLevel)
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
function cognitiveRoutingThinkingLevels(model, registry) {
|
|
507
|
+
if (typeof registry?.clampThinkingLevel !== "function") {
|
|
508
|
+
return model?.reasoning === true ? [...COGNITIVE_ROUTING_THINKING_LEVELS] : ["off"];
|
|
509
|
+
}
|
|
510
|
+
return COGNITIVE_ROUTING_THINKING_LEVELS.filter((level) => {
|
|
511
|
+
try {
|
|
512
|
+
return registry.clampThinkingLevel(model, level) === level;
|
|
513
|
+
} catch {
|
|
514
|
+
return false;
|
|
515
|
+
}
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
function cognitiveRoutingModelOptions(ctx) {
|
|
519
|
+
const registry = ctx?.modelRegistry;
|
|
520
|
+
if (typeof registry?.getAvailable !== "function") return [];
|
|
521
|
+
let models;
|
|
522
|
+
try {
|
|
523
|
+
models = registry.getAvailable();
|
|
524
|
+
} catch {
|
|
525
|
+
return [];
|
|
526
|
+
}
|
|
527
|
+
if (!Array.isArray(models)) return [];
|
|
528
|
+
return models
|
|
529
|
+
.filter((model) => typeof model?.provider === "string" && typeof model?.id === "string")
|
|
530
|
+
.map((model) => {
|
|
531
|
+
const thinkingLevels = cognitiveRoutingThinkingLevels(model, registry);
|
|
532
|
+
const provider = model.provider;
|
|
533
|
+
const modelId = model.id;
|
|
534
|
+
const key = cognitiveRoutingModelKey(provider, modelId);
|
|
535
|
+
const displayName = typeof model.name === "string" && model.name !== modelId ? model.name : undefined;
|
|
536
|
+
return {
|
|
537
|
+
key,
|
|
538
|
+
provider,
|
|
539
|
+
model: modelId,
|
|
540
|
+
label: key,
|
|
541
|
+
description: displayName ? `${displayName} · ${thinkingLevels.join(", ")}` : thinkingLevels.join(", "),
|
|
542
|
+
thinkingLevels,
|
|
543
|
+
};
|
|
544
|
+
})
|
|
545
|
+
.filter((model) => model.thinkingLevels.length > 0)
|
|
546
|
+
.sort((a, b) => a.label.localeCompare(b.label));
|
|
547
|
+
}
|
|
548
|
+
function cognitiveRoutingProfileDisplay(value) {
|
|
549
|
+
return isCognitiveRoutingProfile(value) ? formatCoreValue(value) : "not configured";
|
|
550
|
+
}
|
|
551
|
+
function cognitiveRoutingSettingsSource(source) {
|
|
552
|
+
if (source === "repository") return "repository";
|
|
553
|
+
if (source === "personal" || source === "local") return "local";
|
|
554
|
+
return "builtin";
|
|
555
|
+
}
|
|
556
|
+
function cognitiveRoutingProfileDisplaySuffix(source, scope) {
|
|
557
|
+
if (!source) return undefined;
|
|
558
|
+
if (source === "personal" && scope === "repository") return "(effective personal)";
|
|
559
|
+
return `(${source})`;
|
|
560
|
+
}
|
|
561
|
+
function cognitiveRoutingConfirmStep(summary) {
|
|
562
|
+
return {
|
|
563
|
+
title: "Confirm preset",
|
|
564
|
+
choices: [
|
|
565
|
+
{
|
|
566
|
+
key: "__save__",
|
|
567
|
+
value: "save",
|
|
568
|
+
label: `Save ${summary}`,
|
|
569
|
+
description: "Write the complete profile atomically.",
|
|
570
|
+
},
|
|
571
|
+
{ key: "__cancel__", value: "cancel", label: "Cancel", description: "Leave the previous preset unchanged." },
|
|
572
|
+
],
|
|
573
|
+
selectedKey: "__save__",
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
function createCognitiveRoutingProfileWizard(currentValue, models, allowInherit) {
|
|
577
|
+
const currentProfile = isCognitiveRoutingProfile(currentValue) ? currentValue : undefined;
|
|
578
|
+
const currentModelKey = currentProfile
|
|
579
|
+
? cognitiveRoutingModelKey(currentProfile.provider, currentProfile.model)
|
|
580
|
+
: undefined;
|
|
581
|
+
const firstChoices = [
|
|
582
|
+
...(allowInherit
|
|
583
|
+
? [
|
|
584
|
+
{
|
|
585
|
+
key: LOCAL_INHERIT,
|
|
586
|
+
value: LOCAL_INHERIT,
|
|
587
|
+
label: "Inherit repository preset",
|
|
588
|
+
description: "Remove the personal override without changing the repository preset.",
|
|
589
|
+
},
|
|
590
|
+
]
|
|
591
|
+
: []),
|
|
592
|
+
...models.map((model) => ({
|
|
593
|
+
key: model.key,
|
|
594
|
+
value: model,
|
|
595
|
+
label: model.label,
|
|
596
|
+
description: model.description,
|
|
597
|
+
})),
|
|
701
598
|
];
|
|
599
|
+
if (firstChoices.length === 0) {
|
|
600
|
+
firstChoices.push({
|
|
601
|
+
key: "__cancel__",
|
|
602
|
+
value: "cancel",
|
|
603
|
+
label: "No authenticated models available",
|
|
604
|
+
description: "Authenticate a provider before configuring this preset.",
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
return {
|
|
608
|
+
firstStep: () => ({
|
|
609
|
+
title: "Choose model",
|
|
610
|
+
choices: firstChoices,
|
|
611
|
+
selectedKey:
|
|
612
|
+
currentValue === LOCAL_INHERIT
|
|
613
|
+
? LOCAL_INHERIT
|
|
614
|
+
: firstChoices.some((choice) => choice.key === currentModelKey)
|
|
615
|
+
? currentModelKey
|
|
616
|
+
: firstChoices[0]?.key,
|
|
617
|
+
}),
|
|
618
|
+
nextStep: (selectedValues) => {
|
|
619
|
+
if (selectedValues.length === 1) {
|
|
620
|
+
const selected = selectedValues[0];
|
|
621
|
+
if (selected === LOCAL_INHERIT) {
|
|
622
|
+
return cognitiveRoutingConfirmStep("inherit repository preset");
|
|
623
|
+
}
|
|
624
|
+
const model = selected;
|
|
625
|
+
if (!model?.key || model.thinkingLevels.length === 0) return undefined;
|
|
626
|
+
const currentEffort =
|
|
627
|
+
currentProfile && currentModelKey === model.key ? currentProfile.thinkingLevel : undefined;
|
|
628
|
+
return {
|
|
629
|
+
title: `Choose effort · ${model.label}`,
|
|
630
|
+
choices: model.thinkingLevels.map((level) => ({
|
|
631
|
+
key: level,
|
|
632
|
+
value: level,
|
|
633
|
+
label: level,
|
|
634
|
+
description: COGNITIVE_ROUTING_EFFORT_DESCRIPTIONS[level],
|
|
635
|
+
})),
|
|
636
|
+
selectedKey:
|
|
637
|
+
currentEffort && model.thinkingLevels.includes(currentEffort) ? currentEffort : model.thinkingLevels[0],
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
if (selectedValues.length === 2) {
|
|
641
|
+
const model = selectedValues[0];
|
|
642
|
+
const effort = selectedValues[1];
|
|
643
|
+
return cognitiveRoutingConfirmStep(`${model.label} · ${effort}`);
|
|
644
|
+
}
|
|
645
|
+
return undefined;
|
|
646
|
+
},
|
|
647
|
+
valueFromSelections: (selectedValues) => {
|
|
648
|
+
if (selectedValues[0] === LOCAL_INHERIT) return undefined;
|
|
649
|
+
const model = selectedValues[0];
|
|
650
|
+
return {
|
|
651
|
+
provider: model.provider,
|
|
652
|
+
model: model.model,
|
|
653
|
+
thinkingLevel: selectedValues[1],
|
|
654
|
+
};
|
|
655
|
+
},
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
function cognitiveRoutingProfileItem(options) {
|
|
659
|
+
const path = ["cognitiveRouting", "profiles", options.name];
|
|
660
|
+
const repositoryProfile = getPath(options.rawConfig, path);
|
|
661
|
+
const localProfile = getPath(options.localConfig, path);
|
|
662
|
+
const effectiveProfile = options.capabilityState?.profiles?.[options.name] ?? repositoryProfile;
|
|
663
|
+
const localValue = isCognitiveRoutingProfile(localProfile) ? localProfile : LOCAL_INHERIT;
|
|
664
|
+
const value = options.scope === "local" ? localValue : repositoryProfile;
|
|
665
|
+
const source =
|
|
666
|
+
options.capabilityState?.profileSources?.[options.name] ??
|
|
667
|
+
(isCognitiveRoutingProfile(repositoryProfile) ? "repository" : undefined);
|
|
668
|
+
const models = cognitiveRoutingModelOptions(options.ctx);
|
|
669
|
+
const inactive = options.scope === "local" && models.length === 0 && localValue !== LOCAL_INHERIT;
|
|
670
|
+
return {
|
|
671
|
+
id: `freeflow.cognitiveRouting.${options.name}`,
|
|
672
|
+
label: `${options.name.charAt(0).toUpperCase()}${options.name.slice(1)} preset`,
|
|
673
|
+
description:
|
|
674
|
+
"Choose an authenticated available model and one effort supported by that model. Confirming writes the complete preset; cancel leaves it unchanged.",
|
|
675
|
+
path,
|
|
676
|
+
kind: "string",
|
|
677
|
+
value,
|
|
678
|
+
format: cognitiveRoutingProfileDisplay,
|
|
679
|
+
configScope: options.scope,
|
|
680
|
+
effectiveValue: effectiveProfile,
|
|
681
|
+
effectiveSource: cognitiveRoutingSettingsSource(source),
|
|
682
|
+
inheritedValue: repositoryProfile,
|
|
683
|
+
inheritedSource: "repository",
|
|
684
|
+
inactive,
|
|
685
|
+
displaySuffix: cognitiveRoutingProfileDisplaySuffix(source, options.scope),
|
|
686
|
+
wizard: () => createCognitiveRoutingProfileWizard(value, models, options.scope === "local"),
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
function cognitiveRoutingSessionStartItem(options) {
|
|
690
|
+
const path = ["cognitiveRouting", "sessionStart", options.setting];
|
|
691
|
+
const defaultValue = options.setting === "control" ? "automatic" : "standard";
|
|
692
|
+
const values = options.setting === "control" ? ["automatic", "manual"] : ["standard", "reasoning"];
|
|
693
|
+
const labels = Object.fromEntries(values.map((value) => [value, value]));
|
|
694
|
+
const descriptions =
|
|
695
|
+
options.setting === "control"
|
|
696
|
+
? {
|
|
697
|
+
automatic: "Let Cognitive Routing manage profile transitions for the new session.",
|
|
698
|
+
manual: "Hold the selected profile for the new session until the user changes it.",
|
|
699
|
+
}
|
|
700
|
+
: {
|
|
701
|
+
standard: "Start a new session with the configured Standard preset.",
|
|
702
|
+
reasoning: "Start a new session with the configured Reasoning preset.",
|
|
703
|
+
};
|
|
704
|
+
const repositoryValue = getPath(options.rawConfig, path);
|
|
705
|
+
const localValue = getPath(options.localConfig, path);
|
|
706
|
+
const isValidValue = (value) => values.includes(value);
|
|
707
|
+
const repositorySetting = isValidValue(repositoryValue) ? repositoryValue : undefined;
|
|
708
|
+
const localSetting = isValidValue(localValue) ? localValue : undefined;
|
|
709
|
+
const inheritedValue = repositorySetting ?? defaultValue;
|
|
710
|
+
const inheritedSource = repositorySetting ? "repository" : "builtin";
|
|
711
|
+
const configuredSource =
|
|
712
|
+
options.capabilityState?.sessionStartSources?.[options.setting] ?? (repositorySetting ? "repository" : undefined);
|
|
713
|
+
const effectiveValue = options.capabilityState?.sessionStart?.[options.setting] ?? inheritedValue;
|
|
714
|
+
const effectiveSource = cognitiveRoutingSettingsSource(configuredSource);
|
|
715
|
+
const label = options.setting === "control" ? "Session start control" : "Session start profile";
|
|
716
|
+
const description =
|
|
717
|
+
options.setting === "control"
|
|
718
|
+
? "Choose automatic or manual control for new sessions only; changing this does not alter the active session."
|
|
719
|
+
: "Choose the initial profile for new sessions only; changing this does not alter the active session.";
|
|
720
|
+
const item =
|
|
721
|
+
options.scope === "local"
|
|
722
|
+
? {
|
|
723
|
+
id: `freeflow.cognitiveRouting.sessionStart.${options.setting}`,
|
|
724
|
+
label,
|
|
725
|
+
description: `${description} Choose inherit to use the repository value; use /freeflow settings repo to edit shared defaults.`,
|
|
726
|
+
path,
|
|
727
|
+
kind: "enum",
|
|
728
|
+
value: localSetting ?? LOCAL_INHERIT,
|
|
729
|
+
values: [LOCAL_INHERIT, ...values],
|
|
730
|
+
valueLabels: { inherit: "Inherit repository", ...labels },
|
|
731
|
+
valueDescriptions: {
|
|
732
|
+
inherit: `Use ${inheritedValue} from ${inheritedSource}.`,
|
|
733
|
+
...descriptions,
|
|
734
|
+
},
|
|
735
|
+
format: (value) => String(value),
|
|
736
|
+
configScope: "local",
|
|
737
|
+
configValues: { inherit: undefined },
|
|
738
|
+
effectiveValue,
|
|
739
|
+
effectiveSource,
|
|
740
|
+
inheritedValue,
|
|
741
|
+
inheritedSource,
|
|
742
|
+
}
|
|
743
|
+
: {
|
|
744
|
+
id: `freeflow.cognitiveRouting.sessionStart.${options.setting}`,
|
|
745
|
+
label,
|
|
746
|
+
description: `${description} This edits shared .freeflow/config.json.`,
|
|
747
|
+
path,
|
|
748
|
+
kind: "enum",
|
|
749
|
+
value: inheritedValue,
|
|
750
|
+
values,
|
|
751
|
+
valueLabels: labels,
|
|
752
|
+
valueDescriptions: descriptions,
|
|
753
|
+
format: (value) => String(value),
|
|
754
|
+
defaultValue,
|
|
755
|
+
configScope: "repository",
|
|
756
|
+
effectiveValue,
|
|
757
|
+
effectiveSource,
|
|
758
|
+
localOverrideValue: localSetting,
|
|
759
|
+
};
|
|
760
|
+
item.displaySuffix = coreDisplaySuffix(item);
|
|
761
|
+
return item;
|
|
702
762
|
}
|
|
703
763
|
function freeflowItems(rawConfig, modeState, options = {}) {
|
|
704
764
|
const scope = options.scope ?? "repository";
|
|
@@ -734,12 +794,36 @@ function freeflowItems(rawConfig, modeState, options = {}) {
|
|
|
734
794
|
localConfig,
|
|
735
795
|
id: "freeflow.skills.enabled",
|
|
736
796
|
label: "Skills",
|
|
737
|
-
description: "Expose Freeflow skills and
|
|
797
|
+
description: "Expose Freeflow skills and discover Workflow before consequential or mutating work.",
|
|
738
798
|
path: ["skills", "enabled"],
|
|
739
799
|
effectiveValue: core.skills.enabled,
|
|
740
800
|
effectiveSource: sources.skillsEnabled,
|
|
741
801
|
defaultValue: DEFAULT_SKILLS_ENABLED,
|
|
742
802
|
});
|
|
803
|
+
const contextVirtualizationItem = createScopedBooleanItem({
|
|
804
|
+
scope,
|
|
805
|
+
rawConfig,
|
|
806
|
+
localConfig,
|
|
807
|
+
id: "freeflow.contextVirtualization",
|
|
808
|
+
label: "Context Virtualization",
|
|
809
|
+
description: "Let the model archive consumed tool results from future context while preserving session history.",
|
|
810
|
+
path: ["contextVirtualization"],
|
|
811
|
+
effectiveValue: core.contextVirtualization,
|
|
812
|
+
effectiveSource: sources.contextVirtualization,
|
|
813
|
+
defaultValue: DEFAULT_CONTEXT_VIRTUALIZATION_ENABLED,
|
|
814
|
+
});
|
|
815
|
+
const conversationHistoryItem = createScopedBooleanItem({
|
|
816
|
+
scope,
|
|
817
|
+
rawConfig,
|
|
818
|
+
localConfig,
|
|
819
|
+
id: "freeflow.conversationHistory",
|
|
820
|
+
label: "Conversation History",
|
|
821
|
+
description: "Let the model search and retrieve hidden conversation history on the active branch.",
|
|
822
|
+
path: ["conversationHistory"],
|
|
823
|
+
effectiveValue: core.conversationHistory,
|
|
824
|
+
effectiveSource: sources.conversationHistory,
|
|
825
|
+
defaultValue: DEFAULT_CONVERSATION_HISTORY_ENABLED,
|
|
826
|
+
});
|
|
743
827
|
const repositoryModeValue = validModeOrUndefined(rawConfig.defaultMode);
|
|
744
828
|
const repositoryDefaultMode = repositoryModeValue ?? "workflow";
|
|
745
829
|
const repositoryDefaultSource = repositoryModeValue ? "repository" : "builtin";
|
|
@@ -756,6 +840,8 @@ function freeflowItems(rawConfig, modeState, options = {}) {
|
|
|
756
840
|
const skillsEnabled = core.skills.enabled;
|
|
757
841
|
interactionItem.inactive = freeflowInactive;
|
|
758
842
|
skillsItem.inactive = freeflowInactive;
|
|
843
|
+
contextVirtualizationItem.inactive = freeflowInactive || !skillsEnabled;
|
|
844
|
+
conversationHistoryItem.inactive = freeflowInactive || !skillsEnabled;
|
|
759
845
|
defaultModeItem.inactive = freeflowInactive;
|
|
760
846
|
defaultModeItem.displaySuffix = coreDisplaySuffix(defaultModeItem, freeflowInactive || !skillsEnabled);
|
|
761
847
|
const sessionMode = modeState?.currentMode ?? "default";
|
|
@@ -783,175 +869,100 @@ function freeflowItems(rawConfig, modeState, options = {}) {
|
|
|
783
869
|
freeflowInactive || !skillsEnabled,
|
|
784
870
|
),
|
|
785
871
|
};
|
|
786
|
-
const
|
|
787
|
-
const
|
|
872
|
+
const cognitiveRoutingState = options.cognitiveRouting;
|
|
873
|
+
const cognitiveRoutingRuntimeDisabled = !isPiFlowHost(options.hostInfo);
|
|
874
|
+
const cognitiveRoutingGroup = (() => {
|
|
875
|
+
const cognitiveRoutingEnabledItem = createScopedBooleanItem({
|
|
876
|
+
scope,
|
|
877
|
+
rawConfig,
|
|
878
|
+
localConfig,
|
|
879
|
+
id: "freeflow.cognitiveRouting.enabled",
|
|
880
|
+
label: "Enabled",
|
|
881
|
+
description: cognitiveRoutingRuntimeDisabled
|
|
882
|
+
? "Configured for PiFlow, but disabled in normal Pi."
|
|
883
|
+
: "Allow Cognitive Routing to own the session model lease and switch the configured profiles.",
|
|
884
|
+
path: ["cognitiveRouting", "enabled"],
|
|
885
|
+
effectiveValue: cognitiveRoutingState?.enabled ?? false,
|
|
886
|
+
effectiveSource: cognitiveRoutingSettingsSource(cognitiveRoutingState?.enabledSource),
|
|
887
|
+
defaultValue: false,
|
|
888
|
+
});
|
|
889
|
+
cognitiveRoutingEnabledItem.inactive = freeflowInactive || !skillsEnabled || cognitiveRoutingRuntimeDisabled;
|
|
890
|
+
cognitiveRoutingEnabledItem.runtimeInactive = cognitiveRoutingRuntimeDisabled;
|
|
891
|
+
const cognitiveRoutingProfiles = ["standard", "reasoning"].map((name) =>
|
|
892
|
+
cognitiveRoutingProfileItem({
|
|
893
|
+
name: name,
|
|
894
|
+
scope,
|
|
895
|
+
rawConfig,
|
|
896
|
+
localConfig,
|
|
897
|
+
capabilityState: cognitiveRoutingState,
|
|
898
|
+
ctx: options.ctx,
|
|
899
|
+
}),
|
|
900
|
+
);
|
|
901
|
+
const cognitiveRoutingSessionStart = ["control", "profile"].map((setting) =>
|
|
902
|
+
cognitiveRoutingSessionStartItem({
|
|
903
|
+
setting,
|
|
904
|
+
scope,
|
|
905
|
+
rawConfig,
|
|
906
|
+
localConfig,
|
|
907
|
+
capabilityState: cognitiveRoutingState,
|
|
908
|
+
}),
|
|
909
|
+
);
|
|
910
|
+
for (const item of [...cognitiveRoutingProfiles, ...cognitiveRoutingSessionStart]) {
|
|
911
|
+
item.inactive ||= freeflowInactive || !skillsEnabled || cognitiveRoutingRuntimeDisabled;
|
|
912
|
+
item.runtimeInactive = cognitiveRoutingRuntimeDisabled;
|
|
913
|
+
}
|
|
914
|
+
const cognitiveRoutingStatus = cognitiveRoutingRuntimeDisabled
|
|
915
|
+
? "disabled · PiFlow only"
|
|
916
|
+
: cognitiveRoutingState
|
|
917
|
+
? cognitiveRoutingState.effective
|
|
918
|
+
? "active"
|
|
919
|
+
: cognitiveRoutingState.blockingReason?.code === "profile_missing"
|
|
920
|
+
? "not configured"
|
|
921
|
+
: (cognitiveRoutingState.blockingReason?.code ?? "inactive")
|
|
922
|
+
: "unavailable";
|
|
923
|
+
return {
|
|
924
|
+
id: "freeflow.cognitiveRouting",
|
|
925
|
+
label: "Cognitive Routing",
|
|
926
|
+
description: cognitiveRoutingRuntimeDisabled
|
|
927
|
+
? "Cognitive Routing configuration is visible for inspection but can only run in PiFlow."
|
|
928
|
+
: "Configure the automatic standard/reasoning profiles and choose whether Freeflow may manage model state for this repository.",
|
|
929
|
+
kind: "group",
|
|
930
|
+
value: cognitiveRoutingRuntimeDisabled ? false : (cognitiveRoutingState?.enabled ?? false),
|
|
931
|
+
inactive: freeflowInactive || !skillsEnabled,
|
|
932
|
+
displaySuffix: cognitiveRoutingStatus,
|
|
933
|
+
children: [cognitiveRoutingEnabledItem, ...cognitiveRoutingProfiles, ...cognitiveRoutingSessionStart],
|
|
934
|
+
};
|
|
935
|
+
})();
|
|
788
936
|
return [
|
|
789
937
|
freeflowItem,
|
|
790
938
|
interactionItem,
|
|
791
939
|
skillsItem,
|
|
792
940
|
sessionModeItem,
|
|
793
941
|
defaultModeItem,
|
|
942
|
+
...(cognitiveRoutingGroup ? [cognitiveRoutingGroup] : []),
|
|
794
943
|
{
|
|
795
|
-
id: "
|
|
796
|
-
label: "
|
|
797
|
-
description:
|
|
798
|
-
"Shared repository settings for routed evidence tools, native output safety net, vault storage, script transforms, and observed tool routing.",
|
|
944
|
+
id: "freeflow.context",
|
|
945
|
+
label: "Freeflow Context",
|
|
946
|
+
description: "Choose which context projection and conversation-history operations are available to the model.",
|
|
799
947
|
kind: "group",
|
|
800
|
-
value:
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
children: routerItems,
|
|
948
|
+
value: contextVirtualizationItem.effectiveValue === true || conversationHistoryItem.effectiveValue === true,
|
|
949
|
+
displaySuffix: `${[contextVirtualizationItem, conversationHistoryItem].filter((item) => item.effectiveValue === true).length}/2 enabled`,
|
|
950
|
+
children: [contextVirtualizationItem, conversationHistoryItem],
|
|
804
951
|
},
|
|
805
952
|
];
|
|
806
953
|
}
|
|
807
|
-
function migrateLegacyRouterConfig(config) {
|
|
808
|
-
const outputRouter = isRecord(config.outputRouter) ? config.outputRouter : undefined;
|
|
809
|
-
if (!outputRouter) return;
|
|
810
|
-
if (config.scriptTransform !== undefined && outputRouter.scriptTransform === undefined) {
|
|
811
|
-
outputRouter.scriptTransform = config.scriptTransform;
|
|
812
|
-
delete config.scriptTransform;
|
|
813
|
-
}
|
|
814
|
-
if (config.observedRouting !== undefined && outputRouter.observedRouting === undefined) {
|
|
815
|
-
outputRouter.observedRouting = config.observedRouting;
|
|
816
|
-
delete config.observedRouting;
|
|
817
|
-
}
|
|
818
|
-
const thresholds = isRecord(outputRouter.thresholds) ? outputRouter.thresholds : {};
|
|
819
|
-
if (outputRouter.largeOutputBytes !== undefined && thresholds.largeOutputBytes === undefined) {
|
|
820
|
-
thresholds.largeOutputBytes = outputRouter.largeOutputBytes;
|
|
821
|
-
outputRouter.thresholds = thresholds;
|
|
822
|
-
delete outputRouter.largeOutputBytes;
|
|
823
|
-
}
|
|
824
|
-
if (outputRouter.largeOutputLines !== undefined && thresholds.largeOutputLines === undefined) {
|
|
825
|
-
thresholds.largeOutputLines = outputRouter.largeOutputLines;
|
|
826
|
-
outputRouter.thresholds = thresholds;
|
|
827
|
-
delete outputRouter.largeOutputLines;
|
|
828
|
-
}
|
|
829
|
-
const vault = isRecord(outputRouter.vault) ? outputRouter.vault : {};
|
|
830
|
-
if (outputRouter.vaultRoot !== undefined && vault.root === undefined) {
|
|
831
|
-
vault.root = outputRouter.vaultRoot;
|
|
832
|
-
outputRouter.vault = vault;
|
|
833
|
-
delete outputRouter.vaultRoot;
|
|
834
|
-
}
|
|
835
|
-
if (outputRouter.vaultRetentionDays !== undefined && !isRecord(vault.retention)) {
|
|
836
|
-
vault.retention = {
|
|
837
|
-
strategy: "ttl",
|
|
838
|
-
ttlDays: outputRouter.vaultRetentionDays,
|
|
839
|
-
};
|
|
840
|
-
outputRouter.vault = vault;
|
|
841
|
-
delete outputRouter.vaultRetentionDays;
|
|
842
|
-
}
|
|
843
|
-
const hints = isRecord(outputRouter.hints) ? outputRouter.hints : {};
|
|
844
|
-
if (outputRouter.generatedPaths !== undefined && hints.generatedPathGlobs === undefined) {
|
|
845
|
-
hints.generatedPathGlobs = outputRouter.generatedPaths;
|
|
846
|
-
outputRouter.hints = hints;
|
|
847
|
-
delete outputRouter.generatedPaths;
|
|
848
|
-
}
|
|
849
|
-
if (outputRouter.noisyCommandHints !== undefined && hints.noisyCommandPatterns === undefined) {
|
|
850
|
-
hints.noisyCommandPatterns = outputRouter.noisyCommandHints;
|
|
851
|
-
outputRouter.hints = hints;
|
|
852
|
-
delete outputRouter.noisyCommandHints;
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
954
|
function pruneKnownDefaults(config) {
|
|
856
955
|
const defaultPaths = [
|
|
857
956
|
{ path: ["enabled"], value: DEFAULT_FREEFLOW_ENABLED },
|
|
858
|
-
{
|
|
859
|
-
path: ["interactionContract"],
|
|
860
|
-
value: DEFAULT_INTERACTION_CONTRACT_ENABLED,
|
|
861
|
-
},
|
|
957
|
+
{ path: ["interactionContract"], value: DEFAULT_INTERACTION_CONTRACT_ENABLED },
|
|
862
958
|
{ path: ["skills", "enabled"], value: DEFAULT_SKILLS_ENABLED },
|
|
863
|
-
{ path: ["
|
|
864
|
-
{
|
|
865
|
-
path: ["outputRouter", "postToolRouting"],
|
|
866
|
-
value: DEFAULT_POST_TOOL_ROUTING,
|
|
867
|
-
},
|
|
868
|
-
{ path: ["outputRouter", "storagePolicy"], value: DEFAULT_STORAGE_POLICY },
|
|
869
|
-
{
|
|
870
|
-
path: ["outputRouter", "thresholds", "largeOutputBytes"],
|
|
871
|
-
value: DEFAULT_ROUTER_THRESHOLDS.largeOutputBytes,
|
|
872
|
-
},
|
|
873
|
-
{
|
|
874
|
-
path: ["outputRouter", "thresholds", "largeOutputLines"],
|
|
875
|
-
value: DEFAULT_ROUTER_THRESHOLDS.largeOutputLines,
|
|
876
|
-
},
|
|
877
|
-
{ path: ["outputRouter", "vault", "root"], value: DEFAULT_VAULT_ROOT },
|
|
878
|
-
{
|
|
879
|
-
path: ["outputRouter", "vault", "retention", "ttlDays"],
|
|
880
|
-
value: DEFAULT_VAULT_RETENTION.ttlDays,
|
|
881
|
-
},
|
|
882
|
-
{
|
|
883
|
-
path: ["outputRouter", "scriptTransform", "enabled"],
|
|
884
|
-
value: DEFAULT_SCRIPT_TRANSFORM_CONFIG.enabled,
|
|
885
|
-
},
|
|
886
|
-
{
|
|
887
|
-
path: ["outputRouter", "scriptTransform", "sandbox"],
|
|
888
|
-
value: DEFAULT_SCRIPT_TRANSFORM_CONFIG.sandbox,
|
|
889
|
-
},
|
|
890
|
-
{
|
|
891
|
-
path: ["outputRouter", "scriptTransform", "languages"],
|
|
892
|
-
value: DEFAULT_SCRIPT_TRANSFORM_CONFIG.languages,
|
|
893
|
-
},
|
|
894
|
-
{
|
|
895
|
-
path: ["outputRouter", "scriptTransform", "network"],
|
|
896
|
-
value: DEFAULT_SCRIPT_TRANSFORM_CONFIG.network,
|
|
897
|
-
},
|
|
898
|
-
{
|
|
899
|
-
path: ["outputRouter", "scriptTransform", "rawScriptPersistence"],
|
|
900
|
-
value: DEFAULT_SCRIPT_TRANSFORM_CONFIG.rawScriptPersistence,
|
|
901
|
-
},
|
|
902
|
-
{
|
|
903
|
-
path: ["outputRouter", "scriptTransform", "limits", "timeoutMs"],
|
|
904
|
-
value: DEFAULT_SCRIPT_TRANSFORM_CONFIG.limits.timeoutMs,
|
|
905
|
-
},
|
|
906
|
-
{
|
|
907
|
-
path: ["outputRouter", "scriptTransform", "limits", "maxInputBytes"],
|
|
908
|
-
value: DEFAULT_SCRIPT_TRANSFORM_CONFIG.limits.maxInputBytes,
|
|
909
|
-
},
|
|
910
|
-
{
|
|
911
|
-
path: ["outputRouter", "scriptTransform", "limits", "maxOutputBytes"],
|
|
912
|
-
value: DEFAULT_SCRIPT_TRANSFORM_CONFIG.limits.maxOutputBytes,
|
|
913
|
-
},
|
|
914
|
-
{
|
|
915
|
-
path: ["outputRouter", "observedRouting", "enabled"],
|
|
916
|
-
value: DEFAULT_OBSERVED_ROUTING_CONFIG.enabled,
|
|
917
|
-
},
|
|
918
|
-
{
|
|
919
|
-
path: ["outputRouter", "observedRouting", "onRoutingFailure"],
|
|
920
|
-
value: DEFAULT_OBSERVED_ROUTING_CONFIG.onRoutingFailure,
|
|
921
|
-
},
|
|
922
|
-
{
|
|
923
|
-
path: ["outputRouter", "observedRouting", "mcp", "servers"],
|
|
924
|
-
value: DEFAULT_OBSERVED_ROUTING_CONFIG.mcp.servers,
|
|
925
|
-
},
|
|
926
|
-
{
|
|
927
|
-
path: ["outputRouter", "observedRouting", "web", "enabled"],
|
|
928
|
-
value: DEFAULT_OBSERVED_ROUTING_CONFIG.web.enabled,
|
|
929
|
-
},
|
|
930
|
-
{
|
|
931
|
-
path: ["outputRouter", "observedRouting", "fetch", "enabled"],
|
|
932
|
-
value: DEFAULT_OBSERVED_ROUTING_CONFIG.fetch.enabled,
|
|
933
|
-
},
|
|
934
|
-
{
|
|
935
|
-
path: ["outputRouter", "observedRouting", "codeSearch", "enabled"],
|
|
936
|
-
value: DEFAULT_OBSERVED_ROUTING_CONFIG.codeSearch.enabled,
|
|
937
|
-
},
|
|
959
|
+
{ path: ["conversationHistory"], value: DEFAULT_CONVERSATION_HISTORY_ENABLED },
|
|
938
960
|
];
|
|
939
961
|
for (const item of defaultPaths) {
|
|
940
962
|
if (valuesEqual(getPath(config, item.path), item.value)) {
|
|
941
963
|
deletePath(config, item.path);
|
|
942
964
|
}
|
|
943
965
|
}
|
|
944
|
-
for (const producer of ["web", "fetch", "codeSearch"]) {
|
|
945
|
-
const enabledPath = ["outputRouter", "observedRouting", producer, "enabled"];
|
|
946
|
-
const persistencePath = ["outputRouter", "observedRouting", producer, "persistence"];
|
|
947
|
-
if (getPath(config, enabledPath) !== true && getPath(config, persistencePath) === "none") {
|
|
948
|
-
deletePath(config, persistencePath);
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
const servers = getPath(config, ["outputRouter", "observedRouting", "mcp", "servers"]);
|
|
952
|
-
if (isRecord(servers) && Object.keys(servers).length === 0) {
|
|
953
|
-
deletePath(config, ["outputRouter", "observedRouting", "mcp", "servers"]);
|
|
954
|
-
}
|
|
955
966
|
}
|
|
956
967
|
async function ensureLocalConfigIgnored(cwd) {
|
|
957
968
|
let gitPath;
|
|
@@ -1014,14 +1025,18 @@ async function updateConfig(cwd, item, value, scope = item.configScope ?? "repos
|
|
|
1014
1025
|
const current = await readFreeflowConfig(cwd);
|
|
1015
1026
|
const next = cloneJson(current);
|
|
1016
1027
|
setConfigValue(next, item, value);
|
|
1017
|
-
migrateLegacyRouterConfig(next);
|
|
1018
1028
|
pruneKnownDefaults(next);
|
|
1019
1029
|
await mkdir(join(cwd, ".freeflow"), { recursive: true });
|
|
1020
1030
|
await writeFile(join(cwd, ".freeflow/config.json"), `${JSON.stringify(next, null, 2)}\n`, "utf8");
|
|
1021
1031
|
}
|
|
1032
|
+
function isCognitiveRoutingProfileItem(item) {
|
|
1033
|
+
return item.id === "freeflow.cognitiveRouting.standard" || item.id === "freeflow.cognitiveRouting.reasoning";
|
|
1034
|
+
}
|
|
1022
1035
|
function valueForDisplay(item) {
|
|
1023
1036
|
let value;
|
|
1024
|
-
if (item
|
|
1037
|
+
if (isCognitiveRoutingProfileItem(item) && item.format) {
|
|
1038
|
+
value = item.format(effectiveItemValue(item));
|
|
1039
|
+
} else if (item.configScope === "local" || item.configScope === "session") {
|
|
1025
1040
|
value = formatCoreValue(effectiveItemValue(item));
|
|
1026
1041
|
} else if (item.kind === "boolean") {
|
|
1027
1042
|
value = booleanValue(item.value);
|
|
@@ -1055,10 +1070,14 @@ function refreshSettingsDerivedState(items) {
|
|
|
1055
1070
|
const freeflowInactive = freeflowItem ? effectiveItemValue(freeflowItem) !== true : false;
|
|
1056
1071
|
const skillsItem = findSettingsItem(items, "freeflow.skills.enabled");
|
|
1057
1072
|
const skillsEnabled = skillsItem ? effectiveItemValue(skillsItem) === true : true;
|
|
1058
|
-
const routerEnabled = findSettingsItem(items, "outputRouter.enabled")?.value === true;
|
|
1059
1073
|
const sessionModeItem = findSettingsItem(items, "freeflow.sessionMode");
|
|
1060
1074
|
const defaultModeItem = findSettingsItem(items, "freeflow.defaultMode");
|
|
1061
|
-
const
|
|
1075
|
+
const cognitiveRoutingGroup = findSettingsItem(items, "freeflow.cognitiveRouting");
|
|
1076
|
+
const cognitiveRoutingEnabledItem = findSettingsItem(items, "freeflow.cognitiveRouting.enabled");
|
|
1077
|
+
const cognitiveRoutingProfiles = [
|
|
1078
|
+
findSettingsItem(items, "freeflow.cognitiveRouting.standard"),
|
|
1079
|
+
findSettingsItem(items, "freeflow.cognitiveRouting.reasoning"),
|
|
1080
|
+
];
|
|
1062
1081
|
if (sessionModeItem) {
|
|
1063
1082
|
const defaultMode = String(
|
|
1064
1083
|
defaultModeItem ? effectiveItemValue(defaultModeItem) : (sessionModeItem.inheritedValue ?? "workflow"),
|
|
@@ -1076,28 +1095,57 @@ function refreshSettingsDerivedState(items) {
|
|
|
1076
1095
|
...MODE_DESCRIPTIONS,
|
|
1077
1096
|
};
|
|
1078
1097
|
}
|
|
1079
|
-
if (
|
|
1080
|
-
|
|
1081
|
-
|
|
1098
|
+
if (cognitiveRoutingGroup && cognitiveRoutingEnabledItem) {
|
|
1099
|
+
const enabled = effectiveItemValue(cognitiveRoutingEnabledItem) === true;
|
|
1100
|
+
const profilesConfigured = cognitiveRoutingProfiles.every((item) =>
|
|
1101
|
+
item ? isCognitiveRoutingProfile(effectiveItemValue(item)) : false,
|
|
1102
|
+
);
|
|
1103
|
+
cognitiveRoutingGroup.value = enabled;
|
|
1104
|
+
cognitiveRoutingGroup.displaySuffix = enabled
|
|
1105
|
+
? profilesConfigured
|
|
1106
|
+
? cognitiveRoutingGroup.displaySuffix === "active"
|
|
1107
|
+
? "active"
|
|
1108
|
+
: "configured"
|
|
1109
|
+
: "not configured"
|
|
1110
|
+
: "disabled";
|
|
1111
|
+
cognitiveRoutingGroup.inactive = freeflowInactive || !skillsEnabled;
|
|
1112
|
+
}
|
|
1113
|
+
const contextGroup = findSettingsItem(items, "freeflow.context");
|
|
1114
|
+
if (contextGroup?.children?.length) {
|
|
1115
|
+
const enabledCount = contextGroup.children.filter((item) => effectiveItemValue(item) === true).length;
|
|
1116
|
+
contextGroup.value = enabledCount > 0;
|
|
1117
|
+
contextGroup.displaySuffix = `${enabledCount}/${contextGroup.children.length} enabled`;
|
|
1082
1118
|
}
|
|
1083
1119
|
walkSettingsItems(items, (candidate) => {
|
|
1084
1120
|
if (candidate.id === "freeflow.session.reset") {
|
|
1085
1121
|
candidate.inactive = false;
|
|
1086
1122
|
} else if (candidate.configScope) {
|
|
1087
|
-
const inactive =
|
|
1123
|
+
const inactive =
|
|
1124
|
+
candidate.id === "freeflow.enabled"
|
|
1125
|
+
? false
|
|
1126
|
+
: candidate.runtimeInactive === true ||
|
|
1127
|
+
freeflowInactive ||
|
|
1128
|
+
(!skillsEnabled &&
|
|
1129
|
+
[
|
|
1130
|
+
"freeflow.cognitiveRouting",
|
|
1131
|
+
"freeflow.cognitiveRouting.enabled",
|
|
1132
|
+
"freeflow.cognitiveRouting.standard",
|
|
1133
|
+
"freeflow.cognitiveRouting.reasoning",
|
|
1134
|
+
"freeflow.cognitiveRouting.sessionStart.control",
|
|
1135
|
+
"freeflow.cognitiveRouting.sessionStart.profile",
|
|
1136
|
+
"freeflow.contextVirtualization",
|
|
1137
|
+
"freeflow.conversationHistory",
|
|
1138
|
+
].includes(candidate.id));
|
|
1088
1139
|
const displayInactive = candidate.id === "freeflow.defaultMode" ? freeflowInactive || !skillsEnabled : inactive;
|
|
1089
1140
|
candidate.inactive = inactive;
|
|
1090
1141
|
candidate.displaySuffix = coreDisplaySuffix(candidate, displayInactive);
|
|
1091
1142
|
} else if (candidate.id === "freeflow.sessionMode") {
|
|
1092
|
-
candidate.inactive = freeflowInactive || !skillsEnabled;
|
|
1093
|
-
} else if (candidate.id === "outputRouter.group") {
|
|
1094
|
-
candidate.inactive = freeflowInactive;
|
|
1095
|
-
} else if (candidate.id === "outputRouter.enabled") {
|
|
1096
|
-
candidate.inactive = freeflowInactive;
|
|
1097
|
-
} else if (candidate.id.startsWith("outputRouter.")) {
|
|
1098
|
-
candidate.inactive = freeflowInactive || !routerEnabled;
|
|
1143
|
+
candidate.inactive = candidate.runtimeInactive === true || freeflowInactive || !skillsEnabled;
|
|
1099
1144
|
} else {
|
|
1100
|
-
candidate.inactive =
|
|
1145
|
+
candidate.inactive =
|
|
1146
|
+
candidate.runtimeInactive === true ||
|
|
1147
|
+
freeflowInactive ||
|
|
1148
|
+
(!skillsEnabled && candidate.id === "freeflow.cognitiveRouting");
|
|
1101
1149
|
}
|
|
1102
1150
|
});
|
|
1103
1151
|
}
|
|
@@ -1137,6 +1185,7 @@ function settingsEntries(items, rootItems, onChange) {
|
|
|
1137
1185
|
currentChoiceKey: () => String(item.value),
|
|
1138
1186
|
choices: settingsChoices(item),
|
|
1139
1187
|
children: item.children ? () => settingsEntries(item.children, rootItems, onChange) : undefined,
|
|
1188
|
+
wizard: item.wizard ? () => item.wizard() : undefined,
|
|
1140
1189
|
edit:
|
|
1141
1190
|
!item.children && !["boolean", "enum", "group"].includes(item.kind)
|
|
1142
1191
|
? {
|
|
@@ -1191,42 +1240,44 @@ async function openSettings(options) {
|
|
|
1191
1240
|
await component?.waitForWrites();
|
|
1192
1241
|
return component?.sessionResult() ?? { changed: false, configChanged: false, failed: false };
|
|
1193
1242
|
}
|
|
1194
|
-
function
|
|
1195
|
-
const settingsConfig = normalizedSettingsConfig(rawConfig);
|
|
1196
|
-
const actual = normalizeFreeflowConfig(rawConfig).config;
|
|
1197
|
-
const freeflowEnabled = rawConfig.enabled !== false;
|
|
1198
|
-
const router = actual.outputRouter;
|
|
1199
|
-
const configured = settingsConfig;
|
|
1200
|
-
const generatedPaths = router.hints?.generatedPathGlobs?.length ?? 0;
|
|
1201
|
-
const noisyHints = router.hints?.noisyCommandPatterns?.length ?? 0;
|
|
1202
|
-
return [
|
|
1203
|
-
`Output Router: ${freeflowEnabled && router.enabled ? "enabled" : "disabled"}${freeflowEnabled ? "" : " (inactive: Freeflow off)"}`,
|
|
1204
|
-
`script transform: ${freeflowEnabled && configured.scriptTransform.enabled ? "enabled" : "disabled"}${freeflowEnabled && router.enabled ? "" : " (inactive)"}`,
|
|
1205
|
-
`observed routing: ${freeflowEnabled && configured.observedRouting.enabled ? "enabled" : "disabled"}${freeflowEnabled && router.enabled ? "" : " (inactive)"}`,
|
|
1206
|
-
`native safety net: ${freeflowEnabled ? router.postToolRouting : "off"}`,
|
|
1207
|
-
`storage: ${router.storagePolicy}`,
|
|
1208
|
-
`generated paths: ${generatedPaths}`,
|
|
1209
|
-
`noisy command hints: ${noisyHints}`,
|
|
1210
|
-
].join("; ");
|
|
1211
|
-
}
|
|
1212
|
-
function freeflowStatusText(state) {
|
|
1243
|
+
function freeflowStatusText(state, cognitiveRoutingController) {
|
|
1213
1244
|
if (!state.configured) {
|
|
1214
1245
|
return state.configExists
|
|
1215
1246
|
? `Freeflow: inactive (invalid config: ${state.parseError ?? "unknown parse error"}); run /setup-freeflow or fix .freeflow/config.json`
|
|
1216
1247
|
: "Freeflow: inactive (repo not set up); run /setup-freeflow";
|
|
1217
1248
|
}
|
|
1218
1249
|
const sessionSuffix = (source) => (source === "session" ? " (session override)" : "");
|
|
1250
|
+
const routingState = cognitiveRoutingController?.state();
|
|
1251
|
+
const cognitiveRouting = state.cognitiveRouting;
|
|
1252
|
+
const cognitiveRoutingStatus = cognitiveRouting
|
|
1253
|
+
? cognitiveRouting.blockingReason?.code === "runtime_disabled"
|
|
1254
|
+
? "disabled (PiFlow only)"
|
|
1255
|
+
: cognitiveRouting.effective
|
|
1256
|
+
? routingState?.effective
|
|
1257
|
+
? `active (${routingState.activeProfile ?? "unknown"}, ${routingState.controlMode})`
|
|
1258
|
+
: "effective (inactive)"
|
|
1259
|
+
: cognitiveRouting.enabled
|
|
1260
|
+
? `blocked (${cognitiveRouting.blockingReason.code})`
|
|
1261
|
+
: "disabled"
|
|
1262
|
+
: undefined;
|
|
1263
|
+
const contextEnabled = state.contextVirtualization?.effective || state.conversationHistory?.effective;
|
|
1219
1264
|
return [
|
|
1220
1265
|
`Freeflow: ${state.enabled ? "enabled" : "disabled"}${sessionSuffix(state.configSources.enabled)}`,
|
|
1221
1266
|
`interaction contract: ${state.interactionContract.effective ? "enabled" : "disabled"}${sessionSuffix(state.configSources.interactionContract)}`,
|
|
1222
1267
|
`skills: ${state.skills.effective ? "enabled" : "disabled (workflow modes inactive)"}${sessionSuffix(state.configSources.skillsEnabled)}`,
|
|
1223
|
-
`
|
|
1268
|
+
`context: ${contextEnabled ? "enabled" : "disabled"} (virtualization ${state.contextVirtualization?.effective ? "enabled" : "disabled"}, history ${state.conversationHistory?.effective ? "enabled" : "disabled"})`,
|
|
1269
|
+
...(cognitiveRoutingStatus ? [`cognitive routing: ${cognitiveRoutingStatus}`] : []),
|
|
1224
1270
|
].join("; ");
|
|
1225
1271
|
}
|
|
1226
1272
|
const NON_TUI_SETTINGS_GUIDANCE =
|
|
1227
1273
|
"Freeflow settings require Pi TUI mode. Use /freeflow status to inspect current state; supported non-TUI changes are /freeflow enable, /freeflow disable, and /freeflow mode <conversation|workflow|strict-workflow|reset>.";
|
|
1228
1274
|
const NON_TUI_MODE_GUIDANCE =
|
|
1229
1275
|
"Freeflow mode selector requires Pi TUI mode. Use /freeflow mode conversation, /freeflow mode workflow, /freeflow mode strict-workflow, or /freeflow mode reset.";
|
|
1276
|
+
function ensureSettingsIdle(ctx) {
|
|
1277
|
+
if (typeof ctx?.isIdle !== "function" || ctx.isIdle()) return true;
|
|
1278
|
+
ctx.ui?.notify?.("Freeflow settings and profile changes are available only while Pi is idle.", "warning");
|
|
1279
|
+
return false;
|
|
1280
|
+
}
|
|
1230
1281
|
function nonTuiGuidance(ctx, message) {
|
|
1231
1282
|
if (ctx?.hasUI === false) throw new Error(message);
|
|
1232
1283
|
ctx?.ui?.notify?.(message, "warning");
|
|
@@ -1238,9 +1289,10 @@ async function finalizeSettingsSession(
|
|
|
1238
1289
|
afterChange,
|
|
1239
1290
|
savedMessage,
|
|
1240
1291
|
reloadWarning = "Run /reload for Freeflow changes to fully apply.",
|
|
1292
|
+
afterChangeOptions = {},
|
|
1241
1293
|
) {
|
|
1242
1294
|
if (!session.configChanged) return false;
|
|
1243
|
-
await afterChange(true);
|
|
1295
|
+
await afterChange(true, afterChangeOptions);
|
|
1244
1296
|
if (session.failed) return false;
|
|
1245
1297
|
ctx.ui.notify(savedMessage, "info");
|
|
1246
1298
|
if (typeof ctx.reload !== "function") {
|
|
@@ -1266,29 +1318,7 @@ async function finalizeSessionSettings(session, ctx, afterChange) {
|
|
|
1266
1318
|
await ctx.reload();
|
|
1267
1319
|
return true;
|
|
1268
1320
|
}
|
|
1269
|
-
function
|
|
1270
|
-
return action === "" || action === "settings" || ["enable", "on", "true", "disable", "off", "false"].includes(action);
|
|
1271
|
-
}
|
|
1272
|
-
async function maybeBlockLayerMutation(action, ctx, layerName) {
|
|
1273
|
-
if (!actionIsMutation(action)) return false;
|
|
1274
|
-
const state = await readCapabilityState(ctx.cwd);
|
|
1275
|
-
if (!state.configured) {
|
|
1276
|
-
ctx.ui.notify(
|
|
1277
|
-
`Freeflow is installed but this repo is not set up. Run /setup-freeflow before configuring ${layerName}.`,
|
|
1278
|
-
"warning",
|
|
1279
|
-
);
|
|
1280
|
-
return true;
|
|
1281
|
-
}
|
|
1282
|
-
if (!state.enabled) {
|
|
1283
|
-
ctx.ui.notify(
|
|
1284
|
-
`Freeflow is disabled for this repo. Use /freeflow enable before configuring ${layerName}.`,
|
|
1285
|
-
"warning",
|
|
1286
|
-
);
|
|
1287
|
-
return true;
|
|
1288
|
-
}
|
|
1289
|
-
return false;
|
|
1290
|
-
}
|
|
1291
|
-
export async function handleFreeflowCommand(args, ctx, afterChange, pi) {
|
|
1321
|
+
export async function handleFreeflowCommand(args, ctx, afterChange, pi, cognitiveRoutingController) {
|
|
1292
1322
|
const input = (args ?? "settings").trim().toLowerCase() || "settings";
|
|
1293
1323
|
const [action, ...rest] = input.split(/\s+/);
|
|
1294
1324
|
const actionValue = rest.join(" ");
|
|
@@ -1304,17 +1334,17 @@ export async function handleFreeflowCommand(args, ctx, afterChange, pi) {
|
|
|
1304
1334
|
}
|
|
1305
1335
|
const [layers, state, modeState] = await Promise.all([
|
|
1306
1336
|
readFreeflowConfigLayers(ctx.cwd),
|
|
1307
|
-
readCapabilityState(ctx.cwd),
|
|
1337
|
+
readCapabilityState(ctx.cwd, ctx, pi?.host),
|
|
1308
1338
|
readModeState(ctx.cwd),
|
|
1309
1339
|
]);
|
|
1310
1340
|
const configState = layers.repository;
|
|
1311
1341
|
const raw = configState.valid ? configState.parsed : {};
|
|
1312
1342
|
if (action === "status") {
|
|
1313
|
-
ctx.ui.notify(freeflowStatusText(state), "info");
|
|
1343
|
+
ctx.ui.notify(freeflowStatusText(state, cognitiveRoutingController), "info");
|
|
1314
1344
|
return { changed: false, reloaded: false };
|
|
1315
1345
|
}
|
|
1316
1346
|
if (!configState.valid) {
|
|
1317
|
-
ctx.ui.notify(freeflowStatusText(state), "warning");
|
|
1347
|
+
ctx.ui.notify(freeflowStatusText(state, cognitiveRoutingController), "warning");
|
|
1318
1348
|
return { changed: false, reloaded: false, error: "not_configured" };
|
|
1319
1349
|
}
|
|
1320
1350
|
if (layers.local.exists && !layers.local.valid) {
|
|
@@ -1325,6 +1355,7 @@ export async function handleFreeflowCommand(args, ctx, afterChange, pi) {
|
|
|
1325
1355
|
return { changed: false, reloaded: false, error: "invalid_local_config" };
|
|
1326
1356
|
}
|
|
1327
1357
|
if (action === "mode") {
|
|
1358
|
+
if (!ensureSettingsIdle(ctx)) return { changed: false, reloaded: false, error: "busy" };
|
|
1328
1359
|
if (actionValue) {
|
|
1329
1360
|
const result = await handleModeCommand(actionValue, ctx, pi);
|
|
1330
1361
|
return { changed: result.changed, reloaded: false, error: result.error };
|
|
@@ -1340,6 +1371,9 @@ export async function handleFreeflowCommand(args, ctx, afterChange, pi) {
|
|
|
1340
1371
|
const item = freeflowItems(raw, modeState, {
|
|
1341
1372
|
scope: "local",
|
|
1342
1373
|
layers,
|
|
1374
|
+
cognitiveRouting: state.cognitiveRouting,
|
|
1375
|
+
ctx,
|
|
1376
|
+
hostInfo: pi?.host,
|
|
1343
1377
|
}).find((candidate) => candidate.id === "freeflow.sessionMode");
|
|
1344
1378
|
const session = await openSettings({
|
|
1345
1379
|
title: "Freeflow Mode",
|
|
@@ -1354,10 +1388,14 @@ export async function handleFreeflowCommand(args, ctx, afterChange, pi) {
|
|
|
1354
1388
|
return { changed: session.changed, reloaded: false, error: session.failed ? "write_failed" : undefined };
|
|
1355
1389
|
}
|
|
1356
1390
|
if (["enable", "on", "true", "disable", "off", "false"].includes(action)) {
|
|
1391
|
+
if (!ensureSettingsIdle(ctx)) return { changed: false, reloaded: false, error: "busy" };
|
|
1357
1392
|
const enabled = ["enable", "on", "true"].includes(action);
|
|
1358
1393
|
const item = freeflowItems(raw, modeState, {
|
|
1359
1394
|
scope: "repository",
|
|
1360
1395
|
layers,
|
|
1396
|
+
cognitiveRouting: state.cognitiveRouting,
|
|
1397
|
+
ctx,
|
|
1398
|
+
hostInfo: pi?.host,
|
|
1361
1399
|
}).find((candidate) => candidate.id === "freeflow.enabled");
|
|
1362
1400
|
await updateConfig(ctx.cwd, item, enabled, "repository");
|
|
1363
1401
|
await afterChange(true);
|
|
@@ -1388,10 +1426,18 @@ export async function handleFreeflowCommand(args, ctx, afterChange, pi) {
|
|
|
1388
1426
|
);
|
|
1389
1427
|
return { changed: false, reloaded: false, error: "invalid_scope" };
|
|
1390
1428
|
}
|
|
1429
|
+
if (!ensureSettingsIdle(ctx)) return { changed: false, reloaded: false, error: "busy" };
|
|
1430
|
+
let reconcileCognitiveRouting = settingsScope === "session";
|
|
1391
1431
|
const items =
|
|
1392
1432
|
settingsScope === "session"
|
|
1393
|
-
? sessionFreeflowItems(state, modeState)
|
|
1394
|
-
: freeflowItems(raw, modeState, {
|
|
1433
|
+
? sessionFreeflowItems(state, modeState, cognitiveRoutingController)
|
|
1434
|
+
: freeflowItems(raw, modeState, {
|
|
1435
|
+
scope: settingsScope,
|
|
1436
|
+
layers,
|
|
1437
|
+
cognitiveRouting: state.cognitiveRouting,
|
|
1438
|
+
ctx,
|
|
1439
|
+
hostInfo: pi?.host,
|
|
1440
|
+
});
|
|
1395
1441
|
const session = await openSettings({
|
|
1396
1442
|
title:
|
|
1397
1443
|
settingsScope === "session"
|
|
@@ -1402,10 +1448,34 @@ export async function handleFreeflowCommand(args, ctx, afterChange, pi) {
|
|
|
1402
1448
|
items,
|
|
1403
1449
|
ctx,
|
|
1404
1450
|
onChange: async (item, value) => {
|
|
1451
|
+
if (!ensureSettingsIdle(ctx)) return { changed: false, reloadRequired: false };
|
|
1452
|
+
if (!isPiFlowHost(pi?.host) && item.id.startsWith("freeflow.cognitiveRouting.")) {
|
|
1453
|
+
ctx.ui.notify("Cognitive Routing is available only in PiFlow.", "warning");
|
|
1454
|
+
return { changed: false, reloadRequired: false };
|
|
1455
|
+
}
|
|
1405
1456
|
if (item.id === "freeflow.sessionMode") {
|
|
1406
1457
|
const result = await handleModeCommand(String(value), ctx, pi);
|
|
1407
1458
|
return { changed: result.changed, reloadRequired: false };
|
|
1408
1459
|
}
|
|
1460
|
+
if (item.id === "freeflow.cognitiveRouting.profile") {
|
|
1461
|
+
if (!cognitiveRoutingController) {
|
|
1462
|
+
ctx.ui.notify("Cognitive Routing is unavailable for this session.", "warning");
|
|
1463
|
+
return { changed: false, reloadRequired: false };
|
|
1464
|
+
}
|
|
1465
|
+
const result =
|
|
1466
|
+
value === "auto"
|
|
1467
|
+
? await cognitiveRoutingController.setAutomaticControl("profile-settings")
|
|
1468
|
+
: await cognitiveRoutingController.setManualProfile(value, "profile-settings");
|
|
1469
|
+
if ((result.status !== "automatic" && result.status !== "active") || result.reason) {
|
|
1470
|
+
ctx.ui.notify(
|
|
1471
|
+
`Cognitive Routing settings could not be applied: ${result.reason ?? result.status}.`,
|
|
1472
|
+
"warning",
|
|
1473
|
+
);
|
|
1474
|
+
return { changed: false, reloadRequired: false };
|
|
1475
|
+
}
|
|
1476
|
+
await afterChange(false);
|
|
1477
|
+
return { changed: true, reloadRequired: false };
|
|
1478
|
+
}
|
|
1409
1479
|
if (item.id === "freeflow.session.reset") {
|
|
1410
1480
|
const result = await resetSessionOverrides(ctx, pi);
|
|
1411
1481
|
return { changed: result.changed, reloadRequired: result.reloadRequired };
|
|
@@ -1415,6 +1485,8 @@ export async function handleFreeflowCommand(args, ctx, afterChange, pi) {
|
|
|
1415
1485
|
"freeflow.enabled": "enabled",
|
|
1416
1486
|
"freeflow.interactionContract": "interactionContract",
|
|
1417
1487
|
"freeflow.skills.enabled": "skillsEnabled",
|
|
1488
|
+
"freeflow.contextVirtualization": "contextVirtualization",
|
|
1489
|
+
"freeflow.conversationHistory": "conversationHistory",
|
|
1418
1490
|
};
|
|
1419
1491
|
const key = keyById[item.id];
|
|
1420
1492
|
const override = value === LOCAL_INHERIT ? null : value === "true";
|
|
@@ -1422,6 +1494,9 @@ export async function handleFreeflowCommand(args, ctx, afterChange, pi) {
|
|
|
1422
1494
|
return { changed: result.changed, reloadRequired: result.reloadRequired === true };
|
|
1423
1495
|
}
|
|
1424
1496
|
await updateConfig(ctx.cwd, item, value, item.configScope ?? "repository");
|
|
1497
|
+
if (!item.id.startsWith("freeflow.cognitiveRouting.sessionStart.")) {
|
|
1498
|
+
reconcileCognitiveRouting = true;
|
|
1499
|
+
}
|
|
1425
1500
|
return { changed: true, reloadRequired: true };
|
|
1426
1501
|
},
|
|
1427
1502
|
});
|
|
@@ -1435,53 +1510,8 @@ export async function handleFreeflowCommand(args, ctx, afterChange, pi) {
|
|
|
1435
1510
|
ctx,
|
|
1436
1511
|
afterChange,
|
|
1437
1512
|
`Freeflow ${savedTarget} saved. Reloading Freeflow runtime...`,
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
}
|
|
1441
|
-
export async function handleOutputRouterCommand(args, ctx, afterChange) {
|
|
1442
|
-
const action = (args ?? "").trim().toLowerCase();
|
|
1443
|
-
const raw = await readFreeflowConfig(ctx.cwd);
|
|
1444
|
-
if (await maybeBlockLayerMutation(action, ctx, "Output Router")) {
|
|
1445
|
-
return { changed: false, reloaded: false, error: "freeflow_inactive" };
|
|
1446
|
-
}
|
|
1447
|
-
if (action === "status") {
|
|
1448
|
-
const state = await readCapabilityState(ctx.cwd);
|
|
1449
|
-
const prefix = !state.configured || !state.enabled ? `${freeflowStatusText(state)}; ` : "";
|
|
1450
|
-
ctx.ui.notify(`${prefix}${outputRouterStatusText(raw)}`, !state.configured || !state.enabled ? "warning" : "info");
|
|
1451
|
-
return { changed: false, reloaded: false };
|
|
1452
|
-
}
|
|
1453
|
-
if (["enable", "on", "true", "disable", "off", "false"].includes(action)) {
|
|
1454
|
-
const enabled = ["enable", "on", "true"].includes(action);
|
|
1455
|
-
const item = outputRouterItems(raw).find((candidate) => candidate.id === "outputRouter.enabled");
|
|
1456
|
-
await updateConfig(ctx.cwd, item, enabled);
|
|
1457
|
-
await afterChange(true);
|
|
1458
|
-
ctx.ui.notify(`Output Router ${enabled ? "enabled" : "disabled"}. Reloading Freeflow runtime...`, "info");
|
|
1459
|
-
if (typeof ctx.reload === "function") {
|
|
1460
|
-
await ctx.reload();
|
|
1461
|
-
return { changed: true, reloaded: true };
|
|
1462
|
-
}
|
|
1463
|
-
ctx.ui.notify("Run /reload for Output Router changes to fully apply.", "warning");
|
|
1464
|
-
return { changed: true, reloaded: false };
|
|
1465
|
-
}
|
|
1466
|
-
if (action && action !== "settings") {
|
|
1467
|
-
ctx.ui.notify("Usage: /output-router, /output-router settings, or /output-router status", "warning");
|
|
1468
|
-
return { changed: false, reloaded: false, error: "invalid_action" };
|
|
1469
|
-
}
|
|
1470
|
-
const session = await openSettings({
|
|
1471
|
-
title: "Output Router Settings",
|
|
1472
|
-
items: outputRouterItems(raw),
|
|
1473
|
-
ctx,
|
|
1474
|
-
onChange: async (item, value) => {
|
|
1475
|
-
await updateConfig(ctx.cwd, item, value);
|
|
1476
|
-
return { changed: true, reloadRequired: true };
|
|
1477
|
-
},
|
|
1478
|
-
});
|
|
1479
|
-
const reloaded = await finalizeSettingsSession(
|
|
1480
|
-
session,
|
|
1481
|
-
ctx,
|
|
1482
|
-
afterChange,
|
|
1483
|
-
"Output Router settings saved. Reloading Freeflow runtime...",
|
|
1484
|
-
"Run /reload for Output Router changes to fully apply.",
|
|
1513
|
+
"Run /reload for Freeflow changes to fully apply.",
|
|
1514
|
+
{ reconcileCognitiveRouting },
|
|
1485
1515
|
);
|
|
1486
1516
|
return { changed: session.changed, reloaded, error: session.failed ? "write_failed" : undefined };
|
|
1487
1517
|
}
|