@selesai/code 0.5.28 → 0.6.1
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/CHANGELOG.md +24 -0
- package/README.md +1 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +9 -1
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/config-selector.d.ts.map +1 -1
- package/dist/cli/config-selector.js +1 -1
- package/dist/cli/config-selector.js.map +1 -1
- package/dist/cli/credential-print.d.ts +23 -0
- package/dist/cli/credential-print.d.ts.map +1 -0
- package/dist/cli/credential-print.js +117 -0
- package/dist/cli/credential-print.js.map +1 -0
- package/dist/cli/startup-ui.d.ts.map +1 -1
- package/dist/cli/startup-ui.js +1 -1
- package/dist/cli/startup-ui.js.map +1 -1
- package/dist/core/agent-session-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js +3 -0
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/agent-session.d.ts +12 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +20 -14
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +11 -3
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +1 -0
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +11 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +14 -1
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/footer-data-provider.d.ts +10 -0
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +1 -1
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/llama/provider.d.ts.map +1 -1
- package/dist/core/llama/provider.js +8 -3
- package/dist/core/llama/provider.js.map +1 -1
- package/dist/core/model-config.d.ts +30 -0
- package/dist/core/model-config.d.ts.map +1 -1
- package/dist/core/model-config.js +6 -0
- package/dist/core/model-config.js.map +1 -1
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +2 -2
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts +1 -0
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +20 -3
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/model-runtime.d.ts +2 -0
- package/dist/core/model-runtime.d.ts.map +1 -1
- package/dist/core/model-runtime.js +5 -4
- package/dist/core/model-runtime.js.map +1 -1
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +13 -6
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/remote-catalog-provider.d.ts +1 -1
- package/dist/core/remote-catalog-provider.d.ts.map +1 -1
- package/dist/core/remote-catalog-provider.js +24 -11
- package/dist/core/remote-catalog-provider.js.map +1 -1
- package/dist/core/resource-loader.d.ts +15 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +66 -9
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/settings-manager.d.ts +1 -1
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +19 -1
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/system-prompt.test.d.ts +2 -0
- package/dist/core/system-prompt.test.d.ts.map +1 -0
- package/dist/core/system-prompt.test.js +89 -0
- package/dist/core/system-prompt.test.js.map +1 -0
- package/dist/core/tools/bash.d.ts +2 -0
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +34 -5
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
- package/dist/core/tools/tool-definition-wrapper.js +3 -1
- package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
- package/dist/defaults/models.json +13 -45
- package/dist/defaults/settings.json +1 -2
- package/dist/extensions/copy-turn.test.ts +131 -0
- package/dist/extensions/copy-turn.ts +6 -1
- package/dist/extensions/package.json +0 -1
- package/dist/extensions/pi-intercom/CHANGELOG.md +249 -0
- package/dist/extensions/pi-intercom/README.md +102 -39
- package/dist/extensions/pi-intercom/broker/broker.ts +1233 -36
- package/dist/extensions/pi-intercom/broker/client.test.ts +83 -0
- package/dist/extensions/pi-intercom/broker/client.ts +315 -12
- package/dist/extensions/pi-intercom/broker/extension-state.ts +186 -0
- package/dist/extensions/pi-intercom/broker/extension.test.ts +387 -0
- package/dist/extensions/pi-intercom/broker/framing.test.ts +114 -0
- package/dist/extensions/pi-intercom/broker/framing.ts +82 -24
- package/dist/extensions/pi-intercom/broker/paths.test.ts +153 -0
- package/dist/extensions/pi-intercom/broker/paths.ts +117 -8
- package/dist/extensions/pi-intercom/broker/runtime-claim.test.ts +34 -0
- package/dist/extensions/pi-intercom/broker/runtime-claim.ts +21 -0
- package/dist/extensions/pi-intercom/broker/spawn.test.ts +160 -23
- package/dist/extensions/pi-intercom/broker/spawn.ts +113 -27
- package/dist/extensions/pi-intercom/config.test.ts +93 -0
- package/dist/extensions/pi-intercom/config.ts +55 -6
- package/dist/extensions/pi-intercom/cwd.test.ts +40 -0
- package/dist/extensions/pi-intercom/cwd.ts +31 -0
- package/dist/extensions/pi-intercom/extension-api.ts +44 -0
- package/dist/extensions/pi-intercom/format-context.test.ts +31 -0
- package/dist/extensions/pi-intercom/format-context.ts +32 -0
- package/dist/extensions/pi-intercom/index.ts +742 -145
- package/dist/extensions/pi-intercom/intercom.integration.test.ts +2646 -0
- package/dist/extensions/pi-intercom/package.json +15 -5
- package/dist/extensions/pi-intercom/reply-tracker.test.ts +134 -0
- package/dist/extensions/pi-intercom/reply-tracker.ts +31 -13
- package/dist/extensions/pi-intercom/skills/pi-intercom/SKILL.md +13 -11
- package/dist/extensions/pi-intercom/test/inline-message.test.ts +184 -0
- package/dist/extensions/pi-intercom/test/overlay-width.test.ts +66 -0
- package/dist/extensions/pi-intercom/types.ts +94 -4
- package/dist/extensions/pi-intercom/ui/compose.ts +8 -4
- package/dist/extensions/pi-intercom/ui/inline-message.ts +61 -25
- package/dist/extensions/pi-intercom/ui/session-list.ts +7 -3
- package/dist/extensions/pi-subagents/CHANGELOG.md +88 -0
- package/dist/extensions/pi-subagents/LICENSE +21 -0
- package/dist/extensions/pi-subagents/README.md +158 -69
- package/dist/extensions/pi-subagents/agents/architect.md +5 -4
- package/dist/extensions/pi-subagents/agents/builder.md +6 -4
- package/dist/extensions/pi-subagents/agents/commentator.md +3 -2
- package/dist/extensions/pi-subagents/agents/explorer.md +3 -2
- package/dist/extensions/pi-subagents/agents/recapper.md +3 -2
- package/dist/extensions/pi-subagents/agents/researcher.md +4 -3
- package/dist/extensions/pi-subagents/package-lock.json +2 -2
- package/dist/extensions/pi-subagents/package.json +3 -3
- package/dist/extensions/pi-subagents/prompts/review-loop.md +1 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +22 -988
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +257 -0
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +431 -0
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +144 -0
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +281 -0
- package/dist/extensions/pi-subagents/src/agents/agent-management.ts +125 -37
- package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +4 -0
- package/dist/extensions/pi-subagents/src/agents/agents.ts +118 -9
- package/dist/extensions/pi-subagents/src/agents/skills.ts +14 -12
- package/dist/extensions/pi-subagents/src/agents/task-aware-routing.ts +125 -0
- package/dist/extensions/pi-subagents/src/api/delegation.ts +3 -0
- package/dist/extensions/pi-subagents/src/api/preflight.ts +17 -13
- package/dist/extensions/pi-subagents/src/extension/chain-validation.ts +17 -1
- package/dist/extensions/pi-subagents/src/extension/index.ts +22 -8
- package/dist/extensions/pi-subagents/src/extension/rpc.ts +248 -6
- package/dist/extensions/pi-subagents/src/extension/schemas.ts +33 -8
- package/dist/extensions/pi-subagents/src/extension/tool-description.ts +32 -14
- package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +9 -4
- package/dist/extensions/pi-subagents/src/intercom/result-intercom.ts +33 -4
- package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +86 -21
- package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +24 -13
- package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +7 -5
- package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +18 -2
- package/dist/extensions/pi-subagents/src/runs/background/chain-append.ts +48 -5
- package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +68 -1
- package/dist/extensions/pi-subagents/src/runs/background/notify.ts +56 -5
- package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +92 -11
- package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +29 -3
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +388 -29
- package/dist/extensions/pi-subagents/src/runs/foreground/async-stop-action.ts +65 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-clarify.ts +3 -3
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +214 -60
- package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +546 -252
- package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +42 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +553 -154
- package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +19 -13
- package/dist/extensions/pi-subagents/src/runs/shared/capability-ceiling.ts +51 -19
- package/dist/extensions/pi-subagents/src/runs/shared/chain-outputs.ts +3 -1
- package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +1 -1
- package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +44 -11
- package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +8 -0
- package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +97 -20
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +43 -12
- package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +52 -4
- package/dist/extensions/pi-subagents/src/runs/shared/process-signal.ts +19 -0
- package/dist/extensions/pi-subagents/src/runs/shared/run-history.ts +45 -9
- package/dist/extensions/pi-subagents/src/runs/shared/runtime-acknowledged-extensions.ts +71 -0
- package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +63 -9
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +31 -1
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-startup-retry.ts +101 -0
- package/dist/extensions/pi-subagents/src/runs/shared/task-intent.ts +22 -1
- package/dist/extensions/pi-subagents/src/runs/shared/usage-budget.ts +65 -0
- package/dist/extensions/pi-subagents/src/runs/shared/workflow-graph.ts +26 -1
- package/dist/extensions/pi-subagents/src/shared/settings.ts +17 -1
- package/dist/extensions/pi-subagents/src/shared/types.ts +196 -12
- package/dist/extensions/pi-subagents/src/shared/utils.ts +102 -5
- package/dist/extensions/pi-subagents/src/slash/delegation-adapters.ts +10 -1
- package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +46 -4
- package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +5 -3
- package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +87 -24
- package/dist/extensions/pi-subagents/src/tui/fleet.ts +182 -9
- package/dist/extensions/pi-subagents/src/tui/render.ts +55 -34
- package/dist/extensions/pi-subagents/src/watchdog/register-main.ts +14 -7
- package/dist/extensions/pi-subagents/src/watchdog/review.ts +5 -4
- package/dist/extensions/pi-subagents/src/watchdog/runtime.ts +170 -17
- package/dist/extensions/pi-subagents/src/watchdog/scope.ts +62 -0
- package/dist/extensions/pi-subagents/src/watchdog/settings.ts +41 -1
- package/dist/extensions/pi-subagents/src/watchdog/types.ts +10 -0
- package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +113 -8
- package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +419 -47
- package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +84 -0
- package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +4 -2
- package/dist/extensions/pi-subagents/test/integration/chain-clarify.test.ts +51 -2
- package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +62 -22
- package/dist/extensions/pi-subagents/test/integration/detect-error.test.ts +48 -0
- package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +8 -6
- package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +231 -19
- package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +48 -7
- package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +44 -0
- package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +199 -17
- package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +898 -16
- package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +112 -23
- package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +17 -5
- package/dist/extensions/pi-subagents/test/support/mock-pi.ts +2 -0
- package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +18 -2
- package/dist/extensions/pi-subagents/test/support/register-loader.mjs +3 -3
- package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +3 -1
- package/dist/extensions/pi-subagents/test/unit/agent-disabled.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +198 -6
- package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +161 -1
- package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +47 -2
- package/dist/extensions/pi-subagents/test/unit/async-recovery-descriptor.test.ts +60 -0
- package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +8 -0
- package/dist/extensions/pi-subagents/test/unit/builtin-agent-documentation.test.ts +63 -0
- package/dist/extensions/pi-subagents/test/unit/capability-ceiling-agent-allowlist.test.ts +136 -0
- package/dist/extensions/pi-subagents/test/unit/capability-ceiling.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/chain-append.test.ts +6 -0
- package/dist/extensions/pi-subagents/test/unit/chain-validation.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +48 -48
- package/dist/extensions/pi-subagents/test/unit/config-dir-runtime.test.ts +23 -0
- package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +27 -0
- package/dist/extensions/pi-subagents/test/unit/delegation-api.test.ts +28 -2
- package/dist/extensions/pi-subagents/test/unit/dynamic-fanout.test.ts +1 -0
- package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +176 -8
- package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +164 -5
- package/dist/extensions/pi-subagents/test/unit/foreground-control.test.ts +41 -1
- package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +17 -12
- package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +18 -2
- package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +20 -1
- package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +30 -2
- package/dist/extensions/pi-subagents/test/unit/notify.test.ts +39 -2
- package/dist/extensions/pi-subagents/test/unit/parallel-utils.test.ts +22 -0
- package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +103 -0
- package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +18 -0
- package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +48 -0
- package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +13 -1
- package/dist/extensions/pi-subagents/test/unit/result-intercom.test.ts +29 -4
- package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +223 -4
- package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +30 -0
- package/dist/extensions/pi-subagents/test/unit/runtime-acknowledged-extensions.test.ts +52 -0
- package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +46 -2
- package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +91 -1
- package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/streamed-progress-bounds.test.ts +78 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +41 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-startup-retry.test.ts +77 -0
- package/dist/extensions/pi-subagents/test/unit/task-aware-routing.test.ts +213 -0
- package/dist/extensions/pi-subagents/test/unit/task-intent.test.ts +26 -1
- package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +61 -10
- package/dist/extensions/pi-subagents/test/unit/total-cost.test.ts +1 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-runtime.test.ts +210 -1
- package/dist/extensions/pi-subagents/test/unit/watchdog-scope.test.ts +33 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-settings.test.ts +32 -0
- package/dist/extensions/pi-subagents/test/unit/writer-budget-guidance.test.ts +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +43 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/custom-message.d.ts +3 -1
- package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-message.js +10 -2
- package/dist/modes/interactive/components/custom-message.js.map +1 -1
- package/dist/modes/interactive/components/extension-editor.d.ts +1 -2
- package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-editor.js +16 -46
- package/dist/modes/interactive/components/extension-editor.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +4 -1
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.js +22 -13
- package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
- package/dist/modes/interactive/external-editor.d.ts +12 -0
- package/dist/modes/interactive/external-editor.d.ts.map +1 -0
- package/dist/modes/interactive/external-editor.js +37 -0
- package/dist/modes/interactive/external-editor.js.map +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +72 -64
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +14 -0
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/skills/ponytail/SKILL.md +1 -3
- package/dist/utils/clipboard.d.ts.map +1 -1
- package/dist/utils/clipboard.js +19 -8
- package/dist/utils/clipboard.js.map +1 -1
- package/dist/utils/version-check.d.ts.map +1 -1
- package/dist/utils/version-check.js +1 -1
- package/dist/utils/version-check.js.map +1 -1
- package/docs/compaction.md +1 -1
- package/docs/custom-provider.md +14 -5
- package/docs/environment-variables.md +86 -0
- package/docs/extensions.md +15 -5
- package/docs/index.md +1 -0
- package/docs/models.md +9 -2
- package/docs/plans/subagent-delegation/phase-0-correctness.md +265 -0
- package/docs/plans/subagent-delegation/phase-1-behavioral-contract.md +486 -0
- package/docs/plans/subagent-delegation/phase-2-context-controls.md +282 -0
- package/docs/plans/subagent-delegation/phase-3-advisory-routing.md +362 -0
- package/docs/plans/subagent-delegation/phase-4-optional-enforcement.md +381 -0
- package/docs/providers.md +21 -2
- package/docs/rpc.md +23 -6
- package/docs/session-format.md +2 -0
- package/docs/settings.md +1 -1
- package/docs/usage.md +0 -13
- package/examples/extensions/custom-compaction.ts +5 -2
- package/examples/extensions/custom-provider-anthropic/index.ts +9 -3
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/handoff.ts +11 -3
- package/examples/extensions/message-renderer.ts +3 -3
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/summarize.ts +5 -2
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/examples/sdk/12-full-control.ts +3 -1
- package/package.json +16 -7
- package/dist/extensions/caveman/caveman-instructions.cjs +0 -11
- package/dist/extensions/caveman/index.js +0 -118
- package/dist/extensions/caveman/package.json +0 -8
- package/dist/extensions/caveman/test/extension.test.js +0 -203
- package/dist/extensions/caveman/test/helpers.test.js +0 -58
- package/dist/skills/caveman/SKILL.md +0 -50
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import type { Socket } from "net";
|
|
2
2
|
|
|
3
|
+
export const MAX_FRAME_BYTES = 1024 * 1024;
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* Write a length-prefixed message to a socket.
|
|
5
7
|
* Format: 4-byte big-endian length + JSON payload
|
|
6
8
|
*/
|
|
7
9
|
export function writeMessage(socket: Socket, msg: unknown): void {
|
|
8
10
|
const json = JSON.stringify(msg);
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
const payloadLength = Buffer.byteLength(json, "utf-8");
|
|
12
|
+
const frame = Buffer.allocUnsafe(4 + payloadLength);
|
|
13
|
+
frame.writeUInt32BE(payloadLength, 0);
|
|
14
|
+
frame.write(json, 4, payloadLength, "utf-8");
|
|
15
|
+
socket.write(frame);
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
/**
|
|
@@ -20,36 +23,91 @@ export function writeMessage(socket: Socket, msg: unknown): void {
|
|
|
20
23
|
export function createMessageReader(
|
|
21
24
|
onMessage: (msg: unknown) => void,
|
|
22
25
|
onError: (error: Error) => void,
|
|
26
|
+
maxFrameBytes = MAX_FRAME_BYTES,
|
|
23
27
|
) {
|
|
24
|
-
|
|
28
|
+
const header = Buffer.allocUnsafe(4);
|
|
29
|
+
let headerBytes = 0;
|
|
30
|
+
let payload: Buffer | null = null;
|
|
31
|
+
let payloadBytes = 0;
|
|
32
|
+
let payloadLength = 0;
|
|
33
|
+
|
|
34
|
+
function reportMessage(framePayload: Buffer): boolean {
|
|
35
|
+
let msg: unknown;
|
|
36
|
+
try {
|
|
37
|
+
msg = JSON.parse(framePayload.toString("utf-8"));
|
|
38
|
+
} catch (error) {
|
|
39
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
40
|
+
onError(new Error(`Failed to parse intercom message: ${message}`, { cause: error }));
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
onMessage(msg);
|
|
46
|
+
return true;
|
|
47
|
+
} catch (error) {
|
|
48
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
49
|
+
onError(new Error(`Failed to handle intercom message: ${message}`, { cause: error }));
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
25
53
|
|
|
26
54
|
return (data: Buffer) => {
|
|
27
|
-
|
|
55
|
+
let offset = 0;
|
|
28
56
|
|
|
29
|
-
while (
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
57
|
+
while (offset < data.length) {
|
|
58
|
+
if (headerBytes < 4) {
|
|
59
|
+
const bytes = Math.min(4 - headerBytes, data.length - offset);
|
|
60
|
+
data.copy(header, headerBytes, offset, offset + bytes);
|
|
61
|
+
headerBytes += bytes;
|
|
62
|
+
offset += bytes;
|
|
63
|
+
if (headerBytes < 4) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
payloadLength = header.readUInt32BE(0);
|
|
68
|
+
if (payloadLength > maxFrameBytes) {
|
|
69
|
+
headerBytes = 0;
|
|
70
|
+
onError(new Error(`Intercom frame length ${payloadLength} exceeds maximum ${maxFrameBytes} bytes`));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Fast path: the whole payload is already in this chunk, so parse it
|
|
76
|
+
// in place without copying into a reassembly buffer.
|
|
77
|
+
if (payloadBytes === 0 && data.length - offset >= payloadLength) {
|
|
78
|
+
const framePayload = data.subarray(offset, offset + payloadLength);
|
|
79
|
+
offset += payloadLength;
|
|
80
|
+
headerBytes = 0;
|
|
81
|
+
payload = null;
|
|
82
|
+
payloadLength = 0;
|
|
83
|
+
if (!reportMessage(framePayload)) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
continue;
|
|
34
87
|
}
|
|
35
88
|
|
|
36
|
-
|
|
37
|
-
|
|
89
|
+
// A buffer allocated for a previous frame (e.g. when a chunk ended
|
|
90
|
+
// exactly on a header boundary) must not be reused for a frame of a
|
|
91
|
+
// different size, so the reassembly buffer is size-checked, not just
|
|
92
|
+
// presence-checked.
|
|
93
|
+
if (payload === null || payload.length !== payloadLength) {
|
|
94
|
+
payload = Buffer.allocUnsafe(payloadLength);
|
|
95
|
+
}
|
|
96
|
+
const bytes = Math.min(payloadLength - payloadBytes, data.length - offset);
|
|
97
|
+
data.copy(payload, payloadBytes, offset, offset + bytes);
|
|
98
|
+
payloadBytes += bytes;
|
|
99
|
+
offset += bytes;
|
|
38
100
|
|
|
39
|
-
|
|
40
|
-
try {
|
|
41
|
-
msg = JSON.parse(payload.toString("utf-8"));
|
|
42
|
-
} catch (error) {
|
|
43
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
44
|
-
onError(new Error(`Failed to parse intercom message: ${message}`, { cause: error }));
|
|
101
|
+
if (payloadBytes < payloadLength) {
|
|
45
102
|
return;
|
|
46
103
|
}
|
|
47
104
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
105
|
+
const framePayload = payload;
|
|
106
|
+
headerBytes = 0;
|
|
107
|
+
payload = null;
|
|
108
|
+
payloadBytes = 0;
|
|
109
|
+
payloadLength = 0;
|
|
110
|
+
if (!reportMessage(framePayload)) {
|
|
53
111
|
return;
|
|
54
112
|
}
|
|
55
113
|
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { chmodSync, mkdtempSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { getAgentDir } from "@selesai/code";
|
|
7
|
+
import {
|
|
8
|
+
ensureIntercomRuntimeDir,
|
|
9
|
+
getAgentDirPath,
|
|
10
|
+
getBrokerConnectTarget,
|
|
11
|
+
getBrokerListenTarget,
|
|
12
|
+
getBrokerPortFilePath,
|
|
13
|
+
getBrokerSocketPath,
|
|
14
|
+
getIntercomDirPath,
|
|
15
|
+
INTERCOM_DIR_MODE,
|
|
16
|
+
INTERCOM_RUNTIME_FILE_MODE,
|
|
17
|
+
INTERCOM_TCP_HOST,
|
|
18
|
+
restrictIntercomRuntimeFile,
|
|
19
|
+
shouldUseWindowsTcpTransport,
|
|
20
|
+
} from "./paths.ts";
|
|
21
|
+
|
|
22
|
+
test("getAgentDirPath defaults to Selesai's configured agent directory", () => {
|
|
23
|
+
assert.equal(getAgentDirPath({}), getAgentDir());
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("getAgentDirPath honors SELESAI_CODING_AGENT_DIR", () => {
|
|
27
|
+
assert.equal(getAgentDirPath({ SELESAI_CODING_AGENT_DIR: "/tmp/selesai-agent" }), "/tmp/selesai-agent");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("getAgentDirPath resolves relative SELESAI_CODING_AGENT_DIR values from the caller cwd", () => {
|
|
31
|
+
const cwd = join(tmpdir(), "workspace", "project");
|
|
32
|
+
assert.equal(
|
|
33
|
+
getAgentDirPath({ SELESAI_CODING_AGENT_DIR: "relative-agent" }, cwd),
|
|
34
|
+
join(cwd, "relative-agent"),
|
|
35
|
+
);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("getIntercomDirPath points at the intercom runtime directory under the agent dir", () => {
|
|
39
|
+
assert.equal(getIntercomDirPath("/tmp/selesai-agent"), join("/tmp/selesai-agent", "intercom"));
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("getBrokerSocketPath uses named pipe on Windows", () => {
|
|
43
|
+
const pipePath = getBrokerSocketPath("win32", "C:/Users/rcroh/.selesai/agent");
|
|
44
|
+
assert.match(pipePath, /^\\\\\.\\pipe\\pi-intercom-/);
|
|
45
|
+
assert.doesNotMatch(pipePath, /broker\.sock$/);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("getBrokerSocketPath uses broker.sock under SELESAI_CODING_AGENT_DIR on non-Windows", () => {
|
|
49
|
+
const socketPath = getBrokerSocketPath("linux", "/tmp/selesai-agent");
|
|
50
|
+
assert.equal(socketPath, join("/tmp/selesai-agent", "intercom", "broker.sock"));
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("Windows TCP transport is opt-in", () => {
|
|
54
|
+
assert.equal(shouldUseWindowsTcpTransport("win32", {}), false);
|
|
55
|
+
assert.equal(shouldUseWindowsTcpTransport("win32", { PI_INTERCOM_TRANSPORT: "tcp" }), true);
|
|
56
|
+
assert.equal(shouldUseWindowsTcpTransport("win32", { PI_INTERCOM_TCP: "1" }), true);
|
|
57
|
+
assert.equal(shouldUseWindowsTcpTransport("linux", { PI_INTERCOM_TRANSPORT: "tcp" }), false);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("getBrokerListenTarget uses dynamic localhost TCP only when opted in on Windows", () => {
|
|
61
|
+
assert.deepEqual(getBrokerListenTarget("win32", { PI_INTERCOM_TRANSPORT: "tcp" }), {
|
|
62
|
+
transport: "tcp",
|
|
63
|
+
host: INTERCOM_TCP_HOST,
|
|
64
|
+
port: 0,
|
|
65
|
+
});
|
|
66
|
+
assert.equal(getBrokerListenTarget("win32", {}), getBrokerSocketPath("win32", getAgentDirPath({})));
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("getBrokerConnectTarget reads opt-in Windows TCP endpoint from intercom state", () => {
|
|
70
|
+
const root = mkdtempSync(join(tmpdir(), "pi-intercom-paths-"));
|
|
71
|
+
const intercomDir = join(root, "intercom");
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
ensureIntercomRuntimeDir(intercomDir, "win32");
|
|
75
|
+
writeFileSync(getBrokerPortFilePath(intercomDir), JSON.stringify({
|
|
76
|
+
transport: "tcp",
|
|
77
|
+
host: "127.0.0.1",
|
|
78
|
+
port: 41234,
|
|
79
|
+
stateId: "state-1",
|
|
80
|
+
}));
|
|
81
|
+
assert.deepEqual(getBrokerConnectTarget("win32", { PI_INTERCOM_TRANSPORT: "tcp" }, intercomDir), {
|
|
82
|
+
transport: "tcp",
|
|
83
|
+
host: "127.0.0.1",
|
|
84
|
+
port: 41234,
|
|
85
|
+
stateId: "state-1",
|
|
86
|
+
});
|
|
87
|
+
} finally {
|
|
88
|
+
rmSync(root, { recursive: true, force: true });
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("getBrokerConnectTarget rejects non-local TCP endpoint hosts", () => {
|
|
93
|
+
const root = mkdtempSync(join(tmpdir(), "pi-intercom-paths-"));
|
|
94
|
+
const intercomDir = join(root, "intercom");
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
ensureIntercomRuntimeDir(intercomDir, "win32");
|
|
98
|
+
writeFileSync(getBrokerPortFilePath(intercomDir), JSON.stringify({
|
|
99
|
+
transport: "tcp",
|
|
100
|
+
host: "10.0.0.5",
|
|
101
|
+
port: 41234,
|
|
102
|
+
stateId: "state-1",
|
|
103
|
+
}));
|
|
104
|
+
assert.throws(
|
|
105
|
+
() => getBrokerConnectTarget("win32", { PI_INTERCOM_TRANSPORT: "tcp" }, intercomDir),
|
|
106
|
+
/Invalid intercom TCP endpoint/,
|
|
107
|
+
);
|
|
108
|
+
} finally {
|
|
109
|
+
rmSync(root, { recursive: true, force: true });
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("ensureIntercomRuntimeDir creates and repairs restrictive Unix directory permissions", { skip: process.platform === "win32" }, () => {
|
|
114
|
+
const root = mkdtempSync(join(tmpdir(), "pi-intercom-paths-"));
|
|
115
|
+
const intercomDir = join(root, "intercom");
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
ensureIntercomRuntimeDir(intercomDir, "linux");
|
|
119
|
+
assert.equal(statSync(intercomDir).mode & 0o777, INTERCOM_DIR_MODE);
|
|
120
|
+
|
|
121
|
+
chmodSync(intercomDir, 0o755);
|
|
122
|
+
ensureIntercomRuntimeDir(intercomDir, "linux");
|
|
123
|
+
assert.equal(statSync(intercomDir).mode & 0o777, INTERCOM_DIR_MODE);
|
|
124
|
+
} finally {
|
|
125
|
+
rmSync(root, { recursive: true, force: true });
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("restrictIntercomRuntimeFile applies restrictive Unix file permissions", { skip: process.platform === "win32" }, () => {
|
|
130
|
+
const root = mkdtempSync(join(tmpdir(), "pi-intercom-paths-"));
|
|
131
|
+
const filePath = join(root, "broker.pid");
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
writeFileSync(filePath, "123", { mode: 0o644 });
|
|
135
|
+
restrictIntercomRuntimeFile(filePath, "linux");
|
|
136
|
+
assert.equal(statSync(filePath).mode & 0o777, INTERCOM_RUNTIME_FILE_MODE);
|
|
137
|
+
} finally {
|
|
138
|
+
rmSync(root, { recursive: true, force: true });
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("runtime permission helpers skip chmod on Windows paths", () => {
|
|
143
|
+
const root = mkdtempSync(join(tmpdir(), "pi-intercom-paths-"));
|
|
144
|
+
const filePath = join(root, "broker.pid");
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
ensureIntercomRuntimeDir(root, "win32");
|
|
148
|
+
writeFileSync(filePath, "123");
|
|
149
|
+
assert.doesNotThrow(() => restrictIntercomRuntimeFile(filePath, "win32"));
|
|
150
|
+
} finally {
|
|
151
|
+
rmSync(root, { recursive: true, force: true });
|
|
152
|
+
}
|
|
153
|
+
});
|
|
@@ -1,6 +1,22 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { chmodSync, mkdirSync, readFileSync } from "fs";
|
|
2
|
+
import { isAbsolute, join, resolve } from "path";
|
|
2
3
|
import { getAgentDir } from "@selesai/code";
|
|
3
4
|
|
|
5
|
+
export const INTERCOM_DIR_MODE = 0o700;
|
|
6
|
+
export const INTERCOM_RUNTIME_FILE_MODE = 0o600;
|
|
7
|
+
export const INTERCOM_TCP_HOST = "127.0.0.1";
|
|
8
|
+
export const INTERCOM_PROTOCOL_NAME = "pi-intercom";
|
|
9
|
+
export const INTERCOM_PROTOCOL_VERSION = 1;
|
|
10
|
+
|
|
11
|
+
export interface BrokerTcpEndpoint {
|
|
12
|
+
transport: "tcp";
|
|
13
|
+
host: string;
|
|
14
|
+
port: number;
|
|
15
|
+
stateId?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type BrokerConnectTarget = string | BrokerTcpEndpoint;
|
|
19
|
+
|
|
4
20
|
function sanitizePipeSegment(value: string): string {
|
|
5
21
|
return value
|
|
6
22
|
.replace(/[^a-zA-Z0-9]+/g, "-")
|
|
@@ -8,18 +24,111 @@ function sanitizePipeSegment(value: string): string {
|
|
|
8
24
|
.toLowerCase() || "default";
|
|
9
25
|
}
|
|
10
26
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
27
|
+
export function getAgentDirPath(
|
|
28
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
29
|
+
cwd: string = process.cwd(),
|
|
30
|
+
): string {
|
|
31
|
+
const configured = env.SELESAI_CODING_AGENT_DIR?.trim();
|
|
32
|
+
if (!configured) {
|
|
33
|
+
return getAgentDir();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return isAbsolute(configured) ? configured : resolve(cwd, configured);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function getIntercomDirPath(agentDir: string = getAgentDirPath()): string {
|
|
40
|
+
return join(agentDir, "intercom");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function shouldUseWindowsTcpTransport(
|
|
44
|
+
platform: NodeJS.Platform = process.platform,
|
|
45
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
46
|
+
): boolean {
|
|
47
|
+
if (platform !== "win32") {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const transport = env.PI_INTERCOM_TRANSPORT?.trim().toLowerCase();
|
|
52
|
+
if (transport === "tcp") {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const legacyOptIn = env.PI_INTERCOM_TCP?.trim().toLowerCase();
|
|
57
|
+
return legacyOptIn === "1" || legacyOptIn === "true";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function getBrokerPortFilePath(intercomDir: string = getIntercomDirPath()): string {
|
|
61
|
+
return join(intercomDir, "broker.port.json");
|
|
14
62
|
}
|
|
15
63
|
|
|
16
64
|
export function getBrokerSocketPath(
|
|
17
65
|
platform: NodeJS.Platform = process.platform,
|
|
18
|
-
|
|
66
|
+
agentDir: string = getAgentDirPath(),
|
|
19
67
|
): string {
|
|
20
68
|
if (platform === "win32") {
|
|
21
|
-
return `\\\\.\\pipe\\pi-intercom-${sanitizePipeSegment(
|
|
69
|
+
return `\\\\.\\pipe\\pi-intercom-${sanitizePipeSegment(agentDir)}`;
|
|
22
70
|
}
|
|
23
71
|
|
|
24
|
-
return join(
|
|
25
|
-
}
|
|
72
|
+
return join(getIntercomDirPath(agentDir), "broker.sock");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function getBrokerConnectTarget(
|
|
76
|
+
platform: NodeJS.Platform = process.platform,
|
|
77
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
78
|
+
intercomDir: string = getIntercomDirPath(getAgentDirPath(env)),
|
|
79
|
+
): BrokerConnectTarget {
|
|
80
|
+
if (shouldUseWindowsTcpTransport(platform, env)) {
|
|
81
|
+
const endpointFile = getBrokerPortFilePath(intercomDir);
|
|
82
|
+
const raw = readFileSync(endpointFile, "utf-8");
|
|
83
|
+
const parsed: unknown = JSON.parse(raw);
|
|
84
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
85
|
+
throw new Error(`Invalid intercom TCP endpoint at ${endpointFile}: expected a JSON object`);
|
|
86
|
+
}
|
|
87
|
+
const endpoint = parsed as Record<string, unknown>;
|
|
88
|
+
if (
|
|
89
|
+
endpoint.transport !== "tcp"
|
|
90
|
+
|| endpoint.host !== INTERCOM_TCP_HOST
|
|
91
|
+
|| typeof endpoint.port !== "number"
|
|
92
|
+
|| !Number.isSafeInteger(endpoint.port)
|
|
93
|
+
|| endpoint.port <= 0
|
|
94
|
+
|| endpoint.port > 65535
|
|
95
|
+
|| typeof endpoint.stateId !== "string"
|
|
96
|
+
|| endpoint.stateId.length === 0
|
|
97
|
+
) {
|
|
98
|
+
throw new Error(`Invalid intercom TCP endpoint at ${endpointFile}`);
|
|
99
|
+
}
|
|
100
|
+
return { transport: "tcp", host: endpoint.host, port: endpoint.port, stateId: endpoint.stateId };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return getBrokerSocketPath(platform, getAgentDirPath(env));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function getBrokerListenTarget(
|
|
107
|
+
platform: NodeJS.Platform = process.platform,
|
|
108
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
109
|
+
): BrokerConnectTarget {
|
|
110
|
+
if (shouldUseWindowsTcpTransport(platform, env)) {
|
|
111
|
+
return { transport: "tcp", host: INTERCOM_TCP_HOST, port: 0 };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return getBrokerSocketPath(platform, getAgentDirPath(env));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function ensureIntercomRuntimeDir(
|
|
118
|
+
intercomDir: string = getIntercomDirPath(),
|
|
119
|
+
platform: NodeJS.Platform = process.platform,
|
|
120
|
+
): void {
|
|
121
|
+
mkdirSync(intercomDir, { recursive: true, mode: INTERCOM_DIR_MODE });
|
|
122
|
+
if (platform !== "win32") {
|
|
123
|
+
chmodSync(intercomDir, INTERCOM_DIR_MODE);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function restrictIntercomRuntimeFile(
|
|
128
|
+
filePath: string,
|
|
129
|
+
platform: NodeJS.Platform = process.platform,
|
|
130
|
+
): void {
|
|
131
|
+
if (platform !== "win32") {
|
|
132
|
+
chmodSync(filePath, INTERCOM_RUNTIME_FILE_MODE);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { assertNoLiveBroker } from "./runtime-claim.ts";
|
|
7
|
+
|
|
8
|
+
test("broker startup refuses to replace a live broker PID", () => {
|
|
9
|
+
const directory = mkdtempSync(path.join(tmpdir(), "pi-intercom-runtime-"));
|
|
10
|
+
const pidPath = path.join(directory, "broker.pid");
|
|
11
|
+
try {
|
|
12
|
+
writeFileSync(pidPath, `${process.pid}\n`);
|
|
13
|
+
assert.throws(
|
|
14
|
+
() => assertNoLiveBroker(pidPath),
|
|
15
|
+
new RegExp(`Refusing to replace live intercom broker process ${process.pid}`),
|
|
16
|
+
);
|
|
17
|
+
} finally {
|
|
18
|
+
rmSync(directory, { recursive: true, force: true });
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("broker startup tolerates absent, invalid, and stale PID files", () => {
|
|
23
|
+
const directory = mkdtempSync(path.join(tmpdir(), "pi-intercom-runtime-"));
|
|
24
|
+
const pidPath = path.join(directory, "broker.pid");
|
|
25
|
+
try {
|
|
26
|
+
assert.doesNotThrow(() => assertNoLiveBroker(pidPath));
|
|
27
|
+
writeFileSync(pidPath, "invalid\n");
|
|
28
|
+
assert.doesNotThrow(() => assertNoLiveBroker(pidPath));
|
|
29
|
+
writeFileSync(pidPath, "2147483647\n");
|
|
30
|
+
assert.doesNotThrow(() => assertNoLiveBroker(pidPath));
|
|
31
|
+
} finally {
|
|
32
|
+
rmSync(directory, { recursive: true, force: true });
|
|
33
|
+
}
|
|
34
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
export function assertNoLiveBroker(pidPath: string): void {
|
|
4
|
+
if (!existsSync(pidPath)) return;
|
|
5
|
+
|
|
6
|
+
let pid: number;
|
|
7
|
+
try {
|
|
8
|
+
pid = Number.parseInt(readFileSync(pidPath, "utf8").trim(), 10);
|
|
9
|
+
} catch {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
if (!Number.isSafeInteger(pid) || pid <= 0) return;
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
process.kill(pid, 0);
|
|
16
|
+
} catch (error) {
|
|
17
|
+
if ((error as NodeJS.ErrnoException).code === "ESRCH") return;
|
|
18
|
+
throw error;
|
|
19
|
+
}
|
|
20
|
+
throw new Error(`Refusing to replace live intercom broker process ${pid}`);
|
|
21
|
+
}
|
|
@@ -1,31 +1,168 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { existsSync, mkdtempSync, rmSync } from "node:fs";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import {
|
|
7
|
+
getBrokerLaunchSpec,
|
|
8
|
+
getBrokerSpawnOptions,
|
|
9
|
+
getTsxCliPath,
|
|
10
|
+
getWindowsHiddenLauncherScript,
|
|
11
|
+
getWindowsBrokerCommandLine,
|
|
12
|
+
getWindowsHiddenLauncherPath,
|
|
13
|
+
isBrokerHealthOkMessage,
|
|
14
|
+
} from "./spawn.ts";
|
|
4
15
|
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
16
|
+
test("getTsxCliPath resolves tsx cli via module resolution", () => {
|
|
17
|
+
const cliPath = getTsxCliPath("C:/repo");
|
|
18
|
+
// getTsxCliPath resolves the tsx package main entry and locates cli.mjs next
|
|
19
|
+
// to it, so the path reflects the real install location (bundled under
|
|
20
|
+
// extensionDir or hoisted by npm) rather than a hardcoded relative path.
|
|
21
|
+
assert.equal(path.basename(cliPath), "cli.mjs");
|
|
22
|
+
assert.equal(path.basename(path.dirname(cliPath)), "dist");
|
|
23
|
+
assert.equal(path.basename(path.dirname(path.dirname(cliPath))), "tsx");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("getWindowsHiddenLauncherPath points at the broker launcher script", () => {
|
|
27
|
+
const launcherPath = getWindowsHiddenLauncherPath("C:/tmp/intercom");
|
|
28
|
+
assert.equal(launcherPath, path.join("C:/tmp/intercom", "broker-launch.vbs"));
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("getWindowsBrokerCommandLine wraps node, resolved tsx cli, and broker path", () => {
|
|
32
|
+
const commandLine = getWindowsBrokerCommandLine(
|
|
33
|
+
"C:/repo/broker.ts",
|
|
34
|
+
"C:/repo",
|
|
35
|
+
"C:/Program Files/nodejs/node.exe",
|
|
36
|
+
);
|
|
37
|
+
const expectedTsxPath = getTsxCliPath("C:/repo");
|
|
38
|
+
assert.equal(
|
|
39
|
+
commandLine,
|
|
40
|
+
`"C:/Program Files/nodejs/node.exe" "${expectedTsxPath}" "C:/repo/broker.ts"`,
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("getWindowsHiddenLauncherScript runs the broker command without showing a console", () => {
|
|
45
|
+
const script = getWindowsHiddenLauncherScript('"C:/Program Files/nodejs/node.exe" "C:/repo/node_modules/tsx/dist/cli.mjs" "C:/repo/broker.ts"');
|
|
46
|
+
assert.match(script, /WshShell\.Run/);
|
|
47
|
+
assert.match(script, /, 0, False/);
|
|
48
|
+
});
|
|
9
49
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const tsxCliPath = require.resolve("tsx/cli");
|
|
50
|
+
test("getBrokerLaunchSpec uses wscript launcher on Windows without writing files", () => {
|
|
51
|
+
const intercomDir = mkdtempSync(path.join(tmpdir(), "pi-intercom-"));
|
|
13
52
|
|
|
14
|
-
|
|
15
|
-
|
|
53
|
+
try {
|
|
54
|
+
const spec = getBrokerLaunchSpec(
|
|
55
|
+
"C:/repo/broker.ts",
|
|
56
|
+
"npx",
|
|
57
|
+
["--no-install", "tsx"],
|
|
58
|
+
"C:/repo",
|
|
59
|
+
"win32",
|
|
60
|
+
intercomDir,
|
|
61
|
+
"C:/Program Files/nodejs/node.exe",
|
|
16
62
|
);
|
|
17
|
-
|
|
18
|
-
|
|
63
|
+
assert.equal(spec.command, "wscript.exe");
|
|
64
|
+
assert.deepEqual(spec.args, [path.join(intercomDir, "broker-launch.vbs")]);
|
|
65
|
+
assert.equal(spec.kind, "windows-launcher");
|
|
66
|
+
const expectedTsxPath = getTsxCliPath("C:/repo");
|
|
67
|
+
assert.equal(spec.launcherCommandLine, `"C:/Program Files/nodejs/node.exe" "${expectedTsxPath}" "C:/repo/broker.ts"`);
|
|
68
|
+
assert.equal(existsSync(path.join(intercomDir, "broker-launch.vbs")), false);
|
|
69
|
+
} finally {
|
|
70
|
+
rmSync(intercomDir, { recursive: true, force: true });
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("getBrokerLaunchSpec falls back to PATH node for a standalone Selesai executable on Windows", () => {
|
|
75
|
+
const intercomDir = mkdtempSync(path.join(tmpdir(), "pi-intercom-"));
|
|
19
76
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
77
|
+
try {
|
|
78
|
+
const spec = getBrokerLaunchSpec(
|
|
79
|
+
"C:/repo/broker.ts",
|
|
80
|
+
"npx",
|
|
81
|
+
["--no-install", "tsx"],
|
|
82
|
+
"C:/repo",
|
|
83
|
+
"win32",
|
|
84
|
+
intercomDir,
|
|
85
|
+
"C:/Program Files/Selesai/selesai.exe",
|
|
23
86
|
);
|
|
87
|
+
assert.equal(spec.kind, "windows-launcher");
|
|
88
|
+
assert.equal(spec.launcherCommandLine, `"node" "${getTsxCliPath("C:/repo")}" "C:/repo/broker.ts"`);
|
|
89
|
+
} finally {
|
|
90
|
+
rmSync(intercomDir, { recursive: true, force: true });
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("getBrokerLaunchSpec uses custom broker command on Windows", () => {
|
|
95
|
+
const intercomDir = mkdtempSync(path.join(tmpdir(), "pi-intercom-"));
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const spec = getBrokerLaunchSpec("C:/repo/broker.ts", "bun", ["--smol"], "C:/repo", "win32", intercomDir, "C:/Program Files/nodejs/node.exe");
|
|
99
|
+
assert.equal(spec.command, "wscript.exe");
|
|
100
|
+
assert.equal(spec.kind, "windows-launcher");
|
|
101
|
+
assert.equal(spec.launcherCommandLine, `"bun" "--smol" "C:/repo/broker.ts"`);
|
|
102
|
+
} finally {
|
|
103
|
+
rmSync(intercomDir, { recursive: true, force: true });
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("getBrokerLaunchSpec uses node + resolved tsx for the default non-Windows launch", () => {
|
|
108
|
+
const spec = getBrokerLaunchSpec("C:/repo/broker.ts", "npx", ["--no-install", "tsx"], "C:/repo", "linux", "/tmp/intercom", "/usr/bin/node");
|
|
109
|
+
assert.equal(spec.command, "/usr/bin/node");
|
|
110
|
+
assert.deepEqual(spec.args, [
|
|
111
|
+
getTsxCliPath("C:/repo"),
|
|
112
|
+
"C:/repo/broker.ts",
|
|
113
|
+
]);
|
|
114
|
+
assert.equal(spec.kind, "direct");
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("getBrokerLaunchSpec falls back to PATH node for a standalone Selesai executable on non-Windows", () => {
|
|
118
|
+
const spec = getBrokerLaunchSpec(
|
|
119
|
+
"/repo/broker.ts",
|
|
120
|
+
"npx",
|
|
121
|
+
["--no-install", "tsx"],
|
|
122
|
+
"/repo",
|
|
123
|
+
"darwin",
|
|
124
|
+
"/tmp/intercom",
|
|
125
|
+
"/Applications/Selesai.app/Contents/MacOS/selesai",
|
|
126
|
+
);
|
|
127
|
+
assert.equal(spec.command, "node");
|
|
128
|
+
assert.deepEqual(spec.args, [
|
|
129
|
+
getTsxCliPath("/repo"),
|
|
130
|
+
"/repo/broker.ts",
|
|
131
|
+
]);
|
|
132
|
+
assert.equal(spec.kind, "direct");
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("getBrokerLaunchSpec uses custom broker command on non-Windows", () => {
|
|
136
|
+
const spec = getBrokerLaunchSpec("/repo/broker.ts", "bun", [], "/repo", "linux", "/tmp/intercom", "/usr/bin/node");
|
|
137
|
+
assert.equal(spec.command, "bun");
|
|
138
|
+
assert.deepEqual(spec.args, ["/repo/broker.ts"]);
|
|
139
|
+
assert.equal(spec.kind, "direct");
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("getBrokerSpawnOptions hides the broker console window on Windows", () => {
|
|
143
|
+
const options = getBrokerSpawnOptions("C:/repo");
|
|
144
|
+
assert.equal(options.windowsHide, true);
|
|
145
|
+
assert.equal(options.detached, true);
|
|
146
|
+
assert.equal(options.stdio, "ignore");
|
|
147
|
+
assert.equal(options.cwd, "C:/repo");
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("getBrokerSpawnOptions keeps portable defaults on non-Windows platforms", () => {
|
|
151
|
+
const options = getBrokerSpawnOptions("/repo");
|
|
152
|
+
assert.equal(options.windowsHide, true);
|
|
153
|
+
assert.equal(options.detached, true);
|
|
154
|
+
assert.equal(options.stdio, "ignore");
|
|
155
|
+
assert.equal(options.cwd, "/repo");
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test("getBrokerSpawnOptions passes an absolute SELESAI_CODING_AGENT_DIR to the broker", () => {
|
|
159
|
+
const options = getBrokerSpawnOptions("/repo", { SELESAI_CODING_AGENT_DIR: "relative-agent" });
|
|
160
|
+
assert.equal(options.env.SELESAI_CODING_AGENT_DIR, path.resolve("relative-agent"));
|
|
161
|
+
});
|
|
24
162
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
});
|
|
163
|
+
test("isBrokerHealthOkMessage requires the intercom protocol marker", () => {
|
|
164
|
+
assert.equal(isBrokerHealthOkMessage({ type: "health_ok", requestId: "req-1", protocol: "pi-intercom", version: 1 }, "req-1"), true);
|
|
165
|
+
assert.equal(isBrokerHealthOkMessage({ type: "health_ok", requestId: "req-1" }, "req-1"), false);
|
|
166
|
+
assert.equal(isBrokerHealthOkMessage({ type: "health_ok", requestId: "req-2", protocol: "pi-intercom", version: 1 }, "req-1"), false);
|
|
167
|
+
assert.equal(isBrokerHealthOkMessage("ok", "req-1"), false);
|
|
31
168
|
});
|