@hassangameryt/freeflow 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +148 -154
- package/capabilities/cognitive-routing/SKILL.md +229 -0
- package/capabilities/context-virtualization/SKILL.md +103 -0
- package/capabilities/conversation-history/SKILL.md +53 -0
- package/command-surface.json +6 -13
- package/hooks/freeflow-runtime-context.mjs +35 -91
- package/hooks/tests/check-runtime-context-hook.sh +56 -39
- package/package.json +24 -16
- package/pi-extension/dist/cognitive-routing/commands.js +96 -0
- package/pi-extension/dist/cognitive-routing/config.js +236 -0
- package/pi-extension/dist/cognitive-routing/controller.js +862 -0
- package/pi-extension/dist/cognitive-routing/history.js +439 -0
- package/pi-extension/dist/cognitive-routing/host.js +126 -0
- package/pi-extension/dist/cognitive-routing/runtime.js +43 -0
- package/pi-extension/dist/cognitive-routing/tool.js +211 -0
- package/pi-extension/dist/cognitive-routing/types.js +6 -0
- package/pi-extension/dist/context-virtualization/commands.js +89 -0
- package/pi-extension/dist/context-virtualization/projector.js +42 -0
- package/pi-extension/dist/context-virtualization/runtime.js +356 -0
- package/pi-extension/dist/context-virtualization/state.js +57 -0
- package/pi-extension/dist/context-virtualization/types.js +10 -0
- package/pi-extension/dist/conversation-history/passages.js +66 -0
- package/pi-extension/dist/conversation-history/ranking.js +105 -0
- package/pi-extension/dist/conversation-history/runtime.js +226 -0
- package/pi-extension/dist/conversation-history/search.js +56 -0
- package/pi-extension/dist/conversation-history/types.js +1 -0
- package/pi-extension/dist/freeflow-context/resolver.js +120 -0
- package/pi-extension/dist/freeflow-context/runtime.js +80 -0
- package/pi-extension/dist/freeflow-context/source-projector.js +95 -0
- package/pi-extension/dist/freeflow-context/tool.js +308 -0
- package/pi-extension/dist/freeflow-context/types.js +12 -0
- package/pi-extension/dist/freeflow-context/visibility.js +28 -0
- package/pi-extension/dist/index.js +516 -98
- package/pi-extension/dist/runtime/runtime-context.js +318 -271
- package/pi-extension/dist/runtime/runtime-identity.js +5 -0
- package/pi-extension/dist/settings/settings-tui.js +78 -0
- package/pi-extension/dist/settings/settings-ui.js +593 -563
- package/pi-extension/dist/ui/text-component.js +57 -0
- package/pi-extension/src/cognitive-routing/commands.ts +119 -0
- package/pi-extension/src/cognitive-routing/config.ts +307 -0
- package/pi-extension/src/cognitive-routing/controller.ts +1118 -0
- package/pi-extension/src/cognitive-routing/history.ts +638 -0
- package/pi-extension/src/cognitive-routing/host.ts +172 -0
- package/pi-extension/src/cognitive-routing/runtime.ts +57 -0
- package/pi-extension/src/cognitive-routing/tool.ts +272 -0
- package/pi-extension/src/cognitive-routing/types.ts +89 -0
- package/pi-extension/src/context-virtualization/commands.ts +111 -0
- package/pi-extension/src/context-virtualization/projector.ts +62 -0
- package/pi-extension/src/context-virtualization/runtime.ts +406 -0
- package/pi-extension/src/context-virtualization/state.ts +79 -0
- package/pi-extension/src/context-virtualization/types.ts +92 -0
- package/pi-extension/src/conversation-history/passages.ts +81 -0
- package/pi-extension/src/conversation-history/ranking.ts +155 -0
- package/pi-extension/src/conversation-history/runtime.ts +250 -0
- package/pi-extension/src/conversation-history/search.ts +79 -0
- package/pi-extension/src/conversation-history/types.ts +31 -0
- package/pi-extension/src/freeflow-context/resolver.ts +139 -0
- package/pi-extension/src/freeflow-context/runtime.ts +100 -0
- package/pi-extension/src/freeflow-context/source-projector.ts +106 -0
- package/pi-extension/src/freeflow-context/tool.ts +360 -0
- package/pi-extension/src/freeflow-context/types.ts +70 -0
- package/pi-extension/src/freeflow-context/visibility.ts +53 -0
- package/pi-extension/src/index.ts +553 -101
- package/pi-extension/src/runtime/runtime-context.ts +339 -281
- package/pi-extension/src/runtime/runtime-identity.ts +8 -0
- package/pi-extension/src/settings/settings-tui.ts +93 -0
- package/pi-extension/src/settings/settings-ui.ts +700 -613
- package/pi-extension/src/ui/text-component.ts +66 -0
- package/pi-extension/tests/cognitive-routing/commands.test.js +134 -0
- package/pi-extension/tests/cognitive-routing/controller.test.js +1099 -0
- package/pi-extension/tests/cognitive-routing/extension-lifecycle.test.js +1071 -0
- package/pi-extension/tests/cognitive-routing/history.test.js +274 -0
- package/pi-extension/tests/cognitive-routing/host-fixture.js +4 -0
- package/pi-extension/tests/cognitive-routing/host-identity.test.js +63 -0
- package/pi-extension/tests/cognitive-routing/profile-resolution.test.js +236 -0
- package/pi-extension/tests/cognitive-routing/tool.test.js +230 -0
- package/pi-extension/tests/context-virtualization/extension.test.js +749 -0
- package/pi-extension/tests/context-virtualization/projector.test.js +110 -0
- package/pi-extension/tests/context-virtualization/runtime.test.js +216 -0
- package/pi-extension/tests/conversation-history/runtime.test.js +98 -0
- package/pi-extension/tests/conversation-history/search.test.js +97 -0
- package/pi-extension/tests/integration/pi-extension.test.js +927 -2048
- package/pi-extension/tests/integration/prompt-architecture.test.js +305 -0
- package/runtime/prompts/cognitive-routing.md +9 -0
- package/runtime/prompts/context-virtualization.md +3 -0
- package/runtime/prompts/conversation-history.md +3 -0
- package/runtime/prompts/core.md +13 -0
- package/runtime/prompts/interaction-contract.md +7 -0
- package/runtime/prompts/skills.md +65 -0
- package/skills/action-selection/SKILL.md +91 -0
- package/skills/bypass/SKILL.md +3 -2
- package/skills/commit-work/SKILL.md +1 -1
- package/skills/commit-work/references/staging-decisions.md +1 -1
- package/skills/decision-gate/SKILL.md +1 -1
- package/skills/design-for-depth/SKILL.md +14 -33
- package/skills/design-for-depth/references/design-pressure-signals.md +1 -1
- package/skills/design-for-depth/references/interface-design-loop.md +1 -1
- package/skills/design-for-depth/references/software-design-philosophy.md +1 -1
- package/skills/diagnose-failure/SKILL.md +20 -14
- package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +1 -3
- package/skills/diagnose-failure/references/{flaky-and-performance.md → flaky-and-performance-diagnosis.md} +1 -1
- package/skills/discuss/SKILL.md +34 -22
- package/skills/discuss/references/discussion-continuity.md +71 -0
- package/skills/evaluate-skill/SKILL.md +5 -5
- package/skills/evaluate-skill/references/{definition-schema.md → evaluation-definition-schema.md} +42 -2
- package/skills/evaluate-skill/references/execution-and-evidence.md +8 -3
- package/skills/evaluate-skill/scripts/lib/definitions.mjs +114 -3
- package/skills/evaluate-skill/scripts/lib/grade-interaction.mjs +156 -1
- package/skills/evaluate-skill/scripts/lib/grade.mjs +9 -1
- package/skills/evaluate-skill/scripts/lib/pi.mjs +147 -32
- package/skills/evaluate-skill/scripts/lib/rpc.mjs +4 -4
- package/skills/evaluate-skill/scripts/lib/runner.mjs +36 -14
- package/skills/evaluate-skill/scripts/lib/sandbox.mjs +92 -0
- package/skills/evaluate-skill/scripts/pi-guard.mjs +1 -1
- package/skills/evaluate-skill/scripts/pi-observer.mjs +66 -0
- package/skills/evaluate-skill/scripts/skill-eval.mjs +6 -5
- package/skills/execute-work/SKILL.md +18 -15
- package/skills/execute-work/references/code-practices.md +1 -1
- package/skills/execute-work/references/execute-work-edges.md +42 -0
- package/skills/finish-branch/SKILL.md +2 -2
- package/skills/finish-branch/references/integration-options.md +1 -1
- package/skills/handoff/SKILL.md +3 -3
- package/skills/launch-work/SKILL.md +1 -1
- package/skills/launch-work/references/launch-readiness.md +2 -2
- package/skills/migration-work/SKILL.md +2 -2
- package/skills/migration-work/references/migration-lifecycle.md +1 -1
- package/skills/mode-contract/SKILL.md +4 -4
- package/skills/release-work/SKILL.md +1 -1
- package/skills/release-work/references/release-evidence.md +1 -1
- package/skills/review-artifact/SKILL.md +28 -26
- package/skills/review-work/SKILL.md +28 -26
- package/skills/review-work/references/{reviewer-prompt.md → independent-work-reviewer-contract.md} +1 -1
- package/skills/review-work/references/security-risk-lens.md +1 -1
- package/skills/setup-freeflow/SKILL.md +3 -1
- package/skills/setup-freeflow/references/activation-contract.md +2 -2
- package/skills/setup-freeflow/references/host-setup.md +2 -2
- package/skills/simplify-code/SKILL.md +1 -1
- package/skills/simplify-code/references/simplification-patterns.md +1 -1
- package/skills/tdd/SKILL.md +18 -38
- package/skills/tdd/references/test-design.md +2 -2
- package/skills/track-work/SKILL.md +362 -58
- package/skills/track-work/scripts/lib/codec.mjs +570 -0
- package/skills/track-work/scripts/lib/commands.mjs +568 -0
- package/skills/track-work/scripts/lib/contract.mjs +303 -0
- package/skills/track-work/scripts/lib/model.mjs +506 -0
- package/skills/track-work/scripts/lib/operations.mjs +965 -0
- package/skills/track-work/scripts/lib/record-store.mjs +264 -0
- package/skills/track-work/scripts/lib/result.mjs +37 -0
- package/skills/track-work/scripts/lib/rewrites.mjs +740 -0
- package/skills/track-work/scripts/lib/schema.mjs +277 -0
- package/skills/track-work/scripts/lib/source-inventory.mjs +162 -0
- package/skills/track-work/scripts/lib/views.mjs +443 -0
- package/skills/track-work/scripts/lib/workspace.mjs +134 -0
- package/skills/track-work/scripts/working-record.mjs +122 -0
- package/skills/verify-work/SKILL.md +14 -2
- package/skills/verify-work/references/browser-runtime-evidence.md +1 -1
- package/skills/verify-work/references/integration-evidence.md +1 -1
- package/skills/verify-work/references/performance-evidence.md +1 -1
- package/skills/workflow/SKILL.md +105 -53
- package/skills/workflow/references/domain-skill-composition.md +22 -42
- package/skills/write-plan/SKILL.md +1 -1
- package/skills/write-skill/SKILL.md +3 -2
- package/skills/write-skill/references/progressive-disclosure.md +17 -3
- package/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/track-work/references/working-record-schema.md +0 -378
- package/skills/workflow/references/workflow-loop.md +0 -92
- /package/skills/handoff/references/{templates.md → handoff-templates.md} +0 -0
- /package/skills/review-artifact/references/{reviewer-prompt.md → independent-artifact-reviewer-contract.md} +0 -0
|
@@ -3,17 +3,6 @@ import { appendFile, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
|
3
3
|
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
4
4
|
import { promisify } from "node:util";
|
|
5
5
|
|
|
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
6
|
import {
|
|
18
7
|
handleModeCommand,
|
|
19
8
|
readCapabilityState,
|
|
@@ -30,15 +19,22 @@ import {
|
|
|
30
19
|
type SettingsCommitResult,
|
|
31
20
|
type SettingsEntry,
|
|
32
21
|
type SettingsSessionResult,
|
|
22
|
+
type SettingsWizard,
|
|
23
|
+
type SettingsWizardStep,
|
|
33
24
|
} from "./settings-tui.js";
|
|
25
|
+
import { isPiFlowHost } from "../runtime/runtime-identity.js";
|
|
26
|
+
import type {
|
|
27
|
+
CognitiveRoutingCapabilityState,
|
|
28
|
+
CognitiveRoutingProfile,
|
|
29
|
+
CognitiveRoutingProfileName,
|
|
30
|
+
CognitiveRoutingThinkingLevel,
|
|
31
|
+
} from "../cognitive-routing/types.js";
|
|
34
32
|
|
|
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
33
|
const DEFAULT_FREEFLOW_ENABLED = true;
|
|
40
34
|
const DEFAULT_INTERACTION_CONTRACT_ENABLED = true;
|
|
41
35
|
const DEFAULT_SKILLS_ENABLED = true;
|
|
36
|
+
const DEFAULT_CONTEXT_VIRTUALIZATION_ENABLED = false;
|
|
37
|
+
const DEFAULT_CONVERSATION_HISTORY_ENABLED = false;
|
|
42
38
|
const MODE_VALUES = ["conversation", "workflow", "strict-workflow"];
|
|
43
39
|
const MODE_LABELS = {
|
|
44
40
|
default: "Use configured default",
|
|
@@ -58,6 +54,7 @@ type ConfigScope = "session" | "local" | "repository";
|
|
|
58
54
|
type ConfigSource = "session" | "local" | "repository" | "builtin";
|
|
59
55
|
|
|
60
56
|
type SettingKind = "boolean" | "enum" | "integer" | "string" | "list" | "json" | "group";
|
|
57
|
+
type SettingsValue = boolean | number | string | null | SettingsValue[] | { [key: string]: SettingsValue };
|
|
61
58
|
|
|
62
59
|
type SettingsItem = {
|
|
63
60
|
id: string;
|
|
@@ -71,8 +68,10 @@ type SettingsItem = {
|
|
|
71
68
|
valueLabels?: Record<string, string>;
|
|
72
69
|
valueDescriptions?: Record<string, string>;
|
|
73
70
|
inactive?: boolean;
|
|
71
|
+
runtimeInactive?: boolean;
|
|
74
72
|
displaySuffix?: string;
|
|
75
73
|
children?: SettingsItem[];
|
|
74
|
+
wizard?: () => SettingsWizard;
|
|
76
75
|
parse?: (text: string) => unknown;
|
|
77
76
|
format?: (value: unknown) => string;
|
|
78
77
|
configScope?: ConfigScope;
|
|
@@ -85,6 +84,18 @@ type SettingsItem = {
|
|
|
85
84
|
transient?: boolean;
|
|
86
85
|
};
|
|
87
86
|
|
|
87
|
+
type CognitiveRoutingSettingsController = {
|
|
88
|
+
state(): { effective: boolean; controlMode: string; activeProfile?: string };
|
|
89
|
+
setManualProfile(profile: "standard" | "reasoning", mechanism?: string): Promise<{ status: string; reason?: string }>;
|
|
90
|
+
setAutomaticControl(mechanism?: string): Promise<{ status: string; reason?: string }>;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
type AfterChangeOptions = {
|
|
94
|
+
reconcileCognitiveRouting?: boolean;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
type AfterChange = (changed: boolean, options?: AfterChangeOptions) => Promise<void> | void;
|
|
98
|
+
|
|
88
99
|
type OpenSettingsOptions = {
|
|
89
100
|
title: string;
|
|
90
101
|
items: SettingsItem[];
|
|
@@ -106,13 +117,13 @@ function cloneJson<T>(value: T): T {
|
|
|
106
117
|
}
|
|
107
118
|
}
|
|
108
119
|
|
|
109
|
-
function getPath(source: unknown, path: string[]):
|
|
110
|
-
let current = source;
|
|
120
|
+
function getPath(source: unknown, path: string[]): SettingsValue | undefined {
|
|
121
|
+
let current: unknown = source;
|
|
111
122
|
for (const key of path) {
|
|
112
123
|
if (!isRecord(current)) return undefined;
|
|
113
|
-
current = current[key];
|
|
124
|
+
current = current[key] as SettingsValue | undefined;
|
|
114
125
|
}
|
|
115
|
-
return current;
|
|
126
|
+
return current as SettingsValue | undefined;
|
|
116
127
|
}
|
|
117
128
|
|
|
118
129
|
function setPath(target: Record<string, unknown>, path: string[], value: unknown) {
|
|
@@ -170,318 +181,29 @@ function setConfigValue(config: Record<string, unknown>, item: SettingsItem, val
|
|
|
170
181
|
setPath(config, item.path, value);
|
|
171
182
|
}
|
|
172
183
|
|
|
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
184
|
function booleanValue(value: unknown): string {
|
|
212
185
|
return value === true ? "enabled" : "disabled";
|
|
213
186
|
}
|
|
214
187
|
|
|
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 {
|
|
188
|
+
function configValueForChoice(item: SettingsItem, value: unknown): SettingsValue | undefined {
|
|
472
189
|
const key = String(value);
|
|
473
190
|
if (item.configValues && Object.hasOwn(item.configValues, key)) {
|
|
474
|
-
return item.configValues[key];
|
|
191
|
+
return item.configValues[key] as SettingsValue | undefined;
|
|
475
192
|
}
|
|
476
|
-
return value;
|
|
193
|
+
return value as SettingsValue | undefined;
|
|
477
194
|
}
|
|
478
195
|
|
|
479
|
-
function effectiveItemValue(item: SettingsItem):
|
|
480
|
-
return item.effectiveValue
|
|
196
|
+
function effectiveItemValue(item: SettingsItem): SettingsValue | undefined {
|
|
197
|
+
return (item.effectiveValue === undefined ? item.value : item.effectiveValue) as SettingsValue | undefined;
|
|
481
198
|
}
|
|
482
199
|
|
|
483
200
|
function formatCoreValue(value: unknown): string {
|
|
484
|
-
|
|
201
|
+
if (typeof value === "boolean") return booleanValue(value);
|
|
202
|
+
if (isRecord(value) && typeof value.provider === "string" && typeof value.model === "string") {
|
|
203
|
+
const effort = typeof value.thinkingLevel === "string" ? ` · ${value.thinkingLevel}` : "";
|
|
204
|
+
return `${value.provider}/${value.model}${effort}`;
|
|
205
|
+
}
|
|
206
|
+
return String(value ?? "");
|
|
485
207
|
}
|
|
486
208
|
|
|
487
209
|
function coreDisplaySuffix(item: SettingsItem, inactive = false): string {
|
|
@@ -669,14 +391,19 @@ function resolveSettingsCoreView(
|
|
|
669
391
|
const fallbackCore = {
|
|
670
392
|
enabled: getPath(rawConfig, ["enabled"]) !== false,
|
|
671
393
|
interactionContract: getPath(rawConfig, ["interactionContract"]) !== false,
|
|
394
|
+
contextVirtualization: getPath(rawConfig, ["contextVirtualization"]) === true,
|
|
395
|
+
conversationHistory: getPath(rawConfig, ["conversationHistory"]) === true,
|
|
672
396
|
skills: { enabled: getPath(repositorySkills, ["enabled"]) !== false },
|
|
673
397
|
defaultMode: validModeOrUndefined(rawConfig.defaultMode) ?? "workflow",
|
|
674
398
|
};
|
|
675
399
|
const fallbackSources = {
|
|
676
400
|
enabled: typeof getPath(rawConfig, ["enabled"]) === "boolean" ? "repository" : "builtin",
|
|
677
401
|
interactionContract: typeof getPath(rawConfig, ["interactionContract"]) === "boolean" ? "repository" : "builtin",
|
|
402
|
+
contextVirtualization:
|
|
403
|
+
typeof getPath(rawConfig, ["contextVirtualization"]) === "boolean" ? "repository" : "builtin",
|
|
404
|
+
conversationHistory: typeof getPath(rawConfig, ["conversationHistory"]) === "boolean" ? "repository" : "builtin",
|
|
678
405
|
skillsEnabled: typeof getPath(repositorySkills, ["enabled"]) === "boolean" ? "repository" : "builtin",
|
|
679
|
-
defaultMode: validModeOrUndefined(rawConfig.defaultMode)
|
|
406
|
+
defaultMode: validModeOrUndefined(rawConfig.defaultMode) === undefined ? "builtin" : "repository",
|
|
680
407
|
} as const;
|
|
681
408
|
return {
|
|
682
409
|
localConfig,
|
|
@@ -684,21 +411,19 @@ function resolveSettingsCoreView(
|
|
|
684
411
|
sources: (layers?.sources ?? fallbackSources) as {
|
|
685
412
|
enabled: ConfigSource;
|
|
686
413
|
interactionContract: ConfigSource;
|
|
414
|
+
contextVirtualization: ConfigSource;
|
|
415
|
+
conversationHistory: ConfigSource;
|
|
687
416
|
skillsEnabled: ConfigSource;
|
|
688
417
|
defaultMode: ConfigSource;
|
|
689
418
|
},
|
|
690
419
|
};
|
|
691
420
|
}
|
|
692
421
|
|
|
693
|
-
function groupEnabled(items: SettingsItem[], id: string): boolean {
|
|
694
|
-
return items.find((item) => item.id === id)?.value === true;
|
|
695
|
-
}
|
|
696
|
-
|
|
697
422
|
function createSessionBooleanItem(options: {
|
|
698
423
|
id: string;
|
|
699
424
|
label: string;
|
|
700
425
|
description: string;
|
|
701
|
-
key: "enabled" | "interactionContract" | "skillsEnabled";
|
|
426
|
+
key: "enabled" | "interactionContract" | "skillsEnabled" | "contextVirtualization" | "conversationHistory";
|
|
702
427
|
inheritedValue: boolean;
|
|
703
428
|
inheritedSource: ConfigSource;
|
|
704
429
|
effectiveValue: boolean;
|
|
@@ -745,17 +470,22 @@ function createSessionBooleanItem(options: {
|
|
|
745
470
|
function sessionFreeflowItems(
|
|
746
471
|
state: Awaited<ReturnType<typeof readCapabilityState>>,
|
|
747
472
|
modeState: Awaited<ReturnType<typeof readModeState>>,
|
|
473
|
+
cognitiveRoutingController?: CognitiveRoutingSettingsController,
|
|
748
474
|
): SettingsItem[] {
|
|
749
475
|
const sessionOverrides = state.sessionOverrides as Record<string, boolean>;
|
|
750
476
|
const configured = state.configuredCoreConfig;
|
|
751
477
|
const configuredSources = state.configuredSources as {
|
|
752
478
|
enabled: ConfigSource;
|
|
753
479
|
interactionContract: ConfigSource;
|
|
480
|
+
contextVirtualization: ConfigSource;
|
|
481
|
+
conversationHistory: ConfigSource;
|
|
754
482
|
skillsEnabled: ConfigSource;
|
|
755
483
|
};
|
|
756
484
|
const effectiveSources = state.configSources as {
|
|
757
485
|
enabled: ConfigSource;
|
|
758
486
|
interactionContract: ConfigSource;
|
|
487
|
+
contextVirtualization: ConfigSource;
|
|
488
|
+
conversationHistory: ConfigSource;
|
|
759
489
|
skillsEnabled: ConfigSource;
|
|
760
490
|
};
|
|
761
491
|
|
|
@@ -792,11 +522,35 @@ function sessionFreeflowItems(
|
|
|
792
522
|
effectiveSource: effectiveSources.skillsEnabled,
|
|
793
523
|
sessionOverrides,
|
|
794
524
|
});
|
|
525
|
+
const contextVirtualizationItem = createSessionBooleanItem({
|
|
526
|
+
id: "freeflow.contextVirtualization",
|
|
527
|
+
label: "Context Virtualization",
|
|
528
|
+
description: "Temporary Context Virtualization override for this Pi session.",
|
|
529
|
+
key: "contextVirtualization",
|
|
530
|
+
inheritedValue: configured.contextVirtualization,
|
|
531
|
+
inheritedSource: configuredSources.contextVirtualization,
|
|
532
|
+
effectiveValue: state.contextVirtualization.enabled,
|
|
533
|
+
effectiveSource: effectiveSources.contextVirtualization,
|
|
534
|
+
sessionOverrides,
|
|
535
|
+
});
|
|
536
|
+
const conversationHistoryItem = createSessionBooleanItem({
|
|
537
|
+
id: "freeflow.conversationHistory",
|
|
538
|
+
label: "Conversation History",
|
|
539
|
+
description: "Temporary Conversation History override for this Pi session.",
|
|
540
|
+
key: "conversationHistory",
|
|
541
|
+
inheritedValue: configured.conversationHistory,
|
|
542
|
+
inheritedSource: configuredSources.conversationHistory,
|
|
543
|
+
effectiveValue: state.conversationHistory.enabled,
|
|
544
|
+
effectiveSource: effectiveSources.conversationHistory,
|
|
545
|
+
sessionOverrides,
|
|
546
|
+
});
|
|
795
547
|
|
|
796
548
|
const freeflowInactive = !state.enabled;
|
|
797
549
|
const skillsEnabled = state.skills.enabled;
|
|
798
550
|
interactionItem.inactive = freeflowInactive;
|
|
799
551
|
skillsItem.inactive = freeflowInactive;
|
|
552
|
+
contextVirtualizationItem.inactive = freeflowInactive || !skillsEnabled;
|
|
553
|
+
conversationHistoryItem.inactive = freeflowInactive || !skillsEnabled;
|
|
800
554
|
|
|
801
555
|
const sessionMode = modeState.currentMode ?? "default";
|
|
802
556
|
const sessionModeItem: SettingsItem = {
|
|
@@ -825,15 +579,48 @@ function sessionFreeflowItems(
|
|
|
825
579
|
inheritedSource: modeState.defaultModeSource,
|
|
826
580
|
};
|
|
827
581
|
|
|
582
|
+
const cognitiveRoutingState = cognitiveRoutingController?.state();
|
|
583
|
+
const cognitiveRoutingProfile =
|
|
584
|
+
cognitiveRoutingState?.controlMode === "manual-standard"
|
|
585
|
+
? "standard"
|
|
586
|
+
: cognitiveRoutingState?.controlMode === "manual-reasoning"
|
|
587
|
+
? "reasoning"
|
|
588
|
+
: "auto";
|
|
589
|
+
const cognitiveRoutingItem: SettingsItem | undefined = state.cognitiveRouting
|
|
590
|
+
? {
|
|
591
|
+
id: "freeflow.cognitiveRouting.profile",
|
|
592
|
+
label: "Cognitive Routing",
|
|
593
|
+
description: "Hold a profile manually or release the hold for automatic model control.",
|
|
594
|
+
kind: "enum",
|
|
595
|
+
value: cognitiveRoutingProfile,
|
|
596
|
+
values: ["auto", "standard", "reasoning"],
|
|
597
|
+
valueLabels: {
|
|
598
|
+
auto: "automatic",
|
|
599
|
+
standard: "manual · standard",
|
|
600
|
+
reasoning: "manual · reasoning",
|
|
601
|
+
},
|
|
602
|
+
valueDescriptions: {
|
|
603
|
+
auto: "Release the manual hold without forcing a model transition.",
|
|
604
|
+
standard: "Hold the standard profile until /freeflow profile auto.",
|
|
605
|
+
reasoning: "Hold the reasoning profile until /freeflow profile auto.",
|
|
606
|
+
},
|
|
607
|
+
inactive: !state.cognitiveRouting.effective || cognitiveRoutingController === undefined,
|
|
608
|
+
runtimeInactive: state.cognitiveRouting.blockingReason?.code === "runtime_disabled",
|
|
609
|
+
displaySuffix: cognitiveRoutingState?.effective ? cognitiveRoutingProfile : "unavailable",
|
|
610
|
+
}
|
|
611
|
+
: undefined;
|
|
612
|
+
|
|
828
613
|
return [
|
|
829
614
|
freeflowItem,
|
|
830
615
|
interactionItem,
|
|
831
616
|
skillsItem,
|
|
832
617
|
sessionModeItem,
|
|
618
|
+
...(cognitiveRoutingItem ? [cognitiveRoutingItem] : []),
|
|
833
619
|
{
|
|
834
620
|
id: "freeflow.session.reset",
|
|
835
621
|
label: "Reset session overrides",
|
|
836
|
-
description:
|
|
622
|
+
description:
|
|
623
|
+
"Clear Freeflow, Interaction Contract, Skills, Context Virtualization, Conversation History, and mode overrides for this Pi session.",
|
|
837
624
|
kind: "enum",
|
|
838
625
|
value: "available",
|
|
839
626
|
values: ["reset"],
|
|
@@ -842,15 +629,359 @@ function sessionFreeflowItems(
|
|
|
842
629
|
format: () => "available",
|
|
843
630
|
transient: true,
|
|
844
631
|
},
|
|
632
|
+
{
|
|
633
|
+
id: "freeflow.context",
|
|
634
|
+
label: "Freeflow Context",
|
|
635
|
+
description: "Choose which context projection and conversation-history operations are available to the model.",
|
|
636
|
+
kind: "group",
|
|
637
|
+
value: contextVirtualizationItem.effectiveValue === true || conversationHistoryItem.effectiveValue === true,
|
|
638
|
+
displaySuffix: `${[contextVirtualizationItem, conversationHistoryItem].filter((item) => item.effectiveValue === true).length}/2 enabled`,
|
|
639
|
+
children: [contextVirtualizationItem, conversationHistoryItem],
|
|
640
|
+
},
|
|
845
641
|
];
|
|
846
642
|
}
|
|
847
643
|
|
|
644
|
+
const COGNITIVE_ROUTING_THINKING_LEVELS: CognitiveRoutingThinkingLevel[] = [
|
|
645
|
+
"off",
|
|
646
|
+
"minimal",
|
|
647
|
+
"low",
|
|
648
|
+
"medium",
|
|
649
|
+
"high",
|
|
650
|
+
"xhigh",
|
|
651
|
+
"max",
|
|
652
|
+
];
|
|
653
|
+
|
|
654
|
+
const COGNITIVE_ROUTING_EFFORT_DESCRIPTIONS: Record<CognitiveRoutingThinkingLevel, string> = {
|
|
655
|
+
off: "No reasoning",
|
|
656
|
+
minimal: "Very brief reasoning",
|
|
657
|
+
low: "Light reasoning",
|
|
658
|
+
medium: "Moderate reasoning",
|
|
659
|
+
high: "Deep reasoning",
|
|
660
|
+
xhigh: "Extra-high reasoning",
|
|
661
|
+
max: "Maximum reasoning",
|
|
662
|
+
};
|
|
663
|
+
|
|
664
|
+
type CognitiveRoutingModelOption = {
|
|
665
|
+
key: string;
|
|
666
|
+
provider: string;
|
|
667
|
+
model: string;
|
|
668
|
+
label: string;
|
|
669
|
+
description: string;
|
|
670
|
+
thinkingLevels: CognitiveRoutingThinkingLevel[];
|
|
671
|
+
};
|
|
672
|
+
|
|
673
|
+
function cognitiveRoutingModelKey(provider: string, model: string): string {
|
|
674
|
+
return `${provider}/${model}`;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
function isCognitiveRoutingProfile(value: unknown): value is CognitiveRoutingProfile {
|
|
678
|
+
return (
|
|
679
|
+
isRecord(value) &&
|
|
680
|
+
typeof value.provider === "string" &&
|
|
681
|
+
typeof value.model === "string" &&
|
|
682
|
+
typeof value.thinkingLevel === "string" &&
|
|
683
|
+
COGNITIVE_ROUTING_THINKING_LEVELS.includes(value.thinkingLevel as CognitiveRoutingThinkingLevel)
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
function cognitiveRoutingThinkingLevels(model: any, registry: any): CognitiveRoutingThinkingLevel[] {
|
|
688
|
+
if (typeof registry?.clampThinkingLevel !== "function") {
|
|
689
|
+
return model?.reasoning === true ? [...COGNITIVE_ROUTING_THINKING_LEVELS] : ["off"];
|
|
690
|
+
}
|
|
691
|
+
return COGNITIVE_ROUTING_THINKING_LEVELS.filter((level) => {
|
|
692
|
+
try {
|
|
693
|
+
return registry.clampThinkingLevel(model, level) === level;
|
|
694
|
+
} catch {
|
|
695
|
+
return false;
|
|
696
|
+
}
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
function cognitiveRoutingModelOptions(ctx: any): CognitiveRoutingModelOption[] {
|
|
701
|
+
const registry = ctx?.modelRegistry;
|
|
702
|
+
if (typeof registry?.getAvailable !== "function") return [];
|
|
703
|
+
let models: any[];
|
|
704
|
+
try {
|
|
705
|
+
models = registry.getAvailable();
|
|
706
|
+
} catch {
|
|
707
|
+
return [];
|
|
708
|
+
}
|
|
709
|
+
if (!Array.isArray(models)) return [];
|
|
710
|
+
|
|
711
|
+
return models
|
|
712
|
+
.filter((model) => typeof model?.provider === "string" && typeof model?.id === "string")
|
|
713
|
+
.map((model) => {
|
|
714
|
+
const thinkingLevels = cognitiveRoutingThinkingLevels(model, registry);
|
|
715
|
+
const provider = model.provider as string;
|
|
716
|
+
const modelId = model.id as string;
|
|
717
|
+
const key = cognitiveRoutingModelKey(provider, modelId);
|
|
718
|
+
const displayName = typeof model.name === "string" && model.name !== modelId ? model.name : undefined;
|
|
719
|
+
return {
|
|
720
|
+
key,
|
|
721
|
+
provider,
|
|
722
|
+
model: modelId,
|
|
723
|
+
label: key,
|
|
724
|
+
description: displayName ? `${displayName} · ${thinkingLevels.join(", ")}` : thinkingLevels.join(", "),
|
|
725
|
+
thinkingLevels,
|
|
726
|
+
};
|
|
727
|
+
})
|
|
728
|
+
.filter((model) => model.thinkingLevels.length > 0)
|
|
729
|
+
.sort((a, b) => a.label.localeCompare(b.label));
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
function cognitiveRoutingProfileDisplay(value: unknown): string {
|
|
733
|
+
return isCognitiveRoutingProfile(value) ? formatCoreValue(value) : "not configured";
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
function cognitiveRoutingSettingsSource(source: unknown): ConfigSource {
|
|
737
|
+
if (source === "repository") return "repository";
|
|
738
|
+
if (source === "personal" || source === "local") return "local";
|
|
739
|
+
return "builtin";
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
function cognitiveRoutingProfileDisplaySuffix(source: unknown, scope: ConfigScope): string | undefined {
|
|
743
|
+
if (!source) return undefined;
|
|
744
|
+
if (source === "personal" && scope === "repository") return "(effective personal)";
|
|
745
|
+
return `(${source})`;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
function cognitiveRoutingConfirmStep(summary: string): SettingsWizardStep {
|
|
749
|
+
return {
|
|
750
|
+
title: "Confirm preset",
|
|
751
|
+
choices: [
|
|
752
|
+
{
|
|
753
|
+
key: "__save__",
|
|
754
|
+
value: "save",
|
|
755
|
+
label: `Save ${summary}`,
|
|
756
|
+
description: "Write the complete profile atomically.",
|
|
757
|
+
},
|
|
758
|
+
{ key: "__cancel__", value: "cancel", label: "Cancel", description: "Leave the previous preset unchanged." },
|
|
759
|
+
],
|
|
760
|
+
selectedKey: "__save__",
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
function createCognitiveRoutingProfileWizard(
|
|
765
|
+
currentValue: unknown,
|
|
766
|
+
models: CognitiveRoutingModelOption[],
|
|
767
|
+
allowInherit: boolean,
|
|
768
|
+
): SettingsWizard {
|
|
769
|
+
const currentProfile = isCognitiveRoutingProfile(currentValue) ? currentValue : undefined;
|
|
770
|
+
const currentModelKey = currentProfile
|
|
771
|
+
? cognitiveRoutingModelKey(currentProfile.provider, currentProfile.model)
|
|
772
|
+
: undefined;
|
|
773
|
+
const firstChoices = [
|
|
774
|
+
...(allowInherit
|
|
775
|
+
? [
|
|
776
|
+
{
|
|
777
|
+
key: LOCAL_INHERIT,
|
|
778
|
+
value: LOCAL_INHERIT,
|
|
779
|
+
label: "Inherit repository preset",
|
|
780
|
+
description: "Remove the personal override without changing the repository preset.",
|
|
781
|
+
},
|
|
782
|
+
]
|
|
783
|
+
: []),
|
|
784
|
+
...models.map((model) => ({
|
|
785
|
+
key: model.key,
|
|
786
|
+
value: model,
|
|
787
|
+
label: model.label,
|
|
788
|
+
description: model.description,
|
|
789
|
+
})),
|
|
790
|
+
];
|
|
791
|
+
if (firstChoices.length === 0) {
|
|
792
|
+
firstChoices.push({
|
|
793
|
+
key: "__cancel__",
|
|
794
|
+
value: "cancel",
|
|
795
|
+
label: "No authenticated models available",
|
|
796
|
+
description: "Authenticate a provider before configuring this preset.",
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
return {
|
|
801
|
+
firstStep: () => ({
|
|
802
|
+
title: "Choose model",
|
|
803
|
+
choices: firstChoices,
|
|
804
|
+
selectedKey:
|
|
805
|
+
currentValue === LOCAL_INHERIT
|
|
806
|
+
? LOCAL_INHERIT
|
|
807
|
+
: firstChoices.some((choice) => choice.key === currentModelKey)
|
|
808
|
+
? currentModelKey
|
|
809
|
+
: firstChoices[0]?.key,
|
|
810
|
+
}),
|
|
811
|
+
nextStep: (selectedValues) => {
|
|
812
|
+
if (selectedValues.length === 1) {
|
|
813
|
+
const selected = selectedValues[0];
|
|
814
|
+
if (selected === LOCAL_INHERIT) {
|
|
815
|
+
return cognitiveRoutingConfirmStep("inherit repository preset");
|
|
816
|
+
}
|
|
817
|
+
const model = selected as CognitiveRoutingModelOption;
|
|
818
|
+
if (!model?.key || model.thinkingLevels.length === 0) return undefined;
|
|
819
|
+
const currentEffort =
|
|
820
|
+
currentProfile && currentModelKey === model.key ? currentProfile.thinkingLevel : undefined;
|
|
821
|
+
return {
|
|
822
|
+
title: `Choose effort · ${model.label}`,
|
|
823
|
+
choices: model.thinkingLevels.map((level) => ({
|
|
824
|
+
key: level,
|
|
825
|
+
value: level,
|
|
826
|
+
label: level,
|
|
827
|
+
description: COGNITIVE_ROUTING_EFFORT_DESCRIPTIONS[level],
|
|
828
|
+
})),
|
|
829
|
+
selectedKey:
|
|
830
|
+
currentEffort && model.thinkingLevels.includes(currentEffort) ? currentEffort : model.thinkingLevels[0],
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
if (selectedValues.length === 2) {
|
|
834
|
+
const model = selectedValues[0] as CognitiveRoutingModelOption;
|
|
835
|
+
const effort = selectedValues[1] as CognitiveRoutingThinkingLevel;
|
|
836
|
+
return cognitiveRoutingConfirmStep(`${model.label} · ${effort}`);
|
|
837
|
+
}
|
|
838
|
+
return undefined;
|
|
839
|
+
},
|
|
840
|
+
valueFromSelections: (selectedValues) => {
|
|
841
|
+
if (selectedValues[0] === LOCAL_INHERIT) return undefined;
|
|
842
|
+
const model = selectedValues[0] as CognitiveRoutingModelOption;
|
|
843
|
+
return {
|
|
844
|
+
provider: model.provider,
|
|
845
|
+
model: model.model,
|
|
846
|
+
thinkingLevel: selectedValues[1] as CognitiveRoutingThinkingLevel,
|
|
847
|
+
} satisfies CognitiveRoutingProfile;
|
|
848
|
+
},
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
function cognitiveRoutingProfileItem(options: {
|
|
853
|
+
name: CognitiveRoutingProfileName;
|
|
854
|
+
scope: ConfigScope;
|
|
855
|
+
rawConfig: Record<string, unknown>;
|
|
856
|
+
localConfig: Record<string, unknown>;
|
|
857
|
+
capabilityState: CognitiveRoutingCapabilityState | undefined;
|
|
858
|
+
ctx: any;
|
|
859
|
+
}): SettingsItem {
|
|
860
|
+
const path = ["cognitiveRouting", "profiles", options.name];
|
|
861
|
+
const repositoryProfile = getPath(options.rawConfig, path);
|
|
862
|
+
const localProfile = getPath(options.localConfig, path);
|
|
863
|
+
const effectiveProfile = options.capabilityState?.profiles?.[options.name] ?? repositoryProfile;
|
|
864
|
+
const localValue = isCognitiveRoutingProfile(localProfile) ? localProfile : LOCAL_INHERIT;
|
|
865
|
+
const value = options.scope === "local" ? localValue : repositoryProfile;
|
|
866
|
+
const source =
|
|
867
|
+
options.capabilityState?.profileSources?.[options.name] ??
|
|
868
|
+
(isCognitiveRoutingProfile(repositoryProfile) ? "repository" : undefined);
|
|
869
|
+
const models = cognitiveRoutingModelOptions(options.ctx);
|
|
870
|
+
const inactive = options.scope === "local" && models.length === 0 && localValue !== LOCAL_INHERIT;
|
|
871
|
+
|
|
872
|
+
return {
|
|
873
|
+
id: `freeflow.cognitiveRouting.${options.name}`,
|
|
874
|
+
label: `${options.name.charAt(0).toUpperCase()}${options.name.slice(1)} preset`,
|
|
875
|
+
description:
|
|
876
|
+
"Choose an authenticated available model and one effort supported by that model. Confirming writes the complete preset; cancel leaves it unchanged.",
|
|
877
|
+
path,
|
|
878
|
+
kind: "string",
|
|
879
|
+
value,
|
|
880
|
+
format: cognitiveRoutingProfileDisplay,
|
|
881
|
+
configScope: options.scope,
|
|
882
|
+
effectiveValue: effectiveProfile,
|
|
883
|
+
effectiveSource: cognitiveRoutingSettingsSource(source),
|
|
884
|
+
inheritedValue: repositoryProfile,
|
|
885
|
+
inheritedSource: "repository",
|
|
886
|
+
inactive,
|
|
887
|
+
displaySuffix: cognitiveRoutingProfileDisplaySuffix(source, options.scope),
|
|
888
|
+
wizard: () => createCognitiveRoutingProfileWizard(value, models, options.scope === "local"),
|
|
889
|
+
};
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
type CognitiveRoutingSessionStartSetting = "control" | "profile";
|
|
893
|
+
|
|
894
|
+
function cognitiveRoutingSessionStartItem(options: {
|
|
895
|
+
setting: CognitiveRoutingSessionStartSetting;
|
|
896
|
+
scope: ConfigScope;
|
|
897
|
+
rawConfig: Record<string, unknown>;
|
|
898
|
+
localConfig: Record<string, unknown>;
|
|
899
|
+
capabilityState: CognitiveRoutingCapabilityState | undefined;
|
|
900
|
+
}): SettingsItem {
|
|
901
|
+
const path = ["cognitiveRouting", "sessionStart", options.setting];
|
|
902
|
+
const defaultValue = options.setting === "control" ? "automatic" : "standard";
|
|
903
|
+
const values = options.setting === "control" ? ["automatic", "manual"] : ["standard", "reasoning"];
|
|
904
|
+
const labels = Object.fromEntries(values.map((value) => [value, value]));
|
|
905
|
+
const descriptions =
|
|
906
|
+
options.setting === "control"
|
|
907
|
+
? {
|
|
908
|
+
automatic: "Let Cognitive Routing manage profile transitions for the new session.",
|
|
909
|
+
manual: "Hold the selected profile for the new session until the user changes it.",
|
|
910
|
+
}
|
|
911
|
+
: {
|
|
912
|
+
standard: "Start a new session with the configured Standard preset.",
|
|
913
|
+
reasoning: "Start a new session with the configured Reasoning preset.",
|
|
914
|
+
};
|
|
915
|
+
const repositoryValue = getPath(options.rawConfig, path);
|
|
916
|
+
const localValue = getPath(options.localConfig, path);
|
|
917
|
+
const isValidValue = (value: unknown): value is string => values.includes(value as string);
|
|
918
|
+
const repositorySetting = isValidValue(repositoryValue) ? repositoryValue : undefined;
|
|
919
|
+
const localSetting = isValidValue(localValue) ? localValue : undefined;
|
|
920
|
+
const inheritedValue = repositorySetting ?? defaultValue;
|
|
921
|
+
const inheritedSource: ConfigSource = repositorySetting ? "repository" : "builtin";
|
|
922
|
+
const configuredSource =
|
|
923
|
+
options.capabilityState?.sessionStartSources?.[options.setting] ?? (repositorySetting ? "repository" : undefined);
|
|
924
|
+
const effectiveValue = options.capabilityState?.sessionStart?.[options.setting] ?? inheritedValue;
|
|
925
|
+
const effectiveSource = cognitiveRoutingSettingsSource(configuredSource);
|
|
926
|
+
const label = options.setting === "control" ? "Session start control" : "Session start profile";
|
|
927
|
+
const description =
|
|
928
|
+
options.setting === "control"
|
|
929
|
+
? "Choose automatic or manual control for new sessions only; changing this does not alter the active session."
|
|
930
|
+
: "Choose the initial profile for new sessions only; changing this does not alter the active session.";
|
|
931
|
+
|
|
932
|
+
const item: SettingsItem =
|
|
933
|
+
options.scope === "local"
|
|
934
|
+
? {
|
|
935
|
+
id: `freeflow.cognitiveRouting.sessionStart.${options.setting}`,
|
|
936
|
+
label,
|
|
937
|
+
description: `${description} Choose inherit to use the repository value; use /freeflow settings repo to edit shared defaults.`,
|
|
938
|
+
path,
|
|
939
|
+
kind: "enum",
|
|
940
|
+
value: localSetting ?? LOCAL_INHERIT,
|
|
941
|
+
values: [LOCAL_INHERIT, ...values],
|
|
942
|
+
valueLabels: { inherit: "Inherit repository", ...labels },
|
|
943
|
+
valueDescriptions: {
|
|
944
|
+
inherit: `Use ${inheritedValue} from ${inheritedSource}.`,
|
|
945
|
+
...descriptions,
|
|
946
|
+
},
|
|
947
|
+
format: (value) => String(value),
|
|
948
|
+
configScope: "local",
|
|
949
|
+
configValues: { inherit: undefined },
|
|
950
|
+
effectiveValue,
|
|
951
|
+
effectiveSource,
|
|
952
|
+
inheritedValue,
|
|
953
|
+
inheritedSource,
|
|
954
|
+
}
|
|
955
|
+
: {
|
|
956
|
+
id: `freeflow.cognitiveRouting.sessionStart.${options.setting}`,
|
|
957
|
+
label,
|
|
958
|
+
description: `${description} This edits shared .freeflow/config.json.`,
|
|
959
|
+
path,
|
|
960
|
+
kind: "enum",
|
|
961
|
+
value: inheritedValue,
|
|
962
|
+
values,
|
|
963
|
+
valueLabels: labels,
|
|
964
|
+
valueDescriptions: descriptions,
|
|
965
|
+
format: (value) => String(value),
|
|
966
|
+
defaultValue,
|
|
967
|
+
configScope: "repository",
|
|
968
|
+
effectiveValue,
|
|
969
|
+
effectiveSource,
|
|
970
|
+
localOverrideValue: localSetting,
|
|
971
|
+
};
|
|
972
|
+
item.displaySuffix = coreDisplaySuffix(item);
|
|
973
|
+
return item;
|
|
974
|
+
}
|
|
975
|
+
|
|
848
976
|
function freeflowItems(
|
|
849
977
|
rawConfig: Record<string, unknown>,
|
|
850
978
|
modeState?: Awaited<ReturnType<typeof readModeState>>,
|
|
851
979
|
options: {
|
|
852
980
|
scope?: ConfigScope;
|
|
853
981
|
layers?: Awaited<ReturnType<typeof readFreeflowConfigLayers>>;
|
|
982
|
+
cognitiveRouting?: CognitiveRoutingCapabilityState;
|
|
983
|
+
ctx?: any;
|
|
984
|
+
hostInfo?: unknown;
|
|
854
985
|
} = {},
|
|
855
986
|
): SettingsItem[] {
|
|
856
987
|
const scope = options.scope ?? "repository";
|
|
@@ -887,12 +1018,36 @@ function freeflowItems(
|
|
|
887
1018
|
localConfig,
|
|
888
1019
|
id: "freeflow.skills.enabled",
|
|
889
1020
|
label: "Skills",
|
|
890
|
-
description: "Expose Freeflow skills and
|
|
1021
|
+
description: "Expose Freeflow skills and discover Workflow before consequential or mutating work.",
|
|
891
1022
|
path: ["skills", "enabled"],
|
|
892
1023
|
effectiveValue: core.skills.enabled,
|
|
893
1024
|
effectiveSource: sources.skillsEnabled,
|
|
894
1025
|
defaultValue: DEFAULT_SKILLS_ENABLED,
|
|
895
1026
|
});
|
|
1027
|
+
const contextVirtualizationItem = createScopedBooleanItem({
|
|
1028
|
+
scope,
|
|
1029
|
+
rawConfig,
|
|
1030
|
+
localConfig,
|
|
1031
|
+
id: "freeflow.contextVirtualization",
|
|
1032
|
+
label: "Context Virtualization",
|
|
1033
|
+
description: "Let the model archive consumed tool results from future context while preserving session history.",
|
|
1034
|
+
path: ["contextVirtualization"],
|
|
1035
|
+
effectiveValue: core.contextVirtualization,
|
|
1036
|
+
effectiveSource: sources.contextVirtualization,
|
|
1037
|
+
defaultValue: DEFAULT_CONTEXT_VIRTUALIZATION_ENABLED,
|
|
1038
|
+
});
|
|
1039
|
+
const conversationHistoryItem = createScopedBooleanItem({
|
|
1040
|
+
scope,
|
|
1041
|
+
rawConfig,
|
|
1042
|
+
localConfig,
|
|
1043
|
+
id: "freeflow.conversationHistory",
|
|
1044
|
+
label: "Conversation History",
|
|
1045
|
+
description: "Let the model search and retrieve hidden conversation history on the active branch.",
|
|
1046
|
+
path: ["conversationHistory"],
|
|
1047
|
+
effectiveValue: core.conversationHistory,
|
|
1048
|
+
effectiveSource: sources.conversationHistory,
|
|
1049
|
+
defaultValue: DEFAULT_CONVERSATION_HISTORY_ENABLED,
|
|
1050
|
+
});
|
|
896
1051
|
|
|
897
1052
|
const repositoryModeValue = validModeOrUndefined(rawConfig.defaultMode);
|
|
898
1053
|
const repositoryDefaultMode = repositoryModeValue ?? "workflow";
|
|
@@ -911,6 +1066,8 @@ function freeflowItems(
|
|
|
911
1066
|
const skillsEnabled = core.skills.enabled;
|
|
912
1067
|
interactionItem.inactive = freeflowInactive;
|
|
913
1068
|
skillsItem.inactive = freeflowInactive;
|
|
1069
|
+
contextVirtualizationItem.inactive = freeflowInactive || !skillsEnabled;
|
|
1070
|
+
conversationHistoryItem.inactive = freeflowInactive || !skillsEnabled;
|
|
914
1071
|
defaultModeItem.inactive = freeflowInactive;
|
|
915
1072
|
defaultModeItem.displaySuffix = coreDisplaySuffix(defaultModeItem, freeflowInactive || !skillsEnabled);
|
|
916
1073
|
const sessionMode = modeState?.currentMode ?? "default";
|
|
@@ -939,8 +1096,72 @@ function freeflowItems(
|
|
|
939
1096
|
),
|
|
940
1097
|
};
|
|
941
1098
|
|
|
942
|
-
const
|
|
943
|
-
const
|
|
1099
|
+
const cognitiveRoutingState = options.cognitiveRouting;
|
|
1100
|
+
const cognitiveRoutingRuntimeDisabled = !isPiFlowHost(options.hostInfo);
|
|
1101
|
+
const cognitiveRoutingGroup = (() => {
|
|
1102
|
+
const cognitiveRoutingEnabledItem = createScopedBooleanItem({
|
|
1103
|
+
scope,
|
|
1104
|
+
rawConfig,
|
|
1105
|
+
localConfig,
|
|
1106
|
+
id: "freeflow.cognitiveRouting.enabled",
|
|
1107
|
+
label: "Enabled",
|
|
1108
|
+
description: cognitiveRoutingRuntimeDisabled
|
|
1109
|
+
? "Configured for PiFlow, but disabled in normal Pi."
|
|
1110
|
+
: "Allow Cognitive Routing to own the session model lease and switch the configured profiles.",
|
|
1111
|
+
path: ["cognitiveRouting", "enabled"],
|
|
1112
|
+
effectiveValue: cognitiveRoutingState?.enabled ?? false,
|
|
1113
|
+
effectiveSource: cognitiveRoutingSettingsSource(cognitiveRoutingState?.enabledSource),
|
|
1114
|
+
defaultValue: false,
|
|
1115
|
+
});
|
|
1116
|
+
cognitiveRoutingEnabledItem.inactive = freeflowInactive || !skillsEnabled || cognitiveRoutingRuntimeDisabled;
|
|
1117
|
+
cognitiveRoutingEnabledItem.runtimeInactive = cognitiveRoutingRuntimeDisabled;
|
|
1118
|
+
|
|
1119
|
+
const cognitiveRoutingProfiles = ["standard", "reasoning"].map((name) =>
|
|
1120
|
+
cognitiveRoutingProfileItem({
|
|
1121
|
+
name: name as CognitiveRoutingProfileName,
|
|
1122
|
+
scope,
|
|
1123
|
+
rawConfig,
|
|
1124
|
+
localConfig,
|
|
1125
|
+
capabilityState: cognitiveRoutingState,
|
|
1126
|
+
ctx: options.ctx,
|
|
1127
|
+
}),
|
|
1128
|
+
);
|
|
1129
|
+
const cognitiveRoutingSessionStart = (["control", "profile"] as CognitiveRoutingSessionStartSetting[]).map(
|
|
1130
|
+
(setting) =>
|
|
1131
|
+
cognitiveRoutingSessionStartItem({
|
|
1132
|
+
setting,
|
|
1133
|
+
scope,
|
|
1134
|
+
rawConfig,
|
|
1135
|
+
localConfig,
|
|
1136
|
+
capabilityState: cognitiveRoutingState,
|
|
1137
|
+
}),
|
|
1138
|
+
);
|
|
1139
|
+
for (const item of [...cognitiveRoutingProfiles, ...cognitiveRoutingSessionStart]) {
|
|
1140
|
+
item.inactive ||= freeflowInactive || !skillsEnabled || cognitiveRoutingRuntimeDisabled;
|
|
1141
|
+
item.runtimeInactive = cognitiveRoutingRuntimeDisabled;
|
|
1142
|
+
}
|
|
1143
|
+
const cognitiveRoutingStatus = cognitiveRoutingRuntimeDisabled
|
|
1144
|
+
? "disabled · PiFlow only"
|
|
1145
|
+
: cognitiveRoutingState
|
|
1146
|
+
? cognitiveRoutingState.effective
|
|
1147
|
+
? "active"
|
|
1148
|
+
: cognitiveRoutingState.blockingReason?.code === "profile_missing"
|
|
1149
|
+
? "not configured"
|
|
1150
|
+
: (cognitiveRoutingState.blockingReason?.code ?? "inactive")
|
|
1151
|
+
: "unavailable";
|
|
1152
|
+
return {
|
|
1153
|
+
id: "freeflow.cognitiveRouting",
|
|
1154
|
+
label: "Cognitive Routing",
|
|
1155
|
+
description: cognitiveRoutingRuntimeDisabled
|
|
1156
|
+
? "Cognitive Routing configuration is visible for inspection but can only run in PiFlow."
|
|
1157
|
+
: "Configure the automatic standard/reasoning profiles and choose whether Freeflow may manage model state for this repository.",
|
|
1158
|
+
kind: "group" as const,
|
|
1159
|
+
value: cognitiveRoutingRuntimeDisabled ? false : (cognitiveRoutingState?.enabled ?? false),
|
|
1160
|
+
inactive: freeflowInactive || !skillsEnabled,
|
|
1161
|
+
displaySuffix: cognitiveRoutingStatus,
|
|
1162
|
+
children: [cognitiveRoutingEnabledItem, ...cognitiveRoutingProfiles, ...cognitiveRoutingSessionStart],
|
|
1163
|
+
} satisfies SettingsItem;
|
|
1164
|
+
})();
|
|
944
1165
|
|
|
945
1166
|
return [
|
|
946
1167
|
freeflowItem,
|
|
@@ -948,155 +1169,25 @@ function freeflowItems(
|
|
|
948
1169
|
skillsItem,
|
|
949
1170
|
sessionModeItem,
|
|
950
1171
|
defaultModeItem,
|
|
1172
|
+
...(cognitiveRoutingGroup ? [cognitiveRoutingGroup] : []),
|
|
951
1173
|
{
|
|
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.",
|
|
1174
|
+
id: "freeflow.context",
|
|
1175
|
+
label: "Freeflow Context",
|
|
1176
|
+
description: "Choose which context projection and conversation-history operations are available to the model.",
|
|
956
1177
|
kind: "group",
|
|
957
|
-
value:
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
children: routerItems,
|
|
1178
|
+
value: contextVirtualizationItem.effectiveValue === true || conversationHistoryItem.effectiveValue === true,
|
|
1179
|
+
displaySuffix: `${[contextVirtualizationItem, conversationHistoryItem].filter((item) => item.effectiveValue === true).length}/2 enabled`,
|
|
1180
|
+
children: [contextVirtualizationItem, conversationHistoryItem],
|
|
961
1181
|
},
|
|
962
1182
|
];
|
|
963
1183
|
}
|
|
964
1184
|
|
|
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
1185
|
function pruneKnownDefaults(config: Record<string, unknown>) {
|
|
1018
1186
|
const defaultPaths: Array<{ path: string[]; value: unknown }> = [
|
|
1019
1187
|
{ path: ["enabled"], value: DEFAULT_FREEFLOW_ENABLED },
|
|
1020
|
-
{
|
|
1021
|
-
path: ["interactionContract"],
|
|
1022
|
-
value: DEFAULT_INTERACTION_CONTRACT_ENABLED,
|
|
1023
|
-
},
|
|
1188
|
+
{ path: ["interactionContract"], value: DEFAULT_INTERACTION_CONTRACT_ENABLED },
|
|
1024
1189
|
{ path: ["skills", "enabled"], value: DEFAULT_SKILLS_ENABLED },
|
|
1025
|
-
{ path: ["
|
|
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
|
-
},
|
|
1190
|
+
{ path: ["conversationHistory"], value: DEFAULT_CONVERSATION_HISTORY_ENABLED },
|
|
1100
1191
|
];
|
|
1101
1192
|
|
|
1102
1193
|
for (const item of defaultPaths) {
|
|
@@ -1104,19 +1195,6 @@ function pruneKnownDefaults(config: Record<string, unknown>) {
|
|
|
1104
1195
|
deletePath(config, item.path);
|
|
1105
1196
|
}
|
|
1106
1197
|
}
|
|
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
1198
|
}
|
|
1121
1199
|
|
|
1122
1200
|
async function ensureLocalConfigIgnored(cwd: string) {
|
|
@@ -1190,15 +1268,20 @@ async function updateConfig(
|
|
|
1190
1268
|
const current = await readFreeflowConfig(cwd);
|
|
1191
1269
|
const next = cloneJson(current) as Record<string, unknown>;
|
|
1192
1270
|
setConfigValue(next, item, value);
|
|
1193
|
-
migrateLegacyRouterConfig(next);
|
|
1194
1271
|
pruneKnownDefaults(next);
|
|
1195
1272
|
await mkdir(join(cwd, ".freeflow"), { recursive: true });
|
|
1196
1273
|
await writeFile(join(cwd, ".freeflow/config.json"), `${JSON.stringify(next, null, 2)}\n`, "utf8");
|
|
1197
1274
|
}
|
|
1198
1275
|
|
|
1276
|
+
function isCognitiveRoutingProfileItem(item: SettingsItem): boolean {
|
|
1277
|
+
return item.id === "freeflow.cognitiveRouting.standard" || item.id === "freeflow.cognitiveRouting.reasoning";
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1199
1280
|
function valueForDisplay(item: SettingsItem): string {
|
|
1200
1281
|
let value: string;
|
|
1201
|
-
if (item
|
|
1282
|
+
if (isCognitiveRoutingProfileItem(item) && item.format) {
|
|
1283
|
+
value = item.format(effectiveItemValue(item));
|
|
1284
|
+
} else if (item.configScope === "local" || item.configScope === "session") {
|
|
1202
1285
|
value = formatCoreValue(effectiveItemValue(item));
|
|
1203
1286
|
} else if (item.kind === "boolean") {
|
|
1204
1287
|
value = booleanValue(item.value);
|
|
@@ -1235,10 +1318,14 @@ function refreshSettingsDerivedState(items: SettingsItem[]) {
|
|
|
1235
1318
|
const freeflowInactive = freeflowItem ? effectiveItemValue(freeflowItem) !== true : false;
|
|
1236
1319
|
const skillsItem = findSettingsItem(items, "freeflow.skills.enabled");
|
|
1237
1320
|
const skillsEnabled = skillsItem ? effectiveItemValue(skillsItem) === true : true;
|
|
1238
|
-
const routerEnabled = findSettingsItem(items, "outputRouter.enabled")?.value === true;
|
|
1239
1321
|
const sessionModeItem = findSettingsItem(items, "freeflow.sessionMode");
|
|
1240
1322
|
const defaultModeItem = findSettingsItem(items, "freeflow.defaultMode");
|
|
1241
|
-
const
|
|
1323
|
+
const cognitiveRoutingGroup = findSettingsItem(items, "freeflow.cognitiveRouting");
|
|
1324
|
+
const cognitiveRoutingEnabledItem = findSettingsItem(items, "freeflow.cognitiveRouting.enabled");
|
|
1325
|
+
const cognitiveRoutingProfiles = [
|
|
1326
|
+
findSettingsItem(items, "freeflow.cognitiveRouting.standard"),
|
|
1327
|
+
findSettingsItem(items, "freeflow.cognitiveRouting.reasoning"),
|
|
1328
|
+
];
|
|
1242
1329
|
|
|
1243
1330
|
if (sessionModeItem) {
|
|
1244
1331
|
const defaultMode = String(
|
|
@@ -1258,29 +1345,59 @@ function refreshSettingsDerivedState(items: SettingsItem[]) {
|
|
|
1258
1345
|
};
|
|
1259
1346
|
}
|
|
1260
1347
|
|
|
1261
|
-
if (
|
|
1262
|
-
|
|
1263
|
-
|
|
1348
|
+
if (cognitiveRoutingGroup && cognitiveRoutingEnabledItem) {
|
|
1349
|
+
const enabled = effectiveItemValue(cognitiveRoutingEnabledItem) === true;
|
|
1350
|
+
const profilesConfigured = cognitiveRoutingProfiles.every((item) =>
|
|
1351
|
+
item ? isCognitiveRoutingProfile(effectiveItemValue(item)) : false,
|
|
1352
|
+
);
|
|
1353
|
+
cognitiveRoutingGroup.value = enabled;
|
|
1354
|
+
cognitiveRoutingGroup.displaySuffix = enabled
|
|
1355
|
+
? profilesConfigured
|
|
1356
|
+
? cognitiveRoutingGroup.displaySuffix === "active"
|
|
1357
|
+
? "active"
|
|
1358
|
+
: "configured"
|
|
1359
|
+
: "not configured"
|
|
1360
|
+
: "disabled";
|
|
1361
|
+
cognitiveRoutingGroup.inactive = freeflowInactive || !skillsEnabled;
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
const contextGroup = findSettingsItem(items, "freeflow.context");
|
|
1365
|
+
if (contextGroup?.children?.length) {
|
|
1366
|
+
const enabledCount = contextGroup.children.filter((item) => effectiveItemValue(item) === true).length;
|
|
1367
|
+
contextGroup.value = enabledCount > 0;
|
|
1368
|
+
contextGroup.displaySuffix = `${enabledCount}/${contextGroup.children.length} enabled`;
|
|
1264
1369
|
}
|
|
1265
1370
|
|
|
1266
1371
|
walkSettingsItems(items, (candidate) => {
|
|
1267
1372
|
if (candidate.id === "freeflow.session.reset") {
|
|
1268
1373
|
candidate.inactive = false;
|
|
1269
1374
|
} else if (candidate.configScope) {
|
|
1270
|
-
const inactive =
|
|
1375
|
+
const inactive =
|
|
1376
|
+
candidate.id === "freeflow.enabled"
|
|
1377
|
+
? false
|
|
1378
|
+
: candidate.runtimeInactive === true ||
|
|
1379
|
+
freeflowInactive ||
|
|
1380
|
+
(!skillsEnabled &&
|
|
1381
|
+
[
|
|
1382
|
+
"freeflow.cognitiveRouting",
|
|
1383
|
+
"freeflow.cognitiveRouting.enabled",
|
|
1384
|
+
"freeflow.cognitiveRouting.standard",
|
|
1385
|
+
"freeflow.cognitiveRouting.reasoning",
|
|
1386
|
+
"freeflow.cognitiveRouting.sessionStart.control",
|
|
1387
|
+
"freeflow.cognitiveRouting.sessionStart.profile",
|
|
1388
|
+
"freeflow.contextVirtualization",
|
|
1389
|
+
"freeflow.conversationHistory",
|
|
1390
|
+
].includes(candidate.id));
|
|
1271
1391
|
const displayInactive = candidate.id === "freeflow.defaultMode" ? freeflowInactive || !skillsEnabled : inactive;
|
|
1272
1392
|
candidate.inactive = inactive;
|
|
1273
1393
|
candidate.displaySuffix = coreDisplaySuffix(candidate, displayInactive);
|
|
1274
1394
|
} 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;
|
|
1395
|
+
candidate.inactive = candidate.runtimeInactive === true || freeflowInactive || !skillsEnabled;
|
|
1282
1396
|
} else {
|
|
1283
|
-
candidate.inactive =
|
|
1397
|
+
candidate.inactive =
|
|
1398
|
+
candidate.runtimeInactive === true ||
|
|
1399
|
+
freeflowInactive ||
|
|
1400
|
+
(!skillsEnabled && candidate.id === "freeflow.cognitiveRouting");
|
|
1284
1401
|
}
|
|
1285
1402
|
});
|
|
1286
1403
|
}
|
|
@@ -1327,6 +1444,7 @@ function settingsEntries(
|
|
|
1327
1444
|
currentChoiceKey: () => String(item.value),
|
|
1328
1445
|
choices: settingsChoices(item),
|
|
1329
1446
|
children: item.children ? () => settingsEntries(item.children!, rootItems, onChange) : undefined,
|
|
1447
|
+
wizard: item.wizard ? () => item.wizard!() : undefined,
|
|
1330
1448
|
edit:
|
|
1331
1449
|
!item.children && !["boolean", "enum", "group"].includes(item.kind)
|
|
1332
1450
|
? {
|
|
@@ -1385,32 +1503,30 @@ async function openSettings(options: OpenSettingsOptions): Promise<SettingsSessi
|
|
|
1385
1503
|
return component?.sessionResult() ?? { changed: false, configChanged: false, failed: false };
|
|
1386
1504
|
}
|
|
1387
1505
|
|
|
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 {
|
|
1506
|
+
function freeflowStatusText(
|
|
1507
|
+
state: Awaited<ReturnType<typeof readCapabilityState>>,
|
|
1508
|
+
cognitiveRoutingController?: CognitiveRoutingSettingsController,
|
|
1509
|
+
): string {
|
|
1408
1510
|
if (!state.configured) {
|
|
1409
1511
|
return state.configExists
|
|
1410
1512
|
? `Freeflow: inactive (invalid config: ${state.parseError ?? "unknown parse error"}); run /setup-freeflow or fix .freeflow/config.json`
|
|
1411
1513
|
: "Freeflow: inactive (repo not set up); run /setup-freeflow";
|
|
1412
1514
|
}
|
|
1413
1515
|
const sessionSuffix = (source: ConfigSource) => (source === "session" ? " (session override)" : "");
|
|
1516
|
+
const routingState = cognitiveRoutingController?.state();
|
|
1517
|
+
const cognitiveRouting = state.cognitiveRouting;
|
|
1518
|
+
const cognitiveRoutingStatus = cognitiveRouting
|
|
1519
|
+
? cognitiveRouting.blockingReason?.code === "runtime_disabled"
|
|
1520
|
+
? "disabled (PiFlow only)"
|
|
1521
|
+
: cognitiveRouting.effective
|
|
1522
|
+
? routingState?.effective
|
|
1523
|
+
? `active (${routingState.activeProfile ?? "unknown"}, ${routingState.controlMode})`
|
|
1524
|
+
: "effective (inactive)"
|
|
1525
|
+
: cognitiveRouting.enabled
|
|
1526
|
+
? `blocked (${cognitiveRouting.blockingReason.code})`
|
|
1527
|
+
: "disabled"
|
|
1528
|
+
: undefined;
|
|
1529
|
+
const contextEnabled = state.contextVirtualization?.effective || state.conversationHistory?.effective;
|
|
1414
1530
|
return [
|
|
1415
1531
|
`Freeflow: ${state.enabled ? "enabled" : "disabled"}${sessionSuffix(state.configSources.enabled as ConfigSource)}`,
|
|
1416
1532
|
`interaction contract: ${state.interactionContract.effective ? "enabled" : "disabled"}${sessionSuffix(
|
|
@@ -1419,7 +1535,8 @@ function freeflowStatusText(state: Awaited<ReturnType<typeof readCapabilityState
|
|
|
1419
1535
|
`skills: ${state.skills.effective ? "enabled" : "disabled (workflow modes inactive)"}${sessionSuffix(
|
|
1420
1536
|
state.configSources.skillsEnabled as ConfigSource,
|
|
1421
1537
|
)}`,
|
|
1422
|
-
`
|
|
1538
|
+
`context: ${contextEnabled ? "enabled" : "disabled"} (virtualization ${state.contextVirtualization?.effective ? "enabled" : "disabled"}, history ${state.conversationHistory?.effective ? "enabled" : "disabled"})`,
|
|
1539
|
+
...(cognitiveRoutingStatus ? [`cognitive routing: ${cognitiveRoutingStatus}`] : []),
|
|
1423
1540
|
].join("; ");
|
|
1424
1541
|
}
|
|
1425
1542
|
|
|
@@ -1428,6 +1545,12 @@ const NON_TUI_SETTINGS_GUIDANCE =
|
|
|
1428
1545
|
const NON_TUI_MODE_GUIDANCE =
|
|
1429
1546
|
"Freeflow mode selector requires Pi TUI mode. Use /freeflow mode conversation, /freeflow mode workflow, /freeflow mode strict-workflow, or /freeflow mode reset.";
|
|
1430
1547
|
|
|
1548
|
+
function ensureSettingsIdle(ctx: any): boolean {
|
|
1549
|
+
if (typeof ctx?.isIdle !== "function" || ctx.isIdle()) return true;
|
|
1550
|
+
ctx.ui?.notify?.("Freeflow settings and profile changes are available only while Pi is idle.", "warning");
|
|
1551
|
+
return false;
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1431
1554
|
function nonTuiGuidance(ctx: any, message: string) {
|
|
1432
1555
|
if (ctx?.hasUI === false) throw new Error(message);
|
|
1433
1556
|
ctx?.ui?.notify?.(message, "warning");
|
|
@@ -1437,13 +1560,14 @@ function nonTuiGuidance(ctx: any, message: string) {
|
|
|
1437
1560
|
async function finalizeSettingsSession(
|
|
1438
1561
|
session: SettingsSessionResult,
|
|
1439
1562
|
ctx: any,
|
|
1440
|
-
afterChange:
|
|
1563
|
+
afterChange: AfterChange,
|
|
1441
1564
|
savedMessage: string,
|
|
1442
1565
|
reloadWarning = "Run /reload for Freeflow changes to fully apply.",
|
|
1566
|
+
afterChangeOptions: AfterChangeOptions = {},
|
|
1443
1567
|
) {
|
|
1444
1568
|
if (!session.configChanged) return false;
|
|
1445
1569
|
|
|
1446
|
-
await afterChange(true);
|
|
1570
|
+
await afterChange(true, afterChangeOptions);
|
|
1447
1571
|
if (session.failed) return false;
|
|
1448
1572
|
|
|
1449
1573
|
ctx.ui.notify(savedMessage, "info");
|
|
@@ -1456,11 +1580,7 @@ async function finalizeSettingsSession(
|
|
|
1456
1580
|
return true;
|
|
1457
1581
|
}
|
|
1458
1582
|
|
|
1459
|
-
async function finalizeSessionSettings(
|
|
1460
|
-
session: SettingsSessionResult,
|
|
1461
|
-
ctx: any,
|
|
1462
|
-
afterChange: (changed: boolean) => Promise<void> | void,
|
|
1463
|
-
) {
|
|
1583
|
+
async function finalizeSessionSettings(session: SettingsSessionResult, ctx: any, afterChange: AfterChange) {
|
|
1464
1584
|
if (!session.changed) return false;
|
|
1465
1585
|
|
|
1466
1586
|
await afterChange(true);
|
|
@@ -1480,35 +1600,12 @@ async function finalizeSessionSettings(
|
|
|
1480
1600
|
return true;
|
|
1481
1601
|
}
|
|
1482
1602
|
|
|
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
1603
|
export async function handleFreeflowCommand(
|
|
1508
1604
|
args: string | undefined,
|
|
1509
1605
|
ctx: any,
|
|
1510
|
-
afterChange:
|
|
1606
|
+
afterChange: AfterChange,
|
|
1511
1607
|
pi: any,
|
|
1608
|
+
cognitiveRoutingController?: CognitiveRoutingSettingsController,
|
|
1512
1609
|
) {
|
|
1513
1610
|
const input = (args ?? "settings").trim().toLowerCase() || "settings";
|
|
1514
1611
|
const [action, ...rest] = input.split(/\s+/);
|
|
@@ -1526,19 +1623,19 @@ export async function handleFreeflowCommand(
|
|
|
1526
1623
|
|
|
1527
1624
|
const [layers, state, modeState] = await Promise.all([
|
|
1528
1625
|
readFreeflowConfigLayers(ctx.cwd),
|
|
1529
|
-
readCapabilityState(ctx.cwd),
|
|
1626
|
+
readCapabilityState(ctx.cwd, ctx, pi?.host),
|
|
1530
1627
|
readModeState(ctx.cwd),
|
|
1531
1628
|
]);
|
|
1532
1629
|
const configState = layers.repository;
|
|
1533
1630
|
const raw = configState.valid ? configState.parsed : {};
|
|
1534
1631
|
|
|
1535
1632
|
if (action === "status") {
|
|
1536
|
-
ctx.ui.notify(freeflowStatusText(state), "info");
|
|
1633
|
+
ctx.ui.notify(freeflowStatusText(state, cognitiveRoutingController), "info");
|
|
1537
1634
|
return { changed: false, reloaded: false };
|
|
1538
1635
|
}
|
|
1539
1636
|
|
|
1540
1637
|
if (!configState.valid) {
|
|
1541
|
-
ctx.ui.notify(freeflowStatusText(state), "warning");
|
|
1638
|
+
ctx.ui.notify(freeflowStatusText(state, cognitiveRoutingController), "warning");
|
|
1542
1639
|
return { changed: false, reloaded: false, error: "not_configured" };
|
|
1543
1640
|
}
|
|
1544
1641
|
if (layers.local.exists && !layers.local.valid) {
|
|
@@ -1550,6 +1647,7 @@ export async function handleFreeflowCommand(
|
|
|
1550
1647
|
}
|
|
1551
1648
|
|
|
1552
1649
|
if (action === "mode") {
|
|
1650
|
+
if (!ensureSettingsIdle(ctx)) return { changed: false, reloaded: false, error: "busy" };
|
|
1553
1651
|
if (actionValue) {
|
|
1554
1652
|
const result = await handleModeCommand(actionValue, ctx, pi);
|
|
1555
1653
|
return { changed: result.changed, reloaded: false, error: result.error };
|
|
@@ -1566,6 +1664,9 @@ export async function handleFreeflowCommand(
|
|
|
1566
1664
|
const item = freeflowItems(raw, modeState, {
|
|
1567
1665
|
scope: "local",
|
|
1568
1666
|
layers,
|
|
1667
|
+
cognitiveRouting: state.cognitiveRouting as CognitiveRoutingCapabilityState,
|
|
1668
|
+
ctx,
|
|
1669
|
+
hostInfo: pi?.host,
|
|
1569
1670
|
}).find((candidate) => candidate.id === "freeflow.sessionMode")!;
|
|
1570
1671
|
const session = await openSettings({
|
|
1571
1672
|
title: "Freeflow Mode",
|
|
@@ -1581,10 +1682,14 @@ export async function handleFreeflowCommand(
|
|
|
1581
1682
|
}
|
|
1582
1683
|
|
|
1583
1684
|
if (["enable", "on", "true", "disable", "off", "false"].includes(action)) {
|
|
1685
|
+
if (!ensureSettingsIdle(ctx)) return { changed: false, reloaded: false, error: "busy" };
|
|
1584
1686
|
const enabled = ["enable", "on", "true"].includes(action);
|
|
1585
1687
|
const item = freeflowItems(raw, modeState, {
|
|
1586
1688
|
scope: "repository",
|
|
1587
1689
|
layers,
|
|
1690
|
+
cognitiveRouting: state.cognitiveRouting as CognitiveRoutingCapabilityState,
|
|
1691
|
+
ctx,
|
|
1692
|
+
hostInfo: pi?.host,
|
|
1588
1693
|
}).find((candidate) => candidate.id === "freeflow.enabled")!;
|
|
1589
1694
|
await updateConfig(ctx.cwd, item, enabled, "repository");
|
|
1590
1695
|
await afterChange(true);
|
|
@@ -1618,10 +1723,19 @@ export async function handleFreeflowCommand(
|
|
|
1618
1723
|
return { changed: false, reloaded: false, error: "invalid_scope" };
|
|
1619
1724
|
}
|
|
1620
1725
|
|
|
1726
|
+
if (!ensureSettingsIdle(ctx)) return { changed: false, reloaded: false, error: "busy" };
|
|
1727
|
+
|
|
1728
|
+
let reconcileCognitiveRouting = settingsScope === "session";
|
|
1621
1729
|
const items =
|
|
1622
1730
|
settingsScope === "session"
|
|
1623
|
-
? sessionFreeflowItems(state, modeState)
|
|
1624
|
-
: freeflowItems(raw, modeState, {
|
|
1731
|
+
? sessionFreeflowItems(state, modeState, cognitiveRoutingController)
|
|
1732
|
+
: freeflowItems(raw, modeState, {
|
|
1733
|
+
scope: settingsScope,
|
|
1734
|
+
layers,
|
|
1735
|
+
cognitiveRouting: state.cognitiveRouting as CognitiveRoutingCapabilityState,
|
|
1736
|
+
ctx,
|
|
1737
|
+
hostInfo: pi?.host,
|
|
1738
|
+
});
|
|
1625
1739
|
const session = await openSettings({
|
|
1626
1740
|
title:
|
|
1627
1741
|
settingsScope === "session"
|
|
@@ -1632,10 +1746,34 @@ export async function handleFreeflowCommand(
|
|
|
1632
1746
|
items,
|
|
1633
1747
|
ctx,
|
|
1634
1748
|
onChange: async (item, value) => {
|
|
1749
|
+
if (!ensureSettingsIdle(ctx)) return { changed: false, reloadRequired: false };
|
|
1750
|
+
if (!isPiFlowHost(pi?.host) && item.id.startsWith("freeflow.cognitiveRouting.")) {
|
|
1751
|
+
ctx.ui.notify("Cognitive Routing is available only in PiFlow.", "warning");
|
|
1752
|
+
return { changed: false, reloadRequired: false };
|
|
1753
|
+
}
|
|
1635
1754
|
if (item.id === "freeflow.sessionMode") {
|
|
1636
1755
|
const result = await handleModeCommand(String(value), ctx, pi);
|
|
1637
1756
|
return { changed: result.changed, reloadRequired: false };
|
|
1638
1757
|
}
|
|
1758
|
+
if (item.id === "freeflow.cognitiveRouting.profile") {
|
|
1759
|
+
if (!cognitiveRoutingController) {
|
|
1760
|
+
ctx.ui.notify("Cognitive Routing is unavailable for this session.", "warning");
|
|
1761
|
+
return { changed: false, reloadRequired: false };
|
|
1762
|
+
}
|
|
1763
|
+
const result =
|
|
1764
|
+
value === "auto"
|
|
1765
|
+
? await cognitiveRoutingController.setAutomaticControl("profile-settings")
|
|
1766
|
+
: await cognitiveRoutingController.setManualProfile(value as "standard" | "reasoning", "profile-settings");
|
|
1767
|
+
if ((result.status !== "automatic" && result.status !== "active") || result.reason) {
|
|
1768
|
+
ctx.ui.notify(
|
|
1769
|
+
`Cognitive Routing settings could not be applied: ${result.reason ?? result.status}.`,
|
|
1770
|
+
"warning",
|
|
1771
|
+
);
|
|
1772
|
+
return { changed: false, reloadRequired: false };
|
|
1773
|
+
}
|
|
1774
|
+
await afterChange(false);
|
|
1775
|
+
return { changed: true, reloadRequired: false };
|
|
1776
|
+
}
|
|
1639
1777
|
if (item.id === "freeflow.session.reset") {
|
|
1640
1778
|
const result = await resetSessionOverrides(ctx, pi);
|
|
1641
1779
|
return { changed: result.changed, reloadRequired: result.reloadRequired };
|
|
@@ -1645,6 +1783,8 @@ export async function handleFreeflowCommand(
|
|
|
1645
1783
|
"freeflow.enabled": "enabled",
|
|
1646
1784
|
"freeflow.interactionContract": "interactionContract",
|
|
1647
1785
|
"freeflow.skills.enabled": "skillsEnabled",
|
|
1786
|
+
"freeflow.contextVirtualization": "contextVirtualization",
|
|
1787
|
+
"freeflow.conversationHistory": "conversationHistory",
|
|
1648
1788
|
} as const;
|
|
1649
1789
|
const key = keyById[item.id as keyof typeof keyById];
|
|
1650
1790
|
const override = value === LOCAL_INHERIT ? null : value === "true";
|
|
@@ -1652,6 +1792,9 @@ export async function handleFreeflowCommand(
|
|
|
1652
1792
|
return { changed: result.changed, reloadRequired: result.reloadRequired === true };
|
|
1653
1793
|
}
|
|
1654
1794
|
await updateConfig(ctx.cwd, item, value, item.configScope ?? "repository");
|
|
1795
|
+
if (!item.id.startsWith("freeflow.cognitiveRouting.sessionStart.")) {
|
|
1796
|
+
reconcileCognitiveRouting = true;
|
|
1797
|
+
}
|
|
1655
1798
|
return { changed: true, reloadRequired: true };
|
|
1656
1799
|
},
|
|
1657
1800
|
});
|
|
@@ -1667,64 +1810,8 @@ export async function handleFreeflowCommand(
|
|
|
1667
1810
|
ctx,
|
|
1668
1811
|
afterChange,
|
|
1669
1812
|
`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.",
|
|
1813
|
+
"Run /reload for Freeflow changes to fully apply.",
|
|
1814
|
+
{ reconcileCognitiveRouting },
|
|
1728
1815
|
);
|
|
1729
1816
|
return { changed: session.changed, reloaded, error: session.failed ? "write_failed" : undefined };
|
|
1730
1817
|
}
|