@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,36 +1,103 @@
|
|
|
1
|
+
import { calculateContextTokens, compact as piCompact, estimateTokens } from "@earendil-works/pi-coding-agent";
|
|
1
2
|
import { dirname, resolve } from "node:path";
|
|
2
3
|
import { fileURLToPath } from "node:url";
|
|
3
|
-
import { RequestHistory } from "./
|
|
4
|
+
import { RequestHistory } from "./host/request-history.js";
|
|
4
5
|
import { registerProviderSupport } from "./provider-support/index.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
6
|
+
import { CacheHealth } from "./provider-support/cache/health.js";
|
|
7
|
+
import { CacheMonitor } from "./provider-support/cache/monitor.js";
|
|
8
|
+
import { RoutingRuntime } from "./cognitive-routing/runtime.js";
|
|
9
|
+
import { applyRoutingToolVisibility, registerRoutingTools } from "./cognitive-routing/tools.js";
|
|
10
|
+
import { handleFreeflowCommand } from "./host/settings/freeflow-command.js";
|
|
11
|
+
import { toolExecutionSections, toolExecutionTail } from "./tool-execution/prompt.js";
|
|
12
|
+
import { FileTracking } from "./tool-execution/file-tracking.js";
|
|
13
|
+
import { registerApplyPatch } from "./tool-execution/apply-patch/tool.js";
|
|
14
|
+
import { applyToolExecutionTools } from "./tool-execution/tools.js";
|
|
15
|
+
import { BackgroundJobs, NOTICE_PREFIX, NOTICE_TYPE, registerBackgroundTools } from "./tool-execution/background.js";
|
|
16
|
+
import { backgroundRefusal } from "./tool-execution/bash-guard.js";
|
|
17
|
+
import { CompactionController, USER_REQUEST } from "./compaction/controller.js";
|
|
18
|
+
import { applyCompactionTools, registerCompactionTool } from "./compaction/tool.js";
|
|
19
|
+
import { strictest } from "./compaction/thresholds.js";
|
|
20
|
+
import { tagProjectedMessages } from "./host/projection-tags.js";
|
|
21
|
+
import { trustStartedSession } from "./host/read-only-session.js";
|
|
22
|
+
import { takeStage } from "./host/staging.js";
|
|
15
23
|
import {
|
|
16
24
|
CONTRIBUTOR_COMMANDS,
|
|
17
25
|
WORKFLOW_COMMANDS,
|
|
18
26
|
freeflowModelSkillPaths,
|
|
27
|
+
STABLE_FREEFLOW_SURFACE,
|
|
28
|
+
skillPrompt,
|
|
29
|
+
} from "./host/catalog.js";
|
|
30
|
+
import {
|
|
31
|
+
readCapabilityState,
|
|
32
|
+
restoreSessionOverrides,
|
|
33
|
+
setSessionCoreOverride,
|
|
34
|
+
writeStagedSessionOverrides,
|
|
35
|
+
} from "./host/config.js";
|
|
36
|
+
import {
|
|
19
37
|
getRuntimeContext,
|
|
20
38
|
hasUsableMandatoryPrompts,
|
|
21
39
|
isPromptAvailable,
|
|
22
|
-
readCapabilityState,
|
|
23
40
|
refreshRuntimeContext,
|
|
24
|
-
restoreSessionOverrides,
|
|
25
41
|
stableRuntimeContext,
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
42
|
+
} from "./host/prompts.js";
|
|
43
|
+
import { filterBootstrapMessage, withFreeflowRuntimeState } from "./host/runtime-state.js";
|
|
44
|
+
import { setFreeflowStatus } from "./host/status.js";
|
|
45
|
+
import { cachedBranch } from "./host/branch.js";
|
|
46
|
+
const COMPACTION_NOTICE_TYPE = "freeflow-compaction-notice";
|
|
47
|
+
/** Estimated size of the full native history plus the system prompt: what a request without projection carries. */
|
|
48
|
+
/**
|
|
49
|
+
* The context a turn ended with, as Pi estimates it: the turn's own usage plus the tool results after it. Pi's
|
|
50
|
+
* getContextUsage rebuilds the session projection to find the same numbers. Undefined when the turn has no usable usage
|
|
51
|
+
* (aborted, failed, or none reported), where Pi's own estimate applies.
|
|
52
|
+
*/
|
|
53
|
+
function turnContextTokens(turn) {
|
|
54
|
+
const message = turn?.message;
|
|
55
|
+
if (message?.role !== "assistant" || message.stopReason === "aborted" || message.stopReason === "error") return;
|
|
56
|
+
if (!message.usage || calculateContextTokens(message.usage) <= 0) return;
|
|
57
|
+
return (turn.toolResults ?? []).reduce(
|
|
58
|
+
(sum, result) => sum + estimateTokens(result),
|
|
59
|
+
calculateContextTokens(message.usage),
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
/** Entry types that add no message to Pi's session projection. */
|
|
63
|
+
const NO_MESSAGE = new Set(["custom", "label", "session_info", "model_change", "thinking_level_change"]);
|
|
64
|
+
const historyEstimate = new WeakMap();
|
|
65
|
+
/**
|
|
66
|
+
* The full history's size, as every message's estimate plus the system prompt. Kept per session and extended by the
|
|
67
|
+
* messages appended since, because the branch only grows between compactions; a compaction, summary or custom
|
|
68
|
+
* message (which Pi's projection may place or replace) recomputes it from Pi's projection.
|
|
69
|
+
*/
|
|
70
|
+
function fullHistoryTokens(ctx) {
|
|
71
|
+
const manager = ctx.sessionManager;
|
|
72
|
+
const system = Math.ceil((ctx.getSystemPrompt?.() ?? "").length / 4);
|
|
73
|
+
if (!manager?.buildSessionProjection) return system;
|
|
74
|
+
const branch = cachedBranch(manager);
|
|
75
|
+
const previous = historyEstimate.get(manager);
|
|
76
|
+
let tokens;
|
|
77
|
+
if (previous && branch === previous.branch) tokens = previous.tokens;
|
|
78
|
+
else if (
|
|
79
|
+
previous &&
|
|
80
|
+
previous.branch.length > 0 &&
|
|
81
|
+
branch.length >= previous.branch.length &&
|
|
82
|
+
branch[previous.branch.length - 1] === previous.branch.at(-1)
|
|
83
|
+
) {
|
|
84
|
+
const added = branch.slice(previous.branch.length);
|
|
85
|
+
if (added.every((entry) => entry.type === "message" || NO_MESSAGE.has(entry.type)))
|
|
86
|
+
tokens = added.reduce(
|
|
87
|
+
(sum, entry) => sum + (entry.type === "message" ? estimateTokens(entry.message) : 0),
|
|
88
|
+
previous.tokens,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
if (tokens === undefined)
|
|
92
|
+
tokens = (manager.buildSessionProjection()?.messages ?? []).reduce(
|
|
93
|
+
(sum, message) => sum + estimateTokens(message),
|
|
94
|
+
0,
|
|
95
|
+
);
|
|
96
|
+
historyEstimate.set(manager, { branch, tokens });
|
|
97
|
+
return tokens + system;
|
|
98
|
+
}
|
|
32
99
|
async function sendSkillCommand(pi, ctx, skill, args) {
|
|
33
|
-
const state = await readCapabilityState(ctx.cwd, ctx
|
|
100
|
+
const state = await readCapabilityState(ctx.cwd, ctx);
|
|
34
101
|
if (skill === "setup-freeflow" && !state.configured) {
|
|
35
102
|
await pi.sendUserMessage(skillPrompt(skill, args), { expandPromptTemplates: true });
|
|
36
103
|
return;
|
|
@@ -62,43 +129,65 @@ function freeflowCompletions(prefix, routingAvailable) {
|
|
|
62
129
|
["profile auto", "auto", "Return to automatic Coordinator reconciliation"],
|
|
63
130
|
["profile history", "history", "Read routing observations"],
|
|
64
131
|
]
|
|
65
|
-
:
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
[
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
[
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
["profile", "profile", "Hold or release Cognitive Routing profile control"],
|
|
79
|
-
["resume", "resume", "Resume the current saved routing responsibility"],
|
|
80
|
-
]
|
|
81
|
-
: []),
|
|
82
|
-
["enable", "enable", "Enable Freeflow for this repository"],
|
|
83
|
-
["disable", "disable", "Disable Freeflow for this repository"],
|
|
84
|
-
];
|
|
132
|
+
: [
|
|
133
|
+
["settings", "settings", "Open settings; Tab switches Session, Personal and Repository"],
|
|
134
|
+
["status", "status", "Show effective Freeflow state"],
|
|
135
|
+
["compact", "compact", "Prepare, compact and recover now"],
|
|
136
|
+
...(routingAvailable
|
|
137
|
+
? [
|
|
138
|
+
["profile", "profile", "Hold or release Cognitive Routing profile control"],
|
|
139
|
+
["resume", "resume", "Resume the current saved routing responsibility"],
|
|
140
|
+
]
|
|
141
|
+
: []),
|
|
142
|
+
["enable", "enable", "Enable Freeflow for this repository"],
|
|
143
|
+
["disable", "disable", "Disable Freeflow for this repository"],
|
|
144
|
+
];
|
|
85
145
|
return choices
|
|
86
146
|
.filter(([value]) => value.startsWith(query))
|
|
87
147
|
.map(([value, label, description]) => ({ value, label, description }));
|
|
88
148
|
}
|
|
89
149
|
export default function freeflow(pi) {
|
|
90
|
-
|
|
150
|
+
// Cache diagnostics are reported to /freeflow status; the footer only shows current settings.
|
|
151
|
+
const cacheMonitor = new CacheMonitor();
|
|
152
|
+
// Only an explicit master-switch disable turns provider support off; unconfigured repositories keep it.
|
|
153
|
+
const providerSupport = registerProviderSupport(
|
|
154
|
+
pi,
|
|
155
|
+
() => !(capability?.configured === true && capability.enabled === false),
|
|
156
|
+
cacheMonitor,
|
|
157
|
+
);
|
|
91
158
|
const api = pi;
|
|
92
159
|
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
|
|
93
160
|
const routing = new RoutingRuntime(api, [packageRoot]);
|
|
94
|
-
|
|
161
|
+
// A Coordinator waiting on its worker will resume, so its prompt cache is worth keeping warm.
|
|
162
|
+
providerSupport.keepAlive.setHoldSource(() => routing.suspendedCoordinator());
|
|
163
|
+
// A worker may share the Coordinator's model; each profile keeps its own warmed request.
|
|
164
|
+
providerSupport.keepAlive.setRequesterSource(() => routing.observationScope().profile);
|
|
165
|
+
const requestHistory = new RequestHistory(api, cacheMonitor);
|
|
166
|
+
// Where the Freeflow Runtime State was last placed on paths that bypass request history.
|
|
167
|
+
const runtimeStateAnchor = {};
|
|
168
|
+
const resetHistory = () => {
|
|
169
|
+
requestHistory.reset();
|
|
170
|
+
delete runtimeStateAnchor.content;
|
|
171
|
+
delete runtimeStateAnchor.index;
|
|
172
|
+
};
|
|
95
173
|
let capability;
|
|
174
|
+
const files = new FileTracking();
|
|
175
|
+
const toolExecutionEffective = () => capability?.toolExecution?.effective === true;
|
|
176
|
+
const compactionEffective = () => capability?.compaction?.effective === true;
|
|
177
|
+
const cacheHealth = new CacheHealth();
|
|
96
178
|
let prompts;
|
|
97
|
-
let context;
|
|
98
|
-
let virtualization;
|
|
99
|
-
let history;
|
|
100
179
|
let refreshState = true;
|
|
101
|
-
|
|
180
|
+
// Pi's event contexts read the host model and effort live. Its interactive shortcut contexts copy them at the
|
|
181
|
+
// key press, so after a switch they still name the previous model; routing must never judge a switch by them.
|
|
182
|
+
let liveHost;
|
|
183
|
+
const withLiveModel = (ctx) =>
|
|
184
|
+
Object.defineProperties(
|
|
185
|
+
{ ...ctx },
|
|
186
|
+
{
|
|
187
|
+
model: { enumerable: true, get: () => liveHost?.model ?? ctx.model },
|
|
188
|
+
thinkingLevel: { enumerable: true, get: () => api.getThinkingLevel?.() ?? ctx.thinkingLevel },
|
|
189
|
+
},
|
|
190
|
+
);
|
|
102
191
|
let surfaceGeneration = 0;
|
|
103
192
|
const unavailable = (state, message) => ({
|
|
104
193
|
...state,
|
|
@@ -107,171 +196,328 @@ export default function freeflow(pi) {
|
|
|
107
196
|
});
|
|
108
197
|
async function loadSurface(ctx) {
|
|
109
198
|
const generation = surfaceGeneration;
|
|
110
|
-
const next = await readCapabilityState(ctx.cwd, ctx
|
|
199
|
+
const next = await readCapabilityState(ctx.cwd, ctx);
|
|
111
200
|
const loaded = await getRuntimeContext(STABLE_FREEFLOW_SURFACE);
|
|
112
201
|
if (generation !== surfaceGeneration) throw new Error("Discarded surface preparation for a replaced session.");
|
|
113
202
|
if (!hasUsableMandatoryPrompts(loaded)) {
|
|
114
|
-
for (const key of ["cognitiveRouting", "
|
|
203
|
+
for (const key of ["cognitiveRouting", "toolExecution"])
|
|
115
204
|
next[key] = unavailable(next[key], "Mandatory Freeflow prompts are unavailable.");
|
|
116
205
|
}
|
|
117
206
|
if (next.cognitiveRouting.effective && !isPromptAvailable(loaded.cognitiveRoutingPrompt))
|
|
118
207
|
next.cognitiveRouting = unavailable(next.cognitiveRouting, "Routing bootstrap cue is unavailable.");
|
|
119
|
-
if (next.contextVirtualization.effective && !isPromptAvailable(loaded.contextVirtualizationPrompt))
|
|
120
|
-
next.contextVirtualization = unavailable(
|
|
121
|
-
next.contextVirtualization,
|
|
122
|
-
"Context Virtualization guidance is unavailable.",
|
|
123
|
-
);
|
|
124
|
-
if (next.conversationHistory.effective && !isPromptAvailable(loaded.conversationHistoryPrompt))
|
|
125
|
-
next.conversationHistory = unavailable(next.conversationHistory, "Conversation History guidance is unavailable.");
|
|
126
|
-
// The existing reduction engine is not the future Context Control port. Do not pretend
|
|
127
|
-
// its independent transform ordering has been qualified with the new projection.
|
|
128
|
-
if (
|
|
129
|
-
next.cognitiveRouting.effective &&
|
|
130
|
-
next.cognitiveRouting.projection &&
|
|
131
|
-
(next.contextVirtualization.effective || next.conversationHistory.effective)
|
|
132
|
-
)
|
|
133
|
-
next.cognitiveRouting = unavailable(
|
|
134
|
-
next.cognitiveRouting,
|
|
135
|
-
"New routing projection with legacy context transforms is not qualified. Disable that combination explicitly.",
|
|
136
|
-
);
|
|
137
|
-
if (next.cognitiveRouting.enabled && isPiFlowHost(pi.host))
|
|
138
|
-
next.cognitiveRouting = unavailable(
|
|
139
|
-
next.cognitiveRouting,
|
|
140
|
-
"The redesigned PiFlow adapter requires separate qualification.",
|
|
141
|
-
);
|
|
142
208
|
capability = next;
|
|
143
209
|
prompts = loaded;
|
|
144
|
-
sessionContext = ctx;
|
|
145
210
|
return next;
|
|
146
211
|
}
|
|
147
|
-
function contextTools() {
|
|
148
|
-
registerFreeflowContextTool(
|
|
149
|
-
api,
|
|
150
|
-
() => virtualization,
|
|
151
|
-
() => history,
|
|
152
|
-
{
|
|
153
|
-
contextVirtualization: capability?.contextVirtualization?.effective === true,
|
|
154
|
-
conversationHistory: capability?.conversationHistory?.effective === true,
|
|
155
|
-
},
|
|
156
|
-
);
|
|
157
|
-
if (api.getActiveTools && api.setActiveTools) {
|
|
158
|
-
const current = new Set(api.getActiveTools());
|
|
159
|
-
current.add(CONTEXT_VIRTUALIZATION_TOOL_NAME);
|
|
160
|
-
const next = [...current];
|
|
161
|
-
if (JSON.stringify(next) !== JSON.stringify(api.getActiveTools())) api.setActiveTools(next);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
212
|
function status(ctx) {
|
|
165
213
|
applyRoutingToolVisibility(api, routing, capability?.cognitiveRouting?.effective === true);
|
|
214
|
+
applyToolExecutionTools(api, toolExecutionEffective());
|
|
215
|
+
applyCompactionTools(api, compactionEffective());
|
|
166
216
|
setFreeflowStatus(ctx, capability, routing.state(), prompts);
|
|
167
217
|
}
|
|
218
|
+
// Control and setting changes made between prompts are staged; their net effect is written as a prompt starts.
|
|
219
|
+
function writeStagedControl(ctx) {
|
|
220
|
+
const staged = takeStage(ctx.sessionManager);
|
|
221
|
+
routing.writeStaged(staged.events);
|
|
222
|
+
writeStagedSessionOverrides(staged.overrides, api, ctx.sessionManager);
|
|
223
|
+
}
|
|
168
224
|
async function update(ctx) {
|
|
169
225
|
const next = await loadSurface(ctx);
|
|
170
226
|
await routing.refresh(ctx, next.cognitiveRouting);
|
|
171
|
-
contextTools();
|
|
172
227
|
status(ctx);
|
|
173
228
|
refreshState = true;
|
|
174
229
|
}
|
|
230
|
+
/**
|
|
231
|
+
* Turn Cognitive Routing on for this session when it is off: clear a session "off" (such as the one an outside model
|
|
232
|
+
* change sets), and switch it on for the session if configuration leaves it off. Re-enabling hands control to the
|
|
233
|
+
* automatic Coordinator. Configuration that still blocks routing (no profiles, invalid presets) is reported by the
|
|
234
|
+
* control that follows.
|
|
235
|
+
*/
|
|
236
|
+
async function turnRoutingOn(ctx) {
|
|
237
|
+
if (capability?.cognitiveRouting?.effective === true || ctx.isIdle?.() === false) return;
|
|
238
|
+
if (capability?.cognitiveRouting?.enabledSource === "session")
|
|
239
|
+
await setSessionCoreOverride("cognitiveRouting.enabled", null, ctx, api);
|
|
240
|
+
if ((await readCapabilityState(ctx.cwd, ctx)).cognitiveRouting?.enabled !== true)
|
|
241
|
+
await setSessionCoreOverride("cognitiveRouting.enabled", true, ctx, api);
|
|
242
|
+
await update(ctx);
|
|
243
|
+
}
|
|
175
244
|
registerRoutingTools(api, routing);
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
() =>
|
|
179
|
-
() =>
|
|
180
|
-
|
|
181
|
-
|
|
245
|
+
registerApplyPatch(api, {
|
|
246
|
+
effective: toolExecutionEffective,
|
|
247
|
+
files: (ctx) => files.forPatch(ctx),
|
|
248
|
+
written: (paths, ctx) => files.written(paths, ctx),
|
|
249
|
+
});
|
|
250
|
+
const backgroundHost = {
|
|
251
|
+
effective: toolExecutionEffective,
|
|
252
|
+
shellPath: () => api.getSettings?.()?.shellPath,
|
|
253
|
+
// An exit notice starts a turn when idle; mid-run it is steered in after the current turn's tool results.
|
|
254
|
+
send: (content, details, triggerTurn) => {
|
|
255
|
+
const notice = () =>
|
|
256
|
+
Promise.resolve(
|
|
257
|
+
api.sendMessage(
|
|
258
|
+
{ customType: NOTICE_TYPE, content, display: true, details },
|
|
259
|
+
triggerTurn ? { triggerTurn: true, deliverAs: "steer" } : { triggerTurn: false },
|
|
260
|
+
),
|
|
261
|
+
).catch(() => {});
|
|
262
|
+
// When idle, the notice starts the run as a user message: a run an extension message starts loses Freeflow's
|
|
263
|
+
// system sections from its second request (Pi #10267). If a run started meanwhile, it is steered in instead.
|
|
264
|
+
if (!triggerTurn || liveHost?.isIdle?.() !== true) return notice();
|
|
265
|
+
try {
|
|
266
|
+
return Promise.resolve(api.sendUserMessage(content)).catch(notice);
|
|
267
|
+
} catch {
|
|
268
|
+
return notice();
|
|
269
|
+
}
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
const background = new BackgroundJobs(backgroundHost);
|
|
273
|
+
registerBackgroundTools(api, background, backgroundHost);
|
|
274
|
+
const compaction = new CompactionController({
|
|
275
|
+
effective: compactionEffective,
|
|
276
|
+
routingProfile: () => {
|
|
277
|
+
const state = routing.state();
|
|
278
|
+
return state.effective ? state.activeProfile : undefined;
|
|
279
|
+
},
|
|
280
|
+
routingAssignment: () => routing.currentAssignment(),
|
|
281
|
+
assignmentResults: () => routing.assignmentResults(),
|
|
282
|
+
background: () => background.running().map(({ id, label, outputPath }) => ({ id, label, outputPath })),
|
|
283
|
+
noticePrefix: NOTICE_PREFIX,
|
|
284
|
+
carryEnabled: () => capability?.compaction?.carry !== false,
|
|
285
|
+
nativeRefs: () => capability?.cognitiveRouting?.effective === true,
|
|
286
|
+
resolveRef: (ref) => routing.evidenceSource(ref),
|
|
287
|
+
coordinatorUnderProjection: () =>
|
|
288
|
+
capability?.cognitiveRouting?.effective === true &&
|
|
289
|
+
capability.cognitiveRouting.projection === true &&
|
|
290
|
+
routing.state().effective === true &&
|
|
291
|
+
routing.state().activeProfile === "coordinator",
|
|
292
|
+
summarize: async (preparation, instructions, signal, ctx) => {
|
|
293
|
+
const auth = await ctx.modelRegistry?.getApiKeyAndHeaders?.(ctx.model);
|
|
294
|
+
if (!ctx.model || !auth?.ok) throw new Error("No request auth for the summarizer");
|
|
295
|
+
const model = auth.baseUrl ? { ...ctx.model, baseUrl: auth.baseUrl } : ctx.model;
|
|
296
|
+
return piCompact(
|
|
297
|
+
preparation,
|
|
298
|
+
model,
|
|
299
|
+
auth.apiKey,
|
|
300
|
+
auth.headers,
|
|
301
|
+
instructions,
|
|
302
|
+
signal,
|
|
303
|
+
api.getThinkingLevel?.(),
|
|
304
|
+
undefined,
|
|
305
|
+
auth.env,
|
|
306
|
+
);
|
|
307
|
+
},
|
|
308
|
+
measure: (ctx, turn) => {
|
|
309
|
+
// Every model that may receive the full history: the active one and, under routing, each profile's.
|
|
310
|
+
const routingCapability = capability?.cognitiveRouting;
|
|
311
|
+
const models = [ctx.model];
|
|
312
|
+
if (routingCapability?.effective)
|
|
313
|
+
for (const profile of Object.values(routingCapability.profiles ?? {}))
|
|
314
|
+
models.push(ctx.modelRegistry?.find?.(profile.provider, profile.model));
|
|
315
|
+
const limits = strictest(models, api.getSettings?.());
|
|
316
|
+
if (!limits) return undefined;
|
|
317
|
+
let tokens = turnContextTokens(turn) ?? ctx.getContextUsage?.()?.tokens ?? 0;
|
|
318
|
+
// A Coordinator under projection sends a reduced view; the next worker request carries the whole history.
|
|
319
|
+
if (
|
|
320
|
+
routingCapability?.effective &&
|
|
321
|
+
routingCapability.projection &&
|
|
322
|
+
routing.state().activeProfile === "coordinator"
|
|
323
|
+
)
|
|
324
|
+
tokens = Math.max(tokens, fullHistoryTokens(ctx));
|
|
325
|
+
return { tokens, thresholds: limits };
|
|
326
|
+
},
|
|
327
|
+
});
|
|
328
|
+
registerCompactionTool(api, compaction);
|
|
329
|
+
// The user's /freeflow compact makes compaction due now: the agent prepares, compacts and recovers.
|
|
330
|
+
async function compactNow(ctx) {
|
|
331
|
+
if (!compactionEffective()) {
|
|
332
|
+
ctx.ui.notify("Freeflow compaction is off; Pi's /compact still works.", "warning");
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
compaction.requestByUser(ctx);
|
|
336
|
+
// The user asked, so this is their message. A user message also starts an ordinary run, which keeps Freeflow's
|
|
337
|
+
// system sections; a run started by an extension message loses them from its second request (Pi #10267).
|
|
338
|
+
const index = compaction.indexText(ctx);
|
|
339
|
+
const text = `${USER_REQUEST}${index ? `\n\n${index}` : ""}`;
|
|
340
|
+
await Promise.resolve(
|
|
341
|
+
api.sendUserMessage(text, ctx.isIdle?.() === false ? { deliverAs: "steer" } : undefined),
|
|
342
|
+
).catch(() => {});
|
|
343
|
+
}
|
|
182
344
|
pi.on("resources_discover", async (event, ctx) => {
|
|
183
345
|
const state = capability ?? (await loadSurface(ctx ?? { cwd: event?.cwd ?? process.cwd() }));
|
|
184
|
-
return {
|
|
346
|
+
return {
|
|
347
|
+
skillPaths: freeflowModelSkillPaths({ ...STABLE_FREEFLOW_SURFACE, toolExecution: state.toolExecution }),
|
|
348
|
+
};
|
|
185
349
|
});
|
|
186
350
|
pi.on("session_start", async (event, ctx) => {
|
|
351
|
+
liveHost = ctx;
|
|
187
352
|
const generation = ++surfaceGeneration;
|
|
188
|
-
|
|
353
|
+
resetHistory();
|
|
354
|
+
// Where Pi has just parsed this session file, later checks read only the bytes appended since.
|
|
355
|
+
trustStartedSession(event?.reason, ctx.sessionManager);
|
|
189
356
|
routing.unbind();
|
|
190
357
|
capability = undefined;
|
|
191
358
|
prompts = undefined;
|
|
192
359
|
refreshState = true;
|
|
193
360
|
restoreSessionOverrides(ctx);
|
|
194
|
-
const initial = await readCapabilityState(ctx.cwd, ctx, pi.host);
|
|
195
361
|
await refreshRuntimeContext(STABLE_FREEFLOW_SURFACE);
|
|
196
362
|
if (generation !== surfaceGeneration) return;
|
|
197
363
|
await loadSurface(ctx);
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
ctx,
|
|
202
|
-
(entryId) => virtualization?.isSourceFullyProjected(entryId) ?? true,
|
|
203
|
-
context,
|
|
204
|
-
);
|
|
205
|
-
await virtualization.recover(ctx);
|
|
364
|
+
cacheHealth.reset();
|
|
365
|
+
files.reset();
|
|
366
|
+
compaction.reset();
|
|
206
367
|
await routing.bind(ctx, capability.cognitiveRouting, event?.reason !== "reload");
|
|
207
|
-
contextTools();
|
|
208
368
|
status(ctx);
|
|
209
369
|
});
|
|
210
370
|
pi.on("session_shutdown", async () => {
|
|
211
371
|
surfaceGeneration++;
|
|
212
|
-
|
|
372
|
+
// Background processes belong to this extension instance; none may report into the next session.
|
|
373
|
+
await background.stopAll();
|
|
374
|
+
files.reset();
|
|
375
|
+
compaction.reset();
|
|
376
|
+
resetHistory();
|
|
213
377
|
routing.unbind();
|
|
214
|
-
context = undefined;
|
|
215
|
-
virtualization = undefined;
|
|
216
|
-
history = undefined;
|
|
217
378
|
capability = undefined;
|
|
218
379
|
prompts = undefined;
|
|
219
|
-
sessionContext = undefined;
|
|
220
380
|
});
|
|
221
381
|
pi.on("before_agent_start", async (event, ctx) => {
|
|
222
382
|
await update(ctx);
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
383
|
+
try {
|
|
384
|
+
await routing.beforeRun(ctx);
|
|
385
|
+
} finally {
|
|
386
|
+
// Pi reports a handler error and still sends the prompt, so the fixed guidance section and the staged
|
|
387
|
+
// control must never be skipped: a missing section would change the cached prompt head.
|
|
388
|
+
// After the run's own reconciliation, so everything staged lands ahead of the prompt in one pass.
|
|
389
|
+
writeStagedControl(ctx);
|
|
390
|
+
status(ctx);
|
|
391
|
+
// Pi 0.87 records changed sections at their native transcript position.
|
|
392
|
+
// Returning systemPrompt would force one replacement head for every request.
|
|
393
|
+
const sections = event.systemPromptOptions.sections;
|
|
394
|
+
const toolExecution = capability?.toolExecution?.effective === true;
|
|
395
|
+
// Pi's own sections are replaced in place. Freeflow's are re-inserted at the end in a fixed order: its guidance,
|
|
396
|
+
// then Tool Execution's environment facts and guidance.
|
|
397
|
+
if (toolExecution)
|
|
398
|
+
Object.assign(sections, toolExecutionSections(event.systemPrompt, api.getSettings?.()?.shellPath));
|
|
399
|
+
for (const name of ["freeflow_guidance", "environment", "tool_execution"]) delete sections[name];
|
|
400
|
+
sections.freeflow_guidance = stableRuntimeContext(prompts);
|
|
401
|
+
if (toolExecution)
|
|
402
|
+
Object.assign(sections, toolExecutionTail(api.getSettings?.()?.shellPath, prompts?.toolExecutionPrompt));
|
|
403
|
+
}
|
|
404
|
+
// Like Claude Code's next-turn reminder: files the model read that changed while it was not running a tool.
|
|
405
|
+
const changed = toolExecutionEffective() ? await files.changedNotice(ctx, false) : undefined;
|
|
406
|
+
if (changed)
|
|
407
|
+
return { message: { customType: "freeflow-files", content: `${NOTICE_PREFIX} ${changed}`, display: true } };
|
|
227
408
|
});
|
|
228
|
-
pi.on("
|
|
229
|
-
pi.on("
|
|
409
|
+
pi.on("agent_start", () => routing.runStarted());
|
|
410
|
+
pi.on("message_end", async (event, ctx) => {
|
|
411
|
+
const message = event.message;
|
|
230
412
|
if (
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
413
|
+
capability?.enabled === true &&
|
|
414
|
+
message?.role === "assistant" &&
|
|
415
|
+
message.usage &&
|
|
416
|
+
message.stopReason !== "error"
|
|
417
|
+
) {
|
|
418
|
+
const scope = routing.observationScope();
|
|
419
|
+
const lifetime = ctx.model?.promptCache?.[process.env.PI_CACHE_RETENTION === "long" ? "long" : "short"];
|
|
420
|
+
const warning = cacheHealth.observe({
|
|
421
|
+
provider: message.provider,
|
|
422
|
+
model: message.model,
|
|
423
|
+
thinking: ctx.thinkingLevel,
|
|
424
|
+
at: Date.now(),
|
|
425
|
+
input: message.usage.input ?? 0,
|
|
426
|
+
cacheRead: message.usage.cacheRead ?? 0,
|
|
427
|
+
cacheWrite: message.usage.cacheWrite ?? 0,
|
|
428
|
+
...(scope.control === "automatic" && scope.profile !== "solo" ? { lane: scope.profile } : {}),
|
|
429
|
+
...(typeof lifetime === "number" ? { ttlMs: lifetime * 1000 } : {}),
|
|
430
|
+
});
|
|
431
|
+
if (warning) ctx.ui?.notify?.(warning, "warning");
|
|
432
|
+
}
|
|
433
|
+
routing.messageEnd(event.message);
|
|
434
|
+
// A run started by an extension's message (a background notice, for one) gets the same preparation as a prompt.
|
|
435
|
+
if (routing.startedByMessage(event.message)) {
|
|
436
|
+
await update(ctx);
|
|
437
|
+
try {
|
|
438
|
+
await routing.beforeRun(ctx, false);
|
|
439
|
+
} finally {
|
|
440
|
+
writeStagedControl(ctx);
|
|
441
|
+
status(ctx);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
237
444
|
});
|
|
445
|
+
pi.on("tool_call", async (event, ctx) => {
|
|
446
|
+
const gate = routing.preflight(event, ctx);
|
|
447
|
+
if (gate?.block || !toolExecutionEffective()) return gate;
|
|
448
|
+
const refusal = event.toolName === "bash" ? backgroundRefusal(event.input?.command) : undefined;
|
|
449
|
+
if (refusal) return { block: true, reason: refusal };
|
|
450
|
+
return (await files.toolCall(event, ctx)) ?? gate;
|
|
451
|
+
});
|
|
452
|
+
pi.on("tool_result", async (event, ctx) => (toolExecutionEffective() ? files.toolResult(event, ctx) : undefined));
|
|
238
453
|
pi.on("turn_end", async (event, ctx) => {
|
|
239
454
|
await routing.turnEnd(event, ctx);
|
|
455
|
+
files.clearPending();
|
|
240
456
|
status(ctx);
|
|
457
|
+
const compacting = await compaction.turnEnd(event, ctx);
|
|
458
|
+
if (compacting) return compacting;
|
|
459
|
+
const notice = compaction.observe(ctx, event);
|
|
460
|
+
if (notice) {
|
|
461
|
+
// Mid-run the notice joins the next request; when the run is ending it waits for the next prompt rather than
|
|
462
|
+
// starting work nobody asked for.
|
|
463
|
+
const midRun = (event.message?.content ?? []).some((block) => block?.type === "toolCall");
|
|
464
|
+
await Promise.resolve(
|
|
465
|
+
api.sendMessage(
|
|
466
|
+
{ customType: COMPACTION_NOTICE_TYPE, content: notice.text, display: true, details: { level: notice.level } },
|
|
467
|
+
{ deliverAs: midRun ? "steer" : "nextTurn" },
|
|
468
|
+
),
|
|
469
|
+
).catch(() => {});
|
|
470
|
+
}
|
|
471
|
+
return undefined;
|
|
472
|
+
});
|
|
473
|
+
// A Freeflow compaction fires no session_compact, so its resets run here, before the new cycle's first request.
|
|
474
|
+
pi.on("turn_start", async (_event, ctx) => {
|
|
475
|
+
const compacted = compaction.takeCompacted();
|
|
476
|
+
if (!compacted) return;
|
|
477
|
+
await restore(ctx, false);
|
|
478
|
+
await files.compacted(compacted.carriedFiles, ctx);
|
|
241
479
|
});
|
|
242
480
|
pi.on("agent_settled", async (_event, ctx) => {
|
|
243
481
|
await routing.settled(ctx);
|
|
244
482
|
await update(ctx);
|
|
245
483
|
});
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
484
|
+
// A model or effort picked in Pi itself (/model, Shift+Tab) ends automatic routing. Routing then turns off for this
|
|
485
|
+
// session through its Session switch, so settings and the footer show it off, and any routing control turns it on.
|
|
486
|
+
const externalChange = async (ctx) => {
|
|
487
|
+
cacheHealth.noteSwitch();
|
|
488
|
+
if (await routing.nativeChange(ctx)) {
|
|
489
|
+
await setSessionCoreOverride("cognitiveRouting.enabled", false, ctx, api);
|
|
490
|
+
await update(ctx);
|
|
491
|
+
}
|
|
252
492
|
status(ctx);
|
|
253
|
-
}
|
|
254
|
-
pi.on("
|
|
493
|
+
};
|
|
494
|
+
pi.on("model_select", async (_event, ctx) => externalChange(ctx));
|
|
495
|
+
pi.on("thinking_level_select", async (_event, ctx) => externalChange(ctx));
|
|
496
|
+
pi.on("context_with_system", async (event, ctx) => {
|
|
497
|
+
writeStagedControl(ctx);
|
|
255
498
|
if (!capability) await loadSurface(ctx);
|
|
499
|
+
// Entry identity lets attribution and request history skip hashing unchanged native history.
|
|
500
|
+
tagProjectedMessages(
|
|
501
|
+
event.messages,
|
|
502
|
+
ctx.sessionManager?.buildSessionProjection?.(),
|
|
503
|
+
cachedBranch(ctx.sessionManager) ?? [],
|
|
504
|
+
);
|
|
256
505
|
let messages = event.messages.map(filterBootstrapMessage).filter(Boolean);
|
|
257
|
-
if (virtualization) {
|
|
258
|
-
virtualization.setContext(ctx);
|
|
259
|
-
const projected = await virtualization.project(messages, capability.contextVirtualization.effective);
|
|
260
|
-
messages = projected.messages;
|
|
261
|
-
}
|
|
262
|
-
if (history && capability.conversationHistory.effective) {
|
|
263
|
-
history.setContext(ctx);
|
|
264
|
-
history.capture(capability.contextVirtualization.effective);
|
|
265
|
-
}
|
|
266
506
|
messages = withFreeflowRuntimeState(messages, capability, routing.state(), prompts, {
|
|
267
507
|
force: refreshState,
|
|
508
|
+
anchor: runtimeStateAnchor,
|
|
268
509
|
projectionFailure: routing.state().projectionFailure,
|
|
269
510
|
});
|
|
270
511
|
refreshState = false;
|
|
271
|
-
|
|
272
|
-
const projected =
|
|
512
|
+
// Disabled Freeflow pays no per-request provenance work; re-enabling rebuilds it from session history.
|
|
513
|
+
const projected = capability?.enabled === true ? await routing.context(ctx, messages) : messages;
|
|
273
514
|
const state = routing.state();
|
|
274
|
-
if (projected.some((message) => message.details?.routingRequestBlocked === true))
|
|
515
|
+
if (projected.some((message) => message.details?.routingRequestBlocked === true)) {
|
|
516
|
+
// Routing must not expose hidden worker history on failure, but Pi's full-
|
|
517
|
+
// transcript hook still requires the leading system prompt/tool state.
|
|
518
|
+
const head = event.messages[0]?.role === "system" ? [event.messages[0]] : [];
|
|
519
|
+
return { messages: [...head, ...projected] };
|
|
520
|
+
}
|
|
275
521
|
const view = routing.projectionEnabled && state.activeProfile === "coordinator" ? "coordinator" : "ordinary";
|
|
276
522
|
return {
|
|
277
523
|
messages: await requestHistory.assemble(projected, view, ctx, (assembled) =>
|
|
@@ -280,19 +526,22 @@ export default function freeflow(pi) {
|
|
|
280
526
|
};
|
|
281
527
|
});
|
|
282
528
|
const restore = async (ctx, navigation = true) => {
|
|
283
|
-
|
|
529
|
+
cacheHealth.noteBreak();
|
|
530
|
+
resetHistory();
|
|
531
|
+
// Navigation selects another branch, whose reads differ; compaction keeps the branch and its file state.
|
|
532
|
+
if (navigation) files.invalidate();
|
|
284
533
|
restoreSessionOverrides(ctx);
|
|
285
534
|
refreshState = true;
|
|
286
|
-
if (virtualization) {
|
|
287
|
-
virtualization.setContext(ctx);
|
|
288
|
-
await virtualization.recover(ctx);
|
|
289
|
-
}
|
|
290
|
-
history?.setContext(ctx);
|
|
291
535
|
await routing.ancestryChanged(ctx, navigation);
|
|
292
536
|
await update(ctx);
|
|
293
537
|
};
|
|
294
538
|
pi.on("session_tree", async (_event, ctx) => restore(ctx));
|
|
295
|
-
|
|
539
|
+
// Pi compacting on its own (threshold, overflow, /compact) keeps its summarizer and kept tail, with Freeflow's additions.
|
|
540
|
+
pi.on("session_before_compact", async (event, ctx) => compaction.fallback(event, ctx));
|
|
541
|
+
pi.on("session_compact", async (_event, ctx) => {
|
|
542
|
+
await restore(ctx, false);
|
|
543
|
+
background.restate();
|
|
544
|
+
});
|
|
296
545
|
pi.on("session_compact_failed", async (_event, ctx) => {
|
|
297
546
|
refreshState = true;
|
|
298
547
|
status(ctx);
|
|
@@ -305,26 +554,26 @@ export default function freeflow(pi) {
|
|
|
305
554
|
) {
|
|
306
555
|
pi.registerShortcut("ctrl+shift+r", {
|
|
307
556
|
description: "Cycle enabled Cognitive Routing manual holds",
|
|
308
|
-
handler: async (
|
|
557
|
+
handler: async (shortcutCtx) => {
|
|
558
|
+
const ctx = withLiveModel(shortcutCtx);
|
|
309
559
|
if (!ctx.isIdle()) {
|
|
310
560
|
ctx.ui.notify("Wait for Pi to become idle before changing control.", "warning");
|
|
311
561
|
return;
|
|
312
562
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
const current = profiles.indexOf(state.activeProfile ?? "coordinator");
|
|
316
|
-
const target = profiles[(current + 1) % profiles.length];
|
|
317
|
-
ctx.ui.notify(JSON.stringify(await routing.setManualProfile(target)), "info");
|
|
563
|
+
await turnRoutingOn(ctx);
|
|
564
|
+
ctx.ui.notify(JSON.stringify(await routing.cycleManualProfile()), "info");
|
|
318
565
|
await update(ctx);
|
|
319
566
|
},
|
|
320
567
|
});
|
|
321
568
|
pi.registerShortcut("ctrl+shift+a", {
|
|
322
|
-
description: "
|
|
323
|
-
handler: async (
|
|
569
|
+
description: "Turn Cognitive Routing on, or release a manual hold, to the automatic Coordinator",
|
|
570
|
+
handler: async (shortcutCtx) => {
|
|
571
|
+
const ctx = withLiveModel(shortcutCtx);
|
|
324
572
|
if (!ctx.isIdle()) {
|
|
325
573
|
ctx.ui.notify("Wait for Pi to become idle before changing control.", "warning");
|
|
326
574
|
return;
|
|
327
575
|
}
|
|
576
|
+
await turnRoutingOn(ctx);
|
|
328
577
|
ctx.ui.notify(JSON.stringify(await routing.setAutomaticControl()), "info");
|
|
329
578
|
await update(ctx);
|
|
330
579
|
},
|
|
@@ -350,7 +599,7 @@ export default function freeflow(pi) {
|
|
|
350
599
|
handler: (args, ctx) => sendSkillCommand(pi, ctx, skill, args),
|
|
351
600
|
});
|
|
352
601
|
pi.registerCommand("freeflow", {
|
|
353
|
-
description: "Freeflow settings, status, profile control, saved-operation resume
|
|
602
|
+
description: "Freeflow settings, status, profile control, and saved-operation resume",
|
|
354
603
|
getArgumentCompletions: (prefix) =>
|
|
355
604
|
freeflowCompletions(
|
|
356
605
|
prefix,
|
|
@@ -359,22 +608,23 @@ export default function freeflow(pi) {
|
|
|
359
608
|
typeof api.setThinkingLevel === "function",
|
|
360
609
|
),
|
|
361
610
|
handler: async (args, ctx) => {
|
|
362
|
-
if (
|
|
363
|
-
await
|
|
611
|
+
if ((args ?? "").trim().toLowerCase() === "compact") {
|
|
612
|
+
await compactNow(ctx);
|
|
364
613
|
return;
|
|
365
614
|
}
|
|
366
|
-
|
|
367
|
-
if (
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
ctx,
|
|
371
|
-
virtualization,
|
|
372
|
-
capability?.contextVirtualization.effective,
|
|
373
|
-
capability?.conversationHistory.effective,
|
|
374
|
-
);
|
|
615
|
+
// A routing control (a hold or `auto`) turns routing on first when it is off.
|
|
616
|
+
if (/^profile\s+(?!history\b)\S/i.test((args ?? "").trim())) await turnRoutingOn(ctx);
|
|
617
|
+
if (await routing.command(args ?? "", ctx)) {
|
|
618
|
+
await update(ctx);
|
|
375
619
|
return;
|
|
376
620
|
}
|
|
377
|
-
await handleFreeflowCommand(args, ctx, async () => update(ctx), pi, routing
|
|
621
|
+
await handleFreeflowCommand(args, ctx, async () => update(ctx), pi, routing, {
|
|
622
|
+
status: () => ({
|
|
623
|
+
cacheHealth: [...cacheHealth.status(), ...cacheMonitor.lines()],
|
|
624
|
+
backgroundRunning: background.running().length,
|
|
625
|
+
compaction: compaction.statusText(ctx),
|
|
626
|
+
}),
|
|
627
|
+
});
|
|
378
628
|
},
|
|
379
629
|
});
|
|
380
630
|
}
|