@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,15 +1,25 @@
|
|
|
1
1
|
import { spawn } from "child_process";
|
|
2
|
-
import { existsSync,
|
|
3
|
-
import { createRequire } from "module";
|
|
2
|
+
import { existsSync, readFileSync, unlinkSync, writeFileSync } from "fs";
|
|
4
3
|
import { join, dirname } from "path";
|
|
5
4
|
import { fileURLToPath } from "url";
|
|
5
|
+
import { createRequire } from "module";
|
|
6
6
|
import net from "net";
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
import { randomUUID } from "crypto";
|
|
8
|
+
import { createMessageReader, writeMessage } from "./framing.ts";
|
|
9
|
+
import {
|
|
10
|
+
ensureIntercomRuntimeDir,
|
|
11
|
+
getAgentDirPath,
|
|
12
|
+
getBrokerConnectTarget,
|
|
13
|
+
getIntercomDirPath,
|
|
14
|
+
INTERCOM_PROTOCOL_NAME,
|
|
15
|
+
INTERCOM_PROTOCOL_VERSION,
|
|
16
|
+
INTERCOM_RUNTIME_FILE_MODE,
|
|
17
|
+
restrictIntercomRuntimeFile,
|
|
18
|
+
type BrokerConnectTarget,
|
|
19
|
+
} from "./paths.ts";
|
|
20
|
+
|
|
21
|
+
const INTERCOM_DIR = getIntercomDirPath();
|
|
11
22
|
const EXTENSION_DIR = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
12
|
-
const BROKER_SOCKET = getBrokerSocketPath();
|
|
13
23
|
const BROKER_PID = join(INTERCOM_DIR, "broker.pid");
|
|
14
24
|
const BROKER_SPAWN_LOCK = join(INTERCOM_DIR, "broker.spawn.lock");
|
|
15
25
|
|
|
@@ -31,8 +41,19 @@ function sleep(ms: number): Promise<void> {
|
|
|
31
41
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
32
42
|
}
|
|
33
43
|
|
|
34
|
-
export function getTsxCliPath(): string {
|
|
35
|
-
|
|
44
|
+
export function getTsxCliPath(extensionDir: string = EXTENSION_DIR): string {
|
|
45
|
+
// Resolve tsx via Node's module resolution so it works regardless of whether
|
|
46
|
+
// tsx is bundled under extensionDir/node_modules or hoisted to a workspace
|
|
47
|
+
// root by npm. We resolve the tsx package main entry (its "exports" field
|
|
48
|
+
// does not expose ./dist/cli.mjs as a subpath) and then locate cli.mjs next
|
|
49
|
+
// to it. Falls back to the legacy relative path if resolution fails.
|
|
50
|
+
try {
|
|
51
|
+
const requireFromExtension = createRequire(import.meta.url);
|
|
52
|
+
const tsxMain = requireFromExtension.resolve("tsx");
|
|
53
|
+
return join(dirname(tsxMain), "cli.mjs");
|
|
54
|
+
} catch {
|
|
55
|
+
return join(extensionDir, "node_modules", "tsx", "dist", "cli.mjs");
|
|
56
|
+
}
|
|
36
57
|
}
|
|
37
58
|
|
|
38
59
|
function quoteWindowsArg(value: string): string {
|
|
@@ -50,6 +71,13 @@ function usesDefaultBrokerCommand(brokerCommand: string, brokerArgs: string[]):
|
|
|
50
71
|
&& brokerArgs[1] === "tsx";
|
|
51
72
|
}
|
|
52
73
|
|
|
74
|
+
function getNodeCommand(nodePath: string): string {
|
|
75
|
+
const executableName = nodePath.split(/[\\/]/).pop();
|
|
76
|
+
return executableName && /^node(?:js)?(?:\.exe)?$/i.test(executableName)
|
|
77
|
+
? nodePath
|
|
78
|
+
: "node";
|
|
79
|
+
}
|
|
80
|
+
|
|
53
81
|
export function getWindowsBrokerCommandLine(
|
|
54
82
|
brokerPath: string,
|
|
55
83
|
extensionDir: string = EXTENSION_DIR,
|
|
@@ -58,7 +86,7 @@ export function getWindowsBrokerCommandLine(
|
|
|
58
86
|
brokerArgs: string[] = ["--no-install", "tsx"],
|
|
59
87
|
): string {
|
|
60
88
|
if (usesDefaultBrokerCommand(brokerCommand, brokerArgs)) {
|
|
61
|
-
return [quoteWindowsArg(nodePath), quoteWindowsArg(getTsxCliPath()), quoteWindowsArg(brokerPath)].join(" ");
|
|
89
|
+
return [quoteWindowsArg(getNodeCommand(nodePath)), quoteWindowsArg(getTsxCliPath(extensionDir)), quoteWindowsArg(brokerPath)].join(" ");
|
|
62
90
|
}
|
|
63
91
|
|
|
64
92
|
return [quoteWindowsArg(brokerCommand), ...brokerArgs.map(quoteWindowsArg), quoteWindowsArg(brokerPath)].join(" ");
|
|
@@ -73,12 +101,27 @@ export function getWindowsHiddenLauncherScript(commandLine: string): string {
|
|
|
73
101
|
].join("\r\n");
|
|
74
102
|
}
|
|
75
103
|
|
|
104
|
+
export function isBrokerHealthOkMessage(message: unknown, requestId: string): boolean {
|
|
105
|
+
if (typeof message !== "object" || message === null || !("type" in message)) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
const response = message as Record<string, unknown>;
|
|
109
|
+
return response.type === "health_ok"
|
|
110
|
+
&& response.requestId === requestId
|
|
111
|
+
&& response.protocol === INTERCOM_PROTOCOL_NAME
|
|
112
|
+
&& response.version === INTERCOM_PROTOCOL_VERSION;
|
|
113
|
+
}
|
|
114
|
+
|
|
76
115
|
function writeWindowsHiddenLauncher(
|
|
77
116
|
commandLine: string,
|
|
78
117
|
launcherPath: string = getWindowsHiddenLauncherPath(),
|
|
79
118
|
): string {
|
|
80
|
-
|
|
81
|
-
writeFileSync(launcherPath, getWindowsHiddenLauncherScript(commandLine),
|
|
119
|
+
ensureIntercomRuntimeDir(dirname(launcherPath));
|
|
120
|
+
writeFileSync(launcherPath, getWindowsHiddenLauncherScript(commandLine), {
|
|
121
|
+
encoding: "utf-8",
|
|
122
|
+
mode: INTERCOM_RUNTIME_FILE_MODE,
|
|
123
|
+
});
|
|
124
|
+
restrictIntercomRuntimeFile(launcherPath);
|
|
82
125
|
return launcherPath;
|
|
83
126
|
}
|
|
84
127
|
|
|
@@ -102,6 +145,14 @@ export function getBrokerLaunchSpec(
|
|
|
102
145
|
};
|
|
103
146
|
}
|
|
104
147
|
|
|
148
|
+
if (usesDefaultBrokerCommand(brokerCommand, brokerArgs)) {
|
|
149
|
+
return {
|
|
150
|
+
kind: "direct",
|
|
151
|
+
command: getNodeCommand(nodePath),
|
|
152
|
+
args: [getTsxCliPath(extensionDir), brokerPath],
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
105
156
|
return {
|
|
106
157
|
kind: "direct",
|
|
107
158
|
command: brokerCommand,
|
|
@@ -109,7 +160,10 @@ export function getBrokerLaunchSpec(
|
|
|
109
160
|
};
|
|
110
161
|
}
|
|
111
162
|
|
|
112
|
-
export function getBrokerSpawnOptions(
|
|
163
|
+
export function getBrokerSpawnOptions(
|
|
164
|
+
extensionDir: string = EXTENSION_DIR,
|
|
165
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
166
|
+
): {
|
|
113
167
|
detached: true;
|
|
114
168
|
stdio: "ignore";
|
|
115
169
|
cwd: string;
|
|
@@ -120,7 +174,7 @@ export function getBrokerSpawnOptions(extensionDir: string = EXTENSION_DIR): {
|
|
|
120
174
|
detached: true,
|
|
121
175
|
stdio: "ignore",
|
|
122
176
|
cwd: extensionDir,
|
|
123
|
-
env: { ...
|
|
177
|
+
env: { ...env, SELESAI_CODING_AGENT_DIR: getAgentDirPath(env), NODE_NO_WARNINGS: "1" },
|
|
124
178
|
windowsHide: true,
|
|
125
179
|
};
|
|
126
180
|
}
|
|
@@ -130,7 +184,7 @@ function toError(error: unknown): Error {
|
|
|
130
184
|
}
|
|
131
185
|
|
|
132
186
|
export async function spawnBrokerIfNeeded(brokerCommand: string, brokerArgs: string[]): Promise<void> {
|
|
133
|
-
|
|
187
|
+
ensureIntercomRuntimeDir(INTERCOM_DIR);
|
|
134
188
|
|
|
135
189
|
if (await isBrokerRunning()) {
|
|
136
190
|
return;
|
|
@@ -211,29 +265,57 @@ async function isBrokerRunning(): Promise<boolean> {
|
|
|
211
265
|
}
|
|
212
266
|
}
|
|
213
267
|
|
|
268
|
+
function connectToBrokerTarget(target: BrokerConnectTarget): net.Socket {
|
|
269
|
+
return typeof target === "string"
|
|
270
|
+
? net.connect(target)
|
|
271
|
+
: net.connect({ host: target.host, port: target.port });
|
|
272
|
+
}
|
|
273
|
+
|
|
214
274
|
function checkSocketConnectable(): Promise<boolean> {
|
|
215
275
|
return new Promise((resolve) => {
|
|
216
|
-
|
|
276
|
+
let target: BrokerConnectTarget;
|
|
277
|
+
try {
|
|
278
|
+
target = getBrokerConnectTarget();
|
|
279
|
+
} catch {
|
|
280
|
+
resolve(false);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const socket = connectToBrokerTarget(target);
|
|
285
|
+
const requestId = randomUUID();
|
|
286
|
+
const expectedStateId = typeof target === "string" ? undefined : target.stateId;
|
|
287
|
+
let settled = false;
|
|
217
288
|
const finish = (isConnected: boolean) => {
|
|
289
|
+
if (settled) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
settled = true;
|
|
218
293
|
clearTimeout(timeout);
|
|
219
294
|
socket.off("connect", onConnect);
|
|
220
295
|
socket.off("error", onError);
|
|
296
|
+
socket.off("data", reader);
|
|
297
|
+
socket.destroy();
|
|
221
298
|
resolve(isConnected);
|
|
222
299
|
};
|
|
223
300
|
const onConnect = () => {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
301
|
+
try {
|
|
302
|
+
writeMessage(socket, {
|
|
303
|
+
type: "health",
|
|
304
|
+
requestId,
|
|
305
|
+
...(expectedStateId ? { stateId: expectedStateId } : {}),
|
|
306
|
+
});
|
|
307
|
+
} catch {
|
|
308
|
+
finish(false);
|
|
309
|
+
}
|
|
230
310
|
};
|
|
311
|
+
const onError = () => finish(false);
|
|
312
|
+
const reader = createMessageReader((message) => {
|
|
313
|
+
finish(isBrokerHealthOkMessage(message, requestId));
|
|
314
|
+
}, () => finish(false));
|
|
231
315
|
socket.on("connect", onConnect);
|
|
232
316
|
socket.on("error", onError);
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
finish(false);
|
|
236
|
-
}, 1000);
|
|
317
|
+
socket.on("data", reader);
|
|
318
|
+
const timeout = setTimeout(() => finish(false), 1000);
|
|
237
319
|
});
|
|
238
320
|
}
|
|
239
321
|
|
|
@@ -241,7 +323,11 @@ function acquireSpawnLock(): boolean {
|
|
|
241
323
|
const maxRetries = 5;
|
|
242
324
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
243
325
|
try {
|
|
244
|
-
writeFileSync(BROKER_SPAWN_LOCK, `${process.pid}\n${Date.now()}\n`, {
|
|
326
|
+
writeFileSync(BROKER_SPAWN_LOCK, `${process.pid}\n${Date.now()}\n`, {
|
|
327
|
+
flag: "wx",
|
|
328
|
+
mode: INTERCOM_RUNTIME_FILE_MODE,
|
|
329
|
+
});
|
|
330
|
+
restrictIntercomRuntimeFile(BROKER_SPAWN_LOCK);
|
|
245
331
|
return true;
|
|
246
332
|
} catch (error) {
|
|
247
333
|
if (!(error instanceof Error) || (error as NodeJS.ErrnoException).code !== "EEXIST") {
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { getConfigPath, loadConfig } from "./config.ts";
|
|
7
|
+
|
|
8
|
+
async function withAgentDir<T>(agentDir: string, fn: () => T | Promise<T>): Promise<T> {
|
|
9
|
+
const previousAgentDir = process.env.SELESAI_CODING_AGENT_DIR;
|
|
10
|
+
process.env.SELESAI_CODING_AGENT_DIR = agentDir;
|
|
11
|
+
try {
|
|
12
|
+
return await fn();
|
|
13
|
+
} finally {
|
|
14
|
+
if (previousAgentDir === undefined) delete process.env.SELESAI_CODING_AGENT_DIR;
|
|
15
|
+
else process.env.SELESAI_CODING_AGENT_DIR = previousAgentDir;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
test("getConfigPath uses the centralized intercom runtime directory", () => {
|
|
20
|
+
assert.equal(getConfigPath("/tmp/pi-agent/intercom"), join("/tmp/pi-agent", "intercom", "config.json"));
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("loadConfig reads config below SELESAI_CODING_AGENT_DIR", async () => {
|
|
24
|
+
const root = mkdtempSync(join(tmpdir(), "pi-intercom-config-"));
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const intercomDir = join(root, "intercom");
|
|
28
|
+
mkdirSync(intercomDir, { recursive: true });
|
|
29
|
+
writeFileSync(join(intercomDir, "config.json"), JSON.stringify({ status: "platform-test" }));
|
|
30
|
+
|
|
31
|
+
await withAgentDir(root, () => {
|
|
32
|
+
assert.equal(loadConfig().status, "platform-test");
|
|
33
|
+
});
|
|
34
|
+
} finally {
|
|
35
|
+
rmSync(root, { recursive: true, force: true });
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("loadConfig defaults inboundTrigger to current auto-trigger behavior", async () => {
|
|
40
|
+
const root = mkdtempSync(join(tmpdir(), "pi-intercom-config-"));
|
|
41
|
+
try {
|
|
42
|
+
await withAgentDir(root, () => {
|
|
43
|
+
assert.equal(loadConfig().inboundTrigger, "always");
|
|
44
|
+
});
|
|
45
|
+
} finally {
|
|
46
|
+
rmSync(root, { recursive: true, force: true });
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("loadConfig accepts inboundTrigger replies policy", async () => {
|
|
51
|
+
const root = mkdtempSync(join(tmpdir(), "pi-intercom-config-"));
|
|
52
|
+
try {
|
|
53
|
+
mkdirSync(join(root, "intercom"), { recursive: true });
|
|
54
|
+
writeFileSync(join(root, "intercom", "config.json"), JSON.stringify({ inboundTrigger: "replies" }));
|
|
55
|
+
await withAgentDir(root, () => {
|
|
56
|
+
assert.equal(loadConfig().inboundTrigger, "replies");
|
|
57
|
+
});
|
|
58
|
+
} finally {
|
|
59
|
+
rmSync(root, { recursive: true, force: true });
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("loadConfig accepts a restart-stable intercom id", async () => {
|
|
64
|
+
const root = mkdtempSync(join(tmpdir(), "pi-intercom-config-"));
|
|
65
|
+
try {
|
|
66
|
+
mkdirSync(join(root, "intercom"), { recursive: true });
|
|
67
|
+
writeFileSync(join(root, "intercom", "config.json"), JSON.stringify({ stableId: " pinned-worker " }));
|
|
68
|
+
await withAgentDir(root, () => {
|
|
69
|
+
assert.equal(loadConfig().stableId, "pinned-worker");
|
|
70
|
+
});
|
|
71
|
+
} finally {
|
|
72
|
+
rmSync(root, { recursive: true, force: true });
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("loadConfig rejects invalid inboundTrigger values by failing closed", async () => {
|
|
77
|
+
const root = mkdtempSync(join(tmpdir(), "pi-intercom-config-"));
|
|
78
|
+
try {
|
|
79
|
+
mkdirSync(join(root, "intercom"), { recursive: true });
|
|
80
|
+
writeFileSync(join(root, "intercom", "config.json"), JSON.stringify({ inboundTrigger: "prompt" }));
|
|
81
|
+
const previousError = console.error;
|
|
82
|
+
console.error = () => undefined;
|
|
83
|
+
try {
|
|
84
|
+
await withAgentDir(root, () => {
|
|
85
|
+
assert.equal(loadConfig().inboundTrigger, "never");
|
|
86
|
+
});
|
|
87
|
+
} finally {
|
|
88
|
+
console.error = previousError;
|
|
89
|
+
}
|
|
90
|
+
} finally {
|
|
91
|
+
rmSync(root, { recursive: true, force: true });
|
|
92
|
+
}
|
|
93
|
+
});
|
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "fs";
|
|
2
2
|
import { join } from "path";
|
|
3
|
-
import {
|
|
3
|
+
import { getIntercomDirPath } from "./broker/paths.ts";
|
|
4
|
+
|
|
5
|
+
export const DEFAULT_ASK_TIMEOUT_MS = 10 * 60 * 1000;
|
|
6
|
+
|
|
7
|
+
export function getAskTimeoutMs(): number {
|
|
8
|
+
const raw = process.env.PI_INTERCOM_ASK_TIMEOUT_MS;
|
|
9
|
+
if (raw === undefined || raw.trim() === "") {
|
|
10
|
+
return DEFAULT_ASK_TIMEOUT_MS;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const value = Number(raw);
|
|
14
|
+
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
15
|
+
throw new Error("PI_INTERCOM_ASK_TIMEOUT_MS must be a positive integer number of milliseconds");
|
|
16
|
+
}
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type InboundTriggerPolicy = "always" | "replies" | "never";
|
|
4
21
|
|
|
5
22
|
export interface IntercomConfig {
|
|
6
23
|
/** Broker command used to spawn the broker process (e.g. "npx" or "bun") */
|
|
@@ -12,8 +29,14 @@ export interface IntercomConfig {
|
|
|
12
29
|
/** Require confirmation before non-reply sends from interactive sessions */
|
|
13
30
|
confirmSend: boolean;
|
|
14
31
|
|
|
32
|
+
/** Controls whether inbound broker messages may automatically trigger a model turn */
|
|
33
|
+
inboundTrigger: InboundTriggerPolicy;
|
|
34
|
+
|
|
15
35
|
/** Optional custom status suffix shown after automatic lifecycle status */
|
|
16
36
|
status?: string;
|
|
37
|
+
|
|
38
|
+
/** Optional stable intercom session ID for restart-stable addressing */
|
|
39
|
+
stableId?: string;
|
|
17
40
|
|
|
18
41
|
/** Enable/disable intercom (default: true) */
|
|
19
42
|
enabled: boolean;
|
|
@@ -22,23 +45,27 @@ export interface IntercomConfig {
|
|
|
22
45
|
replyHint: boolean;
|
|
23
46
|
}
|
|
24
47
|
|
|
25
|
-
|
|
48
|
+
export function getConfigPath(intercomDir: string = getIntercomDirPath()): string {
|
|
49
|
+
return join(intercomDir, "config.json");
|
|
50
|
+
}
|
|
26
51
|
|
|
27
52
|
const defaults: IntercomConfig = {
|
|
28
53
|
brokerCommand: "npx",
|
|
29
54
|
brokerArgs: ["--no-install", "tsx"],
|
|
30
55
|
confirmSend: false,
|
|
56
|
+
inboundTrigger: "always",
|
|
31
57
|
enabled: true,
|
|
32
58
|
replyHint: true,
|
|
33
59
|
};
|
|
34
60
|
|
|
35
61
|
export function loadConfig(): IntercomConfig {
|
|
36
|
-
|
|
62
|
+
const configPath = getConfigPath();
|
|
63
|
+
if (!existsSync(configPath)) {
|
|
37
64
|
return { ...defaults };
|
|
38
65
|
}
|
|
39
66
|
|
|
40
67
|
try {
|
|
41
|
-
const raw = readFileSync(
|
|
68
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
42
69
|
const parsed: unknown = JSON.parse(raw);
|
|
43
70
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
44
71
|
throw new Error("Config must be a JSON object");
|
|
@@ -86,6 +113,17 @@ export function loadConfig(): IntercomConfig {
|
|
|
86
113
|
config.enabled = parsedConfig.enabled;
|
|
87
114
|
}
|
|
88
115
|
|
|
116
|
+
if (Object.hasOwn(parsedConfig, "inboundTrigger")) {
|
|
117
|
+
if (
|
|
118
|
+
parsedConfig.inboundTrigger !== "always"
|
|
119
|
+
&& parsedConfig.inboundTrigger !== "replies"
|
|
120
|
+
&& parsedConfig.inboundTrigger !== "never"
|
|
121
|
+
) {
|
|
122
|
+
throw new Error(`"inboundTrigger" must be "always", "replies", or "never"`);
|
|
123
|
+
}
|
|
124
|
+
config.inboundTrigger = parsedConfig.inboundTrigger;
|
|
125
|
+
}
|
|
126
|
+
|
|
89
127
|
if (Object.hasOwn(parsedConfig, "replyHint")) {
|
|
90
128
|
if (typeof parsedConfig.replyHint !== "boolean") {
|
|
91
129
|
throw new Error(`"replyHint" must be a boolean`);
|
|
@@ -100,9 +138,20 @@ export function loadConfig(): IntercomConfig {
|
|
|
100
138
|
config.status = parsedConfig.status;
|
|
101
139
|
}
|
|
102
140
|
|
|
141
|
+
if (Object.hasOwn(parsedConfig, "stableId")) {
|
|
142
|
+
if (typeof parsedConfig.stableId !== "string") {
|
|
143
|
+
throw new Error(`"stableId" must be a string`);
|
|
144
|
+
}
|
|
145
|
+
const stableId = parsedConfig.stableId.trim();
|
|
146
|
+
if (!stableId) {
|
|
147
|
+
throw new Error(`"stableId" must not be empty`);
|
|
148
|
+
}
|
|
149
|
+
config.stableId = stableId;
|
|
150
|
+
}
|
|
151
|
+
|
|
103
152
|
return config;
|
|
104
153
|
} catch (error) {
|
|
105
|
-
console.error(`Failed to load intercom config at ${
|
|
106
|
-
return { ...defaults };
|
|
154
|
+
console.error(`Failed to load intercom config at ${configPath}:`, error);
|
|
155
|
+
return { ...defaults, inboundTrigger: "never" };
|
|
107
156
|
}
|
|
108
157
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdtempSync, rmSync, symlinkSync, mkdirSync, realpathSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { normalizeCwd, sameCwd } from "./cwd.ts";
|
|
7
|
+
|
|
8
|
+
test("sameCwd collapses a trailing slash", () => {
|
|
9
|
+
assert.equal(sameCwd("/usr/local", "/usr/local/"), true);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test("sameCwd collapses lexical '.' and '..' segments", () => {
|
|
13
|
+
assert.equal(sameCwd("/usr/local/../local/./bin", "/usr/local/bin"), true);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("sameCwd treats genuinely different directories as different", () => {
|
|
17
|
+
assert.equal(sameCwd("/usr/local", "/usr/lib"), false);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("sameCwd collapses a symlink to its real target", () => {
|
|
21
|
+
const base = mkdtempSync(join(tmpdir(), "cwd-symlink-"));
|
|
22
|
+
try {
|
|
23
|
+
const real = join(base, "real");
|
|
24
|
+
const link = join(base, "link");
|
|
25
|
+
mkdirSync(real);
|
|
26
|
+
symlinkSync(real, link);
|
|
27
|
+
// Reached via the symlink vs its canonical path — must compare equal.
|
|
28
|
+
assert.equal(sameCwd(link, real), true);
|
|
29
|
+
// And normalizeCwd resolves the link to the real path.
|
|
30
|
+
assert.equal(normalizeCwd(link), realpathSync(real));
|
|
31
|
+
} finally {
|
|
32
|
+
rmSync(base, { recursive: true, force: true });
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("normalizeCwd falls back to the resolved path for a nonexistent dir", () => {
|
|
37
|
+
// realpathSync throws for a missing dir; normalizeCwd must still collapse the
|
|
38
|
+
// lexical variants rather than throw.
|
|
39
|
+
assert.equal(normalizeCwd("/no/such/dir/../dir"), "/no/such/dir");
|
|
40
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { realpathSync } from "node:fs";
|
|
3
|
+
|
|
4
|
+
// Normalize a cwd for same-directory comparison. A raw string match ("a === b")
|
|
5
|
+
// hides genuine same-directory peers when two cwd strings differ only by a
|
|
6
|
+
// trailing slash, a "."/".." segment, or a symlink (e.g. macOS /tmp <->
|
|
7
|
+
// /private/tmp). resolve() collapses the lexical variants; realpathSync()
|
|
8
|
+
// collapses symlinks (best-effort: falls back to the resolved path if the
|
|
9
|
+
// directory no longer exists). Memoized — the set of distinct cwd strings is
|
|
10
|
+
// small and stable, so repeat comparisons are free after warmup.
|
|
11
|
+
const normalizeCache = new Map<string, string>();
|
|
12
|
+
|
|
13
|
+
export function normalizeCwd(cwd: string): string {
|
|
14
|
+
const cached = normalizeCache.get(cwd);
|
|
15
|
+
if (cached !== undefined) {
|
|
16
|
+
return cached;
|
|
17
|
+
}
|
|
18
|
+
const resolved = resolve(cwd);
|
|
19
|
+
let normalized: string;
|
|
20
|
+
try {
|
|
21
|
+
normalized = realpathSync(resolved);
|
|
22
|
+
} catch {
|
|
23
|
+
normalized = resolved;
|
|
24
|
+
}
|
|
25
|
+
normalizeCache.set(cwd, normalized);
|
|
26
|
+
return normalized;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function sameCwd(a: string, b: string): boolean {
|
|
30
|
+
return normalizeCwd(a) === normalizeCwd(b);
|
|
31
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { SessionInfo } from "./types.ts";
|
|
2
|
+
|
|
3
|
+
export const INTERCOM_EXTENSION_REGISTER_EVENT = "intercom:extension-register";
|
|
4
|
+
export const INTERCOM_EXTENSION_REGISTRY_READY_EVENT = "intercom:extension-registry-ready";
|
|
5
|
+
|
|
6
|
+
export interface IntercomExtensionOwner {
|
|
7
|
+
sessionId: string;
|
|
8
|
+
epoch: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface IntercomExtensionState {
|
|
12
|
+
revision: number;
|
|
13
|
+
payload: unknown;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type IntercomExtensionEvent =
|
|
17
|
+
| { type: "connection"; connected: boolean; supported: boolean }
|
|
18
|
+
| { type: "owner"; owner?: IntercomExtensionOwner }
|
|
19
|
+
| { type: "message"; fromSessionId: string; owner?: IntercomExtensionOwner; payload: unknown }
|
|
20
|
+
| { type: "state"; state: IntercomExtensionState }
|
|
21
|
+
| { type: "state_result"; committed: boolean; revision: number; reason?: string }
|
|
22
|
+
| { type: "session_joined"; session: SessionInfo }
|
|
23
|
+
| { type: "session_left"; sessionId: string }
|
|
24
|
+
| { type: "presence_update"; session: SessionInfo };
|
|
25
|
+
|
|
26
|
+
export interface IntercomExtensionChannel {
|
|
27
|
+
readonly namespace: string;
|
|
28
|
+
snapshot(): {
|
|
29
|
+
connected: boolean;
|
|
30
|
+
supported: boolean;
|
|
31
|
+
owner?: IntercomExtensionOwner;
|
|
32
|
+
state?: IntercomExtensionState;
|
|
33
|
+
};
|
|
34
|
+
publish(payload: unknown, options?: { audience?: "owner" | "capable"; ownerOnly?: boolean }): void;
|
|
35
|
+
commitState(payload: unknown, expectedRevision?: number): void;
|
|
36
|
+
listSessions(): Promise<SessionInfo[]>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface IntercomExtensionRegistration {
|
|
40
|
+
namespace: string;
|
|
41
|
+
ownerEligible: boolean;
|
|
42
|
+
onEvent(event: IntercomExtensionEvent): void;
|
|
43
|
+
onReady(channel: IntercomExtensionChannel): void;
|
|
44
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { formatTokenCount, formatContextUsage } from "./format-context.ts";
|
|
4
|
+
import type { SessionInfo } from "./types.ts";
|
|
5
|
+
|
|
6
|
+
const base: SessionInfo = { id: "s1", cwd: "/w", model: "m", pid: 1, startedAt: 0, lastActivity: 0 };
|
|
7
|
+
|
|
8
|
+
test("formatTokenCount compacts thousands", () => {
|
|
9
|
+
assert.equal(formatTokenCount(0), "0");
|
|
10
|
+
assert.equal(formatTokenCount(999), "999");
|
|
11
|
+
assert.equal(formatTokenCount(1432), "1.4k");
|
|
12
|
+
assert.equal(formatTokenCount(144000), "144k");
|
|
13
|
+
assert.equal(formatTokenCount(200000), "200k");
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("formatContextUsage renders percent + token detail when all known", () => {
|
|
17
|
+
assert.equal(
|
|
18
|
+
formatContextUsage({ ...base, contextPct: 72, contextTokens: 144000, contextWindow: 200000 }),
|
|
19
|
+
" · 72% ctx (144k/200k)",
|
|
20
|
+
);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("formatContextUsage shows percent only when token counts are absent", () => {
|
|
24
|
+
assert.equal(formatContextUsage({ ...base, contextPct: 30 }), " · 30% ctx");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("formatContextUsage renders nothing when percent is unknown (never a stale %)", () => {
|
|
28
|
+
assert.equal(formatContextUsage(base), "");
|
|
29
|
+
// Tokens present but no percent (e.g. right after a compaction) still renders nothing.
|
|
30
|
+
assert.equal(formatContextUsage({ ...base, contextTokens: 100, contextWindow: 200 }), "");
|
|
31
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { SessionInfo } from "./types.ts";
|
|
2
|
+
|
|
3
|
+
// Compact token count for display: 1432 -> "1.4k", 144000 -> "144k". Keeps list
|
|
4
|
+
// rows short while staying legible.
|
|
5
|
+
export function formatTokenCount(tokens: number): string {
|
|
6
|
+
if (tokens < 1000) {
|
|
7
|
+
return String(Math.max(0, Math.round(tokens)));
|
|
8
|
+
}
|
|
9
|
+
const k = tokens / 1000;
|
|
10
|
+
const value = k >= 100 ? String(Math.round(k)) : k.toFixed(1).replace(/\.0$/, "");
|
|
11
|
+
return `${value}k`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Render a session's context-window usage to sit beside its model, e.g.
|
|
15
|
+
// " · 72% ctx (144k/200k)". The token detail is appended only when both counts
|
|
16
|
+
// are known. Unknown percent (e.g. right after a compaction, before the next
|
|
17
|
+
// assistant response) renders nothing, so a frozen value is never shown as a
|
|
18
|
+
// stale percentage.
|
|
19
|
+
export function formatContextUsage(session: SessionInfo): string {
|
|
20
|
+
if (typeof session.contextPct !== "number") {
|
|
21
|
+
return "";
|
|
22
|
+
}
|
|
23
|
+
let out = ` · ${session.contextPct}% ctx`;
|
|
24
|
+
if (
|
|
25
|
+
typeof session.contextTokens === "number"
|
|
26
|
+
&& typeof session.contextWindow === "number"
|
|
27
|
+
&& session.contextWindow > 0
|
|
28
|
+
) {
|
|
29
|
+
out += ` (${formatTokenCount(session.contextTokens)}/${formatTokenCount(session.contextWindow)})`;
|
|
30
|
+
}
|
|
31
|
+
return out;
|
|
32
|
+
}
|