@hassangameryt/freeflow 0.5.0 → 0.7.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 -2
- package/.codex-plugin/plugin.json +4 -5
- package/README.md +141 -163
- package/capabilities/cognitive-routing/SKILL.md +387 -0
- package/capabilities/context-virtualization/SKILL.md +103 -0
- package/capabilities/conversation-history/SKILL.md +53 -0
- package/command-surface.json +6 -37
- package/hooks/freeflow-runtime-context.mjs +50 -541
- package/hooks/hooks.json +1 -26
- package/hooks/tests/check-runtime-context-hook.sh +114 -308
- package/package.json +27 -17
- package/pi-extension/dist/cognitive-routing/commands.js +96 -0
- package/pi-extension/dist/cognitive-routing/config.js +232 -0
- package/pi-extension/dist/cognitive-routing/controller.js +1292 -0
- package/pi-extension/dist/cognitive-routing/history.js +451 -0
- package/pi-extension/dist/cognitive-routing/host.js +188 -0
- package/pi-extension/dist/cognitive-routing/pi-routing-session.js +414 -0
- package/pi-extension/dist/cognitive-routing/pi-session-control.js +320 -0
- package/pi-extension/dist/cognitive-routing/runtime.js +43 -0
- package/pi-extension/dist/cognitive-routing/tool.js +212 -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 +564 -129
- package/pi-extension/dist/runtime/runtime-context.js +357 -470
- 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 +572 -826
- package/pi-extension/dist/ui/text-component.js +57 -0
- package/pi-extension/src/cognitive-routing/commands.ts +129 -0
- package/pi-extension/src/cognitive-routing/config.ts +303 -0
- package/pi-extension/src/cognitive-routing/controller.ts +1628 -0
- package/pi-extension/src/cognitive-routing/history.ts +650 -0
- package/pi-extension/src/cognitive-routing/host.ts +266 -0
- package/pi-extension/src/cognitive-routing/pi-routing-session.ts +538 -0
- package/pi-extension/src/cognitive-routing/pi-session-control.ts +444 -0
- package/pi-extension/src/cognitive-routing/runtime.ts +57 -0
- package/pi-extension/src/cognitive-routing/tool.ts +273 -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 +597 -135
- package/pi-extension/src/runtime/runtime-context.ts +373 -491
- 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 +679 -910
- 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 +1128 -0
- package/pi-extension/tests/cognitive-routing/extension-lifecycle.test.js +1142 -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/pi-normal-lifecycle.test.js +434 -0
- package/pi-extension/tests/cognitive-routing/pi-session-control.test.js +174 -0
- package/pi-extension/tests/cognitive-routing/pi-session-history.test.js +91 -0
- package/pi-extension/tests/cognitive-routing/profile-resolution.test.js +279 -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/mode-free-core.test.js +218 -0
- package/pi-extension/tests/integration/pi-extension.test.js +532 -3701
- package/pi-extension/tests/integration/prompt-architecture.test.js +350 -0
- package/pi-extension/tests/integration/unaffected-regressions.test.js +411 -0
- package/runtime/prompts/cognitive-routing.md +10 -0
- package/runtime/prompts/context-virtualization.md +3 -0
- package/runtime/prompts/conversation-history.md +3 -0
- package/runtime/prompts/core.md +84 -0
- package/runtime/prompts/interaction-contract.md +11 -0
- package/skills/action-selection/SKILL.md +140 -0
- package/skills/action-selection/references/trajectory-stalls.md +71 -0
- package/skills/bypass/SKILL.md +109 -38
- package/skills/commit-work/SKILL.md +94 -55
- package/skills/commit-work/references/git-state-edges.md +94 -0
- package/skills/commit-work/references/simple-push.md +92 -0
- package/skills/commit-work/references/staging-decisions.md +79 -46
- package/skills/decision-gate/SKILL.md +80 -22
- package/skills/design-for-depth/SKILL.md +206 -60
- package/skills/design-for-depth/references/design-pressure-signals.md +108 -240
- package/skills/design-for-depth/references/interface-design-loop.md +77 -97
- package/skills/design-for-depth/references/module-and-dependency-design.md +147 -0
- package/skills/design-for-depth/references/software-design-philosophy.md +128 -174
- package/skills/design-for-depth/references/state-and-failure-boundaries.md +136 -0
- package/skills/diagnose-failure/SKILL.md +200 -48
- package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +18 -6
- package/skills/diagnose-failure/references/{flaky-and-performance.md → flaky-and-performance-diagnosis.md} +13 -5
- package/skills/discuss/SKILL.md +119 -65
- package/skills/discuss/references/discussion-continuity.md +123 -0
- package/skills/evaluate-skill/SKILL.md +166 -52
- package/skills/evaluate-skill/references/{definition-schema.md → evaluation-definition-schema.md} +43 -3
- package/skills/evaluate-skill/references/evaluation-design.md +56 -30
- package/skills/evaluate-skill/references/execution-and-evidence.md +8 -3
- package/skills/evaluate-skill/references/review-and-revision.md +62 -21
- 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 +99 -57
- package/skills/execute-work/references/code-practices.md +4 -4
- package/skills/execute-work/references/execute-work-edges.md +62 -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/release-work/SKILL.md +1 -1
- package/skills/release-work/references/release-evidence.md +1 -1
- package/skills/review-artifact/SKILL.md +129 -66
- package/skills/review-artifact/references/adjudicate-artifact-review.md +101 -0
- package/skills/review-artifact/references/independent-artifact-reviewer-contract.md +159 -0
- package/skills/review-work/SKILL.md +113 -68
- package/skills/review-work/references/adjudicate-work-review.md +97 -0
- package/skills/review-work/references/independent-work-reviewer-contract.md +139 -0
- package/skills/review-work/references/security-risk-lens.md +18 -16
- package/skills/setup-freeflow/SKILL.md +16 -17
- package/skills/setup-freeflow/references/activation-contract.md +24 -30
- package/skills/setup-freeflow/references/host-setup.md +6 -7
- package/skills/simplify-code/SKILL.md +97 -39
- package/skills/simplify-code/references/simplification-patterns.md +1 -1
- package/skills/tdd/SKILL.md +100 -53
- package/skills/tdd/references/test-design.md +3 -3
- package/skills/track-work/SKILL.md +195 -58
- package/skills/track-work/references/working-record-format.md +515 -0
- package/skills/track-work/scripts/lib/document.mjs +713 -0
- package/skills/track-work/scripts/lib/format.mjs +166 -0
- package/skills/track-work/scripts/lib/store.mjs +233 -0
- package/skills/track-work/scripts/lib/transitions.mjs +790 -0
- package/skills/track-work/scripts/lib/views.mjs +40 -0
- package/skills/track-work/scripts/working-record.mjs +316 -0
- package/skills/verify-work/SKILL.md +57 -19
- package/skills/verify-work/references/browser-runtime-evidence.md +2 -2
- package/skills/verify-work/references/integration-evidence.md +1 -7
- package/skills/verify-work/references/performance-evidence.md +2 -2
- package/skills/workflow/SKILL.md +137 -55
- package/skills/workflow/references/domain-skill-composition.md +26 -40
- package/skills/write-plan/SKILL.md +1 -1
- package/skills/write-skill/SKILL.md +218 -61
- package/skills/write-skill/references/activation-boundaries.md +63 -21
- package/skills/write-skill/references/agent-first-instructions.md +61 -27
- package/skills/write-skill/references/progressive-disclosure.md +97 -33
- package/skills/write-skill/references/skill-dependency-graphs.md +190 -0
- package/capabilities/output-router/SKILL.md +0 -206
- package/capabilities/output-router/references/safety-policy.md +0 -40
- package/capabilities/output-router/references/setup.md +0 -110
- 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/mode-contract/SKILL.md +0 -104
- package/skills/review-artifact/references/reviewer-prompt.md +0 -148
- package/skills/review-work/references/reviewer-prompt.md +0 -134
- 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
|
@@ -3,47 +3,19 @@ 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
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
|
-
import {
|
|
17
|
-
handleModeCommand,
|
|
18
6
|
readCapabilityState,
|
|
19
7
|
readFreeflowConfig,
|
|
20
8
|
readFreeflowConfigLayers,
|
|
21
9
|
readFreeflowLocalConfig,
|
|
22
|
-
readModeState,
|
|
23
10
|
resetSessionOverrides,
|
|
24
11
|
setSessionCoreOverride,
|
|
25
|
-
VALID_MODES,
|
|
26
12
|
} from "../runtime/runtime-context.js";
|
|
27
13
|
import { PiSettingsComponent } from "./settings-tui.js";
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const OBSERVED_PERSISTENCE_VALUES = ["none", "metadata-only", "exact"];
|
|
31
|
-
const SCRIPT_LANGUAGES = ["javascript", "python", "jq"];
|
|
14
|
+
import { supportsPiSessionModelStateApi } from "../cognitive-routing/pi-session-control.js";
|
|
15
|
+
import { isPiFlowHost } from "../runtime/runtime-identity.js";
|
|
32
16
|
const DEFAULT_FREEFLOW_ENABLED = true;
|
|
33
|
-
const
|
|
34
|
-
const
|
|
35
|
-
const MODE_VALUES = ["conversation", "workflow", "strict-workflow"];
|
|
36
|
-
const MODE_LABELS = {
|
|
37
|
-
default: "Use configured default",
|
|
38
|
-
conversation: "conversation",
|
|
39
|
-
workflow: "workflow",
|
|
40
|
-
"strict-workflow": "strict-workflow",
|
|
41
|
-
};
|
|
42
|
-
const MODE_DESCRIPTIONS = {
|
|
43
|
-
conversation: "Discussion and read-only inspection",
|
|
44
|
-
workflow: "Adaptive workflow for consequential work",
|
|
45
|
-
"strict-workflow": "Stronger decision and evidence pressure for high-risk work",
|
|
46
|
-
};
|
|
17
|
+
const DEFAULT_CONTEXT_VIRTUALIZATION_ENABLED = false;
|
|
18
|
+
const DEFAULT_CONVERSATION_HISTORY_ENABLED = false;
|
|
47
19
|
const LOCAL_INHERIT = "inherit";
|
|
48
20
|
const execFileAsync = promisify(execFile);
|
|
49
21
|
function isRecord(value) {
|
|
@@ -114,288 +86,9 @@ function setConfigValue(config, item, value) {
|
|
|
114
86
|
}
|
|
115
87
|
setPath(config, item.path, value);
|
|
116
88
|
}
|
|
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
89
|
function booleanValue(value) {
|
|
151
90
|
return value === true ? "enabled" : "disabled";
|
|
152
91
|
}
|
|
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
92
|
function configValueForChoice(item, value) {
|
|
400
93
|
const key = String(value);
|
|
401
94
|
if (item.configValues && Object.hasOwn(item.configValues, key)) {
|
|
@@ -404,10 +97,15 @@ function configValueForChoice(item, value) {
|
|
|
404
97
|
return value;
|
|
405
98
|
}
|
|
406
99
|
function effectiveItemValue(item) {
|
|
407
|
-
return item.effectiveValue
|
|
100
|
+
return item.effectiveValue === undefined ? item.value : item.effectiveValue;
|
|
408
101
|
}
|
|
409
102
|
function formatCoreValue(value) {
|
|
410
|
-
|
|
103
|
+
if (typeof value === "boolean") return booleanValue(value);
|
|
104
|
+
if (isRecord(value) && typeof value.provider === "string" && typeof value.model === "string") {
|
|
105
|
+
const effort = typeof value.thinkingLevel === "string" ? ` · ${value.thinkingLevel}` : "";
|
|
106
|
+
return `${value.provider}/${value.model}${effort}`;
|
|
107
|
+
}
|
|
108
|
+
return String(value ?? "");
|
|
411
109
|
}
|
|
412
110
|
function coreDisplaySuffix(item, inactive = false) {
|
|
413
111
|
const source = item.effectiveSource ?? "builtin";
|
|
@@ -494,78 +192,18 @@ function createScopedBooleanItem(options) {
|
|
|
494
192
|
item.displaySuffix = coreDisplaySuffix(item);
|
|
495
193
|
return item;
|
|
496
194
|
}
|
|
497
|
-
function createScopedDefaultModeItem(options) {
|
|
498
|
-
let item;
|
|
499
|
-
if (options.scope === "local") {
|
|
500
|
-
item = {
|
|
501
|
-
id: "freeflow.defaultMode",
|
|
502
|
-
label: "Default mode",
|
|
503
|
-
description:
|
|
504
|
-
"Personal default mode for this checkout. Choose inherit to use the repository default; use /freeflow settings repo to edit shared defaults.",
|
|
505
|
-
path: ["defaultMode"],
|
|
506
|
-
kind: "enum",
|
|
507
|
-
value: options.localDefaultMode ?? LOCAL_INHERIT,
|
|
508
|
-
values: [LOCAL_INHERIT, ...MODE_VALUES],
|
|
509
|
-
valueLabels: {
|
|
510
|
-
inherit: "Inherit repository",
|
|
511
|
-
...MODE_LABELS,
|
|
512
|
-
},
|
|
513
|
-
valueDescriptions: {
|
|
514
|
-
inherit: `Use ${options.repositoryDefaultMode} from ${options.repositoryDefaultSource}.`,
|
|
515
|
-
...MODE_DESCRIPTIONS,
|
|
516
|
-
},
|
|
517
|
-
format: (value) => String(value),
|
|
518
|
-
configScope: "local",
|
|
519
|
-
configValues: { inherit: undefined },
|
|
520
|
-
effectiveValue: options.effectiveValue,
|
|
521
|
-
effectiveSource: options.effectiveSource,
|
|
522
|
-
inheritedValue: options.repositoryDefaultMode,
|
|
523
|
-
inheritedSource: options.repositoryDefaultSource,
|
|
524
|
-
};
|
|
525
|
-
} else {
|
|
526
|
-
item = {
|
|
527
|
-
id: "freeflow.defaultMode",
|
|
528
|
-
label: "Default mode",
|
|
529
|
-
description:
|
|
530
|
-
"Shared repository default Freeflow mode used when Skills are enabled. This edits .freeflow/config.json.",
|
|
531
|
-
path: ["defaultMode"],
|
|
532
|
-
kind: "enum",
|
|
533
|
-
value: options.repositoryDefaultMode,
|
|
534
|
-
values: MODE_VALUES,
|
|
535
|
-
valueLabels: MODE_LABELS,
|
|
536
|
-
valueDescriptions: MODE_DESCRIPTIONS,
|
|
537
|
-
configScope: "repository",
|
|
538
|
-
effectiveValue: options.effectiveValue,
|
|
539
|
-
effectiveSource: options.effectiveSource,
|
|
540
|
-
localOverrideValue: options.localDefaultMode,
|
|
541
|
-
};
|
|
542
|
-
}
|
|
543
|
-
item.displaySuffix = coreDisplaySuffix(item);
|
|
544
|
-
return item;
|
|
545
|
-
}
|
|
546
|
-
function sessionModeDisplaySuffix(sessionMode, defaultMode, defaultSource, inactive) {
|
|
547
|
-
if (sessionMode !== "default") return undefined;
|
|
548
|
-
const inactiveSuffix = inactive ? " · inactive" : "";
|
|
549
|
-
return `(${defaultMode} · ${defaultSource}${inactiveSuffix})`;
|
|
550
|
-
}
|
|
551
|
-
function validModeOrUndefined(value) {
|
|
552
|
-
return typeof value === "string" && VALID_MODES.has(value) ? value : undefined;
|
|
553
|
-
}
|
|
554
195
|
function resolveSettingsCoreView(rawConfig, layers) {
|
|
555
196
|
const localConfig = layers?.local.valid && isRecord(layers.local.parsed) ? layers.local.parsed : {};
|
|
556
|
-
const repositorySkillsValue = getPath(rawConfig, ["skills"]);
|
|
557
|
-
const repositorySkills = isRecord(repositorySkillsValue) ? repositorySkillsValue : {};
|
|
558
197
|
const fallbackCore = {
|
|
559
198
|
enabled: getPath(rawConfig, ["enabled"]) !== false,
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
defaultMode: validModeOrUndefined(rawConfig.defaultMode) ?? "workflow",
|
|
199
|
+
contextVirtualization: getPath(rawConfig, ["contextVirtualization"]) === true,
|
|
200
|
+
conversationHistory: getPath(rawConfig, ["conversationHistory"]) === true,
|
|
563
201
|
};
|
|
564
202
|
const fallbackSources = {
|
|
565
203
|
enabled: typeof getPath(rawConfig, ["enabled"]) === "boolean" ? "repository" : "builtin",
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
204
|
+
contextVirtualization:
|
|
205
|
+
typeof getPath(rawConfig, ["contextVirtualization"]) === "boolean" ? "repository" : "builtin",
|
|
206
|
+
conversationHistory: typeof getPath(rawConfig, ["conversationHistory"]) === "boolean" ? "repository" : "builtin",
|
|
569
207
|
};
|
|
570
208
|
return {
|
|
571
209
|
localConfig,
|
|
@@ -573,9 +211,6 @@ function resolveSettingsCoreView(rawConfig, layers) {
|
|
|
573
211
|
sources: layers?.sources ?? fallbackSources,
|
|
574
212
|
};
|
|
575
213
|
}
|
|
576
|
-
function groupEnabled(items, id) {
|
|
577
|
-
return items.find((item) => item.id === id)?.value === true;
|
|
578
|
-
}
|
|
579
214
|
function createSessionBooleanItem(options) {
|
|
580
215
|
const override = options.sessionOverrides[options.key];
|
|
581
216
|
const item = {
|
|
@@ -613,7 +248,7 @@ function createSessionBooleanItem(options) {
|
|
|
613
248
|
item.displaySuffix = coreDisplaySuffix(item);
|
|
614
249
|
return item;
|
|
615
250
|
}
|
|
616
|
-
function sessionFreeflowItems(state,
|
|
251
|
+
function sessionFreeflowItems(state, cognitiveRoutingController) {
|
|
617
252
|
const sessionOverrides = state.sessionOverrides;
|
|
618
253
|
const configured = state.configuredCoreConfig;
|
|
619
254
|
const configuredSources = state.configuredSources;
|
|
@@ -629,67 +264,68 @@ function sessionFreeflowItems(state, modeState) {
|
|
|
629
264
|
effectiveSource: effectiveSources.enabled,
|
|
630
265
|
sessionOverrides,
|
|
631
266
|
});
|
|
632
|
-
const
|
|
633
|
-
id: "freeflow.
|
|
634
|
-
label: "
|
|
635
|
-
description: "Temporary
|
|
636
|
-
key: "
|
|
637
|
-
inheritedValue: configured.
|
|
638
|
-
inheritedSource: configuredSources.
|
|
639
|
-
effectiveValue: state.
|
|
640
|
-
effectiveSource: effectiveSources.
|
|
267
|
+
const contextVirtualizationItem = createSessionBooleanItem({
|
|
268
|
+
id: "freeflow.contextVirtualization",
|
|
269
|
+
label: "Context Virtualization",
|
|
270
|
+
description: "Temporary Context Virtualization override for this Pi session.",
|
|
271
|
+
key: "contextVirtualization",
|
|
272
|
+
inheritedValue: configured.contextVirtualization,
|
|
273
|
+
inheritedSource: configuredSources.contextVirtualization,
|
|
274
|
+
effectiveValue: state.contextVirtualization.enabled,
|
|
275
|
+
effectiveSource: effectiveSources.contextVirtualization,
|
|
641
276
|
sessionOverrides,
|
|
642
277
|
});
|
|
643
|
-
const
|
|
644
|
-
id: "freeflow.
|
|
645
|
-
label: "
|
|
646
|
-
description: "Temporary
|
|
647
|
-
key: "
|
|
648
|
-
inheritedValue: configured.
|
|
649
|
-
inheritedSource: configuredSources.
|
|
650
|
-
effectiveValue: state.
|
|
651
|
-
effectiveSource: effectiveSources.
|
|
278
|
+
const conversationHistoryItem = createSessionBooleanItem({
|
|
279
|
+
id: "freeflow.conversationHistory",
|
|
280
|
+
label: "Conversation History",
|
|
281
|
+
description: "Temporary Conversation History override for this Pi session.",
|
|
282
|
+
key: "conversationHistory",
|
|
283
|
+
inheritedValue: configured.conversationHistory,
|
|
284
|
+
inheritedSource: configuredSources.conversationHistory,
|
|
285
|
+
effectiveValue: state.conversationHistory.enabled,
|
|
286
|
+
effectiveSource: effectiveSources.conversationHistory,
|
|
652
287
|
sessionOverrides,
|
|
653
288
|
});
|
|
654
289
|
const freeflowInactive = !state.enabled;
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
const
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
290
|
+
contextVirtualizationItem.inactive = freeflowInactive;
|
|
291
|
+
conversationHistoryItem.inactive = freeflowInactive;
|
|
292
|
+
const cognitiveRoutingState = cognitiveRoutingController?.state();
|
|
293
|
+
const cognitiveRoutingProfile =
|
|
294
|
+
cognitiveRoutingState?.controlMode === "manual-standard"
|
|
295
|
+
? "standard"
|
|
296
|
+
: cognitiveRoutingState?.controlMode === "manual-reasoning"
|
|
297
|
+
? "reasoning"
|
|
298
|
+
: "auto";
|
|
299
|
+
const cognitiveRoutingItem = state.cognitiveRouting
|
|
300
|
+
? {
|
|
301
|
+
id: "freeflow.cognitiveRouting.profile",
|
|
302
|
+
label: "Cognitive Routing",
|
|
303
|
+
description: "Hold a profile manually or release the hold for automatic model control.",
|
|
304
|
+
kind: "enum",
|
|
305
|
+
value: cognitiveRoutingProfile,
|
|
306
|
+
values: ["auto", "standard", "reasoning"],
|
|
307
|
+
valueLabels: {
|
|
308
|
+
auto: "automatic",
|
|
309
|
+
standard: "manual · standard",
|
|
310
|
+
reasoning: "manual · reasoning",
|
|
311
|
+
},
|
|
312
|
+
valueDescriptions: {
|
|
313
|
+
auto: "Release the manual hold without forcing a model transition.",
|
|
314
|
+
standard: "Hold the standard profile until /freeflow profile auto.",
|
|
315
|
+
reasoning: "Hold the reasoning profile until /freeflow profile auto.",
|
|
316
|
+
},
|
|
317
|
+
inactive: freeflowInactive || !state.cognitiveRouting.effective || cognitiveRoutingController === undefined,
|
|
318
|
+
runtimeInactive: state.cognitiveRouting.blockingReason?.code === "runtime_disabled",
|
|
319
|
+
displaySuffix: cognitiveRoutingState?.effective ? cognitiveRoutingProfile : "unavailable",
|
|
320
|
+
}
|
|
321
|
+
: undefined;
|
|
684
322
|
return [
|
|
685
323
|
freeflowItem,
|
|
686
|
-
|
|
687
|
-
skillsItem,
|
|
688
|
-
sessionModeItem,
|
|
324
|
+
...(cognitiveRoutingItem ? [cognitiveRoutingItem] : []),
|
|
689
325
|
{
|
|
690
326
|
id: "freeflow.session.reset",
|
|
691
327
|
label: "Reset session overrides",
|
|
692
|
-
description: "Clear Freeflow,
|
|
328
|
+
description: "Clear Freeflow, Context Virtualization, and Conversation History overrides for this Pi session.",
|
|
693
329
|
kind: "enum",
|
|
694
330
|
value: "available",
|
|
695
331
|
values: ["reset"],
|
|
@@ -698,9 +334,308 @@ function sessionFreeflowItems(state, modeState) {
|
|
|
698
334
|
format: () => "available",
|
|
699
335
|
transient: true,
|
|
700
336
|
},
|
|
337
|
+
{
|
|
338
|
+
id: "freeflow.context",
|
|
339
|
+
label: "Freeflow Context",
|
|
340
|
+
description: "Choose which context projection and conversation-history operations are available to the model.",
|
|
341
|
+
kind: "group",
|
|
342
|
+
value: contextVirtualizationItem.effectiveValue === true || conversationHistoryItem.effectiveValue === true,
|
|
343
|
+
displaySuffix: `${[contextVirtualizationItem, conversationHistoryItem].filter((item) => item.effectiveValue === true).length}/2 enabled`,
|
|
344
|
+
children: [contextVirtualizationItem, conversationHistoryItem],
|
|
345
|
+
},
|
|
346
|
+
];
|
|
347
|
+
}
|
|
348
|
+
const COGNITIVE_ROUTING_THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
349
|
+
const COGNITIVE_ROUTING_EFFORT_DESCRIPTIONS = {
|
|
350
|
+
off: "No reasoning",
|
|
351
|
+
minimal: "Very brief reasoning",
|
|
352
|
+
low: "Light reasoning",
|
|
353
|
+
medium: "Moderate reasoning",
|
|
354
|
+
high: "Deep reasoning",
|
|
355
|
+
xhigh: "Extra-high reasoning",
|
|
356
|
+
max: "Maximum reasoning",
|
|
357
|
+
};
|
|
358
|
+
function cognitiveRoutingModelKey(provider, model) {
|
|
359
|
+
return `${provider}/${model}`;
|
|
360
|
+
}
|
|
361
|
+
function isCognitiveRoutingProfile(value) {
|
|
362
|
+
return (
|
|
363
|
+
isRecord(value) &&
|
|
364
|
+
typeof value.provider === "string" &&
|
|
365
|
+
typeof value.model === "string" &&
|
|
366
|
+
typeof value.thinkingLevel === "string" &&
|
|
367
|
+
COGNITIVE_ROUTING_THINKING_LEVELS.includes(value.thinkingLevel)
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
function cognitiveRoutingThinkingLevels(model, registry) {
|
|
371
|
+
if (typeof registry?.clampThinkingLevel !== "function") {
|
|
372
|
+
return model?.reasoning === true ? [...COGNITIVE_ROUTING_THINKING_LEVELS] : ["off"];
|
|
373
|
+
}
|
|
374
|
+
return COGNITIVE_ROUTING_THINKING_LEVELS.filter((level) => {
|
|
375
|
+
try {
|
|
376
|
+
return registry.clampThinkingLevel(model, level) === level;
|
|
377
|
+
} catch {
|
|
378
|
+
return false;
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
function cognitiveRoutingModelOptions(ctx) {
|
|
383
|
+
const registry = ctx?.modelRegistry;
|
|
384
|
+
if (typeof registry?.getAvailable !== "function") return [];
|
|
385
|
+
let models;
|
|
386
|
+
try {
|
|
387
|
+
models = registry.getAvailable();
|
|
388
|
+
} catch {
|
|
389
|
+
return [];
|
|
390
|
+
}
|
|
391
|
+
if (!Array.isArray(models)) return [];
|
|
392
|
+
return models
|
|
393
|
+
.filter((model) => typeof model?.provider === "string" && typeof model?.id === "string")
|
|
394
|
+
.map((model) => {
|
|
395
|
+
const thinkingLevels = cognitiveRoutingThinkingLevels(model, registry);
|
|
396
|
+
const provider = model.provider;
|
|
397
|
+
const modelId = model.id;
|
|
398
|
+
const key = cognitiveRoutingModelKey(provider, modelId);
|
|
399
|
+
const displayName = typeof model.name === "string" && model.name !== modelId ? model.name : undefined;
|
|
400
|
+
return {
|
|
401
|
+
key,
|
|
402
|
+
provider,
|
|
403
|
+
model: modelId,
|
|
404
|
+
label: key,
|
|
405
|
+
description: displayName ? `${displayName} · ${thinkingLevels.join(", ")}` : thinkingLevels.join(", "),
|
|
406
|
+
thinkingLevels,
|
|
407
|
+
};
|
|
408
|
+
})
|
|
409
|
+
.filter((model) => model.thinkingLevels.length > 0)
|
|
410
|
+
.sort((a, b) => a.label.localeCompare(b.label));
|
|
411
|
+
}
|
|
412
|
+
function cognitiveRoutingProfileDisplay(value) {
|
|
413
|
+
return isCognitiveRoutingProfile(value) ? formatCoreValue(value) : "not configured";
|
|
414
|
+
}
|
|
415
|
+
function cognitiveRoutingSettingsSource(source) {
|
|
416
|
+
if (source === "repository") return "repository";
|
|
417
|
+
if (source === "personal" || source === "local") return "local";
|
|
418
|
+
return "builtin";
|
|
419
|
+
}
|
|
420
|
+
function cognitiveRoutingProfileDisplaySuffix(source, scope) {
|
|
421
|
+
if (!source) return undefined;
|
|
422
|
+
if (source === "personal" && scope === "repository") return "(effective personal)";
|
|
423
|
+
return `(${source})`;
|
|
424
|
+
}
|
|
425
|
+
function cognitiveRoutingConfirmStep(summary) {
|
|
426
|
+
return {
|
|
427
|
+
title: "Confirm preset",
|
|
428
|
+
choices: [
|
|
429
|
+
{
|
|
430
|
+
key: "__save__",
|
|
431
|
+
value: "save",
|
|
432
|
+
label: `Save ${summary}`,
|
|
433
|
+
description: "Write the complete profile atomically.",
|
|
434
|
+
},
|
|
435
|
+
{ key: "__cancel__", value: "cancel", label: "Cancel", description: "Leave the previous preset unchanged." },
|
|
436
|
+
],
|
|
437
|
+
selectedKey: "__save__",
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
function createCognitiveRoutingProfileWizard(currentValue, models, allowInherit) {
|
|
441
|
+
const currentProfile = isCognitiveRoutingProfile(currentValue) ? currentValue : undefined;
|
|
442
|
+
const currentModelKey = currentProfile
|
|
443
|
+
? cognitiveRoutingModelKey(currentProfile.provider, currentProfile.model)
|
|
444
|
+
: undefined;
|
|
445
|
+
const firstChoices = [
|
|
446
|
+
...(allowInherit
|
|
447
|
+
? [
|
|
448
|
+
{
|
|
449
|
+
key: LOCAL_INHERIT,
|
|
450
|
+
value: LOCAL_INHERIT,
|
|
451
|
+
label: "Inherit repository preset",
|
|
452
|
+
description: "Remove the personal override without changing the repository preset.",
|
|
453
|
+
},
|
|
454
|
+
]
|
|
455
|
+
: []),
|
|
456
|
+
...models.map((model) => ({
|
|
457
|
+
key: model.key,
|
|
458
|
+
value: model,
|
|
459
|
+
label: model.label,
|
|
460
|
+
description: model.description,
|
|
461
|
+
})),
|
|
701
462
|
];
|
|
463
|
+
if (firstChoices.length === 0) {
|
|
464
|
+
firstChoices.push({
|
|
465
|
+
key: "__cancel__",
|
|
466
|
+
value: "cancel",
|
|
467
|
+
label: "No authenticated models available",
|
|
468
|
+
description: "Authenticate a provider before configuring this preset.",
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
return {
|
|
472
|
+
firstStep: () => ({
|
|
473
|
+
title: "Choose model",
|
|
474
|
+
choices: firstChoices,
|
|
475
|
+
selectedKey:
|
|
476
|
+
currentValue === LOCAL_INHERIT
|
|
477
|
+
? LOCAL_INHERIT
|
|
478
|
+
: firstChoices.some((choice) => choice.key === currentModelKey)
|
|
479
|
+
? currentModelKey
|
|
480
|
+
: firstChoices[0]?.key,
|
|
481
|
+
}),
|
|
482
|
+
nextStep: (selectedValues) => {
|
|
483
|
+
if (selectedValues.length === 1) {
|
|
484
|
+
const selected = selectedValues[0];
|
|
485
|
+
if (selected === LOCAL_INHERIT) {
|
|
486
|
+
return cognitiveRoutingConfirmStep("inherit repository preset");
|
|
487
|
+
}
|
|
488
|
+
const model = selected;
|
|
489
|
+
if (!model?.key || model.thinkingLevels.length === 0) return undefined;
|
|
490
|
+
const currentEffort =
|
|
491
|
+
currentProfile && currentModelKey === model.key ? currentProfile.thinkingLevel : undefined;
|
|
492
|
+
return {
|
|
493
|
+
title: `Choose effort · ${model.label}`,
|
|
494
|
+
choices: model.thinkingLevels.map((level) => ({
|
|
495
|
+
key: level,
|
|
496
|
+
value: level,
|
|
497
|
+
label: level,
|
|
498
|
+
description: COGNITIVE_ROUTING_EFFORT_DESCRIPTIONS[level],
|
|
499
|
+
})),
|
|
500
|
+
selectedKey:
|
|
501
|
+
currentEffort && model.thinkingLevels.includes(currentEffort) ? currentEffort : model.thinkingLevels[0],
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
if (selectedValues.length === 2) {
|
|
505
|
+
const model = selectedValues[0];
|
|
506
|
+
const effort = selectedValues[1];
|
|
507
|
+
return cognitiveRoutingConfirmStep(`${model.label} · ${effort}`);
|
|
508
|
+
}
|
|
509
|
+
return undefined;
|
|
510
|
+
},
|
|
511
|
+
valueFromSelections: (selectedValues) => {
|
|
512
|
+
if (selectedValues[0] === LOCAL_INHERIT) return undefined;
|
|
513
|
+
const model = selectedValues[0];
|
|
514
|
+
return {
|
|
515
|
+
provider: model.provider,
|
|
516
|
+
model: model.model,
|
|
517
|
+
thinkingLevel: selectedValues[1],
|
|
518
|
+
};
|
|
519
|
+
},
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
function cognitiveRoutingProfileItem(options) {
|
|
523
|
+
const path = ["cognitiveRouting", "profiles", options.name];
|
|
524
|
+
const repositoryProfile = getPath(options.rawConfig, path);
|
|
525
|
+
const localProfile = getPath(options.localConfig, path);
|
|
526
|
+
const effectiveProfile = options.capabilityState?.profiles?.[options.name] ?? repositoryProfile;
|
|
527
|
+
const localValue = isCognitiveRoutingProfile(localProfile) ? localProfile : LOCAL_INHERIT;
|
|
528
|
+
const value = options.scope === "local" ? localValue : repositoryProfile;
|
|
529
|
+
const source =
|
|
530
|
+
options.capabilityState?.profileSources?.[options.name] ??
|
|
531
|
+
(isCognitiveRoutingProfile(repositoryProfile) ? "repository" : undefined);
|
|
532
|
+
const models = cognitiveRoutingModelOptions(options.ctx);
|
|
533
|
+
const inactive = options.scope === "local" && models.length === 0 && localValue !== LOCAL_INHERIT;
|
|
534
|
+
return {
|
|
535
|
+
id: `freeflow.cognitiveRouting.${options.name}`,
|
|
536
|
+
label: `${options.name.charAt(0).toUpperCase()}${options.name.slice(1)} preset`,
|
|
537
|
+
description:
|
|
538
|
+
"Choose an authenticated available model and one effort supported by that model. Confirming writes the complete preset; cancel leaves it unchanged.",
|
|
539
|
+
path,
|
|
540
|
+
kind: "string",
|
|
541
|
+
value,
|
|
542
|
+
format: cognitiveRoutingProfileDisplay,
|
|
543
|
+
configScope: options.scope,
|
|
544
|
+
effectiveValue: effectiveProfile,
|
|
545
|
+
effectiveSource: cognitiveRoutingSettingsSource(source),
|
|
546
|
+
inheritedValue: repositoryProfile,
|
|
547
|
+
inheritedSource: "repository",
|
|
548
|
+
inactive,
|
|
549
|
+
displaySuffix: cognitiveRoutingProfileDisplaySuffix(source, options.scope),
|
|
550
|
+
wizard: () => createCognitiveRoutingProfileWizard(value, models, options.scope === "local"),
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
function cognitiveRoutingSessionStartItem(options) {
|
|
554
|
+
const path = ["cognitiveRouting", "sessionStart", options.setting];
|
|
555
|
+
const defaultValue = options.setting === "control" ? "automatic" : "reasoning";
|
|
556
|
+
const values = options.setting === "control" ? ["automatic", "manual"] : ["standard", "reasoning"];
|
|
557
|
+
const labels = Object.fromEntries(values.map((value) => [value, value]));
|
|
558
|
+
const descriptions =
|
|
559
|
+
options.setting === "control"
|
|
560
|
+
? {
|
|
561
|
+
automatic: "Start the new session in Reasoning; automatic delegation may use Standard during execution.",
|
|
562
|
+
manual: "Hold the selected profile for the new session until the user changes it.",
|
|
563
|
+
}
|
|
564
|
+
: {
|
|
565
|
+
standard: "Start a new manual session with the configured Standard preset.",
|
|
566
|
+
reasoning: "Start a new manual session with the configured Reasoning preset.",
|
|
567
|
+
};
|
|
568
|
+
const repositoryValue = getPath(options.rawConfig, path);
|
|
569
|
+
const localValue = getPath(options.localConfig, path);
|
|
570
|
+
const isValidValue = (value) => values.includes(value);
|
|
571
|
+
const repositorySetting = isValidValue(repositoryValue) ? repositoryValue : undefined;
|
|
572
|
+
const localSetting = isValidValue(localValue) ? localValue : undefined;
|
|
573
|
+
const inheritedValue = repositorySetting ?? defaultValue;
|
|
574
|
+
const inheritedSource = repositorySetting ? "repository" : "builtin";
|
|
575
|
+
const configuredSource =
|
|
576
|
+
options.capabilityState?.sessionStartSources?.[options.setting] ?? (repositorySetting ? "repository" : undefined);
|
|
577
|
+
const effectiveSessionStartControl =
|
|
578
|
+
options.capabilityState?.sessionStart?.control ??
|
|
579
|
+
getPath(options.localConfig, ["cognitiveRouting", "sessionStart", "control"]) ??
|
|
580
|
+
getPath(options.rawConfig, ["cognitiveRouting", "sessionStart", "control"]) ??
|
|
581
|
+
"automatic";
|
|
582
|
+
const effectiveValue =
|
|
583
|
+
options.setting === "profile" && effectiveSessionStartControl === "automatic"
|
|
584
|
+
? "reasoning"
|
|
585
|
+
: (options.capabilityState?.sessionStart?.[options.setting] ?? inheritedValue);
|
|
586
|
+
const effectiveSource = cognitiveRoutingSettingsSource(configuredSource);
|
|
587
|
+
const label = options.setting === "control" ? "Session start control" : "Manual session-start profile";
|
|
588
|
+
const description =
|
|
589
|
+
options.setting === "control"
|
|
590
|
+
? "Choose automatic or manual control for new sessions only; changing this does not alter the active session."
|
|
591
|
+
: "Choose the profile for manual session starts; automatic sessions always start in Reasoning.";
|
|
592
|
+
const item =
|
|
593
|
+
options.scope === "local"
|
|
594
|
+
? {
|
|
595
|
+
id: `freeflow.cognitiveRouting.sessionStart.${options.setting}`,
|
|
596
|
+
label,
|
|
597
|
+
description: `${description} Choose inherit to use the repository value; use /freeflow settings repo to edit shared defaults.`,
|
|
598
|
+
path,
|
|
599
|
+
kind: "enum",
|
|
600
|
+
value: localSetting ?? LOCAL_INHERIT,
|
|
601
|
+
values: [LOCAL_INHERIT, ...values],
|
|
602
|
+
valueLabels: { inherit: "Inherit repository", ...labels },
|
|
603
|
+
valueDescriptions: {
|
|
604
|
+
inherit: `Use ${inheritedValue} from ${inheritedSource}.`,
|
|
605
|
+
...descriptions,
|
|
606
|
+
},
|
|
607
|
+
format: (value) => String(value),
|
|
608
|
+
configScope: "local",
|
|
609
|
+
configValues: { inherit: undefined },
|
|
610
|
+
effectiveValue,
|
|
611
|
+
effectiveSource,
|
|
612
|
+
inheritedValue,
|
|
613
|
+
inheritedSource,
|
|
614
|
+
}
|
|
615
|
+
: {
|
|
616
|
+
id: `freeflow.cognitiveRouting.sessionStart.${options.setting}`,
|
|
617
|
+
label,
|
|
618
|
+
description: `${description} This edits shared .freeflow/config.json.`,
|
|
619
|
+
path,
|
|
620
|
+
kind: "enum",
|
|
621
|
+
value: inheritedValue,
|
|
622
|
+
values,
|
|
623
|
+
valueLabels: labels,
|
|
624
|
+
valueDescriptions: descriptions,
|
|
625
|
+
format: (value) => String(value),
|
|
626
|
+
defaultValue,
|
|
627
|
+
configScope: "repository",
|
|
628
|
+
effectiveValue,
|
|
629
|
+
effectiveSource,
|
|
630
|
+
localOverrideValue: localSetting,
|
|
631
|
+
};
|
|
632
|
+
item.displaySuffix = coreDisplaySuffix(item);
|
|
633
|
+
return item;
|
|
702
634
|
}
|
|
703
|
-
function
|
|
635
|
+
function isCognitiveRoutingRuntimeAvailable(pi) {
|
|
636
|
+
return isPiFlowHost(pi?.host) || supportsPiSessionModelStateApi(pi);
|
|
637
|
+
}
|
|
638
|
+
function freeflowItems(rawConfig, options = {}) {
|
|
704
639
|
const scope = options.scope ?? "repository";
|
|
705
640
|
const layers = options.layers;
|
|
706
641
|
const { localConfig, core, sources } = resolveSettingsCoreView(rawConfig, layers);
|
|
@@ -710,248 +645,128 @@ function freeflowItems(rawConfig, modeState, options = {}) {
|
|
|
710
645
|
localConfig,
|
|
711
646
|
id: "freeflow.enabled",
|
|
712
647
|
label: "Freeflow",
|
|
713
|
-
description: "Master switch for
|
|
648
|
+
description: "Master switch for Freeflow's core guidance, skills, and optional capabilities in this checkout.",
|
|
714
649
|
path: ["enabled"],
|
|
715
650
|
effectiveValue: core.enabled,
|
|
716
651
|
effectiveSource: sources.enabled,
|
|
717
652
|
defaultValue: DEFAULT_FREEFLOW_ENABLED,
|
|
718
653
|
});
|
|
719
|
-
const
|
|
654
|
+
const contextVirtualizationItem = createScopedBooleanItem({
|
|
720
655
|
scope,
|
|
721
656
|
rawConfig,
|
|
722
657
|
localConfig,
|
|
723
|
-
id: "freeflow.
|
|
724
|
-
label: "
|
|
725
|
-
description: "
|
|
726
|
-
path: ["
|
|
727
|
-
effectiveValue: core.
|
|
728
|
-
effectiveSource: sources.
|
|
729
|
-
defaultValue:
|
|
658
|
+
id: "freeflow.contextVirtualization",
|
|
659
|
+
label: "Context Virtualization",
|
|
660
|
+
description: "Let the model archive consumed tool results from future context while preserving session history.",
|
|
661
|
+
path: ["contextVirtualization"],
|
|
662
|
+
effectiveValue: core.contextVirtualization,
|
|
663
|
+
effectiveSource: sources.contextVirtualization,
|
|
664
|
+
defaultValue: DEFAULT_CONTEXT_VIRTUALIZATION_ENABLED,
|
|
730
665
|
});
|
|
731
|
-
const
|
|
666
|
+
const conversationHistoryItem = createScopedBooleanItem({
|
|
732
667
|
scope,
|
|
733
668
|
rawConfig,
|
|
734
669
|
localConfig,
|
|
735
|
-
id: "freeflow.
|
|
736
|
-
label: "
|
|
737
|
-
description: "
|
|
738
|
-
path: ["
|
|
739
|
-
effectiveValue: core.
|
|
740
|
-
effectiveSource: sources.
|
|
741
|
-
defaultValue:
|
|
742
|
-
});
|
|
743
|
-
const repositoryModeValue = validModeOrUndefined(rawConfig.defaultMode);
|
|
744
|
-
const repositoryDefaultMode = repositoryModeValue ?? "workflow";
|
|
745
|
-
const repositoryDefaultSource = repositoryModeValue ? "repository" : "builtin";
|
|
746
|
-
const localDefaultMode = validModeOrUndefined(localConfig.defaultMode);
|
|
747
|
-
const defaultModeItem = createScopedDefaultModeItem({
|
|
748
|
-
scope,
|
|
749
|
-
repositoryDefaultMode,
|
|
750
|
-
repositoryDefaultSource,
|
|
751
|
-
localDefaultMode,
|
|
752
|
-
effectiveValue: core.defaultMode,
|
|
753
|
-
effectiveSource: sources.defaultMode,
|
|
670
|
+
id: "freeflow.conversationHistory",
|
|
671
|
+
label: "Conversation History",
|
|
672
|
+
description: "Let the model search and retrieve hidden conversation history on the active branch.",
|
|
673
|
+
path: ["conversationHistory"],
|
|
674
|
+
effectiveValue: core.conversationHistory,
|
|
675
|
+
effectiveSource: sources.conversationHistory,
|
|
676
|
+
defaultValue: DEFAULT_CONVERSATION_HISTORY_ENABLED,
|
|
754
677
|
});
|
|
755
678
|
const freeflowInactive = !core.enabled;
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
679
|
+
contextVirtualizationItem.inactive = freeflowInactive;
|
|
680
|
+
conversationHistoryItem.inactive = freeflowInactive;
|
|
681
|
+
const cognitiveRoutingState = options.cognitiveRouting;
|
|
682
|
+
const cognitiveRoutingRuntimeDisabled = options.runtimeAvailable !== true;
|
|
683
|
+
const cognitiveRoutingGroup = (() => {
|
|
684
|
+
const cognitiveRoutingEnabledItem = createScopedBooleanItem({
|
|
685
|
+
scope,
|
|
686
|
+
rawConfig,
|
|
687
|
+
localConfig,
|
|
688
|
+
id: "freeflow.cognitiveRouting.enabled",
|
|
689
|
+
label: "Enabled",
|
|
690
|
+
description: cognitiveRoutingRuntimeDisabled
|
|
691
|
+
? "Requires a host model-state control API; configuration is read-only on this host."
|
|
692
|
+
: "Allow Cognitive Routing to own the session model lease and switch the configured profiles.",
|
|
693
|
+
path: ["cognitiveRouting", "enabled"],
|
|
694
|
+
effectiveValue: cognitiveRoutingState?.enabled ?? false,
|
|
695
|
+
effectiveSource: cognitiveRoutingSettingsSource(cognitiveRoutingState?.enabledSource),
|
|
696
|
+
defaultValue: false,
|
|
697
|
+
});
|
|
698
|
+
cognitiveRoutingEnabledItem.inactive = freeflowInactive || cognitiveRoutingRuntimeDisabled;
|
|
699
|
+
cognitiveRoutingEnabledItem.runtimeInactive = cognitiveRoutingRuntimeDisabled;
|
|
700
|
+
const cognitiveRoutingProfiles = ["standard", "reasoning"].map((name) =>
|
|
701
|
+
cognitiveRoutingProfileItem({
|
|
702
|
+
name: name,
|
|
703
|
+
scope,
|
|
704
|
+
rawConfig,
|
|
705
|
+
localConfig,
|
|
706
|
+
capabilityState: cognitiveRoutingState,
|
|
707
|
+
ctx: options.ctx,
|
|
708
|
+
}),
|
|
709
|
+
);
|
|
710
|
+
const cognitiveRoutingSessionStart = ["control", "profile"].map((setting) =>
|
|
711
|
+
cognitiveRoutingSessionStartItem({
|
|
712
|
+
setting,
|
|
713
|
+
scope,
|
|
714
|
+
rawConfig,
|
|
715
|
+
localConfig,
|
|
716
|
+
capabilityState: cognitiveRoutingState,
|
|
717
|
+
}),
|
|
718
|
+
);
|
|
719
|
+
for (const item of [...cognitiveRoutingProfiles, ...cognitiveRoutingSessionStart]) {
|
|
720
|
+
item.inactive ||= freeflowInactive || cognitiveRoutingRuntimeDisabled;
|
|
721
|
+
item.runtimeInactive = cognitiveRoutingRuntimeDisabled;
|
|
722
|
+
}
|
|
723
|
+
const cognitiveRoutingStatus = cognitiveRoutingRuntimeDisabled
|
|
724
|
+
? "unavailable · host unsupported"
|
|
725
|
+
: cognitiveRoutingState
|
|
726
|
+
? cognitiveRoutingState.effective
|
|
727
|
+
? "active"
|
|
728
|
+
: cognitiveRoutingState.blockingReason?.code === "profile_missing"
|
|
729
|
+
? "not configured"
|
|
730
|
+
: (cognitiveRoutingState.blockingReason?.code ?? "inactive")
|
|
731
|
+
: "unavailable";
|
|
732
|
+
return {
|
|
733
|
+
id: "freeflow.cognitiveRouting",
|
|
734
|
+
label: "Cognitive Routing",
|
|
735
|
+
description: cognitiveRoutingRuntimeDisabled
|
|
736
|
+
? "Cognitive Routing configuration is visible for inspection but requires a host model-state control API."
|
|
737
|
+
: "Configure the Standard and Reasoning profiles and choose whether Freeflow may manage model state for this repository.",
|
|
738
|
+
kind: "group",
|
|
739
|
+
value: cognitiveRoutingRuntimeDisabled ? false : (cognitiveRoutingState?.enabled ?? false),
|
|
740
|
+
inactive: freeflowInactive,
|
|
741
|
+
displaySuffix: cognitiveRoutingStatus,
|
|
742
|
+
children: [cognitiveRoutingEnabledItem, ...cognitiveRoutingProfiles, ...cognitiveRoutingSessionStart],
|
|
743
|
+
};
|
|
744
|
+
})();
|
|
788
745
|
return [
|
|
789
746
|
freeflowItem,
|
|
790
|
-
|
|
791
|
-
skillsItem,
|
|
792
|
-
sessionModeItem,
|
|
793
|
-
defaultModeItem,
|
|
747
|
+
...(cognitiveRoutingGroup ? [cognitiveRoutingGroup] : []),
|
|
794
748
|
{
|
|
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.",
|
|
749
|
+
id: "freeflow.context",
|
|
750
|
+
label: "Freeflow Context",
|
|
751
|
+
description: "Choose which context projection and conversation-history operations are available to the model.",
|
|
799
752
|
kind: "group",
|
|
800
|
-
value:
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
children: routerItems,
|
|
753
|
+
value: contextVirtualizationItem.effectiveValue === true || conversationHistoryItem.effectiveValue === true,
|
|
754
|
+
displaySuffix: `${[contextVirtualizationItem, conversationHistoryItem].filter((item) => item.effectiveValue === true).length}/2 enabled`,
|
|
755
|
+
children: [contextVirtualizationItem, conversationHistoryItem],
|
|
804
756
|
},
|
|
805
757
|
];
|
|
806
758
|
}
|
|
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
759
|
function pruneKnownDefaults(config) {
|
|
856
760
|
const defaultPaths = [
|
|
857
761
|
{ path: ["enabled"], value: DEFAULT_FREEFLOW_ENABLED },
|
|
858
|
-
{
|
|
859
|
-
|
|
860
|
-
value: DEFAULT_INTERACTION_CONTRACT_ENABLED,
|
|
861
|
-
},
|
|
862
|
-
{ path: ["skills", "enabled"], value: DEFAULT_SKILLS_ENABLED },
|
|
863
|
-
{ path: ["outputRouter", "enabled"], value: DEFAULT_OUTPUT_ROUTER_ENABLED },
|
|
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
|
-
},
|
|
762
|
+
{ path: ["contextVirtualization"], value: DEFAULT_CONTEXT_VIRTUALIZATION_ENABLED },
|
|
763
|
+
{ path: ["conversationHistory"], value: DEFAULT_CONVERSATION_HISTORY_ENABLED },
|
|
938
764
|
];
|
|
939
765
|
for (const item of defaultPaths) {
|
|
940
766
|
if (valuesEqual(getPath(config, item.path), item.value)) {
|
|
941
767
|
deletePath(config, item.path);
|
|
942
768
|
}
|
|
943
769
|
}
|
|
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
770
|
}
|
|
956
771
|
async function ensureLocalConfigIgnored(cwd) {
|
|
957
772
|
let gitPath;
|
|
@@ -1014,14 +829,18 @@ async function updateConfig(cwd, item, value, scope = item.configScope ?? "repos
|
|
|
1014
829
|
const current = await readFreeflowConfig(cwd);
|
|
1015
830
|
const next = cloneJson(current);
|
|
1016
831
|
setConfigValue(next, item, value);
|
|
1017
|
-
migrateLegacyRouterConfig(next);
|
|
1018
832
|
pruneKnownDefaults(next);
|
|
1019
833
|
await mkdir(join(cwd, ".freeflow"), { recursive: true });
|
|
1020
834
|
await writeFile(join(cwd, ".freeflow/config.json"), `${JSON.stringify(next, null, 2)}\n`, "utf8");
|
|
1021
835
|
}
|
|
836
|
+
function isCognitiveRoutingProfileItem(item) {
|
|
837
|
+
return item.id === "freeflow.cognitiveRouting.standard" || item.id === "freeflow.cognitiveRouting.reasoning";
|
|
838
|
+
}
|
|
1022
839
|
function valueForDisplay(item) {
|
|
1023
840
|
let value;
|
|
1024
|
-
if (item
|
|
841
|
+
if (isCognitiveRoutingProfileItem(item) && item.format) {
|
|
842
|
+
value = item.format(effectiveItemValue(item));
|
|
843
|
+
} else if (item.configScope === "local" || item.configScope === "session") {
|
|
1025
844
|
value = formatCoreValue(effectiveItemValue(item));
|
|
1026
845
|
} else if (item.kind === "boolean") {
|
|
1027
846
|
value = booleanValue(item.value);
|
|
@@ -1053,51 +872,43 @@ function findSettingsItem(items, id) {
|
|
|
1053
872
|
function refreshSettingsDerivedState(items) {
|
|
1054
873
|
const freeflowItem = findSettingsItem(items, "freeflow.enabled");
|
|
1055
874
|
const freeflowInactive = freeflowItem ? effectiveItemValue(freeflowItem) !== true : false;
|
|
1056
|
-
const
|
|
1057
|
-
const
|
|
1058
|
-
const
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
if (
|
|
1063
|
-
const
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
const defaultSource = defaultModeItem?.effectiveSource ?? sessionModeItem.inheritedSource ?? "builtin";
|
|
1067
|
-
sessionModeItem.inactive = freeflowInactive || !skillsEnabled;
|
|
1068
|
-
sessionModeItem.displaySuffix = sessionModeDisplaySuffix(
|
|
1069
|
-
String(sessionModeItem.value),
|
|
1070
|
-
defaultMode,
|
|
1071
|
-
defaultSource,
|
|
1072
|
-
freeflowInactive || !skillsEnabled,
|
|
875
|
+
const cognitiveRoutingGroup = findSettingsItem(items, "freeflow.cognitiveRouting");
|
|
876
|
+
const cognitiveRoutingEnabledItem = findSettingsItem(items, "freeflow.cognitiveRouting.enabled");
|
|
877
|
+
const cognitiveRoutingProfiles = [
|
|
878
|
+
findSettingsItem(items, "freeflow.cognitiveRouting.standard"),
|
|
879
|
+
findSettingsItem(items, "freeflow.cognitiveRouting.reasoning"),
|
|
880
|
+
];
|
|
881
|
+
if (cognitiveRoutingGroup && cognitiveRoutingEnabledItem) {
|
|
882
|
+
const enabled = effectiveItemValue(cognitiveRoutingEnabledItem) === true;
|
|
883
|
+
const profilesConfigured = cognitiveRoutingProfiles.every((item) =>
|
|
884
|
+
item ? isCognitiveRoutingProfile(effectiveItemValue(item)) : false,
|
|
1073
885
|
);
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
886
|
+
cognitiveRoutingGroup.value = enabled;
|
|
887
|
+
cognitiveRoutingGroup.displaySuffix = enabled
|
|
888
|
+
? profilesConfigured
|
|
889
|
+
? cognitiveRoutingGroup.displaySuffix === "active"
|
|
890
|
+
? "active"
|
|
891
|
+
: "configured"
|
|
892
|
+
: "not configured"
|
|
893
|
+
: "disabled";
|
|
894
|
+
cognitiveRoutingGroup.inactive = freeflowInactive;
|
|
1078
895
|
}
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
896
|
+
const contextGroup = findSettingsItem(items, "freeflow.context");
|
|
897
|
+
if (contextGroup?.children?.length) {
|
|
898
|
+
const enabledCount = contextGroup.children.filter((item) => effectiveItemValue(item) === true).length;
|
|
899
|
+
contextGroup.value = enabledCount > 0;
|
|
900
|
+
contextGroup.displaySuffix = `${enabledCount}/${contextGroup.children.length} enabled`;
|
|
1082
901
|
}
|
|
1083
902
|
walkSettingsItems(items, (candidate) => {
|
|
1084
903
|
if (candidate.id === "freeflow.session.reset") {
|
|
1085
904
|
candidate.inactive = false;
|
|
1086
905
|
} else if (candidate.configScope) {
|
|
1087
|
-
const inactive =
|
|
1088
|
-
|
|
906
|
+
const inactive =
|
|
907
|
+
candidate.id === "freeflow.enabled" ? false : candidate.runtimeInactive === true || freeflowInactive;
|
|
1089
908
|
candidate.inactive = inactive;
|
|
1090
|
-
candidate.displaySuffix = coreDisplaySuffix(candidate,
|
|
1091
|
-
} 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;
|
|
909
|
+
candidate.displaySuffix = coreDisplaySuffix(candidate, inactive);
|
|
1099
910
|
} else {
|
|
1100
|
-
candidate.inactive = freeflowInactive;
|
|
911
|
+
candidate.inactive = candidate.runtimeInactive === true || freeflowInactive;
|
|
1101
912
|
}
|
|
1102
913
|
});
|
|
1103
914
|
}
|
|
@@ -1137,6 +948,7 @@ function settingsEntries(items, rootItems, onChange) {
|
|
|
1137
948
|
currentChoiceKey: () => String(item.value),
|
|
1138
949
|
choices: settingsChoices(item),
|
|
1139
950
|
children: item.children ? () => settingsEntries(item.children, rootItems, onChange) : undefined,
|
|
951
|
+
wizard: item.wizard ? () => item.wizard() : undefined,
|
|
1140
952
|
edit:
|
|
1141
953
|
!item.children && !["boolean", "enum", "group"].includes(item.kind)
|
|
1142
954
|
? {
|
|
@@ -1191,42 +1003,40 @@ async function openSettings(options) {
|
|
|
1191
1003
|
await component?.waitForWrites();
|
|
1192
1004
|
return component?.sessionResult() ?? { changed: false, configChanged: false, failed: false };
|
|
1193
1005
|
}
|
|
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) {
|
|
1006
|
+
function freeflowStatusText(state, cognitiveRoutingController) {
|
|
1213
1007
|
if (!state.configured) {
|
|
1214
1008
|
return state.configExists
|
|
1215
1009
|
? `Freeflow: inactive (invalid config: ${state.parseError ?? "unknown parse error"}); run /setup-freeflow or fix .freeflow/config.json`
|
|
1216
1010
|
: "Freeflow: inactive (repo not set up); run /setup-freeflow";
|
|
1217
1011
|
}
|
|
1218
1012
|
const sessionSuffix = (source) => (source === "session" ? " (session override)" : "");
|
|
1013
|
+
const routingState = cognitiveRoutingController?.state();
|
|
1014
|
+
const cognitiveRouting = state.cognitiveRouting;
|
|
1015
|
+
const cognitiveRoutingStatus = cognitiveRouting
|
|
1016
|
+
? cognitiveRouting.blockingReason?.code === "runtime_disabled"
|
|
1017
|
+
? "unavailable (host unsupported)"
|
|
1018
|
+
: cognitiveRouting.effective
|
|
1019
|
+
? routingState?.effective
|
|
1020
|
+
? `active (${routingState.activeProfile ?? "unknown"}, ${routingState.controlMode})`
|
|
1021
|
+
: "effective (inactive)"
|
|
1022
|
+
: cognitiveRouting.enabled
|
|
1023
|
+
? `blocked (${cognitiveRouting.blockingReason.code})`
|
|
1024
|
+
: "disabled"
|
|
1025
|
+
: undefined;
|
|
1026
|
+
const contextEnabled = state.contextVirtualization?.effective || state.conversationHistory?.effective;
|
|
1219
1027
|
return [
|
|
1220
1028
|
`Freeflow: ${state.enabled ? "enabled" : "disabled"}${sessionSuffix(state.configSources.enabled)}`,
|
|
1221
|
-
`
|
|
1222
|
-
`
|
|
1223
|
-
`output router: ${state.outputRouter.enabled ? "enabled" : "disabled"}`,
|
|
1029
|
+
`context: ${contextEnabled ? "enabled" : "disabled"} (virtualization ${state.contextVirtualization?.effective ? "enabled" : "disabled"}, history ${state.conversationHistory?.effective ? "enabled" : "disabled"})`,
|
|
1030
|
+
...(cognitiveRoutingStatus ? [`cognitive routing: ${cognitiveRoutingStatus}`] : []),
|
|
1224
1031
|
].join("; ");
|
|
1225
1032
|
}
|
|
1226
1033
|
const NON_TUI_SETTINGS_GUIDANCE =
|
|
1227
|
-
"Freeflow settings require Pi TUI mode. Use /freeflow status to inspect current state; supported non-TUI changes are /freeflow enable
|
|
1228
|
-
|
|
1229
|
-
|
|
1034
|
+
"Freeflow settings require Pi TUI mode. Use /freeflow status to inspect current state; supported non-TUI changes are /freeflow enable and /freeflow disable.";
|
|
1035
|
+
function ensureSettingsIdle(ctx) {
|
|
1036
|
+
if (typeof ctx?.isIdle !== "function" || ctx.isIdle()) return true;
|
|
1037
|
+
ctx.ui?.notify?.("Freeflow settings and profile changes are available only while Pi is idle.", "warning");
|
|
1038
|
+
return false;
|
|
1039
|
+
}
|
|
1230
1040
|
function nonTuiGuidance(ctx, message) {
|
|
1231
1041
|
if (ctx?.hasUI === false) throw new Error(message);
|
|
1232
1042
|
ctx?.ui?.notify?.(message, "warning");
|
|
@@ -1238,9 +1048,10 @@ async function finalizeSettingsSession(
|
|
|
1238
1048
|
afterChange,
|
|
1239
1049
|
savedMessage,
|
|
1240
1050
|
reloadWarning = "Run /reload for Freeflow changes to fully apply.",
|
|
1051
|
+
afterChangeOptions = {},
|
|
1241
1052
|
) {
|
|
1242
1053
|
if (!session.configChanged) return false;
|
|
1243
|
-
await afterChange(true);
|
|
1054
|
+
await afterChange(true, afterChangeOptions);
|
|
1244
1055
|
if (session.failed) return false;
|
|
1245
1056
|
ctx.ui.notify(savedMessage, "info");
|
|
1246
1057
|
if (typeof ctx.reload !== "function") {
|
|
@@ -1266,55 +1077,29 @@ async function finalizeSessionSettings(session, ctx, afterChange) {
|
|
|
1266
1077
|
await ctx.reload();
|
|
1267
1078
|
return true;
|
|
1268
1079
|
}
|
|
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) {
|
|
1080
|
+
export async function handleFreeflowCommand(args, ctx, afterChange, pi, cognitiveRoutingController) {
|
|
1292
1081
|
const input = (args ?? "settings").trim().toLowerCase() || "settings";
|
|
1293
1082
|
const [action, ...rest] = input.split(/\s+/);
|
|
1294
1083
|
const actionValue = rest.join(" ");
|
|
1295
1084
|
const nonTui = ctx?.mode && ctx.mode !== "tui";
|
|
1296
|
-
if (nonTui && action === "mode" && !actionValue) {
|
|
1297
|
-
return nonTuiGuidance(ctx, NON_TUI_MODE_GUIDANCE);
|
|
1298
|
-
}
|
|
1299
1085
|
const settingsSelector =
|
|
1300
1086
|
action === "settings" &&
|
|
1301
1087
|
(!actionValue || ["session", "local", "personal", "repo", "repository", "shared"].includes(actionValue));
|
|
1302
1088
|
if (nonTui && settingsSelector) {
|
|
1303
1089
|
return nonTuiGuidance(ctx, NON_TUI_SETTINGS_GUIDANCE);
|
|
1304
1090
|
}
|
|
1305
|
-
const [layers, state
|
|
1091
|
+
const [layers, state] = await Promise.all([
|
|
1306
1092
|
readFreeflowConfigLayers(ctx.cwd),
|
|
1307
|
-
readCapabilityState(ctx.cwd),
|
|
1308
|
-
readModeState(ctx.cwd),
|
|
1093
|
+
readCapabilityState(ctx.cwd, ctx, pi?.host),
|
|
1309
1094
|
]);
|
|
1310
1095
|
const configState = layers.repository;
|
|
1311
1096
|
const raw = configState.valid ? configState.parsed : {};
|
|
1312
1097
|
if (action === "status") {
|
|
1313
|
-
ctx.ui.notify(freeflowStatusText(state), "info");
|
|
1098
|
+
ctx.ui.notify(freeflowStatusText(state, cognitiveRoutingController), "info");
|
|
1314
1099
|
return { changed: false, reloaded: false };
|
|
1315
1100
|
}
|
|
1316
1101
|
if (!configState.valid) {
|
|
1317
|
-
ctx.ui.notify(freeflowStatusText(state), "warning");
|
|
1102
|
+
ctx.ui.notify(freeflowStatusText(state, cognitiveRoutingController), "warning");
|
|
1318
1103
|
return { changed: false, reloaded: false, error: "not_configured" };
|
|
1319
1104
|
}
|
|
1320
1105
|
if (layers.local.exists && !layers.local.valid) {
|
|
@@ -1324,40 +1109,15 @@ export async function handleFreeflowCommand(args, ctx, afterChange, pi) {
|
|
|
1324
1109
|
);
|
|
1325
1110
|
return { changed: false, reloaded: false, error: "invalid_local_config" };
|
|
1326
1111
|
}
|
|
1327
|
-
if (action === "mode") {
|
|
1328
|
-
if (actionValue) {
|
|
1329
|
-
const result = await handleModeCommand(actionValue, ctx, pi);
|
|
1330
|
-
return { changed: result.changed, reloaded: false, error: result.error };
|
|
1331
|
-
}
|
|
1332
|
-
if (!state.enabled || !state.skills.effective) {
|
|
1333
|
-
const result = await handleModeCommand(undefined, ctx, pi);
|
|
1334
|
-
return { changed: false, reloaded: false, error: result.error };
|
|
1335
|
-
}
|
|
1336
|
-
if (typeof ctx?.ui?.custom !== "function") {
|
|
1337
|
-
const result = await handleModeCommand(undefined, ctx, pi);
|
|
1338
|
-
return { changed: false, reloaded: false, error: result.error };
|
|
1339
|
-
}
|
|
1340
|
-
const item = freeflowItems(raw, modeState, {
|
|
1341
|
-
scope: "local",
|
|
1342
|
-
layers,
|
|
1343
|
-
}).find((candidate) => candidate.id === "freeflow.sessionMode");
|
|
1344
|
-
const session = await openSettings({
|
|
1345
|
-
title: "Freeflow Mode",
|
|
1346
|
-
items: [item],
|
|
1347
|
-
initialChoice: item,
|
|
1348
|
-
ctx,
|
|
1349
|
-
onChange: async (_item, value) => {
|
|
1350
|
-
const result = await handleModeCommand(String(value), ctx, pi);
|
|
1351
|
-
return { changed: result.changed, reloadRequired: false };
|
|
1352
|
-
},
|
|
1353
|
-
});
|
|
1354
|
-
return { changed: session.changed, reloaded: false, error: session.failed ? "write_failed" : undefined };
|
|
1355
|
-
}
|
|
1356
1112
|
if (["enable", "on", "true", "disable", "off", "false"].includes(action)) {
|
|
1113
|
+
if (!ensureSettingsIdle(ctx)) return { changed: false, reloaded: false, error: "busy" };
|
|
1357
1114
|
const enabled = ["enable", "on", "true"].includes(action);
|
|
1358
|
-
const item = freeflowItems(raw,
|
|
1115
|
+
const item = freeflowItems(raw, {
|
|
1359
1116
|
scope: "repository",
|
|
1360
1117
|
layers,
|
|
1118
|
+
cognitiveRouting: state.cognitiveRouting,
|
|
1119
|
+
ctx,
|
|
1120
|
+
runtimeAvailable: isCognitiveRoutingRuntimeAvailable(pi),
|
|
1361
1121
|
}).find((candidate) => candidate.id === "freeflow.enabled");
|
|
1362
1122
|
await updateConfig(ctx.cwd, item, enabled, "repository");
|
|
1363
1123
|
await afterChange(true);
|
|
@@ -1371,7 +1131,7 @@ export async function handleFreeflowCommand(args, ctx, afterChange, pi) {
|
|
|
1371
1131
|
}
|
|
1372
1132
|
if (action && action !== "settings") {
|
|
1373
1133
|
ctx.ui.notify(
|
|
1374
|
-
"Usage: /freeflow, /freeflow settings [local|repo], /freeflow status, /freeflow
|
|
1134
|
+
"Usage: /freeflow, /freeflow settings [local|repo], /freeflow status, /freeflow enable, or /freeflow disable",
|
|
1375
1135
|
"warning",
|
|
1376
1136
|
);
|
|
1377
1137
|
return { changed: false, reloaded: false, error: "invalid_action" };
|
|
@@ -1388,10 +1148,18 @@ export async function handleFreeflowCommand(args, ctx, afterChange, pi) {
|
|
|
1388
1148
|
);
|
|
1389
1149
|
return { changed: false, reloaded: false, error: "invalid_scope" };
|
|
1390
1150
|
}
|
|
1151
|
+
if (!ensureSettingsIdle(ctx)) return { changed: false, reloaded: false, error: "busy" };
|
|
1152
|
+
let reconcileCognitiveRouting = settingsScope === "session";
|
|
1391
1153
|
const items =
|
|
1392
1154
|
settingsScope === "session"
|
|
1393
|
-
? sessionFreeflowItems(state,
|
|
1394
|
-
: freeflowItems(raw,
|
|
1155
|
+
? sessionFreeflowItems(state, cognitiveRoutingController)
|
|
1156
|
+
: freeflowItems(raw, {
|
|
1157
|
+
scope: settingsScope,
|
|
1158
|
+
layers,
|
|
1159
|
+
cognitiveRouting: state.cognitiveRouting,
|
|
1160
|
+
ctx,
|
|
1161
|
+
runtimeAvailable: isCognitiveRoutingRuntimeAvailable(pi),
|
|
1162
|
+
});
|
|
1395
1163
|
const session = await openSettings({
|
|
1396
1164
|
title:
|
|
1397
1165
|
settingsScope === "session"
|
|
@@ -1402,9 +1170,29 @@ export async function handleFreeflowCommand(args, ctx, afterChange, pi) {
|
|
|
1402
1170
|
items,
|
|
1403
1171
|
ctx,
|
|
1404
1172
|
onChange: async (item, value) => {
|
|
1405
|
-
if (
|
|
1406
|
-
|
|
1407
|
-
|
|
1173
|
+
if (!ensureSettingsIdle(ctx)) return { changed: false, reloadRequired: false };
|
|
1174
|
+
if (!isCognitiveRoutingRuntimeAvailable(pi) && item.id.startsWith("freeflow.cognitiveRouting.")) {
|
|
1175
|
+
ctx.ui.notify("Cognitive Routing is unavailable because this host lacks model-state controls.", "warning");
|
|
1176
|
+
return { changed: false, reloadRequired: false };
|
|
1177
|
+
}
|
|
1178
|
+
if (item.id === "freeflow.cognitiveRouting.profile") {
|
|
1179
|
+
if (!cognitiveRoutingController) {
|
|
1180
|
+
ctx.ui.notify("Cognitive Routing is unavailable for this session.", "warning");
|
|
1181
|
+
return { changed: false, reloadRequired: false };
|
|
1182
|
+
}
|
|
1183
|
+
const result =
|
|
1184
|
+
value === "auto"
|
|
1185
|
+
? await cognitiveRoutingController.setAutomaticControl("profile-settings")
|
|
1186
|
+
: await cognitiveRoutingController.setManualProfile(value, "profile-settings");
|
|
1187
|
+
if ((result.status !== "automatic" && result.status !== "active") || result.reason) {
|
|
1188
|
+
ctx.ui.notify(
|
|
1189
|
+
`Cognitive Routing settings could not be applied: ${result.reason ?? result.status}.`,
|
|
1190
|
+
"warning",
|
|
1191
|
+
);
|
|
1192
|
+
return { changed: false, reloadRequired: false };
|
|
1193
|
+
}
|
|
1194
|
+
await afterChange(false);
|
|
1195
|
+
return { changed: true, reloadRequired: false };
|
|
1408
1196
|
}
|
|
1409
1197
|
if (item.id === "freeflow.session.reset") {
|
|
1410
1198
|
const result = await resetSessionOverrides(ctx, pi);
|
|
@@ -1413,8 +1201,8 @@ export async function handleFreeflowCommand(args, ctx, afterChange, pi) {
|
|
|
1413
1201
|
if (item.configScope === "session") {
|
|
1414
1202
|
const keyById = {
|
|
1415
1203
|
"freeflow.enabled": "enabled",
|
|
1416
|
-
"freeflow.
|
|
1417
|
-
"freeflow.
|
|
1204
|
+
"freeflow.contextVirtualization": "contextVirtualization",
|
|
1205
|
+
"freeflow.conversationHistory": "conversationHistory",
|
|
1418
1206
|
};
|
|
1419
1207
|
const key = keyById[item.id];
|
|
1420
1208
|
const override = value === LOCAL_INHERIT ? null : value === "true";
|
|
@@ -1422,6 +1210,9 @@ export async function handleFreeflowCommand(args, ctx, afterChange, pi) {
|
|
|
1422
1210
|
return { changed: result.changed, reloadRequired: result.reloadRequired === true };
|
|
1423
1211
|
}
|
|
1424
1212
|
await updateConfig(ctx.cwd, item, value, item.configScope ?? "repository");
|
|
1213
|
+
if (!item.id.startsWith("freeflow.cognitiveRouting.sessionStart.")) {
|
|
1214
|
+
reconcileCognitiveRouting = true;
|
|
1215
|
+
}
|
|
1425
1216
|
return { changed: true, reloadRequired: true };
|
|
1426
1217
|
},
|
|
1427
1218
|
});
|
|
@@ -1435,53 +1226,8 @@ export async function handleFreeflowCommand(args, ctx, afterChange, pi) {
|
|
|
1435
1226
|
ctx,
|
|
1436
1227
|
afterChange,
|
|
1437
1228
|
`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.",
|
|
1229
|
+
"Run /reload for Freeflow changes to fully apply.",
|
|
1230
|
+
{ reconcileCognitiveRouting },
|
|
1485
1231
|
);
|
|
1486
1232
|
return { changed: session.changed, reloaded, error: session.failed ? "write_failed" : undefined };
|
|
1487
1233
|
}
|