@hassangameryt/freeflow 0.7.3 → 0.9.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/.agents/plugins/marketplace.json +2 -2
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +2 -2
- package/.codex-plugin/plugin.json +3 -3
- package/.cursor-plugin/plugin.json +1 -1
- package/NOTICE +14 -0
- package/README.md +101 -159
- package/capabilities/cognitive-routing/SKILL.md +47 -210
- package/capabilities/cognitive-routing/references/coordinator.md +163 -0
- package/capabilities/cognitive-routing/references/worker.md +38 -0
- package/capabilities/compaction/SKILL.md +49 -0
- package/capabilities/compaction/references/summary-format.md +250 -0
- package/capabilities/messaging/SKILL.md +49 -0
- package/capabilities/task-memory/SKILL.md +87 -0
- package/command-surface.json +8 -52
- package/gemini-extension.json +1 -1
- package/hooks/claude/hooks.json +13 -3
- package/hooks/codex/hooks.json +2 -1
- package/hooks/shared/runtime-context.mjs +73 -83
- package/licenses/Apache-2.0.txt +55 -0
- package/package.json +42 -12
- package/pi-extension/README.md +41 -0
- package/pi-extension/dist/ask-user/ask.js +108 -0
- package/pi-extension/dist/ask-user/form.js +250 -0
- package/pi-extension/dist/ask-user/questions.js +66 -0
- package/pi-extension/dist/ask-user/tool.js +124 -0
- package/pi-extension/dist/cognitive-routing/assembler.js +417 -0
- package/pi-extension/dist/cognitive-routing/bootstrap.js +90 -0
- package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/config.js +8 -6
- 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} +107 -41
- package/pi-extension/dist/cognitive-routing/gate.js +256 -0
- package/pi-extension/dist/cognitive-routing/handlers.js +901 -0
- package/pi-extension/dist/cognitive-routing/handoffs.js +350 -0
- package/pi-extension/dist/cognitive-routing/model-control.js +476 -0
- package/pi-extension/dist/cognitive-routing/projection.js +520 -0
- package/pi-extension/dist/cognitive-routing/provenance.js +83 -0
- package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/render.js +15 -15
- package/pi-extension/dist/cognitive-routing/runtime.js +398 -0
- package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/schemas.js +47 -11
- package/pi-extension/dist/cognitive-routing/session.js +223 -0
- package/pi-extension/dist/{session-sources → cognitive-routing}/sources.js +126 -87
- package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/state.js +55 -386
- package/pi-extension/dist/cognitive-routing/status.js +81 -0
- package/pi-extension/dist/cognitive-routing/tools.js +74 -0
- package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/types.js +14 -11
- package/pi-extension/dist/compaction/carry.js +205 -0
- package/pi-extension/dist/compaction/config.js +24 -0
- package/pi-extension/dist/compaction/controller.js +494 -0
- package/pi-extension/dist/compaction/harness.js +93 -0
- package/pi-extension/dist/compaction/results.js +159 -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 +62 -0
- package/pi-extension/dist/host/config-validation.js +233 -0
- package/pi-extension/dist/host/config.js +361 -0
- package/pi-extension/dist/host/inbound.js +142 -0
- package/pi-extension/dist/host/projection-tags.js +60 -0
- package/pi-extension/dist/host/prompts.js +93 -0
- package/pi-extension/dist/{session-sources → host}/read-only-session.js +117 -1
- package/pi-extension/dist/{runtime → host}/request-history.js +125 -39
- package/pi-extension/dist/host/rows.js +25 -0
- package/pi-extension/dist/host/runtime-state.js +204 -0
- package/pi-extension/dist/host/section-keeper.js +85 -0
- package/pi-extension/dist/host/settings/freeflow-command.js +2323 -0
- package/pi-extension/dist/{settings/settings-tui.js → host/settings/settings-view.js} +205 -73
- package/pi-extension/dist/host/staging.js +28 -0
- package/pi-extension/dist/host/status.js +115 -0
- package/pi-extension/dist/index.js +1004 -188
- package/pi-extension/dist/messaging/config.js +27 -0
- package/pi-extension/dist/messaging/messaging.js +398 -0
- package/pi-extension/dist/messaging/registry.js +57 -0
- package/pi-extension/dist/messaging/render.js +90 -0
- package/pi-extension/dist/messaging/tools.js +109 -0
- package/pi-extension/dist/messaging/transport.js +101 -0
- 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 +222 -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/task-memory/config.js +49 -0
- package/pi-extension/dist/task-memory/registry.js +119 -0
- package/pi-extension/dist/task-memory/render.js +107 -0
- package/pi-extension/dist/task-memory/runtime.js +994 -0
- package/pi-extension/dist/task-memory/tool.js +233 -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 +430 -0
- package/pi-extension/dist/tool-execution/bash-guard.js +23 -0
- package/pi-extension/dist/tool-execution/config.js +33 -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 +163 -0
- package/pi-extension/dist/tool-execution/guidance-notices.js +124 -0
- package/pi-extension/dist/tool-execution/prompt.js +54 -0
- package/pi-extension/dist/tool-execution/skill-reads.js +143 -0
- package/pi-extension/dist/tool-execution/tools.js +16 -0
- package/pi-extension/freeflow/README.md +9 -0
- package/runtime/prompts/cognitive-routing.md +4 -4
- package/runtime/prompts/core.md +29 -72
- package/runtime/prompts/interaction-contract.md +18 -8
- package/runtime/prompts/tool-execution.md +25 -0
- package/runtime/prompts/working-method.md +25 -0
- package/skills/deliver-work/SKILL.md +76 -0
- package/skills/deliver-work/references/branch-closeout.md +134 -0
- package/skills/deliver-work/references/commit.md +83 -0
- package/skills/deliver-work/references/launch.md +94 -0
- package/skills/deliver-work/references/release.md +85 -0
- package/skills/deliver-work/scripts/commit-state.mjs +139 -0
- package/skills/deliver-work/scripts/delivery-state.mjs +107 -0
- package/skills/deliver-work/scripts/lib/git-state.mjs +178 -0
- package/skills/deliver-work/scripts/push-route.mjs +122 -0
- package/skills/deliver-work/scripts/release-preflight.mjs +177 -0
- package/skills/discuss-work/SKILL.md +148 -0
- package/skills/{discuss → discuss-work}/references/discussion-continuity.md +13 -50
- package/skills/discuss-work/references/learning-action.md +36 -0
- package/skills/discuss-work/scripts/choices.mjs +101 -0
- package/skills/discuss-work/scripts/lib/choices.mjs +159 -0
- package/skills/evaluate-behavior/SKILL.md +107 -0
- package/skills/evaluate-behavior/references/authoring.md +381 -0
- package/skills/evaluate-behavior/references/coding-tasks.md +145 -0
- package/skills/evaluate-behavior/references/conversations-pipelines-judges.md +177 -0
- package/skills/evaluate-behavior/references/design-and-measurement.md +273 -0
- package/skills/evaluate-behavior/references/running-and-evidence.md +245 -0
- package/skills/evaluate-behavior/scripts/claude-guard.mjs +76 -0
- package/skills/evaluate-behavior/scripts/guard-rules.mjs +309 -0
- package/skills/evaluate-behavior/scripts/lib/agreement.mjs +90 -0
- package/skills/evaluate-behavior/scripts/lib/arms.mjs +55 -0
- package/skills/evaluate-behavior/scripts/lib/budget.mjs +87 -0
- package/skills/evaluate-behavior/scripts/lib/build-source.mjs +287 -0
- package/skills/evaluate-behavior/scripts/lib/clear.mjs +74 -0
- package/skills/evaluate-behavior/scripts/lib/container.mjs +395 -0
- package/skills/evaluate-behavior/scripts/lib/decision.mjs +418 -0
- package/skills/evaluate-behavior/scripts/lib/definitions.mjs +1072 -0
- package/skills/evaluate-behavior/scripts/lib/freeflow.mjs +103 -0
- package/skills/evaluate-behavior/scripts/lib/grade-command.mjs +591 -0
- package/skills/evaluate-behavior/scripts/lib/grade-comparison.mjs +90 -0
- package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/grade-interaction.mjs +19 -18
- package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/grade-support.mjs +32 -5
- package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/grade-workspace.mjs +28 -12
- package/skills/evaluate-behavior/scripts/lib/grade.mjs +101 -0
- package/skills/evaluate-behavior/scripts/lib/hooks.mjs +98 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/confinement.mjs +196 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/constraints.mjs +111 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/index.mjs +48 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/isolation.mjs +74 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/launch.mjs +123 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/model-call.mjs +73 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/recorder.mjs +178 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/session.mjs +494 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/skills.mjs +81 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/index.mjs +31 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/names.mjs +3 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/pi/confinement.mjs +173 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/pi/host.mjs +148 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/pi/index.mjs +70 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/pi/isolation.mjs +81 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/pi/model-call.mjs +79 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/pi/process.mjs +241 -0
- package/skills/{evaluate-skill/scripts/lib → evaluate-behavior/scripts/lib/hosts/pi}/rpc.mjs +144 -26
- package/skills/evaluate-behavior/scripts/lib/identity.mjs +82 -0
- package/skills/evaluate-behavior/scripts/lib/judge.mjs +688 -0
- package/skills/evaluate-behavior/scripts/lib/metrics.mjs +332 -0
- package/skills/evaluate-behavior/scripts/lib/model-call.mjs +17 -0
- package/skills/evaluate-behavior/scripts/lib/participants.mjs +616 -0
- package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/process.mjs +22 -0
- package/skills/evaluate-behavior/scripts/lib/queue.mjs +54 -0
- package/skills/evaluate-behavior/scripts/lib/reference.mjs +343 -0
- package/skills/evaluate-behavior/scripts/lib/reuse.mjs +277 -0
- package/skills/evaluate-behavior/scripts/lib/review.mjs +933 -0
- package/skills/evaluate-behavior/scripts/lib/runner.mjs +732 -0
- package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/sandbox.mjs +121 -14
- package/skills/evaluate-behavior/scripts/lib/scan-shell.mjs +457 -0
- package/skills/evaluate-behavior/scripts/lib/scan.mjs +479 -0
- package/skills/evaluate-behavior/scripts/lib/seal.mjs +91 -0
- package/skills/evaluate-behavior/scripts/lib/shell.mjs +560 -0
- package/skills/evaluate-behavior/scripts/lib/source-override.mjs +64 -0
- package/skills/evaluate-behavior/scripts/lib/subject-file.mjs +88 -0
- package/skills/{evaluate-skill/scripts/lib/pi.mjs → evaluate-behavior/scripts/lib/subject.mjs} +239 -262
- package/skills/evaluate-behavior/scripts/lib/task-copy.mjs +124 -0
- package/skills/evaluate-behavior/scripts/lib/trials.mjs +524 -0
- package/skills/evaluate-behavior/scripts/lib/validity.mjs +68 -0
- package/skills/evaluate-behavior/scripts/lib/view-html.mjs +454 -0
- package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/view.mjs +218 -11
- package/skills/evaluate-behavior/scripts/pi-command-tool.mjs +159 -0
- package/skills/evaluate-behavior/scripts/pi-container-route.mjs +23 -0
- package/skills/evaluate-behavior/scripts/pi-guard.mjs +77 -0
- package/skills/evaluate-behavior/scripts/pi-login-refresh.mjs +31 -0
- package/skills/evaluate-behavior/scripts/skill-eval.mjs +586 -0
- package/skills/execute-work/SKILL.md +96 -72
- package/skills/execute-work/references/code-practices.md +27 -49
- package/skills/execute-work/references/design.md +271 -0
- package/skills/execute-work/references/diagnose.md +117 -0
- package/skills/execute-work/references/migration.md +108 -0
- package/skills/execute-work/references/simplify.md +65 -0
- package/skills/execute-work/scripts/self-review.mjs +422 -0
- package/skills/execute-work/scripts/touch-map.mjs +290 -0
- package/skills/review-work/SKILL.md +94 -64
- package/skills/review-work/references/adjudication.md +94 -0
- package/skills/review-work/references/artifact.md +198 -0
- package/skills/review-work/references/evals-and-judges.md +66 -0
- package/skills/review-work/references/work.md +177 -0
- package/skills/review-work/scripts/check-brief.mjs +128 -0
- package/skills/review-work/scripts/review-packet.mjs +233 -0
- package/skills/setup-freeflow/SKILL.md +69 -16
- package/skills/track-work/SKILL.md +135 -151
- package/skills/track-work/references/handoff.md +190 -0
- package/skills/track-work/references/projects.md +51 -0
- package/skills/track-work/references/working-record-format.md +201 -344
- package/skills/track-work/scripts/lib/document.mjs +79 -19
- package/skills/track-work/scripts/lib/format.mjs +18 -1
- package/skills/track-work/scripts/lib/recover.mjs +254 -0
- package/skills/track-work/scripts/lib/registry.mjs +656 -0
- package/skills/track-work/scripts/lib/search.mjs +40 -0
- package/skills/track-work/scripts/lib/store.mjs +53 -21
- package/skills/track-work/scripts/lib/transitions.mjs +79 -4
- package/skills/track-work/scripts/lib/views.mjs +50 -1
- package/skills/track-work/scripts/working-record.mjs +263 -22
- package/skills/verify-work/SKILL.md +64 -47
- package/skills/verify-work/references/feature-map.md +120 -0
- package/skills/verify-work/references/runtime-evidence.md +203 -0
- package/skills/verify-work/references/test-design.md +27 -6
- package/skills/verify-work/scripts/test-changes.mjs +246 -0
- package/skills/write-docs/SKILL.md +171 -0
- package/skills/write-docs/references/documentation-shapes.md +184 -0
- package/skills/write-docs/references/spec-and-plan-shapes.md +308 -0
- package/skills/write-docs/scripts/artifact-check.mjs +148 -0
- package/skills/write-docs/scripts/docs-check.mjs +229 -0
- package/skills/write-docs/scripts/plan-lint.mjs +123 -0
- package/skills/write-skill/SKILL.md +165 -165
- package/skills/write-skill/references/activation-and-wording.md +143 -0
- package/skills/write-skill/references/placement-and-dependencies.md +154 -0
- package/skills/write-skill/scripts/lib/compare.mjs +701 -0
- package/skills/write-skill/scripts/lib/similarity.mjs +56 -0
- package/skills/write-skill/scripts/lib/skill-author.mjs +197 -3
- package/skills/write-skill/scripts/skill-author.mjs +67 -8
- 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/projection.js +0 -330
- package/pi-extension/dist/cognitive-routing-v2/runtime.js +0 -2181
- package/pi-extension/dist/cognitive-routing-v2/tools.js +0 -60
- 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/settings/settings-ui.js +0 -1360
- 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/plugin.json +0 -33
- package/runtime/prompts/context-virtualization.md +0 -3
- package/runtime/prompts/conversation-history.md +0 -3
- package/skills/action-selection/SKILL.md +0 -100
- package/skills/action-selection/references/trajectory-stalls.md +0 -71
- package/skills/bypass/SKILL.md +0 -153
- package/skills/commit-work/SKILL.md +0 -145
- package/skills/commit-work/references/git-state-edges.md +0 -94
- package/skills/commit-work/references/simple-push.md +0 -92
- package/skills/commit-work/references/staging-decisions.md +0 -106
- package/skills/decision-gate/SKILL.md +0 -106
- package/skills/design-for-depth/SKILL.md +0 -117
- package/skills/design-for-depth/references/design-pressure-signals.md +0 -182
- package/skills/design-for-depth/references/implementation-decisions.md +0 -76
- package/skills/design-for-depth/references/interface-design-loop.md +0 -136
- package/skills/design-for-depth/references/module-and-dependency-design.md +0 -147
- package/skills/design-for-depth/references/software-design-philosophy.md +0 -202
- package/skills/design-for-depth/references/state-and-failure-boundaries.md +0 -136
- package/skills/diagnose-failure/SKILL.md +0 -125
- package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +0 -48
- package/skills/diagnose-failure/references/flaky-and-performance-diagnosis.md +0 -51
- package/skills/discuss/SKILL.md +0 -116
- package/skills/evaluate-skill/SKILL.md +0 -219
- package/skills/evaluate-skill/references/evaluation-definition-schema.md +0 -385
- package/skills/evaluate-skill/references/evaluation-design.md +0 -112
- package/skills/evaluate-skill/references/execution-and-evidence.md +0 -90
- package/skills/evaluate-skill/references/review-and-revision.md +0 -92
- package/skills/evaluate-skill/scripts/lib/definitions.mjs +0 -407
- package/skills/evaluate-skill/scripts/lib/grade-comparison.mjs +0 -60
- package/skills/evaluate-skill/scripts/lib/grade.mjs +0 -64
- package/skills/evaluate-skill/scripts/lib/runner.mjs +0 -368
- package/skills/evaluate-skill/scripts/pi-guard.mjs +0 -164
- package/skills/evaluate-skill/scripts/skill-eval.mjs +0 -102
- package/skills/execute-work/references/execute-work-edges.md +0 -68
- package/skills/finish-branch/SKILL.md +0 -117
- package/skills/finish-branch/references/integration-options.md +0 -80
- package/skills/handoff/SKILL.md +0 -82
- package/skills/handoff/references/handoff-templates.md +0 -150
- package/skills/launch-work/SKILL.md +0 -116
- package/skills/launch-work/references/launch-readiness.md +0 -85
- package/skills/migration-work/SKILL.md +0 -115
- package/skills/migration-work/references/migration-lifecycle.md +0 -82
- package/skills/release-work/SKILL.md +0 -118
- package/skills/release-work/references/release-evidence.md +0 -78
- package/skills/review-artifact/SKILL.md +0 -142
- package/skills/review-artifact/references/adjudicate-artifact-review.md +0 -101
- package/skills/review-artifact/references/independent-artifact-reviewer-contract.md +0 -159
- package/skills/review-work/references/adjudicate-work-review.md +0 -97
- package/skills/review-work/references/independent-work-reviewer-contract.md +0 -139
- package/skills/review-work/references/security-risk-lens.md +0 -69
- package/skills/setup-freeflow/references/activation-contract.md +0 -109
- package/skills/setup-freeflow/references/host-setup.md +0 -67
- package/skills/simplify-code/SKILL.md +0 -85
- package/skills/simplify-code/references/simplification-patterns.md +0 -70
- package/skills/verify-work/references/browser-runtime-evidence.md +0 -55
- package/skills/verify-work/references/integration-evidence.md +0 -47
- package/skills/verify-work/references/performance-evidence.md +0 -57
- package/skills/workflow/SKILL.md +0 -127
- package/skills/workflow/references/domain-skill-composition.md +0 -43
- package/skills/write-plan/SKILL.md +0 -92
- package/skills/write-plan/references/plan-shapes.md +0 -156
- package/skills/write-skill/references/activation-boundaries.md +0 -104
- package/skills/write-skill/references/agent-first-instructions.md +0 -121
- package/skills/write-skill/references/progressive-disclosure.md +0 -127
- package/skills/write-skill/references/skill-dependency-graphs.md +0 -190
- package/skills/write-spec/SKILL.md +0 -92
- package/skills/write-spec/references/artifact-standards.md +0 -86
- package/skills/write-spec/references/decision-records.md +0 -78
- package/skills/write-spec/references/spec-shapes.md +0 -143
- /package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/budget.js +0 -0
- /package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/evidence.mjs +0 -0
- /package/skills/{evaluate-skill → evaluate-behavior}/scripts/pi-observer.mjs +0 -0
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { resolveCognitiveRoutingState, supportsCognitiveRoutingModelRegistry } from "../cognitive-routing/config.js";
|
|
4
|
+
import { sessionStage, stagedFor } from "./staging.js";
|
|
5
|
+
import { resolveToolExecutionConfig } from "../tool-execution/config.js";
|
|
6
|
+
import { resolveCompactionConfig } from "../compaction/config.js";
|
|
7
|
+
import { AUTO_APPROVE_GROUPS, resolveTaskMemoryConfig } from "../task-memory/config.js";
|
|
8
|
+
import { INBOUND_POLICIES, resolveMessagingConfig } from "../messaging/config.js";
|
|
9
|
+
import {
|
|
10
|
+
REVIEW_MODES,
|
|
11
|
+
RUNTIME_PROMPTS,
|
|
12
|
+
SWITCHABLE_SKILLS,
|
|
13
|
+
resolveReviewSettings,
|
|
14
|
+
sectionEnabled,
|
|
15
|
+
selectedRuntimePrompts,
|
|
16
|
+
skillsOff,
|
|
17
|
+
validateLocalConfig,
|
|
18
|
+
validateRepositoryConfig,
|
|
19
|
+
} from "./config-validation.js";
|
|
20
|
+
import { cachedBranch } from "./branch.js";
|
|
21
|
+
/**
|
|
22
|
+
* Freeflow configuration: the repository and personal config files, their layering, per-session core overrides, and
|
|
23
|
+
* the resulting capability state, with optional capabilities disabled inside subagents.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Settings a session may override, as dotted config paths: every setting the settings screen shows, so the Session
|
|
27
|
+
* scope has what the file scopes have. "enabled" is Freeflow's master switch; the others are applied as a layer above
|
|
28
|
+
* the personal config. Most are switches; SESSION_CHOICES lists the ones that hold a value.
|
|
29
|
+
*/
|
|
30
|
+
export const SESSION_OVERRIDE_KEYS = [
|
|
31
|
+
"enabled",
|
|
32
|
+
"cognitiveRouting.enabled",
|
|
33
|
+
"cognitiveRouting.projection",
|
|
34
|
+
"toolExecution.enabled",
|
|
35
|
+
"toolExecution.guidanceNotices",
|
|
36
|
+
"compaction.enabled",
|
|
37
|
+
"compaction.carry",
|
|
38
|
+
"taskMemory.enabled",
|
|
39
|
+
...Object.keys(AUTO_APPROVE_GROUPS).map((group) => `taskMemory.autoApprove.${group}`),
|
|
40
|
+
"messaging.enabled",
|
|
41
|
+
"messaging.inbound",
|
|
42
|
+
"review.mode",
|
|
43
|
+
"review.model",
|
|
44
|
+
"prompts.enabled",
|
|
45
|
+
...RUNTIME_PROMPTS.map((prompt) => `prompts.${prompt}`),
|
|
46
|
+
"skills.enabled",
|
|
47
|
+
...SWITCHABLE_SKILLS.map((skill) => `skills.${skill}`),
|
|
48
|
+
];
|
|
49
|
+
/** The overrides that hold a value rather than on or off, and the values each accepts (a model is any one-line name). */
|
|
50
|
+
const SESSION_CHOICES = {
|
|
51
|
+
"messaging.inbound": INBOUND_POLICIES,
|
|
52
|
+
"review.mode": REVIEW_MODES,
|
|
53
|
+
"review.model": "name",
|
|
54
|
+
};
|
|
55
|
+
function validSessionValue(key, value) {
|
|
56
|
+
const choice = SESSION_CHOICES[key];
|
|
57
|
+
if (!choice) return typeof value === "boolean";
|
|
58
|
+
if (choice === "name") return typeof value === "string" && value.trim() !== "" && !/[\p{Cc}`]/u.test(value);
|
|
59
|
+
return choice.includes(value);
|
|
60
|
+
}
|
|
61
|
+
const SESSION_OVERRIDES_ENTRY = "freeflow-session-overrides";
|
|
62
|
+
const SESSION_CORE_KEYS = new Set(SESSION_OVERRIDE_KEYS);
|
|
63
|
+
let currentSessionOverrides = {};
|
|
64
|
+
// pi-subagents stamps child system prompts with this tag before binding extensions.
|
|
65
|
+
// Keeping detection at the prompt boundary avoids a process-global child flag.
|
|
66
|
+
const SUBAGENT_AGENT_TAG = /<active_agent\s+name="[^"]+"\s*\/>/;
|
|
67
|
+
const FREEFLOW_SUBAGENT_CAPABILITIES_DISABLED_MARKER = "<!-- freeflow-subagent-capabilities: disabled -->";
|
|
68
|
+
const SUBAGENT_OPTIONAL_CAPABILITIES_MESSAGE = "Optional Freeflow capabilities are disabled for subagents.";
|
|
69
|
+
function isSubagentContext(context) {
|
|
70
|
+
try {
|
|
71
|
+
const systemPrompt = context?.getSystemPrompt?.();
|
|
72
|
+
return (
|
|
73
|
+
typeof systemPrompt === "string" &&
|
|
74
|
+
(SUBAGENT_AGENT_TAG.test(systemPrompt) || systemPrompt.includes(FREEFLOW_SUBAGENT_CAPABILITIES_DISABLED_MARKER))
|
|
75
|
+
);
|
|
76
|
+
} catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function disableSubagentCapability(capability) {
|
|
81
|
+
return {
|
|
82
|
+
...capability,
|
|
83
|
+
enabled: false,
|
|
84
|
+
effective: false,
|
|
85
|
+
blockingReason: { code: "disabled", message: SUBAGENT_OPTIONAL_CAPABILITIES_MESSAGE },
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function isRecord(value) {
|
|
89
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
90
|
+
}
|
|
91
|
+
async function readConfigFileState(path, validate) {
|
|
92
|
+
try {
|
|
93
|
+
const raw = await readFile(path, "utf8");
|
|
94
|
+
const parsed = JSON.parse(raw);
|
|
95
|
+
const validationError = validate(parsed);
|
|
96
|
+
if (validationError) {
|
|
97
|
+
return {
|
|
98
|
+
path,
|
|
99
|
+
exists: true,
|
|
100
|
+
valid: false,
|
|
101
|
+
parsed: {},
|
|
102
|
+
parseError: validationError,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
return { path, exists: true, valid: true, parsed, parseError: null };
|
|
106
|
+
} catch (error) {
|
|
107
|
+
const code = error && typeof error === "object" && "code" in error ? error.code : undefined;
|
|
108
|
+
if (code === "ENOENT") {
|
|
109
|
+
return {
|
|
110
|
+
path,
|
|
111
|
+
exists: false,
|
|
112
|
+
valid: false,
|
|
113
|
+
parsed: {},
|
|
114
|
+
parseError: null,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
118
|
+
return {
|
|
119
|
+
path,
|
|
120
|
+
exists: true,
|
|
121
|
+
valid: false,
|
|
122
|
+
parsed: {},
|
|
123
|
+
parseError: message,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function readFreeflowConfigState(cwd) {
|
|
128
|
+
return readConfigFileState(join(cwd, ".freeflow/config.json"), validateRepositoryConfig);
|
|
129
|
+
}
|
|
130
|
+
function readFreeflowLocalConfigState(cwd) {
|
|
131
|
+
return readConfigFileState(join(cwd, ".freeflow/local.json"), validateLocalConfig);
|
|
132
|
+
}
|
|
133
|
+
export async function readFreeflowConfig(cwd) {
|
|
134
|
+
const state = await readFreeflowConfigState(cwd);
|
|
135
|
+
return state.valid ? state.parsed : {};
|
|
136
|
+
}
|
|
137
|
+
export async function readFreeflowLocalConfig(cwd) {
|
|
138
|
+
const state = await readFreeflowLocalConfigState(cwd);
|
|
139
|
+
return state.valid ? state.parsed : {};
|
|
140
|
+
}
|
|
141
|
+
function resolveLayeredValue(repository, local, key, fallback) {
|
|
142
|
+
if (Object.hasOwn(local, key)) {
|
|
143
|
+
return { value: local[key], source: "local" };
|
|
144
|
+
}
|
|
145
|
+
if (Object.hasOwn(repository, key)) {
|
|
146
|
+
return { value: repository[key], source: "repository" };
|
|
147
|
+
}
|
|
148
|
+
return { value: fallback, source: "builtin" };
|
|
149
|
+
}
|
|
150
|
+
function resolveCoreConfig(repository, local) {
|
|
151
|
+
const enabled = resolveLayeredValue(repository, local, "enabled", true);
|
|
152
|
+
return {
|
|
153
|
+
config: { enabled: enabled.value },
|
|
154
|
+
sources: { enabled: enabled.source },
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function normalizeSessionOverrides(value) {
|
|
158
|
+
if (!isRecord(value)) return {};
|
|
159
|
+
const overrides = {};
|
|
160
|
+
for (const key of SESSION_CORE_KEYS) {
|
|
161
|
+
if (validSessionValue(key, value[key])) {
|
|
162
|
+
overrides[key] = value[key];
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return overrides;
|
|
166
|
+
}
|
|
167
|
+
/** The setting overrides of this session as a config layer, e.g. { compaction: { carry: false } }. */
|
|
168
|
+
function sessionConfigLayer(overrides) {
|
|
169
|
+
const layer = {};
|
|
170
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
171
|
+
const path = key.split(".");
|
|
172
|
+
if (path.length < 2 || value === undefined) continue;
|
|
173
|
+
let at = layer;
|
|
174
|
+
for (const part of path.slice(0, -1)) at = at[part] = isRecord(at[part]) ? at[part] : {};
|
|
175
|
+
at[path.at(-1)] = value;
|
|
176
|
+
}
|
|
177
|
+
return layer;
|
|
178
|
+
}
|
|
179
|
+
/** The personal config with the session layer over it, merged object by object. */
|
|
180
|
+
function withSessionLayer(local, session) {
|
|
181
|
+
const merged = { ...local };
|
|
182
|
+
for (const [key, value] of Object.entries(session))
|
|
183
|
+
merged[key] = isRecord(value) && isRecord(local[key]) ? withSessionLayer(local[key], value) : value;
|
|
184
|
+
return merged;
|
|
185
|
+
}
|
|
186
|
+
function resolveSessionCoreConfig(layers) {
|
|
187
|
+
const configured = layers.coreConfig;
|
|
188
|
+
const sources = layers.sources;
|
|
189
|
+
const enabled = currentSessionOverrides.enabled;
|
|
190
|
+
return {
|
|
191
|
+
config: { enabled: typeof enabled === "boolean" ? enabled : configured.enabled },
|
|
192
|
+
sources: { enabled: typeof enabled === "boolean" ? "session" : sources.enabled },
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
export async function readFreeflowConfigLayers(cwd) {
|
|
196
|
+
const [repository, local] = await Promise.all([readFreeflowConfigState(cwd), readFreeflowLocalConfigState(cwd)]);
|
|
197
|
+
const repositoryConfig = repository.valid ? repository.parsed : {};
|
|
198
|
+
const localConfig = local.valid ? local.parsed : {};
|
|
199
|
+
const core = resolveCoreConfig(repositoryConfig, localConfig);
|
|
200
|
+
const localValid = !local.exists || local.valid;
|
|
201
|
+
const configured = repository.valid && localValid;
|
|
202
|
+
let blockingState = null;
|
|
203
|
+
if (!repository.valid) {
|
|
204
|
+
blockingState = repository;
|
|
205
|
+
} else if (local.exists && !local.valid) {
|
|
206
|
+
blockingState = local;
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
configured,
|
|
210
|
+
repositoryConfigured: repository.valid,
|
|
211
|
+
repository,
|
|
212
|
+
local,
|
|
213
|
+
coreConfig: core.config,
|
|
214
|
+
sources: core.sources,
|
|
215
|
+
blockingConfigPath: blockingState?.path ?? null,
|
|
216
|
+
parseError: blockingState?.parseError ?? null,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
// host is Pi's extension context (or none); Pi's runtime objects are wider than its exported types.
|
|
220
|
+
export async function readCapabilityState(cwd, host = undefined) {
|
|
221
|
+
const layers = await readFreeflowConfigLayers(cwd);
|
|
222
|
+
const effectiveCore = resolveSessionCoreConfig(layers);
|
|
223
|
+
const enabled = layers.configured && effectiveCore.config.enabled;
|
|
224
|
+
const subagentContext = isSubagentContext(host);
|
|
225
|
+
const hostSupportsCognitiveRouting = !subagentContext && supportsCognitiveRoutingModelRegistry(host);
|
|
226
|
+
const sessionLayer = sessionConfigLayer(currentSessionOverrides);
|
|
227
|
+
const configuredCognitiveRouting = await resolveCognitiveRoutingState(
|
|
228
|
+
layers.repository.parsed,
|
|
229
|
+
layers.local.parsed,
|
|
230
|
+
hostSupportsCognitiveRouting ? host : undefined,
|
|
231
|
+
sessionLayer,
|
|
232
|
+
);
|
|
233
|
+
const disabledReason = enabled ? undefined : { code: "disabled", message: "Freeflow is disabled" };
|
|
234
|
+
const cognitiveRouting = enabled
|
|
235
|
+
? configuredCognitiveRouting
|
|
236
|
+
: {
|
|
237
|
+
...configuredCognitiveRouting,
|
|
238
|
+
enabled: false,
|
|
239
|
+
effective: false,
|
|
240
|
+
blockingReason: disabledReason,
|
|
241
|
+
};
|
|
242
|
+
const repositoryConfig = layers.repository.valid ? layers.repository.parsed : {};
|
|
243
|
+
const localConfig = layers.local.valid ? layers.local.parsed : {};
|
|
244
|
+
const sessionLocalConfig = withSessionLayer(localConfig, sessionLayer);
|
|
245
|
+
const toolExecution = resolveToolExecutionConfig(repositoryConfig, sessionLocalConfig, enabled);
|
|
246
|
+
const compaction = resolveCompactionConfig(repositoryConfig, sessionLocalConfig, enabled);
|
|
247
|
+
const taskMemory = resolveTaskMemoryConfig(repositoryConfig, sessionLocalConfig, enabled);
|
|
248
|
+
const messaging = resolveMessagingConfig(repositoryConfig, sessionLocalConfig, enabled);
|
|
249
|
+
// Not capabilities: the user's choices for Review Work, the runtime prompts and the skills, on every host.
|
|
250
|
+
const review = resolveReviewSettings(repositoryConfig, sessionLocalConfig);
|
|
251
|
+
const prompts = selectedRuntimePrompts(repositoryConfig, sessionLocalConfig);
|
|
252
|
+
const skills = {
|
|
253
|
+
enabled: sectionEnabled(repositoryConfig, sessionLocalConfig, "skills"),
|
|
254
|
+
off: skillsOff(repositoryConfig, sessionLocalConfig),
|
|
255
|
+
};
|
|
256
|
+
const promptsEnabled = sectionEnabled(repositoryConfig, sessionLocalConfig, "prompts");
|
|
257
|
+
const capabilityState = {
|
|
258
|
+
configured: layers.configured,
|
|
259
|
+
repositoryConfigured: layers.repositoryConfigured,
|
|
260
|
+
configExists: layers.repository.exists,
|
|
261
|
+
configValid: layers.configured,
|
|
262
|
+
configPath: layers.blockingConfigPath ?? layers.repository.path,
|
|
263
|
+
parseError: layers.parseError,
|
|
264
|
+
localConfigExists: layers.local.exists,
|
|
265
|
+
localConfigValid: !layers.local.exists || layers.local.valid,
|
|
266
|
+
localConfigPath: layers.local.path,
|
|
267
|
+
localConfigParseError: layers.local.parseError,
|
|
268
|
+
configuredCoreConfig: layers.coreConfig,
|
|
269
|
+
configuredSources: layers.sources,
|
|
270
|
+
sessionOverrides: { ...currentSessionOverrides },
|
|
271
|
+
configSources: effectiveCore.sources,
|
|
272
|
+
enabled,
|
|
273
|
+
hostSupportsCognitiveRouting,
|
|
274
|
+
cognitiveRouting,
|
|
275
|
+
toolExecution,
|
|
276
|
+
compaction,
|
|
277
|
+
taskMemory,
|
|
278
|
+
messaging,
|
|
279
|
+
review,
|
|
280
|
+
prompts,
|
|
281
|
+
promptsEnabled,
|
|
282
|
+
skills,
|
|
283
|
+
// ask_user: core, on whenever Freeflow is enabled (Task 002 D-001); off in subagents, which have no user.
|
|
284
|
+
askUser: { enabled, effective: enabled },
|
|
285
|
+
};
|
|
286
|
+
if (!subagentContext) return capabilityState;
|
|
287
|
+
return {
|
|
288
|
+
...capabilityState,
|
|
289
|
+
cognitiveRouting: disableSubagentCapability(capabilityState.cognitiveRouting),
|
|
290
|
+
toolExecution: disableSubagentCapability(capabilityState.toolExecution),
|
|
291
|
+
compaction: disableSubagentCapability(capabilityState.compaction),
|
|
292
|
+
taskMemory: disableSubagentCapability(capabilityState.taskMemory),
|
|
293
|
+
messaging: disableSubagentCapability(capabilityState.messaging),
|
|
294
|
+
askUser: disableSubagentCapability(capabilityState.askUser),
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
function recordedSessionOverrides(sessionManager) {
|
|
298
|
+
let recorded = {};
|
|
299
|
+
for (const entry of sessionManager?.getBranch ? cachedBranch(sessionManager) : (sessionManager?.getEntries?.() ?? []))
|
|
300
|
+
if (entry.type === "custom" && entry.customType === SESSION_OVERRIDES_ENTRY)
|
|
301
|
+
recorded = normalizeSessionOverrides(entry.data?.overrides);
|
|
302
|
+
return recorded;
|
|
303
|
+
}
|
|
304
|
+
export function restoreSessionOverrides(ctx) {
|
|
305
|
+
const staged = stagedFor(ctx.sessionManager)?.overrides;
|
|
306
|
+
currentSessionOverrides = normalizeSessionOverrides(staged ?? recordedSessionOverrides(ctx.sessionManager));
|
|
307
|
+
}
|
|
308
|
+
/** Session overrides change nothing until the next prompt, so they are staged and written then. */
|
|
309
|
+
function recordSessionOverrides(ctx, pi) {
|
|
310
|
+
const stage = sessionStage(ctx?.sessionManager);
|
|
311
|
+
if (stage) stage.overrides = { ...currentSessionOverrides };
|
|
312
|
+
else pi?.appendEntry?.(SESSION_OVERRIDES_ENTRY, { overrides: { ...currentSessionOverrides } });
|
|
313
|
+
}
|
|
314
|
+
/** Write staged session overrides at the start of a prompt, unless they equal what the session recorded. */
|
|
315
|
+
export function writeStagedSessionOverrides(overrides, pi, sessionManager) {
|
|
316
|
+
if (!overrides) return;
|
|
317
|
+
const next = normalizeSessionOverrides(overrides);
|
|
318
|
+
if (JSON.stringify(next) === JSON.stringify(recordedSessionOverrides(sessionManager))) return;
|
|
319
|
+
pi?.appendEntry?.(SESSION_OVERRIDES_ENTRY, { overrides: { ...next } });
|
|
320
|
+
}
|
|
321
|
+
export async function setSessionCoreOverride(key, value, ctx, pi) {
|
|
322
|
+
if (!SESSION_CORE_KEYS.has(key)) {
|
|
323
|
+
throw new Error(`Invalid Freeflow session override: ${String(key)}`);
|
|
324
|
+
}
|
|
325
|
+
if (value !== null && !validSessionValue(key, value)) {
|
|
326
|
+
throw new Error(`Invalid Freeflow session override value for ${key}: ${String(value)}`);
|
|
327
|
+
}
|
|
328
|
+
const hasOverride = Object.hasOwn(currentSessionOverrides, key);
|
|
329
|
+
if ((value === null && !hasOverride) || (value !== null && hasOverride && currentSessionOverrides[key] === value)) {
|
|
330
|
+
return { changed: false, sessionOverrides: { ...currentSessionOverrides } };
|
|
331
|
+
}
|
|
332
|
+
const next = { ...currentSessionOverrides };
|
|
333
|
+
if (value === null) {
|
|
334
|
+
delete next[key];
|
|
335
|
+
} else {
|
|
336
|
+
next[key] = value;
|
|
337
|
+
}
|
|
338
|
+
currentSessionOverrides = next;
|
|
339
|
+
recordSessionOverrides(ctx, pi);
|
|
340
|
+
return {
|
|
341
|
+
changed: true,
|
|
342
|
+
// A session override can switch Freeflow, a capability, a skill or a prompt, which changes tools and guidance: reload.
|
|
343
|
+
reloadRequired: true,
|
|
344
|
+
sessionOverrides: { ...currentSessionOverrides },
|
|
345
|
+
capabilityState: await readCapabilityState(ctx.cwd, ctx),
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
export async function resetSessionOverrides(ctx, pi) {
|
|
349
|
+
const hadCoreOverrides = Object.keys(currentSessionOverrides).length > 0;
|
|
350
|
+
const reloadRequired = hadCoreOverrides;
|
|
351
|
+
if (hadCoreOverrides) {
|
|
352
|
+
currentSessionOverrides = {};
|
|
353
|
+
recordSessionOverrides(ctx, pi);
|
|
354
|
+
}
|
|
355
|
+
return {
|
|
356
|
+
changed: hadCoreOverrides,
|
|
357
|
+
reloadRequired,
|
|
358
|
+
sessionOverrides: { ...currentSessionOverrides },
|
|
359
|
+
capabilityState: await readCapabilityState(ctx.cwd, ctx),
|
|
360
|
+
};
|
|
361
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The inbound channel: content that reaches the model from outside the turn (a background command's exit, and later
|
|
3
|
+
* monitor events and messages from other sessions). One delivery rule for all of them:
|
|
4
|
+
* - while the agent works, a notice is steered in after the current tool batch;
|
|
5
|
+
* - while it is idle, a notice that should wake it starts a turn as Freeflow's own message (host/section-keeper.ts
|
|
6
|
+
* keeps the last prompt's system sections on such a run);
|
|
7
|
+
* - a notice that should not wake the agent waits for the next turn, and so does every notice to a session that has
|
|
8
|
+
* never had a prompt: Pi before 1.2.0 builds the system prompt only for prompts, so a turn there would have none;
|
|
9
|
+
* Notices from one source within a batching window join one notice, and a source that sends faster than its limit is
|
|
10
|
+
* stopped with one notice saying so. Pi sends every custom message to the model in the user role, so a notice marks
|
|
11
|
+
* its source itself: a `<freeflow-notice>` tag for Freeflow's own (freeflowNotice()), a `<message>` tag for a message
|
|
12
|
+
* from another session. Notices are appended to the conversation; nothing earlier changes.
|
|
13
|
+
*/
|
|
14
|
+
import { Text } from "@earendil-works/pi-tui";
|
|
15
|
+
/** The marker Freeflow's notices carried before the tag; saved sessions still contain it, so rows still read it. */
|
|
16
|
+
export const LEGACY_NOTICE_PREFIX = "[Freeflow notice, not from the user]";
|
|
17
|
+
/**
|
|
18
|
+
* Text that came from outside Freeflow (a command's output, another session's message) with every `<freeflow-notice`
|
|
19
|
+
* tag in it broken, so it can neither close a notice nor open one that looks like Freeflow's.
|
|
20
|
+
*/
|
|
21
|
+
export function withoutNoticeTags(text) {
|
|
22
|
+
return text.replace(/<(\/?)(freeflow-notice)/gi, "<\\$1$2");
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Freeflow's own notice as the model reads it. Pi gives the model every custom message in the user role; the tag, which
|
|
26
|
+
* Freeflow's guidance explains, says the text is Freeflow's and not the user's. The text cannot open or close the tag.
|
|
27
|
+
*/
|
|
28
|
+
export function freeflowNotice(type, text) {
|
|
29
|
+
return `<freeflow-notice type="${type}">\n${withoutNoticeTags(text)}\n</freeflow-notice>`;
|
|
30
|
+
}
|
|
31
|
+
/** A notice's text without its tag, or without the old prefix. */
|
|
32
|
+
export function noticeBody(content) {
|
|
33
|
+
return content
|
|
34
|
+
.replace(/^\s*<freeflow-notice[^>]*>\n?/, "")
|
|
35
|
+
.replace(/\n?<\/freeflow-notice>\s*$/, "")
|
|
36
|
+
.split(LEGACY_NOTICE_PREFIX)
|
|
37
|
+
.join("")
|
|
38
|
+
.trim();
|
|
39
|
+
}
|
|
40
|
+
export class Inbound {
|
|
41
|
+
host;
|
|
42
|
+
pending = new Map();
|
|
43
|
+
sent = new Map();
|
|
44
|
+
limited = new Set();
|
|
45
|
+
constructor(host) {
|
|
46
|
+
this.host = host;
|
|
47
|
+
}
|
|
48
|
+
/** Deliver one notice now, or, with a batch key, after its window together with the others for that key. */
|
|
49
|
+
deliver(notice, batch) {
|
|
50
|
+
if (!batch) return void this.send(notice);
|
|
51
|
+
if (this.limited.has(batch.key)) return;
|
|
52
|
+
const pending = this.pending.get(batch.key);
|
|
53
|
+
if (pending) {
|
|
54
|
+
pending.notices.push(notice);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const timer = setTimeout(() => this.flush(batch.key), batch.windowMs ?? 200);
|
|
58
|
+
this.pending.set(batch.key, { notices: [notice], timer, batch });
|
|
59
|
+
}
|
|
60
|
+
/** Deliver what a key has pending now, before its window ends: its source finished and has more to say after it. */
|
|
61
|
+
flush(key) {
|
|
62
|
+
const pending = this.pending.get(key);
|
|
63
|
+
if (!pending) return;
|
|
64
|
+
clearTimeout(pending.timer);
|
|
65
|
+
this.pending.delete(key);
|
|
66
|
+
this.sendBatch(pending);
|
|
67
|
+
}
|
|
68
|
+
/** Forget a source: drop what it has pending and its limit state. */
|
|
69
|
+
close(key) {
|
|
70
|
+
const pending = this.pending.get(key);
|
|
71
|
+
if (pending) clearTimeout(pending.timer);
|
|
72
|
+
this.pending.delete(key);
|
|
73
|
+
this.sent.delete(key);
|
|
74
|
+
this.limited.delete(key);
|
|
75
|
+
}
|
|
76
|
+
/** Drop everything pending, at session end. */
|
|
77
|
+
reset() {
|
|
78
|
+
for (const { timer } of this.pending.values()) clearTimeout(timer);
|
|
79
|
+
this.pending.clear();
|
|
80
|
+
this.sent.clear();
|
|
81
|
+
this.limited.clear();
|
|
82
|
+
}
|
|
83
|
+
sendBatch({ notices, batch }) {
|
|
84
|
+
const now = Date.now();
|
|
85
|
+
const recent = (this.sent.get(batch.key) ?? []).filter((at) => now - at < 60_000);
|
|
86
|
+
if (batch.perMinute !== undefined && recent.length >= batch.perMinute) {
|
|
87
|
+
this.limited.add(batch.key);
|
|
88
|
+
batch.onLimit?.();
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
this.sent.set(batch.key, [...recent, now]);
|
|
92
|
+
const [first] = notices;
|
|
93
|
+
// send() never rejects: a failed delivery is dropped there.
|
|
94
|
+
void this.send({
|
|
95
|
+
customType: first.customType,
|
|
96
|
+
content: batch.join ? batch.join(notices) : notices.map((notice) => notice.content).join("\n"),
|
|
97
|
+
details: { ...first.details, batched: notices.length },
|
|
98
|
+
wake: notices.some((notice) => notice.wake),
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
send(notice) {
|
|
102
|
+
const message = { customType: notice.customType, content: notice.content, display: true, details: notice.details };
|
|
103
|
+
const wake = notice.wake && this.host.canWake?.() !== false;
|
|
104
|
+
// A send that throws at once is caught like one that rejects later: a notice never breaks its source.
|
|
105
|
+
const asMessage = () =>
|
|
106
|
+
new Promise((resolve) =>
|
|
107
|
+
resolve(
|
|
108
|
+
this.host.sendMessage(message, wake ? { triggerTurn: true, deliverAs: "steer" } : { triggerTurn: false }),
|
|
109
|
+
),
|
|
110
|
+
).then(
|
|
111
|
+
() => {},
|
|
112
|
+
() => {},
|
|
113
|
+
);
|
|
114
|
+
// Always Freeflow's own message, never the user's: the run it starts keeps Freeflow's system sections
|
|
115
|
+
// (host/section-keeper.ts); Pi #10267 once made idle notices go out as user messages.
|
|
116
|
+
return asMessage();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* A notice in the transcript: one line naming its source and what happened, expanding to the whole notice. The model
|
|
121
|
+
* receives the same text either way.
|
|
122
|
+
*/
|
|
123
|
+
export function renderNotice(label) {
|
|
124
|
+
return (message, options, theme) => {
|
|
125
|
+
const content = typeof message?.content === "string" ? message.content : "";
|
|
126
|
+
const body = noticeBody(content);
|
|
127
|
+
const paint = (color, text) => (theme?.fg ? theme.fg(color, text) : text);
|
|
128
|
+
// Blank lines separate a notice's parts; the row skips them.
|
|
129
|
+
const lines = body.split("\n").filter((line) => line.trim() !== "");
|
|
130
|
+
// A heading that ends in ":" introduces lines (a monitor's events): the row shows the first of them too.
|
|
131
|
+
const introduces = (lines[0] ?? "").endsWith(":") && lines.length > 1;
|
|
132
|
+
const more = introduces && lines.length > 2 ? ` (+${lines.length - 2} more)` : "";
|
|
133
|
+
// A long first line (a compaction notice is one paragraph) shows its first sentence; the rest is on expand.
|
|
134
|
+
const opening = lines[0] ?? "";
|
|
135
|
+
const sentence = opening.length > 140 ? opening.slice(0, opening.search(/\.(\s|$)/) + 1) || opening : opening;
|
|
136
|
+
const first = introduces ? `${lines[0]} ${lines[1]}${more}` : sentence;
|
|
137
|
+
// "Background command bg1 completed" reads as label plus the rest; anything else gets the label in front.
|
|
138
|
+
const rest = first.toLowerCase().startsWith(label.toLowerCase()) ? first.slice(label.length).trimStart() : first;
|
|
139
|
+
const head = `${paint("customMessageLabel", label)} ${paint("muted", rest)}`;
|
|
140
|
+
return new Text(options?.expanded && (body.includes("\n") || first !== body) ? `${head}\n\n${body}` : head, 0, 0);
|
|
141
|
+
};
|
|
142
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
const projectedEntryIds = new WeakMap();
|
|
2
|
+
// Messages that came from an aligned projection; anything else in the request was generated after it.
|
|
3
|
+
const projectedMessages = new WeakSet();
|
|
4
|
+
// Projected messages whose body a host edit replaced; only these still need content association.
|
|
5
|
+
const editedMessages = new WeakSet();
|
|
6
|
+
/** The unedited native entry behind a request message, when Pi's projection established it. */
|
|
7
|
+
export function projectedEntryId(message) {
|
|
8
|
+
return message && typeof message === "object" ? projectedEntryIds.get(message) : undefined;
|
|
9
|
+
}
|
|
10
|
+
export function tagProjectedMessages(messages, projection, branch) {
|
|
11
|
+
if (!projection) return;
|
|
12
|
+
let count = 0;
|
|
13
|
+
for (const entry of projection.entries) count += entry.messages.length;
|
|
14
|
+
if (count !== messages.length) return;
|
|
15
|
+
let index = 0;
|
|
16
|
+
for (const entry of projection.entries)
|
|
17
|
+
for (const projected of entry.messages) if (projected?.role !== messages[index++]?.role) return;
|
|
18
|
+
// Host edits change the visible body, so their targets keep exact content association.
|
|
19
|
+
const edited = new Set();
|
|
20
|
+
for (const entry of branch) if (entry.type === "context_edit" && entry.targetId) edited.add(entry.targetId);
|
|
21
|
+
index = 0;
|
|
22
|
+
for (const entry of projection.entries) {
|
|
23
|
+
const source = entry.sourceEntry;
|
|
24
|
+
for (const _projected of entry.messages) {
|
|
25
|
+
const message = messages[index++];
|
|
26
|
+
if (!message || typeof message !== "object") continue;
|
|
27
|
+
projectedMessages.add(message);
|
|
28
|
+
if (edited.has(source.id)) editedMessages.add(message);
|
|
29
|
+
if (
|
|
30
|
+
entry.messages.length === 1 &&
|
|
31
|
+
(source.type === "message" || source.type === "custom_message") &&
|
|
32
|
+
!edited.has(source.id)
|
|
33
|
+
)
|
|
34
|
+
projectedEntryIds.set(message, source.id);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// Messages Freeflow renders from an entry (routing's views): the entry and the rendering variant, so a consumer can
|
|
39
|
+
// fingerprint each rendering once. Kept apart from projectedEntryIds, which names only unedited native messages.
|
|
40
|
+
const derivedIdentities = new WeakMap();
|
|
41
|
+
/** Mark a message rendered from a native entry with a stable identity: `<entry id>#<variant>` or another unique key. */
|
|
42
|
+
export function tagDerived(message, identity) {
|
|
43
|
+
derivedIdentities.set(message, identity);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* A stable identity for a request message whose content never changes under that identity: the unedited native entry
|
|
47
|
+
* behind it, or the identity of a rendering. Undefined for generated or host-edited messages.
|
|
48
|
+
*/
|
|
49
|
+
export function occurrenceIdentity(message) {
|
|
50
|
+
if (!message || typeof message !== "object") return undefined;
|
|
51
|
+
return derivedIdentities.get(message) ?? projectedEntryIds.get(message);
|
|
52
|
+
}
|
|
53
|
+
/** Whether a request message came from an aligned Pi projection. */
|
|
54
|
+
export function isProjected(message) {
|
|
55
|
+
return !!message && typeof message === "object" && projectedMessages.has(message);
|
|
56
|
+
}
|
|
57
|
+
/** Whether a host edit replaced the body of a projected request message. */
|
|
58
|
+
export function isHostEdited(message) {
|
|
59
|
+
return !!message && typeof message === "object" && editedMessages.has(message);
|
|
60
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
/**
|
|
3
|
+
* Freeflow's prompt files and the fixed system section built from them. The section never changes with feature state
|
|
4
|
+
* so it stays cached; the Runtime State tells the model what is active.
|
|
5
|
+
*/
|
|
6
|
+
let runtimeContextCache = null;
|
|
7
|
+
export function isPromptAvailable(value) {
|
|
8
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
9
|
+
}
|
|
10
|
+
export function hasUsableMandatoryPrompts(freeflowContext) {
|
|
11
|
+
return (
|
|
12
|
+
isPromptAvailable(freeflowContext?.corePrompt) && isPromptAvailable(freeflowContext?.interactionContractPrompt)
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
async function readPromptFile(url) {
|
|
16
|
+
try {
|
|
17
|
+
const prompt = await readFile(url, "utf8");
|
|
18
|
+
return isPromptAvailable(prompt) ? prompt.trim() : null;
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async function loadRuntimeContext(capabilityState = undefined) {
|
|
24
|
+
const freeflowEnabled = capabilityState?.enabled === true;
|
|
25
|
+
const cognitiveRoutingEnabled = capabilityState?.cognitiveRouting?.effective === true;
|
|
26
|
+
const [corePrompt, interactionContractPrompt, workingMethodPrompt, cognitiveRoutingPrompt, toolExecutionPrompt] =
|
|
27
|
+
await Promise.all([
|
|
28
|
+
freeflowEnabled
|
|
29
|
+
? readPromptFile(new URL("../../../runtime/prompts/core.md", import.meta.url))
|
|
30
|
+
: Promise.resolve(null),
|
|
31
|
+
freeflowEnabled
|
|
32
|
+
? readPromptFile(new URL("../../../runtime/prompts/interaction-contract.md", import.meta.url))
|
|
33
|
+
: Promise.resolve(null),
|
|
34
|
+
freeflowEnabled
|
|
35
|
+
? readPromptFile(new URL("../../../runtime/prompts/working-method.md", import.meta.url))
|
|
36
|
+
: Promise.resolve(null),
|
|
37
|
+
cognitiveRoutingEnabled
|
|
38
|
+
? readPromptFile(new URL("../../../runtime/prompts/cognitive-routing.md", import.meta.url))
|
|
39
|
+
: Promise.resolve(null),
|
|
40
|
+
capabilityState?.toolExecution?.effective === true
|
|
41
|
+
? readPromptFile(new URL("../../../runtime/prompts/tool-execution.md", import.meta.url))
|
|
42
|
+
: Promise.resolve(null),
|
|
43
|
+
]);
|
|
44
|
+
return { corePrompt, interactionContractPrompt, workingMethodPrompt, cognitiveRoutingPrompt, toolExecutionPrompt };
|
|
45
|
+
}
|
|
46
|
+
function runtimeContextCacheSatisfies(capabilityState) {
|
|
47
|
+
if (!runtimeContextCache) return false;
|
|
48
|
+
const expected = {
|
|
49
|
+
corePrompt: capabilityState?.enabled === true,
|
|
50
|
+
interactionContractPrompt: capabilityState?.enabled === true,
|
|
51
|
+
cognitiveRoutingPrompt: capabilityState?.cognitiveRouting?.effective === true,
|
|
52
|
+
};
|
|
53
|
+
return Object.entries(expected).every(
|
|
54
|
+
([key, required]) => !required || isPromptAvailable(runtimeContextCache?.[key]),
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
export async function refreshRuntimeContext(capabilityState = undefined) {
|
|
58
|
+
runtimeContextCache = await loadRuntimeContext(capabilityState);
|
|
59
|
+
return runtimeContextCache;
|
|
60
|
+
}
|
|
61
|
+
export async function getRuntimeContext(capabilityState = undefined) {
|
|
62
|
+
if (runtimeContextCache && runtimeContextCacheSatisfies(capabilityState)) {
|
|
63
|
+
return runtimeContextCache;
|
|
64
|
+
}
|
|
65
|
+
return refreshRuntimeContext(capabilityState);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Freeflow's system section for the current configuration: sent only while Freeflow is enabled, with the routing cue
|
|
69
|
+
* only while Cognitive Routing is effective (Task 003 D-002). It changes only when a setting changes, which costs one
|
|
70
|
+
* prompt-cache write (D-001); between such changes it is identical from request to request.
|
|
71
|
+
*/
|
|
72
|
+
export function stableRuntimeContext(context, options = {}) {
|
|
73
|
+
// A prompt the user turned off (`prompts` in the config) is left out; a missing file is a different matter, which
|
|
74
|
+
// hasUsableMandatoryPrompts reports.
|
|
75
|
+
const on = (prompt) => options.prompts?.[prompt] !== false;
|
|
76
|
+
const sections = [
|
|
77
|
+
...(on("core") ? [["Freeflow core", context?.corePrompt]] : []),
|
|
78
|
+
...(on("interactionContract") ? [["Freeflow core", context?.interactionContractPrompt]] : []),
|
|
79
|
+
...(on("workingMethod") ? [["Working Method", context?.workingMethodPrompt]] : []),
|
|
80
|
+
...(options.routing ? [["Cognitive Routing", context?.cognitiveRoutingPrompt]] : []),
|
|
81
|
+
];
|
|
82
|
+
return [
|
|
83
|
+
"Text inside `<freeflow-notice>` tags comes from Freeflow, not the user: it reports facts and harness steps and is not the user's instruction, except the user's own earlier messages it quotes in `<user-message>` tags.",
|
|
84
|
+
...(options.routing
|
|
85
|
+
? [
|
|
86
|
+
"For Cognitive Routing, follow the latest control/profile/responsibility snapshots on this branch. Earlier snapshots and notices are historical; they grant no current permission. Tool permissions are checked by the runtime.",
|
|
87
|
+
]
|
|
88
|
+
: []),
|
|
89
|
+
...sections
|
|
90
|
+
.filter(([, text]) => isPromptAvailable(text))
|
|
91
|
+
.map(([feature, text]) => `## ${feature}\n\n${text.trim()}`),
|
|
92
|
+
].join("\n\n");
|
|
93
|
+
}
|