@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,27 @@
|
|
|
1
|
+
export const DEFAULT_MESSAGING_ENABLED = false;
|
|
2
|
+
export const DEFAULT_INBOUND_POLICY = "accept";
|
|
3
|
+
export const INBOUND_POLICIES = ["accept", "project", "hold", "refuse"];
|
|
4
|
+
const POLICIES = INBOUND_POLICIES;
|
|
5
|
+
/** The keys `messaging` accepts; the settings test checks each has a /freeflow settings row. */
|
|
6
|
+
export const MESSAGING_KEYS = ["enabled", "inbound"];
|
|
7
|
+
function record(value) {
|
|
8
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9
|
+
}
|
|
10
|
+
export function validateMessagingConfig(value) {
|
|
11
|
+
if (!record(value)) return "messaging must be an object";
|
|
12
|
+
const unknown = Object.keys(value).find((key) => !MESSAGING_KEYS.includes(key));
|
|
13
|
+
if (unknown) return `messaging has unsupported key: ${unknown}`;
|
|
14
|
+
if (value.enabled !== undefined && typeof value.enabled !== "boolean") return "messaging.enabled must be a boolean";
|
|
15
|
+
if (value.inbound !== undefined && !POLICIES.includes(value.inbound))
|
|
16
|
+
return `messaging.inbound must be one of ${POLICIES.join(", ")}`;
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
export function resolveMessagingConfig(repository, local, freeflowEnabled) {
|
|
20
|
+
const repositoryConfig = record(repository.messaging) ? repository.messaging : {};
|
|
21
|
+
const localConfig = record(local.messaging) ? local.messaging : {};
|
|
22
|
+
const pick = (key) => (Object.hasOwn(localConfig, key) ? localConfig[key] : repositoryConfig[key]);
|
|
23
|
+
const value = pick("enabled");
|
|
24
|
+
const enabled = value === undefined ? DEFAULT_MESSAGING_ENABLED : value === true;
|
|
25
|
+
const inbound = POLICIES.includes(pick("inbound")) ? pick("inbound") : DEFAULT_INBOUND_POLICY;
|
|
26
|
+
return { enabled, effective: freeflowEnabled && enabled, inbound };
|
|
27
|
+
}
|
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { basename } from "node:path";
|
|
3
|
+
import { withoutNoticeTags } from "../host/inbound.js";
|
|
4
|
+
import { LiveRegistry } from "./registry.js";
|
|
5
|
+
import { close, listen, request, socketPathFor } from "./transport.js";
|
|
6
|
+
/**
|
|
7
|
+
* Messaging between live Pi sessions on this machine (Task 002 D-005), following Claude Code's cross-session
|
|
8
|
+
* messaging: a session listens on its own socket while messaging is effective and lists itself in the live registry.
|
|
9
|
+
* A message from another session is delivered as a notice (steered in while busy, a new turn while idle), held for the
|
|
10
|
+
* user in a dialog that expires, or refused, by the inbound policy; the sender learns which. A message is information
|
|
11
|
+
* from another agent, never the user's instruction, and every delivered notice says so.
|
|
12
|
+
*/
|
|
13
|
+
export const MESSAGE_TYPE = "freeflow-message";
|
|
14
|
+
export const MAX_MESSAGE_CHARS = 100_000;
|
|
15
|
+
export const HOLD_EXPIRY_MS = 5 * 60_000;
|
|
16
|
+
export const MAX_HELD = 100;
|
|
17
|
+
/** A sender may send at most this many messages in BURST_WINDOW_MS. */
|
|
18
|
+
export const BURST_LIMIT = 5;
|
|
19
|
+
export const BURST_WINDOW_MS = 10_000;
|
|
20
|
+
/** A receiver takes at most this many messages a minute from one sender, and this many in all. */
|
|
21
|
+
export const PER_SENDER_PER_MINUTE = 20;
|
|
22
|
+
export const ACCEPTED_PER_MINUTE = 50;
|
|
23
|
+
export const WATCH_EXPIRY_MS = 12 * 60 * 60_000;
|
|
24
|
+
const shortId = (sessionId) => sessionId.replace(/-/g, "").slice(-8);
|
|
25
|
+
const attribute = (value) =>
|
|
26
|
+
value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/\n/g, " ");
|
|
27
|
+
/** A message body cannot close its own tag, or pass for one of Freeflow's notices. */
|
|
28
|
+
const escapeBody = (text) => withoutNoticeTags(text).replace(/<\/(message|message-status|session-status)/gi, "<\\/$1");
|
|
29
|
+
export function displayName(identity) {
|
|
30
|
+
return identity.name?.trim() || `${basename(identity.cwd) || "session"}-${shortId(identity.sessionId).slice(0, 4)}`;
|
|
31
|
+
}
|
|
32
|
+
export class Messaging {
|
|
33
|
+
host;
|
|
34
|
+
registry;
|
|
35
|
+
server;
|
|
36
|
+
me;
|
|
37
|
+
busyNow = false;
|
|
38
|
+
writing = Promise.resolve();
|
|
39
|
+
stopping = false;
|
|
40
|
+
held = [];
|
|
41
|
+
asking = false;
|
|
42
|
+
watchers = new Map();
|
|
43
|
+
sent = [];
|
|
44
|
+
received = new Map();
|
|
45
|
+
accepted = [];
|
|
46
|
+
/** Display names of the sessions seen in the registry, for transcript rows. */
|
|
47
|
+
names = new Map();
|
|
48
|
+
holdExpiryMs;
|
|
49
|
+
constructor(host, home, options = {}) {
|
|
50
|
+
this.host = host;
|
|
51
|
+
this.registry = new LiveRegistry(home);
|
|
52
|
+
this.holdExpiryMs = options.holdExpiryMs ?? HOLD_EXPIRY_MS;
|
|
53
|
+
}
|
|
54
|
+
running() {
|
|
55
|
+
return this.server !== undefined;
|
|
56
|
+
}
|
|
57
|
+
self() {
|
|
58
|
+
return this.me;
|
|
59
|
+
}
|
|
60
|
+
heldCount() {
|
|
61
|
+
return this.held.length;
|
|
62
|
+
}
|
|
63
|
+
/** Listen and list this session; idempotent. Call again after the identity changes (name, focus) to refresh it. */
|
|
64
|
+
async start() {
|
|
65
|
+
const identity = await this.host.identity();
|
|
66
|
+
const socket = socketPathFor(identity.sessionId);
|
|
67
|
+
if (this.server && this.me?.sessionId !== identity.sessionId) await this.stop();
|
|
68
|
+
if (!this.server) this.server = await listen(socket, (body) => this.handle(body));
|
|
69
|
+
this.me = {
|
|
70
|
+
sessionId: identity.sessionId,
|
|
71
|
+
name: displayName(identity),
|
|
72
|
+
cwd: identity.cwd,
|
|
73
|
+
socket,
|
|
74
|
+
...(identity.project ? { project: identity.project } : {}),
|
|
75
|
+
...(identity.task ? { task: identity.task } : {}),
|
|
76
|
+
...(identity.owns?.length ? { owns: identity.owns } : {}),
|
|
77
|
+
};
|
|
78
|
+
this.busyNow = this.host.busy();
|
|
79
|
+
await this.publish();
|
|
80
|
+
}
|
|
81
|
+
/** Session end or messaging off: tell watchers it exited, forget held messages, leave the registry. */
|
|
82
|
+
async stop() {
|
|
83
|
+
const me = this.me;
|
|
84
|
+
if (me) {
|
|
85
|
+
// No registry write starts from here on, and the one in flight lands before the entry is removed.
|
|
86
|
+
this.stopping = true;
|
|
87
|
+
await this.writing;
|
|
88
|
+
await this.notifyWatchers("exited");
|
|
89
|
+
await this.registry.remove(me.sessionId);
|
|
90
|
+
this.published = "";
|
|
91
|
+
}
|
|
92
|
+
for (const held of this.held.splice(0)) await this.outcome(held, "expired");
|
|
93
|
+
await close(this.server, me?.socket ?? "");
|
|
94
|
+
this.server = undefined;
|
|
95
|
+
this.me = undefined;
|
|
96
|
+
this.stopping = false;
|
|
97
|
+
this.watchers.clear();
|
|
98
|
+
}
|
|
99
|
+
/** The agent started or settled. Written only on change; turning idle notifies sessions waiting for it. */
|
|
100
|
+
async setBusy(busy) {
|
|
101
|
+
if (!this.me || busy === this.busyNow) return;
|
|
102
|
+
this.busyNow = busy;
|
|
103
|
+
await this.publish();
|
|
104
|
+
if (!busy) await this.notifyWatchers("idle");
|
|
105
|
+
}
|
|
106
|
+
async list() {
|
|
107
|
+
const sessions = await this.registry.list();
|
|
108
|
+
for (const session of sessions) this.names.set(session.sessionId, session.name);
|
|
109
|
+
return sessions;
|
|
110
|
+
}
|
|
111
|
+
/** The display name of a session this one has seen, for a row that has only its ID. */
|
|
112
|
+
nameOf(sessionId) {
|
|
113
|
+
return this.names.get(sessionId);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* The live session with this session ID. Only a full ID addresses a session: names and folders are for display and
|
|
117
|
+
* may repeat (Task 002 D-009). An address that is not an ID gets the IDs it might mean, so the next call can use one.
|
|
118
|
+
*/
|
|
119
|
+
async resolve(to) {
|
|
120
|
+
const address = to.trim();
|
|
121
|
+
if (address === this.me?.sessionId) return { error: "That is this session. Address another one." };
|
|
122
|
+
const sessions = (await this.list()).filter((session) => session.sessionId !== this.me?.sessionId);
|
|
123
|
+
const session = sessions.find((each) => each.sessionId === address);
|
|
124
|
+
if (session) return { session };
|
|
125
|
+
const compact = address.replace(/-/g, "");
|
|
126
|
+
const near = sessions.filter(
|
|
127
|
+
(each) =>
|
|
128
|
+
each.name === address ||
|
|
129
|
+
each.cwd === address ||
|
|
130
|
+
each.owns?.includes(address) ||
|
|
131
|
+
each.task === address ||
|
|
132
|
+
(compact.length >= 4 && each.sessionId.replace(/-/g, "").includes(compact)),
|
|
133
|
+
);
|
|
134
|
+
const how = "Address a session by its session ID, from list_sessions or the session= of a message it sent.";
|
|
135
|
+
return {
|
|
136
|
+
error: near.length
|
|
137
|
+
? `"${to}" is not a session ID. ${how} Sessions it may mean: ${near.map((each) => `${each.name} (${each.sessionId})`).join(", ")}.`
|
|
138
|
+
: `No live session with messaging on has the session ID "${to}". ${how}`,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
async send(to, text, options = {}) {
|
|
142
|
+
const me = this.me;
|
|
143
|
+
if (!me) return this.result(false, "Messaging is not running in this session.", {});
|
|
144
|
+
const body = typeof text === "string" ? text : "";
|
|
145
|
+
if (body.trim() === "") return this.result(false, "The message is empty.", {});
|
|
146
|
+
if (body.length > MAX_MESSAGE_CHARS)
|
|
147
|
+
return this.result(false, `The message has ${body.length} characters; the limit is ${MAX_MESSAGE_CHARS}.`, {});
|
|
148
|
+
const now = Date.now();
|
|
149
|
+
this.sent = this.sent.filter((at) => now - at < BURST_WINDOW_MS);
|
|
150
|
+
if (this.sent.length >= BURST_LIMIT)
|
|
151
|
+
return this.result(
|
|
152
|
+
false,
|
|
153
|
+
`Not sent: more than ${BURST_LIMIT} messages in ${BURST_WINDOW_MS / 1000} seconds. Wait for answers before sending more.`,
|
|
154
|
+
{ status: "refused" },
|
|
155
|
+
);
|
|
156
|
+
const { session, error } = await this.resolve(to);
|
|
157
|
+
if (!session) return this.result(false, error ?? "No such session.", {});
|
|
158
|
+
this.sent.push(now);
|
|
159
|
+
const id = randomUUID();
|
|
160
|
+
let reply;
|
|
161
|
+
try {
|
|
162
|
+
reply = await request(session.socket, {
|
|
163
|
+
type: "message",
|
|
164
|
+
id,
|
|
165
|
+
from: me,
|
|
166
|
+
text: body,
|
|
167
|
+
...(options.summary ? { summary: options.summary } : {}),
|
|
168
|
+
});
|
|
169
|
+
} catch (failure) {
|
|
170
|
+
return this.result(
|
|
171
|
+
false,
|
|
172
|
+
`Not delivered to ${session.name}: ${failure instanceof Error ? failure.message : String(failure)}`,
|
|
173
|
+
{ to: session.sessionId, status: "failed" },
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
const status = String(reply?.status ?? "failed");
|
|
177
|
+
let watch = "";
|
|
178
|
+
if (options.notifyWhenIdle && (status === "delivered" || status === "held")) {
|
|
179
|
+
try {
|
|
180
|
+
const watched = await request(session.socket, { type: "watch", from: me });
|
|
181
|
+
watch =
|
|
182
|
+
watched?.status === "idle"
|
|
183
|
+
? ` ${session.name} is idle now.`
|
|
184
|
+
: ` You will get one notice when ${session.name} next goes idle or exits.`;
|
|
185
|
+
} catch {
|
|
186
|
+
watch = ` Could not ask to be told when ${session.name} goes idle.`;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
const said =
|
|
190
|
+
status === "delivered" && reply?.waiting === true
|
|
191
|
+
? `Delivered to ${session.name} (${session.sessionId}), which has had no prompt yet: its agent reads the message when its user next sends one. A reply, if it sends one, reaches you as a notice: you do not need to wait or look for it.`
|
|
192
|
+
: status === "delivered"
|
|
193
|
+
? `Delivered to ${session.name} (${session.sessionId}). A reply, if it sends one, reaches you as a notice: you do not need to wait or look for it.`
|
|
194
|
+
: status === "held"
|
|
195
|
+
? `Held for ${session.name}'s user to accept or refuse; you will get a notice with the outcome (at most ${HOLD_EXPIRY_MS / 60_000} minutes).`
|
|
196
|
+
: `Refused by ${session.name}${reply?.reason ? `: ${reply.reason}` : "."}`;
|
|
197
|
+
return this.result(status === "delivered" || status === "held", `${said}${watch}`, {
|
|
198
|
+
to: session.sessionId,
|
|
199
|
+
name: session.name,
|
|
200
|
+
status,
|
|
201
|
+
id,
|
|
202
|
+
...(reply?.waiting === true ? { waiting: true } : {}),
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
result(ok, text, details) {
|
|
206
|
+
return { ok, text, details };
|
|
207
|
+
}
|
|
208
|
+
published = "";
|
|
209
|
+
/** Write this session's registry entry when it changed. */
|
|
210
|
+
/** One registry write at a time, in order, so a busy flag written later is never overwritten by an earlier one. */
|
|
211
|
+
publish() {
|
|
212
|
+
const run = this.writing.then(() => this.write());
|
|
213
|
+
this.writing = run.catch(() => {});
|
|
214
|
+
return run;
|
|
215
|
+
}
|
|
216
|
+
async write() {
|
|
217
|
+
const me = this.me;
|
|
218
|
+
if (!me || this.stopping) return;
|
|
219
|
+
const entry = {
|
|
220
|
+
sessionId: me.sessionId,
|
|
221
|
+
name: me.name,
|
|
222
|
+
cwd: me.cwd,
|
|
223
|
+
pid: process.pid,
|
|
224
|
+
socket: me.socket,
|
|
225
|
+
busy: this.busyNow,
|
|
226
|
+
...(me.project ? { project: me.project } : {}),
|
|
227
|
+
...(me.task ? { task: me.task } : {}),
|
|
228
|
+
...(me.owns?.length ? { owns: me.owns } : {}),
|
|
229
|
+
startedAt: this.startedAt,
|
|
230
|
+
};
|
|
231
|
+
const text = JSON.stringify(entry);
|
|
232
|
+
if (text === this.published) return;
|
|
233
|
+
await this.registry.write(entry);
|
|
234
|
+
this.published = text;
|
|
235
|
+
}
|
|
236
|
+
startedAt = new Date().toISOString();
|
|
237
|
+
/** One request from another session. */
|
|
238
|
+
async handle(body) {
|
|
239
|
+
const from = body?.from;
|
|
240
|
+
if (!this.me) return { status: "refused", reason: "Messaging is off in this session." };
|
|
241
|
+
if (!from || typeof from.sessionId !== "string" || typeof from.socket !== "string")
|
|
242
|
+
return { status: "refused", reason: "The request names no sender." };
|
|
243
|
+
if (body.type === "message") return this.receive(body, from);
|
|
244
|
+
if (body.type === "watch") {
|
|
245
|
+
if (!this.busyNow) return { status: "idle" };
|
|
246
|
+
this.watchers.set(from.sessionId, { sender: from, until: Date.now() + WATCH_EXPIRY_MS });
|
|
247
|
+
return { status: "watching" };
|
|
248
|
+
}
|
|
249
|
+
if (body.type === "outcome") {
|
|
250
|
+
const outcome =
|
|
251
|
+
body.status === "accepted"
|
|
252
|
+
? "was accepted by its user and delivered"
|
|
253
|
+
: body.status === "refused"
|
|
254
|
+
? "was refused by its user"
|
|
255
|
+
: "was not answered in time and was dropped";
|
|
256
|
+
this.host.deliver(
|
|
257
|
+
`<message-status to="${attribute(from.name)}" session="${attribute(from.sessionId)}">Your message ${outcome}.</message-status>`,
|
|
258
|
+
{
|
|
259
|
+
kind: "outcome",
|
|
260
|
+
from: from.sessionId,
|
|
261
|
+
name: from.name,
|
|
262
|
+
status: body.status,
|
|
263
|
+
id: body.id,
|
|
264
|
+
},
|
|
265
|
+
);
|
|
266
|
+
return { status: "ok" };
|
|
267
|
+
}
|
|
268
|
+
if (body.type === "idle" || body.type === "exited") {
|
|
269
|
+
this.host.deliver(
|
|
270
|
+
`<session-status name="${attribute(from.name)}" session="${attribute(from.sessionId)}">${body.type === "idle" ? "The session is now idle." : "The session has ended."}</session-status>`,
|
|
271
|
+
{ kind: body.type, from: from.sessionId, name: from.name },
|
|
272
|
+
);
|
|
273
|
+
return { status: "ok" };
|
|
274
|
+
}
|
|
275
|
+
return { status: "refused", reason: `Unknown request type ${String(body.type)}.` };
|
|
276
|
+
}
|
|
277
|
+
async receive(body, from) {
|
|
278
|
+
const text = typeof body.text === "string" ? body.text : "";
|
|
279
|
+
if (text.trim() === "") return { status: "refused", reason: "The message is empty." };
|
|
280
|
+
if (text.length > MAX_MESSAGE_CHARS) return { status: "refused", reason: "The message is too large." };
|
|
281
|
+
const now = Date.now();
|
|
282
|
+
const recent = (this.received.get(from.sessionId) ?? []).filter((at) => now - at < 60_000);
|
|
283
|
+
if (recent.length >= PER_SENDER_PER_MINUTE)
|
|
284
|
+
return { status: "refused", reason: `More than ${PER_SENDER_PER_MINUTE} messages a minute from one session.` };
|
|
285
|
+
this.accepted = this.accepted.filter((at) => now - at < 60_000);
|
|
286
|
+
if (this.accepted.length >= ACCEPTED_PER_MINUTE)
|
|
287
|
+
return { status: "refused", reason: "This session is receiving too many messages; try again later." };
|
|
288
|
+
this.received.set(from.sessionId, [...recent, now]);
|
|
289
|
+
const policy = this.host.policy();
|
|
290
|
+
const decision =
|
|
291
|
+
policy === "refuse"
|
|
292
|
+
? "refuse"
|
|
293
|
+
: policy === "accept"
|
|
294
|
+
? "accept"
|
|
295
|
+
: policy === "hold"
|
|
296
|
+
? "hold"
|
|
297
|
+
: from.project && from.project === this.me?.project
|
|
298
|
+
? "accept"
|
|
299
|
+
: "hold";
|
|
300
|
+
if (decision === "refuse") return { status: "refused", reason: "This session does not accept messages." };
|
|
301
|
+
if (decision === "hold" && !this.host.canAsk())
|
|
302
|
+
return {
|
|
303
|
+
status: "refused",
|
|
304
|
+
reason: "This session holds messages from outside its project, and has no UI to ask its user.",
|
|
305
|
+
};
|
|
306
|
+
const message = {
|
|
307
|
+
id: String(body.id ?? randomUUID()),
|
|
308
|
+
from,
|
|
309
|
+
text,
|
|
310
|
+
...(typeof body.summary === "string" ? { summary: body.summary } : {}),
|
|
311
|
+
at: now,
|
|
312
|
+
};
|
|
313
|
+
if (decision === "accept") {
|
|
314
|
+
this.accepted.push(now);
|
|
315
|
+
this.deliverMessage(message);
|
|
316
|
+
return { status: "delivered", ...(this.host.canWake?.() === false ? { waiting: true } : {}) };
|
|
317
|
+
}
|
|
318
|
+
if (this.held.length >= MAX_HELD) {
|
|
319
|
+
// The oldest waiting message goes, never the one the user is looking at in the dialog.
|
|
320
|
+
const [dropped] = this.held.splice(this.asking ? 1 : 0, 1);
|
|
321
|
+
if (dropped) void this.outcome(dropped, "expired");
|
|
322
|
+
}
|
|
323
|
+
this.held.push(message);
|
|
324
|
+
void this.askNext();
|
|
325
|
+
return { status: "held" };
|
|
326
|
+
}
|
|
327
|
+
/** Put held messages to the user one at a time; each dialog expires, and the sender learns the outcome. */
|
|
328
|
+
async askNext() {
|
|
329
|
+
if (this.asking) return;
|
|
330
|
+
this.asking = true;
|
|
331
|
+
try {
|
|
332
|
+
while (this.held.length > 0) {
|
|
333
|
+
const message = this.held[0];
|
|
334
|
+
const left = this.holdExpiryMs - (Date.now() - message.at);
|
|
335
|
+
let answer = false;
|
|
336
|
+
if (left > 0) {
|
|
337
|
+
const preview = message.text.length > 600 ? `${message.text.slice(0, 600)}...` : message.text;
|
|
338
|
+
answer = await this.host.confirm(
|
|
339
|
+
`Message from ${message.from.name}`,
|
|
340
|
+
`${message.from.name} (${message.from.cwd}${message.from.project ? `, project ${message.from.project}` : ""}) sent:\n\n${preview}\n\nDeliver it to the agent in this session?`,
|
|
341
|
+
left,
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
if (this.held[0] !== message) continue;
|
|
345
|
+
this.held.shift();
|
|
346
|
+
if (answer === true) {
|
|
347
|
+
this.accepted.push(Date.now());
|
|
348
|
+
this.deliverMessage(message);
|
|
349
|
+
}
|
|
350
|
+
const expired =
|
|
351
|
+
!answer && Date.now() - message.at >= this.holdExpiryMs - Math.min(1_000, this.holdExpiryMs / 10);
|
|
352
|
+
await this.outcome(message, answer ? "accepted" : expired ? "expired" : "refused");
|
|
353
|
+
}
|
|
354
|
+
} finally {
|
|
355
|
+
this.asking = false;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
deliverMessage(message) {
|
|
359
|
+
const from = message.from;
|
|
360
|
+
// The tag marks it as another agent's message; the messaging skill says what to do with it (Task 002 D-008).
|
|
361
|
+
const attributes = [
|
|
362
|
+
`from="${attribute(from.name)}"`,
|
|
363
|
+
`session="${attribute(from.sessionId)}"`,
|
|
364
|
+
`folder="${attribute(from.cwd)}"`,
|
|
365
|
+
...(from.task ? [`task="${attribute(from.task)}"`] : []),
|
|
366
|
+
].join(" ");
|
|
367
|
+
this.host.deliver(`<message ${attributes}>\n${escapeBody(message.text)}\n</message>`, {
|
|
368
|
+
kind: "message",
|
|
369
|
+
from: from.sessionId,
|
|
370
|
+
name: from.name,
|
|
371
|
+
id: message.id,
|
|
372
|
+
...(message.summary ? { summary: message.summary } : {}),
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
async outcome(message, status) {
|
|
376
|
+
if (!this.me) return;
|
|
377
|
+
try {
|
|
378
|
+
await request(message.from.socket, { type: "outcome", id: message.id, status, from: this.me });
|
|
379
|
+
} catch {
|
|
380
|
+
// The sender is gone; nothing to tell.
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
async notifyWatchers(type) {
|
|
384
|
+
const me = this.me;
|
|
385
|
+
if (!me) return;
|
|
386
|
+
const now = Date.now();
|
|
387
|
+
const watchers = [...this.watchers.values()];
|
|
388
|
+
this.watchers.clear();
|
|
389
|
+
for (const { sender, until } of watchers) {
|
|
390
|
+
if (until < now) continue;
|
|
391
|
+
try {
|
|
392
|
+
await request(sender.socket, { type, from: me });
|
|
393
|
+
} catch {
|
|
394
|
+
// The watcher is gone.
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { mkdir, readdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
const fileName = (sessionId) => `${sessionId.replace(/[^A-Za-z0-9._-]/g, "_")}.json`;
|
|
5
|
+
export function alive(pid) {
|
|
6
|
+
try {
|
|
7
|
+
process.kill(pid, 0);
|
|
8
|
+
return true;
|
|
9
|
+
} catch (error) {
|
|
10
|
+
return error?.code === "EPERM";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export class LiveRegistry {
|
|
14
|
+
folder;
|
|
15
|
+
constructor(home) {
|
|
16
|
+
this.folder = join(home, "sessions");
|
|
17
|
+
}
|
|
18
|
+
async write(entry) {
|
|
19
|
+
await mkdir(this.folder, { recursive: true, mode: 0o700 });
|
|
20
|
+
const path = join(this.folder, fileName(entry.sessionId));
|
|
21
|
+
// One temporary file per write, so two writes in flight never rename each other's file.
|
|
22
|
+
const temporary = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
23
|
+
await writeFile(temporary, `${JSON.stringify(entry, null, 2)}\n`, { mode: 0o600 });
|
|
24
|
+
await rename(temporary, path);
|
|
25
|
+
}
|
|
26
|
+
async remove(sessionId) {
|
|
27
|
+
await rm(join(this.folder, fileName(sessionId)), { force: true });
|
|
28
|
+
}
|
|
29
|
+
/** Every live session, oldest first; stale entries and their sockets are removed on the way. */
|
|
30
|
+
async list() {
|
|
31
|
+
let names;
|
|
32
|
+
try {
|
|
33
|
+
names = await readdir(this.folder);
|
|
34
|
+
} catch {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
const sessions = [];
|
|
38
|
+
for (const name of names.filter((each) => each.endsWith(".json"))) {
|
|
39
|
+
const path = join(this.folder, name);
|
|
40
|
+
let entry;
|
|
41
|
+
try {
|
|
42
|
+
entry = JSON.parse(await readFile(path, "utf8"));
|
|
43
|
+
} catch {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (typeof entry?.pid !== "number" || typeof entry.sessionId !== "string" || typeof entry.socket !== "string")
|
|
47
|
+
continue;
|
|
48
|
+
if (!alive(entry.pid)) {
|
|
49
|
+
await rm(path, { force: true });
|
|
50
|
+
await rm(entry.socket, { force: true });
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
sessions.push(entry);
|
|
54
|
+
}
|
|
55
|
+
return sessions.sort((a, b) => a.startedAt.localeCompare(b.startedAt));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { Text } from "@earendil-works/pi-tui";
|
|
2
|
+
import { firstLine, foldRow, paint } from "../host/rows.js";
|
|
3
|
+
/** Transcript rows for messaging (Task 002 D-008): what was sent to whom, what arrived from whom, and outcomes. */
|
|
4
|
+
const statusColor = (status) =>
|
|
5
|
+
status === "delivered"
|
|
6
|
+
? "success"
|
|
7
|
+
: status === "held"
|
|
8
|
+
? "warning"
|
|
9
|
+
: status === "refused" || status === "failed"
|
|
10
|
+
? "error"
|
|
11
|
+
: "muted";
|
|
12
|
+
/** What the row adds under its head: the whole text when expanded, otherwise the lines after the first, if any. */
|
|
13
|
+
function bodyAfterHead(text, expanded) {
|
|
14
|
+
if (expanded) return text.length > 100 || text.includes("\n") ? [text] : [];
|
|
15
|
+
const lines = text.split("\n");
|
|
16
|
+
const first = lines.findIndex((line) => line.trim());
|
|
17
|
+
return lines
|
|
18
|
+
.slice(first + 1)
|
|
19
|
+
.join("\n")
|
|
20
|
+
.trim()
|
|
21
|
+
? [
|
|
22
|
+
lines
|
|
23
|
+
.slice(first + 1)
|
|
24
|
+
.join("\n")
|
|
25
|
+
.trim(),
|
|
26
|
+
]
|
|
27
|
+
: [];
|
|
28
|
+
}
|
|
29
|
+
/** A session's name for a row: the name the result gave, or the one this session has seen, or a short ID. */
|
|
30
|
+
function shownName(id, context, nameOf) {
|
|
31
|
+
const named = context?.state?.name ?? nameOf?.(id);
|
|
32
|
+
if (named) return named;
|
|
33
|
+
return id.length > 12 ? `${id.slice(0, 8)}…` : id;
|
|
34
|
+
}
|
|
35
|
+
export function renderSendCall(args, theme, context, nameOf) {
|
|
36
|
+
const to = shownName(String(args?.to ?? ""), context, nameOf);
|
|
37
|
+
const message = String(args?.message ?? "");
|
|
38
|
+
const head = `${paint(theme, "toolTitle", "Message")} ${paint(theme, "accent", `→ ${to}`)}${message ? `: ${paint(theme, "muted", firstLine(message))}` : ""}`;
|
|
39
|
+
return foldRow(head, bodyAfterHead(message, context?.expanded === true), context?.expanded === true, theme);
|
|
40
|
+
}
|
|
41
|
+
export function renderSendResult(result, options, theme, context) {
|
|
42
|
+
const details = result?.details ?? {};
|
|
43
|
+
// The call line shows the address as a name once the result names the session.
|
|
44
|
+
if (context?.state && details.name && context.state.name !== details.name) {
|
|
45
|
+
context.state.name = details.name;
|
|
46
|
+
// After this render: invalidating inside it makes Pi rebuild the row and then add this result again.
|
|
47
|
+
queueMicrotask(() => context.invalidate?.());
|
|
48
|
+
}
|
|
49
|
+
const status = String(details.status ?? (result?.isError ? "failed" : "sent"));
|
|
50
|
+
const name = details.name ?? details.to ?? "";
|
|
51
|
+
const text = (result?.content ?? []).map((part) => part?.text ?? "").join("\n");
|
|
52
|
+
const said =
|
|
53
|
+
status === "delivered"
|
|
54
|
+
? `Delivered${name ? ` to ${name}` : ""}`
|
|
55
|
+
: status === "held"
|
|
56
|
+
? `Held for ${name || "the other session"}'s user to accept`
|
|
57
|
+
: firstLine(text, 140) || status;
|
|
58
|
+
const extra = text.split("\n\n").slice(1).join("\n\n");
|
|
59
|
+
return foldRow(paint(theme, statusColor(status), said), extra ? [extra] : [], options?.expanded === true, theme);
|
|
60
|
+
}
|
|
61
|
+
export function renderListResult(result, options, theme) {
|
|
62
|
+
const sessions = result?.details?.sessions ?? [];
|
|
63
|
+
const text = (result?.content ?? []).map((part) => part?.text ?? "").join("\n");
|
|
64
|
+
const head = paint(theme, "muted", `${sessions.length} other ${sessions.length === 1 ? "session" : "sessions"}`);
|
|
65
|
+
return foldRow(head, text.split("\n").slice(1), options?.expanded === true, theme);
|
|
66
|
+
}
|
|
67
|
+
/** A received message, a held message's outcome, or a watched session's status, delivered as a Freeflow message. */
|
|
68
|
+
export function renderMessage(message, options, theme) {
|
|
69
|
+
const details = message?.details ?? {};
|
|
70
|
+
const content = typeof message?.content === "string" ? message.content : "";
|
|
71
|
+
const inner = content.replace(/^<[^>]*>\n?/, "").replace(/\n?<\/[a-z-]+>\s*$/, "");
|
|
72
|
+
const label = paint(theme, "customMessageLabel", "Message");
|
|
73
|
+
if (details.kind === "outcome") {
|
|
74
|
+
const outcome =
|
|
75
|
+
details.status === "accepted"
|
|
76
|
+
? "accepted and delivered"
|
|
77
|
+
: details.status === "refused"
|
|
78
|
+
? "refused"
|
|
79
|
+
: "not answered in time";
|
|
80
|
+
return new Text(`${label} ${paint(theme, "muted", `to ${details.name ?? details.from}: ${outcome}`)}`, 0, 0);
|
|
81
|
+
}
|
|
82
|
+
if (details.kind === "idle" || details.kind === "exited")
|
|
83
|
+
return new Text(
|
|
84
|
+
`${paint(theme, "customMessageLabel", "Session")} ${paint(theme, "muted", `${details.name ?? details.from} ${details.kind === "idle" ? "is now idle" : "has ended"}`)}`,
|
|
85
|
+
0,
|
|
86
|
+
0,
|
|
87
|
+
);
|
|
88
|
+
const head = `${label} ${paint(theme, "accent", `← ${details.name ?? details.from ?? "another session"}`)}: ${paint(theme, "muted", firstLine(inner))}`;
|
|
89
|
+
return foldRow(head, bodyAfterHead(inner, options?.expanded === true), options?.expanded === true, theme);
|
|
90
|
+
}
|