@oh-my-pi/pi-coding-agent 17.0.8 → 17.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +87 -1
- package/README.md +1 -1
- package/dist/cli.js +5495 -5021
- package/dist/types/async/job-manager.d.ts +37 -1
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/bench-cli.d.ts +41 -0
- package/dist/types/commands/bench.d.ts +15 -3
- package/dist/types/commands/launch.d.ts +4 -0
- package/dist/types/config/__tests__/model-registry.test.d.ts +1 -0
- package/dist/types/config/model-registry.d.ts +30 -1
- package/dist/types/config/models-config-schema.d.ts +18 -6
- package/dist/types/config/models-config.d.ts +14 -5
- package/dist/types/config/provider-globals.d.ts +2 -2
- package/dist/types/config/service-tier.d.ts +5 -1
- package/dist/types/config/settings-schema.d.ts +350 -50
- package/dist/types/config/settings.d.ts +7 -0
- package/dist/types/dap/client.d.ts +20 -0
- package/dist/types/exec/bash-executor.d.ts +38 -0
- package/dist/types/exec/direnv.d.ts +33 -0
- package/dist/types/export/html/args.d.ts +15 -0
- package/dist/types/export/html/index.d.ts +11 -26
- package/dist/types/export/html/web-palette.d.ts +55 -116
- package/dist/types/extensibility/extensions/loader.d.ts +3 -0
- package/dist/types/extensibility/extensions/types.d.ts +17 -2
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +1 -0
- package/dist/types/extensibility/legacy-pi-tui-shim.d.ts +10 -0
- package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +14 -1
- package/dist/types/internal-urls/registry-helpers.d.ts +11 -0
- package/dist/types/live/controller.d.ts +40 -0
- package/dist/types/live/protocol.d.ts +85 -0
- package/dist/types/live/protocol.test.d.ts +1 -0
- package/dist/types/live/transport.d.ts +48 -0
- package/dist/types/live/visualizer.d.ts +33 -0
- package/dist/types/lsp/client.d.ts +18 -1
- package/dist/types/mcp/config-writer.test.d.ts +1 -0
- package/dist/types/mcp/manager.d.ts +6 -2
- package/dist/types/mcp/oauth-discovery.d.ts +2 -0
- package/dist/types/mcp/render.d.ts +2 -2
- package/dist/types/mcp/smithery-auth.d.ts +1 -1
- package/dist/types/mcp/smithery-auth.test.d.ts +1 -0
- package/dist/types/mcp/tool-bridge.d.ts +7 -3
- package/dist/types/mcp/types.d.ts +6 -0
- package/dist/types/memory-backend/tool-names.d.ts +2 -0
- package/dist/types/modes/components/login-dialog.d.ts +2 -2
- package/dist/types/modes/components/oauth-selector.d.ts +8 -0
- package/dist/types/modes/components/plan-review-overlay.d.ts +26 -0
- package/dist/types/modes/components/settings-defs.d.ts +8 -1
- package/dist/types/modes/components/status-line/component.jj-cache.test.d.ts +1 -0
- package/dist/types/modes/components/tool-execution.d.ts +9 -1
- package/dist/types/modes/controllers/command-controller.d.ts +1 -1
- package/dist/types/modes/controllers/event-controller.d.ts +6 -1
- package/dist/types/modes/controllers/live-command-controller.d.ts +14 -0
- package/dist/types/modes/controllers/mcp-command-controller.d.ts +3 -0
- package/dist/types/modes/interactive-mode.d.ts +5 -0
- package/dist/types/modes/rpc/rpc-client.d.ts +10 -1
- package/dist/types/modes/rpc/rpc-frame.d.ts +16 -0
- package/dist/types/modes/rpc/rpc-messages.d.ts +26 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +40 -0
- package/dist/types/modes/setup-wizard/scenes/sign-in.d.ts +1 -1
- package/dist/types/modes/setup-wizard/scenes/types.d.ts +9 -1
- package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +1 -1
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +3 -1
- package/dist/types/modes/utils/context-usage.d.ts +12 -2
- package/dist/types/plan-mode/plan-files.d.ts +10 -0
- package/dist/types/registry/agent-lifecycle.d.ts +21 -9
- package/dist/types/registry/agent-registry.d.ts +11 -4
- package/dist/types/sdk.d.ts +15 -1
- package/dist/types/secrets/index.d.ts +15 -2
- package/dist/types/secrets/obfuscator.d.ts +113 -15
- package/dist/types/session/acp-permission-gate.d.ts +19 -0
- package/dist/types/session/agent-session-events.d.ts +84 -0
- package/dist/types/session/agent-session-types.d.ts +314 -0
- package/dist/types/session/agent-session.d.ts +120 -750
- package/dist/types/session/async-job-delivery.d.ts +28 -0
- package/dist/types/session/bash-runner.d.ts +75 -0
- package/dist/types/session/blob-store.d.ts +11 -1
- package/dist/types/session/blob-store.test.d.ts +1 -0
- package/dist/types/session/checkpoint-entries.d.ts +28 -0
- package/dist/types/session/eval-runner.d.ts +49 -0
- package/dist/types/session/irc-bridge.d.ts +46 -0
- package/dist/types/session/messages.d.ts +22 -0
- package/dist/types/session/model-controls.d.ts +180 -0
- package/dist/types/session/prewalk.d.ts +46 -0
- package/dist/types/session/queued-messages.d.ts +22 -0
- package/dist/types/session/redis-session-storage.d.ts +1 -0
- package/dist/types/session/retry-fallback-chains.d.ts +74 -0
- package/dist/types/session/role-models.d.ts +15 -0
- package/dist/types/session/session-advisors.d.ts +228 -0
- package/dist/types/session/session-entries.d.ts +8 -0
- package/dist/types/session/session-handoff.d.ts +70 -0
- package/dist/types/session/session-history-format.d.ts +6 -1
- package/dist/types/session/session-loader.d.ts +6 -4
- package/dist/types/session/session-maintenance.d.ts +246 -0
- package/dist/types/session/session-manager.d.ts +41 -0
- package/dist/types/session/session-memory.d.ts +56 -0
- package/dist/types/session/session-provider-boundary.d.ts +62 -0
- package/dist/types/session/session-stats.d.ts +48 -0
- package/dist/types/session/session-tools.d.ts +143 -0
- package/dist/types/session/session-workspace.d.ts +32 -0
- package/dist/types/session/stream-guards.d.ts +44 -0
- package/dist/types/session/todo-tracker.d.ts +56 -0
- package/dist/types/session/ttsr-coordinator.d.ts +51 -0
- package/dist/types/session/turn-recovery.d.ts +225 -0
- package/dist/types/system-prompt.d.ts +2 -0
- package/dist/types/task/executor.d.ts +4 -1
- package/dist/types/task/output-manager.d.ts +3 -1
- package/dist/types/task/types.d.ts +14 -0
- package/dist/types/tools/approval.d.ts +1 -0
- package/dist/types/tools/browser/launch.d.ts +5 -0
- package/dist/types/tools/hub/jobs.d.ts +1 -1
- package/dist/types/tools/image-gen.d.ts +4 -4
- package/dist/types/tools/image-providers.d.ts +38 -0
- package/dist/types/tools/index.d.ts +8 -0
- package/dist/types/tools/path-utils.d.ts +8 -0
- package/dist/types/tools/report-tool-issue.d.ts +24 -9
- package/dist/types/tools/todo.d.ts +10 -4
- package/dist/types/tools/xdev.d.ts +5 -1
- package/dist/types/tts/streaming-player.d.ts +14 -0
- package/dist/types/tts/vocalizer.d.ts +5 -0
- package/dist/types/utils/jj.d.ts +29 -0
- package/dist/types/utils/lang-from-path.d.ts +1 -0
- package/dist/types/utils/title-generator.d.ts +30 -0
- package/dist/types/vibe/runtime.d.ts +36 -8
- package/dist/types/web/search/index.d.ts +1 -1
- package/dist/types/web/search/provider.d.ts +15 -5
- package/dist/types/web/search/providers/firecrawl.d.ts +9 -0
- package/dist/types/web/search/types.d.ts +95 -1
- package/package.json +13 -12
- package/scripts/generate-share-viewer.ts +4 -6
- package/scripts/legacy-pi-virtual-module.ts +1 -1
- package/src/advisor/__tests__/advisor.test.ts +643 -0
- package/src/advisor/runtime.ts +144 -43
- package/src/async/job-manager.ts +90 -2
- package/src/cli/args.ts +2 -0
- package/src/cli/auth-gateway-cli.ts +18 -3
- package/src/cli/bench-cli.ts +395 -29
- package/src/cli/flag-tables.ts +3 -0
- package/src/cli/gc-cli.ts +2 -2
- package/src/cli/grievances-cli.ts +2 -2
- package/src/cli/usage-cli.ts +11 -2
- package/src/collab/host.ts +3 -2
- package/src/commands/bench.ts +18 -4
- package/src/commands/launch.ts +4 -0
- package/src/config/__tests__/model-registry.test.ts +147 -0
- package/src/config/model-registry.ts +50 -2
- package/src/config/model-resolver.ts +0 -1
- package/src/config/models-config-schema.ts +15 -4
- package/src/config/provider-globals.ts +9 -9
- package/src/config/service-tier.ts +26 -1
- package/src/config/settings-schema.ts +250 -54
- package/src/config/settings.ts +81 -2
- package/src/dap/client.ts +37 -4
- package/src/edit/index.ts +49 -12
- package/src/eval/py/executor.ts +103 -33
- package/src/exec/bash-executor.ts +89 -6
- package/src/exec/direnv.ts +145 -0
- package/src/export/html/args.ts +20 -0
- package/src/export/html/index.ts +45 -47
- package/src/export/html/template.css +19 -1
- package/src/export/html/template.html +6 -0
- package/src/export/html/template.js +21 -0
- package/src/export/html/tool-views.generated.js +31 -31
- package/src/export/html/web-palette.ts +116 -132
- package/src/export/share.ts +249 -49
- package/src/extensibility/extensions/loader.ts +29 -1
- package/src/extensibility/extensions/runner.ts +6 -0
- package/src/extensibility/extensions/types.ts +33 -2
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +1 -0
- package/src/extensibility/legacy-pi-tui-shim.ts +10 -0
- package/src/extensibility/plugins/legacy-pi-compat.ts +870 -311
- package/src/hindsight/state.ts +64 -6
- package/src/internal-urls/local-protocol.ts +2 -1
- package/src/internal-urls/memory-protocol.ts +2 -2
- package/src/internal-urls/registry-helpers.ts +40 -0
- package/src/internal-urls/skill-protocol.ts +2 -2
- package/src/internal-urls/ssh-protocol.ts +2 -1
- package/src/internal-urls/vault-protocol.ts +2 -1
- package/src/live/audio-worklet.txt +59 -0
- package/src/live/browser-runtime.txt +221 -0
- package/src/live/controller.ts +464 -0
- package/src/live/prompts/agent-final-message.md +3 -0
- package/src/live/prompts/live-instructions.md +23 -0
- package/src/live/protocol.test.ts +140 -0
- package/src/live/protocol.ts +233 -0
- package/src/live/transport.ts +490 -0
- package/src/live/visualizer.ts +234 -0
- package/src/lsp/client.ts +52 -9
- package/src/lsp/clients/biome-client.ts +3 -4
- package/src/lsp/index.ts +44 -11
- package/src/main.ts +9 -1
- package/src/mcp/config-writer.test.ts +43 -0
- package/src/mcp/config-writer.ts +109 -82
- package/src/mcp/manager.ts +39 -8
- package/src/mcp/oauth-discovery.ts +10 -2
- package/src/mcp/render.ts +94 -35
- package/src/mcp/smithery-auth.test.ts +29 -0
- package/src/mcp/smithery-auth.ts +3 -2
- package/src/mcp/tool-bridge.ts +107 -11
- package/src/mcp/types.ts +7 -0
- package/src/memories/index.ts +40 -20
- package/src/memory-backend/tool-names.ts +2 -0
- package/src/mnemopi/backend.ts +24 -7
- package/src/modes/acp/acp-agent.ts +56 -10
- package/src/modes/acp/acp-event-mapper.ts +8 -1
- package/src/modes/components/agent-hub.ts +27 -10
- package/src/modes/components/login-dialog.ts +14 -4
- package/src/modes/components/oauth-selector.ts +24 -7
- package/src/modes/components/plan-review-overlay.ts +350 -35
- package/src/modes/components/settings-defs.ts +28 -3
- package/src/modes/components/settings-selector.ts +178 -6
- package/src/modes/components/status-line/component.jj-cache.test.ts +229 -0
- package/src/modes/components/status-line/component.ts +123 -2
- package/src/modes/components/tool-execution.test.ts +63 -2
- package/src/modes/components/tool-execution.ts +11 -2
- package/src/modes/controllers/command-controller.ts +70 -22
- package/src/modes/controllers/event-controller.ts +111 -16
- package/src/modes/controllers/extension-ui-controller.test.ts +1 -0
- package/src/modes/controllers/extension-ui-controller.ts +20 -4
- package/src/modes/controllers/live-command-controller.ts +178 -0
- package/src/modes/controllers/mcp-command-controller.ts +80 -41
- package/src/modes/controllers/omfg-controller.ts +44 -40
- package/src/modes/controllers/selector-controller.ts +18 -10
- package/src/modes/controllers/session-focus-controller.ts +6 -1
- package/src/modes/interactive-mode.ts +160 -38
- package/src/modes/rpc/rpc-client.ts +94 -3
- package/src/modes/rpc/rpc-frame.ts +164 -4
- package/src/modes/rpc/rpc-messages.ts +127 -0
- package/src/modes/rpc/rpc-mode.ts +79 -7
- package/src/modes/rpc/rpc-types.ts +34 -2
- package/src/modes/runtime-init.ts +2 -0
- package/src/modes/setup-wizard/scenes/model.ts +5 -7
- package/src/modes/setup-wizard/scenes/providers.ts +3 -2
- package/src/modes/setup-wizard/scenes/sign-in.ts +8 -2
- package/src/modes/setup-wizard/scenes/theme.ts +13 -3
- package/src/modes/setup-wizard/scenes/types.ts +9 -1
- package/src/modes/setup-wizard/scenes/web-search.ts +23 -10
- package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
- package/src/modes/theme/theme.ts +1 -1
- package/src/modes/types.ts +3 -1
- package/src/modes/utils/context-usage.ts +23 -7
- package/src/modes/utils/ui-helpers.ts +10 -5
- package/src/plan-mode/plan-files.ts +40 -0
- package/src/prompts/bench/cache-prefix-chunk.md +1 -0
- package/src/prompts/bench/cache-prefix.md +3 -0
- package/src/prompts/bench/cache-suffix.md +1 -0
- package/src/prompts/goals/guided-goal-system.md +24 -3
- package/src/prompts/system/custom-system-prompt.md +1 -1
- package/src/prompts/system/project-prompt.md +8 -1
- package/src/prompts/system/subagent-async-pending.md +6 -0
- package/src/prompts/system/system-prompt.md +1 -1
- package/src/prompts/system/workflow-notice.md +48 -40
- package/src/prompts/system/xdev-mount-notice.md +4 -0
- package/src/prompts/tools/task.md +12 -2
- package/src/prompts/tools/todo.md +3 -1
- package/src/registry/agent-lifecycle.ts +116 -46
- package/src/registry/agent-registry.ts +35 -8
- package/src/registry/persisted-agents.ts +26 -2
- package/src/sdk.ts +255 -139
- package/src/secrets/index.ts +127 -4
- package/src/secrets/obfuscator.ts +2328 -105
- package/src/session/acp-permission-gate.ts +165 -0
- package/src/session/agent-session-error-log.test.ts +1 -1
- package/src/session/agent-session-events.ts +66 -0
- package/src/session/agent-session-types.ts +334 -0
- package/src/session/agent-session.ts +1910 -12213
- package/src/session/artifacts.ts +6 -5
- package/src/session/async-job-delivery.ts +74 -0
- package/src/session/bash-runner.ts +326 -0
- package/src/session/blob-store.test.ts +56 -0
- package/src/session/blob-store.ts +18 -2
- package/src/session/checkpoint-entries.ts +81 -0
- package/src/session/eval-runner.ts +212 -0
- package/src/session/indexed-session-storage.ts +9 -2
- package/src/session/irc-bridge.ts +203 -0
- package/src/session/messages.ts +263 -1
- package/src/session/model-controls.ts +714 -0
- package/src/session/prewalk.ts +252 -0
- package/src/session/queued-messages.ts +93 -0
- package/src/session/redis-session-storage.ts +51 -11
- package/src/session/retry-fallback-chains.ts +455 -0
- package/src/session/role-models.ts +85 -0
- package/src/session/session-advisors.ts +1679 -0
- package/src/session/session-context.test.ts +224 -1
- package/src/session/session-context.ts +47 -5
- package/src/session/session-entries.ts +8 -0
- package/src/session/session-handoff.ts +308 -0
- package/src/session/session-history-format.ts +20 -9
- package/src/session/session-loader.ts +10 -51
- package/src/session/session-maintenance.ts +2983 -0
- package/src/session/session-manager.ts +390 -30
- package/src/session/session-memory.ts +222 -0
- package/src/session/session-provider-boundary.ts +307 -0
- package/src/session/session-stats.ts +293 -0
- package/src/session/session-tools.ts +943 -0
- package/src/session/session-workspace.ts +53 -0
- package/src/session/stream-guards.ts +417 -0
- package/src/session/todo-tracker.ts +380 -0
- package/src/session/ttsr-coordinator.ts +496 -0
- package/src/session/turn-recovery.ts +1629 -0
- package/src/slash-commands/builtin-registry.ts +92 -11
- package/src/slash-commands/helpers/usage-report.ts +25 -4
- package/src/system-prompt.ts +41 -17
- package/src/task/executor.ts +288 -70
- package/src/task/index.ts +148 -111
- package/src/task/output-manager.ts +25 -3
- package/src/task/persisted-revive.ts +4 -3
- package/src/task/structured-subagent.ts +4 -1
- package/src/task/types.ts +16 -0
- package/src/tools/approval.ts +55 -10
- package/src/tools/bash-interactive.ts +26 -0
- package/src/tools/bash-skill-urls.ts +28 -2
- package/src/tools/bash.ts +339 -123
- package/src/tools/browser/launch.ts +9 -1
- package/src/tools/browser/tab-supervisor.ts +8 -5
- package/src/tools/browser.ts +94 -47
- package/src/tools/hub/index.ts +1 -1
- package/src/tools/hub/jobs.ts +67 -8
- package/src/tools/image-gen.ts +20 -28
- package/src/tools/image-providers.ts +50 -0
- package/src/tools/index.ts +12 -0
- package/src/tools/path-utils.ts +49 -2
- package/src/tools/read.ts +156 -88
- package/src/tools/renderers.ts +7 -1
- package/src/tools/report-tool-issue.ts +79 -28
- package/src/tools/todo.ts +101 -11
- package/src/tools/vibe.ts +1 -2
- package/src/tools/write.ts +51 -1
- package/src/tools/xdev.ts +67 -3
- package/src/tts/streaming-player.ts +62 -4
- package/src/tts/vocalizer.ts +18 -1
- package/src/utils/jj.ts +125 -4
- package/src/utils/lang-from-path.ts +7 -0
- package/src/utils/title-generator.ts +137 -1
- package/src/vibe/runtime.ts +852 -72
- package/src/web/search/index.ts +3 -6
- package/src/web/search/provider.ts +31 -16
- package/src/web/search/providers/firecrawl.ts +46 -13
- package/src/web/search/providers/xai.ts +7 -1
- package/src/web/search/types.ts +8 -1
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { type Component, matchesKey, truncateToWidth, visibleWidth } from "@oh-my-pi/pi-tui";
|
|
2
|
+
import { sanitizeText } from "@oh-my-pi/pi-utils";
|
|
3
|
+
import { type ThemeColor, theme } from "../modes/theme/theme";
|
|
4
|
+
|
|
5
|
+
/** Distinct states of a realtime call connection. */
|
|
6
|
+
export type LivePhase = "connecting" | "listening" | "working" | "speaking" | "muted" | "error";
|
|
7
|
+
|
|
8
|
+
/** A transcribed turn in the realtime call. */
|
|
9
|
+
export interface LiveTranscript {
|
|
10
|
+
role: "user" | "assistant";
|
|
11
|
+
text: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Configuration callbacks for user interactions in the visualizer. */
|
|
15
|
+
export interface LiveVisualizerOptions {
|
|
16
|
+
onStop(): void;
|
|
17
|
+
onToggleMute(): void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** A compact, fixed-height terminal component for displaying a realtime call. */
|
|
21
|
+
export class LiveVisualizer implements Component {
|
|
22
|
+
readonly wantsKeyRelease = false;
|
|
23
|
+
|
|
24
|
+
readonly #options: LiveVisualizerOptions;
|
|
25
|
+
|
|
26
|
+
#phase: LivePhase = "connecting";
|
|
27
|
+
#inputLevel = 0;
|
|
28
|
+
#outputLevel = 0;
|
|
29
|
+
#transcript: LiveTranscript | undefined;
|
|
30
|
+
#frame = 0;
|
|
31
|
+
|
|
32
|
+
#cache:
|
|
33
|
+
| {
|
|
34
|
+
width: number;
|
|
35
|
+
phase: LivePhase;
|
|
36
|
+
inputLevel: number;
|
|
37
|
+
outputLevel: number;
|
|
38
|
+
transcriptRole: string | undefined;
|
|
39
|
+
transcriptText: string | undefined;
|
|
40
|
+
frame: number;
|
|
41
|
+
lines: readonly string[];
|
|
42
|
+
}
|
|
43
|
+
| undefined;
|
|
44
|
+
|
|
45
|
+
constructor(options: LiveVisualizerOptions) {
|
|
46
|
+
this.#options = options;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Updates the current call phase. */
|
|
50
|
+
setPhase(phase: LivePhase): void {
|
|
51
|
+
if (this.#phase !== phase) {
|
|
52
|
+
this.#phase = phase;
|
|
53
|
+
this.invalidate();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Updates the current input and output volume levels (0..1). */
|
|
58
|
+
setLevels(input: number, output: number): void {
|
|
59
|
+
if (this.#inputLevel !== input || this.#outputLevel !== output) {
|
|
60
|
+
this.#inputLevel = input;
|
|
61
|
+
this.#outputLevel = output;
|
|
62
|
+
this.invalidate();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Updates the latest transcript fragment. */
|
|
67
|
+
setTranscript(transcript: LiveTranscript | undefined): void {
|
|
68
|
+
if (this.#transcript?.role !== transcript?.role || this.#transcript?.text !== transcript?.text) {
|
|
69
|
+
this.#transcript = transcript ? { ...transcript } : undefined;
|
|
70
|
+
this.invalidate();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Updates the animation frame for spinners and waveforms. */
|
|
75
|
+
setFrame(frame: number): void {
|
|
76
|
+
if (this.#frame !== frame) {
|
|
77
|
+
this.#frame = frame;
|
|
78
|
+
this.invalidate();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Processes user keypresses. */
|
|
83
|
+
handleInput(data: string): void {
|
|
84
|
+
if (matchesKey(data, "escape")) {
|
|
85
|
+
this.#options.onStop();
|
|
86
|
+
} else if (matchesKey(data, "space")) {
|
|
87
|
+
this.#options.onToggleMute();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Clears the render cache. */
|
|
92
|
+
invalidate(): void {
|
|
93
|
+
this.#cache = undefined;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Renders the visualizer into a fixed array of rows at the given width. */
|
|
97
|
+
render(width: number): readonly string[] {
|
|
98
|
+
if (
|
|
99
|
+
this.#cache &&
|
|
100
|
+
this.#cache.width === width &&
|
|
101
|
+
this.#cache.phase === this.#phase &&
|
|
102
|
+
this.#cache.inputLevel === this.#inputLevel &&
|
|
103
|
+
this.#cache.outputLevel === this.#outputLevel &&
|
|
104
|
+
this.#cache.frame === this.#frame &&
|
|
105
|
+
this.#cache.transcriptRole === this.#transcript?.role &&
|
|
106
|
+
this.#cache.transcriptText === this.#transcript?.text
|
|
107
|
+
) {
|
|
108
|
+
return this.#cache.lines;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const lines = this.#renderLines(width);
|
|
112
|
+
|
|
113
|
+
this.#cache = {
|
|
114
|
+
width,
|
|
115
|
+
phase: this.#phase,
|
|
116
|
+
inputLevel: this.#inputLevel,
|
|
117
|
+
outputLevel: this.#outputLevel,
|
|
118
|
+
frame: this.#frame,
|
|
119
|
+
transcriptRole: this.#transcript?.role,
|
|
120
|
+
transcriptText: this.#transcript?.text,
|
|
121
|
+
lines,
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
return lines;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
#renderLines(maxWidth: number): readonly string[] {
|
|
128
|
+
const w = Math.max(2, Math.min(maxWidth, 120));
|
|
129
|
+
const innerW = w - 2;
|
|
130
|
+
|
|
131
|
+
const topBorder = theme.fg("border", `┌${"─".repeat(innerW)}${w > 1 ? "┐" : ""}`);
|
|
132
|
+
|
|
133
|
+
const maxWaveW = Math.min(40, Math.max(0, w - 4));
|
|
134
|
+
const sideW = Math.max(0, Math.floor((maxWaveW - 3) / 2));
|
|
135
|
+
|
|
136
|
+
const inWave = this.#generateWaveform(this.#inputLevel, sideW, true);
|
|
137
|
+
const outWave = this.#generateWaveform(this.#outputLevel, sideW, false);
|
|
138
|
+
|
|
139
|
+
const inColor: ThemeColor = this.#phase === "muted" ? "dim" : "success";
|
|
140
|
+
const outColor: ThemeColor = this.#phase === "error" ? "error" : "accent";
|
|
141
|
+
|
|
142
|
+
const waveContent = truncateToWidth(
|
|
143
|
+
theme.fg(inColor, inWave) + theme.fg("dim", " │ ") + theme.fg(outColor, outWave),
|
|
144
|
+
innerW,
|
|
145
|
+
);
|
|
146
|
+
const waveLen = visibleWidth(waveContent);
|
|
147
|
+
|
|
148
|
+
const wavePadL = Math.floor((innerW - waveLen) / 2);
|
|
149
|
+
const wavePadR = innerW - waveLen - wavePadL;
|
|
150
|
+
const row1 =
|
|
151
|
+
theme.fg("border", "│") +
|
|
152
|
+
" ".repeat(Math.max(0, wavePadL)) +
|
|
153
|
+
waveContent +
|
|
154
|
+
" ".repeat(Math.max(0, wavePadR)) +
|
|
155
|
+
(w > 1 ? theme.fg("border", "│") : "");
|
|
156
|
+
|
|
157
|
+
const phaseColors: Record<LivePhase, ThemeColor> = {
|
|
158
|
+
connecting: "dim",
|
|
159
|
+
listening: "success",
|
|
160
|
+
working: "warning",
|
|
161
|
+
speaking: "accent",
|
|
162
|
+
muted: "dim",
|
|
163
|
+
error: "error",
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const spinners = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
167
|
+
let phaseIcon: string;
|
|
168
|
+
if (this.#phase === "working") {
|
|
169
|
+
phaseIcon = spinners[this.#frame % spinners.length];
|
|
170
|
+
} else {
|
|
171
|
+
const staticIcons: Record<LivePhase, string> = {
|
|
172
|
+
connecting: "○",
|
|
173
|
+
listening: "●",
|
|
174
|
+
working: "○",
|
|
175
|
+
speaking: "»",
|
|
176
|
+
muted: "×",
|
|
177
|
+
error: "!",
|
|
178
|
+
};
|
|
179
|
+
phaseIcon = staticIcons[this.#phase];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const phaseText = truncateToWidth(` ${phaseIcon} ${this.#phase.toUpperCase()} `, innerW);
|
|
183
|
+
const coloredPhase = theme.fg(phaseColors[this.#phase], phaseText);
|
|
184
|
+
|
|
185
|
+
const pLen = visibleWidth(coloredPhase);
|
|
186
|
+
const pPadL = Math.floor((innerW - pLen) / 2);
|
|
187
|
+
const pPadR = innerW - pLen - pPadL;
|
|
188
|
+
const row2 =
|
|
189
|
+
theme.fg("border", "│") +
|
|
190
|
+
" ".repeat(Math.max(0, pPadL)) +
|
|
191
|
+
coloredPhase +
|
|
192
|
+
" ".repeat(Math.max(0, pPadR)) +
|
|
193
|
+
(w > 1 ? theme.fg("border", "│") : "");
|
|
194
|
+
|
|
195
|
+
const row3 = theme.fg("border", "│") + " ".repeat(Math.max(0, innerW)) + (w > 1 ? theme.fg("border", "│") : "");
|
|
196
|
+
|
|
197
|
+
let transcriptText = "";
|
|
198
|
+
if (this.#transcript) {
|
|
199
|
+
const cleanText = sanitizeText(this.#transcript.text).replace(/[\r\n\t]+/g, " ");
|
|
200
|
+
const roleStr = this.#transcript.role === "user" ? "User: " : "Assistant: ";
|
|
201
|
+
transcriptText = theme.fg("dim", roleStr + cleanText);
|
|
202
|
+
}
|
|
203
|
+
const row4Content = truncateToWidth(` ${transcriptText}`, Math.max(0, innerW - 2)) || "";
|
|
204
|
+
const r4Pad = Math.max(0, innerW - visibleWidth(row4Content));
|
|
205
|
+
const row4 = theme.fg("border", "│") + row4Content + " ".repeat(r4Pad) + (w > 1 ? theme.fg("border", "│") : "");
|
|
206
|
+
|
|
207
|
+
const footerContent = truncateToWidth(" space mute · esc end ", innerW);
|
|
208
|
+
const dashCount = Math.max(0, innerW - visibleWidth(footerContent));
|
|
209
|
+
let bottomBorder: string;
|
|
210
|
+
if (innerW >= visibleWidth(footerContent) + 2) {
|
|
211
|
+
bottomBorder =
|
|
212
|
+
theme.fg("border", "└─") +
|
|
213
|
+
theme.fg("dim", footerContent) +
|
|
214
|
+
theme.fg("border", "─".repeat(dashCount - 2) + (w > 1 ? "┘" : ""));
|
|
215
|
+
} else {
|
|
216
|
+
bottomBorder = theme.fg("border", `└${"─".repeat(Math.max(0, innerW))}${w > 1 ? "┘" : ""}`);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return [topBorder, row1, row2, row3, row4, bottomBorder];
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
#generateWaveform(level: number, width: number, reverse: boolean): string {
|
|
223
|
+
const chars = [" ", "▂", "▃", "▄", "▅", "▆", "▇", "█"];
|
|
224
|
+
let out = "";
|
|
225
|
+
for (let i = 0; i < width; i++) {
|
|
226
|
+
const dist = reverse ? width - 1 - i : i;
|
|
227
|
+
const decay = Math.max(0, 1 - (dist / width) * 1.5);
|
|
228
|
+
const val = level * decay * (0.5 + 0.5 * Math.sin(this.#frame * 0.5 + dist * 0.8));
|
|
229
|
+
const charIdx = Math.max(0, Math.min(chars.length - 1, Math.floor(val * chars.length)));
|
|
230
|
+
out += chars[charIdx];
|
|
231
|
+
}
|
|
232
|
+
return out;
|
|
233
|
+
}
|
|
234
|
+
}
|
package/src/lsp/client.ts
CHANGED
|
@@ -364,7 +364,15 @@ async function startMessageReader(client: LspClient): Promise<void> {
|
|
|
364
364
|
if (pending) {
|
|
365
365
|
client.pendingRequests.delete(message.id);
|
|
366
366
|
if ("error" in message && message.error) {
|
|
367
|
-
|
|
367
|
+
// Include the JSON-RPC error code: `isMethodNotFoundError` matches
|
|
368
|
+
// `-32601` by substring, so method-not-found is recognized even when
|
|
369
|
+
// the server's message text is nonstandard (e.g. "Unknown request").
|
|
370
|
+
const code = message.error.code;
|
|
371
|
+
pending.reject(
|
|
372
|
+
new Error(
|
|
373
|
+
`LSP error${typeof code === "number" ? ` ${code}` : ""}: ${message.error.message}`,
|
|
374
|
+
),
|
|
375
|
+
);
|
|
368
376
|
} else {
|
|
369
377
|
pending.resolve(message.result);
|
|
370
378
|
}
|
|
@@ -846,6 +854,29 @@ export async function getOrCreateClient(
|
|
|
846
854
|
return clientPromise;
|
|
847
855
|
}
|
|
848
856
|
|
|
857
|
+
/** Return an active or already-starting client without starting a language server. */
|
|
858
|
+
export async function getActiveOrPendingClient(
|
|
859
|
+
config: ServerConfig,
|
|
860
|
+
cwd: string,
|
|
861
|
+
signal?: AbortSignal,
|
|
862
|
+
): Promise<LspClient | undefined> {
|
|
863
|
+
throwIfAborted(signal);
|
|
864
|
+
const client = clients.get(`${config.command}:${cwd}`);
|
|
865
|
+
if (client) {
|
|
866
|
+
client.lastActivity = Date.now();
|
|
867
|
+
return client;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
const pending = clientLocks.get(`${config.command}:${cwd}`);
|
|
871
|
+
if (!pending) return undefined;
|
|
872
|
+
try {
|
|
873
|
+
return await untilAborted(signal, pending);
|
|
874
|
+
} catch {
|
|
875
|
+
throwIfAborted(signal);
|
|
876
|
+
return undefined;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
|
|
849
880
|
/**
|
|
850
881
|
* Ensure a file is opened in the LSP client.
|
|
851
882
|
* Sends didOpen notification if the file is not already tracked.
|
|
@@ -1158,9 +1189,19 @@ async function waitForExit(client: LspClient, timeoutMs: number): Promise<boolea
|
|
|
1158
1189
|
}
|
|
1159
1190
|
|
|
1160
1191
|
/**
|
|
1161
|
-
*
|
|
1192
|
+
* Tear down a specific client instance using the LSP shutdown/exit handshake.
|
|
1193
|
+
*
|
|
1194
|
+
* Removes the client from the registry by identity first (never evicting a
|
|
1195
|
+
* newer client already republished under the same key), then performs a bounded
|
|
1196
|
+
* graceful shutdown, force-killing and awaiting confirmed process exit.
|
|
1197
|
+
*
|
|
1198
|
+
* @returns `true` once the process is confirmed exited, `false` if it outlived
|
|
1199
|
+
* the shutdown budget — callers reporting a restart must treat `false` as a
|
|
1200
|
+
* failed teardown, not a completed restart.
|
|
1162
1201
|
*/
|
|
1163
|
-
async function shutdownClientInstance(client: LspClient): Promise<
|
|
1202
|
+
export async function shutdownClientInstance(client: LspClient): Promise<boolean> {
|
|
1203
|
+
if (clients.get(client.name) === client) clients.delete(client.name);
|
|
1204
|
+
|
|
1164
1205
|
const err = new Error("LSP client shutdown");
|
|
1165
1206
|
for (const pending of Array.from(client.pendingRequests.values())) {
|
|
1166
1207
|
pending.reject(err);
|
|
@@ -1173,21 +1214,23 @@ async function shutdownClientInstance(client: LspClient): Promise<void> {
|
|
|
1173
1214
|
);
|
|
1174
1215
|
if (shutdownCompleted) {
|
|
1175
1216
|
await sendNotification(client, "exit", undefined).catch(() => {});
|
|
1176
|
-
if (await waitForExit(client, EXIT_TIMEOUT_MS)) return;
|
|
1217
|
+
if (await waitForExit(client, EXIT_TIMEOUT_MS)) return true;
|
|
1177
1218
|
}
|
|
1178
1219
|
|
|
1179
1220
|
client.proc.kill();
|
|
1180
|
-
await waitForExit(client, EXIT_TIMEOUT_MS);
|
|
1221
|
+
return await waitForExit(client, EXIT_TIMEOUT_MS);
|
|
1181
1222
|
}
|
|
1182
1223
|
|
|
1183
1224
|
/**
|
|
1184
1225
|
* Shutdown a specific client by key.
|
|
1226
|
+
*
|
|
1227
|
+
* @returns `true` when the client is gone (already absent or confirmed exited),
|
|
1228
|
+
* `false` if a live process outlived the shutdown budget.
|
|
1185
1229
|
*/
|
|
1186
|
-
export async function shutdownClient(key: string): Promise<
|
|
1230
|
+
export async function shutdownClient(key: string): Promise<boolean> {
|
|
1187
1231
|
const client = clients.get(key);
|
|
1188
|
-
if (!client) return;
|
|
1189
|
-
|
|
1190
|
-
await shutdownClientInstance(client);
|
|
1232
|
+
if (!client) return true;
|
|
1233
|
+
return await shutdownClientInstance(client);
|
|
1191
1234
|
}
|
|
1192
1235
|
|
|
1193
1236
|
// =============================================================================
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Biome CLI-based linter client.
|
|
3
3
|
* Uses Biome's CLI with JSON output instead of LSP (which has stale diagnostics issues).
|
|
4
4
|
*/
|
|
5
|
-
import path from "node:path";
|
|
5
|
+
import * as path from "node:path";
|
|
6
6
|
import { logger } from "@oh-my-pi/pi-utils";
|
|
7
7
|
import type { Diagnostic, DiagnosticSeverity, LinterClient, ServerConfig } from "../../lsp/types";
|
|
8
8
|
|
|
@@ -141,14 +141,13 @@ export class BiomeClient implements LinterClient {
|
|
|
141
141
|
) {}
|
|
142
142
|
|
|
143
143
|
async format(filePath: string, content: string): Promise<string> {
|
|
144
|
-
//
|
|
144
|
+
// Keep the standalone LinterClient contract: callers supply the content to
|
|
145
|
+
// format, regardless of what is currently on disk.
|
|
145
146
|
await Bun.write(filePath, content);
|
|
146
147
|
|
|
147
|
-
// Run biome format --write
|
|
148
148
|
const result = await runBiome(["format", "--write", filePath], this.cwd, this.config.resolvedCommand);
|
|
149
149
|
|
|
150
150
|
if (result.success) {
|
|
151
|
-
// Read back formatted content
|
|
152
151
|
return await Bun.file(filePath).text();
|
|
153
152
|
}
|
|
154
153
|
|
package/src/lsp/index.ts
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
ensureFileOpen,
|
|
21
21
|
FileChangeType,
|
|
22
22
|
getActiveClients,
|
|
23
|
+
getActiveOrPendingClient,
|
|
23
24
|
getOrCreateClient,
|
|
24
25
|
type LspServerStatus,
|
|
25
26
|
notifySaved,
|
|
@@ -28,6 +29,7 @@ import {
|
|
|
28
29
|
sendNotification,
|
|
29
30
|
sendRequest,
|
|
30
31
|
setIdleTimeout,
|
|
32
|
+
shutdownClientInstance,
|
|
31
33
|
supportsDocumentDiagnostics,
|
|
32
34
|
syncContent,
|
|
33
35
|
WARMUP_TIMEOUT_MS,
|
|
@@ -211,6 +213,7 @@ async function syncFileContent(
|
|
|
211
213
|
cwd: string,
|
|
212
214
|
servers: Array<[string, ServerConfig]>,
|
|
213
215
|
signal?: AbortSignal,
|
|
216
|
+
createMissing = true,
|
|
214
217
|
): Promise<void> {
|
|
215
218
|
throwIfAborted(signal);
|
|
216
219
|
await Promise.allSettled(
|
|
@@ -219,11 +222,15 @@ async function syncFileContent(
|
|
|
219
222
|
if (serverConfig.createClient) {
|
|
220
223
|
return;
|
|
221
224
|
}
|
|
222
|
-
const client =
|
|
225
|
+
const client = createMissing
|
|
226
|
+
? await getOrCreateClient(serverConfig, cwd, undefined, signal)
|
|
227
|
+
: await getActiveOrPendingClient(serverConfig, cwd, signal);
|
|
228
|
+
if (!client) return;
|
|
223
229
|
throwIfAborted(signal);
|
|
224
230
|
await syncContent(client, absolutePath, content, signal);
|
|
225
231
|
}),
|
|
226
232
|
);
|
|
233
|
+
throwIfAborted(signal);
|
|
227
234
|
}
|
|
228
235
|
|
|
229
236
|
/**
|
|
@@ -239,6 +246,7 @@ async function notifyFileSaved(
|
|
|
239
246
|
cwd: string,
|
|
240
247
|
servers: Array<[string, ServerConfig]>,
|
|
241
248
|
signal?: AbortSignal,
|
|
249
|
+
createMissing = true,
|
|
242
250
|
): Promise<void> {
|
|
243
251
|
throwIfAborted(signal);
|
|
244
252
|
await Promise.allSettled(
|
|
@@ -247,10 +255,14 @@ async function notifyFileSaved(
|
|
|
247
255
|
if (serverConfig.createClient) {
|
|
248
256
|
return;
|
|
249
257
|
}
|
|
250
|
-
const client =
|
|
258
|
+
const client = createMissing
|
|
259
|
+
? await getOrCreateClient(serverConfig, cwd, undefined, signal)
|
|
260
|
+
: await getActiveOrPendingClient(serverConfig, cwd, signal);
|
|
261
|
+
if (!client) return;
|
|
251
262
|
await notifySaved(client, absolutePath, signal);
|
|
252
263
|
}),
|
|
253
264
|
);
|
|
265
|
+
throwIfAborted(signal);
|
|
254
266
|
}
|
|
255
267
|
|
|
256
268
|
// Cache config per cwd to avoid repeated file I/O
|
|
@@ -496,12 +508,18 @@ function isMethodNotFoundError(err: unknown): boolean {
|
|
|
496
508
|
}
|
|
497
509
|
|
|
498
510
|
async function reloadServer(client: LspClient, serverName: string, signal?: AbortSignal): Promise<string> {
|
|
499
|
-
|
|
511
|
+
throwIfAborted(signal);
|
|
512
|
+
// rust-analyzer exposes a real reload request. Every other server rejects it
|
|
513
|
+
// with method-not-found — that alone justifies the generic fallback. A caller
|
|
514
|
+
// cancel or tool timeout must propagate, never be mistaken for an unsupported
|
|
515
|
+
// method and swallowed into a bogus "Restarted" (issue #6369).
|
|
500
516
|
try {
|
|
501
517
|
await sendRequest(client, "rust-analyzer/reloadWorkspace", null, signal);
|
|
502
518
|
return `Reloaded ${serverName}`;
|
|
503
|
-
} catch {
|
|
504
|
-
|
|
519
|
+
} catch (err) {
|
|
520
|
+
throwIfAborted(signal);
|
|
521
|
+
if (!isMethodNotFoundError(err)) throw err;
|
|
522
|
+
// Method not supported — fall through to the generic reload.
|
|
505
523
|
}
|
|
506
524
|
// workspace/didChangeConfiguration is a notification per spec; sending it
|
|
507
525
|
// as a request hangs until the tool deadline on servers that route it to
|
|
@@ -510,7 +528,15 @@ async function reloadServer(client: LspClient, serverName: string, signal?: Abor
|
|
|
510
528
|
await sendNotification(client, "workspace/didChangeConfiguration", { settings: {} }, signal);
|
|
511
529
|
return `Reloaded ${serverName}`;
|
|
512
530
|
} catch {
|
|
513
|
-
|
|
531
|
+
throwIfAborted(signal);
|
|
532
|
+
// The reload notification could not be delivered — the connection is
|
|
533
|
+
// wedged or the process already died. Tear the client down (removing it
|
|
534
|
+
// from the registry by identity and awaiting confirmed process exit) so
|
|
535
|
+
// the next request cold-starts a fresh client. A kill that never confirms
|
|
536
|
+
// exit is not a restart: surface the teardown failure truthfully.
|
|
537
|
+
if (!(await shutdownClientInstance(client))) {
|
|
538
|
+
throw new Error(`Failed to restart ${serverName}: server process did not exit after kill`);
|
|
539
|
+
}
|
|
514
540
|
return `Restarted ${serverName}`;
|
|
515
541
|
}
|
|
516
542
|
}
|
|
@@ -1333,7 +1359,8 @@ async function runLspWritethrough(
|
|
|
1333
1359
|
// Capture diagnostic versions BEFORE syncing to detect stale diagnostics
|
|
1334
1360
|
// Bound client creation by the writethrough budget: a hung/broken server
|
|
1335
1361
|
// must not add its full init wait (30s default) to every edit.
|
|
1336
|
-
const
|
|
1362
|
+
const minVersionsPromise = enableDiagnostics ? captureDiagnosticVersions(cwd, servers, 5_000, signal) : undefined;
|
|
1363
|
+
let minVersions = useCustomFormatter ? undefined : await minVersionsPromise;
|
|
1337
1364
|
let expectedDocumentVersions: ServerVersionMap | undefined;
|
|
1338
1365
|
|
|
1339
1366
|
let formatter: FileFormatResult | undefined;
|
|
@@ -1353,13 +1380,19 @@ async function runLspWritethrough(
|
|
|
1353
1380
|
operationSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
|
|
1354
1381
|
await untilAborted(operationSignal, async () => {
|
|
1355
1382
|
if (useCustomFormatter) {
|
|
1356
|
-
// Custom linters
|
|
1383
|
+
// Custom linters operate on on-disk input; the shared pre-write also
|
|
1384
|
+
// supports implementations that inspect the file before formatting.
|
|
1357
1385
|
await writeContent(content);
|
|
1358
|
-
|
|
1386
|
+
const [formattedContent, capturedVersions] = await Promise.all([
|
|
1387
|
+
formatContent(dst, content, cwd, customLinterServers, operationSignal),
|
|
1388
|
+
minVersionsPromise,
|
|
1389
|
+
]);
|
|
1390
|
+
finalContent = formattedContent;
|
|
1391
|
+
minVersions = capturedVersions;
|
|
1359
1392
|
formatter = finalContent !== content ? FileFormatResult.FORMATTED : FileFormatResult.UNCHANGED;
|
|
1360
1393
|
await writeContent(finalContent);
|
|
1361
1394
|
await notifyWriteCommitted(operationSignal);
|
|
1362
|
-
await syncFileContent(dst, finalContent, cwd, lspServers, operationSignal);
|
|
1395
|
+
await syncFileContent(dst, finalContent, cwd, lspServers, operationSignal, enableDiagnostics);
|
|
1363
1396
|
} else {
|
|
1364
1397
|
// 1. Sync original content to LSP servers
|
|
1365
1398
|
await syncFileContent(dst, content, cwd, lspServers, operationSignal);
|
|
@@ -1385,7 +1418,7 @@ async function runLspWritethrough(
|
|
|
1385
1418
|
}
|
|
1386
1419
|
|
|
1387
1420
|
// 5. Notify saved to LSP servers
|
|
1388
|
-
await notifyFileSaved(dst, cwd, lspServers, operationSignal);
|
|
1421
|
+
await notifyFileSaved(dst, cwd, lspServers, operationSignal, !useCustomFormatter || enableDiagnostics);
|
|
1389
1422
|
});
|
|
1390
1423
|
synced = true;
|
|
1391
1424
|
} catch {
|
package/src/main.ts
CHANGED
|
@@ -134,6 +134,7 @@ async function checkForNewVersion(currentVersion: string): Promise<string | unde
|
|
|
134
134
|
// embedders need project-level opt-outs for reminder/prelude prompt injection.
|
|
135
135
|
const HOST_DEFAULTED_SETTING_PATHS: SettingPath[] = [
|
|
136
136
|
"task.isolation.mode",
|
|
137
|
+
"task.isolation.apply",
|
|
137
138
|
"task.isolation.merge",
|
|
138
139
|
"task.isolation.commits",
|
|
139
140
|
"task.eager",
|
|
@@ -390,7 +391,9 @@ export function createAcpSessionFactory(args: AcpSessionFactoryOptions): AcpSess
|
|
|
390
391
|
authStorage: args.authStorage,
|
|
391
392
|
modelRegistry: args.modelRegistry,
|
|
392
393
|
agentId,
|
|
393
|
-
|
|
394
|
+
// Preserve reserve-policy confirmation until ACP capabilities are known
|
|
395
|
+
// without enabling AskTool or other UI-only session behavior.
|
|
396
|
+
deferUsageReserveConfirmation: true,
|
|
394
397
|
enableMCP: false,
|
|
395
398
|
titleSystemPrompt,
|
|
396
399
|
});
|
|
@@ -833,6 +836,11 @@ export async function buildSessionOptions(
|
|
|
833
836
|
cwd: parsed.cwd ?? getProjectDir(),
|
|
834
837
|
autoApprove: parsed.autoApprove ?? false,
|
|
835
838
|
};
|
|
839
|
+
const cliDirs = parsed.addDir ?? [];
|
|
840
|
+
const settingsDirs = activeSettings.get("workspace.additionalDirectories");
|
|
841
|
+
if (cliDirs.length > 0 || settingsDirs.length > 0) {
|
|
842
|
+
options.additionalDirectories = [...new Set([...cliDirs, ...settingsDirs])];
|
|
843
|
+
}
|
|
836
844
|
if (parsed.maxTime !== undefined) {
|
|
837
845
|
options.deadline = Date.now() + parsed.maxTime * 1000;
|
|
838
846
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
|
|
2
|
+
import * as fs from "node:fs/promises";
|
|
3
|
+
import * as os from "node:os";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import { addMCPServer, readDisabledServers, readMCPConfigFile, setServerDisabled } from "./config-writer";
|
|
6
|
+
|
|
7
|
+
describe("config-writer concurrent mutations", () => {
|
|
8
|
+
let dir: string;
|
|
9
|
+
let filePath: string;
|
|
10
|
+
|
|
11
|
+
beforeEach(async () => {
|
|
12
|
+
dir = await fs.mkdtemp(path.join(os.tmpdir(), "omp-mcp-config-"));
|
|
13
|
+
filePath = path.join(dir, "mcp.json");
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterEach(async () => {
|
|
17
|
+
await fs.rm(dir, { recursive: true, force: true });
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("preserves both servers when two adds race the same file", async () => {
|
|
21
|
+
await Promise.all([
|
|
22
|
+
addMCPServer(filePath, "alpha", { type: "stdio", command: "a" }),
|
|
23
|
+
addMCPServer(filePath, "bravo", { type: "stdio", command: "b" }),
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
const config = await readMCPConfigFile(filePath);
|
|
27
|
+
expect(Object.keys(config.mcpServers ?? {}).sort()).toEqual(["alpha", "bravo"]);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("preserves both denylist edits when disable calls race", async () => {
|
|
31
|
+
await Promise.all([setServerDisabled(filePath, "alpha", true), setServerDisabled(filePath, "bravo", true)]);
|
|
32
|
+
|
|
33
|
+
expect((await readDisabledServers(filePath)).sort()).toEqual(["alpha", "bravo"]);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("writes into a directory that does not exist yet", async () => {
|
|
37
|
+
const nestedPath = path.join(dir, "nested", "deep", "mcp.json");
|
|
38
|
+
await addMCPServer(nestedPath, "alpha", { type: "stdio", command: "a" });
|
|
39
|
+
|
|
40
|
+
const config = await readMCPConfigFile(nestedPath);
|
|
41
|
+
expect(Object.keys(config.mcpServers ?? {})).toEqual(["alpha"]);
|
|
42
|
+
});
|
|
43
|
+
});
|