@hassangameryt/freeflow 0.7.3 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/NOTICE +14 -0
- package/README.md +26 -31
- package/capabilities/cognitive-routing/SKILL.md +106 -161
- package/capabilities/cognitive-routing/references/both-mode.md +36 -0
- package/capabilities/cognitive-routing/references/executor-mode.md +48 -0
- package/capabilities/cognitive-routing/references/helper-mode.md +47 -0
- package/capabilities/compaction/SKILL.md +49 -0
- package/capabilities/compaction/references/summary-format.md +239 -0
- package/command-surface.json +4 -0
- package/gemini-extension.json +1 -1
- package/hooks/shared/runtime-context.mjs +28 -30
- package/licenses/Apache-2.0.txt +55 -0
- package/package.json +22 -9
- package/pi-extension/README.md +41 -0
- package/pi-extension/dist/cognitive-routing/assembler.js +393 -0
- package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/config.js +5 -2
- package/pi-extension/dist/cognitive-routing/economics.js +45 -0
- package/pi-extension/dist/cognitive-routing/event-schema.js +388 -0
- package/pi-extension/dist/{session-sources/events.js → cognitive-routing/event-store.js} +103 -40
- package/pi-extension/dist/cognitive-routing/gate.js +241 -0
- package/pi-extension/dist/cognitive-routing/handlers.js +899 -0
- package/pi-extension/dist/cognitive-routing/handoffs.js +350 -0
- package/pi-extension/dist/cognitive-routing/model-control.js +467 -0
- package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/projection.js +229 -56
- package/pi-extension/dist/cognitive-routing/provenance.js +81 -0
- package/pi-extension/dist/cognitive-routing/runtime.js +396 -0
- package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/schemas.js +43 -7
- package/pi-extension/dist/cognitive-routing/session.js +221 -0
- package/pi-extension/dist/{session-sources → cognitive-routing}/sources.js +99 -85
- package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/state.js +37 -378
- package/pi-extension/dist/cognitive-routing/status.js +80 -0
- package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/tools.js +12 -12
- package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/types.js +5 -11
- package/pi-extension/dist/compaction/carry.js +191 -0
- package/pi-extension/dist/compaction/config.js +22 -0
- package/pi-extension/dist/compaction/controller.js +471 -0
- package/pi-extension/dist/compaction/harness.js +77 -0
- package/pi-extension/dist/compaction/results.js +158 -0
- package/pi-extension/dist/compaction/thresholds.js +35 -0
- package/pi-extension/dist/compaction/tool.js +57 -0
- package/pi-extension/dist/host/branch.js +13 -0
- package/pi-extension/dist/host/canonical.js +12 -0
- package/pi-extension/dist/host/catalog.js +77 -0
- package/pi-extension/dist/host/config.js +374 -0
- package/pi-extension/dist/host/projection-tags.js +60 -0
- package/pi-extension/dist/host/prompts.js +82 -0
- package/pi-extension/dist/{session-sources → host}/read-only-session.js +112 -1
- package/pi-extension/dist/{runtime → host}/request-history.js +121 -39
- package/pi-extension/dist/host/runtime-state.js +189 -0
- package/pi-extension/dist/{settings/settings-ui.js → host/settings/freeflow-command.js} +620 -410
- package/pi-extension/dist/{settings/settings-tui.js → host/settings/settings-view.js} +186 -73
- package/pi-extension/dist/host/staging.js +28 -0
- package/pi-extension/dist/host/status.js +101 -0
- package/pi-extension/dist/index.js +423 -173
- package/pi-extension/dist/provider-support/cache/anchor.js +21 -0
- package/pi-extension/dist/provider-support/cache/anthropic.js +102 -0
- package/pi-extension/dist/provider-support/cache/health.js +73 -0
- package/pi-extension/dist/provider-support/cache/index.js +58 -0
- package/pi-extension/dist/provider-support/cache/keepalive.js +210 -0
- package/pi-extension/dist/provider-support/cache/monitor.js +17 -0
- package/pi-extension/dist/provider-support/cache/openai-responses.js +48 -0
- package/pi-extension/dist/provider-support/effort.js +13 -0
- package/pi-extension/dist/provider-support/index.js +44 -3
- package/pi-extension/dist/provider-support/{astra → openai}/adapter.js +46 -29
- package/pi-extension/dist/provider-support/{astra → openai}/history.js +10 -8
- package/pi-extension/dist/provider-support/{astra → openai}/index.js +8 -4
- package/pi-extension/dist/provider-support/{astra → openai}/session-state.js +29 -38
- package/pi-extension/dist/provider-support/routes.js +17 -0
- package/pi-extension/dist/tool-execution/apply-patch/grammar.js +23 -0
- package/pi-extension/dist/tool-execution/apply-patch/match.js +38 -0
- package/pi-extension/dist/tool-execution/apply-patch/parser.js +177 -0
- package/pi-extension/dist/tool-execution/apply-patch/plan.js +201 -0
- package/pi-extension/dist/tool-execution/apply-patch/tool.js +84 -0
- package/pi-extension/dist/tool-execution/background.js +238 -0
- package/pi-extension/dist/tool-execution/bash-guard.js +23 -0
- package/pi-extension/dist/tool-execution/config.js +22 -0
- package/pi-extension/dist/tool-execution/edit-messages.js +120 -0
- package/pi-extension/dist/tool-execution/file-state.js +138 -0
- package/pi-extension/dist/tool-execution/file-tracking.js +143 -0
- package/pi-extension/dist/tool-execution/prompt.js +54 -0
- package/pi-extension/dist/tool-execution/tools.js +16 -0
- package/pi-extension/freeflow/README.md +9 -0
- package/plugin.json +1 -1
- package/runtime/prompts/cognitive-routing.md +4 -4
- package/runtime/prompts/core.md +22 -75
- package/runtime/prompts/interaction-contract.md +15 -8
- package/runtime/prompts/tool-execution.md +38 -0
- package/runtime/prompts/working-method.md +108 -0
- package/skills/action-selection/SKILL.md +39 -47
- package/skills/action-selection/references/trajectory-stalls.md +2 -0
- package/skills/bypass/SKILL.md +2 -2
- package/skills/commit-work/SKILL.md +31 -83
- package/skills/commit-work/references/git-state-edges.md +1 -1
- package/skills/commit-work/references/simple-push.md +2 -0
- package/skills/commit-work/references/staging-decisions.md +1 -1
- package/skills/decision-gate/SKILL.md +31 -50
- package/skills/design-for-depth/SKILL.md +44 -53
- package/skills/design-for-depth/references/design-pressure-signals.md +46 -144
- package/skills/design-for-depth/references/implementation-decisions.md +1 -1
- package/skills/design-for-depth/references/interface-design-loop.md +5 -36
- package/skills/design-for-depth/references/module-and-dependency-design.md +2 -32
- package/skills/design-for-depth/references/state-and-failure-boundaries.md +2 -13
- package/skills/diagnose-failure/SKILL.md +39 -24
- package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +2 -0
- package/skills/diagnose-failure/references/flaky-and-performance-diagnosis.md +2 -2
- package/skills/discuss/SKILL.md +35 -11
- package/skills/discuss/references/discussion-continuity.md +7 -27
- package/skills/evaluate-skill/SKILL.md +49 -80
- package/skills/evaluate-skill/references/evaluation-definition-schema.md +35 -5
- package/skills/evaluate-skill/references/evaluation-design.md +14 -14
- package/skills/evaluate-skill/references/execution-and-evidence.md +26 -5
- package/skills/evaluate-skill/references/review-and-revision.md +3 -1
- package/skills/evaluate-skill/scripts/lib/definitions.mjs +49 -8
- package/skills/evaluate-skill/scripts/lib/pi.mjs +14 -7
- package/skills/evaluate-skill/scripts/lib/review.mjs +196 -0
- package/skills/evaluate-skill/scripts/lib/runner.mjs +45 -8
- package/skills/evaluate-skill/scripts/lib/sandbox.mjs +2 -0
- package/skills/evaluate-skill/scripts/lib/trials.mjs +103 -0
- package/skills/evaluate-skill/scripts/lib/view.mjs +67 -1
- package/skills/evaluate-skill/scripts/pi-command-tool.mjs +144 -0
- package/skills/evaluate-skill/scripts/pi-guard.mjs +30 -13
- package/skills/evaluate-skill/scripts/skill-eval.mjs +114 -34
- package/skills/execute-work/SKILL.md +41 -9
- package/skills/execute-work/references/code-practices.md +2 -0
- package/skills/execute-work/references/execute-work-edges.md +11 -1
- package/skills/handoff/SKILL.md +1 -1
- package/skills/handoff/references/handoff-templates.md +1 -1
- package/skills/review-artifact/SKILL.md +42 -41
- package/skills/review-artifact/references/adjudicate-artifact-review.md +1 -1
- package/skills/review-artifact/references/independent-artifact-reviewer-contract.md +1 -1
- package/skills/review-work/SKILL.md +38 -32
- package/skills/review-work/references/adjudicate-work-review.md +1 -1
- package/skills/review-work/references/independent-work-reviewer-contract.md +1 -1
- package/skills/review-work/references/security-risk-lens.md +2 -4
- package/skills/setup-freeflow/SKILL.md +3 -1
- package/skills/setup-freeflow/references/activation-contract.md +7 -13
- package/skills/setup-freeflow/references/host-setup.md +3 -3
- package/skills/track-work/SKILL.md +113 -161
- package/skills/track-work/references/working-record-format.md +189 -350
- package/skills/track-work/scripts/lib/document.mjs +62 -6
- package/skills/track-work/scripts/lib/format.mjs +16 -1
- package/skills/track-work/scripts/lib/store.mjs +9 -8
- package/skills/track-work/scripts/lib/transitions.mjs +79 -4
- package/skills/track-work/scripts/working-record.mjs +34 -12
- package/skills/verify-work/SKILL.md +29 -20
- package/skills/verify-work/references/browser-runtime-evidence.md +1 -5
- package/skills/verify-work/references/integration-evidence.md +2 -0
- package/skills/verify-work/references/performance-evidence.md +3 -1
- package/skills/verify-work/references/test-design.md +3 -1
- package/skills/workflow/SKILL.md +65 -21
- package/skills/workflow/references/domain-skill-composition.md +5 -3
- package/skills/write-docs/SKILL.md +83 -0
- package/skills/write-docs/references/documentation-shapes.md +119 -0
- package/skills/write-docs/scripts/docs-check.mjs +139 -0
- package/skills/write-plan/SKILL.md +40 -40
- package/skills/write-plan/references/plan-shapes.md +1 -1
- package/skills/write-skill/SKILL.md +107 -147
- package/skills/write-skill/references/activation-boundaries.md +2 -0
- package/skills/write-skill/references/agent-first-instructions.md +22 -10
- package/skills/write-skill/references/progressive-disclosure.md +4 -2
- package/skills/write-skill/references/skill-dependency-graphs.md +2 -0
- package/skills/write-skill/scripts/lib/similarity.mjs +56 -0
- package/skills/write-skill/scripts/skill-author.mjs +18 -2
- package/skills/write-spec/SKILL.md +34 -41
- package/skills/write-spec/references/artifact-standards.md +1 -1
- package/skills/write-spec/references/decision-records.md +2 -0
- package/skills/write-spec/references/spec-shapes.md +1 -1
- package/capabilities/context-virtualization/SKILL.md +0 -103
- package/capabilities/conversation-history/SKILL.md +0 -53
- package/hooks/tests/check-host-adapters.test.mjs +0 -82
- package/hooks/tests/check-runtime-context-hook.sh +0 -212
- package/pi-extension/dist/cognitive-routing-v2/runtime.js +0 -2181
- package/pi-extension/dist/context-virtualization/commands.js +0 -89
- package/pi-extension/dist/context-virtualization/projector.js +0 -42
- package/pi-extension/dist/context-virtualization/runtime.js +0 -378
- package/pi-extension/dist/context-virtualization/state.js +0 -57
- package/pi-extension/dist/context-virtualization/types.js +0 -10
- package/pi-extension/dist/conversation-history/passages.js +0 -66
- package/pi-extension/dist/conversation-history/ranking.js +0 -105
- package/pi-extension/dist/conversation-history/runtime.js +0 -226
- package/pi-extension/dist/conversation-history/search.js +0 -56
- package/pi-extension/dist/conversation-history/types.js +0 -1
- package/pi-extension/dist/freeflow-context/resolver.js +0 -120
- package/pi-extension/dist/freeflow-context/runtime.js +0 -80
- package/pi-extension/dist/freeflow-context/source-projector.js +0 -95
- package/pi-extension/dist/freeflow-context/tool.js +0 -314
- package/pi-extension/dist/freeflow-context/types.js +0 -12
- package/pi-extension/dist/freeflow-context/visibility.js +0 -28
- package/pi-extension/dist/integrations/mcp-config.js +0 -29
- package/pi-extension/dist/runtime/runtime-context.js +0 -754
- package/pi-extension/dist/runtime/runtime-identity.js +0 -5
- package/pi-extension/dist/session-sources/provenance.js +0 -32
- package/pi-extension/dist/ui/text-component.js +0 -57
- package/pi-extension/src/cognitive-routing-v2/budget.ts +0 -42
- package/pi-extension/src/cognitive-routing-v2/config.ts +0 -173
- package/pi-extension/src/cognitive-routing-v2/projection.ts +0 -375
- package/pi-extension/src/cognitive-routing-v2/render.ts +0 -277
- package/pi-extension/src/cognitive-routing-v2/runtime.ts +0 -2241
- package/pi-extension/src/cognitive-routing-v2/schemas.ts +0 -180
- package/pi-extension/src/cognitive-routing-v2/state.ts +0 -839
- package/pi-extension/src/cognitive-routing-v2/tools.ts +0 -62
- package/pi-extension/src/cognitive-routing-v2/types.ts +0 -254
- package/pi-extension/src/context-virtualization/commands.ts +0 -111
- package/pi-extension/src/context-virtualization/projector.ts +0 -62
- package/pi-extension/src/context-virtualization/runtime.ts +0 -428
- package/pi-extension/src/context-virtualization/state.ts +0 -79
- package/pi-extension/src/context-virtualization/types.ts +0 -92
- package/pi-extension/src/conversation-history/passages.ts +0 -81
- package/pi-extension/src/conversation-history/ranking.ts +0 -155
- package/pi-extension/src/conversation-history/runtime.ts +0 -250
- package/pi-extension/src/conversation-history/search.ts +0 -79
- package/pi-extension/src/conversation-history/types.ts +0 -31
- package/pi-extension/src/freeflow-context/resolver.ts +0 -139
- package/pi-extension/src/freeflow-context/runtime.ts +0 -100
- package/pi-extension/src/freeflow-context/source-projector.ts +0 -106
- package/pi-extension/src/freeflow-context/tool.ts +0 -366
- package/pi-extension/src/freeflow-context/types.ts +0 -70
- package/pi-extension/src/freeflow-context/visibility.ts +0 -53
- package/pi-extension/src/index.ts +0 -390
- package/pi-extension/src/integrations/mcp-config.ts +0 -32
- package/pi-extension/src/provider-support/astra/adapter.ts +0 -108
- package/pi-extension/src/provider-support/astra/history.ts +0 -145
- package/pi-extension/src/provider-support/astra/index.ts +0 -12
- package/pi-extension/src/provider-support/astra/session-state.ts +0 -113
- package/pi-extension/src/provider-support/index.ts +0 -5
- package/pi-extension/src/runtime/request-history.ts +0 -243
- package/pi-extension/src/runtime/runtime-context.ts +0 -860
- package/pi-extension/src/runtime/runtime-identity.ts +0 -8
- package/pi-extension/src/session-sources/events.ts +0 -230
- package/pi-extension/src/session-sources/provenance.ts +0 -39
- package/pi-extension/src/session-sources/read-only-session.ts +0 -170
- package/pi-extension/src/session-sources/sources.ts +0 -334
- package/pi-extension/src/settings/settings-tui.ts +0 -501
- package/pi-extension/src/settings/settings-ui.ts +0 -1652
- package/pi-extension/src/ui/text-component.ts +0 -66
- package/pi-extension/tests/cache-reuse/history.test.js +0 -117
- package/pi-extension/tests/cache-reuse/native.test.js +0 -91
- package/pi-extension/tests/cache-reuse/settings.test.js +0 -211
- package/pi-extension/tests/cognitive-routing-v2/communication.test.js +0 -577
- package/pi-extension/tests/cognitive-routing-v2/compaction-input.test.js +0 -183
- package/pi-extension/tests/cognitive-routing-v2/delegation.test.js +0 -557
- package/pi-extension/tests/cognitive-routing-v2/native.test.js +0 -474
- package/pi-extension/tests/cognitive-routing-v2/performance.test.js +0 -87
- package/pi-extension/tests/cognitive-routing-v2/persistence.test.js +0 -300
- package/pi-extension/tests/cognitive-routing-v2/projection.test.js +0 -241
- package/pi-extension/tests/cognitive-routing-v2/recovery.test.js +0 -1034
- package/pi-extension/tests/cognitive-routing-v2/render.test.js +0 -244
- package/pi-extension/tests/cognitive-routing-v2/runtime.test.js +0 -423
- package/pi-extension/tests/cognitive-routing-v2/stabilization.test.js +0 -521
- package/pi-extension/tests/cognitive-routing-v2/state.test.js +0 -586
- package/pi-extension/tests/context-virtualization/extension.test.js +0 -775
- package/pi-extension/tests/context-virtualization/projector.test.js +0 -110
- package/pi-extension/tests/context-virtualization/runtime.test.js +0 -230
- package/pi-extension/tests/conversation-history/runtime.test.js +0 -98
- package/pi-extension/tests/conversation-history/search.test.js +0 -97
- package/pi-extension/tests/fixtures/pi-host.js +0 -4
- package/pi-extension/tests/fixtures/routing-native.js +0 -191
- package/pi-extension/tests/integration/mode-free-core.test.js +0 -220
- package/pi-extension/tests/integration/pi-extension.test.js +0 -1447
- package/pi-extension/tests/integration/prompt-architecture.test.js +0 -447
- package/pi-extension/tests/integration/unaffected-regressions.test.js +0 -478
- package/pi-extension/tests/provider-support/astra-boundaries.test.js +0 -75
- package/pi-extension/tests/provider-support/astra.test.js +0 -306
- package/pi-extension/tests/provider-support/cli.test.js +0 -92
- package/pi-extension/tests/provider-support/native.test.js +0 -183
- package/pi-extension/tests/settings/settings-tui.test.js +0 -243
- package/pi-extension/tsconfig.json +0 -16
- package/runtime/prompts/context-virtualization.md +0 -3
- package/runtime/prompts/conversation-history.md +0 -3
- package/skills/design-for-depth/references/software-design-philosophy.md +0 -202
- /package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/budget.js +0 -0
- /package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/render.js +0 -0
|
@@ -1,1652 +0,0 @@
|
|
|
1
|
-
import { execFile } from "node:child_process";
|
|
2
|
-
import { appendFile, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
|
-
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
4
|
-
import { promisify } from "node:util";
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
readCapabilityState,
|
|
8
|
-
readFreeflowConfig,
|
|
9
|
-
readFreeflowConfigLayers,
|
|
10
|
-
readFreeflowLocalConfig,
|
|
11
|
-
resetSessionOverrides,
|
|
12
|
-
setSessionCoreOverride,
|
|
13
|
-
} from "../runtime/runtime-context.js";
|
|
14
|
-
import {
|
|
15
|
-
PiSettingsComponent,
|
|
16
|
-
type SettingsCommitResult,
|
|
17
|
-
type SettingsEntry,
|
|
18
|
-
type SettingsSessionResult,
|
|
19
|
-
type SettingsWizard,
|
|
20
|
-
type SettingsWizardStep,
|
|
21
|
-
} from "./settings-tui.js";
|
|
22
|
-
import { isPiFlowHost } from "../runtime/runtime-identity.js";
|
|
23
|
-
import type {
|
|
24
|
-
CognitiveRoutingCapabilityState,
|
|
25
|
-
CognitiveRoutingDelegationMode,
|
|
26
|
-
CognitiveRoutingProfile,
|
|
27
|
-
CognitiveRoutingProfileName,
|
|
28
|
-
CognitiveRoutingThinkingLevel,
|
|
29
|
-
} from "../cognitive-routing-v2/config.js";
|
|
30
|
-
import { isWorkerProfile, workersForDelegation } from "../cognitive-routing-v2/types.js";
|
|
31
|
-
|
|
32
|
-
const DEFAULT_FREEFLOW_ENABLED = true;
|
|
33
|
-
const DEFAULT_CONTEXT_VIRTUALIZATION_ENABLED = false;
|
|
34
|
-
const DEFAULT_CONVERSATION_HISTORY_ENABLED = false;
|
|
35
|
-
const LOCAL_INHERIT = "inherit";
|
|
36
|
-
const execFileAsync = promisify(execFile);
|
|
37
|
-
|
|
38
|
-
type ConfigScope = "session" | "local" | "repository";
|
|
39
|
-
type ConfigSource = "session" | "local" | "repository" | "builtin";
|
|
40
|
-
|
|
41
|
-
type SettingKind = "boolean" | "enum" | "integer" | "string" | "list" | "json" | "group";
|
|
42
|
-
type SettingsValue = boolean | number | string | null | SettingsValue[] | { [key: string]: SettingsValue };
|
|
43
|
-
|
|
44
|
-
type SettingsItem = {
|
|
45
|
-
id: string;
|
|
46
|
-
label: string;
|
|
47
|
-
description: string;
|
|
48
|
-
path?: string[];
|
|
49
|
-
kind: SettingKind;
|
|
50
|
-
value: unknown;
|
|
51
|
-
defaultValue?: unknown;
|
|
52
|
-
values?: string[];
|
|
53
|
-
valueLabels?: Record<string, string>;
|
|
54
|
-
valueDescriptions?: Record<string, string>;
|
|
55
|
-
inactive?: boolean;
|
|
56
|
-
runtimeInactive?: boolean;
|
|
57
|
-
displaySuffix?: string;
|
|
58
|
-
children?: SettingsItem[];
|
|
59
|
-
wizard?: () => SettingsWizard;
|
|
60
|
-
parse?: (text: string) => unknown;
|
|
61
|
-
format?: (value: unknown) => string;
|
|
62
|
-
configScope?: ConfigScope;
|
|
63
|
-
configValues?: Record<string, unknown>;
|
|
64
|
-
effectiveValue?: unknown;
|
|
65
|
-
effectiveSource?: ConfigSource;
|
|
66
|
-
inheritedValue?: unknown;
|
|
67
|
-
inheritedSource?: ConfigSource;
|
|
68
|
-
localOverrideValue?: unknown;
|
|
69
|
-
transient?: boolean;
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
type SessionRoutingPair = {
|
|
73
|
-
provider: string;
|
|
74
|
-
modelId: string;
|
|
75
|
-
thinking: CognitiveRoutingThinkingLevel;
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
type CognitiveRoutingSettingsController = {
|
|
79
|
-
state(): { effective: boolean; controlMode: string; activeProfile?: string };
|
|
80
|
-
sessionProfileOverrides(): Partial<Record<CognitiveRoutingProfileName, SessionRoutingPair>>;
|
|
81
|
-
setManualProfile(
|
|
82
|
-
profile: CognitiveRoutingProfileName,
|
|
83
|
-
mechanism?: string,
|
|
84
|
-
): Promise<{ status: string; reason?: string }>;
|
|
85
|
-
setAutomaticControl(mechanism?: string): Promise<{ status: string; reason?: string }>;
|
|
86
|
-
setSessionProfileOverride(
|
|
87
|
-
profile: CognitiveRoutingProfileName,
|
|
88
|
-
override: SessionRoutingPair | null,
|
|
89
|
-
mechanism?: string,
|
|
90
|
-
): Promise<{ status: string; reason?: string }>;
|
|
91
|
-
resetSessionProfileOverrides(mechanism?: string): Promise<{ status: string; reason?: string }>;
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
type AfterChangeOptions = {
|
|
95
|
-
reconcileCognitiveRouting?: boolean;
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
type AfterChange = (changed: boolean, options?: AfterChangeOptions) => Promise<void> | void;
|
|
99
|
-
|
|
100
|
-
type OpenSettingsOptions = {
|
|
101
|
-
title: string;
|
|
102
|
-
items: SettingsItem[];
|
|
103
|
-
ctx: any;
|
|
104
|
-
onChange: (item: SettingsItem, value: unknown) => Promise<SettingsCommitResult>;
|
|
105
|
-
initialChoice?: SettingsItem;
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
109
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function cloneJson<T>(value: T): T {
|
|
113
|
-
if (value === undefined) return value;
|
|
114
|
-
try {
|
|
115
|
-
return JSON.parse(JSON.stringify(value));
|
|
116
|
-
} catch (error) {
|
|
117
|
-
throw new Error(`Could not clone settings value: ${error instanceof Error ? error.message : String(error)}`);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function getPath(source: unknown, path: string[]): SettingsValue | undefined {
|
|
122
|
-
let current: unknown = source;
|
|
123
|
-
for (const key of path) {
|
|
124
|
-
if (!isRecord(current)) return undefined;
|
|
125
|
-
current = current[key] as SettingsValue | undefined;
|
|
126
|
-
}
|
|
127
|
-
return current as SettingsValue | undefined;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function setPath(target: Record<string, unknown>, path: string[], value: unknown) {
|
|
131
|
-
let current: Record<string, unknown> = target;
|
|
132
|
-
for (const key of path.slice(0, -1)) {
|
|
133
|
-
const next = current[key];
|
|
134
|
-
if (!isRecord(next)) {
|
|
135
|
-
current[key] = {};
|
|
136
|
-
}
|
|
137
|
-
current = current[key] as Record<string, unknown>;
|
|
138
|
-
}
|
|
139
|
-
current[path[path.length - 1]!] = value;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function deletePath(target: Record<string, unknown>, path: string[]) {
|
|
143
|
-
let current: unknown = target;
|
|
144
|
-
const parents: Array<{ object: Record<string, unknown>; key: string }> = [];
|
|
145
|
-
for (const key of path.slice(0, -1)) {
|
|
146
|
-
if (!isRecord(current)) return;
|
|
147
|
-
parents.push({ object: current, key });
|
|
148
|
-
current = current[key];
|
|
149
|
-
}
|
|
150
|
-
if (!isRecord(current)) return;
|
|
151
|
-
delete current[path[path.length - 1]!];
|
|
152
|
-
|
|
153
|
-
for (let index = parents.length - 1; index >= 0; index--) {
|
|
154
|
-
const { object, key } = parents[index]!;
|
|
155
|
-
const child = object[key];
|
|
156
|
-
if (isRecord(child) && Object.keys(child).length === 0) {
|
|
157
|
-
delete object[key];
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function valuesEqual(a: unknown, b: unknown): boolean {
|
|
163
|
-
return JSON.stringify(a) === JSON.stringify(b);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function isEmptyValue(value: unknown): boolean {
|
|
167
|
-
return Array.isArray(value) && value.length === 0;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function setConfigValue(config: Record<string, unknown>, item: SettingsItem, value: unknown) {
|
|
171
|
-
if (!item.path?.length) {
|
|
172
|
-
throw new Error(`${item.label} is a settings group, not a writable setting.`);
|
|
173
|
-
}
|
|
174
|
-
if (item.defaultValue !== undefined && valuesEqual(value, item.defaultValue)) {
|
|
175
|
-
deletePath(config, item.path);
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
if (isEmptyValue(value)) {
|
|
179
|
-
deletePath(config, item.path);
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
setPath(config, item.path, value);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
function booleanValue(value: unknown): string {
|
|
186
|
-
return value === true ? "enabled" : "disabled";
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function configValueForChoice(item: SettingsItem, value: unknown): SettingsValue | undefined {
|
|
190
|
-
const key = String(value);
|
|
191
|
-
if (item.configValues && Object.hasOwn(item.configValues, key)) {
|
|
192
|
-
return item.configValues[key] as SettingsValue | undefined;
|
|
193
|
-
}
|
|
194
|
-
return value as SettingsValue | undefined;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
function effectiveItemValue(item: SettingsItem): SettingsValue | undefined {
|
|
198
|
-
return (item.effectiveValue === undefined ? item.value : item.effectiveValue) as SettingsValue | undefined;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
function formatCoreValue(value: unknown): string {
|
|
202
|
-
if (typeof value === "boolean") return booleanValue(value);
|
|
203
|
-
if (isRecord(value) && typeof value.provider === "string" && typeof value.model === "string") {
|
|
204
|
-
const effort = typeof value.thinking === "string" ? ` · ${value.thinking}` : "";
|
|
205
|
-
return `${value.provider}/${value.model}${effort}`;
|
|
206
|
-
}
|
|
207
|
-
return String(value ?? "");
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
function coreDisplaySuffix(item: SettingsItem, inactive = false): string {
|
|
211
|
-
const source = item.effectiveSource ?? "builtin";
|
|
212
|
-
const parts =
|
|
213
|
-
item.configScope === "repository" && source === "local"
|
|
214
|
-
? [`effective ${formatCoreValue(effectiveItemValue(item))}`, source]
|
|
215
|
-
: [source];
|
|
216
|
-
if (inactive) parts.push("inactive");
|
|
217
|
-
return `(${parts.join(" · ")})`;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
function updateScopedItemState(item: SettingsItem, value: unknown) {
|
|
221
|
-
if (!item.configScope) return;
|
|
222
|
-
const configValue = configValueForChoice(item, value);
|
|
223
|
-
if (item.configScope === "session" || item.configScope === "local") {
|
|
224
|
-
item.effectiveValue = configValue === undefined ? item.inheritedValue : configValue;
|
|
225
|
-
item.effectiveSource = configValue === undefined ? (item.inheritedSource ?? "builtin") : item.configScope;
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
if (item.localOverrideValue !== undefined) {
|
|
229
|
-
item.effectiveValue = item.localOverrideValue;
|
|
230
|
-
item.effectiveSource = "local";
|
|
231
|
-
return;
|
|
232
|
-
}
|
|
233
|
-
item.effectiveValue = configValue;
|
|
234
|
-
item.effectiveSource =
|
|
235
|
-
item.defaultValue !== undefined && valuesEqual(configValue, item.defaultValue) ? "builtin" : "repository";
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
type ScopedBooleanItemOptions = {
|
|
239
|
-
scope: ConfigScope;
|
|
240
|
-
rawConfig: Record<string, unknown>;
|
|
241
|
-
localConfig: Record<string, unknown>;
|
|
242
|
-
id: string;
|
|
243
|
-
label: string;
|
|
244
|
-
description: string;
|
|
245
|
-
path: string[];
|
|
246
|
-
effectiveValue: boolean;
|
|
247
|
-
effectiveSource: ConfigSource;
|
|
248
|
-
defaultValue: boolean;
|
|
249
|
-
};
|
|
250
|
-
|
|
251
|
-
function createScopedBooleanItem(options: ScopedBooleanItemOptions): SettingsItem {
|
|
252
|
-
const repositoryValue = getPath(options.rawConfig, options.path);
|
|
253
|
-
const inheritedValue = typeof repositoryValue === "boolean" ? repositoryValue : options.defaultValue;
|
|
254
|
-
const inheritedSource: ConfigSource = typeof repositoryValue === "boolean" ? "repository" : "builtin";
|
|
255
|
-
const localValue = getPath(options.localConfig, options.path);
|
|
256
|
-
let item: SettingsItem;
|
|
257
|
-
if (options.scope === "local") {
|
|
258
|
-
const selectedValue = typeof localValue === "boolean" ? String(localValue) : LOCAL_INHERIT;
|
|
259
|
-
item = {
|
|
260
|
-
id: options.id,
|
|
261
|
-
label: options.label,
|
|
262
|
-
description: `${options.description} Choose inherit to use the repository value; use /freeflow settings repo to edit shared defaults.`,
|
|
263
|
-
path: options.path,
|
|
264
|
-
kind: "enum",
|
|
265
|
-
value: selectedValue,
|
|
266
|
-
values: [LOCAL_INHERIT, "true", "false"],
|
|
267
|
-
valueLabels: {
|
|
268
|
-
inherit: "Inherit repository",
|
|
269
|
-
true: "enabled",
|
|
270
|
-
false: "disabled",
|
|
271
|
-
},
|
|
272
|
-
valueDescriptions: {
|
|
273
|
-
inherit: `Use ${formatCoreValue(inheritedValue)} from ${inheritedSource}.`,
|
|
274
|
-
true: "Set a personal enabled override.",
|
|
275
|
-
false: "Set a personal disabled override.",
|
|
276
|
-
},
|
|
277
|
-
format: (value) => {
|
|
278
|
-
if (value === LOCAL_INHERIT) return LOCAL_INHERIT;
|
|
279
|
-
return booleanValue(value === "true");
|
|
280
|
-
},
|
|
281
|
-
configScope: "local",
|
|
282
|
-
configValues: {
|
|
283
|
-
inherit: undefined,
|
|
284
|
-
true: true,
|
|
285
|
-
false: false,
|
|
286
|
-
},
|
|
287
|
-
effectiveValue: options.effectiveValue,
|
|
288
|
-
effectiveSource: options.effectiveSource,
|
|
289
|
-
inheritedValue,
|
|
290
|
-
inheritedSource,
|
|
291
|
-
};
|
|
292
|
-
} else {
|
|
293
|
-
item = {
|
|
294
|
-
id: options.id,
|
|
295
|
-
label: options.label,
|
|
296
|
-
description: `${options.description} This edits shared .freeflow/config.json.`,
|
|
297
|
-
path: options.path,
|
|
298
|
-
kind: "boolean",
|
|
299
|
-
value: inheritedValue,
|
|
300
|
-
defaultValue: options.defaultValue,
|
|
301
|
-
configScope: "repository",
|
|
302
|
-
effectiveValue: options.effectiveValue,
|
|
303
|
-
effectiveSource: options.effectiveSource,
|
|
304
|
-
localOverrideValue: typeof localValue === "boolean" ? localValue : undefined,
|
|
305
|
-
};
|
|
306
|
-
}
|
|
307
|
-
item.displaySuffix = coreDisplaySuffix(item);
|
|
308
|
-
return item;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
function createScopedDelegationItem(options: {
|
|
312
|
-
scope: Exclude<ConfigScope, "session">;
|
|
313
|
-
rawConfig: Record<string, unknown>;
|
|
314
|
-
localConfig: Record<string, unknown>;
|
|
315
|
-
effectiveValue: CognitiveRoutingDelegationMode;
|
|
316
|
-
effectiveSource: ConfigSource;
|
|
317
|
-
}): SettingsItem {
|
|
318
|
-
const path = ["cognitiveRouting", "delegation"];
|
|
319
|
-
const repositoryValue = getPath(options.rawConfig, path);
|
|
320
|
-
const inheritedValue: CognitiveRoutingDelegationMode = ["executor", "helper", "both"].includes(
|
|
321
|
-
repositoryValue as string,
|
|
322
|
-
)
|
|
323
|
-
? (repositoryValue as CognitiveRoutingDelegationMode)
|
|
324
|
-
: "executor";
|
|
325
|
-
const inheritedSource: ConfigSource = repositoryValue === undefined ? "builtin" : "repository";
|
|
326
|
-
const localValue = getPath(options.localConfig, path);
|
|
327
|
-
const descriptions = {
|
|
328
|
-
executor: "Coordinator delegates assignments to Executor.",
|
|
329
|
-
helper: "Coordinator delegates supporting assignments to Helper and keeps production implementation.",
|
|
330
|
-
both: "Coordinator chooses Helper or Executor for each assignment.",
|
|
331
|
-
};
|
|
332
|
-
const item: SettingsItem =
|
|
333
|
-
options.scope === "local"
|
|
334
|
-
? {
|
|
335
|
-
id: "freeflow.cognitiveRouting.delegation",
|
|
336
|
-
label: "Delegation method",
|
|
337
|
-
description:
|
|
338
|
-
"Choose enabled workers for automatic routing. Inherit uses the repository value; this setting does not rank model presets.",
|
|
339
|
-
path,
|
|
340
|
-
kind: "enum",
|
|
341
|
-
value: ["executor", "helper", "both"].includes(localValue as string) ? localValue : LOCAL_INHERIT,
|
|
342
|
-
values: [LOCAL_INHERIT, "executor", "helper", "both"],
|
|
343
|
-
valueLabels: {
|
|
344
|
-
inherit: "Inherit repository",
|
|
345
|
-
executor: "Executor only",
|
|
346
|
-
helper: "Helper only",
|
|
347
|
-
both: "Helper and Executor",
|
|
348
|
-
},
|
|
349
|
-
valueDescriptions: {
|
|
350
|
-
inherit: `Use ${inheritedValue} from ${inheritedSource}.`,
|
|
351
|
-
...descriptions,
|
|
352
|
-
},
|
|
353
|
-
configScope: "local",
|
|
354
|
-
configValues: { inherit: undefined, executor: "executor", helper: "helper", both: "both" },
|
|
355
|
-
effectiveValue: options.effectiveValue,
|
|
356
|
-
effectiveSource: options.effectiveSource,
|
|
357
|
-
inheritedValue,
|
|
358
|
-
inheritedSource,
|
|
359
|
-
}
|
|
360
|
-
: {
|
|
361
|
-
id: "freeflow.cognitiveRouting.delegation",
|
|
362
|
-
label: "Delegation method",
|
|
363
|
-
description: "Choose enabled workers for automatic routing. This edits shared .freeflow/config.json.",
|
|
364
|
-
path,
|
|
365
|
-
kind: "enum",
|
|
366
|
-
value: inheritedValue,
|
|
367
|
-
values: ["executor", "helper", "both"],
|
|
368
|
-
valueLabels: {
|
|
369
|
-
executor: "Executor only",
|
|
370
|
-
helper: "Helper only",
|
|
371
|
-
both: "Helper and Executor",
|
|
372
|
-
},
|
|
373
|
-
valueDescriptions: descriptions,
|
|
374
|
-
defaultValue: "executor",
|
|
375
|
-
configScope: "repository",
|
|
376
|
-
effectiveValue: options.effectiveValue,
|
|
377
|
-
effectiveSource: options.effectiveSource,
|
|
378
|
-
localOverrideValue: ["executor", "helper", "both"].includes(localValue as string) ? localValue : undefined,
|
|
379
|
-
};
|
|
380
|
-
item.displaySuffix = coreDisplaySuffix(item);
|
|
381
|
-
return item;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
function resolveSettingsCoreView(
|
|
385
|
-
rawConfig: Record<string, unknown>,
|
|
386
|
-
layers?: Awaited<ReturnType<typeof readFreeflowConfigLayers>>,
|
|
387
|
-
) {
|
|
388
|
-
const localConfig = layers?.local.valid && isRecord(layers.local.parsed) ? layers.local.parsed : {};
|
|
389
|
-
const fallbackCore = {
|
|
390
|
-
enabled: getPath(rawConfig, ["enabled"]) !== false,
|
|
391
|
-
contextVirtualization: getPath(rawConfig, ["contextVirtualization"]) === true,
|
|
392
|
-
conversationHistory: getPath(rawConfig, ["conversationHistory"]) === true,
|
|
393
|
-
};
|
|
394
|
-
const fallbackSources = {
|
|
395
|
-
enabled: typeof getPath(rawConfig, ["enabled"]) === "boolean" ? "repository" : "builtin",
|
|
396
|
-
contextVirtualization:
|
|
397
|
-
typeof getPath(rawConfig, ["contextVirtualization"]) === "boolean" ? "repository" : "builtin",
|
|
398
|
-
conversationHistory: typeof getPath(rawConfig, ["conversationHistory"]) === "boolean" ? "repository" : "builtin",
|
|
399
|
-
} as const;
|
|
400
|
-
return {
|
|
401
|
-
localConfig,
|
|
402
|
-
core: layers?.coreConfig ?? fallbackCore,
|
|
403
|
-
sources: (layers?.sources ?? fallbackSources) as {
|
|
404
|
-
enabled: ConfigSource;
|
|
405
|
-
contextVirtualization: ConfigSource;
|
|
406
|
-
conversationHistory: ConfigSource;
|
|
407
|
-
},
|
|
408
|
-
};
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
function createSessionBooleanItem(options: {
|
|
412
|
-
id: string;
|
|
413
|
-
label: string;
|
|
414
|
-
description: string;
|
|
415
|
-
key: "enabled" | "contextVirtualization" | "conversationHistory";
|
|
416
|
-
inheritedValue: boolean;
|
|
417
|
-
inheritedSource: ConfigSource;
|
|
418
|
-
effectiveValue: boolean;
|
|
419
|
-
effectiveSource: ConfigSource;
|
|
420
|
-
sessionOverrides: Record<string, boolean>;
|
|
421
|
-
}): SettingsItem {
|
|
422
|
-
const override = options.sessionOverrides[options.key];
|
|
423
|
-
const item: SettingsItem = {
|
|
424
|
-
id: options.id,
|
|
425
|
-
label: options.label,
|
|
426
|
-
description: options.description,
|
|
427
|
-
kind: "enum",
|
|
428
|
-
value: typeof override === "boolean" ? String(override) : LOCAL_INHERIT,
|
|
429
|
-
values: [LOCAL_INHERIT, "true", "false"],
|
|
430
|
-
valueLabels: {
|
|
431
|
-
inherit: "Inherit configured value",
|
|
432
|
-
true: "Enabled for this session",
|
|
433
|
-
false: "Disabled for this session",
|
|
434
|
-
},
|
|
435
|
-
valueDescriptions: {
|
|
436
|
-
inherit: `Use ${formatCoreValue(options.inheritedValue)} from ${options.inheritedSource}.`,
|
|
437
|
-
true: "Temporarily enable this setting for the current Pi session.",
|
|
438
|
-
false: "Temporarily disable this setting for the current Pi session.",
|
|
439
|
-
},
|
|
440
|
-
format: (value) => {
|
|
441
|
-
if (value === LOCAL_INHERIT) return LOCAL_INHERIT;
|
|
442
|
-
return booleanValue(value === "true");
|
|
443
|
-
},
|
|
444
|
-
configScope: "session",
|
|
445
|
-
configValues: {
|
|
446
|
-
inherit: undefined,
|
|
447
|
-
true: true,
|
|
448
|
-
false: false,
|
|
449
|
-
},
|
|
450
|
-
effectiveValue: options.effectiveValue,
|
|
451
|
-
effectiveSource: options.effectiveSource,
|
|
452
|
-
inheritedValue: options.inheritedValue,
|
|
453
|
-
inheritedSource: options.inheritedSource,
|
|
454
|
-
};
|
|
455
|
-
item.displaySuffix = coreDisplaySuffix(item);
|
|
456
|
-
return item;
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
function sessionFreeflowItems(
|
|
460
|
-
state: Awaited<ReturnType<typeof readCapabilityState>>,
|
|
461
|
-
cognitiveRoutingController?: CognitiveRoutingSettingsController,
|
|
462
|
-
ctx?: any,
|
|
463
|
-
): SettingsItem[] {
|
|
464
|
-
const sessionOverrides = state.sessionOverrides as Record<string, boolean>;
|
|
465
|
-
const configured = state.configuredCoreConfig;
|
|
466
|
-
const configuredSources = state.configuredSources as {
|
|
467
|
-
enabled: ConfigSource;
|
|
468
|
-
contextVirtualization: ConfigSource;
|
|
469
|
-
conversationHistory: ConfigSource;
|
|
470
|
-
};
|
|
471
|
-
const effectiveSources = state.configSources as {
|
|
472
|
-
enabled: ConfigSource;
|
|
473
|
-
contextVirtualization: ConfigSource;
|
|
474
|
-
conversationHistory: ConfigSource;
|
|
475
|
-
};
|
|
476
|
-
|
|
477
|
-
const freeflowItem = createSessionBooleanItem({
|
|
478
|
-
id: "freeflow.enabled",
|
|
479
|
-
label: "Freeflow",
|
|
480
|
-
description: "Temporary master override for this Pi session.",
|
|
481
|
-
key: "enabled",
|
|
482
|
-
inheritedValue: configured.enabled,
|
|
483
|
-
inheritedSource: configuredSources.enabled,
|
|
484
|
-
effectiveValue: state.enabled,
|
|
485
|
-
effectiveSource: effectiveSources.enabled,
|
|
486
|
-
sessionOverrides,
|
|
487
|
-
});
|
|
488
|
-
const contextVirtualizationItem = createSessionBooleanItem({
|
|
489
|
-
id: "freeflow.contextVirtualization",
|
|
490
|
-
label: "Context Virtualization",
|
|
491
|
-
description: "Temporary Context Virtualization override for this Pi session.",
|
|
492
|
-
key: "contextVirtualization",
|
|
493
|
-
inheritedValue: configured.contextVirtualization,
|
|
494
|
-
inheritedSource: configuredSources.contextVirtualization,
|
|
495
|
-
effectiveValue: state.contextVirtualization.enabled,
|
|
496
|
-
effectiveSource: effectiveSources.contextVirtualization,
|
|
497
|
-
sessionOverrides,
|
|
498
|
-
});
|
|
499
|
-
const conversationHistoryItem = createSessionBooleanItem({
|
|
500
|
-
id: "freeflow.conversationHistory",
|
|
501
|
-
label: "Conversation History",
|
|
502
|
-
description: "Temporary Conversation History override for this Pi session.",
|
|
503
|
-
key: "conversationHistory",
|
|
504
|
-
inheritedValue: configured.conversationHistory,
|
|
505
|
-
inheritedSource: configuredSources.conversationHistory,
|
|
506
|
-
effectiveValue: state.conversationHistory.enabled,
|
|
507
|
-
effectiveSource: effectiveSources.conversationHistory,
|
|
508
|
-
sessionOverrides,
|
|
509
|
-
});
|
|
510
|
-
|
|
511
|
-
const freeflowInactive = !state.enabled;
|
|
512
|
-
contextVirtualizationItem.inactive = freeflowInactive;
|
|
513
|
-
conversationHistoryItem.inactive = freeflowInactive;
|
|
514
|
-
|
|
515
|
-
const cognitiveRoutingState = cognitiveRoutingController?.state();
|
|
516
|
-
const delegation = state.cognitiveRouting?.delegation ?? "executor";
|
|
517
|
-
const heldProfile = cognitiveRoutingState?.controlMode.startsWith("manual-")
|
|
518
|
-
? cognitiveRoutingState.activeProfile
|
|
519
|
-
: undefined;
|
|
520
|
-
const availableProfiles: CognitiveRoutingProfileName[] = ["coordinator", ...workersForDelegation(delegation)];
|
|
521
|
-
if (heldProfile === "coordinator" || isWorkerProfile(heldProfile)) availableProfiles.push(heldProfile);
|
|
522
|
-
const manualProfiles = [...new Set(availableProfiles)];
|
|
523
|
-
const cognitiveRoutingProfile =
|
|
524
|
-
heldProfile && manualProfiles.includes(heldProfile as CognitiveRoutingProfileName) ? heldProfile : "auto";
|
|
525
|
-
const cognitiveRoutingItem: SettingsItem | undefined = state.cognitiveRouting
|
|
526
|
-
? {
|
|
527
|
-
id: "freeflow.cognitiveRouting.profile",
|
|
528
|
-
label: "Cognitive Routing",
|
|
529
|
-
description: "Hold an enabled profile manually or release the hold for automatic model control.",
|
|
530
|
-
kind: "enum",
|
|
531
|
-
value: cognitiveRoutingProfile,
|
|
532
|
-
values: ["auto", ...manualProfiles],
|
|
533
|
-
valueLabels: Object.fromEntries([
|
|
534
|
-
["auto", "automatic"],
|
|
535
|
-
...manualProfiles.map((profile) => [profile, `manual · ${profile}`]),
|
|
536
|
-
]),
|
|
537
|
-
valueDescriptions: Object.fromEntries([
|
|
538
|
-
["auto", "Release the manual hold and return to Coordinator reconciliation."],
|
|
539
|
-
...manualProfiles.map((profile) => [profile, `Hold the ${profile} profile until /freeflow profile auto.`]),
|
|
540
|
-
]),
|
|
541
|
-
inactive: freeflowInactive || !state.cognitiveRouting.effective || cognitiveRoutingController === undefined,
|
|
542
|
-
runtimeInactive: state.cognitiveRouting.blockingReason?.code === "runtime_disabled",
|
|
543
|
-
displaySuffix: cognitiveRoutingState?.effective ? cognitiveRoutingProfile : "unavailable",
|
|
544
|
-
}
|
|
545
|
-
: undefined;
|
|
546
|
-
|
|
547
|
-
const sessionProfiles = cognitiveRoutingController?.sessionProfileOverrides();
|
|
548
|
-
const profileItems =
|
|
549
|
-
state.cognitiveRouting && cognitiveRoutingController
|
|
550
|
-
? manualProfiles.map((name) =>
|
|
551
|
-
cognitiveRoutingProfileItem({
|
|
552
|
-
name,
|
|
553
|
-
scope: "session",
|
|
554
|
-
rawConfig: {},
|
|
555
|
-
localConfig: {},
|
|
556
|
-
capabilityState: state.cognitiveRouting as CognitiveRoutingCapabilityState,
|
|
557
|
-
ctx,
|
|
558
|
-
sessionProfiles,
|
|
559
|
-
}),
|
|
560
|
-
)
|
|
561
|
-
: [];
|
|
562
|
-
const cognitiveRoutingPresets: SettingsItem | undefined = profileItems.length
|
|
563
|
-
? {
|
|
564
|
-
id: "freeflow.cognitiveRouting.presets",
|
|
565
|
-
label: "Cognitive Routing presets",
|
|
566
|
-
description: "Temporarily choose complete model/effort pairs for profiles enabled in this Pi session.",
|
|
567
|
-
kind: "group",
|
|
568
|
-
value: profileItems.some((item) => item.value !== LOCAL_INHERIT && item.value !== undefined),
|
|
569
|
-
displaySuffix: `${profileItems.filter((item) => item.value !== LOCAL_INHERIT && item.value !== undefined).length}/${profileItems.length} session overrides`,
|
|
570
|
-
inactive: freeflowInactive || !state.cognitiveRouting?.effective || cognitiveRoutingController === undefined,
|
|
571
|
-
children: profileItems,
|
|
572
|
-
}
|
|
573
|
-
: undefined;
|
|
574
|
-
|
|
575
|
-
return [
|
|
576
|
-
freeflowItem,
|
|
577
|
-
...(cognitiveRoutingItem ? [cognitiveRoutingItem] : []),
|
|
578
|
-
...(cognitiveRoutingPresets ? [cognitiveRoutingPresets] : []),
|
|
579
|
-
{
|
|
580
|
-
id: "freeflow.session.reset",
|
|
581
|
-
label: "Reset session overrides",
|
|
582
|
-
description:
|
|
583
|
-
"Clear Freeflow, Context Virtualization, Conversation History, and routing profile overrides for this Pi session.",
|
|
584
|
-
kind: "enum",
|
|
585
|
-
value: "available",
|
|
586
|
-
values: ["reset"],
|
|
587
|
-
valueLabels: { reset: "Reset all session overrides" },
|
|
588
|
-
valueDescriptions: { reset: "Return every session setting and routing preset to its configured value." },
|
|
589
|
-
format: () => "available",
|
|
590
|
-
transient: true,
|
|
591
|
-
},
|
|
592
|
-
{
|
|
593
|
-
id: "freeflow.context",
|
|
594
|
-
label: "Freeflow Context",
|
|
595
|
-
description: "Choose which context projection and conversation-history operations are available to the model.",
|
|
596
|
-
kind: "group",
|
|
597
|
-
value: contextVirtualizationItem.effectiveValue === true || conversationHistoryItem.effectiveValue === true,
|
|
598
|
-
displaySuffix: `${[contextVirtualizationItem, conversationHistoryItem].filter((item) => item.effectiveValue === true).length}/2 enabled`,
|
|
599
|
-
children: [contextVirtualizationItem, conversationHistoryItem],
|
|
600
|
-
},
|
|
601
|
-
];
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
const COGNITIVE_ROUTING_THINKING_LEVELS: CognitiveRoutingThinkingLevel[] = [
|
|
605
|
-
"off",
|
|
606
|
-
"minimal",
|
|
607
|
-
"low",
|
|
608
|
-
"medium",
|
|
609
|
-
"high",
|
|
610
|
-
"xhigh",
|
|
611
|
-
"max",
|
|
612
|
-
];
|
|
613
|
-
|
|
614
|
-
const COGNITIVE_ROUTING_EFFORT_DESCRIPTIONS: Record<CognitiveRoutingThinkingLevel, string> = {
|
|
615
|
-
off: "No reasoning",
|
|
616
|
-
minimal: "Very brief reasoning",
|
|
617
|
-
low: "Light reasoning",
|
|
618
|
-
medium: "Moderate reasoning",
|
|
619
|
-
high: "Deep reasoning",
|
|
620
|
-
xhigh: "Extra-high reasoning",
|
|
621
|
-
max: "Maximum reasoning",
|
|
622
|
-
};
|
|
623
|
-
|
|
624
|
-
type CognitiveRoutingModelOption = {
|
|
625
|
-
key: string;
|
|
626
|
-
provider: string;
|
|
627
|
-
model: string;
|
|
628
|
-
label: string;
|
|
629
|
-
description: string;
|
|
630
|
-
thinkingLevels: CognitiveRoutingThinkingLevel[];
|
|
631
|
-
};
|
|
632
|
-
|
|
633
|
-
function cognitiveRoutingModelKey(provider: string, model: string): string {
|
|
634
|
-
return `${provider}/${model}`;
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
function isCognitiveRoutingProfile(value: unknown): value is CognitiveRoutingProfile {
|
|
638
|
-
return (
|
|
639
|
-
isRecord(value) &&
|
|
640
|
-
typeof value.provider === "string" &&
|
|
641
|
-
typeof value.model === "string" &&
|
|
642
|
-
typeof value.thinking === "string" &&
|
|
643
|
-
COGNITIVE_ROUTING_THINKING_LEVELS.includes(value.thinking as CognitiveRoutingThinkingLevel)
|
|
644
|
-
);
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
function cognitiveRoutingThinkingLevels(model: any, registry: any): CognitiveRoutingThinkingLevel[] {
|
|
648
|
-
if (typeof registry?.clampThinkingLevel !== "function") {
|
|
649
|
-
return model?.reasoning === true ? [...COGNITIVE_ROUTING_THINKING_LEVELS] : ["off"];
|
|
650
|
-
}
|
|
651
|
-
return COGNITIVE_ROUTING_THINKING_LEVELS.filter((level) => {
|
|
652
|
-
try {
|
|
653
|
-
return registry.clampThinkingLevel(model, level) === level;
|
|
654
|
-
} catch {
|
|
655
|
-
return false;
|
|
656
|
-
}
|
|
657
|
-
});
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
function cognitiveRoutingModelOptions(ctx: any): CognitiveRoutingModelOption[] {
|
|
661
|
-
const registry = ctx?.modelRegistry;
|
|
662
|
-
if (typeof registry?.getAvailable !== "function") return [];
|
|
663
|
-
let models: any[];
|
|
664
|
-
try {
|
|
665
|
-
models = registry.getAvailable();
|
|
666
|
-
} catch {
|
|
667
|
-
return [];
|
|
668
|
-
}
|
|
669
|
-
if (!Array.isArray(models)) return [];
|
|
670
|
-
|
|
671
|
-
return models
|
|
672
|
-
.filter((model) => typeof model?.provider === "string" && typeof model?.id === "string")
|
|
673
|
-
.map((model) => {
|
|
674
|
-
const thinkingLevels = cognitiveRoutingThinkingLevels(model, registry);
|
|
675
|
-
const provider = model.provider as string;
|
|
676
|
-
const modelId = model.id as string;
|
|
677
|
-
const key = cognitiveRoutingModelKey(provider, modelId);
|
|
678
|
-
const displayName = typeof model.name === "string" && model.name !== modelId ? model.name : undefined;
|
|
679
|
-
return {
|
|
680
|
-
key,
|
|
681
|
-
provider,
|
|
682
|
-
model: modelId,
|
|
683
|
-
label: key,
|
|
684
|
-
description: displayName ? `${displayName} · ${thinkingLevels.join(", ")}` : thinkingLevels.join(", "),
|
|
685
|
-
thinkingLevels,
|
|
686
|
-
};
|
|
687
|
-
})
|
|
688
|
-
.filter((model) => model.thinkingLevels.length > 0)
|
|
689
|
-
.sort((a, b) => a.label.localeCompare(b.label));
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
function cognitiveRoutingProfileDisplay(value: unknown): string {
|
|
693
|
-
return isCognitiveRoutingProfile(value) ? formatCoreValue(value) : "not configured";
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
function cognitiveRoutingSettingsSource(source: unknown): ConfigSource {
|
|
697
|
-
if (source === "session") return "session";
|
|
698
|
-
if (source === "repository") return "repository";
|
|
699
|
-
if (source === "personal" || source === "local") return "local";
|
|
700
|
-
return "builtin";
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
function cognitiveRoutingProfileDisplaySuffix(source: unknown, scope: ConfigScope): string | undefined {
|
|
704
|
-
if (!source) return undefined;
|
|
705
|
-
if (source === "session") return "(session override)";
|
|
706
|
-
if (source === "personal" && scope === "repository") return "(effective personal)";
|
|
707
|
-
return `(${source})`;
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
function cognitiveRoutingConfirmStep(summary: string): SettingsWizardStep {
|
|
711
|
-
return {
|
|
712
|
-
title: "Confirm preset",
|
|
713
|
-
choices: [
|
|
714
|
-
{
|
|
715
|
-
key: "__save__",
|
|
716
|
-
value: "save",
|
|
717
|
-
label: `Save ${summary}`,
|
|
718
|
-
description: "Write the complete profile atomically.",
|
|
719
|
-
},
|
|
720
|
-
{ key: "__cancel__", value: "cancel", label: "Cancel", description: "Leave the previous preset unchanged." },
|
|
721
|
-
],
|
|
722
|
-
selectedKey: "__save__",
|
|
723
|
-
};
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
function createCognitiveRoutingProfileWizard(
|
|
727
|
-
currentValue: unknown,
|
|
728
|
-
models: CognitiveRoutingModelOption[],
|
|
729
|
-
allowInherit: boolean,
|
|
730
|
-
inheritChoice: { label: string; description: string; summary: string } = {
|
|
731
|
-
label: "Inherit repository preset",
|
|
732
|
-
description: "Remove the personal override without changing the repository preset.",
|
|
733
|
-
summary: "inherit repository preset",
|
|
734
|
-
},
|
|
735
|
-
): SettingsWizard {
|
|
736
|
-
const currentProfile = isCognitiveRoutingProfile(currentValue) ? currentValue : undefined;
|
|
737
|
-
const currentModelKey = currentProfile
|
|
738
|
-
? cognitiveRoutingModelKey(currentProfile.provider, currentProfile.model)
|
|
739
|
-
: undefined;
|
|
740
|
-
const firstChoices = [
|
|
741
|
-
...(allowInherit
|
|
742
|
-
? [
|
|
743
|
-
{
|
|
744
|
-
key: LOCAL_INHERIT,
|
|
745
|
-
value: LOCAL_INHERIT,
|
|
746
|
-
label: inheritChoice.label,
|
|
747
|
-
description: inheritChoice.description,
|
|
748
|
-
},
|
|
749
|
-
]
|
|
750
|
-
: []),
|
|
751
|
-
...models.map((model) => ({
|
|
752
|
-
key: model.key,
|
|
753
|
-
value: model,
|
|
754
|
-
label: model.label,
|
|
755
|
-
description: model.description,
|
|
756
|
-
})),
|
|
757
|
-
];
|
|
758
|
-
if (firstChoices.length === 0) {
|
|
759
|
-
firstChoices.push({
|
|
760
|
-
key: "__cancel__",
|
|
761
|
-
value: "cancel",
|
|
762
|
-
label: "No authenticated models available",
|
|
763
|
-
description: "Authenticate a provider before configuring this preset.",
|
|
764
|
-
});
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
return {
|
|
768
|
-
firstStep: () => ({
|
|
769
|
-
title: "Choose model",
|
|
770
|
-
choices: firstChoices,
|
|
771
|
-
selectedKey:
|
|
772
|
-
currentValue === LOCAL_INHERIT
|
|
773
|
-
? LOCAL_INHERIT
|
|
774
|
-
: firstChoices.some((choice) => choice.key === currentModelKey)
|
|
775
|
-
? currentModelKey
|
|
776
|
-
: firstChoices[0]?.key,
|
|
777
|
-
}),
|
|
778
|
-
nextStep: (selectedValues) => {
|
|
779
|
-
if (selectedValues.length === 1) {
|
|
780
|
-
const selected = selectedValues[0];
|
|
781
|
-
if (selected === LOCAL_INHERIT) {
|
|
782
|
-
return cognitiveRoutingConfirmStep(inheritChoice.summary);
|
|
783
|
-
}
|
|
784
|
-
const model = selected as CognitiveRoutingModelOption;
|
|
785
|
-
if (!model?.key || model.thinkingLevels.length === 0) return undefined;
|
|
786
|
-
const currentEffort = currentProfile && currentModelKey === model.key ? currentProfile.thinking : undefined;
|
|
787
|
-
return {
|
|
788
|
-
title: `Choose effort · ${model.label}`,
|
|
789
|
-
choices: model.thinkingLevels.map((level) => ({
|
|
790
|
-
key: level,
|
|
791
|
-
value: level,
|
|
792
|
-
label: level,
|
|
793
|
-
description: COGNITIVE_ROUTING_EFFORT_DESCRIPTIONS[level],
|
|
794
|
-
})),
|
|
795
|
-
selectedKey:
|
|
796
|
-
currentEffort && model.thinkingLevels.includes(currentEffort) ? currentEffort : model.thinkingLevels[0],
|
|
797
|
-
};
|
|
798
|
-
}
|
|
799
|
-
if (selectedValues.length === 2) {
|
|
800
|
-
const model = selectedValues[0] as CognitiveRoutingModelOption;
|
|
801
|
-
const effort = selectedValues[1] as CognitiveRoutingThinkingLevel;
|
|
802
|
-
return cognitiveRoutingConfirmStep(`${model.label} · ${effort}`);
|
|
803
|
-
}
|
|
804
|
-
return undefined;
|
|
805
|
-
},
|
|
806
|
-
valueFromSelections: (selectedValues) => {
|
|
807
|
-
if (selectedValues[0] === LOCAL_INHERIT) return undefined;
|
|
808
|
-
const model = selectedValues[0] as CognitiveRoutingModelOption;
|
|
809
|
-
return {
|
|
810
|
-
provider: model.provider,
|
|
811
|
-
model: model.model,
|
|
812
|
-
thinking: selectedValues[1] as CognitiveRoutingThinkingLevel,
|
|
813
|
-
} satisfies CognitiveRoutingProfile;
|
|
814
|
-
},
|
|
815
|
-
};
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
function cognitiveRoutingProfileItem(options: {
|
|
819
|
-
name: CognitiveRoutingProfileName;
|
|
820
|
-
scope: ConfigScope;
|
|
821
|
-
rawConfig: Record<string, unknown>;
|
|
822
|
-
localConfig: Record<string, unknown>;
|
|
823
|
-
capabilityState: CognitiveRoutingCapabilityState | undefined;
|
|
824
|
-
ctx: any;
|
|
825
|
-
sessionProfiles?: Partial<Record<CognitiveRoutingProfileName, SessionRoutingPair>>;
|
|
826
|
-
}): SettingsItem {
|
|
827
|
-
const path = ["cognitiveRouting", "profiles", options.name];
|
|
828
|
-
const repositoryProfile = getPath(options.rawConfig, path);
|
|
829
|
-
const localProfile = getPath(options.localConfig, path);
|
|
830
|
-
const configuredProfile = options.capabilityState?.profiles?.[options.name] ?? repositoryProfile;
|
|
831
|
-
const sessionPair = options.sessionProfiles?.[options.name];
|
|
832
|
-
const sessionProfile = sessionPair
|
|
833
|
-
? { provider: sessionPair.provider, model: sessionPair.modelId, thinking: sessionPair.thinking }
|
|
834
|
-
: undefined;
|
|
835
|
-
const effectiveProfile = sessionProfile ?? configuredProfile;
|
|
836
|
-
const localValue = isCognitiveRoutingProfile(localProfile) ? localProfile : LOCAL_INHERIT;
|
|
837
|
-
const value =
|
|
838
|
-
options.scope === "session"
|
|
839
|
-
? (sessionProfile ?? LOCAL_INHERIT)
|
|
840
|
-
: options.scope === "local"
|
|
841
|
-
? localValue
|
|
842
|
-
: repositoryProfile;
|
|
843
|
-
const configuredSource =
|
|
844
|
-
options.capabilityState?.profileSources?.[options.name] ??
|
|
845
|
-
(isCognitiveRoutingProfile(repositoryProfile) ? "repository" : undefined);
|
|
846
|
-
const source = options.scope === "session" ? (sessionProfile ? "session" : configuredSource) : configuredSource;
|
|
847
|
-
const models = cognitiveRoutingModelOptions(options.ctx);
|
|
848
|
-
const inactive = options.scope !== "repository" && models.length === 0 && value !== LOCAL_INHERIT;
|
|
849
|
-
const inheritChoice =
|
|
850
|
-
options.scope === "session"
|
|
851
|
-
? {
|
|
852
|
-
label: "Inherit configured preset",
|
|
853
|
-
description: "Remove the session override without changing local or repository configuration.",
|
|
854
|
-
summary: "inherit configured preset",
|
|
855
|
-
}
|
|
856
|
-
: undefined;
|
|
857
|
-
|
|
858
|
-
return {
|
|
859
|
-
id: `freeflow.cognitiveRouting.${options.name}`,
|
|
860
|
-
label: `${options.name.charAt(0).toUpperCase()}${options.name.slice(1)} preset`,
|
|
861
|
-
description:
|
|
862
|
-
options.scope === "session"
|
|
863
|
-
? "Temporarily choose the model and effort for this profile in the current Pi session only."
|
|
864
|
-
: "Choose an authenticated available model and one effort supported by that model. Confirming writes the complete preset; cancel leaves it unchanged.",
|
|
865
|
-
path,
|
|
866
|
-
kind: "string",
|
|
867
|
-
value,
|
|
868
|
-
format: (current) => {
|
|
869
|
-
const inherits = current === LOCAL_INHERIT || (options.scope === "session" && current === undefined);
|
|
870
|
-
return inherits
|
|
871
|
-
? options.scope === "session"
|
|
872
|
-
? "inherit configured preset"
|
|
873
|
-
: "inherit repository preset"
|
|
874
|
-
: cognitiveRoutingProfileDisplay(current);
|
|
875
|
-
},
|
|
876
|
-
configScope: options.scope,
|
|
877
|
-
effectiveValue: effectiveProfile,
|
|
878
|
-
effectiveSource: cognitiveRoutingSettingsSource(source),
|
|
879
|
-
inheritedValue: options.scope === "session" ? configuredProfile : repositoryProfile,
|
|
880
|
-
inheritedSource: cognitiveRoutingSettingsSource(configuredSource ?? "builtin"),
|
|
881
|
-
inactive,
|
|
882
|
-
displaySuffix: cognitiveRoutingProfileDisplaySuffix(source, options.scope),
|
|
883
|
-
wizard: () => createCognitiveRoutingProfileWizard(value, models, options.scope !== "repository", inheritChoice),
|
|
884
|
-
};
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
function isCognitiveRoutingRuntimeAvailable(pi: any): boolean {
|
|
888
|
-
return (
|
|
889
|
-
!isPiFlowHost(pi?.host) &&
|
|
890
|
-
typeof pi?.appendEntry === "function" &&
|
|
891
|
-
typeof pi?.setModel === "function" &&
|
|
892
|
-
typeof pi?.setThinkingLevel === "function"
|
|
893
|
-
);
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
function freeflowItems(
|
|
897
|
-
rawConfig: Record<string, unknown>,
|
|
898
|
-
options: {
|
|
899
|
-
scope?: ConfigScope;
|
|
900
|
-
layers?: Awaited<ReturnType<typeof readFreeflowConfigLayers>>;
|
|
901
|
-
cognitiveRouting?: CognitiveRoutingCapabilityState;
|
|
902
|
-
ctx?: any;
|
|
903
|
-
runtimeAvailable?: boolean;
|
|
904
|
-
} = {},
|
|
905
|
-
): SettingsItem[] {
|
|
906
|
-
const scope: Exclude<ConfigScope, "session"> = options.scope === "local" ? "local" : "repository";
|
|
907
|
-
const layers = options.layers;
|
|
908
|
-
const { localConfig, core, sources } = resolveSettingsCoreView(rawConfig, layers);
|
|
909
|
-
|
|
910
|
-
const freeflowItem = createScopedBooleanItem({
|
|
911
|
-
scope,
|
|
912
|
-
rawConfig,
|
|
913
|
-
localConfig,
|
|
914
|
-
id: "freeflow.enabled",
|
|
915
|
-
label: "Freeflow",
|
|
916
|
-
description: "Master switch for Freeflow's core guidance, skills, and optional capabilities in this checkout.",
|
|
917
|
-
path: ["enabled"],
|
|
918
|
-
effectiveValue: core.enabled,
|
|
919
|
-
effectiveSource: sources.enabled,
|
|
920
|
-
defaultValue: DEFAULT_FREEFLOW_ENABLED,
|
|
921
|
-
});
|
|
922
|
-
const contextVirtualizationItem = createScopedBooleanItem({
|
|
923
|
-
scope,
|
|
924
|
-
rawConfig,
|
|
925
|
-
localConfig,
|
|
926
|
-
id: "freeflow.contextVirtualization",
|
|
927
|
-
label: "Context Virtualization",
|
|
928
|
-
description: "Let the model archive consumed tool results from future context while preserving session history.",
|
|
929
|
-
path: ["contextVirtualization"],
|
|
930
|
-
effectiveValue: core.contextVirtualization,
|
|
931
|
-
effectiveSource: sources.contextVirtualization,
|
|
932
|
-
defaultValue: DEFAULT_CONTEXT_VIRTUALIZATION_ENABLED,
|
|
933
|
-
});
|
|
934
|
-
const conversationHistoryItem = createScopedBooleanItem({
|
|
935
|
-
scope,
|
|
936
|
-
rawConfig,
|
|
937
|
-
localConfig,
|
|
938
|
-
id: "freeflow.conversationHistory",
|
|
939
|
-
label: "Conversation History",
|
|
940
|
-
description: "Let the model search and retrieve hidden conversation history on the active branch.",
|
|
941
|
-
path: ["conversationHistory"],
|
|
942
|
-
effectiveValue: core.conversationHistory,
|
|
943
|
-
effectiveSource: sources.conversationHistory,
|
|
944
|
-
defaultValue: DEFAULT_CONVERSATION_HISTORY_ENABLED,
|
|
945
|
-
});
|
|
946
|
-
|
|
947
|
-
const freeflowInactive = !core.enabled;
|
|
948
|
-
contextVirtualizationItem.inactive = freeflowInactive;
|
|
949
|
-
conversationHistoryItem.inactive = freeflowInactive;
|
|
950
|
-
|
|
951
|
-
const cognitiveRoutingState = options.cognitiveRouting;
|
|
952
|
-
const cognitiveRoutingRuntimeDisabled = options.runtimeAvailable !== true;
|
|
953
|
-
const cognitiveRoutingGroup = (() => {
|
|
954
|
-
const cognitiveRoutingEnabledItem = createScopedBooleanItem({
|
|
955
|
-
scope,
|
|
956
|
-
rawConfig,
|
|
957
|
-
localConfig,
|
|
958
|
-
id: "freeflow.cognitiveRouting.enabled",
|
|
959
|
-
label: "Enabled",
|
|
960
|
-
description: cognitiveRoutingRuntimeDisabled
|
|
961
|
-
? "Requires a host model-state control API; configuration is read-only on this host."
|
|
962
|
-
: "Allow Cognitive Routing to manage explicit assignments and request native profile transitions.",
|
|
963
|
-
path: ["cognitiveRouting", "enabled"],
|
|
964
|
-
effectiveValue: cognitiveRoutingState?.enabled ?? false,
|
|
965
|
-
effectiveSource: cognitiveRoutingSettingsSource(cognitiveRoutingState?.enabledSource),
|
|
966
|
-
defaultValue: false,
|
|
967
|
-
});
|
|
968
|
-
cognitiveRoutingEnabledItem.inactive = freeflowInactive || cognitiveRoutingRuntimeDisabled;
|
|
969
|
-
cognitiveRoutingEnabledItem.runtimeInactive = cognitiveRoutingRuntimeDisabled;
|
|
970
|
-
|
|
971
|
-
const cognitiveRoutingDelegationItem = createScopedDelegationItem({
|
|
972
|
-
scope,
|
|
973
|
-
rawConfig,
|
|
974
|
-
localConfig,
|
|
975
|
-
effectiveValue: cognitiveRoutingState?.delegation ?? "executor",
|
|
976
|
-
effectiveSource: cognitiveRoutingSettingsSource(cognitiveRoutingState?.delegationSource),
|
|
977
|
-
});
|
|
978
|
-
cognitiveRoutingDelegationItem.inactive = freeflowInactive || cognitiveRoutingRuntimeDisabled;
|
|
979
|
-
cognitiveRoutingDelegationItem.runtimeInactive = cognitiveRoutingRuntimeDisabled;
|
|
980
|
-
|
|
981
|
-
const cognitiveRoutingProjectionItem = createScopedBooleanItem({
|
|
982
|
-
scope,
|
|
983
|
-
rawConfig,
|
|
984
|
-
localConfig,
|
|
985
|
-
id: "freeflow.cognitiveRouting.projection",
|
|
986
|
-
label: "Context projection",
|
|
987
|
-
description:
|
|
988
|
-
"Select worker evidence for Coordinator; disable projection to keep ordinary Pi context in all enabled profiles.",
|
|
989
|
-
path: ["cognitiveRouting", "projection"],
|
|
990
|
-
effectiveValue: cognitiveRoutingState?.projection ?? false,
|
|
991
|
-
effectiveSource: cognitiveRoutingSettingsSource(cognitiveRoutingState?.projectionSource),
|
|
992
|
-
defaultValue: false,
|
|
993
|
-
});
|
|
994
|
-
cognitiveRoutingProjectionItem.inactive = freeflowInactive || cognitiveRoutingRuntimeDisabled;
|
|
995
|
-
cognitiveRoutingProjectionItem.runtimeInactive = cognitiveRoutingRuntimeDisabled;
|
|
996
|
-
|
|
997
|
-
const cognitiveRoutingProfiles = ["coordinator", "helper", "executor"].map((name) =>
|
|
998
|
-
cognitiveRoutingProfileItem({
|
|
999
|
-
name: name as CognitiveRoutingProfileName,
|
|
1000
|
-
scope,
|
|
1001
|
-
rawConfig,
|
|
1002
|
-
localConfig,
|
|
1003
|
-
capabilityState: cognitiveRoutingState,
|
|
1004
|
-
ctx: options.ctx,
|
|
1005
|
-
}),
|
|
1006
|
-
);
|
|
1007
|
-
for (const item of cognitiveRoutingProfiles) {
|
|
1008
|
-
item.inactive ||= freeflowInactive || cognitiveRoutingRuntimeDisabled;
|
|
1009
|
-
item.runtimeInactive = cognitiveRoutingRuntimeDisabled;
|
|
1010
|
-
}
|
|
1011
|
-
const cognitiveRoutingStatus = cognitiveRoutingRuntimeDisabled
|
|
1012
|
-
? "unavailable · host unsupported"
|
|
1013
|
-
: cognitiveRoutingState
|
|
1014
|
-
? cognitiveRoutingState.effective
|
|
1015
|
-
? "active"
|
|
1016
|
-
: cognitiveRoutingState.blockingReason?.code === "profile_missing"
|
|
1017
|
-
? "not configured"
|
|
1018
|
-
: (cognitiveRoutingState.blockingReason?.code ?? "inactive")
|
|
1019
|
-
: "unavailable";
|
|
1020
|
-
return {
|
|
1021
|
-
id: "freeflow.cognitiveRouting",
|
|
1022
|
-
label: "Cognitive Routing",
|
|
1023
|
-
description: cognitiveRoutingRuntimeDisabled
|
|
1024
|
-
? "Cognitive Routing configuration is visible for inspection but requires a host model-state control API."
|
|
1025
|
-
: "Configure Coordinator, Helper, and Executor profiles plus the workers available for delegation.",
|
|
1026
|
-
kind: "group" as const,
|
|
1027
|
-
value: cognitiveRoutingRuntimeDisabled ? false : (cognitiveRoutingState?.enabled ?? false),
|
|
1028
|
-
inactive: freeflowInactive,
|
|
1029
|
-
displaySuffix: cognitiveRoutingStatus,
|
|
1030
|
-
children: [
|
|
1031
|
-
cognitiveRoutingEnabledItem,
|
|
1032
|
-
cognitiveRoutingDelegationItem,
|
|
1033
|
-
...cognitiveRoutingProfiles,
|
|
1034
|
-
cognitiveRoutingProjectionItem,
|
|
1035
|
-
],
|
|
1036
|
-
} satisfies SettingsItem;
|
|
1037
|
-
})();
|
|
1038
|
-
|
|
1039
|
-
return [
|
|
1040
|
-
freeflowItem,
|
|
1041
|
-
...(cognitiveRoutingGroup ? [cognitiveRoutingGroup] : []),
|
|
1042
|
-
{
|
|
1043
|
-
id: "freeflow.context",
|
|
1044
|
-
label: "Freeflow Context",
|
|
1045
|
-
description: "Choose which context projection and conversation-history operations are available to the model.",
|
|
1046
|
-
kind: "group",
|
|
1047
|
-
value: contextVirtualizationItem.effectiveValue === true || conversationHistoryItem.effectiveValue === true,
|
|
1048
|
-
displaySuffix: `${[contextVirtualizationItem, conversationHistoryItem].filter((item) => item.effectiveValue === true).length}/2 enabled`,
|
|
1049
|
-
children: [contextVirtualizationItem, conversationHistoryItem],
|
|
1050
|
-
},
|
|
1051
|
-
];
|
|
1052
|
-
}
|
|
1053
|
-
|
|
1054
|
-
function pruneKnownDefaults(config: Record<string, unknown>) {
|
|
1055
|
-
const defaultPaths: Array<{ path: string[]; value: unknown }> = [
|
|
1056
|
-
{ path: ["enabled"], value: DEFAULT_FREEFLOW_ENABLED },
|
|
1057
|
-
{ path: ["contextVirtualization"], value: DEFAULT_CONTEXT_VIRTUALIZATION_ENABLED },
|
|
1058
|
-
{ path: ["conversationHistory"], value: DEFAULT_CONVERSATION_HISTORY_ENABLED },
|
|
1059
|
-
{ path: ["cognitiveRouting", "delegation"], value: "executor" },
|
|
1060
|
-
];
|
|
1061
|
-
|
|
1062
|
-
for (const item of defaultPaths) {
|
|
1063
|
-
if (valuesEqual(getPath(config, item.path), item.value)) {
|
|
1064
|
-
deletePath(config, item.path);
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
async function ensureLocalConfigIgnored(cwd: string) {
|
|
1070
|
-
let gitPath: string;
|
|
1071
|
-
try {
|
|
1072
|
-
const result = await execFileAsync("git", ["-C", cwd, "rev-parse", "--git-path", "info/exclude"]);
|
|
1073
|
-
gitPath = result.stdout.trim();
|
|
1074
|
-
} catch {
|
|
1075
|
-
return;
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
const tracked = await execFileAsync("git", ["-C", cwd, "ls-files", "--", ".freeflow/local.json"]);
|
|
1079
|
-
if (tracked.stdout.trim()) {
|
|
1080
|
-
throw new Error(
|
|
1081
|
-
".freeflow/local.json is tracked by git; remove it from the index before writing personal overrides.",
|
|
1082
|
-
);
|
|
1083
|
-
}
|
|
1084
|
-
|
|
1085
|
-
try {
|
|
1086
|
-
await execFileAsync("git", ["-C", cwd, "check-ignore", "-q", "--", ".freeflow/local.json"]);
|
|
1087
|
-
return;
|
|
1088
|
-
} catch {
|
|
1089
|
-
// Add a local exclude when the repository does not already ignore the file.
|
|
1090
|
-
}
|
|
1091
|
-
|
|
1092
|
-
const excludePath = isAbsolute(gitPath) ? gitPath : resolve(cwd, gitPath);
|
|
1093
|
-
let existing = "";
|
|
1094
|
-
try {
|
|
1095
|
-
existing = await readFile(excludePath, "utf8");
|
|
1096
|
-
} catch {
|
|
1097
|
-
// The git metadata path may not exist yet in a minimal repository.
|
|
1098
|
-
}
|
|
1099
|
-
const rule = ".freeflow/local.json";
|
|
1100
|
-
if (existing.split(/\r?\n/).includes(rule)) return;
|
|
1101
|
-
await mkdir(dirname(excludePath), { recursive: true });
|
|
1102
|
-
const prefix = existing && !existing.endsWith("\n") ? "\n" : "";
|
|
1103
|
-
await appendFile(excludePath, `${prefix}${rule}\n`, "utf8");
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
async function updateConfig(
|
|
1107
|
-
cwd: string,
|
|
1108
|
-
item: SettingsItem,
|
|
1109
|
-
value: unknown,
|
|
1110
|
-
scope: ConfigScope = item.configScope ?? "repository",
|
|
1111
|
-
) {
|
|
1112
|
-
if (scope === "local") {
|
|
1113
|
-
if (!item.path?.length) {
|
|
1114
|
-
throw new Error(`${item.label} is a settings group, not a writable setting.`);
|
|
1115
|
-
}
|
|
1116
|
-
const current = await readFreeflowLocalConfig(cwd);
|
|
1117
|
-
const next = cloneJson(current) as Record<string, unknown>;
|
|
1118
|
-
const configValue = configValueForChoice(item, value);
|
|
1119
|
-
const previousValue = getPath(current, item.path);
|
|
1120
|
-
if (configValue === undefined || isEmptyValue(configValue)) {
|
|
1121
|
-
if (previousValue === undefined) return;
|
|
1122
|
-
deletePath(next, item.path);
|
|
1123
|
-
} else {
|
|
1124
|
-
setPath(next, item.path, configValue);
|
|
1125
|
-
}
|
|
1126
|
-
const path = join(cwd, ".freeflow/local.json");
|
|
1127
|
-
await ensureLocalConfigIgnored(cwd);
|
|
1128
|
-
if (Object.keys(next).length === 0) {
|
|
1129
|
-
await rm(path, { force: true });
|
|
1130
|
-
return;
|
|
1131
|
-
}
|
|
1132
|
-
await mkdir(join(cwd, ".freeflow"), { recursive: true });
|
|
1133
|
-
await writeFile(path, `${JSON.stringify(next, null, 2)}\n`, "utf8");
|
|
1134
|
-
return;
|
|
1135
|
-
}
|
|
1136
|
-
|
|
1137
|
-
const current = await readFreeflowConfig(cwd);
|
|
1138
|
-
const next = cloneJson(current) as Record<string, unknown>;
|
|
1139
|
-
setConfigValue(next, item, value);
|
|
1140
|
-
pruneKnownDefaults(next);
|
|
1141
|
-
await mkdir(join(cwd, ".freeflow"), { recursive: true });
|
|
1142
|
-
await writeFile(join(cwd, ".freeflow/config.json"), `${JSON.stringify(next, null, 2)}\n`, "utf8");
|
|
1143
|
-
}
|
|
1144
|
-
|
|
1145
|
-
function isCognitiveRoutingProfileItem(item: SettingsItem): boolean {
|
|
1146
|
-
return ["coordinator", "helper", "executor"].some((profile) => item.id === `freeflow.cognitiveRouting.${profile}`);
|
|
1147
|
-
}
|
|
1148
|
-
|
|
1149
|
-
function valueForDisplay(item: SettingsItem): string {
|
|
1150
|
-
let value: string;
|
|
1151
|
-
if (isCognitiveRoutingProfileItem(item) && item.format) {
|
|
1152
|
-
value = item.format(effectiveItemValue(item));
|
|
1153
|
-
} else if (item.configScope === "local" || item.configScope === "session") {
|
|
1154
|
-
value = formatCoreValue(effectiveItemValue(item));
|
|
1155
|
-
} else if (item.kind === "boolean") {
|
|
1156
|
-
value = booleanValue(item.value);
|
|
1157
|
-
} else if (item.kind === "group") {
|
|
1158
|
-
const status = typeof item.value === "boolean" ? booleanValue(item.value) : String(item.value ?? "");
|
|
1159
|
-
const count = item.children?.length ?? 0;
|
|
1160
|
-
value = count > 0 ? `${status} (${count})` : status;
|
|
1161
|
-
} else if (item.format) {
|
|
1162
|
-
value = item.format(item.value);
|
|
1163
|
-
} else {
|
|
1164
|
-
value = String(item.value ?? "");
|
|
1165
|
-
}
|
|
1166
|
-
return item.displaySuffix ? `${value} ${item.displaySuffix}` : value;
|
|
1167
|
-
}
|
|
1168
|
-
|
|
1169
|
-
function walkSettingsItems(items: SettingsItem[], visitor: (item: SettingsItem) => void) {
|
|
1170
|
-
for (const item of items) {
|
|
1171
|
-
visitor(item);
|
|
1172
|
-
if (item.children) walkSettingsItems(item.children, visitor);
|
|
1173
|
-
}
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
|
-
function findSettingsItem(items: SettingsItem[], id: string): SettingsItem | undefined {
|
|
1177
|
-
for (const item of items) {
|
|
1178
|
-
if (item.id === id) return item;
|
|
1179
|
-
const child = item.children ? findSettingsItem(item.children, id) : undefined;
|
|
1180
|
-
if (child) return child;
|
|
1181
|
-
}
|
|
1182
|
-
return undefined;
|
|
1183
|
-
}
|
|
1184
|
-
|
|
1185
|
-
function refreshSettingsDerivedState(items: SettingsItem[]) {
|
|
1186
|
-
const freeflowItem = findSettingsItem(items, "freeflow.enabled");
|
|
1187
|
-
const freeflowInactive = freeflowItem ? effectiveItemValue(freeflowItem) !== true : false;
|
|
1188
|
-
const cognitiveRoutingGroup = findSettingsItem(items, "freeflow.cognitiveRouting");
|
|
1189
|
-
const cognitiveRoutingEnabledItem = findSettingsItem(items, "freeflow.cognitiveRouting.enabled");
|
|
1190
|
-
const cognitiveRoutingDelegationItem = findSettingsItem(items, "freeflow.cognitiveRouting.delegation");
|
|
1191
|
-
const delegation = (
|
|
1192
|
-
cognitiveRoutingDelegationItem ? effectiveItemValue(cognitiveRoutingDelegationItem) : "executor"
|
|
1193
|
-
) as CognitiveRoutingDelegationMode;
|
|
1194
|
-
const cognitiveRoutingProfiles = ["coordinator", ...workersForDelegation(delegation)].map((profile) =>
|
|
1195
|
-
findSettingsItem(items, `freeflow.cognitiveRouting.${profile}`),
|
|
1196
|
-
);
|
|
1197
|
-
|
|
1198
|
-
if (cognitiveRoutingGroup && cognitiveRoutingEnabledItem) {
|
|
1199
|
-
const enabled = effectiveItemValue(cognitiveRoutingEnabledItem) === true;
|
|
1200
|
-
const profilesConfigured = cognitiveRoutingProfiles.every((item) =>
|
|
1201
|
-
item ? isCognitiveRoutingProfile(effectiveItemValue(item)) : false,
|
|
1202
|
-
);
|
|
1203
|
-
cognitiveRoutingGroup.value = enabled;
|
|
1204
|
-
cognitiveRoutingGroup.displaySuffix = enabled
|
|
1205
|
-
? profilesConfigured
|
|
1206
|
-
? cognitiveRoutingGroup.displaySuffix === "active"
|
|
1207
|
-
? "active"
|
|
1208
|
-
: "configured"
|
|
1209
|
-
: "not configured"
|
|
1210
|
-
: "disabled";
|
|
1211
|
-
cognitiveRoutingGroup.inactive = freeflowInactive;
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
const contextGroup = findSettingsItem(items, "freeflow.context");
|
|
1215
|
-
if (contextGroup?.children?.length) {
|
|
1216
|
-
const enabledCount = contextGroup.children.filter((item) => effectiveItemValue(item) === true).length;
|
|
1217
|
-
contextGroup.value = enabledCount > 0;
|
|
1218
|
-
contextGroup.displaySuffix = `${enabledCount}/${contextGroup.children.length} enabled`;
|
|
1219
|
-
}
|
|
1220
|
-
|
|
1221
|
-
walkSettingsItems(items, (candidate) => {
|
|
1222
|
-
if (candidate.id === "freeflow.session.reset") {
|
|
1223
|
-
candidate.inactive = false;
|
|
1224
|
-
} else if (candidate.configScope) {
|
|
1225
|
-
const inactive =
|
|
1226
|
-
candidate.id === "freeflow.enabled" ? false : candidate.runtimeInactive === true || freeflowInactive;
|
|
1227
|
-
candidate.inactive = inactive;
|
|
1228
|
-
candidate.displaySuffix = coreDisplaySuffix(candidate, inactive);
|
|
1229
|
-
} else {
|
|
1230
|
-
candidate.inactive = candidate.runtimeInactive === true || freeflowInactive;
|
|
1231
|
-
}
|
|
1232
|
-
});
|
|
1233
|
-
}
|
|
1234
|
-
|
|
1235
|
-
function settingsChoices(item: SettingsItem) {
|
|
1236
|
-
if (item.kind === "boolean") {
|
|
1237
|
-
return [
|
|
1238
|
-
{ key: "true", value: true, label: "enabled", description: "Enable this setting." },
|
|
1239
|
-
{ key: "false", value: false, label: "disabled", description: "Disable this setting." },
|
|
1240
|
-
];
|
|
1241
|
-
}
|
|
1242
|
-
if (item.kind !== "enum") return undefined;
|
|
1243
|
-
return (item.values ?? []).map((value) => ({
|
|
1244
|
-
key: value,
|
|
1245
|
-
value,
|
|
1246
|
-
label: item.valueLabels?.[value] ?? value,
|
|
1247
|
-
description: item.valueDescriptions?.[value],
|
|
1248
|
-
}));
|
|
1249
|
-
}
|
|
1250
|
-
|
|
1251
|
-
function resetSessionItemState(items: SettingsItem[]) {
|
|
1252
|
-
walkSettingsItems(items, (item) => {
|
|
1253
|
-
if (item.configScope === "session") {
|
|
1254
|
-
item.value = LOCAL_INHERIT;
|
|
1255
|
-
item.effectiveValue = item.inheritedValue;
|
|
1256
|
-
item.effectiveSource = item.inheritedSource ?? "builtin";
|
|
1257
|
-
} else if (item.id === "freeflow.sessionMode") {
|
|
1258
|
-
item.value = "default";
|
|
1259
|
-
}
|
|
1260
|
-
});
|
|
1261
|
-
}
|
|
1262
|
-
|
|
1263
|
-
function settingsEntries(
|
|
1264
|
-
items: SettingsItem[],
|
|
1265
|
-
rootItems: SettingsItem[],
|
|
1266
|
-
onChange: OpenSettingsOptions["onChange"],
|
|
1267
|
-
): SettingsEntry[] {
|
|
1268
|
-
return items.map((item) => ({
|
|
1269
|
-
id: item.id,
|
|
1270
|
-
label: item.label,
|
|
1271
|
-
description: item.description,
|
|
1272
|
-
currentValue: () => valueForDisplay(item),
|
|
1273
|
-
inactive: () => item.inactive === true,
|
|
1274
|
-
currentChoiceKey: () => String(item.value),
|
|
1275
|
-
choices: settingsChoices(item),
|
|
1276
|
-
children: item.children ? () => settingsEntries(item.children!, rootItems, onChange) : undefined,
|
|
1277
|
-
wizard: item.wizard ? () => item.wizard!() : undefined,
|
|
1278
|
-
edit:
|
|
1279
|
-
!item.children && !["boolean", "enum", "group"].includes(item.kind)
|
|
1280
|
-
? {
|
|
1281
|
-
initialValue: () => valueForDisplay(item),
|
|
1282
|
-
parse: (text: string) => (item.parse ? item.parse(text) : text),
|
|
1283
|
-
}
|
|
1284
|
-
: undefined,
|
|
1285
|
-
commit:
|
|
1286
|
-
item.kind === "group"
|
|
1287
|
-
? undefined
|
|
1288
|
-
: async (value: unknown) => {
|
|
1289
|
-
if (valuesEqual(item.value, value)) return { changed: false, reloadRequired: false };
|
|
1290
|
-
const outcome = await onChange(item, value);
|
|
1291
|
-
if (outcome.changed) {
|
|
1292
|
-
updateScopedItemState(item, value);
|
|
1293
|
-
if (item.id === "freeflow.session.reset") {
|
|
1294
|
-
resetSessionItemState(rootItems);
|
|
1295
|
-
} else if (!item.transient) {
|
|
1296
|
-
item.value = value;
|
|
1297
|
-
}
|
|
1298
|
-
refreshSettingsDerivedState(rootItems);
|
|
1299
|
-
}
|
|
1300
|
-
return outcome;
|
|
1301
|
-
},
|
|
1302
|
-
}));
|
|
1303
|
-
}
|
|
1304
|
-
|
|
1305
|
-
async function openSettings(options: OpenSettingsOptions): Promise<SettingsSessionResult> {
|
|
1306
|
-
if (typeof options.ctx?.ui?.custom !== "function") {
|
|
1307
|
-
options.ctx?.ui?.notify?.(
|
|
1308
|
-
`${options.title} requires Pi TUI mode. Use the status command for a compact summary.`,
|
|
1309
|
-
"warning",
|
|
1310
|
-
);
|
|
1311
|
-
return { changed: false, configChanged: false, failed: false };
|
|
1312
|
-
}
|
|
1313
|
-
|
|
1314
|
-
const entries = settingsEntries(options.items, options.items, options.onChange);
|
|
1315
|
-
const initialChoice = options.initialChoice
|
|
1316
|
-
? entries.find((entry) => entry.id === options.initialChoice?.id)
|
|
1317
|
-
: undefined;
|
|
1318
|
-
let component: PiSettingsComponent | undefined;
|
|
1319
|
-
await options.ctx.ui.custom((tui: any, theme: any, _keybindings: unknown, done: (value?: undefined) => void) => {
|
|
1320
|
-
component = new PiSettingsComponent({
|
|
1321
|
-
title: options.title,
|
|
1322
|
-
entries,
|
|
1323
|
-
initialChoice,
|
|
1324
|
-
theme: theme ?? {},
|
|
1325
|
-
requestRender: () => tui.requestRender(),
|
|
1326
|
-
notify: (message, level) => options.ctx?.ui?.notify?.(message, level),
|
|
1327
|
-
done,
|
|
1328
|
-
});
|
|
1329
|
-
return component;
|
|
1330
|
-
});
|
|
1331
|
-
|
|
1332
|
-
await component?.waitForWrites();
|
|
1333
|
-
return component?.sessionResult() ?? { changed: false, configChanged: false, failed: false };
|
|
1334
|
-
}
|
|
1335
|
-
|
|
1336
|
-
function freeflowStatusText(
|
|
1337
|
-
state: Awaited<ReturnType<typeof readCapabilityState>>,
|
|
1338
|
-
cognitiveRoutingController?: CognitiveRoutingSettingsController,
|
|
1339
|
-
): string {
|
|
1340
|
-
if (!state.configured) {
|
|
1341
|
-
return state.configExists
|
|
1342
|
-
? `Freeflow: inactive (invalid config: ${state.parseError ?? "unknown parse error"}); run /setup-freeflow or fix .freeflow/config.json`
|
|
1343
|
-
: "Freeflow: inactive (repo not set up); run /setup-freeflow";
|
|
1344
|
-
}
|
|
1345
|
-
const sessionSuffix = (source: ConfigSource) => (source === "session" ? " (session override)" : "");
|
|
1346
|
-
const routingState = cognitiveRoutingController?.state();
|
|
1347
|
-
const cognitiveRouting = state.cognitiveRouting;
|
|
1348
|
-
const cognitiveRoutingStatus = cognitiveRouting
|
|
1349
|
-
? cognitiveRouting.blockingReason?.code === "runtime_disabled"
|
|
1350
|
-
? "unavailable (host unsupported)"
|
|
1351
|
-
: cognitiveRouting.effective
|
|
1352
|
-
? routingState?.effective
|
|
1353
|
-
? `active (${routingState.activeProfile ?? "unknown"}, ${routingState.controlMode})`
|
|
1354
|
-
: "effective (inactive)"
|
|
1355
|
-
: cognitiveRouting.enabled
|
|
1356
|
-
? `blocked (${cognitiveRouting.blockingReason.code})`
|
|
1357
|
-
: "disabled"
|
|
1358
|
-
: undefined;
|
|
1359
|
-
const contextEnabled = state.contextVirtualization?.effective || state.conversationHistory?.effective;
|
|
1360
|
-
return [
|
|
1361
|
-
`Freeflow: ${state.enabled ? "enabled" : "disabled"}${sessionSuffix(state.configSources.enabled as ConfigSource)}`,
|
|
1362
|
-
`context: ${contextEnabled ? "enabled" : "disabled"} (virtualization ${state.contextVirtualization?.effective ? "enabled" : "disabled"}, history ${state.conversationHistory?.effective ? "enabled" : "disabled"})`,
|
|
1363
|
-
...(cognitiveRoutingStatus ? [`cognitive routing: ${cognitiveRoutingStatus}`] : []),
|
|
1364
|
-
].join("; ");
|
|
1365
|
-
}
|
|
1366
|
-
|
|
1367
|
-
const NON_TUI_SETTINGS_GUIDANCE =
|
|
1368
|
-
"Freeflow settings require Pi TUI mode. Use /freeflow status to inspect current state; supported non-TUI changes are /freeflow enable and /freeflow disable.";
|
|
1369
|
-
|
|
1370
|
-
function ensureSettingsIdle(ctx: any): boolean {
|
|
1371
|
-
if (typeof ctx?.isIdle !== "function" || ctx.isIdle()) return true;
|
|
1372
|
-
ctx.ui?.notify?.("Freeflow settings and profile changes are available only while Pi is idle.", "warning");
|
|
1373
|
-
return false;
|
|
1374
|
-
}
|
|
1375
|
-
|
|
1376
|
-
function nonTuiGuidance(ctx: any, message: string) {
|
|
1377
|
-
if (ctx?.hasUI === false) throw new Error(message);
|
|
1378
|
-
ctx?.ui?.notify?.(message, "warning");
|
|
1379
|
-
return { changed: false, reloaded: false, error: "tui_required" };
|
|
1380
|
-
}
|
|
1381
|
-
|
|
1382
|
-
async function finalizeSettingsSession(
|
|
1383
|
-
session: SettingsSessionResult,
|
|
1384
|
-
ctx: any,
|
|
1385
|
-
afterChange: AfterChange,
|
|
1386
|
-
savedMessage: string,
|
|
1387
|
-
reloadWarning = "Run /reload for Freeflow changes to fully apply.",
|
|
1388
|
-
afterChangeOptions: AfterChangeOptions = {},
|
|
1389
|
-
) {
|
|
1390
|
-
if (!session.configChanged) return false;
|
|
1391
|
-
|
|
1392
|
-
await afterChange(true, afterChangeOptions);
|
|
1393
|
-
if (session.failed) return false;
|
|
1394
|
-
|
|
1395
|
-
ctx.ui.notify(savedMessage, "info");
|
|
1396
|
-
if (typeof ctx.reload !== "function") {
|
|
1397
|
-
ctx.ui.notify(reloadWarning, "warning");
|
|
1398
|
-
return false;
|
|
1399
|
-
}
|
|
1400
|
-
|
|
1401
|
-
await ctx.reload();
|
|
1402
|
-
return true;
|
|
1403
|
-
}
|
|
1404
|
-
|
|
1405
|
-
async function finalizeSessionSettings(session: SettingsSessionResult, ctx: any, afterChange: AfterChange) {
|
|
1406
|
-
if (!session.changed) return false;
|
|
1407
|
-
|
|
1408
|
-
await afterChange(true);
|
|
1409
|
-
if (session.failed) return false;
|
|
1410
|
-
if (!session.configChanged) {
|
|
1411
|
-
ctx.ui.notify("Freeflow session overrides updated.", "info");
|
|
1412
|
-
return false;
|
|
1413
|
-
}
|
|
1414
|
-
|
|
1415
|
-
ctx.ui.notify("Freeflow session overrides updated. Reloading skills and resources...", "info");
|
|
1416
|
-
if (typeof ctx.reload !== "function") {
|
|
1417
|
-
ctx.ui.notify("Run /reload for Freeflow session overrides to fully apply.", "warning");
|
|
1418
|
-
return false;
|
|
1419
|
-
}
|
|
1420
|
-
|
|
1421
|
-
await ctx.reload();
|
|
1422
|
-
return true;
|
|
1423
|
-
}
|
|
1424
|
-
|
|
1425
|
-
export async function handleFreeflowCommand(
|
|
1426
|
-
args: string | undefined,
|
|
1427
|
-
ctx: any,
|
|
1428
|
-
afterChange: AfterChange,
|
|
1429
|
-
pi: any,
|
|
1430
|
-
cognitiveRoutingController?: CognitiveRoutingSettingsController,
|
|
1431
|
-
) {
|
|
1432
|
-
const input = (args ?? "settings").trim().toLowerCase() || "settings";
|
|
1433
|
-
const [action, ...rest] = input.split(/\s+/);
|
|
1434
|
-
const actionValue = rest.join(" ");
|
|
1435
|
-
const nonTui = ctx?.mode && ctx.mode !== "tui";
|
|
1436
|
-
const settingsSelector =
|
|
1437
|
-
action === "settings" &&
|
|
1438
|
-
(!actionValue || ["session", "local", "personal", "repo", "repository", "shared"].includes(actionValue));
|
|
1439
|
-
if (nonTui && settingsSelector) {
|
|
1440
|
-
return nonTuiGuidance(ctx, NON_TUI_SETTINGS_GUIDANCE);
|
|
1441
|
-
}
|
|
1442
|
-
|
|
1443
|
-
const [layers, state] = await Promise.all([
|
|
1444
|
-
readFreeflowConfigLayers(ctx.cwd),
|
|
1445
|
-
readCapabilityState(ctx.cwd, ctx, pi?.host),
|
|
1446
|
-
]);
|
|
1447
|
-
const configState = layers.repository;
|
|
1448
|
-
const raw = configState.valid ? configState.parsed : {};
|
|
1449
|
-
|
|
1450
|
-
if (action === "status") {
|
|
1451
|
-
ctx.ui.notify(freeflowStatusText(state, cognitiveRoutingController), "info");
|
|
1452
|
-
return { changed: false, reloaded: false };
|
|
1453
|
-
}
|
|
1454
|
-
|
|
1455
|
-
if (!configState.valid) {
|
|
1456
|
-
ctx.ui.notify(freeflowStatusText(state, cognitiveRoutingController), "warning");
|
|
1457
|
-
return { changed: false, reloaded: false, error: "not_configured" };
|
|
1458
|
-
}
|
|
1459
|
-
if (layers.local.exists && !layers.local.valid) {
|
|
1460
|
-
ctx.ui.notify(
|
|
1461
|
-
`.freeflow/local.json is invalid; repair or remove it before changing Freeflow settings. ${layers.local.parseError ?? ""}`.trim(),
|
|
1462
|
-
"warning",
|
|
1463
|
-
);
|
|
1464
|
-
return { changed: false, reloaded: false, error: "invalid_local_config" };
|
|
1465
|
-
}
|
|
1466
|
-
|
|
1467
|
-
if (["enable", "on", "true", "disable", "off", "false"].includes(action)) {
|
|
1468
|
-
if (!ensureSettingsIdle(ctx)) return { changed: false, reloaded: false, error: "busy" };
|
|
1469
|
-
const enabled = ["enable", "on", "true"].includes(action);
|
|
1470
|
-
const item = freeflowItems(raw, {
|
|
1471
|
-
scope: "repository",
|
|
1472
|
-
layers,
|
|
1473
|
-
cognitiveRouting: state.cognitiveRouting as CognitiveRoutingCapabilityState,
|
|
1474
|
-
ctx,
|
|
1475
|
-
runtimeAvailable: isCognitiveRoutingRuntimeAvailable(pi),
|
|
1476
|
-
}).find((candidate) => candidate.id === "freeflow.enabled")!;
|
|
1477
|
-
await updateConfig(ctx.cwd, item, enabled, "repository");
|
|
1478
|
-
await afterChange(true);
|
|
1479
|
-
ctx.ui.notify(`Freeflow ${enabled ? "enabled" : "disabled"}. Reloading Freeflow runtime...`, "info");
|
|
1480
|
-
if (typeof ctx.reload === "function") {
|
|
1481
|
-
await ctx.reload();
|
|
1482
|
-
return { changed: true, reloaded: true };
|
|
1483
|
-
}
|
|
1484
|
-
ctx.ui.notify("Run /reload for Freeflow changes to fully apply.", "warning");
|
|
1485
|
-
return { changed: true, reloaded: false };
|
|
1486
|
-
}
|
|
1487
|
-
|
|
1488
|
-
if (action && action !== "settings") {
|
|
1489
|
-
ctx.ui.notify(
|
|
1490
|
-
"Usage: /freeflow, /freeflow settings [local|repo], /freeflow status, /freeflow enable, or /freeflow disable",
|
|
1491
|
-
"warning",
|
|
1492
|
-
);
|
|
1493
|
-
return { changed: false, reloaded: false, error: "invalid_action" };
|
|
1494
|
-
}
|
|
1495
|
-
|
|
1496
|
-
let settingsScope: ConfigScope = "local";
|
|
1497
|
-
if (actionValue === "session") {
|
|
1498
|
-
settingsScope = "session";
|
|
1499
|
-
} else if (["repo", "repository", "shared"].includes(actionValue)) {
|
|
1500
|
-
settingsScope = "repository";
|
|
1501
|
-
} else if (actionValue && !["local", "personal"].includes(actionValue)) {
|
|
1502
|
-
ctx.ui.notify(
|
|
1503
|
-
"Usage: /freeflow settings, /freeflow settings session, /freeflow settings local, or /freeflow settings repo",
|
|
1504
|
-
"warning",
|
|
1505
|
-
);
|
|
1506
|
-
return { changed: false, reloaded: false, error: "invalid_scope" };
|
|
1507
|
-
}
|
|
1508
|
-
|
|
1509
|
-
if (!ensureSettingsIdle(ctx)) return { changed: false, reloaded: false, error: "busy" };
|
|
1510
|
-
|
|
1511
|
-
let reconcileCognitiveRouting = settingsScope === "session";
|
|
1512
|
-
const items =
|
|
1513
|
-
settingsScope === "session"
|
|
1514
|
-
? sessionFreeflowItems(state, cognitiveRoutingController, ctx)
|
|
1515
|
-
: freeflowItems(raw, {
|
|
1516
|
-
scope: settingsScope,
|
|
1517
|
-
layers,
|
|
1518
|
-
cognitiveRouting: state.cognitiveRouting as CognitiveRoutingCapabilityState,
|
|
1519
|
-
ctx,
|
|
1520
|
-
runtimeAvailable: isCognitiveRoutingRuntimeAvailable(pi),
|
|
1521
|
-
});
|
|
1522
|
-
const session = await openSettings({
|
|
1523
|
-
title:
|
|
1524
|
-
settingsScope === "session"
|
|
1525
|
-
? "Freeflow Settings · Session overrides"
|
|
1526
|
-
: settingsScope === "local"
|
|
1527
|
-
? "Freeflow Settings · Personal overrides"
|
|
1528
|
-
: "Freeflow Repository Settings · modifies .freeflow/config.json",
|
|
1529
|
-
items,
|
|
1530
|
-
ctx,
|
|
1531
|
-
onChange: async (item, value) => {
|
|
1532
|
-
if (!ensureSettingsIdle(ctx)) return { changed: false, reloadRequired: false };
|
|
1533
|
-
if (!isCognitiveRoutingRuntimeAvailable(pi) && item.id.startsWith("freeflow.cognitiveRouting.")) {
|
|
1534
|
-
ctx.ui.notify("Cognitive Routing is unavailable because this host lacks model-state controls.", "warning");
|
|
1535
|
-
return { changed: false, reloadRequired: false };
|
|
1536
|
-
}
|
|
1537
|
-
if (item.id === "freeflow.cognitiveRouting.profile") {
|
|
1538
|
-
if (!cognitiveRoutingController) {
|
|
1539
|
-
ctx.ui.notify("Cognitive Routing is unavailable for this session.", "warning");
|
|
1540
|
-
return { changed: false, reloadRequired: false };
|
|
1541
|
-
}
|
|
1542
|
-
const result =
|
|
1543
|
-
value === "auto"
|
|
1544
|
-
? await cognitiveRoutingController.setAutomaticControl("profile-settings")
|
|
1545
|
-
: await cognitiveRoutingController.setManualProfile(
|
|
1546
|
-
value as CognitiveRoutingProfileName,
|
|
1547
|
-
"profile-settings",
|
|
1548
|
-
);
|
|
1549
|
-
if ((result.status !== "automatic" && result.status !== "active") || result.reason) {
|
|
1550
|
-
ctx.ui.notify(
|
|
1551
|
-
`Cognitive Routing settings could not be applied: ${result.reason ?? result.status}.`,
|
|
1552
|
-
"warning",
|
|
1553
|
-
);
|
|
1554
|
-
return { changed: false, reloadRequired: false };
|
|
1555
|
-
}
|
|
1556
|
-
await afterChange(false);
|
|
1557
|
-
return { changed: true, reloadRequired: false };
|
|
1558
|
-
}
|
|
1559
|
-
if (item.id === "freeflow.session.reset") {
|
|
1560
|
-
const routingResult = cognitiveRoutingController
|
|
1561
|
-
? await cognitiveRoutingController.resetSessionProfileOverrides("Reset session overrides")
|
|
1562
|
-
: { status: "unchanged" };
|
|
1563
|
-
if (routingResult.status === "blocked" || routingResult.reason) {
|
|
1564
|
-
ctx.ui.notify(
|
|
1565
|
-
`Cognitive Routing session presets could not be reset: ${routingResult.reason ?? routingResult.status}.`,
|
|
1566
|
-
"warning",
|
|
1567
|
-
);
|
|
1568
|
-
return { changed: false, reloadRequired: false };
|
|
1569
|
-
}
|
|
1570
|
-
try {
|
|
1571
|
-
const result = await resetSessionOverrides(ctx, pi);
|
|
1572
|
-
return {
|
|
1573
|
-
changed: result.changed || routingResult.status !== "unchanged",
|
|
1574
|
-
reloadRequired: result.reloadRequired,
|
|
1575
|
-
};
|
|
1576
|
-
} catch (error) {
|
|
1577
|
-
ctx.ui.notify(
|
|
1578
|
-
`Routing presets were reset, but core/context session overrides could not be reset: ${
|
|
1579
|
-
error instanceof Error ? error.message : String(error)
|
|
1580
|
-
}`,
|
|
1581
|
-
"warning",
|
|
1582
|
-
);
|
|
1583
|
-
return {
|
|
1584
|
-
changed: routingResult.status !== "unchanged",
|
|
1585
|
-
reloadRequired: false,
|
|
1586
|
-
};
|
|
1587
|
-
}
|
|
1588
|
-
}
|
|
1589
|
-
if (item.configScope === "session" && isCognitiveRoutingProfileItem(item)) {
|
|
1590
|
-
if (!cognitiveRoutingController) {
|
|
1591
|
-
ctx.ui.notify("Cognitive Routing is unavailable for this session.", "warning");
|
|
1592
|
-
return { changed: false, reloadRequired: false };
|
|
1593
|
-
}
|
|
1594
|
-
const profile = item.id.split(".").at(-1) as CognitiveRoutingProfileName;
|
|
1595
|
-
const override =
|
|
1596
|
-
value === undefined || value === LOCAL_INHERIT
|
|
1597
|
-
? null
|
|
1598
|
-
: {
|
|
1599
|
-
provider: (value as CognitiveRoutingProfile).provider,
|
|
1600
|
-
modelId: (value as CognitiveRoutingProfile).model,
|
|
1601
|
-
thinking: (value as CognitiveRoutingProfile).thinking,
|
|
1602
|
-
};
|
|
1603
|
-
const result = await cognitiveRoutingController.setSessionProfileOverride(
|
|
1604
|
-
profile,
|
|
1605
|
-
override,
|
|
1606
|
-
"Session profile settings",
|
|
1607
|
-
);
|
|
1608
|
-
if (result.status === "blocked" || result.reason) {
|
|
1609
|
-
ctx.ui.notify(
|
|
1610
|
-
`Cognitive Routing session preset could not be applied: ${result.reason ?? result.status}.`,
|
|
1611
|
-
"warning",
|
|
1612
|
-
);
|
|
1613
|
-
return { changed: false, reloadRequired: false };
|
|
1614
|
-
}
|
|
1615
|
-
await afterChange(false);
|
|
1616
|
-
return { changed: result.status !== "unchanged", reloadRequired: false };
|
|
1617
|
-
}
|
|
1618
|
-
if (item.configScope === "session") {
|
|
1619
|
-
const keyById = {
|
|
1620
|
-
"freeflow.enabled": "enabled",
|
|
1621
|
-
"freeflow.contextVirtualization": "contextVirtualization",
|
|
1622
|
-
"freeflow.conversationHistory": "conversationHistory",
|
|
1623
|
-
} as const;
|
|
1624
|
-
const key = keyById[item.id as keyof typeof keyById];
|
|
1625
|
-
const override = value === LOCAL_INHERIT ? null : value === "true";
|
|
1626
|
-
const result = await setSessionCoreOverride(key, override, ctx, pi);
|
|
1627
|
-
return { changed: result.changed, reloadRequired: result.reloadRequired === true };
|
|
1628
|
-
}
|
|
1629
|
-
await updateConfig(ctx.cwd, item, value, item.configScope ?? "repository");
|
|
1630
|
-
if (!item.id.startsWith("freeflow.cognitiveRouting.sessionStart.")) {
|
|
1631
|
-
reconcileCognitiveRouting = true;
|
|
1632
|
-
}
|
|
1633
|
-
return { changed: true, reloadRequired: true };
|
|
1634
|
-
},
|
|
1635
|
-
});
|
|
1636
|
-
|
|
1637
|
-
if (settingsScope === "session") {
|
|
1638
|
-
const reloaded = await finalizeSessionSettings(session, ctx, afterChange);
|
|
1639
|
-
return { changed: session.changed, reloaded, error: session.failed ? "write_failed" : undefined };
|
|
1640
|
-
}
|
|
1641
|
-
|
|
1642
|
-
const savedTarget = settingsScope === "local" ? "personal overrides" : "repository settings";
|
|
1643
|
-
const reloaded = await finalizeSettingsSession(
|
|
1644
|
-
session,
|
|
1645
|
-
ctx,
|
|
1646
|
-
afterChange,
|
|
1647
|
-
`Freeflow ${savedTarget} saved. Reloading Freeflow runtime...`,
|
|
1648
|
-
"Run /reload for Freeflow changes to fully apply.",
|
|
1649
|
-
{ reconcileCognitiveRouting },
|
|
1650
|
-
);
|
|
1651
|
-
return { changed: session.changed, reloaded, error: session.failed ? "write_failed" : undefined };
|
|
1652
|
-
}
|