@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
|
@@ -4,53 +4,33 @@ import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
|
4
4
|
import { promisify } from "node:util";
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
|
-
DEFAULT_OBSERVED_ROUTING_CONFIG,
|
|
8
|
-
DEFAULT_OUTPUT_ROUTER_ENABLED,
|
|
9
|
-
DEFAULT_POST_TOOL_ROUTING,
|
|
10
|
-
DEFAULT_ROUTER_THRESHOLDS,
|
|
11
|
-
DEFAULT_SCRIPT_TRANSFORM_CONFIG,
|
|
12
|
-
DEFAULT_STORAGE_POLICY,
|
|
13
|
-
DEFAULT_VAULT_RETENTION,
|
|
14
|
-
DEFAULT_VAULT_ROOT,
|
|
15
|
-
normalizeFreeflowConfig,
|
|
16
|
-
} from "../../../router/dist/index.js";
|
|
17
|
-
import {
|
|
18
|
-
handleModeCommand,
|
|
19
7
|
readCapabilityState,
|
|
20
8
|
readFreeflowConfig,
|
|
21
9
|
readFreeflowConfigLayers,
|
|
22
10
|
readFreeflowLocalConfig,
|
|
23
|
-
readModeState,
|
|
24
11
|
resetSessionOverrides,
|
|
25
12
|
setSessionCoreOverride,
|
|
26
|
-
VALID_MODES,
|
|
27
13
|
} from "../runtime/runtime-context.js";
|
|
28
14
|
import {
|
|
29
15
|
PiSettingsComponent,
|
|
30
16
|
type SettingsCommitResult,
|
|
31
17
|
type SettingsEntry,
|
|
32
18
|
type SettingsSessionResult,
|
|
19
|
+
type SettingsWizard,
|
|
20
|
+
type SettingsWizardStep,
|
|
33
21
|
} from "./settings-tui.js";
|
|
22
|
+
import { supportsPiSessionModelStateApi } from "../cognitive-routing/pi-session-control.js";
|
|
23
|
+
import { isPiFlowHost } from "../runtime/runtime-identity.js";
|
|
24
|
+
import type {
|
|
25
|
+
CognitiveRoutingCapabilityState,
|
|
26
|
+
CognitiveRoutingProfile,
|
|
27
|
+
CognitiveRoutingProfileName,
|
|
28
|
+
CognitiveRoutingThinkingLevel,
|
|
29
|
+
} from "../cognitive-routing/types.js";
|
|
34
30
|
|
|
35
|
-
const POST_TOOL_ROUTING_VALUES = ["off", "safety-net", "strict"] as const;
|
|
36
|
-
const STORAGE_POLICY_VALUES = ["hybrid-dedupe", "store-everything"] as const;
|
|
37
|
-
const OBSERVED_PERSISTENCE_VALUES = ["none", "metadata-only", "exact"] as const;
|
|
38
|
-
const SCRIPT_LANGUAGES = ["javascript", "python", "jq"] as const;
|
|
39
31
|
const DEFAULT_FREEFLOW_ENABLED = true;
|
|
40
|
-
const
|
|
41
|
-
const
|
|
42
|
-
const MODE_VALUES = ["conversation", "workflow", "strict-workflow"];
|
|
43
|
-
const MODE_LABELS = {
|
|
44
|
-
default: "Use configured default",
|
|
45
|
-
conversation: "conversation",
|
|
46
|
-
workflow: "workflow",
|
|
47
|
-
"strict-workflow": "strict-workflow",
|
|
48
|
-
};
|
|
49
|
-
const MODE_DESCRIPTIONS = {
|
|
50
|
-
conversation: "Discussion and read-only inspection",
|
|
51
|
-
workflow: "Adaptive workflow for consequential work",
|
|
52
|
-
"strict-workflow": "Stronger decision and evidence pressure for high-risk work",
|
|
53
|
-
};
|
|
32
|
+
const DEFAULT_CONTEXT_VIRTUALIZATION_ENABLED = false;
|
|
33
|
+
const DEFAULT_CONVERSATION_HISTORY_ENABLED = false;
|
|
54
34
|
const LOCAL_INHERIT = "inherit";
|
|
55
35
|
const execFileAsync = promisify(execFile);
|
|
56
36
|
|
|
@@ -58,6 +38,7 @@ type ConfigScope = "session" | "local" | "repository";
|
|
|
58
38
|
type ConfigSource = "session" | "local" | "repository" | "builtin";
|
|
59
39
|
|
|
60
40
|
type SettingKind = "boolean" | "enum" | "integer" | "string" | "list" | "json" | "group";
|
|
41
|
+
type SettingsValue = boolean | number | string | null | SettingsValue[] | { [key: string]: SettingsValue };
|
|
61
42
|
|
|
62
43
|
type SettingsItem = {
|
|
63
44
|
id: string;
|
|
@@ -71,8 +52,10 @@ type SettingsItem = {
|
|
|
71
52
|
valueLabels?: Record<string, string>;
|
|
72
53
|
valueDescriptions?: Record<string, string>;
|
|
73
54
|
inactive?: boolean;
|
|
55
|
+
runtimeInactive?: boolean;
|
|
74
56
|
displaySuffix?: string;
|
|
75
57
|
children?: SettingsItem[];
|
|
58
|
+
wizard?: () => SettingsWizard;
|
|
76
59
|
parse?: (text: string) => unknown;
|
|
77
60
|
format?: (value: unknown) => string;
|
|
78
61
|
configScope?: ConfigScope;
|
|
@@ -85,6 +68,18 @@ type SettingsItem = {
|
|
|
85
68
|
transient?: boolean;
|
|
86
69
|
};
|
|
87
70
|
|
|
71
|
+
type CognitiveRoutingSettingsController = {
|
|
72
|
+
state(): { effective: boolean; controlMode: string; activeProfile?: string };
|
|
73
|
+
setManualProfile(profile: "standard" | "reasoning", mechanism?: string): Promise<{ status: string; reason?: string }>;
|
|
74
|
+
setAutomaticControl(mechanism?: string): Promise<{ status: string; reason?: string }>;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
type AfterChangeOptions = {
|
|
78
|
+
reconcileCognitiveRouting?: boolean;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
type AfterChange = (changed: boolean, options?: AfterChangeOptions) => Promise<void> | void;
|
|
82
|
+
|
|
88
83
|
type OpenSettingsOptions = {
|
|
89
84
|
title: string;
|
|
90
85
|
items: SettingsItem[];
|
|
@@ -106,13 +101,13 @@ function cloneJson<T>(value: T): T {
|
|
|
106
101
|
}
|
|
107
102
|
}
|
|
108
103
|
|
|
109
|
-
function getPath(source: unknown, path: string[]):
|
|
110
|
-
let current = source;
|
|
104
|
+
function getPath(source: unknown, path: string[]): SettingsValue | undefined {
|
|
105
|
+
let current: unknown = source;
|
|
111
106
|
for (const key of path) {
|
|
112
107
|
if (!isRecord(current)) return undefined;
|
|
113
|
-
current = current[key];
|
|
108
|
+
current = current[key] as SettingsValue | undefined;
|
|
114
109
|
}
|
|
115
|
-
return current;
|
|
110
|
+
return current as SettingsValue | undefined;
|
|
116
111
|
}
|
|
117
112
|
|
|
118
113
|
function setPath(target: Record<string, unknown>, path: string[], value: unknown) {
|
|
@@ -170,318 +165,29 @@ function setConfigValue(config: Record<string, unknown>, item: SettingsItem, val
|
|
|
170
165
|
setPath(config, item.path, value);
|
|
171
166
|
}
|
|
172
167
|
|
|
173
|
-
function parseStringList(text: string): string[] {
|
|
174
|
-
return text
|
|
175
|
-
.split(",")
|
|
176
|
-
.map((item) => item.trim())
|
|
177
|
-
.filter(Boolean);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
function parsePositiveInteger(text: string): number {
|
|
181
|
-
const value = Number(text.trim());
|
|
182
|
-
if (!Number.isInteger(value) || value <= 0) {
|
|
183
|
-
throw new Error("Expected a positive integer.");
|
|
184
|
-
}
|
|
185
|
-
return value;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function parseJsonObject(text: string): unknown {
|
|
189
|
-
const trimmed = text.trim();
|
|
190
|
-
if (!trimmed) return {};
|
|
191
|
-
let parsed: unknown;
|
|
192
|
-
try {
|
|
193
|
-
parsed = JSON.parse(trimmed);
|
|
194
|
-
} catch (error) {
|
|
195
|
-
throw new Error(`Expected valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
196
|
-
}
|
|
197
|
-
if (!isRecord(parsed)) {
|
|
198
|
-
throw new Error("Expected a JSON object.");
|
|
199
|
-
}
|
|
200
|
-
return parsed;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function formatList(value: unknown): string {
|
|
204
|
-
return Array.isArray(value) ? value.join(", ") : "";
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
function formatJson(value: unknown): string {
|
|
208
|
-
return JSON.stringify(isRecord(value) ? value : {});
|
|
209
|
-
}
|
|
210
|
-
|
|
211
168
|
function booleanValue(value: unknown): string {
|
|
212
169
|
return value === true ? "enabled" : "disabled";
|
|
213
170
|
}
|
|
214
171
|
|
|
215
|
-
function
|
|
216
|
-
const rawRouter = isRecord(rawConfig.outputRouter) ? rawConfig.outputRouter : {};
|
|
217
|
-
const normalized = normalizeFreeflowConfig({
|
|
218
|
-
...rawConfig,
|
|
219
|
-
outputRouter: { ...rawRouter, enabled: true },
|
|
220
|
-
});
|
|
221
|
-
return normalized.config;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
function rawValue(rawConfig: Record<string, unknown>, path: string[], fallback: unknown): unknown {
|
|
225
|
-
const value = getPath(rawConfig, path);
|
|
226
|
-
return value === undefined ? fallback : value;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function outputRouterItems(rawConfig: Record<string, unknown>, freeflowInactive = false): SettingsItem[] {
|
|
230
|
-
const effective = normalizedSettingsConfig(rawConfig);
|
|
231
|
-
const routerEnabled = normalizeFreeflowConfig(rawConfig).config.outputRouter.enabled;
|
|
232
|
-
const router = effective.outputRouter;
|
|
233
|
-
const scriptTransform = effective.scriptTransform;
|
|
234
|
-
const observedRouting = effective.observedRouting;
|
|
235
|
-
const inactive = freeflowInactive || !routerEnabled;
|
|
236
|
-
|
|
237
|
-
return [
|
|
238
|
-
{
|
|
239
|
-
id: "outputRouter.enabled",
|
|
240
|
-
label: "Output Router",
|
|
241
|
-
description: "Master switch for Freeflow routed evidence tools and router runtime guidance.",
|
|
242
|
-
path: ["outputRouter", "enabled"],
|
|
243
|
-
kind: "boolean",
|
|
244
|
-
value: routerEnabled,
|
|
245
|
-
defaultValue: DEFAULT_OUTPUT_ROUTER_ENABLED,
|
|
246
|
-
inactive: freeflowInactive,
|
|
247
|
-
},
|
|
248
|
-
{
|
|
249
|
-
id: "outputRouter.postToolRouting",
|
|
250
|
-
label: "Native safety net",
|
|
251
|
-
description:
|
|
252
|
-
"Post-process large/noisy native read/bash output. safety-net can replace oversized native output with vaulted routed evidence; strict is stronger.",
|
|
253
|
-
path: ["outputRouter", "postToolRouting"],
|
|
254
|
-
kind: "enum",
|
|
255
|
-
value: router.postToolRouting,
|
|
256
|
-
values: [...POST_TOOL_ROUTING_VALUES],
|
|
257
|
-
valueDescriptions: {
|
|
258
|
-
off: "Leave native tool output unchanged",
|
|
259
|
-
"safety-net": "Route oversized or noisy native output",
|
|
260
|
-
strict: "Apply stronger native output routing",
|
|
261
|
-
},
|
|
262
|
-
defaultValue: DEFAULT_POST_TOOL_ROUTING,
|
|
263
|
-
inactive,
|
|
264
|
-
},
|
|
265
|
-
{
|
|
266
|
-
id: "outputRouter.storagePolicy",
|
|
267
|
-
label: "Storage policy",
|
|
268
|
-
description: "Vault storage behavior for exactness-sensitive and noisy output.",
|
|
269
|
-
path: ["outputRouter", "storagePolicy"],
|
|
270
|
-
kind: "enum",
|
|
271
|
-
value: router.storagePolicy,
|
|
272
|
-
values: [...STORAGE_POLICY_VALUES],
|
|
273
|
-
defaultValue: DEFAULT_STORAGE_POLICY,
|
|
274
|
-
inactive,
|
|
275
|
-
},
|
|
276
|
-
{
|
|
277
|
-
id: "outputRouter.thresholds.largeOutputBytes",
|
|
278
|
-
label: "Large output bytes",
|
|
279
|
-
description: "Byte threshold for treating command/tool output as large.",
|
|
280
|
-
path: ["outputRouter", "thresholds", "largeOutputBytes"],
|
|
281
|
-
kind: "integer",
|
|
282
|
-
value: router.thresholds.largeOutputBytes,
|
|
283
|
-
defaultValue: DEFAULT_ROUTER_THRESHOLDS.largeOutputBytes,
|
|
284
|
-
parse: parsePositiveInteger,
|
|
285
|
-
inactive,
|
|
286
|
-
},
|
|
287
|
-
{
|
|
288
|
-
id: "outputRouter.thresholds.largeOutputLines",
|
|
289
|
-
label: "Large output lines",
|
|
290
|
-
description: "Line threshold for treating command/tool output as large.",
|
|
291
|
-
path: ["outputRouter", "thresholds", "largeOutputLines"],
|
|
292
|
-
kind: "integer",
|
|
293
|
-
value: router.thresholds.largeOutputLines,
|
|
294
|
-
defaultValue: DEFAULT_ROUTER_THRESHOLDS.largeOutputLines,
|
|
295
|
-
parse: parsePositiveInteger,
|
|
296
|
-
inactive,
|
|
297
|
-
},
|
|
298
|
-
{
|
|
299
|
-
id: "outputRouter.vault.root",
|
|
300
|
-
label: "Vault root",
|
|
301
|
-
description: "Root directory for recoverable routed output.",
|
|
302
|
-
path: ["outputRouter", "vault", "root"],
|
|
303
|
-
kind: "string",
|
|
304
|
-
value: router.vault.root,
|
|
305
|
-
defaultValue: DEFAULT_VAULT_ROOT,
|
|
306
|
-
inactive,
|
|
307
|
-
},
|
|
308
|
-
{
|
|
309
|
-
id: "outputRouter.vault.retention.ttlDays",
|
|
310
|
-
label: "Vault retention days",
|
|
311
|
-
description: "TTL retention for vaulted output. Set a positive integer; default is 7 days.",
|
|
312
|
-
path: ["outputRouter", "vault", "retention", "ttlDays"],
|
|
313
|
-
kind: "integer",
|
|
314
|
-
value:
|
|
315
|
-
router.vault.retention?.strategy === "ttl" ? router.vault.retention.ttlDays : DEFAULT_VAULT_RETENTION.ttlDays,
|
|
316
|
-
defaultValue: DEFAULT_VAULT_RETENTION.ttlDays,
|
|
317
|
-
parse: parsePositiveInteger,
|
|
318
|
-
inactive,
|
|
319
|
-
},
|
|
320
|
-
{
|
|
321
|
-
id: "outputRouter.generatedPaths",
|
|
322
|
-
label: "Generated paths",
|
|
323
|
-
description:
|
|
324
|
-
"Comma-separated globs for generated repo paths that should be treated as noisy or lower-priority evidence.",
|
|
325
|
-
path: ["outputRouter", "hints", "generatedPathGlobs"],
|
|
326
|
-
kind: "list",
|
|
327
|
-
value: rawValue(
|
|
328
|
-
rawConfig,
|
|
329
|
-
["outputRouter", "hints", "generatedPathGlobs"],
|
|
330
|
-
rawValue(rawConfig, ["outputRouter", "generatedPaths"], router.hints?.generatedPathGlobs ?? []),
|
|
331
|
-
),
|
|
332
|
-
format: formatList,
|
|
333
|
-
parse: parseStringList,
|
|
334
|
-
inactive,
|
|
335
|
-
},
|
|
336
|
-
{
|
|
337
|
-
id: "outputRouter.noisyCommandHints",
|
|
338
|
-
label: "Noisy command hints",
|
|
339
|
-
description: "Comma-separated command patterns that are likely to produce noisy output.",
|
|
340
|
-
path: ["outputRouter", "hints", "noisyCommandPatterns"],
|
|
341
|
-
kind: "list",
|
|
342
|
-
value: rawValue(
|
|
343
|
-
rawConfig,
|
|
344
|
-
["outputRouter", "hints", "noisyCommandPatterns"],
|
|
345
|
-
rawValue(rawConfig, ["outputRouter", "noisyCommandHints"], router.hints?.noisyCommandPatterns ?? []),
|
|
346
|
-
),
|
|
347
|
-
format: formatList,
|
|
348
|
-
parse: parseStringList,
|
|
349
|
-
inactive,
|
|
350
|
-
},
|
|
351
|
-
{
|
|
352
|
-
id: "outputRouter.scriptTransform.enabled",
|
|
353
|
-
label: "Script transform",
|
|
354
|
-
description:
|
|
355
|
-
"Enable sandboxed script transforms/producers. Requires proof-backed adapters; no unsandboxed fallback.",
|
|
356
|
-
path: ["outputRouter", "scriptTransform", "enabled"],
|
|
357
|
-
kind: "boolean",
|
|
358
|
-
value: scriptTransform.enabled,
|
|
359
|
-
defaultValue: DEFAULT_SCRIPT_TRANSFORM_CONFIG.enabled,
|
|
360
|
-
inactive,
|
|
361
|
-
},
|
|
362
|
-
{
|
|
363
|
-
id: "outputRouter.scriptTransform.languages",
|
|
364
|
-
label: "Script languages",
|
|
365
|
-
description: "Comma-separated sandbox languages to allow: javascript, python, jq.",
|
|
366
|
-
path: ["outputRouter", "scriptTransform", "languages"],
|
|
367
|
-
kind: "list",
|
|
368
|
-
value: scriptTransform.languages,
|
|
369
|
-
defaultValue: DEFAULT_SCRIPT_TRANSFORM_CONFIG.languages,
|
|
370
|
-
format: formatList,
|
|
371
|
-
parse: (text) => parseStringList(text).filter((value) => (SCRIPT_LANGUAGES as readonly string[]).includes(value)),
|
|
372
|
-
inactive,
|
|
373
|
-
},
|
|
374
|
-
{
|
|
375
|
-
id: "outputRouter.scriptTransform.limits.timeoutMs",
|
|
376
|
-
label: "Script timeout ms",
|
|
377
|
-
description: "Maximum sandboxed script execution time.",
|
|
378
|
-
path: ["outputRouter", "scriptTransform", "limits", "timeoutMs"],
|
|
379
|
-
kind: "integer",
|
|
380
|
-
value: scriptTransform.limits.timeoutMs,
|
|
381
|
-
defaultValue: DEFAULT_SCRIPT_TRANSFORM_CONFIG.limits.timeoutMs,
|
|
382
|
-
parse: parsePositiveInteger,
|
|
383
|
-
inactive,
|
|
384
|
-
},
|
|
385
|
-
{
|
|
386
|
-
id: "outputRouter.scriptTransform.limits.maxInputBytes",
|
|
387
|
-
label: "Script max input bytes",
|
|
388
|
-
description: "Maximum input size for sandboxed scripts.",
|
|
389
|
-
path: ["outputRouter", "scriptTransform", "limits", "maxInputBytes"],
|
|
390
|
-
kind: "integer",
|
|
391
|
-
value: scriptTransform.limits.maxInputBytes,
|
|
392
|
-
defaultValue: DEFAULT_SCRIPT_TRANSFORM_CONFIG.limits.maxInputBytes,
|
|
393
|
-
parse: parsePositiveInteger,
|
|
394
|
-
inactive,
|
|
395
|
-
},
|
|
396
|
-
{
|
|
397
|
-
id: "outputRouter.scriptTransform.limits.maxOutputBytes",
|
|
398
|
-
label: "Script max output bytes",
|
|
399
|
-
description: "Maximum output size for sandboxed scripts.",
|
|
400
|
-
path: ["outputRouter", "scriptTransform", "limits", "maxOutputBytes"],
|
|
401
|
-
kind: "integer",
|
|
402
|
-
value: scriptTransform.limits.maxOutputBytes,
|
|
403
|
-
defaultValue: DEFAULT_SCRIPT_TRANSFORM_CONFIG.limits.maxOutputBytes,
|
|
404
|
-
parse: parsePositiveInteger,
|
|
405
|
-
inactive,
|
|
406
|
-
},
|
|
407
|
-
{
|
|
408
|
-
id: "outputRouter.observedRouting.enabled",
|
|
409
|
-
label: "Observed routing",
|
|
410
|
-
description: "Route configured host tool results after they run, e.g. MCP/web/fetch/code-search output.",
|
|
411
|
-
path: ["outputRouter", "observedRouting", "enabled"],
|
|
412
|
-
kind: "boolean",
|
|
413
|
-
value: observedRouting.enabled,
|
|
414
|
-
defaultValue: DEFAULT_OBSERVED_ROUTING_CONFIG.enabled,
|
|
415
|
-
inactive,
|
|
416
|
-
},
|
|
417
|
-
...observedProducerItems("web", "Web search", observedRouting.web, inactive),
|
|
418
|
-
...observedProducerItems("fetch", "Fetch content", observedRouting.fetch, inactive),
|
|
419
|
-
...observedProducerItems("codeSearch", "Code search", observedRouting.codeSearch, inactive),
|
|
420
|
-
{
|
|
421
|
-
id: "outputRouter.observedRouting.mcp.servers",
|
|
422
|
-
label: "MCP servers JSON",
|
|
423
|
-
description: 'JSON object keyed by MCP server id. Example: {"github":{"enabled":true,"persistence":"exact"}}',
|
|
424
|
-
path: ["outputRouter", "observedRouting", "mcp", "servers"],
|
|
425
|
-
kind: "json",
|
|
426
|
-
value: observedRouting.mcp?.servers ?? {},
|
|
427
|
-
defaultValue: DEFAULT_OBSERVED_ROUTING_CONFIG.mcp.servers,
|
|
428
|
-
format: formatJson,
|
|
429
|
-
parse: parseJsonObject,
|
|
430
|
-
inactive,
|
|
431
|
-
},
|
|
432
|
-
];
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
function observedProducerItems(
|
|
436
|
-
id: "web" | "fetch" | "codeSearch",
|
|
437
|
-
label: string,
|
|
438
|
-
value: any,
|
|
439
|
-
inactive: boolean,
|
|
440
|
-
): SettingsItem[] {
|
|
441
|
-
return [
|
|
442
|
-
{
|
|
443
|
-
id: `outputRouter.observedRouting.${id}.enabled`,
|
|
444
|
-
label: `${label} observed`,
|
|
445
|
-
description: `Enable observed routing for ${label.toLowerCase()} output.`,
|
|
446
|
-
path: ["outputRouter", "observedRouting", id, "enabled"],
|
|
447
|
-
kind: "boolean",
|
|
448
|
-
value: value?.enabled === true,
|
|
449
|
-
defaultValue: false,
|
|
450
|
-
inactive,
|
|
451
|
-
},
|
|
452
|
-
{
|
|
453
|
-
id: `outputRouter.observedRouting.${id}.persistence`,
|
|
454
|
-
label: `${label} persistence`,
|
|
455
|
-
description: "Persistence mode for this observed producer: none, metadata-only, or exact.",
|
|
456
|
-
path: ["outputRouter", "observedRouting", id, "persistence"],
|
|
457
|
-
kind: "enum",
|
|
458
|
-
value: value?.persistence ?? "none",
|
|
459
|
-
values: [...OBSERVED_PERSISTENCE_VALUES],
|
|
460
|
-
valueDescriptions: {
|
|
461
|
-
none: "Do not persist observed output",
|
|
462
|
-
"metadata-only": "Persist metadata without raw output",
|
|
463
|
-
exact: "Persist exact recoverable output",
|
|
464
|
-
},
|
|
465
|
-
defaultValue: value?.enabled === true ? undefined : "none",
|
|
466
|
-
inactive,
|
|
467
|
-
},
|
|
468
|
-
];
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
function configValueForChoice(item: SettingsItem, value: unknown): unknown {
|
|
172
|
+
function configValueForChoice(item: SettingsItem, value: unknown): SettingsValue | undefined {
|
|
472
173
|
const key = String(value);
|
|
473
174
|
if (item.configValues && Object.hasOwn(item.configValues, key)) {
|
|
474
|
-
return item.configValues[key];
|
|
175
|
+
return item.configValues[key] as SettingsValue | undefined;
|
|
475
176
|
}
|
|
476
|
-
return value;
|
|
177
|
+
return value as SettingsValue | undefined;
|
|
477
178
|
}
|
|
478
179
|
|
|
479
|
-
function effectiveItemValue(item: SettingsItem):
|
|
480
|
-
return item.effectiveValue
|
|
180
|
+
function effectiveItemValue(item: SettingsItem): SettingsValue | undefined {
|
|
181
|
+
return (item.effectiveValue === undefined ? item.value : item.effectiveValue) as SettingsValue | undefined;
|
|
481
182
|
}
|
|
482
183
|
|
|
483
184
|
function formatCoreValue(value: unknown): string {
|
|
484
|
-
|
|
185
|
+
if (typeof value === "boolean") return booleanValue(value);
|
|
186
|
+
if (isRecord(value) && typeof value.provider === "string" && typeof value.model === "string") {
|
|
187
|
+
const effort = typeof value.thinkingLevel === "string" ? ` · ${value.thinkingLevel}` : "";
|
|
188
|
+
return `${value.provider}/${value.model}${effort}`;
|
|
189
|
+
}
|
|
190
|
+
return String(value ?? "");
|
|
485
191
|
}
|
|
486
192
|
|
|
487
193
|
function coreDisplaySuffix(item: SettingsItem, inactive = false): string {
|
|
@@ -585,120 +291,38 @@ function createScopedBooleanItem(options: ScopedBooleanItemOptions): SettingsIte
|
|
|
585
291
|
return item;
|
|
586
292
|
}
|
|
587
293
|
|
|
588
|
-
type ScopedDefaultModeItemOptions = {
|
|
589
|
-
scope: ConfigScope;
|
|
590
|
-
repositoryDefaultMode: string;
|
|
591
|
-
repositoryDefaultSource: ConfigSource;
|
|
592
|
-
localDefaultMode?: string;
|
|
593
|
-
effectiveValue: string;
|
|
594
|
-
effectiveSource: ConfigSource;
|
|
595
|
-
};
|
|
596
|
-
|
|
597
|
-
function createScopedDefaultModeItem(options: ScopedDefaultModeItemOptions): SettingsItem {
|
|
598
|
-
let item: SettingsItem;
|
|
599
|
-
if (options.scope === "local") {
|
|
600
|
-
item = {
|
|
601
|
-
id: "freeflow.defaultMode",
|
|
602
|
-
label: "Default mode",
|
|
603
|
-
description:
|
|
604
|
-
"Personal default mode for this checkout. Choose inherit to use the repository default; use /freeflow settings repo to edit shared defaults.",
|
|
605
|
-
path: ["defaultMode"],
|
|
606
|
-
kind: "enum",
|
|
607
|
-
value: options.localDefaultMode ?? LOCAL_INHERIT,
|
|
608
|
-
values: [LOCAL_INHERIT, ...MODE_VALUES],
|
|
609
|
-
valueLabels: {
|
|
610
|
-
inherit: "Inherit repository",
|
|
611
|
-
...MODE_LABELS,
|
|
612
|
-
},
|
|
613
|
-
valueDescriptions: {
|
|
614
|
-
inherit: `Use ${options.repositoryDefaultMode} from ${options.repositoryDefaultSource}.`,
|
|
615
|
-
...MODE_DESCRIPTIONS,
|
|
616
|
-
},
|
|
617
|
-
format: (value) => String(value),
|
|
618
|
-
configScope: "local",
|
|
619
|
-
configValues: { inherit: undefined },
|
|
620
|
-
effectiveValue: options.effectiveValue,
|
|
621
|
-
effectiveSource: options.effectiveSource,
|
|
622
|
-
inheritedValue: options.repositoryDefaultMode,
|
|
623
|
-
inheritedSource: options.repositoryDefaultSource,
|
|
624
|
-
};
|
|
625
|
-
} else {
|
|
626
|
-
item = {
|
|
627
|
-
id: "freeflow.defaultMode",
|
|
628
|
-
label: "Default mode",
|
|
629
|
-
description:
|
|
630
|
-
"Shared repository default Freeflow mode used when Skills are enabled. This edits .freeflow/config.json.",
|
|
631
|
-
path: ["defaultMode"],
|
|
632
|
-
kind: "enum",
|
|
633
|
-
value: options.repositoryDefaultMode,
|
|
634
|
-
values: MODE_VALUES,
|
|
635
|
-
valueLabels: MODE_LABELS,
|
|
636
|
-
valueDescriptions: MODE_DESCRIPTIONS,
|
|
637
|
-
configScope: "repository",
|
|
638
|
-
effectiveValue: options.effectiveValue,
|
|
639
|
-
effectiveSource: options.effectiveSource,
|
|
640
|
-
localOverrideValue: options.localDefaultMode,
|
|
641
|
-
};
|
|
642
|
-
}
|
|
643
|
-
item.displaySuffix = coreDisplaySuffix(item);
|
|
644
|
-
return item;
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
function sessionModeDisplaySuffix(
|
|
648
|
-
sessionMode: string,
|
|
649
|
-
defaultMode: string,
|
|
650
|
-
defaultSource: ConfigSource,
|
|
651
|
-
inactive: boolean,
|
|
652
|
-
): string | undefined {
|
|
653
|
-
if (sessionMode !== "default") return undefined;
|
|
654
|
-
const inactiveSuffix = inactive ? " · inactive" : "";
|
|
655
|
-
return `(${defaultMode} · ${defaultSource}${inactiveSuffix})`;
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
function validModeOrUndefined(value: unknown): string | undefined {
|
|
659
|
-
return typeof value === "string" && VALID_MODES.has(value) ? value : undefined;
|
|
660
|
-
}
|
|
661
|
-
|
|
662
294
|
function resolveSettingsCoreView(
|
|
663
295
|
rawConfig: Record<string, unknown>,
|
|
664
296
|
layers?: Awaited<ReturnType<typeof readFreeflowConfigLayers>>,
|
|
665
297
|
) {
|
|
666
298
|
const localConfig = layers?.local.valid && isRecord(layers.local.parsed) ? layers.local.parsed : {};
|
|
667
|
-
const repositorySkillsValue = getPath(rawConfig, ["skills"]);
|
|
668
|
-
const repositorySkills = isRecord(repositorySkillsValue) ? repositorySkillsValue : {};
|
|
669
299
|
const fallbackCore = {
|
|
670
300
|
enabled: getPath(rawConfig, ["enabled"]) !== false,
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
defaultMode: validModeOrUndefined(rawConfig.defaultMode) ?? "workflow",
|
|
301
|
+
contextVirtualization: getPath(rawConfig, ["contextVirtualization"]) === true,
|
|
302
|
+
conversationHistory: getPath(rawConfig, ["conversationHistory"]) === true,
|
|
674
303
|
};
|
|
675
304
|
const fallbackSources = {
|
|
676
305
|
enabled: typeof getPath(rawConfig, ["enabled"]) === "boolean" ? "repository" : "builtin",
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
306
|
+
contextVirtualization:
|
|
307
|
+
typeof getPath(rawConfig, ["contextVirtualization"]) === "boolean" ? "repository" : "builtin",
|
|
308
|
+
conversationHistory: typeof getPath(rawConfig, ["conversationHistory"]) === "boolean" ? "repository" : "builtin",
|
|
680
309
|
} as const;
|
|
681
310
|
return {
|
|
682
311
|
localConfig,
|
|
683
312
|
core: layers?.coreConfig ?? fallbackCore,
|
|
684
313
|
sources: (layers?.sources ?? fallbackSources) as {
|
|
685
314
|
enabled: ConfigSource;
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
defaultMode: ConfigSource;
|
|
315
|
+
contextVirtualization: ConfigSource;
|
|
316
|
+
conversationHistory: ConfigSource;
|
|
689
317
|
},
|
|
690
318
|
};
|
|
691
319
|
}
|
|
692
320
|
|
|
693
|
-
function groupEnabled(items: SettingsItem[], id: string): boolean {
|
|
694
|
-
return items.find((item) => item.id === id)?.value === true;
|
|
695
|
-
}
|
|
696
|
-
|
|
697
321
|
function createSessionBooleanItem(options: {
|
|
698
322
|
id: string;
|
|
699
323
|
label: string;
|
|
700
324
|
description: string;
|
|
701
|
-
key: "enabled" | "
|
|
325
|
+
key: "enabled" | "contextVirtualization" | "conversationHistory";
|
|
702
326
|
inheritedValue: boolean;
|
|
703
327
|
inheritedSource: ConfigSource;
|
|
704
328
|
effectiveValue: boolean;
|
|
@@ -744,19 +368,19 @@ function createSessionBooleanItem(options: {
|
|
|
744
368
|
|
|
745
369
|
function sessionFreeflowItems(
|
|
746
370
|
state: Awaited<ReturnType<typeof readCapabilityState>>,
|
|
747
|
-
|
|
371
|
+
cognitiveRoutingController?: CognitiveRoutingSettingsController,
|
|
748
372
|
): SettingsItem[] {
|
|
749
373
|
const sessionOverrides = state.sessionOverrides as Record<string, boolean>;
|
|
750
374
|
const configured = state.configuredCoreConfig;
|
|
751
375
|
const configuredSources = state.configuredSources as {
|
|
752
376
|
enabled: ConfigSource;
|
|
753
|
-
|
|
754
|
-
|
|
377
|
+
contextVirtualization: ConfigSource;
|
|
378
|
+
conversationHistory: ConfigSource;
|
|
755
379
|
};
|
|
756
380
|
const effectiveSources = state.configSources as {
|
|
757
381
|
enabled: ConfigSource;
|
|
758
|
-
|
|
759
|
-
|
|
382
|
+
contextVirtualization: ConfigSource;
|
|
383
|
+
conversationHistory: ConfigSource;
|
|
760
384
|
};
|
|
761
385
|
|
|
762
386
|
const freeflowItem = createSessionBooleanItem({
|
|
@@ -770,70 +394,71 @@ function sessionFreeflowItems(
|
|
|
770
394
|
effectiveSource: effectiveSources.enabled,
|
|
771
395
|
sessionOverrides,
|
|
772
396
|
});
|
|
773
|
-
const
|
|
774
|
-
id: "freeflow.
|
|
775
|
-
label: "
|
|
776
|
-
description: "Temporary
|
|
777
|
-
key: "
|
|
778
|
-
inheritedValue: configured.
|
|
779
|
-
inheritedSource: configuredSources.
|
|
780
|
-
effectiveValue: state.
|
|
781
|
-
effectiveSource: effectiveSources.
|
|
397
|
+
const contextVirtualizationItem = createSessionBooleanItem({
|
|
398
|
+
id: "freeflow.contextVirtualization",
|
|
399
|
+
label: "Context Virtualization",
|
|
400
|
+
description: "Temporary Context Virtualization override for this Pi session.",
|
|
401
|
+
key: "contextVirtualization",
|
|
402
|
+
inheritedValue: configured.contextVirtualization,
|
|
403
|
+
inheritedSource: configuredSources.contextVirtualization,
|
|
404
|
+
effectiveValue: state.contextVirtualization.enabled,
|
|
405
|
+
effectiveSource: effectiveSources.contextVirtualization,
|
|
782
406
|
sessionOverrides,
|
|
783
407
|
});
|
|
784
|
-
const
|
|
785
|
-
id: "freeflow.
|
|
786
|
-
label: "
|
|
787
|
-
description: "Temporary
|
|
788
|
-
key: "
|
|
789
|
-
inheritedValue: configured.
|
|
790
|
-
inheritedSource: configuredSources.
|
|
791
|
-
effectiveValue: state.
|
|
792
|
-
effectiveSource: effectiveSources.
|
|
408
|
+
const conversationHistoryItem = createSessionBooleanItem({
|
|
409
|
+
id: "freeflow.conversationHistory",
|
|
410
|
+
label: "Conversation History",
|
|
411
|
+
description: "Temporary Conversation History override for this Pi session.",
|
|
412
|
+
key: "conversationHistory",
|
|
413
|
+
inheritedValue: configured.conversationHistory,
|
|
414
|
+
inheritedSource: configuredSources.conversationHistory,
|
|
415
|
+
effectiveValue: state.conversationHistory.enabled,
|
|
416
|
+
effectiveSource: effectiveSources.conversationHistory,
|
|
793
417
|
sessionOverrides,
|
|
794
418
|
});
|
|
795
419
|
|
|
796
420
|
const freeflowInactive = !state.enabled;
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
const
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
421
|
+
contextVirtualizationItem.inactive = freeflowInactive;
|
|
422
|
+
conversationHistoryItem.inactive = freeflowInactive;
|
|
423
|
+
|
|
424
|
+
const cognitiveRoutingState = cognitiveRoutingController?.state();
|
|
425
|
+
const cognitiveRoutingProfile =
|
|
426
|
+
cognitiveRoutingState?.controlMode === "manual-standard"
|
|
427
|
+
? "standard"
|
|
428
|
+
: cognitiveRoutingState?.controlMode === "manual-reasoning"
|
|
429
|
+
? "reasoning"
|
|
430
|
+
: "auto";
|
|
431
|
+
const cognitiveRoutingItem: SettingsItem | undefined = state.cognitiveRouting
|
|
432
|
+
? {
|
|
433
|
+
id: "freeflow.cognitiveRouting.profile",
|
|
434
|
+
label: "Cognitive Routing",
|
|
435
|
+
description: "Hold a profile manually or release the hold for automatic model control.",
|
|
436
|
+
kind: "enum",
|
|
437
|
+
value: cognitiveRoutingProfile,
|
|
438
|
+
values: ["auto", "standard", "reasoning"],
|
|
439
|
+
valueLabels: {
|
|
440
|
+
auto: "automatic",
|
|
441
|
+
standard: "manual · standard",
|
|
442
|
+
reasoning: "manual · reasoning",
|
|
443
|
+
},
|
|
444
|
+
valueDescriptions: {
|
|
445
|
+
auto: "Release the manual hold without forcing a model transition.",
|
|
446
|
+
standard: "Hold the standard profile until /freeflow profile auto.",
|
|
447
|
+
reasoning: "Hold the reasoning profile until /freeflow profile auto.",
|
|
448
|
+
},
|
|
449
|
+
inactive: freeflowInactive || !state.cognitiveRouting.effective || cognitiveRoutingController === undefined,
|
|
450
|
+
runtimeInactive: state.cognitiveRouting.blockingReason?.code === "runtime_disabled",
|
|
451
|
+
displaySuffix: cognitiveRoutingState?.effective ? cognitiveRoutingProfile : "unavailable",
|
|
452
|
+
}
|
|
453
|
+
: undefined;
|
|
827
454
|
|
|
828
455
|
return [
|
|
829
456
|
freeflowItem,
|
|
830
|
-
|
|
831
|
-
skillsItem,
|
|
832
|
-
sessionModeItem,
|
|
457
|
+
...(cognitiveRoutingItem ? [cognitiveRoutingItem] : []),
|
|
833
458
|
{
|
|
834
459
|
id: "freeflow.session.reset",
|
|
835
460
|
label: "Reset session overrides",
|
|
836
|
-
description: "Clear Freeflow,
|
|
461
|
+
description: "Clear Freeflow, Context Virtualization, and Conversation History overrides for this Pi session.",
|
|
837
462
|
kind: "enum",
|
|
838
463
|
value: "available",
|
|
839
464
|
values: ["reset"],
|
|
@@ -842,15 +467,370 @@ function sessionFreeflowItems(
|
|
|
842
467
|
format: () => "available",
|
|
843
468
|
transient: true,
|
|
844
469
|
},
|
|
470
|
+
{
|
|
471
|
+
id: "freeflow.context",
|
|
472
|
+
label: "Freeflow Context",
|
|
473
|
+
description: "Choose which context projection and conversation-history operations are available to the model.",
|
|
474
|
+
kind: "group",
|
|
475
|
+
value: contextVirtualizationItem.effectiveValue === true || conversationHistoryItem.effectiveValue === true,
|
|
476
|
+
displaySuffix: `${[contextVirtualizationItem, conversationHistoryItem].filter((item) => item.effectiveValue === true).length}/2 enabled`,
|
|
477
|
+
children: [contextVirtualizationItem, conversationHistoryItem],
|
|
478
|
+
},
|
|
479
|
+
];
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const COGNITIVE_ROUTING_THINKING_LEVELS: CognitiveRoutingThinkingLevel[] = [
|
|
483
|
+
"off",
|
|
484
|
+
"minimal",
|
|
485
|
+
"low",
|
|
486
|
+
"medium",
|
|
487
|
+
"high",
|
|
488
|
+
"xhigh",
|
|
489
|
+
"max",
|
|
490
|
+
];
|
|
491
|
+
|
|
492
|
+
const COGNITIVE_ROUTING_EFFORT_DESCRIPTIONS: Record<CognitiveRoutingThinkingLevel, string> = {
|
|
493
|
+
off: "No reasoning",
|
|
494
|
+
minimal: "Very brief reasoning",
|
|
495
|
+
low: "Light reasoning",
|
|
496
|
+
medium: "Moderate reasoning",
|
|
497
|
+
high: "Deep reasoning",
|
|
498
|
+
xhigh: "Extra-high reasoning",
|
|
499
|
+
max: "Maximum reasoning",
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
type CognitiveRoutingModelOption = {
|
|
503
|
+
key: string;
|
|
504
|
+
provider: string;
|
|
505
|
+
model: string;
|
|
506
|
+
label: string;
|
|
507
|
+
description: string;
|
|
508
|
+
thinkingLevels: CognitiveRoutingThinkingLevel[];
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
function cognitiveRoutingModelKey(provider: string, model: string): string {
|
|
512
|
+
return `${provider}/${model}`;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function isCognitiveRoutingProfile(value: unknown): value is CognitiveRoutingProfile {
|
|
516
|
+
return (
|
|
517
|
+
isRecord(value) &&
|
|
518
|
+
typeof value.provider === "string" &&
|
|
519
|
+
typeof value.model === "string" &&
|
|
520
|
+
typeof value.thinkingLevel === "string" &&
|
|
521
|
+
COGNITIVE_ROUTING_THINKING_LEVELS.includes(value.thinkingLevel as CognitiveRoutingThinkingLevel)
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function cognitiveRoutingThinkingLevels(model: any, registry: any): CognitiveRoutingThinkingLevel[] {
|
|
526
|
+
if (typeof registry?.clampThinkingLevel !== "function") {
|
|
527
|
+
return model?.reasoning === true ? [...COGNITIVE_ROUTING_THINKING_LEVELS] : ["off"];
|
|
528
|
+
}
|
|
529
|
+
return COGNITIVE_ROUTING_THINKING_LEVELS.filter((level) => {
|
|
530
|
+
try {
|
|
531
|
+
return registry.clampThinkingLevel(model, level) === level;
|
|
532
|
+
} catch {
|
|
533
|
+
return false;
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function cognitiveRoutingModelOptions(ctx: any): CognitiveRoutingModelOption[] {
|
|
539
|
+
const registry = ctx?.modelRegistry;
|
|
540
|
+
if (typeof registry?.getAvailable !== "function") return [];
|
|
541
|
+
let models: any[];
|
|
542
|
+
try {
|
|
543
|
+
models = registry.getAvailable();
|
|
544
|
+
} catch {
|
|
545
|
+
return [];
|
|
546
|
+
}
|
|
547
|
+
if (!Array.isArray(models)) return [];
|
|
548
|
+
|
|
549
|
+
return models
|
|
550
|
+
.filter((model) => typeof model?.provider === "string" && typeof model?.id === "string")
|
|
551
|
+
.map((model) => {
|
|
552
|
+
const thinkingLevels = cognitiveRoutingThinkingLevels(model, registry);
|
|
553
|
+
const provider = model.provider as string;
|
|
554
|
+
const modelId = model.id as string;
|
|
555
|
+
const key = cognitiveRoutingModelKey(provider, modelId);
|
|
556
|
+
const displayName = typeof model.name === "string" && model.name !== modelId ? model.name : undefined;
|
|
557
|
+
return {
|
|
558
|
+
key,
|
|
559
|
+
provider,
|
|
560
|
+
model: modelId,
|
|
561
|
+
label: key,
|
|
562
|
+
description: displayName ? `${displayName} · ${thinkingLevels.join(", ")}` : thinkingLevels.join(", "),
|
|
563
|
+
thinkingLevels,
|
|
564
|
+
};
|
|
565
|
+
})
|
|
566
|
+
.filter((model) => model.thinkingLevels.length > 0)
|
|
567
|
+
.sort((a, b) => a.label.localeCompare(b.label));
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
function cognitiveRoutingProfileDisplay(value: unknown): string {
|
|
571
|
+
return isCognitiveRoutingProfile(value) ? formatCoreValue(value) : "not configured";
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function cognitiveRoutingSettingsSource(source: unknown): ConfigSource {
|
|
575
|
+
if (source === "repository") return "repository";
|
|
576
|
+
if (source === "personal" || source === "local") return "local";
|
|
577
|
+
return "builtin";
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function cognitiveRoutingProfileDisplaySuffix(source: unknown, scope: ConfigScope): string | undefined {
|
|
581
|
+
if (!source) return undefined;
|
|
582
|
+
if (source === "personal" && scope === "repository") return "(effective personal)";
|
|
583
|
+
return `(${source})`;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function cognitiveRoutingConfirmStep(summary: string): SettingsWizardStep {
|
|
587
|
+
return {
|
|
588
|
+
title: "Confirm preset",
|
|
589
|
+
choices: [
|
|
590
|
+
{
|
|
591
|
+
key: "__save__",
|
|
592
|
+
value: "save",
|
|
593
|
+
label: `Save ${summary}`,
|
|
594
|
+
description: "Write the complete profile atomically.",
|
|
595
|
+
},
|
|
596
|
+
{ key: "__cancel__", value: "cancel", label: "Cancel", description: "Leave the previous preset unchanged." },
|
|
597
|
+
],
|
|
598
|
+
selectedKey: "__save__",
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
function createCognitiveRoutingProfileWizard(
|
|
603
|
+
currentValue: unknown,
|
|
604
|
+
models: CognitiveRoutingModelOption[],
|
|
605
|
+
allowInherit: boolean,
|
|
606
|
+
): SettingsWizard {
|
|
607
|
+
const currentProfile = isCognitiveRoutingProfile(currentValue) ? currentValue : undefined;
|
|
608
|
+
const currentModelKey = currentProfile
|
|
609
|
+
? cognitiveRoutingModelKey(currentProfile.provider, currentProfile.model)
|
|
610
|
+
: undefined;
|
|
611
|
+
const firstChoices = [
|
|
612
|
+
...(allowInherit
|
|
613
|
+
? [
|
|
614
|
+
{
|
|
615
|
+
key: LOCAL_INHERIT,
|
|
616
|
+
value: LOCAL_INHERIT,
|
|
617
|
+
label: "Inherit repository preset",
|
|
618
|
+
description: "Remove the personal override without changing the repository preset.",
|
|
619
|
+
},
|
|
620
|
+
]
|
|
621
|
+
: []),
|
|
622
|
+
...models.map((model) => ({
|
|
623
|
+
key: model.key,
|
|
624
|
+
value: model,
|
|
625
|
+
label: model.label,
|
|
626
|
+
description: model.description,
|
|
627
|
+
})),
|
|
845
628
|
];
|
|
629
|
+
if (firstChoices.length === 0) {
|
|
630
|
+
firstChoices.push({
|
|
631
|
+
key: "__cancel__",
|
|
632
|
+
value: "cancel",
|
|
633
|
+
label: "No authenticated models available",
|
|
634
|
+
description: "Authenticate a provider before configuring this preset.",
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
return {
|
|
639
|
+
firstStep: () => ({
|
|
640
|
+
title: "Choose model",
|
|
641
|
+
choices: firstChoices,
|
|
642
|
+
selectedKey:
|
|
643
|
+
currentValue === LOCAL_INHERIT
|
|
644
|
+
? LOCAL_INHERIT
|
|
645
|
+
: firstChoices.some((choice) => choice.key === currentModelKey)
|
|
646
|
+
? currentModelKey
|
|
647
|
+
: firstChoices[0]?.key,
|
|
648
|
+
}),
|
|
649
|
+
nextStep: (selectedValues) => {
|
|
650
|
+
if (selectedValues.length === 1) {
|
|
651
|
+
const selected = selectedValues[0];
|
|
652
|
+
if (selected === LOCAL_INHERIT) {
|
|
653
|
+
return cognitiveRoutingConfirmStep("inherit repository preset");
|
|
654
|
+
}
|
|
655
|
+
const model = selected as CognitiveRoutingModelOption;
|
|
656
|
+
if (!model?.key || model.thinkingLevels.length === 0) return undefined;
|
|
657
|
+
const currentEffort =
|
|
658
|
+
currentProfile && currentModelKey === model.key ? currentProfile.thinkingLevel : undefined;
|
|
659
|
+
return {
|
|
660
|
+
title: `Choose effort · ${model.label}`,
|
|
661
|
+
choices: model.thinkingLevels.map((level) => ({
|
|
662
|
+
key: level,
|
|
663
|
+
value: level,
|
|
664
|
+
label: level,
|
|
665
|
+
description: COGNITIVE_ROUTING_EFFORT_DESCRIPTIONS[level],
|
|
666
|
+
})),
|
|
667
|
+
selectedKey:
|
|
668
|
+
currentEffort && model.thinkingLevels.includes(currentEffort) ? currentEffort : model.thinkingLevels[0],
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
if (selectedValues.length === 2) {
|
|
672
|
+
const model = selectedValues[0] as CognitiveRoutingModelOption;
|
|
673
|
+
const effort = selectedValues[1] as CognitiveRoutingThinkingLevel;
|
|
674
|
+
return cognitiveRoutingConfirmStep(`${model.label} · ${effort}`);
|
|
675
|
+
}
|
|
676
|
+
return undefined;
|
|
677
|
+
},
|
|
678
|
+
valueFromSelections: (selectedValues) => {
|
|
679
|
+
if (selectedValues[0] === LOCAL_INHERIT) return undefined;
|
|
680
|
+
const model = selectedValues[0] as CognitiveRoutingModelOption;
|
|
681
|
+
return {
|
|
682
|
+
provider: model.provider,
|
|
683
|
+
model: model.model,
|
|
684
|
+
thinkingLevel: selectedValues[1] as CognitiveRoutingThinkingLevel,
|
|
685
|
+
} satisfies CognitiveRoutingProfile;
|
|
686
|
+
},
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function cognitiveRoutingProfileItem(options: {
|
|
691
|
+
name: CognitiveRoutingProfileName;
|
|
692
|
+
scope: ConfigScope;
|
|
693
|
+
rawConfig: Record<string, unknown>;
|
|
694
|
+
localConfig: Record<string, unknown>;
|
|
695
|
+
capabilityState: CognitiveRoutingCapabilityState | undefined;
|
|
696
|
+
ctx: any;
|
|
697
|
+
}): SettingsItem {
|
|
698
|
+
const path = ["cognitiveRouting", "profiles", options.name];
|
|
699
|
+
const repositoryProfile = getPath(options.rawConfig, path);
|
|
700
|
+
const localProfile = getPath(options.localConfig, path);
|
|
701
|
+
const effectiveProfile = options.capabilityState?.profiles?.[options.name] ?? repositoryProfile;
|
|
702
|
+
const localValue = isCognitiveRoutingProfile(localProfile) ? localProfile : LOCAL_INHERIT;
|
|
703
|
+
const value = options.scope === "local" ? localValue : repositoryProfile;
|
|
704
|
+
const source =
|
|
705
|
+
options.capabilityState?.profileSources?.[options.name] ??
|
|
706
|
+
(isCognitiveRoutingProfile(repositoryProfile) ? "repository" : undefined);
|
|
707
|
+
const models = cognitiveRoutingModelOptions(options.ctx);
|
|
708
|
+
const inactive = options.scope === "local" && models.length === 0 && localValue !== LOCAL_INHERIT;
|
|
709
|
+
|
|
710
|
+
return {
|
|
711
|
+
id: `freeflow.cognitiveRouting.${options.name}`,
|
|
712
|
+
label: `${options.name.charAt(0).toUpperCase()}${options.name.slice(1)} preset`,
|
|
713
|
+
description:
|
|
714
|
+
"Choose an authenticated available model and one effort supported by that model. Confirming writes the complete preset; cancel leaves it unchanged.",
|
|
715
|
+
path,
|
|
716
|
+
kind: "string",
|
|
717
|
+
value,
|
|
718
|
+
format: cognitiveRoutingProfileDisplay,
|
|
719
|
+
configScope: options.scope,
|
|
720
|
+
effectiveValue: effectiveProfile,
|
|
721
|
+
effectiveSource: cognitiveRoutingSettingsSource(source),
|
|
722
|
+
inheritedValue: repositoryProfile,
|
|
723
|
+
inheritedSource: "repository",
|
|
724
|
+
inactive,
|
|
725
|
+
displaySuffix: cognitiveRoutingProfileDisplaySuffix(source, options.scope),
|
|
726
|
+
wizard: () => createCognitiveRoutingProfileWizard(value, models, options.scope === "local"),
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
type CognitiveRoutingSessionStartSetting = "control" | "profile";
|
|
731
|
+
|
|
732
|
+
function cognitiveRoutingSessionStartItem(options: {
|
|
733
|
+
setting: CognitiveRoutingSessionStartSetting;
|
|
734
|
+
scope: ConfigScope;
|
|
735
|
+
rawConfig: Record<string, unknown>;
|
|
736
|
+
localConfig: Record<string, unknown>;
|
|
737
|
+
capabilityState: CognitiveRoutingCapabilityState | undefined;
|
|
738
|
+
}): SettingsItem {
|
|
739
|
+
const path = ["cognitiveRouting", "sessionStart", options.setting];
|
|
740
|
+
const defaultValue = options.setting === "control" ? "automatic" : "reasoning";
|
|
741
|
+
const values = options.setting === "control" ? ["automatic", "manual"] : ["standard", "reasoning"];
|
|
742
|
+
const labels = Object.fromEntries(values.map((value) => [value, value]));
|
|
743
|
+
const descriptions =
|
|
744
|
+
options.setting === "control"
|
|
745
|
+
? {
|
|
746
|
+
automatic: "Start the new session in Reasoning; automatic delegation may use Standard during execution.",
|
|
747
|
+
manual: "Hold the selected profile for the new session until the user changes it.",
|
|
748
|
+
}
|
|
749
|
+
: {
|
|
750
|
+
standard: "Start a new manual session with the configured Standard preset.",
|
|
751
|
+
reasoning: "Start a new manual session with the configured Reasoning preset.",
|
|
752
|
+
};
|
|
753
|
+
const repositoryValue = getPath(options.rawConfig, path);
|
|
754
|
+
const localValue = getPath(options.localConfig, path);
|
|
755
|
+
const isValidValue = (value: unknown): value is string => values.includes(value as string);
|
|
756
|
+
const repositorySetting = isValidValue(repositoryValue) ? repositoryValue : undefined;
|
|
757
|
+
const localSetting = isValidValue(localValue) ? localValue : undefined;
|
|
758
|
+
const inheritedValue = repositorySetting ?? defaultValue;
|
|
759
|
+
const inheritedSource: ConfigSource = repositorySetting ? "repository" : "builtin";
|
|
760
|
+
const configuredSource =
|
|
761
|
+
options.capabilityState?.sessionStartSources?.[options.setting] ?? (repositorySetting ? "repository" : undefined);
|
|
762
|
+
const effectiveSessionStartControl =
|
|
763
|
+
options.capabilityState?.sessionStart?.control ??
|
|
764
|
+
getPath(options.localConfig, ["cognitiveRouting", "sessionStart", "control"]) ??
|
|
765
|
+
getPath(options.rawConfig, ["cognitiveRouting", "sessionStart", "control"]) ??
|
|
766
|
+
"automatic";
|
|
767
|
+
const effectiveValue =
|
|
768
|
+
options.setting === "profile" && effectiveSessionStartControl === "automatic"
|
|
769
|
+
? "reasoning"
|
|
770
|
+
: (options.capabilityState?.sessionStart?.[options.setting] ?? inheritedValue);
|
|
771
|
+
const effectiveSource = cognitiveRoutingSettingsSource(configuredSource);
|
|
772
|
+
const label = options.setting === "control" ? "Session start control" : "Manual session-start profile";
|
|
773
|
+
const description =
|
|
774
|
+
options.setting === "control"
|
|
775
|
+
? "Choose automatic or manual control for new sessions only; changing this does not alter the active session."
|
|
776
|
+
: "Choose the profile for manual session starts; automatic sessions always start in Reasoning.";
|
|
777
|
+
|
|
778
|
+
const item: SettingsItem =
|
|
779
|
+
options.scope === "local"
|
|
780
|
+
? {
|
|
781
|
+
id: `freeflow.cognitiveRouting.sessionStart.${options.setting}`,
|
|
782
|
+
label,
|
|
783
|
+
description: `${description} Choose inherit to use the repository value; use /freeflow settings repo to edit shared defaults.`,
|
|
784
|
+
path,
|
|
785
|
+
kind: "enum",
|
|
786
|
+
value: localSetting ?? LOCAL_INHERIT,
|
|
787
|
+
values: [LOCAL_INHERIT, ...values],
|
|
788
|
+
valueLabels: { inherit: "Inherit repository", ...labels },
|
|
789
|
+
valueDescriptions: {
|
|
790
|
+
inherit: `Use ${inheritedValue} from ${inheritedSource}.`,
|
|
791
|
+
...descriptions,
|
|
792
|
+
},
|
|
793
|
+
format: (value) => String(value),
|
|
794
|
+
configScope: "local",
|
|
795
|
+
configValues: { inherit: undefined },
|
|
796
|
+
effectiveValue,
|
|
797
|
+
effectiveSource,
|
|
798
|
+
inheritedValue,
|
|
799
|
+
inheritedSource,
|
|
800
|
+
}
|
|
801
|
+
: {
|
|
802
|
+
id: `freeflow.cognitiveRouting.sessionStart.${options.setting}`,
|
|
803
|
+
label,
|
|
804
|
+
description: `${description} This edits shared .freeflow/config.json.`,
|
|
805
|
+
path,
|
|
806
|
+
kind: "enum",
|
|
807
|
+
value: inheritedValue,
|
|
808
|
+
values,
|
|
809
|
+
valueLabels: labels,
|
|
810
|
+
valueDescriptions: descriptions,
|
|
811
|
+
format: (value) => String(value),
|
|
812
|
+
defaultValue,
|
|
813
|
+
configScope: "repository",
|
|
814
|
+
effectiveValue,
|
|
815
|
+
effectiveSource,
|
|
816
|
+
localOverrideValue: localSetting,
|
|
817
|
+
};
|
|
818
|
+
item.displaySuffix = coreDisplaySuffix(item);
|
|
819
|
+
return item;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
function isCognitiveRoutingRuntimeAvailable(pi: any): boolean {
|
|
823
|
+
return isPiFlowHost(pi?.host) || supportsPiSessionModelStateApi(pi);
|
|
846
824
|
}
|
|
847
825
|
|
|
848
826
|
function freeflowItems(
|
|
849
827
|
rawConfig: Record<string, unknown>,
|
|
850
|
-
modeState?: Awaited<ReturnType<typeof readModeState>>,
|
|
851
828
|
options: {
|
|
852
829
|
scope?: ConfigScope;
|
|
853
830
|
layers?: Awaited<ReturnType<typeof readFreeflowConfigLayers>>;
|
|
831
|
+
cognitiveRouting?: CognitiveRoutingCapabilityState;
|
|
832
|
+
ctx?: any;
|
|
833
|
+
runtimeAvailable?: boolean;
|
|
854
834
|
} = {},
|
|
855
835
|
): SettingsItem[] {
|
|
856
836
|
const scope = options.scope ?? "repository";
|
|
@@ -863,240 +843,128 @@ function freeflowItems(
|
|
|
863
843
|
localConfig,
|
|
864
844
|
id: "freeflow.enabled",
|
|
865
845
|
label: "Freeflow",
|
|
866
|
-
description: "Master switch for
|
|
846
|
+
description: "Master switch for Freeflow's core guidance, skills, and optional capabilities in this checkout.",
|
|
867
847
|
path: ["enabled"],
|
|
868
848
|
effectiveValue: core.enabled,
|
|
869
849
|
effectiveSource: sources.enabled,
|
|
870
850
|
defaultValue: DEFAULT_FREEFLOW_ENABLED,
|
|
871
851
|
});
|
|
872
|
-
const
|
|
852
|
+
const contextVirtualizationItem = createScopedBooleanItem({
|
|
873
853
|
scope,
|
|
874
854
|
rawConfig,
|
|
875
855
|
localConfig,
|
|
876
|
-
id: "freeflow.
|
|
877
|
-
label: "
|
|
878
|
-
description: "
|
|
879
|
-
path: ["
|
|
880
|
-
effectiveValue: core.
|
|
881
|
-
effectiveSource: sources.
|
|
882
|
-
defaultValue:
|
|
856
|
+
id: "freeflow.contextVirtualization",
|
|
857
|
+
label: "Context Virtualization",
|
|
858
|
+
description: "Let the model archive consumed tool results from future context while preserving session history.",
|
|
859
|
+
path: ["contextVirtualization"],
|
|
860
|
+
effectiveValue: core.contextVirtualization,
|
|
861
|
+
effectiveSource: sources.contextVirtualization,
|
|
862
|
+
defaultValue: DEFAULT_CONTEXT_VIRTUALIZATION_ENABLED,
|
|
883
863
|
});
|
|
884
|
-
const
|
|
864
|
+
const conversationHistoryItem = createScopedBooleanItem({
|
|
885
865
|
scope,
|
|
886
866
|
rawConfig,
|
|
887
867
|
localConfig,
|
|
888
|
-
id: "freeflow.
|
|
889
|
-
label: "
|
|
890
|
-
description: "
|
|
891
|
-
path: ["
|
|
892
|
-
effectiveValue: core.
|
|
893
|
-
effectiveSource: sources.
|
|
894
|
-
defaultValue:
|
|
895
|
-
});
|
|
896
|
-
|
|
897
|
-
const repositoryModeValue = validModeOrUndefined(rawConfig.defaultMode);
|
|
898
|
-
const repositoryDefaultMode = repositoryModeValue ?? "workflow";
|
|
899
|
-
const repositoryDefaultSource: ConfigSource = repositoryModeValue ? "repository" : "builtin";
|
|
900
|
-
const localDefaultMode = validModeOrUndefined(localConfig.defaultMode);
|
|
901
|
-
const defaultModeItem = createScopedDefaultModeItem({
|
|
902
|
-
scope,
|
|
903
|
-
repositoryDefaultMode,
|
|
904
|
-
repositoryDefaultSource,
|
|
905
|
-
localDefaultMode,
|
|
906
|
-
effectiveValue: core.defaultMode,
|
|
907
|
-
effectiveSource: sources.defaultMode,
|
|
868
|
+
id: "freeflow.conversationHistory",
|
|
869
|
+
label: "Conversation History",
|
|
870
|
+
description: "Let the model search and retrieve hidden conversation history on the active branch.",
|
|
871
|
+
path: ["conversationHistory"],
|
|
872
|
+
effectiveValue: core.conversationHistory,
|
|
873
|
+
effectiveSource: sources.conversationHistory,
|
|
874
|
+
defaultValue: DEFAULT_CONVERSATION_HISTORY_ENABLED,
|
|
908
875
|
});
|
|
909
876
|
|
|
910
877
|
const freeflowInactive = !core.enabled;
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
const
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
"
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
inactive: freeflowInactive || !skillsEnabled,
|
|
934
|
-
displaySuffix: sessionModeDisplaySuffix(
|
|
935
|
-
sessionMode,
|
|
936
|
-
core.defaultMode,
|
|
937
|
-
sources.defaultMode,
|
|
938
|
-
freeflowInactive || !skillsEnabled,
|
|
939
|
-
),
|
|
940
|
-
};
|
|
878
|
+
contextVirtualizationItem.inactive = freeflowInactive;
|
|
879
|
+
conversationHistoryItem.inactive = freeflowInactive;
|
|
880
|
+
|
|
881
|
+
const cognitiveRoutingState = options.cognitiveRouting;
|
|
882
|
+
const cognitiveRoutingRuntimeDisabled = options.runtimeAvailable !== true;
|
|
883
|
+
const cognitiveRoutingGroup = (() => {
|
|
884
|
+
const cognitiveRoutingEnabledItem = createScopedBooleanItem({
|
|
885
|
+
scope,
|
|
886
|
+
rawConfig,
|
|
887
|
+
localConfig,
|
|
888
|
+
id: "freeflow.cognitiveRouting.enabled",
|
|
889
|
+
label: "Enabled",
|
|
890
|
+
description: cognitiveRoutingRuntimeDisabled
|
|
891
|
+
? "Requires a host model-state control API; configuration is read-only on this host."
|
|
892
|
+
: "Allow Cognitive Routing to own the session model lease and switch the configured profiles.",
|
|
893
|
+
path: ["cognitiveRouting", "enabled"],
|
|
894
|
+
effectiveValue: cognitiveRoutingState?.enabled ?? false,
|
|
895
|
+
effectiveSource: cognitiveRoutingSettingsSource(cognitiveRoutingState?.enabledSource),
|
|
896
|
+
defaultValue: false,
|
|
897
|
+
});
|
|
898
|
+
cognitiveRoutingEnabledItem.inactive = freeflowInactive || cognitiveRoutingRuntimeDisabled;
|
|
899
|
+
cognitiveRoutingEnabledItem.runtimeInactive = cognitiveRoutingRuntimeDisabled;
|
|
941
900
|
|
|
942
|
-
|
|
943
|
-
|
|
901
|
+
const cognitiveRoutingProfiles = ["standard", "reasoning"].map((name) =>
|
|
902
|
+
cognitiveRoutingProfileItem({
|
|
903
|
+
name: name as CognitiveRoutingProfileName,
|
|
904
|
+
scope,
|
|
905
|
+
rawConfig,
|
|
906
|
+
localConfig,
|
|
907
|
+
capabilityState: cognitiveRoutingState,
|
|
908
|
+
ctx: options.ctx,
|
|
909
|
+
}),
|
|
910
|
+
);
|
|
911
|
+
const cognitiveRoutingSessionStart = (["control", "profile"] as CognitiveRoutingSessionStartSetting[]).map(
|
|
912
|
+
(setting) =>
|
|
913
|
+
cognitiveRoutingSessionStartItem({
|
|
914
|
+
setting,
|
|
915
|
+
scope,
|
|
916
|
+
rawConfig,
|
|
917
|
+
localConfig,
|
|
918
|
+
capabilityState: cognitiveRoutingState,
|
|
919
|
+
}),
|
|
920
|
+
);
|
|
921
|
+
for (const item of [...cognitiveRoutingProfiles, ...cognitiveRoutingSessionStart]) {
|
|
922
|
+
item.inactive ||= freeflowInactive || cognitiveRoutingRuntimeDisabled;
|
|
923
|
+
item.runtimeInactive = cognitiveRoutingRuntimeDisabled;
|
|
924
|
+
}
|
|
925
|
+
const cognitiveRoutingStatus = cognitiveRoutingRuntimeDisabled
|
|
926
|
+
? "unavailable · host unsupported"
|
|
927
|
+
: cognitiveRoutingState
|
|
928
|
+
? cognitiveRoutingState.effective
|
|
929
|
+
? "active"
|
|
930
|
+
: cognitiveRoutingState.blockingReason?.code === "profile_missing"
|
|
931
|
+
? "not configured"
|
|
932
|
+
: (cognitiveRoutingState.blockingReason?.code ?? "inactive")
|
|
933
|
+
: "unavailable";
|
|
934
|
+
return {
|
|
935
|
+
id: "freeflow.cognitiveRouting",
|
|
936
|
+
label: "Cognitive Routing",
|
|
937
|
+
description: cognitiveRoutingRuntimeDisabled
|
|
938
|
+
? "Cognitive Routing configuration is visible for inspection but requires a host model-state control API."
|
|
939
|
+
: "Configure the Standard and Reasoning profiles and choose whether Freeflow may manage model state for this repository.",
|
|
940
|
+
kind: "group" as const,
|
|
941
|
+
value: cognitiveRoutingRuntimeDisabled ? false : (cognitiveRoutingState?.enabled ?? false),
|
|
942
|
+
inactive: freeflowInactive,
|
|
943
|
+
displaySuffix: cognitiveRoutingStatus,
|
|
944
|
+
children: [cognitiveRoutingEnabledItem, ...cognitiveRoutingProfiles, ...cognitiveRoutingSessionStart],
|
|
945
|
+
} satisfies SettingsItem;
|
|
946
|
+
})();
|
|
944
947
|
|
|
945
948
|
return [
|
|
946
949
|
freeflowItem,
|
|
947
|
-
|
|
948
|
-
skillsItem,
|
|
949
|
-
sessionModeItem,
|
|
950
|
-
defaultModeItem,
|
|
950
|
+
...(cognitiveRoutingGroup ? [cognitiveRoutingGroup] : []),
|
|
951
951
|
{
|
|
952
|
-
id: "
|
|
953
|
-
label: "
|
|
954
|
-
description:
|
|
955
|
-
"Shared repository settings for routed evidence tools, native output safety net, vault storage, script transforms, and observed tool routing.",
|
|
952
|
+
id: "freeflow.context",
|
|
953
|
+
label: "Freeflow Context",
|
|
954
|
+
description: "Choose which context projection and conversation-history operations are available to the model.",
|
|
956
955
|
kind: "group",
|
|
957
|
-
value:
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
children: routerItems,
|
|
956
|
+
value: contextVirtualizationItem.effectiveValue === true || conversationHistoryItem.effectiveValue === true,
|
|
957
|
+
displaySuffix: `${[contextVirtualizationItem, conversationHistoryItem].filter((item) => item.effectiveValue === true).length}/2 enabled`,
|
|
958
|
+
children: [contextVirtualizationItem, conversationHistoryItem],
|
|
961
959
|
},
|
|
962
960
|
];
|
|
963
961
|
}
|
|
964
962
|
|
|
965
|
-
function migrateLegacyRouterConfig(config: Record<string, unknown>) {
|
|
966
|
-
const outputRouter = isRecord(config.outputRouter) ? config.outputRouter : undefined;
|
|
967
|
-
if (!outputRouter) return;
|
|
968
|
-
if (config.scriptTransform !== undefined && outputRouter.scriptTransform === undefined) {
|
|
969
|
-
outputRouter.scriptTransform = config.scriptTransform;
|
|
970
|
-
delete config.scriptTransform;
|
|
971
|
-
}
|
|
972
|
-
if (config.observedRouting !== undefined && outputRouter.observedRouting === undefined) {
|
|
973
|
-
outputRouter.observedRouting = config.observedRouting;
|
|
974
|
-
delete config.observedRouting;
|
|
975
|
-
}
|
|
976
|
-
|
|
977
|
-
const thresholds = isRecord(outputRouter.thresholds) ? outputRouter.thresholds : {};
|
|
978
|
-
if (outputRouter.largeOutputBytes !== undefined && thresholds.largeOutputBytes === undefined) {
|
|
979
|
-
thresholds.largeOutputBytes = outputRouter.largeOutputBytes;
|
|
980
|
-
outputRouter.thresholds = thresholds;
|
|
981
|
-
delete outputRouter.largeOutputBytes;
|
|
982
|
-
}
|
|
983
|
-
if (outputRouter.largeOutputLines !== undefined && thresholds.largeOutputLines === undefined) {
|
|
984
|
-
thresholds.largeOutputLines = outputRouter.largeOutputLines;
|
|
985
|
-
outputRouter.thresholds = thresholds;
|
|
986
|
-
delete outputRouter.largeOutputLines;
|
|
987
|
-
}
|
|
988
|
-
|
|
989
|
-
const vault = isRecord(outputRouter.vault) ? outputRouter.vault : {};
|
|
990
|
-
if (outputRouter.vaultRoot !== undefined && vault.root === undefined) {
|
|
991
|
-
vault.root = outputRouter.vaultRoot;
|
|
992
|
-
outputRouter.vault = vault;
|
|
993
|
-
delete outputRouter.vaultRoot;
|
|
994
|
-
}
|
|
995
|
-
if (outputRouter.vaultRetentionDays !== undefined && !isRecord(vault.retention)) {
|
|
996
|
-
vault.retention = {
|
|
997
|
-
strategy: "ttl",
|
|
998
|
-
ttlDays: outputRouter.vaultRetentionDays,
|
|
999
|
-
};
|
|
1000
|
-
outputRouter.vault = vault;
|
|
1001
|
-
delete outputRouter.vaultRetentionDays;
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
const hints = isRecord(outputRouter.hints) ? outputRouter.hints : {};
|
|
1005
|
-
if (outputRouter.generatedPaths !== undefined && hints.generatedPathGlobs === undefined) {
|
|
1006
|
-
hints.generatedPathGlobs = outputRouter.generatedPaths;
|
|
1007
|
-
outputRouter.hints = hints;
|
|
1008
|
-
delete outputRouter.generatedPaths;
|
|
1009
|
-
}
|
|
1010
|
-
if (outputRouter.noisyCommandHints !== undefined && hints.noisyCommandPatterns === undefined) {
|
|
1011
|
-
hints.noisyCommandPatterns = outputRouter.noisyCommandHints;
|
|
1012
|
-
outputRouter.hints = hints;
|
|
1013
|
-
delete outputRouter.noisyCommandHints;
|
|
1014
|
-
}
|
|
1015
|
-
}
|
|
1016
|
-
|
|
1017
963
|
function pruneKnownDefaults(config: Record<string, unknown>) {
|
|
1018
964
|
const defaultPaths: Array<{ path: string[]; value: unknown }> = [
|
|
1019
965
|
{ path: ["enabled"], value: DEFAULT_FREEFLOW_ENABLED },
|
|
1020
|
-
{
|
|
1021
|
-
|
|
1022
|
-
value: DEFAULT_INTERACTION_CONTRACT_ENABLED,
|
|
1023
|
-
},
|
|
1024
|
-
{ path: ["skills", "enabled"], value: DEFAULT_SKILLS_ENABLED },
|
|
1025
|
-
{ path: ["outputRouter", "enabled"], value: DEFAULT_OUTPUT_ROUTER_ENABLED },
|
|
1026
|
-
{
|
|
1027
|
-
path: ["outputRouter", "postToolRouting"],
|
|
1028
|
-
value: DEFAULT_POST_TOOL_ROUTING,
|
|
1029
|
-
},
|
|
1030
|
-
{ path: ["outputRouter", "storagePolicy"], value: DEFAULT_STORAGE_POLICY },
|
|
1031
|
-
{
|
|
1032
|
-
path: ["outputRouter", "thresholds", "largeOutputBytes"],
|
|
1033
|
-
value: DEFAULT_ROUTER_THRESHOLDS.largeOutputBytes,
|
|
1034
|
-
},
|
|
1035
|
-
{
|
|
1036
|
-
path: ["outputRouter", "thresholds", "largeOutputLines"],
|
|
1037
|
-
value: DEFAULT_ROUTER_THRESHOLDS.largeOutputLines,
|
|
1038
|
-
},
|
|
1039
|
-
{ path: ["outputRouter", "vault", "root"], value: DEFAULT_VAULT_ROOT },
|
|
1040
|
-
{
|
|
1041
|
-
path: ["outputRouter", "vault", "retention", "ttlDays"],
|
|
1042
|
-
value: DEFAULT_VAULT_RETENTION.ttlDays,
|
|
1043
|
-
},
|
|
1044
|
-
{
|
|
1045
|
-
path: ["outputRouter", "scriptTransform", "enabled"],
|
|
1046
|
-
value: DEFAULT_SCRIPT_TRANSFORM_CONFIG.enabled,
|
|
1047
|
-
},
|
|
1048
|
-
{
|
|
1049
|
-
path: ["outputRouter", "scriptTransform", "sandbox"],
|
|
1050
|
-
value: DEFAULT_SCRIPT_TRANSFORM_CONFIG.sandbox,
|
|
1051
|
-
},
|
|
1052
|
-
{
|
|
1053
|
-
path: ["outputRouter", "scriptTransform", "languages"],
|
|
1054
|
-
value: DEFAULT_SCRIPT_TRANSFORM_CONFIG.languages,
|
|
1055
|
-
},
|
|
1056
|
-
{
|
|
1057
|
-
path: ["outputRouter", "scriptTransform", "network"],
|
|
1058
|
-
value: DEFAULT_SCRIPT_TRANSFORM_CONFIG.network,
|
|
1059
|
-
},
|
|
1060
|
-
{
|
|
1061
|
-
path: ["outputRouter", "scriptTransform", "rawScriptPersistence"],
|
|
1062
|
-
value: DEFAULT_SCRIPT_TRANSFORM_CONFIG.rawScriptPersistence,
|
|
1063
|
-
},
|
|
1064
|
-
{
|
|
1065
|
-
path: ["outputRouter", "scriptTransform", "limits", "timeoutMs"],
|
|
1066
|
-
value: DEFAULT_SCRIPT_TRANSFORM_CONFIG.limits.timeoutMs,
|
|
1067
|
-
},
|
|
1068
|
-
{
|
|
1069
|
-
path: ["outputRouter", "scriptTransform", "limits", "maxInputBytes"],
|
|
1070
|
-
value: DEFAULT_SCRIPT_TRANSFORM_CONFIG.limits.maxInputBytes,
|
|
1071
|
-
},
|
|
1072
|
-
{
|
|
1073
|
-
path: ["outputRouter", "scriptTransform", "limits", "maxOutputBytes"],
|
|
1074
|
-
value: DEFAULT_SCRIPT_TRANSFORM_CONFIG.limits.maxOutputBytes,
|
|
1075
|
-
},
|
|
1076
|
-
{
|
|
1077
|
-
path: ["outputRouter", "observedRouting", "enabled"],
|
|
1078
|
-
value: DEFAULT_OBSERVED_ROUTING_CONFIG.enabled,
|
|
1079
|
-
},
|
|
1080
|
-
{
|
|
1081
|
-
path: ["outputRouter", "observedRouting", "onRoutingFailure"],
|
|
1082
|
-
value: DEFAULT_OBSERVED_ROUTING_CONFIG.onRoutingFailure,
|
|
1083
|
-
},
|
|
1084
|
-
{
|
|
1085
|
-
path: ["outputRouter", "observedRouting", "mcp", "servers"],
|
|
1086
|
-
value: DEFAULT_OBSERVED_ROUTING_CONFIG.mcp.servers,
|
|
1087
|
-
},
|
|
1088
|
-
{
|
|
1089
|
-
path: ["outputRouter", "observedRouting", "web", "enabled"],
|
|
1090
|
-
value: DEFAULT_OBSERVED_ROUTING_CONFIG.web.enabled,
|
|
1091
|
-
},
|
|
1092
|
-
{
|
|
1093
|
-
path: ["outputRouter", "observedRouting", "fetch", "enabled"],
|
|
1094
|
-
value: DEFAULT_OBSERVED_ROUTING_CONFIG.fetch.enabled,
|
|
1095
|
-
},
|
|
1096
|
-
{
|
|
1097
|
-
path: ["outputRouter", "observedRouting", "codeSearch", "enabled"],
|
|
1098
|
-
value: DEFAULT_OBSERVED_ROUTING_CONFIG.codeSearch.enabled,
|
|
1099
|
-
},
|
|
966
|
+
{ path: ["contextVirtualization"], value: DEFAULT_CONTEXT_VIRTUALIZATION_ENABLED },
|
|
967
|
+
{ path: ["conversationHistory"], value: DEFAULT_CONVERSATION_HISTORY_ENABLED },
|
|
1100
968
|
];
|
|
1101
969
|
|
|
1102
970
|
for (const item of defaultPaths) {
|
|
@@ -1104,19 +972,6 @@ function pruneKnownDefaults(config: Record<string, unknown>) {
|
|
|
1104
972
|
deletePath(config, item.path);
|
|
1105
973
|
}
|
|
1106
974
|
}
|
|
1107
|
-
|
|
1108
|
-
for (const producer of ["web", "fetch", "codeSearch"]) {
|
|
1109
|
-
const enabledPath = ["outputRouter", "observedRouting", producer, "enabled"];
|
|
1110
|
-
const persistencePath = ["outputRouter", "observedRouting", producer, "persistence"];
|
|
1111
|
-
if (getPath(config, enabledPath) !== true && getPath(config, persistencePath) === "none") {
|
|
1112
|
-
deletePath(config, persistencePath);
|
|
1113
|
-
}
|
|
1114
|
-
}
|
|
1115
|
-
|
|
1116
|
-
const servers = getPath(config, ["outputRouter", "observedRouting", "mcp", "servers"]);
|
|
1117
|
-
if (isRecord(servers) && Object.keys(servers).length === 0) {
|
|
1118
|
-
deletePath(config, ["outputRouter", "observedRouting", "mcp", "servers"]);
|
|
1119
|
-
}
|
|
1120
975
|
}
|
|
1121
976
|
|
|
1122
977
|
async function ensureLocalConfigIgnored(cwd: string) {
|
|
@@ -1190,15 +1045,20 @@ async function updateConfig(
|
|
|
1190
1045
|
const current = await readFreeflowConfig(cwd);
|
|
1191
1046
|
const next = cloneJson(current) as Record<string, unknown>;
|
|
1192
1047
|
setConfigValue(next, item, value);
|
|
1193
|
-
migrateLegacyRouterConfig(next);
|
|
1194
1048
|
pruneKnownDefaults(next);
|
|
1195
1049
|
await mkdir(join(cwd, ".freeflow"), { recursive: true });
|
|
1196
1050
|
await writeFile(join(cwd, ".freeflow/config.json"), `${JSON.stringify(next, null, 2)}\n`, "utf8");
|
|
1197
1051
|
}
|
|
1198
1052
|
|
|
1053
|
+
function isCognitiveRoutingProfileItem(item: SettingsItem): boolean {
|
|
1054
|
+
return item.id === "freeflow.cognitiveRouting.standard" || item.id === "freeflow.cognitiveRouting.reasoning";
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1199
1057
|
function valueForDisplay(item: SettingsItem): string {
|
|
1200
1058
|
let value: string;
|
|
1201
|
-
if (item
|
|
1059
|
+
if (isCognitiveRoutingProfileItem(item) && item.format) {
|
|
1060
|
+
value = item.format(effectiveItemValue(item));
|
|
1061
|
+
} else if (item.configScope === "local" || item.configScope === "session") {
|
|
1202
1062
|
value = formatCoreValue(effectiveItemValue(item));
|
|
1203
1063
|
} else if (item.kind === "boolean") {
|
|
1204
1064
|
value = booleanValue(item.value);
|
|
@@ -1233,54 +1093,46 @@ function findSettingsItem(items: SettingsItem[], id: string): SettingsItem | und
|
|
|
1233
1093
|
function refreshSettingsDerivedState(items: SettingsItem[]) {
|
|
1234
1094
|
const freeflowItem = findSettingsItem(items, "freeflow.enabled");
|
|
1235
1095
|
const freeflowInactive = freeflowItem ? effectiveItemValue(freeflowItem) !== true : false;
|
|
1236
|
-
const
|
|
1237
|
-
const
|
|
1238
|
-
const
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
if (
|
|
1244
|
-
const
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
const defaultSource = defaultModeItem?.effectiveSource ?? sessionModeItem.inheritedSource ?? "builtin";
|
|
1248
|
-
sessionModeItem.inactive = freeflowInactive || !skillsEnabled;
|
|
1249
|
-
sessionModeItem.displaySuffix = sessionModeDisplaySuffix(
|
|
1250
|
-
String(sessionModeItem.value),
|
|
1251
|
-
defaultMode,
|
|
1252
|
-
defaultSource,
|
|
1253
|
-
freeflowInactive || !skillsEnabled,
|
|
1096
|
+
const cognitiveRoutingGroup = findSettingsItem(items, "freeflow.cognitiveRouting");
|
|
1097
|
+
const cognitiveRoutingEnabledItem = findSettingsItem(items, "freeflow.cognitiveRouting.enabled");
|
|
1098
|
+
const cognitiveRoutingProfiles = [
|
|
1099
|
+
findSettingsItem(items, "freeflow.cognitiveRouting.standard"),
|
|
1100
|
+
findSettingsItem(items, "freeflow.cognitiveRouting.reasoning"),
|
|
1101
|
+
];
|
|
1102
|
+
|
|
1103
|
+
if (cognitiveRoutingGroup && cognitiveRoutingEnabledItem) {
|
|
1104
|
+
const enabled = effectiveItemValue(cognitiveRoutingEnabledItem) === true;
|
|
1105
|
+
const profilesConfigured = cognitiveRoutingProfiles.every((item) =>
|
|
1106
|
+
item ? isCognitiveRoutingProfile(effectiveItemValue(item)) : false,
|
|
1254
1107
|
);
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1108
|
+
cognitiveRoutingGroup.value = enabled;
|
|
1109
|
+
cognitiveRoutingGroup.displaySuffix = enabled
|
|
1110
|
+
? profilesConfigured
|
|
1111
|
+
? cognitiveRoutingGroup.displaySuffix === "active"
|
|
1112
|
+
? "active"
|
|
1113
|
+
: "configured"
|
|
1114
|
+
: "not configured"
|
|
1115
|
+
: "disabled";
|
|
1116
|
+
cognitiveRoutingGroup.inactive = freeflowInactive;
|
|
1259
1117
|
}
|
|
1260
1118
|
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1119
|
+
const contextGroup = findSettingsItem(items, "freeflow.context");
|
|
1120
|
+
if (contextGroup?.children?.length) {
|
|
1121
|
+
const enabledCount = contextGroup.children.filter((item) => effectiveItemValue(item) === true).length;
|
|
1122
|
+
contextGroup.value = enabledCount > 0;
|
|
1123
|
+
contextGroup.displaySuffix = `${enabledCount}/${contextGroup.children.length} enabled`;
|
|
1264
1124
|
}
|
|
1265
1125
|
|
|
1266
1126
|
walkSettingsItems(items, (candidate) => {
|
|
1267
1127
|
if (candidate.id === "freeflow.session.reset") {
|
|
1268
1128
|
candidate.inactive = false;
|
|
1269
1129
|
} else if (candidate.configScope) {
|
|
1270
|
-
const inactive =
|
|
1271
|
-
|
|
1130
|
+
const inactive =
|
|
1131
|
+
candidate.id === "freeflow.enabled" ? false : candidate.runtimeInactive === true || freeflowInactive;
|
|
1272
1132
|
candidate.inactive = inactive;
|
|
1273
|
-
candidate.displaySuffix = coreDisplaySuffix(candidate,
|
|
1274
|
-
} else if (candidate.id === "freeflow.sessionMode") {
|
|
1275
|
-
candidate.inactive = freeflowInactive || !skillsEnabled;
|
|
1276
|
-
} else if (candidate.id === "outputRouter.group") {
|
|
1277
|
-
candidate.inactive = freeflowInactive;
|
|
1278
|
-
} else if (candidate.id === "outputRouter.enabled") {
|
|
1279
|
-
candidate.inactive = freeflowInactive;
|
|
1280
|
-
} else if (candidate.id.startsWith("outputRouter.")) {
|
|
1281
|
-
candidate.inactive = freeflowInactive || !routerEnabled;
|
|
1133
|
+
candidate.displaySuffix = coreDisplaySuffix(candidate, inactive);
|
|
1282
1134
|
} else {
|
|
1283
|
-
candidate.inactive = freeflowInactive;
|
|
1135
|
+
candidate.inactive = candidate.runtimeInactive === true || freeflowInactive;
|
|
1284
1136
|
}
|
|
1285
1137
|
});
|
|
1286
1138
|
}
|
|
@@ -1327,6 +1179,7 @@ function settingsEntries(
|
|
|
1327
1179
|
currentChoiceKey: () => String(item.value),
|
|
1328
1180
|
choices: settingsChoices(item),
|
|
1329
1181
|
children: item.children ? () => settingsEntries(item.children!, rootItems, onChange) : undefined,
|
|
1182
|
+
wizard: item.wizard ? () => item.wizard!() : undefined,
|
|
1330
1183
|
edit:
|
|
1331
1184
|
!item.children && !["boolean", "enum", "group"].includes(item.kind)
|
|
1332
1185
|
? {
|
|
@@ -1385,48 +1238,45 @@ async function openSettings(options: OpenSettingsOptions): Promise<SettingsSessi
|
|
|
1385
1238
|
return component?.sessionResult() ?? { changed: false, configChanged: false, failed: false };
|
|
1386
1239
|
}
|
|
1387
1240
|
|
|
1388
|
-
function
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
const router = actual.outputRouter;
|
|
1393
|
-
const configured = settingsConfig;
|
|
1394
|
-
const generatedPaths = router.hints?.generatedPathGlobs?.length ?? 0;
|
|
1395
|
-
const noisyHints = router.hints?.noisyCommandPatterns?.length ?? 0;
|
|
1396
|
-
return [
|
|
1397
|
-
`Output Router: ${freeflowEnabled && router.enabled ? "enabled" : "disabled"}${freeflowEnabled ? "" : " (inactive: Freeflow off)"}`,
|
|
1398
|
-
`script transform: ${freeflowEnabled && configured.scriptTransform.enabled ? "enabled" : "disabled"}${freeflowEnabled && router.enabled ? "" : " (inactive)"}`,
|
|
1399
|
-
`observed routing: ${freeflowEnabled && configured.observedRouting.enabled ? "enabled" : "disabled"}${freeflowEnabled && router.enabled ? "" : " (inactive)"}`,
|
|
1400
|
-
`native safety net: ${freeflowEnabled ? router.postToolRouting : "off"}`,
|
|
1401
|
-
`storage: ${router.storagePolicy}`,
|
|
1402
|
-
`generated paths: ${generatedPaths}`,
|
|
1403
|
-
`noisy command hints: ${noisyHints}`,
|
|
1404
|
-
].join("; ");
|
|
1405
|
-
}
|
|
1406
|
-
|
|
1407
|
-
function freeflowStatusText(state: Awaited<ReturnType<typeof readCapabilityState>>): string {
|
|
1241
|
+
function freeflowStatusText(
|
|
1242
|
+
state: Awaited<ReturnType<typeof readCapabilityState>>,
|
|
1243
|
+
cognitiveRoutingController?: CognitiveRoutingSettingsController,
|
|
1244
|
+
): string {
|
|
1408
1245
|
if (!state.configured) {
|
|
1409
1246
|
return state.configExists
|
|
1410
1247
|
? `Freeflow: inactive (invalid config: ${state.parseError ?? "unknown parse error"}); run /setup-freeflow or fix .freeflow/config.json`
|
|
1411
1248
|
: "Freeflow: inactive (repo not set up); run /setup-freeflow";
|
|
1412
1249
|
}
|
|
1413
1250
|
const sessionSuffix = (source: ConfigSource) => (source === "session" ? " (session override)" : "");
|
|
1251
|
+
const routingState = cognitiveRoutingController?.state();
|
|
1252
|
+
const cognitiveRouting = state.cognitiveRouting;
|
|
1253
|
+
const cognitiveRoutingStatus = cognitiveRouting
|
|
1254
|
+
? cognitiveRouting.blockingReason?.code === "runtime_disabled"
|
|
1255
|
+
? "unavailable (host unsupported)"
|
|
1256
|
+
: cognitiveRouting.effective
|
|
1257
|
+
? routingState?.effective
|
|
1258
|
+
? `active (${routingState.activeProfile ?? "unknown"}, ${routingState.controlMode})`
|
|
1259
|
+
: "effective (inactive)"
|
|
1260
|
+
: cognitiveRouting.enabled
|
|
1261
|
+
? `blocked (${cognitiveRouting.blockingReason.code})`
|
|
1262
|
+
: "disabled"
|
|
1263
|
+
: undefined;
|
|
1264
|
+
const contextEnabled = state.contextVirtualization?.effective || state.conversationHistory?.effective;
|
|
1414
1265
|
return [
|
|
1415
1266
|
`Freeflow: ${state.enabled ? "enabled" : "disabled"}${sessionSuffix(state.configSources.enabled as ConfigSource)}`,
|
|
1416
|
-
`
|
|
1417
|
-
|
|
1418
|
-
)}`,
|
|
1419
|
-
`skills: ${state.skills.effective ? "enabled" : "disabled (workflow modes inactive)"}${sessionSuffix(
|
|
1420
|
-
state.configSources.skillsEnabled as ConfigSource,
|
|
1421
|
-
)}`,
|
|
1422
|
-
`output router: ${state.outputRouter.enabled ? "enabled" : "disabled"}`,
|
|
1267
|
+
`context: ${contextEnabled ? "enabled" : "disabled"} (virtualization ${state.contextVirtualization?.effective ? "enabled" : "disabled"}, history ${state.conversationHistory?.effective ? "enabled" : "disabled"})`,
|
|
1268
|
+
...(cognitiveRoutingStatus ? [`cognitive routing: ${cognitiveRoutingStatus}`] : []),
|
|
1423
1269
|
].join("; ");
|
|
1424
1270
|
}
|
|
1425
1271
|
|
|
1426
1272
|
const NON_TUI_SETTINGS_GUIDANCE =
|
|
1427
|
-
"Freeflow settings require Pi TUI mode. Use /freeflow status to inspect current state; supported non-TUI changes are /freeflow enable
|
|
1428
|
-
|
|
1429
|
-
|
|
1273
|
+
"Freeflow settings require Pi TUI mode. Use /freeflow status to inspect current state; supported non-TUI changes are /freeflow enable and /freeflow disable.";
|
|
1274
|
+
|
|
1275
|
+
function ensureSettingsIdle(ctx: any): boolean {
|
|
1276
|
+
if (typeof ctx?.isIdle !== "function" || ctx.isIdle()) return true;
|
|
1277
|
+
ctx.ui?.notify?.("Freeflow settings and profile changes are available only while Pi is idle.", "warning");
|
|
1278
|
+
return false;
|
|
1279
|
+
}
|
|
1430
1280
|
|
|
1431
1281
|
function nonTuiGuidance(ctx: any, message: string) {
|
|
1432
1282
|
if (ctx?.hasUI === false) throw new Error(message);
|
|
@@ -1437,13 +1287,14 @@ function nonTuiGuidance(ctx: any, message: string) {
|
|
|
1437
1287
|
async function finalizeSettingsSession(
|
|
1438
1288
|
session: SettingsSessionResult,
|
|
1439
1289
|
ctx: any,
|
|
1440
|
-
afterChange:
|
|
1290
|
+
afterChange: AfterChange,
|
|
1441
1291
|
savedMessage: string,
|
|
1442
1292
|
reloadWarning = "Run /reload for Freeflow changes to fully apply.",
|
|
1293
|
+
afterChangeOptions: AfterChangeOptions = {},
|
|
1443
1294
|
) {
|
|
1444
1295
|
if (!session.configChanged) return false;
|
|
1445
1296
|
|
|
1446
|
-
await afterChange(true);
|
|
1297
|
+
await afterChange(true, afterChangeOptions);
|
|
1447
1298
|
if (session.failed) return false;
|
|
1448
1299
|
|
|
1449
1300
|
ctx.ui.notify(savedMessage, "info");
|
|
@@ -1456,11 +1307,7 @@ async function finalizeSettingsSession(
|
|
|
1456
1307
|
return true;
|
|
1457
1308
|
}
|
|
1458
1309
|
|
|
1459
|
-
async function finalizeSessionSettings(
|
|
1460
|
-
session: SettingsSessionResult,
|
|
1461
|
-
ctx: any,
|
|
1462
|
-
afterChange: (changed: boolean) => Promise<void> | void,
|
|
1463
|
-
) {
|
|
1310
|
+
async function finalizeSessionSettings(session: SettingsSessionResult, ctx: any, afterChange: AfterChange) {
|
|
1464
1311
|
if (!session.changed) return false;
|
|
1465
1312
|
|
|
1466
1313
|
await afterChange(true);
|
|
@@ -1480,43 +1327,17 @@ async function finalizeSessionSettings(
|
|
|
1480
1327
|
return true;
|
|
1481
1328
|
}
|
|
1482
1329
|
|
|
1483
|
-
function actionIsMutation(action: string): boolean {
|
|
1484
|
-
return action === "" || action === "settings" || ["enable", "on", "true", "disable", "off", "false"].includes(action);
|
|
1485
|
-
}
|
|
1486
|
-
|
|
1487
|
-
async function maybeBlockLayerMutation(action: string, ctx: any, layerName: string) {
|
|
1488
|
-
if (!actionIsMutation(action)) return false;
|
|
1489
|
-
const state = await readCapabilityState(ctx.cwd);
|
|
1490
|
-
if (!state.configured) {
|
|
1491
|
-
ctx.ui.notify(
|
|
1492
|
-
`Freeflow is installed but this repo is not set up. Run /setup-freeflow before configuring ${layerName}.`,
|
|
1493
|
-
"warning",
|
|
1494
|
-
);
|
|
1495
|
-
return true;
|
|
1496
|
-
}
|
|
1497
|
-
if (!state.enabled) {
|
|
1498
|
-
ctx.ui.notify(
|
|
1499
|
-
`Freeflow is disabled for this repo. Use /freeflow enable before configuring ${layerName}.`,
|
|
1500
|
-
"warning",
|
|
1501
|
-
);
|
|
1502
|
-
return true;
|
|
1503
|
-
}
|
|
1504
|
-
return false;
|
|
1505
|
-
}
|
|
1506
|
-
|
|
1507
1330
|
export async function handleFreeflowCommand(
|
|
1508
1331
|
args: string | undefined,
|
|
1509
1332
|
ctx: any,
|
|
1510
|
-
afterChange:
|
|
1333
|
+
afterChange: AfterChange,
|
|
1511
1334
|
pi: any,
|
|
1335
|
+
cognitiveRoutingController?: CognitiveRoutingSettingsController,
|
|
1512
1336
|
) {
|
|
1513
1337
|
const input = (args ?? "settings").trim().toLowerCase() || "settings";
|
|
1514
1338
|
const [action, ...rest] = input.split(/\s+/);
|
|
1515
1339
|
const actionValue = rest.join(" ");
|
|
1516
1340
|
const nonTui = ctx?.mode && ctx.mode !== "tui";
|
|
1517
|
-
if (nonTui && action === "mode" && !actionValue) {
|
|
1518
|
-
return nonTuiGuidance(ctx, NON_TUI_MODE_GUIDANCE);
|
|
1519
|
-
}
|
|
1520
1341
|
const settingsSelector =
|
|
1521
1342
|
action === "settings" &&
|
|
1522
1343
|
(!actionValue || ["session", "local", "personal", "repo", "repository", "shared"].includes(actionValue));
|
|
@@ -1524,21 +1345,20 @@ export async function handleFreeflowCommand(
|
|
|
1524
1345
|
return nonTuiGuidance(ctx, NON_TUI_SETTINGS_GUIDANCE);
|
|
1525
1346
|
}
|
|
1526
1347
|
|
|
1527
|
-
const [layers, state
|
|
1348
|
+
const [layers, state] = await Promise.all([
|
|
1528
1349
|
readFreeflowConfigLayers(ctx.cwd),
|
|
1529
|
-
readCapabilityState(ctx.cwd),
|
|
1530
|
-
readModeState(ctx.cwd),
|
|
1350
|
+
readCapabilityState(ctx.cwd, ctx, pi?.host),
|
|
1531
1351
|
]);
|
|
1532
1352
|
const configState = layers.repository;
|
|
1533
1353
|
const raw = configState.valid ? configState.parsed : {};
|
|
1534
1354
|
|
|
1535
1355
|
if (action === "status") {
|
|
1536
|
-
ctx.ui.notify(freeflowStatusText(state), "info");
|
|
1356
|
+
ctx.ui.notify(freeflowStatusText(state, cognitiveRoutingController), "info");
|
|
1537
1357
|
return { changed: false, reloaded: false };
|
|
1538
1358
|
}
|
|
1539
1359
|
|
|
1540
1360
|
if (!configState.valid) {
|
|
1541
|
-
ctx.ui.notify(freeflowStatusText(state), "warning");
|
|
1361
|
+
ctx.ui.notify(freeflowStatusText(state, cognitiveRoutingController), "warning");
|
|
1542
1362
|
return { changed: false, reloaded: false, error: "not_configured" };
|
|
1543
1363
|
}
|
|
1544
1364
|
if (layers.local.exists && !layers.local.valid) {
|
|
@@ -1549,42 +1369,15 @@ export async function handleFreeflowCommand(
|
|
|
1549
1369
|
return { changed: false, reloaded: false, error: "invalid_local_config" };
|
|
1550
1370
|
}
|
|
1551
1371
|
|
|
1552
|
-
if (action === "mode") {
|
|
1553
|
-
if (actionValue) {
|
|
1554
|
-
const result = await handleModeCommand(actionValue, ctx, pi);
|
|
1555
|
-
return { changed: result.changed, reloaded: false, error: result.error };
|
|
1556
|
-
}
|
|
1557
|
-
if (!state.enabled || !state.skills.effective) {
|
|
1558
|
-
const result = await handleModeCommand(undefined, ctx, pi);
|
|
1559
|
-
return { changed: false, reloaded: false, error: result.error };
|
|
1560
|
-
}
|
|
1561
|
-
if (typeof ctx?.ui?.custom !== "function") {
|
|
1562
|
-
const result = await handleModeCommand(undefined, ctx, pi);
|
|
1563
|
-
return { changed: false, reloaded: false, error: result.error };
|
|
1564
|
-
}
|
|
1565
|
-
|
|
1566
|
-
const item = freeflowItems(raw, modeState, {
|
|
1567
|
-
scope: "local",
|
|
1568
|
-
layers,
|
|
1569
|
-
}).find((candidate) => candidate.id === "freeflow.sessionMode")!;
|
|
1570
|
-
const session = await openSettings({
|
|
1571
|
-
title: "Freeflow Mode",
|
|
1572
|
-
items: [item],
|
|
1573
|
-
initialChoice: item,
|
|
1574
|
-
ctx,
|
|
1575
|
-
onChange: async (_item, value) => {
|
|
1576
|
-
const result = await handleModeCommand(String(value), ctx, pi);
|
|
1577
|
-
return { changed: result.changed, reloadRequired: false };
|
|
1578
|
-
},
|
|
1579
|
-
});
|
|
1580
|
-
return { changed: session.changed, reloaded: false, error: session.failed ? "write_failed" : undefined };
|
|
1581
|
-
}
|
|
1582
|
-
|
|
1583
1372
|
if (["enable", "on", "true", "disable", "off", "false"].includes(action)) {
|
|
1373
|
+
if (!ensureSettingsIdle(ctx)) return { changed: false, reloaded: false, error: "busy" };
|
|
1584
1374
|
const enabled = ["enable", "on", "true"].includes(action);
|
|
1585
|
-
const item = freeflowItems(raw,
|
|
1375
|
+
const item = freeflowItems(raw, {
|
|
1586
1376
|
scope: "repository",
|
|
1587
1377
|
layers,
|
|
1378
|
+
cognitiveRouting: state.cognitiveRouting as CognitiveRoutingCapabilityState,
|
|
1379
|
+
ctx,
|
|
1380
|
+
runtimeAvailable: isCognitiveRoutingRuntimeAvailable(pi),
|
|
1588
1381
|
}).find((candidate) => candidate.id === "freeflow.enabled")!;
|
|
1589
1382
|
await updateConfig(ctx.cwd, item, enabled, "repository");
|
|
1590
1383
|
await afterChange(true);
|
|
@@ -1599,7 +1392,7 @@ export async function handleFreeflowCommand(
|
|
|
1599
1392
|
|
|
1600
1393
|
if (action && action !== "settings") {
|
|
1601
1394
|
ctx.ui.notify(
|
|
1602
|
-
"Usage: /freeflow, /freeflow settings [local|repo], /freeflow status, /freeflow
|
|
1395
|
+
"Usage: /freeflow, /freeflow settings [local|repo], /freeflow status, /freeflow enable, or /freeflow disable",
|
|
1603
1396
|
"warning",
|
|
1604
1397
|
);
|
|
1605
1398
|
return { changed: false, reloaded: false, error: "invalid_action" };
|
|
@@ -1618,10 +1411,19 @@ export async function handleFreeflowCommand(
|
|
|
1618
1411
|
return { changed: false, reloaded: false, error: "invalid_scope" };
|
|
1619
1412
|
}
|
|
1620
1413
|
|
|
1414
|
+
if (!ensureSettingsIdle(ctx)) return { changed: false, reloaded: false, error: "busy" };
|
|
1415
|
+
|
|
1416
|
+
let reconcileCognitiveRouting = settingsScope === "session";
|
|
1621
1417
|
const items =
|
|
1622
1418
|
settingsScope === "session"
|
|
1623
|
-
? sessionFreeflowItems(state,
|
|
1624
|
-
: freeflowItems(raw,
|
|
1419
|
+
? sessionFreeflowItems(state, cognitiveRoutingController)
|
|
1420
|
+
: freeflowItems(raw, {
|
|
1421
|
+
scope: settingsScope,
|
|
1422
|
+
layers,
|
|
1423
|
+
cognitiveRouting: state.cognitiveRouting as CognitiveRoutingCapabilityState,
|
|
1424
|
+
ctx,
|
|
1425
|
+
runtimeAvailable: isCognitiveRoutingRuntimeAvailable(pi),
|
|
1426
|
+
});
|
|
1625
1427
|
const session = await openSettings({
|
|
1626
1428
|
title:
|
|
1627
1429
|
settingsScope === "session"
|
|
@@ -1632,9 +1434,29 @@ export async function handleFreeflowCommand(
|
|
|
1632
1434
|
items,
|
|
1633
1435
|
ctx,
|
|
1634
1436
|
onChange: async (item, value) => {
|
|
1635
|
-
if (
|
|
1636
|
-
|
|
1637
|
-
|
|
1437
|
+
if (!ensureSettingsIdle(ctx)) return { changed: false, reloadRequired: false };
|
|
1438
|
+
if (!isCognitiveRoutingRuntimeAvailable(pi) && item.id.startsWith("freeflow.cognitiveRouting.")) {
|
|
1439
|
+
ctx.ui.notify("Cognitive Routing is unavailable because this host lacks model-state controls.", "warning");
|
|
1440
|
+
return { changed: false, reloadRequired: false };
|
|
1441
|
+
}
|
|
1442
|
+
if (item.id === "freeflow.cognitiveRouting.profile") {
|
|
1443
|
+
if (!cognitiveRoutingController) {
|
|
1444
|
+
ctx.ui.notify("Cognitive Routing is unavailable for this session.", "warning");
|
|
1445
|
+
return { changed: false, reloadRequired: false };
|
|
1446
|
+
}
|
|
1447
|
+
const result =
|
|
1448
|
+
value === "auto"
|
|
1449
|
+
? await cognitiveRoutingController.setAutomaticControl("profile-settings")
|
|
1450
|
+
: await cognitiveRoutingController.setManualProfile(value as "standard" | "reasoning", "profile-settings");
|
|
1451
|
+
if ((result.status !== "automatic" && result.status !== "active") || result.reason) {
|
|
1452
|
+
ctx.ui.notify(
|
|
1453
|
+
`Cognitive Routing settings could not be applied: ${result.reason ?? result.status}.`,
|
|
1454
|
+
"warning",
|
|
1455
|
+
);
|
|
1456
|
+
return { changed: false, reloadRequired: false };
|
|
1457
|
+
}
|
|
1458
|
+
await afterChange(false);
|
|
1459
|
+
return { changed: true, reloadRequired: false };
|
|
1638
1460
|
}
|
|
1639
1461
|
if (item.id === "freeflow.session.reset") {
|
|
1640
1462
|
const result = await resetSessionOverrides(ctx, pi);
|
|
@@ -1643,8 +1465,8 @@ export async function handleFreeflowCommand(
|
|
|
1643
1465
|
if (item.configScope === "session") {
|
|
1644
1466
|
const keyById = {
|
|
1645
1467
|
"freeflow.enabled": "enabled",
|
|
1646
|
-
"freeflow.
|
|
1647
|
-
"freeflow.
|
|
1468
|
+
"freeflow.contextVirtualization": "contextVirtualization",
|
|
1469
|
+
"freeflow.conversationHistory": "conversationHistory",
|
|
1648
1470
|
} as const;
|
|
1649
1471
|
const key = keyById[item.id as keyof typeof keyById];
|
|
1650
1472
|
const override = value === LOCAL_INHERIT ? null : value === "true";
|
|
@@ -1652,6 +1474,9 @@ export async function handleFreeflowCommand(
|
|
|
1652
1474
|
return { changed: result.changed, reloadRequired: result.reloadRequired === true };
|
|
1653
1475
|
}
|
|
1654
1476
|
await updateConfig(ctx.cwd, item, value, item.configScope ?? "repository");
|
|
1477
|
+
if (!item.id.startsWith("freeflow.cognitiveRouting.sessionStart.")) {
|
|
1478
|
+
reconcileCognitiveRouting = true;
|
|
1479
|
+
}
|
|
1655
1480
|
return { changed: true, reloadRequired: true };
|
|
1656
1481
|
},
|
|
1657
1482
|
});
|
|
@@ -1667,64 +1492,8 @@ export async function handleFreeflowCommand(
|
|
|
1667
1492
|
ctx,
|
|
1668
1493
|
afterChange,
|
|
1669
1494
|
`Freeflow ${savedTarget} saved. Reloading Freeflow runtime...`,
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
}
|
|
1673
|
-
|
|
1674
|
-
export async function handleOutputRouterCommand(
|
|
1675
|
-
args: string | undefined,
|
|
1676
|
-
ctx: any,
|
|
1677
|
-
afterChange: (changed: boolean) => Promise<void> | void,
|
|
1678
|
-
) {
|
|
1679
|
-
const action = (args ?? "").trim().toLowerCase();
|
|
1680
|
-
const raw = await readFreeflowConfig(ctx.cwd);
|
|
1681
|
-
|
|
1682
|
-
if (await maybeBlockLayerMutation(action, ctx, "Output Router")) {
|
|
1683
|
-
return { changed: false, reloaded: false, error: "freeflow_inactive" };
|
|
1684
|
-
}
|
|
1685
|
-
|
|
1686
|
-
if (action === "status") {
|
|
1687
|
-
const state = await readCapabilityState(ctx.cwd);
|
|
1688
|
-
const prefix = !state.configured || !state.enabled ? `${freeflowStatusText(state)}; ` : "";
|
|
1689
|
-
ctx.ui.notify(`${prefix}${outputRouterStatusText(raw)}`, !state.configured || !state.enabled ? "warning" : "info");
|
|
1690
|
-
return { changed: false, reloaded: false };
|
|
1691
|
-
}
|
|
1692
|
-
|
|
1693
|
-
if (["enable", "on", "true", "disable", "off", "false"].includes(action)) {
|
|
1694
|
-
const enabled = ["enable", "on", "true"].includes(action);
|
|
1695
|
-
const item = outputRouterItems(raw).find((candidate) => candidate.id === "outputRouter.enabled")!;
|
|
1696
|
-
await updateConfig(ctx.cwd, item, enabled);
|
|
1697
|
-
await afterChange(true);
|
|
1698
|
-
ctx.ui.notify(`Output Router ${enabled ? "enabled" : "disabled"}. Reloading Freeflow runtime...`, "info");
|
|
1699
|
-
if (typeof ctx.reload === "function") {
|
|
1700
|
-
await ctx.reload();
|
|
1701
|
-
return { changed: true, reloaded: true };
|
|
1702
|
-
}
|
|
1703
|
-
ctx.ui.notify("Run /reload for Output Router changes to fully apply.", "warning");
|
|
1704
|
-
return { changed: true, reloaded: false };
|
|
1705
|
-
}
|
|
1706
|
-
|
|
1707
|
-
if (action && action !== "settings") {
|
|
1708
|
-
ctx.ui.notify("Usage: /output-router, /output-router settings, or /output-router status", "warning");
|
|
1709
|
-
return { changed: false, reloaded: false, error: "invalid_action" };
|
|
1710
|
-
}
|
|
1711
|
-
|
|
1712
|
-
const session = await openSettings({
|
|
1713
|
-
title: "Output Router Settings",
|
|
1714
|
-
items: outputRouterItems(raw),
|
|
1715
|
-
ctx,
|
|
1716
|
-
onChange: async (item, value) => {
|
|
1717
|
-
await updateConfig(ctx.cwd, item, value);
|
|
1718
|
-
return { changed: true, reloadRequired: true };
|
|
1719
|
-
},
|
|
1720
|
-
});
|
|
1721
|
-
|
|
1722
|
-
const reloaded = await finalizeSettingsSession(
|
|
1723
|
-
session,
|
|
1724
|
-
ctx,
|
|
1725
|
-
afterChange,
|
|
1726
|
-
"Output Router settings saved. Reloading Freeflow runtime...",
|
|
1727
|
-
"Run /reload for Output Router changes to fully apply.",
|
|
1495
|
+
"Run /reload for Freeflow changes to fully apply.",
|
|
1496
|
+
{ reconcileCognitiveRouting },
|
|
1728
1497
|
);
|
|
1729
1498
|
return { changed: session.changed, reloaded, error: session.failed ? "write_failed" : undefined };
|
|
1730
1499
|
}
|