@hassangameryt/freeflow 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +2 -1
- package/.codex-plugin/plugin.json +2 -1
- package/README.md +150 -155
- package/capabilities/cognitive-routing/SKILL.md +229 -0
- package/capabilities/context-virtualization/SKILL.md +103 -0
- package/capabilities/conversation-history/SKILL.md +53 -0
- package/command-surface.json +17 -13
- package/hooks/freeflow-runtime-context.mjs +393 -159
- package/hooks/hooks.json +26 -1
- package/hooks/tests/check-runtime-context-hook.sh +223 -54
- package/package.json +25 -16
- package/pi-extension/dist/cognitive-routing/commands.js +96 -0
- package/pi-extension/dist/cognitive-routing/config.js +236 -0
- package/pi-extension/dist/cognitive-routing/controller.js +862 -0
- package/pi-extension/dist/cognitive-routing/history.js +439 -0
- package/pi-extension/dist/cognitive-routing/host.js +126 -0
- package/pi-extension/dist/cognitive-routing/runtime.js +43 -0
- package/pi-extension/dist/cognitive-routing/tool.js +211 -0
- package/pi-extension/dist/cognitive-routing/types.js +6 -0
- package/pi-extension/dist/context-virtualization/commands.js +89 -0
- package/pi-extension/dist/context-virtualization/projector.js +42 -0
- package/pi-extension/dist/context-virtualization/runtime.js +356 -0
- package/pi-extension/dist/context-virtualization/state.js +57 -0
- package/pi-extension/dist/context-virtualization/types.js +10 -0
- package/pi-extension/dist/conversation-history/passages.js +66 -0
- package/pi-extension/dist/conversation-history/ranking.js +105 -0
- package/pi-extension/dist/conversation-history/runtime.js +226 -0
- package/pi-extension/dist/conversation-history/search.js +56 -0
- package/pi-extension/dist/conversation-history/types.js +1 -0
- package/pi-extension/dist/freeflow-context/resolver.js +120 -0
- package/pi-extension/dist/freeflow-context/runtime.js +80 -0
- package/pi-extension/dist/freeflow-context/source-projector.js +95 -0
- package/pi-extension/dist/freeflow-context/tool.js +308 -0
- package/pi-extension/dist/freeflow-context/types.js +12 -0
- package/pi-extension/dist/freeflow-context/visibility.js +28 -0
- package/pi-extension/dist/index.js +516 -98
- package/pi-extension/dist/runtime/runtime-context.js +318 -271
- package/pi-extension/dist/runtime/runtime-identity.js +5 -0
- package/pi-extension/dist/settings/settings-tui.js +78 -0
- package/pi-extension/dist/settings/settings-ui.js +593 -563
- package/pi-extension/dist/ui/text-component.js +57 -0
- package/pi-extension/src/cognitive-routing/commands.ts +119 -0
- package/pi-extension/src/cognitive-routing/config.ts +307 -0
- package/pi-extension/src/cognitive-routing/controller.ts +1118 -0
- package/pi-extension/src/cognitive-routing/history.ts +638 -0
- package/pi-extension/src/cognitive-routing/host.ts +172 -0
- package/pi-extension/src/cognitive-routing/runtime.ts +57 -0
- package/pi-extension/src/cognitive-routing/tool.ts +272 -0
- package/pi-extension/src/cognitive-routing/types.ts +89 -0
- package/pi-extension/src/context-virtualization/commands.ts +111 -0
- package/pi-extension/src/context-virtualization/projector.ts +62 -0
- package/pi-extension/src/context-virtualization/runtime.ts +406 -0
- package/pi-extension/src/context-virtualization/state.ts +79 -0
- package/pi-extension/src/context-virtualization/types.ts +92 -0
- package/pi-extension/src/conversation-history/passages.ts +81 -0
- package/pi-extension/src/conversation-history/ranking.ts +155 -0
- package/pi-extension/src/conversation-history/runtime.ts +250 -0
- package/pi-extension/src/conversation-history/search.ts +79 -0
- package/pi-extension/src/conversation-history/types.ts +31 -0
- package/pi-extension/src/freeflow-context/resolver.ts +139 -0
- package/pi-extension/src/freeflow-context/runtime.ts +100 -0
- package/pi-extension/src/freeflow-context/source-projector.ts +106 -0
- package/pi-extension/src/freeflow-context/tool.ts +360 -0
- package/pi-extension/src/freeflow-context/types.ts +70 -0
- package/pi-extension/src/freeflow-context/visibility.ts +53 -0
- package/pi-extension/src/index.ts +553 -101
- package/pi-extension/src/runtime/runtime-context.ts +339 -281
- package/pi-extension/src/runtime/runtime-identity.ts +8 -0
- package/pi-extension/src/settings/settings-tui.ts +93 -0
- package/pi-extension/src/settings/settings-ui.ts +700 -613
- package/pi-extension/src/ui/text-component.ts +66 -0
- package/pi-extension/tests/cognitive-routing/commands.test.js +134 -0
- package/pi-extension/tests/cognitive-routing/controller.test.js +1099 -0
- package/pi-extension/tests/cognitive-routing/extension-lifecycle.test.js +1071 -0
- package/pi-extension/tests/cognitive-routing/history.test.js +274 -0
- package/pi-extension/tests/cognitive-routing/host-fixture.js +4 -0
- package/pi-extension/tests/cognitive-routing/host-identity.test.js +63 -0
- package/pi-extension/tests/cognitive-routing/profile-resolution.test.js +236 -0
- package/pi-extension/tests/cognitive-routing/tool.test.js +230 -0
- package/pi-extension/tests/context-virtualization/extension.test.js +749 -0
- package/pi-extension/tests/context-virtualization/projector.test.js +110 -0
- package/pi-extension/tests/context-virtualization/runtime.test.js +216 -0
- package/pi-extension/tests/conversation-history/runtime.test.js +98 -0
- package/pi-extension/tests/conversation-history/search.test.js +97 -0
- package/pi-extension/tests/integration/pi-extension.test.js +928 -2047
- package/pi-extension/tests/integration/prompt-architecture.test.js +305 -0
- package/runtime/prompts/cognitive-routing.md +9 -0
- package/runtime/prompts/context-virtualization.md +3 -0
- package/runtime/prompts/conversation-history.md +3 -0
- package/runtime/prompts/core.md +13 -0
- package/runtime/prompts/interaction-contract.md +7 -0
- package/runtime/prompts/skills.md +65 -0
- package/skills/action-selection/SKILL.md +91 -0
- package/skills/bypass/SKILL.md +3 -2
- package/skills/commit-work/SKILL.md +1 -1
- package/skills/commit-work/references/staging-decisions.md +1 -1
- package/skills/decision-gate/SKILL.md +1 -1
- package/skills/design-for-depth/SKILL.md +14 -33
- package/skills/design-for-depth/references/design-pressure-signals.md +1 -1
- package/skills/design-for-depth/references/interface-design-loop.md +1 -1
- package/skills/design-for-depth/references/software-design-philosophy.md +1 -1
- package/skills/diagnose-failure/SKILL.md +20 -14
- package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +1 -3
- package/skills/diagnose-failure/references/{flaky-and-performance.md → flaky-and-performance-diagnosis.md} +1 -1
- package/skills/discuss/SKILL.md +34 -22
- package/skills/discuss/references/discussion-continuity.md +71 -0
- package/skills/evaluate-skill/SKILL.md +5 -5
- package/skills/evaluate-skill/references/{definition-schema.md → evaluation-definition-schema.md} +42 -2
- package/skills/evaluate-skill/references/execution-and-evidence.md +8 -3
- package/skills/evaluate-skill/scripts/lib/definitions.mjs +114 -3
- package/skills/evaluate-skill/scripts/lib/grade-interaction.mjs +156 -1
- package/skills/evaluate-skill/scripts/lib/grade.mjs +9 -1
- package/skills/evaluate-skill/scripts/lib/pi.mjs +147 -32
- package/skills/evaluate-skill/scripts/lib/rpc.mjs +4 -4
- package/skills/evaluate-skill/scripts/lib/runner.mjs +36 -14
- package/skills/evaluate-skill/scripts/lib/sandbox.mjs +92 -0
- package/skills/evaluate-skill/scripts/pi-guard.mjs +1 -1
- package/skills/evaluate-skill/scripts/pi-observer.mjs +66 -0
- package/skills/evaluate-skill/scripts/skill-eval.mjs +6 -5
- package/skills/execute-work/SKILL.md +18 -15
- package/skills/execute-work/references/code-practices.md +1 -1
- package/skills/execute-work/references/execute-work-edges.md +42 -0
- package/skills/finish-branch/SKILL.md +2 -2
- package/skills/finish-branch/references/integration-options.md +1 -1
- package/skills/handoff/SKILL.md +3 -3
- package/skills/launch-work/SKILL.md +1 -1
- package/skills/launch-work/references/launch-readiness.md +2 -2
- package/skills/migration-work/SKILL.md +2 -2
- package/skills/migration-work/references/migration-lifecycle.md +1 -1
- package/skills/mode-contract/SKILL.md +32 -10
- package/skills/release-work/SKILL.md +1 -1
- package/skills/release-work/references/release-evidence.md +1 -1
- package/skills/review-artifact/SKILL.md +28 -26
- package/skills/review-work/SKILL.md +28 -26
- package/skills/review-work/references/{reviewer-prompt.md → independent-work-reviewer-contract.md} +1 -1
- package/skills/review-work/references/security-risk-lens.md +1 -1
- package/skills/setup-freeflow/SKILL.md +6 -10
- package/skills/setup-freeflow/references/activation-contract.md +5 -10
- package/skills/setup-freeflow/references/host-setup.md +3 -7
- package/skills/simplify-code/SKILL.md +1 -1
- package/skills/simplify-code/references/simplification-patterns.md +1 -1
- package/skills/tdd/SKILL.md +18 -38
- package/skills/tdd/references/test-design.md +2 -2
- package/skills/track-work/SKILL.md +362 -58
- package/skills/track-work/scripts/lib/codec.mjs +570 -0
- package/skills/track-work/scripts/lib/commands.mjs +568 -0
- package/skills/track-work/scripts/lib/contract.mjs +303 -0
- package/skills/track-work/scripts/lib/model.mjs +506 -0
- package/skills/track-work/scripts/lib/operations.mjs +965 -0
- package/skills/track-work/scripts/lib/record-store.mjs +264 -0
- package/skills/track-work/scripts/lib/result.mjs +37 -0
- package/skills/track-work/scripts/lib/rewrites.mjs +740 -0
- package/skills/track-work/scripts/lib/schema.mjs +277 -0
- package/skills/track-work/scripts/lib/source-inventory.mjs +162 -0
- package/skills/track-work/scripts/lib/views.mjs +443 -0
- package/skills/track-work/scripts/lib/workspace.mjs +134 -0
- package/skills/track-work/scripts/working-record.mjs +122 -0
- package/skills/verify-work/SKILL.md +14 -2
- package/skills/verify-work/references/browser-runtime-evidence.md +1 -1
- package/skills/verify-work/references/integration-evidence.md +1 -1
- package/skills/verify-work/references/performance-evidence.md +1 -1
- package/skills/workflow/SKILL.md +105 -53
- package/skills/workflow/references/domain-skill-composition.md +22 -42
- package/skills/write-plan/SKILL.md +1 -1
- package/skills/write-skill/SKILL.md +3 -2
- package/skills/write-skill/references/progressive-disclosure.md +17 -3
- package/pi-extension/dist/output-router/host-producer-identification.js +0 -232
- package/pi-extension/dist/output-router/native-safety-net.js +0 -216
- package/pi-extension/dist/output-router/observed-tool-routing.js +0 -122
- package/pi-extension/dist/output-router/renderers.js +0 -1023
- package/pi-extension/dist/output-router/router-tools.js +0 -554
- package/pi-extension/dist/output-router/schemas.js +0 -383
- package/pi-extension/dist/output-router/status.js +0 -744
- package/pi-extension/dist/output-router/utils.js +0 -502
- package/pi-extension/src/output-router/host-producer-identification.ts +0 -256
- package/pi-extension/src/output-router/native-safety-net.ts +0 -248
- package/pi-extension/src/output-router/observed-tool-routing.ts +0 -134
- package/pi-extension/src/output-router/renderers.ts +0 -1108
- package/pi-extension/src/output-router/router-tools.ts +0 -579
- package/pi-extension/src/output-router/schemas.ts +0 -387
- package/pi-extension/src/output-router/status.ts +0 -791
- package/pi-extension/src/output-router/utils.ts +0 -554
- package/pi-extension/tests/output-router/observed-producers.test.js +0 -132
- package/pi-extension/tests/output-router/search.test.js +0 -722
- package/router/dist/benchmarks/benchmark-harness.d.ts +0 -35
- package/router/dist/benchmarks/benchmark-harness.js +0 -92
- package/router/dist/benchmarks/benchmarks.d.ts +0 -109
- package/router/dist/benchmarks/benchmarks.js +0 -979
- package/router/dist/benchmarks/codex-qa-benchmarks.d.ts +0 -95
- package/router/dist/benchmarks/codex-qa-benchmarks.js +0 -500
- package/router/dist/benchmarks/command-benchmarks.d.ts +0 -143
- package/router/dist/benchmarks/command-benchmarks.js +0 -698
- package/router/dist/benchmarks/context-mode-normalized-benchmarks.d.ts +0 -82
- package/router/dist/benchmarks/context-mode-normalized-benchmarks.js +0 -773
- package/router/dist/benchmarks/context-mode-real-deep-benchmark.d.ts +0 -132
- package/router/dist/benchmarks/context-mode-real-deep-benchmark.js +0 -1380
- package/router/dist/benchmarks/context-saving-baseline.d.ts +0 -60
- package/router/dist/benchmarks/context-saving-baseline.js +0 -470
- package/router/dist/benchmarks/index-benchmarks.d.ts +0 -103
- package/router/dist/benchmarks/index-benchmarks.js +0 -862
- package/router/dist/benchmarks/storage-policy-benchmarks.d.ts +0 -101
- package/router/dist/benchmarks/storage-policy-benchmarks.js +0 -618
- package/router/dist/benchmarks/vault-index-benchmarks.d.ts +0 -56
- package/router/dist/benchmarks/vault-index-benchmarks.js +0 -322
- package/router/dist/config/config.d.ts +0 -103
- package/router/dist/config/config.js +0 -632
- package/router/dist/config/router-contract.d.ts +0 -10
- package/router/dist/config/router-contract.js +0 -54
- package/router/dist/config/schema.d.ts +0 -27
- package/router/dist/config/schema.js +0 -648
- package/router/dist/config/types.d.ts +0 -573
- package/router/dist/config/types.js +0 -41
- package/router/dist/evidence/bounded-evidence.d.ts +0 -40
- package/router/dist/evidence/bounded-evidence.js +0 -323
- package/router/dist/evidence/evidence-range-selector.d.ts +0 -24
- package/router/dist/evidence/evidence-range-selector.js +0 -232
- package/router/dist/evidence/evidence-search.d.ts +0 -27
- package/router/dist/evidence/evidence-search.js +0 -490
- package/router/dist/evidence/evidence.d.ts +0 -38
- package/router/dist/evidence/evidence.js +0 -200
- package/router/dist/evidence/failure-contracts.d.ts +0 -37
- package/router/dist/evidence/failure-contracts.js +0 -166
- package/router/dist/evidence/line-ranges.d.ts +0 -24
- package/router/dist/evidence/line-ranges.js +0 -22
- package/router/dist/experiments/local-index.d.ts +0 -64
- package/router/dist/experiments/local-index.js +0 -581
- package/router/dist/index.d.ts +0 -30
- package/router/dist/index.js +0 -32
- package/router/dist/local/local-traversal.d.ts +0 -18
- package/router/dist/local/local-traversal.js +0 -334
- package/router/dist/processing/engine.d.ts +0 -132
- package/router/dist/processing/engine.js +0 -533
- package/router/dist/processing/reducers.d.ts +0 -280
- package/router/dist/processing/reducers.js +0 -1673
- package/router/dist/processing/renderers.d.ts +0 -31
- package/router/dist/processing/renderers.js +0 -195
- package/router/dist/processing/scripts.d.ts +0 -91
- package/router/dist/processing/scripts.js +0 -386
- package/router/dist/repo/repo-traversal.d.ts +0 -18
- package/router/dist/repo/repo-traversal.js +0 -245
- package/router/dist/routing/observed-reducers.d.ts +0 -41
- package/router/dist/routing/observed-reducers.js +0 -661
- package/router/dist/routing/observed-routing.d.ts +0 -26
- package/router/dist/routing/observed-routing.js +0 -378
- package/router/dist/routing/parsers.d.ts +0 -16
- package/router/dist/routing/parsers.js +0 -521
- package/router/dist/routing/run-filters.d.ts +0 -60
- package/router/dist/routing/run-filters.js +0 -238
- package/router/dist/routing/run-reducers.d.ts +0 -49
- package/router/dist/routing/run-reducers.js +0 -122
- package/router/dist/sandbox/adapter-roots.d.ts +0 -46
- package/router/dist/sandbox/adapter-roots.js +0 -66
- package/router/dist/sandbox/eryx-python-adapter.d.ts +0 -13
- package/router/dist/sandbox/eryx-python-adapter.js +0 -868
- package/router/dist/sandbox/jq-wasm-adapter.d.ts +0 -9
- package/router/dist/sandbox/jq-wasm-adapter.js +0 -429
- package/router/dist/sandbox/quickjs-wasi-adapter.d.ts +0 -13
- package/router/dist/sandbox/quickjs-wasi-adapter.js +0 -384
- package/router/dist/sandbox/script-sandbox.d.ts +0 -134
- package/router/dist/sandbox/script-sandbox.js +0 -337
- package/router/dist/setup/script-transform-adapters.d.ts +0 -32
- package/router/dist/setup/script-transform-adapters.js +0 -278
- package/router/dist/tools/batch.d.ts +0 -33
- package/router/dist/tools/batch.js +0 -586
- package/router/dist/tools/run.d.ts +0 -62
- package/router/dist/tools/run.js +0 -1304
- package/router/dist/tools/search.d.ts +0 -66
- package/router/dist/tools/search.js +0 -1364
- package/router/dist/transform/engine.d.ts +0 -168
- package/router/dist/transform/engine.js +0 -2072
- package/router/dist/vault/vault-index.d.ts +0 -125
- package/router/dist/vault/vault-index.js +0 -416
- package/router/dist/vault/vault.d.ts +0 -138
- package/router/dist/vault/vault.js +0 -630
- package/runtime/interaction-contract.md +0 -16
- package/skills/discuss/references/checkpoints.md +0 -66
- package/skills/execute-work/references/execution-loop.md +0 -77
- package/skills/output-router/SKILL.md +0 -206
- package/skills/output-router/references/safety-policy.md +0 -40
- package/skills/setup-freeflow/references/output-router-setup.md +0 -110
- package/skills/track-work/references/working-record-schema.md +0 -378
- package/skills/workflow/references/workflow-loop.md +0 -92
- /package/skills/handoff/references/{templates.md → handoff-templates.md} +0 -0
- /package/skills/review-artifact/references/{reviewer-prompt.md → independent-artifact-reviewer-contract.md} +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import crypto from "node:crypto";
|
|
3
4
|
import fs from "node:fs";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
import process from "node:process";
|
|
@@ -8,6 +9,9 @@ import { fileURLToPath } from "node:url";
|
|
|
8
9
|
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
9
10
|
const PLUGIN_ROOT = path.resolve(SCRIPT_DIR, "..");
|
|
10
11
|
const VALID_MODES = new Set(["conversation", "workflow", "strict-workflow"]);
|
|
12
|
+
const SESSION_STATE_VERSION = 1;
|
|
13
|
+
const SESSION_STATE_MAX_AGE_MS = 90 * 24 * 60 * 60 * 1000;
|
|
14
|
+
const CLEAR_TRANSFER_MAX_AGE_MS = 60 * 1000;
|
|
11
15
|
|
|
12
16
|
function readStdinJson() {
|
|
13
17
|
const raw = fs.readFileSync(0, "utf8").trim();
|
|
@@ -49,24 +53,17 @@ function findWorkspaceRoot(cwd) {
|
|
|
49
53
|
function loadRuntimeContext(options = {}) {
|
|
50
54
|
const includeInteractionContract = options.interactionContract === true;
|
|
51
55
|
const includeSkills = options.skills === true;
|
|
52
|
-
const
|
|
56
|
+
const corePrompt = readText(path.join(PLUGIN_ROOT, "runtime", "prompts", "core.md"));
|
|
53
57
|
const interactionContract = includeInteractionContract
|
|
54
|
-
? readText(path.join(PLUGIN_ROOT, "runtime", "interaction-contract.md"))
|
|
55
|
-
: null;
|
|
56
|
-
const workflowSkill = includeSkills ? readText(path.join(PLUGIN_ROOT, "skills", "workflow", "SKILL.md")) : null;
|
|
57
|
-
const outputRouterSkill = includeOutputRouter
|
|
58
|
-
? readText(path.join(PLUGIN_ROOT, "skills", "output-router", "SKILL.md"))
|
|
58
|
+
? readText(path.join(PLUGIN_ROOT, "runtime", "prompts", "interaction-contract.md"))
|
|
59
59
|
: null;
|
|
60
|
+
const skillsPrompt = includeSkills ? readText(path.join(PLUGIN_ROOT, "runtime", "prompts", "skills.md")) : null;
|
|
60
61
|
|
|
61
|
-
if (
|
|
62
|
-
(
|
|
63
|
-
(includeSkills && !workflowSkill) ||
|
|
64
|
-
(includeOutputRouter && !outputRouterSkill)
|
|
65
|
-
) {
|
|
66
|
-
throw new Error("Freeflow runtime context files are missing.");
|
|
62
|
+
if (!corePrompt || (includeInteractionContract && !interactionContract) || (includeSkills && !skillsPrompt)) {
|
|
63
|
+
throw new Error("Freeflow runtime prompt files are missing.");
|
|
67
64
|
}
|
|
68
65
|
|
|
69
|
-
return {
|
|
66
|
+
return { corePrompt, interactionContract, skillsPrompt };
|
|
70
67
|
}
|
|
71
68
|
|
|
72
69
|
function readConfig(root) {
|
|
@@ -76,7 +73,6 @@ function readConfig(root) {
|
|
|
76
73
|
const configured = repository.valid && localValid;
|
|
77
74
|
const core = resolveCoreConfig(repository.valid ? repository.parsed : {}, local.valid ? local.parsed : {});
|
|
78
75
|
const enabled = configured && core.config.enabled;
|
|
79
|
-
const repositoryConfig = repository.valid ? repository.parsed : {};
|
|
80
76
|
|
|
81
77
|
return {
|
|
82
78
|
exists: repository.exists,
|
|
@@ -85,13 +81,12 @@ function readConfig(root) {
|
|
|
85
81
|
repositoryValid: repository.valid,
|
|
86
82
|
localExists: local.exists,
|
|
87
83
|
localValid,
|
|
88
|
-
error:
|
|
84
|
+
error: repository.valid ? local.error : repository.error,
|
|
89
85
|
enabled,
|
|
90
86
|
interactionContractEnabled: enabled && core.config.interactionContract,
|
|
91
87
|
skillsEnabled: enabled && core.config.skills.enabled,
|
|
92
88
|
defaultMode: core.config.defaultMode,
|
|
93
89
|
sources: core.sources,
|
|
94
|
-
outputRouterEnabled: enabled && repositoryConfig?.outputRouter?.enabled === true,
|
|
95
90
|
};
|
|
96
91
|
}
|
|
97
92
|
|
|
@@ -126,23 +121,20 @@ function isRecord(value) {
|
|
|
126
121
|
}
|
|
127
122
|
|
|
128
123
|
function validateCoreConfigFields(value) {
|
|
129
|
-
if (Object.
|
|
124
|
+
if (Object.hasOwn(value, "defaultMode") && !VALID_MODES.has(value.defaultMode)) {
|
|
130
125
|
return "defaultMode must be conversation, workflow, or strict-workflow";
|
|
131
126
|
}
|
|
132
|
-
if (Object.
|
|
127
|
+
if (Object.hasOwn(value, "enabled") && typeof value.enabled !== "boolean") {
|
|
133
128
|
return "enabled must be a boolean";
|
|
134
129
|
}
|
|
135
|
-
if (
|
|
136
|
-
Object.prototype.hasOwnProperty.call(value, "interactionContract") &&
|
|
137
|
-
typeof value.interactionContract !== "boolean"
|
|
138
|
-
) {
|
|
130
|
+
if (Object.hasOwn(value, "interactionContract") && typeof value.interactionContract !== "boolean") {
|
|
139
131
|
return "interactionContract must be a boolean";
|
|
140
132
|
}
|
|
141
|
-
if (Object.
|
|
133
|
+
if (Object.hasOwn(value, "skills")) {
|
|
142
134
|
if (!isRecord(value.skills)) {
|
|
143
135
|
return "skills must be an object";
|
|
144
136
|
}
|
|
145
|
-
if (Object.
|
|
137
|
+
if (Object.hasOwn(value.skills, "enabled") && typeof value.skills.enabled !== "boolean") {
|
|
146
138
|
return "skills.enabled must be a boolean";
|
|
147
139
|
}
|
|
148
140
|
}
|
|
@@ -162,6 +154,7 @@ function isValidSetupConfig(value) {
|
|
|
162
154
|
"outputRouter",
|
|
163
155
|
"observedRouting",
|
|
164
156
|
"scriptTransform",
|
|
157
|
+
"cognitiveRouting",
|
|
165
158
|
]);
|
|
166
159
|
if (!Object.keys(value).every((key) => allowedKeys.has(key))) {
|
|
167
160
|
return "repository config contains unsupported top-level keys";
|
|
@@ -173,7 +166,7 @@ function isValidSetupConfig(value) {
|
|
|
173
166
|
}
|
|
174
167
|
|
|
175
168
|
for (const key of ["outputRouter", "observedRouting", "scriptTransform"]) {
|
|
176
|
-
if (Object.
|
|
169
|
+
if (Object.hasOwn(value, key) && !isRecord(value[key])) {
|
|
177
170
|
return `${key} must be an object`;
|
|
178
171
|
}
|
|
179
172
|
}
|
|
@@ -185,7 +178,14 @@ function isValidLocalConfig(value) {
|
|
|
185
178
|
return "local config must be a JSON object";
|
|
186
179
|
}
|
|
187
180
|
|
|
188
|
-
const allowedKeys = new Set([
|
|
181
|
+
const allowedKeys = new Set([
|
|
182
|
+
"enabled",
|
|
183
|
+
"defaultMode",
|
|
184
|
+
"interactionContract",
|
|
185
|
+
"skills",
|
|
186
|
+
"processing",
|
|
187
|
+
"cognitiveRouting",
|
|
188
|
+
]);
|
|
189
189
|
if (!Object.keys(value).every((key) => allowedKeys.has(key))) {
|
|
190
190
|
return "local config contains unsupported top-level keys";
|
|
191
191
|
}
|
|
@@ -193,7 +193,7 @@ function isValidLocalConfig(value) {
|
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
function hasOwn(value, key) {
|
|
196
|
-
return Object.
|
|
196
|
+
return Object.hasOwn(value, key);
|
|
197
197
|
}
|
|
198
198
|
|
|
199
199
|
function resolveLayeredValue(repository, local, key, fallback) {
|
|
@@ -230,159 +230,391 @@ function resolveCoreConfig(repository, local) {
|
|
|
230
230
|
};
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
-
function
|
|
234
|
-
return
|
|
233
|
+
function hostName() {
|
|
234
|
+
return process.env.PLUGIN_DATA ? "codex" : "claude";
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
-
function
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}
|
|
241
|
-
const source = config.sources.defaultMode;
|
|
242
|
-
const personal = config.localExists ? "; personal overrides present" : "";
|
|
243
|
-
return `defaultMode \`${config.defaultMode}\` from ${source}${personal}`;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
function modeOverlayGuidance(mode) {
|
|
247
|
-
if (mode === "conversation") {
|
|
248
|
-
return [
|
|
249
|
-
"",
|
|
250
|
-
"## Conversation Mode Boundary",
|
|
251
|
-
"",
|
|
252
|
-
"Conversation mode is active and read-only.",
|
|
253
|
-
"",
|
|
254
|
-
"Do not call write, edit, or mutating tools. Do not create, delete, commit, push, or change repository, system, external, or durable state.",
|
|
255
|
-
"",
|
|
256
|
-
"A mutation request does not switch mode, and an execution skill does not override this boundary. Explain that conversation mode is read-only and ask the user to switch to workflow or strict-workflow.",
|
|
257
|
-
];
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
if (mode === "strict-workflow") {
|
|
261
|
-
return [
|
|
262
|
-
"",
|
|
263
|
-
"## Strict Workflow Overlay",
|
|
264
|
-
"",
|
|
265
|
-
"Strict Workflow is active. Use the adaptive Workflow, but increase decision, evidence, verification, and checkpoint pressure at high-risk or hard-to-reverse boundaries.",
|
|
266
|
-
"",
|
|
267
|
-
"For work affecting security, privacy, billing, data loss, migrations, public interfaces, compatibility, deployment, or architecture:",
|
|
268
|
-
"",
|
|
269
|
-
"- stop for any user-owned choice or source conflict;",
|
|
270
|
-
"- inspect the relevant risk surface before crossing the boundary;",
|
|
271
|
-
"- select only artifacts, checkpoints, and independent review that materially reduce risk;",
|
|
272
|
-
"- verify at the affected boundary before claiming success.",
|
|
273
|
-
"",
|
|
274
|
-
"Do not manufacture ceremony for low-risk, reversible work. Strict Workflow does not authorize mutation, bypass safety, or make every implementation detail a user decision.",
|
|
275
|
-
];
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
return [];
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
function modeGuidance(mode) {
|
|
282
|
-
return [
|
|
283
|
-
`Current Freeflow default mode: \`${mode}\`.`,
|
|
284
|
-
"Treat this as the resolved default at session start, resume, clear, and compact.",
|
|
285
|
-
"Mode behavior:",
|
|
286
|
-
"- `conversation`: answer, discuss, critique, and inspect read-only; switch modes before mutating state.",
|
|
287
|
-
"- `workflow`: use the adaptive workflow for normal consequential work.",
|
|
288
|
-
"- `strict-workflow`: strengthen user-owned decisions, review, and verification for high-risk or hard-to-reverse work.",
|
|
289
|
-
"Do not announce the current mode on every reply. Mention it when the user asks, setup/config is discussed, or the mode changes the next action.",
|
|
290
|
-
...modeOverlayGuidance(mode),
|
|
291
|
-
];
|
|
237
|
+
function pluginDataRoot() {
|
|
238
|
+
const configured = process.env.PLUGIN_DATA || process.env.CLAUDE_PLUGIN_DATA;
|
|
239
|
+
return configured ? path.resolve(configured) : null;
|
|
292
240
|
}
|
|
293
241
|
|
|
294
|
-
function
|
|
295
|
-
const
|
|
296
|
-
if (!
|
|
297
|
-
return
|
|
242
|
+
function sessionStatePath(sessionId) {
|
|
243
|
+
const dataRoot = pluginDataRoot();
|
|
244
|
+
if (!dataRoot || typeof sessionId !== "string" || sessionId.length === 0) {
|
|
245
|
+
return null;
|
|
298
246
|
}
|
|
299
247
|
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
: [`Resolved default mode: \`${config.defaultMode}\` (dormant because Skills are disabled).`];
|
|
303
|
-
return [
|
|
304
|
-
`Setup status: configured by \`.freeflow/config.json\` with ${configStatus(config)}.`,
|
|
305
|
-
"Runtime delivery: confirmed for this lifecycle-hook invocation.",
|
|
306
|
-
...modeStatus,
|
|
307
|
-
].join("\n");
|
|
248
|
+
const key = crypto.createHash("sha256").update(`${hostName()}\0${sessionId}`).digest("hex");
|
|
249
|
+
return path.join(dataRoot, "session-modes", hostName(), `${key}.json`);
|
|
308
250
|
}
|
|
309
251
|
|
|
310
|
-
function
|
|
311
|
-
|
|
252
|
+
function workspaceKey(root) {
|
|
253
|
+
return crypto.createHash("sha256").update(path.resolve(root)).digest("hex");
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function claudeProcessKey() {
|
|
257
|
+
const processId = process.env.CLAUDE_PID;
|
|
258
|
+
if (typeof processId !== "string" || processId.length === 0) {
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
return crypto.createHash("sha256").update(processId).digest("hex");
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function clearTransferPath(root) {
|
|
265
|
+
const dataRoot = pluginDataRoot();
|
|
266
|
+
const processKey = claudeProcessKey();
|
|
267
|
+
if (!dataRoot || hostName() !== "claude" || !processKey) {
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
270
|
+
return path.join(dataRoot, "session-modes", "claude-clear", `${workspaceKey(root)}-${processKey}.json`);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function writeJsonAtomic(filePath, value) {
|
|
274
|
+
const directory = path.dirname(filePath);
|
|
275
|
+
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
276
|
+
const temporaryPath = `${filePath}.${process.pid}.${crypto.randomUUID()}.tmp`;
|
|
277
|
+
try {
|
|
278
|
+
fs.writeFileSync(temporaryPath, `${JSON.stringify(value)}\n`, { encoding: "utf8", mode: 0o600 });
|
|
279
|
+
fs.renameSync(temporaryPath, filePath);
|
|
280
|
+
} catch (error) {
|
|
281
|
+
try {
|
|
282
|
+
fs.unlinkSync(temporaryPath);
|
|
283
|
+
} catch {
|
|
284
|
+
// Best-effort cleanup of a failed atomic write.
|
|
285
|
+
}
|
|
286
|
+
throw error;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function readSessionMode(sessionId) {
|
|
291
|
+
const filePath = sessionStatePath(sessionId);
|
|
292
|
+
if (!filePath) {
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
try {
|
|
297
|
+
const state = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
298
|
+
if (state.version !== SESSION_STATE_VERSION || !VALID_MODES.has(state.mode)) {
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
return state.mode;
|
|
302
|
+
} catch {
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function writeSessionMode(sessionId, mode) {
|
|
308
|
+
const filePath = sessionStatePath(sessionId);
|
|
309
|
+
if (!filePath) {
|
|
312
310
|
return false;
|
|
313
311
|
}
|
|
314
312
|
|
|
315
|
-
|
|
313
|
+
writeJsonAtomic(filePath, {
|
|
314
|
+
version: SESSION_STATE_VERSION,
|
|
315
|
+
mode,
|
|
316
|
+
updatedAt: new Date().toISOString(),
|
|
317
|
+
});
|
|
318
|
+
return true;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function clearSessionMode(sessionId) {
|
|
322
|
+
const filePath = sessionStatePath(sessionId);
|
|
323
|
+
if (!filePath) {
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
try {
|
|
328
|
+
fs.unlinkSync(filePath);
|
|
329
|
+
} catch (error) {
|
|
330
|
+
if (error?.code !== "ENOENT") {
|
|
331
|
+
throw error;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return true;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function stageClearTransfer(input) {
|
|
338
|
+
if (hostName() !== "claude" || input.reason !== "clear") {
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const root = findWorkspaceRoot(input.cwd || process.cwd());
|
|
343
|
+
const mode = readSessionMode(input.session_id);
|
|
344
|
+
const filePath = clearTransferPath(root);
|
|
345
|
+
if (!filePath) {
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (!mode) {
|
|
350
|
+
try {
|
|
351
|
+
fs.unlinkSync(filePath);
|
|
352
|
+
} catch (error) {
|
|
353
|
+
if (error?.code !== "ENOENT") {
|
|
354
|
+
throw error;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
writeJsonAtomic(filePath, {
|
|
361
|
+
version: SESSION_STATE_VERSION,
|
|
362
|
+
mode,
|
|
363
|
+
sourceSessionKey: crypto.createHash("sha256").update(String(input.session_id)).digest("hex"),
|
|
364
|
+
createdAt: new Date().toISOString(),
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function consumeClearTransfer(input, root) {
|
|
369
|
+
if (hostName() !== "claude" || input.source !== "clear") {
|
|
370
|
+
return null;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const filePath = clearTransferPath(root);
|
|
374
|
+
if (!filePath) {
|
|
375
|
+
return null;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const claimedPath = `${filePath}.${process.pid}.${crypto.randomUUID()}.claimed`;
|
|
379
|
+
try {
|
|
380
|
+
fs.renameSync(filePath, claimedPath);
|
|
381
|
+
} catch (error) {
|
|
382
|
+
if (error?.code === "ENOENT") {
|
|
383
|
+
return null;
|
|
384
|
+
}
|
|
385
|
+
return null;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
let state;
|
|
389
|
+
try {
|
|
390
|
+
state = JSON.parse(fs.readFileSync(claimedPath, "utf8"));
|
|
391
|
+
} catch {
|
|
392
|
+
return null;
|
|
393
|
+
} finally {
|
|
394
|
+
try {
|
|
395
|
+
fs.unlinkSync(claimedPath);
|
|
396
|
+
} catch {
|
|
397
|
+
// The atomic claim already prevents reuse; cleanup is best-effort.
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (!isRecord(state)) {
|
|
402
|
+
return null;
|
|
403
|
+
}
|
|
404
|
+
const createdAt = Date.parse(state.createdAt);
|
|
405
|
+
if (
|
|
406
|
+
state.version !== SESSION_STATE_VERSION ||
|
|
407
|
+
!VALID_MODES.has(state.mode) ||
|
|
408
|
+
!Number.isFinite(createdAt) ||
|
|
409
|
+
Date.now() - createdAt > CLEAR_TRANSFER_MAX_AGE_MS ||
|
|
410
|
+
Date.now() < createdAt
|
|
411
|
+
) {
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return writeSessionMode(input.session_id, state.mode) ? state.mode : null;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function cleanupExpiredSessionModes() {
|
|
419
|
+
const root = pluginDataRoot();
|
|
420
|
+
if (!root) {
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const stateRoot = path.join(root, "session-modes");
|
|
425
|
+
const cutoff = Date.now() - SESSION_STATE_MAX_AGE_MS;
|
|
426
|
+
for (const host of ["claude", "codex", "claude-clear"]) {
|
|
427
|
+
const directory = path.join(stateRoot, host);
|
|
428
|
+
let entries = [];
|
|
429
|
+
try {
|
|
430
|
+
entries = fs.readdirSync(directory, { withFileTypes: true });
|
|
431
|
+
} catch {
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
for (const entry of entries) {
|
|
436
|
+
if (!entry.isFile() || !entry.name.endsWith(".json")) {
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
const filePath = path.join(directory, entry.name);
|
|
440
|
+
try {
|
|
441
|
+
if (fs.statSync(filePath).mtimeMs < cutoff) {
|
|
442
|
+
fs.unlinkSync(filePath);
|
|
443
|
+
}
|
|
444
|
+
} catch {
|
|
445
|
+
// Cleanup is best-effort and must not prevent runtime delivery.
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function sourceLabel(source) {
|
|
452
|
+
if (source === "local") {
|
|
453
|
+
return "personal";
|
|
454
|
+
}
|
|
455
|
+
return source;
|
|
316
456
|
}
|
|
317
457
|
|
|
318
|
-
function
|
|
458
|
+
function resolveRuntimeState(input) {
|
|
459
|
+
const root = findWorkspaceRoot(input.cwd || process.cwd());
|
|
460
|
+
const config = readConfig(root);
|
|
461
|
+
const restoredMode = config.valid && config.enabled ? consumeClearTransfer(input, root) : null;
|
|
462
|
+
const sessionMode = config.valid && config.enabled ? restoredMode || readSessionMode(input.session_id) : null;
|
|
463
|
+
const resolvedMode = sessionMode || config.defaultMode;
|
|
464
|
+
|
|
465
|
+
return {
|
|
466
|
+
root,
|
|
467
|
+
config,
|
|
468
|
+
sessionMode,
|
|
469
|
+
resolvedMode,
|
|
470
|
+
effectiveMode: config.enabled && config.skillsEnabled ? resolvedMode : null,
|
|
471
|
+
modeSource: sessionMode ? "session override" : "configured default",
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function stateLines(state, sessionChange = null) {
|
|
476
|
+
const { config } = state;
|
|
477
|
+
const lines = [`Configured default: \`${config.defaultMode}\` (${sourceLabel(config.sources.defaultMode)})`];
|
|
478
|
+
|
|
479
|
+
if (sessionChange === "cleared") {
|
|
480
|
+
lines.push("Session override: cleared");
|
|
481
|
+
} else if (state.sessionMode) {
|
|
482
|
+
lines.push(`Session override: \`${state.sessionMode}\``);
|
|
483
|
+
} else {
|
|
484
|
+
lines.push("Session override: none");
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
if (state.effectiveMode) {
|
|
488
|
+
lines.push(`Effective mode: \`${state.effectiveMode}\` (${state.modeSource}, active)`);
|
|
489
|
+
} else {
|
|
490
|
+
lines.push(`Resolved mode: \`${state.resolvedMode}\` (dormant because Skills are disabled)`);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
return lines;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function capabilityLines(config) {
|
|
319
497
|
return [
|
|
320
|
-
"
|
|
321
|
-
""
|
|
322
|
-
`- Interaction Contract: ${config.interactionContractEnabled ? "enabled" : "disabled"}.`,
|
|
323
|
-
`- Skills: ${config.skillsEnabled ? "enabled" : "disabled"}.`,
|
|
324
|
-
`- Output router: ${config.outputRouterEnabled ? "enabled" : "disabled"}.`,
|
|
325
|
-
"",
|
|
326
|
-
"Capability-specific instructions are active only while that capability is enabled.",
|
|
498
|
+
`Interaction Contract: ${config.interactionContractEnabled ? "enabled" : "disabled"}`,
|
|
499
|
+
`Skills: ${config.skillsEnabled ? "enabled" : "disabled"}`,
|
|
327
500
|
];
|
|
328
501
|
}
|
|
329
502
|
|
|
330
|
-
function
|
|
331
|
-
const
|
|
332
|
-
|
|
503
|
+
function runtimeStateContext(state, sessionChange = null) {
|
|
504
|
+
const { config } = state;
|
|
505
|
+
return ["# Freeflow Runtime State", "", ...stateLines(state, sessionChange), "", ...capabilityLines(config)].join(
|
|
506
|
+
"\n",
|
|
507
|
+
);
|
|
508
|
+
}
|
|
333
509
|
|
|
334
|
-
|
|
510
|
+
function renderSessionStart(state) {
|
|
511
|
+
const { config } = state;
|
|
512
|
+
if (!config.valid) {
|
|
335
513
|
return "";
|
|
336
514
|
}
|
|
337
515
|
|
|
338
|
-
if (!
|
|
339
|
-
return
|
|
340
|
-
"# Freeflow Disabled",
|
|
341
|
-
"",
|
|
342
|
-
`Freeflow is configured for this repo, but effective \`enabled\` is false from ${setup.config.sources.enabled}.`,
|
|
343
|
-
"Do not inject the Interaction Contract, Freeflow workflow skills, output routing, or setup pressure while disabled.",
|
|
344
|
-
"Re-enable only if the user asks; use /freeflow settings for a personal override or /freeflow settings repo for the shared repository default in Pi.",
|
|
345
|
-
].join("\n");
|
|
516
|
+
if (!config.enabled) {
|
|
517
|
+
return "";
|
|
346
518
|
}
|
|
347
519
|
|
|
348
|
-
const {
|
|
349
|
-
interactionContract:
|
|
350
|
-
skills:
|
|
351
|
-
outputRouter: setup.config.outputRouterEnabled,
|
|
520
|
+
const { corePrompt, interactionContract, skillsPrompt } = loadRuntimeContext({
|
|
521
|
+
interactionContract: config.interactionContractEnabled,
|
|
522
|
+
skills: config.skillsEnabled,
|
|
352
523
|
});
|
|
353
|
-
const
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
return;
|
|
524
|
+
const context = [corePrompt.trim()];
|
|
525
|
+
|
|
526
|
+
if (config.interactionContractEnabled) {
|
|
527
|
+
context.push(interactionContract.trim());
|
|
528
|
+
}
|
|
529
|
+
if (config.skillsEnabled) {
|
|
530
|
+
context.push(skillsPrompt.trim());
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
context.push(runtimeStateContext(state));
|
|
534
|
+
return context.filter(Boolean).join("\n\n");
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
function normalizePrompt(prompt) {
|
|
538
|
+
return prompt
|
|
539
|
+
.trim()
|
|
540
|
+
.replace(/[.!]+$/u, "")
|
|
541
|
+
.replace(/\s+/gu, " ");
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function parseModeControl(prompt) {
|
|
545
|
+
if (typeof prompt !== "string" || prompt.length > 300 || /\?\s*$/u.test(prompt.trim())) {
|
|
546
|
+
return null;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
const normalized = normalizePrompt(prompt);
|
|
550
|
+
const nativeSet = normalized.match(
|
|
551
|
+
/^(?:\/freeflow mode|\$mode-contract|\/(?:freeflow:)?mode-contract) (conversation|workflow|strict-workflow)$/iu,
|
|
552
|
+
);
|
|
553
|
+
if (nativeSet) {
|
|
554
|
+
return { action: "set", mode: nativeSet[1].toLowerCase() };
|
|
555
|
+
}
|
|
556
|
+
if (/^(?:\/freeflow mode|\$mode-contract|\/(?:freeflow:)?mode-contract) reset$/iu.test(normalized)) {
|
|
557
|
+
return { action: "reset" };
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
const setPatterns = [
|
|
561
|
+
/^switch to (conversation|workflow|strict-workflow)(?: mode)?$/iu,
|
|
562
|
+
/^switch (?:this|the) session to (conversation|workflow|strict-workflow)(?: mode)?$/iu,
|
|
563
|
+
/^use (conversation|workflow|strict-workflow)(?: mode)? for (?:this|the) session$/iu,
|
|
564
|
+
/^(?:change|set) (?:the )?(?:current|active|session) mode to (conversation|workflow|strict-workflow)$/iu,
|
|
565
|
+
/^switch (?:the )?(?:current|active|session) mode to (conversation|workflow|strict-workflow)$/iu,
|
|
566
|
+
];
|
|
567
|
+
for (const pattern of setPatterns) {
|
|
568
|
+
const match = normalized.match(pattern);
|
|
569
|
+
if (match) {
|
|
570
|
+
return { action: "set", mode: match[1].toLowerCase() };
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
if (/^(?:reset|clear|remove) (?:the )?(?:current |active )?session mode(?: override)?$/iu.test(normalized)) {
|
|
575
|
+
return { action: "reset" };
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
return null;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function handleUserPromptSubmit(input) {
|
|
582
|
+
const control = parseModeControl(input.prompt);
|
|
583
|
+
if (!control) {
|
|
584
|
+
return "";
|
|
384
585
|
}
|
|
385
586
|
|
|
587
|
+
const before = resolveRuntimeState(input);
|
|
588
|
+
if (!before.config.valid || !before.config.enabled) {
|
|
589
|
+
return "";
|
|
590
|
+
}
|
|
591
|
+
if (!before.config.skillsEnabled) {
|
|
592
|
+
return runtimeStateContext(before);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
if (control.action === "set") {
|
|
596
|
+
if (!writeSessionMode(input.session_id, control.mode)) {
|
|
597
|
+
return runtimeStateContext(before);
|
|
598
|
+
}
|
|
599
|
+
const state = resolveRuntimeState(input);
|
|
600
|
+
return runtimeStateContext(state, "set");
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
if (!clearSessionMode(input.session_id)) {
|
|
604
|
+
return runtimeStateContext(before);
|
|
605
|
+
}
|
|
606
|
+
const state = resolveRuntimeState(input);
|
|
607
|
+
return runtimeStateContext(state, "cleared");
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
function shouldHandle(eventName) {
|
|
611
|
+
if (process.env.FREEFLOW_DISABLE_RUNTIME_CONTEXT === "1") {
|
|
612
|
+
return false;
|
|
613
|
+
}
|
|
614
|
+
return eventName === "SessionStart" || eventName === "UserPromptSubmit" || eventName === "SessionEnd";
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function emitAdditionalContext(eventName, additionalContext) {
|
|
386
618
|
process.stdout.write(
|
|
387
619
|
`${JSON.stringify({
|
|
388
620
|
hookSpecificOutput: {
|
|
@@ -397,19 +629,21 @@ function main() {
|
|
|
397
629
|
const { input, raw } = readStdinJson();
|
|
398
630
|
const eventName = process.argv[2] || input.hook_event_name || "";
|
|
399
631
|
|
|
400
|
-
if (!raw && !eventName) {
|
|
632
|
+
if ((!raw && !eventName) || !shouldHandle(eventName)) {
|
|
401
633
|
return;
|
|
402
634
|
}
|
|
403
635
|
|
|
404
|
-
|
|
636
|
+
cleanupExpiredSessionModes();
|
|
637
|
+
if (eventName === "SessionEnd") {
|
|
638
|
+
stageClearTransfer(input);
|
|
405
639
|
return;
|
|
406
640
|
}
|
|
407
|
-
|
|
408
|
-
|
|
641
|
+
const additionalContext =
|
|
642
|
+
eventName === "SessionStart" ? renderSessionStart(resolveRuntimeState(input)) : handleUserPromptSubmit(input);
|
|
409
643
|
if (!additionalContext.trim()) {
|
|
410
644
|
return;
|
|
411
645
|
}
|
|
412
|
-
emitAdditionalContext(eventName,
|
|
646
|
+
emitAdditionalContext(eventName, additionalContext);
|
|
413
647
|
}
|
|
414
648
|
|
|
415
649
|
try {
|