@selesai/code 0.5.28 β 0.5.29
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/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 +1 -1
- package/dist/core/system-prompt.js.map +1 -1
- 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/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 +85 -0
- package/dist/extensions/pi-subagents/LICENSE +21 -0
- package/dist/extensions/pi-subagents/README.md +146 -52
- package/dist/extensions/pi-subagents/agents/architect.md +1 -0
- package/dist/extensions/pi-subagents/agents/builder.md +1 -0
- 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 +21 -989
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +256 -0
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +430 -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 +282 -0
- package/dist/extensions/pi-subagents/src/agents/agent-management.ts +71 -30
- 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/api/delegation.ts +3 -0
- package/dist/extensions/pi-subagents/src/api/preflight.ts +16 -12
- package/dist/extensions/pi-subagents/src/extension/chain-validation.ts +17 -1
- package/dist/extensions/pi-subagents/src/extension/index.ts +17 -7
- package/dist/extensions/pi-subagents/src/extension/rpc.ts +248 -6
- package/dist/extensions/pi-subagents/src/extension/schemas.ts +31 -6
- package/dist/extensions/pi-subagents/src/extension/tool-description.ts +8 -7
- 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 +63 -16
- 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 +29 -4
- package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +29 -6
- package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +29 -3
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +372 -28
- 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 +143 -43
- package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +527 -247
- package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +42 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +426 -123
- package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +15 -7
- 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/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 +1 -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 +155 -10
- package/dist/extensions/pi-subagents/src/shared/utils.ts +73 -4
- package/dist/extensions/pi-subagents/src/slash/delegation-adapters.ts +5 -0
- 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 +27 -28
- 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 +2 -2
- package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +348 -7
- 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 +26 -1
- 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 +3 -3
- package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +213 -13
- package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +34 -0
- 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 +118 -12
- package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +850 -7
- 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/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-frontmatter.test.ts +128 -0
- 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/capability-ceiling-agent-allowlist.test.ts +102 -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 +4 -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 +11 -11
- 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 +10 -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 +46 -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 +34 -2
- 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-intent.test.ts +3 -0
- package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +1 -1
- 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/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/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 +15 -6
- package/dist/extensions/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-intercom",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -8,13 +8,16 @@
|
|
|
8
8
|
"index.ts",
|
|
9
9
|
"types.ts",
|
|
10
10
|
"config.ts",
|
|
11
|
+
"cwd.ts",
|
|
12
|
+
"extension-api.ts",
|
|
13
|
+
"format-context.ts",
|
|
11
14
|
"reply-tracker.ts",
|
|
12
15
|
"broker/**/*.ts",
|
|
13
16
|
"ui/**/*.ts",
|
|
14
17
|
"skills/**/*"
|
|
15
18
|
],
|
|
16
19
|
"scripts": {
|
|
17
|
-
"test": "tsx --test broker/paths.test.ts broker/spawn.test.ts reply-tracker.test.ts intercom.integration.test.ts test/inline-message.test.ts"
|
|
20
|
+
"test": "tsx --test broker/client.test.ts broker/extension.test.ts broker/framing.test.ts broker/paths.test.ts broker/runtime-claim.test.ts broker/spawn.test.ts config.test.ts cwd.test.ts format-context.test.ts reply-tracker.test.ts intercom.integration.test.ts test/inline-message.test.ts test/overlay-width.test.ts"
|
|
18
21
|
},
|
|
19
22
|
"keywords": [
|
|
20
23
|
"pi-package"
|
|
@@ -28,11 +31,18 @@
|
|
|
28
31
|
]
|
|
29
32
|
},
|
|
30
33
|
"peerDependencies": {
|
|
34
|
+
"@earendil-works/pi-ai": "*",
|
|
31
35
|
"@selesai/code": "*",
|
|
32
|
-
"@earendil-works/pi-tui": "*"
|
|
36
|
+
"@earendil-works/pi-tui": "*",
|
|
37
|
+
"typebox": "*"
|
|
33
38
|
},
|
|
34
39
|
"dependencies": {
|
|
35
|
-
"tsx": "^4.20.0"
|
|
36
|
-
|
|
40
|
+
"tsx": "^4.20.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@earendil-works/pi-ai": "*",
|
|
44
|
+
"@selesai/code": "*",
|
|
45
|
+
"@earendil-works/pi-tui": "*",
|
|
46
|
+
"typebox": "*"
|
|
37
47
|
}
|
|
38
48
|
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { getAskTimeoutMs } from "./config.ts";
|
|
4
|
+
import { ReplyTracker } from "./reply-tracker.ts";
|
|
5
|
+
import type { Message, SessionInfo } from "./types.ts";
|
|
6
|
+
|
|
7
|
+
function createSession(id: string, name: string): SessionInfo {
|
|
8
|
+
return {
|
|
9
|
+
id,
|
|
10
|
+
name,
|
|
11
|
+
cwd: "/tmp/project",
|
|
12
|
+
model: "test-model",
|
|
13
|
+
pid: 1,
|
|
14
|
+
startedAt: 1,
|
|
15
|
+
lastActivity: 1,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function createMessage(id: string, text: string, expectsReply = true): Message {
|
|
20
|
+
return {
|
|
21
|
+
id,
|
|
22
|
+
timestamp: 1,
|
|
23
|
+
expectsReply,
|
|
24
|
+
content: { text },
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
test("reply resolves from current triggered message context", () => {
|
|
29
|
+
const tracker = new ReplyTracker();
|
|
30
|
+
const from = createSession("planner-id", "planner");
|
|
31
|
+
const message = createMessage("ask-1", "Need a decision");
|
|
32
|
+
|
|
33
|
+
const context = tracker.recordIncomingMessage(from, message, 1000);
|
|
34
|
+
tracker.queueTurnContext(context);
|
|
35
|
+
tracker.beginTurn(1001);
|
|
36
|
+
|
|
37
|
+
assert.equal(tracker.resolveReplyTarget({}, 1002).message.id, "ask-1");
|
|
38
|
+
assert.equal(tracker.resolveReplyTarget({}, 1002).from.id, "planner-id");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("reply resolves from single pending ask without current turn context", () => {
|
|
42
|
+
const tracker = new ReplyTracker();
|
|
43
|
+
tracker.recordIncomingMessage(createSession("planner-id", "planner"), createMessage("ask-1", "Need a decision"), 1000);
|
|
44
|
+
|
|
45
|
+
assert.equal(tracker.resolveReplyTarget({}, 1001).message.id, "ask-1");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("reply with to resolves matching pending ask", () => {
|
|
49
|
+
const tracker = new ReplyTracker();
|
|
50
|
+
tracker.recordIncomingMessage(createSession("planner-id", "planner"), createMessage("ask-1", "First"), 1000);
|
|
51
|
+
tracker.recordIncomingMessage(createSession("reviewer-id", "reviewer"), createMessage("ask-2", "Second"), 1001);
|
|
52
|
+
|
|
53
|
+
assert.equal(tracker.resolveReplyTarget({ to: "reviewer" }, 1002).message.id, "ask-2");
|
|
54
|
+
assert.equal(tracker.resolveReplyTarget({ to: "planner-id" }, 1002).message.id, "ask-1");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("explicit to overrides the current turn context", () => {
|
|
58
|
+
const tracker = new ReplyTracker();
|
|
59
|
+
const current = tracker.recordIncomingMessage(createSession("planner-id", "planner"), createMessage("ask-1", "First"), 1000);
|
|
60
|
+
tracker.recordIncomingMessage(createSession("reviewer-id", "reviewer"), createMessage("ask-2", "Second"), 1001);
|
|
61
|
+
tracker.queueTurnContext(current);
|
|
62
|
+
tracker.beginTurn(1002);
|
|
63
|
+
|
|
64
|
+
assert.equal(tracker.resolveReplyTarget({ to: "reviewer" }, 1003).message.id, "ask-2");
|
|
65
|
+
assert.throws(() => tracker.resolveReplyTarget({ to: "missing" }, 1003), /No pending ask from/);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("replyTo resolves the exact pending ask", () => {
|
|
69
|
+
const tracker = new ReplyTracker();
|
|
70
|
+
tracker.recordIncomingMessage(createSession("planner-id", "planner"), createMessage("ask-1", "First"), 1000);
|
|
71
|
+
tracker.recordIncomingMessage(createSession("reviewer-id", "reviewer"), createMessage("ask-2", "Second"), 1001);
|
|
72
|
+
|
|
73
|
+
assert.equal(tracker.resolveReplyTarget({ replyTo: "ask-2" }, 1002).from.id, "reviewer-id");
|
|
74
|
+
assert.throws(() => tracker.resolveReplyTarget({ to: "planner", replyTo: "ask-2" }, 1002), /is not from/);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("reply errors when no context and no pending asks", () => {
|
|
78
|
+
const tracker = new ReplyTracker();
|
|
79
|
+
|
|
80
|
+
assert.throws(() => tracker.resolveReplyTarget({}, 1000), /No active intercom context to reply to/);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("reply errors when multiple pending asks and no to", () => {
|
|
84
|
+
const tracker = new ReplyTracker();
|
|
85
|
+
tracker.recordIncomingMessage(createSession("planner-id", "planner"), createMessage("ask-1", "First"), 1000);
|
|
86
|
+
tracker.recordIncomingMessage(createSession("reviewer-id", "reviewer"), createMessage("ask-2", "Second"), 1001);
|
|
87
|
+
|
|
88
|
+
assert.throws(() => tracker.resolveReplyTarget({}, 1002), /Multiple pending asks β specify `to`/);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("reply removes pending ask after successful reply", () => {
|
|
92
|
+
const tracker = new ReplyTracker();
|
|
93
|
+
tracker.recordIncomingMessage(createSession("planner-id", "planner"), createMessage("ask-1", "Need a decision"), 1000);
|
|
94
|
+
|
|
95
|
+
tracker.markReplied("ask-1");
|
|
96
|
+
|
|
97
|
+
assert.deepEqual(tracker.listPending(1001), []);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("ask timeout can be configured from environment", () => {
|
|
101
|
+
const previous = process.env.PI_INTERCOM_ASK_TIMEOUT_MS;
|
|
102
|
+
process.env.PI_INTERCOM_ASK_TIMEOUT_MS = "42";
|
|
103
|
+
try {
|
|
104
|
+
assert.equal(getAskTimeoutMs(), 42);
|
|
105
|
+
assert.throws(() => {
|
|
106
|
+
process.env.PI_INTERCOM_ASK_TIMEOUT_MS = "0";
|
|
107
|
+
getAskTimeoutMs();
|
|
108
|
+
}, /positive integer/);
|
|
109
|
+
} finally {
|
|
110
|
+
if (previous === undefined) delete process.env.PI_INTERCOM_ASK_TIMEOUT_MS;
|
|
111
|
+
else process.env.PI_INTERCOM_ASK_TIMEOUT_MS = previous;
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("pending asks can be explicitly dismissed without removing retryable failures", () => {
|
|
116
|
+
const tracker = new ReplyTracker();
|
|
117
|
+
tracker.recordIncomingMessage(createSession("planner-id", "planner"), createMessage("ask-1", "Need a decision"), 1000);
|
|
118
|
+
tracker.recordIncomingMessage(createSession("reviewer-id", "reviewer"), createMessage("ask-2", "Retryable"), 1001);
|
|
119
|
+
|
|
120
|
+
tracker.dismissPendingAsk("ask-1");
|
|
121
|
+
|
|
122
|
+
assert.deepEqual(tracker.listPending(1002).map((context) => context.message.id), ["ask-2"]);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("dismissing a pending ask removes queued turn context", () => {
|
|
126
|
+
const tracker = new ReplyTracker();
|
|
127
|
+
const context = tracker.recordIncomingMessage(createSession("planner-id", "planner"), createMessage("ask-1", "Need a decision"), 1000);
|
|
128
|
+
tracker.queueTurnContext(context);
|
|
129
|
+
|
|
130
|
+
tracker.dismissPendingAsk("ask-1");
|
|
131
|
+
tracker.beginTurn(1001);
|
|
132
|
+
|
|
133
|
+
assert.throws(() => tracker.resolveReplyTarget({}, 1002), /No active intercom context to reply to/);
|
|
134
|
+
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getAskTimeoutMs } from "./config.ts";
|
|
1
2
|
import type { Message, SessionInfo } from "./types.ts";
|
|
2
3
|
|
|
3
4
|
export interface IntercomContext {
|
|
@@ -19,7 +20,7 @@ export class ReplyTracker {
|
|
|
19
20
|
private readonly pendingTurnContexts: IntercomContext[] = [];
|
|
20
21
|
private currentTurnContext: IntercomContext | null = null;
|
|
21
22
|
|
|
22
|
-
constructor(private readonly askTimeoutMs =
|
|
23
|
+
constructor(private readonly askTimeoutMs = getAskTimeoutMs()) {}
|
|
23
24
|
|
|
24
25
|
recordIncomingMessage(from: SessionInfo, message: Message, receivedAt = Date.now()): IntercomContext {
|
|
25
26
|
const context = { from, message, receivedAt };
|
|
@@ -48,31 +49,39 @@ export class ReplyTracker {
|
|
|
48
49
|
this.currentTurnContext = null;
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
resolveReplyTarget(options: { to?: string }, now = Date.now()): IntercomContext {
|
|
52
|
+
resolveReplyTarget(options: { to?: string; replyTo?: string }, now = Date.now()): IntercomContext {
|
|
52
53
|
this.pruneExpired(now);
|
|
53
54
|
|
|
54
|
-
if (
|
|
55
|
-
|
|
55
|
+
if (options.replyTo) {
|
|
56
|
+
const target = this.pendingAsks.get(options.replyTo);
|
|
57
|
+
if (!target) {
|
|
58
|
+
throw new Error(`No pending ask with message ID "${options.replyTo}"`);
|
|
59
|
+
}
|
|
60
|
+
if (options.to && !matchesPendingSender(target, options.to)) {
|
|
61
|
+
throw new Error(`Pending ask "${options.replyTo}" is not from "${options.to}"`);
|
|
62
|
+
}
|
|
63
|
+
return target;
|
|
56
64
|
}
|
|
57
65
|
|
|
58
66
|
const pending = Array.from(this.pendingAsks.values());
|
|
59
|
-
if (pending.length === 1) {
|
|
60
|
-
return pending[0]!;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
67
|
if (options.to) {
|
|
64
68
|
const matches = pending.filter((context) => matchesPendingSender(context, options.to!));
|
|
65
69
|
if (matches.length === 1) {
|
|
66
70
|
return matches[0]!;
|
|
67
71
|
}
|
|
68
72
|
if (matches.length > 1) {
|
|
69
|
-
throw new Error(`Multiple pending asks from
|
|
70
|
-
}
|
|
71
|
-
if (pending.length > 1) {
|
|
72
|
-
throw new Error(`No pending ask from \"${options.to}\"`);
|
|
73
|
+
throw new Error(`Multiple pending asks from "${options.to}" β use the sender session ID instead.`);
|
|
73
74
|
}
|
|
75
|
+
throw new Error(`No pending ask from "${options.to}"`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (this.currentTurnContext) {
|
|
79
|
+
return this.currentTurnContext;
|
|
74
80
|
}
|
|
75
81
|
|
|
82
|
+
if (pending.length === 1) {
|
|
83
|
+
return pending[0]!;
|
|
84
|
+
}
|
|
76
85
|
if (pending.length === 0) {
|
|
77
86
|
throw new Error("No active intercom context to reply to");
|
|
78
87
|
}
|
|
@@ -81,7 +90,16 @@ export class ReplyTracker {
|
|
|
81
90
|
}
|
|
82
91
|
|
|
83
92
|
markReplied(replyTo: string): void {
|
|
93
|
+
this.dismissPendingAsk(replyTo);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
dismissPendingAsk(replyTo: string): void {
|
|
84
97
|
this.pendingAsks.delete(replyTo);
|
|
98
|
+
for (let index = this.pendingTurnContexts.length - 1; index >= 0; index -= 1) {
|
|
99
|
+
if (this.pendingTurnContexts[index]?.message.id === replyTo) {
|
|
100
|
+
this.pendingTurnContexts.splice(index, 1);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
85
103
|
if (this.currentTurnContext?.message.id === replyTo) {
|
|
86
104
|
this.currentTurnContext = null;
|
|
87
105
|
}
|
|
@@ -95,7 +113,7 @@ export class ReplyTracker {
|
|
|
95
113
|
private pruneExpired(now: number): void {
|
|
96
114
|
for (const [messageId, context] of this.pendingAsks) {
|
|
97
115
|
if (now - context.receivedAt > this.askTimeoutMs) {
|
|
98
|
-
this.
|
|
116
|
+
this.dismissPendingAsk(messageId);
|
|
99
117
|
}
|
|
100
118
|
}
|
|
101
119
|
}
|
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
name: pi-intercom
|
|
3
3
|
description: |
|
|
4
4
|
Streamline session-to-session coordination with pi-intercom. Send messages,
|
|
5
|
-
delegate tasks, and coordinate work across multiple
|
|
5
|
+
delegate tasks, and coordinate work across multiple Selesai sessions on the same
|
|
6
6
|
machine. Use for planner-worker workflows, cross-session context sharing,
|
|
7
7
|
and real-time collaboration between sessions.
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
-
#
|
|
10
|
+
# Selesai Intercom Skill
|
|
11
11
|
|
|
12
|
-
Use this skill when you need to coordinate work across multiple
|
|
13
|
-
running on the same machine.
|
|
12
|
+
Use this skill when you need to coordinate work across multiple Selesai sessions
|
|
13
|
+
running on the same machine. pi-intercom enables direct 1:1 messaging between
|
|
14
14
|
sessions for delegation, context sharing, and collaborative workflows.
|
|
15
15
|
|
|
16
16
|
When you are supervising `pi-subagents`, delegated child agents can escalate to
|
|
@@ -156,8 +156,8 @@ This works because `reply` resolves the correct sender and message ID automatica
|
|
|
156
156
|
|
|
157
157
|
| Type | What it means | How to respond |
|
|
158
158
|
|------|---------------|----------------|
|
|
159
|
-
| `need_decision` | Subagent is blocked and waiting for your answer.
|
|
160
|
-
| `interview_request` | Subagent needs multiple structured answers in one blocking exchange.
|
|
159
|
+
| `need_decision` | Subagent is blocked and waiting for your answer. Uses the shared ask timeout: 10 minutes by default, configurable with `PI_INTERCOM_ASK_TIMEOUT_MS`. | Reply promptly with a clear decision. If you need more context, ask follow-up questions via `reply`. |
|
|
160
|
+
| `interview_request` | Subagent needs multiple structured answers in one blocking exchange. Uses the shared ask timeout: 10 minutes by default, configurable with `PI_INTERCOM_ASK_TIMEOUT_MS`. | Reply with plain JSON or a fenced `json` block using the provided `{ "responses": [...] }` shape. |
|
|
161
161
|
| `progress_update` | Subagent is sharing meaningful progress or a plan-changing discovery. Not blocking. | Read and acknowledge. No reply required unless you want to redirect. |
|
|
162
162
|
|
|
163
163
|
**When a subagent asks:**
|
|
@@ -197,7 +197,7 @@ it as a `contact_supervisor` escalation.
|
|
|
197
197
|
| Action | Behavior | Use When |
|
|
198
198
|
|--------|----------|----------|
|
|
199
199
|
| `send` | Fire-and-forget | You don't need a response |
|
|
200
|
-
| `ask` | Blocks until reply (10 min
|
|
200
|
+
| `ask` | Blocks until reply (10 min default, configurable with `PI_INTERCOM_ASK_TIMEOUT_MS`) | You need an answer to continue |
|
|
201
201
|
| `reply` | Responds to the active or pending inbound ask | You were asked something and need to answer naturally |
|
|
202
202
|
| `pending` | Lists unresolved inbound asks | You need to see who is waiting before replying |
|
|
203
203
|
| `list` | Returns all sessions with live status | You need to discover targets or choose an idle peer |
|
|
@@ -301,7 +301,7 @@ If neither `cmux` nor `tmux` is available, skip this path and use normal `interc
|
|
|
301
301
|
|
|
302
302
|
### `ask` Limitations
|
|
303
303
|
|
|
304
|
-
- **
|
|
304
|
+
- **Configurable timeout**: If no reply arrives before the shared ask timeout, the ask fails. The default is 10 minutes; set `PI_INTERCOM_ASK_TIMEOUT_MS` to a positive millisecond value to change it.
|
|
305
305
|
- **One at a time**: Cannot have multiple pending asks from the same session
|
|
306
306
|
- **Cannot self-target**: A session cannot ask itself
|
|
307
307
|
|
|
@@ -402,11 +402,13 @@ if (!result.delivered) {
|
|
|
402
402
|
await intercom({ action: "list" });
|
|
403
403
|
}
|
|
404
404
|
```
|
|
405
|
+
Replies to recently disconnected named senders can be queued by the broker and delivered if that sender reconnects with the same name. New sends still need a known live or recently disconnected target.
|
|
405
406
|
|
|
406
|
-
**Ask timeout
|
|
407
|
+
**Ask timeout**
|
|
407
408
|
```typescript
|
|
408
409
|
// The ask will reject with a timeout error
|
|
409
|
-
//
|
|
410
|
+
// Default: 10 minutes
|
|
411
|
+
// Override: set PI_INTERCOM_ASK_TIMEOUT_MS to a positive millisecond value
|
|
410
412
|
// For longer tasks, use send + follow-up ask pattern
|
|
411
413
|
```
|
|
412
414
|
|
|
@@ -490,7 +492,7 @@ intercom({ action: "send", to: "planner", message: "Task-3 complete. All done."
|
|
|
490
492
|
### Long-Running Task with Checkpoints
|
|
491
493
|
|
|
492
494
|
```typescript
|
|
493
|
-
// For tasks that might exceed
|
|
495
|
+
// For tasks that might exceed the ask timeout, use send + periodic asks
|
|
494
496
|
|
|
495
497
|
// 1. Initial send with full context
|
|
496
498
|
intercom({
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { stripVTControlCharacters } from "node:util";
|
|
4
|
+
|
|
5
|
+
import { visibleWidth } from "@earendil-works/pi-tui";
|
|
6
|
+
import { InlineMessageComponent } from "../ui/inline-message.ts";
|
|
7
|
+
import type { Message, SessionInfo } from "../types.ts";
|
|
8
|
+
|
|
9
|
+
const theme = {
|
|
10
|
+
fg(_name: string, text: string): string {
|
|
11
|
+
return text;
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const from: SessionInfo = {
|
|
16
|
+
id: "session-12345678",
|
|
17
|
+
name: "sender",
|
|
18
|
+
cwd: "/tmp/project",
|
|
19
|
+
model: "model",
|
|
20
|
+
pid: 1,
|
|
21
|
+
startedAt: 0,
|
|
22
|
+
lastActivity: 0,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const message: Message = {
|
|
26
|
+
id: "message-1",
|
|
27
|
+
timestamp: 0,
|
|
28
|
+
content: {
|
|
29
|
+
text: "This is a long message that should use the available terminal width instead of a narrow fixed card.",
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
test("inline intercom messages render at the available terminal width", () => {
|
|
34
|
+
const component = new InlineMessageComponent(from, message, theme as any);
|
|
35
|
+
|
|
36
|
+
const lines = component.render(120);
|
|
37
|
+
|
|
38
|
+
assert.ok(lines.length > 0);
|
|
39
|
+
for (const line of lines) assert.equal(visibleWidth(line), 120);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("expanded inline intercom messages show the full body without collapse controls", () => {
|
|
43
|
+
const component = new InlineMessageComponent(from, message, theme as any, "intercom({ action: \"reply\", message: \"...\" })");
|
|
44
|
+
|
|
45
|
+
const rendered = component.render(100).join("\n");
|
|
46
|
+
|
|
47
|
+
assert.match(rendered, /available terminal width/);
|
|
48
|
+
assert.match(rendered, /narrow fixed/);
|
|
49
|
+
assert.match(rendered, /card/);
|
|
50
|
+
assert.match(rendered, /To reply: intercom/);
|
|
51
|
+
assert.doesNotMatch(rendered, /Ctrl\+O/);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("collapsed inline intercom messages keep preview, reply hint, and expand key visible", () => {
|
|
55
|
+
const component = new InlineMessageComponent(
|
|
56
|
+
from,
|
|
57
|
+
{
|
|
58
|
+
...message,
|
|
59
|
+
content: {
|
|
60
|
+
text: "Alpha beta gamma delta epsilon zeta eta theta iota kappa lambda mu. This tail should only appear when expanded because the collapsed preview is intentionally brief.",
|
|
61
|
+
attachments: [{ type: "snippet", name: "note.txt", content: "important details" }],
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
theme as any,
|
|
65
|
+
"intercom({ action: \"reply\", message: \"...\" })",
|
|
66
|
+
undefined,
|
|
67
|
+
true,
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const lines = component.render(120);
|
|
71
|
+
const rendered = lines.join("\n");
|
|
72
|
+
|
|
73
|
+
assert.equal(lines.length, 4);
|
|
74
|
+
for (const line of lines) assert.equal(visibleWidth(line), 120);
|
|
75
|
+
assert.match(rendered, /Alpha beta gamma/);
|
|
76
|
+
assert.doesNotMatch(rendered, /intentionally brief/);
|
|
77
|
+
assert.match(rendered, /To reply: intercom/);
|
|
78
|
+
assert.match(rendered, /Ctrl\+O/);
|
|
79
|
+
assert.match(rendered, /1 attachment/);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const roleCodes = {
|
|
83
|
+
accent: 31,
|
|
84
|
+
toolTitle: 32,
|
|
85
|
+
text: 33,
|
|
86
|
+
muted: 34,
|
|
87
|
+
dim: 35,
|
|
88
|
+
} as const;
|
|
89
|
+
|
|
90
|
+
function styledTheme(calls: string[] = []) {
|
|
91
|
+
return {
|
|
92
|
+
fg(name: keyof typeof roleCodes, text: string): string {
|
|
93
|
+
calls.push(name);
|
|
94
|
+
return `\u001b[${roleCodes[name]}m${text}\u001b[0m`;
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
test("inline message colors follow the tool-title, text, muted-border, and dim-metadata hierarchy", () => {
|
|
100
|
+
const calls: string[] = [];
|
|
101
|
+
const component = new InlineMessageComponent(
|
|
102
|
+
from,
|
|
103
|
+
{
|
|
104
|
+
...message,
|
|
105
|
+
replyTo: "parent-message-id",
|
|
106
|
+
content: {
|
|
107
|
+
text: "Body copy",
|
|
108
|
+
attachments: [{ type: "snippet", name: "note.txt", content: "details" }],
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
styledTheme(calls) as any,
|
|
112
|
+
"intercom reply",
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const lines = component.render(72);
|
|
116
|
+
const rendered = lines.join("\n");
|
|
117
|
+
|
|
118
|
+
assert.match(lines[0], /^\u001b\[34mβ\u001b\[0m\u001b\[32m π¨ From:/);
|
|
119
|
+
assert.match(rendered, /\u001b\[34mβ\u001b\[0m\u001b\[33mBody copy\u001b\[0m/);
|
|
120
|
+
assert.match(rendered, /\u001b\[35m β© To reply: intercom reply\u001b\[0m/);
|
|
121
|
+
assert.match(rendered, /\u001b\[35m π note\.txt\u001b\[0m/);
|
|
122
|
+
assert.match(rendered, /\u001b\[35m β³ Reply to parent-m\u001b\[0m/);
|
|
123
|
+
assert.match(lines.at(-1)!, /^\u001b\[34mβ°β+β―\u001b\[0m$/);
|
|
124
|
+
assert.ok(calls.includes("toolTitle"));
|
|
125
|
+
assert.ok(calls.includes("text"));
|
|
126
|
+
assert.ok(calls.includes("muted"));
|
|
127
|
+
assert.ok(calls.includes("dim"));
|
|
128
|
+
assert.ok(!calls.includes("accent"));
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("collapsed inline messages preserve the same hierarchy without accent", () => {
|
|
132
|
+
const calls: string[] = [];
|
|
133
|
+
const component = new InlineMessageComponent(from, message, styledTheme(calls) as any, "intercom reply", undefined, true);
|
|
134
|
+
|
|
135
|
+
const rendered = component.render(72).join("\n");
|
|
136
|
+
|
|
137
|
+
assert.match(rendered, /\u001b\[32m π¨ From:/);
|
|
138
|
+
assert.match(rendered, /\u001b\[33mThis is a long message/);
|
|
139
|
+
assert.match(rendered, /\u001b\[35m β© To reply:/);
|
|
140
|
+
assert.ok(!calls.includes("accent"));
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test("semantic styling remains ANSI- and Unicode-width safe", () => {
|
|
144
|
+
const unicodeFrom = { ...from, name: "ιδΏ‘θ
π°οΈ", cwd: "/tmp/θ¨η»" };
|
|
145
|
+
const unicodeMessage = {
|
|
146
|
+
...message,
|
|
147
|
+
content: { text: "\u001b[36mθ²δ»γζ¬ζ\u001b[0m with emoji π§ͺ and a long Unicode tail θ¨η»θ¨η»θ¨η»" },
|
|
148
|
+
};
|
|
149
|
+
const component = new InlineMessageComponent(unicodeFrom, unicodeMessage, styledTheme() as any, "θΏδΏ‘ π¨");
|
|
150
|
+
|
|
151
|
+
for (const width of [18, 31, 52]) {
|
|
152
|
+
const lines = component.render(width);
|
|
153
|
+
assert.ok(lines.length > 0);
|
|
154
|
+
for (const line of lines) {
|
|
155
|
+
assert.equal(visibleWidth(line), width, `${width}: ${stripVTControlCharacters(line)}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("inline messages pick up mutable theme proxy changes on rerender", () => {
|
|
161
|
+
const palette: Record<string, number> = { ...roleCodes };
|
|
162
|
+
const mutableTheme = new Proxy(
|
|
163
|
+
{},
|
|
164
|
+
{
|
|
165
|
+
get(_target, property) {
|
|
166
|
+
if (property !== "fg") return undefined;
|
|
167
|
+
return (name: string, text: string) => `\u001b[${palette[name]}m${text}\u001b[0m`;
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
);
|
|
171
|
+
const component = new InlineMessageComponent(from, message, mutableTheme as any);
|
|
172
|
+
|
|
173
|
+
const before = component.render(72).join("\n");
|
|
174
|
+
palette.toolTitle = 96;
|
|
175
|
+
palette.text = 97;
|
|
176
|
+
palette.muted = 90;
|
|
177
|
+
const after = component.render(72).join("\n");
|
|
178
|
+
|
|
179
|
+
assert.match(before, /\u001b\[32m π¨ From:/);
|
|
180
|
+
assert.match(after, /\u001b\[96m π¨ From:/);
|
|
181
|
+
assert.match(after, /\u001b\[97mThis is a long message/);
|
|
182
|
+
assert.match(after, /\u001b\[90mβ/);
|
|
183
|
+
assert.notEqual(before, after);
|
|
184
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
|
|
4
|
+
import { visibleWidth } from "@earendil-works/pi-tui";
|
|
5
|
+
import { ComposeOverlay } from "../ui/compose.ts";
|
|
6
|
+
import { SessionListOverlay } from "../ui/session-list.ts";
|
|
7
|
+
import type { SessionInfo } from "../types.ts";
|
|
8
|
+
|
|
9
|
+
const theme = {
|
|
10
|
+
fg(_name: string, text: string): string {
|
|
11
|
+
return text;
|
|
12
|
+
},
|
|
13
|
+
bold(text: string): string {
|
|
14
|
+
return text;
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const keybindings = {
|
|
19
|
+
matches(): boolean {
|
|
20
|
+
return false;
|
|
21
|
+
},
|
|
22
|
+
getKeys(id: string): string[] {
|
|
23
|
+
return id.includes("confirm") ? ["enter"] : ["escape", "ctrl+c"];
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const session: SessionInfo = {
|
|
28
|
+
id: "session-12345678",
|
|
29
|
+
name: "subagent-chat-019ecaf6",
|
|
30
|
+
cwd: "/Users/envvar/.config/ghostty",
|
|
31
|
+
model: "bsy-deepseek-v4-pro",
|
|
32
|
+
pid: 1,
|
|
33
|
+
startedAt: 0,
|
|
34
|
+
lastActivity: 0,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
function assertLineWidths(label: string, lines: string[], expectedWidth: number): void {
|
|
38
|
+
assert.ok(lines.length > 0, `${label} should render lines`);
|
|
39
|
+
for (const [index, line] of lines.entries()) {
|
|
40
|
+
assert.equal(visibleWidth(line), expectedWidth, `${label} line ${index} should match overlay width`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
test("compose overlay renders lines at the declared overlay width", () => {
|
|
45
|
+
const overlay = new ComposeOverlay(
|
|
46
|
+
{ requestRender() {} } as any,
|
|
47
|
+
theme as any,
|
|
48
|
+
keybindings as any,
|
|
49
|
+
session,
|
|
50
|
+
"subagent-chat-019ecaf6",
|
|
51
|
+
{ send: async () => ({ delivered: true, id: "message-1" }) } as any,
|
|
52
|
+
() => {},
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
for (const width of [1, 2, 20, 40, 72]) {
|
|
56
|
+
assertLineWidths("compose overlay", overlay.render(width), width);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("session list overlay renders lines at the declared overlay width", () => {
|
|
61
|
+
const overlay = new SessionListOverlay(theme as any, keybindings as any, session, [session], () => {});
|
|
62
|
+
|
|
63
|
+
for (const width of [1, 2, 20, 50, 88]) {
|
|
64
|
+
assertLineWidths("session list overlay", overlay.render(width), width);
|
|
65
|
+
}
|
|
66
|
+
});
|