@oh-my-pi/pi-coding-agent 17.1.5 → 17.1.7
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 +54 -0
- package/dist/{CHANGELOG-qs3vd6xf.md → CHANGELOG-5k4dq4g6.md} +54 -0
- package/dist/cli.js +3342 -3274
- package/dist/types/capability/index.d.ts +1 -1
- package/dist/types/capability/types.d.ts +23 -1
- package/dist/types/config/settings-schema.d.ts +39 -2
- package/dist/types/cursor.d.ts +2 -1
- package/dist/types/extensibility/extensions/runner.d.ts +4 -0
- package/dist/types/extensibility/shared-events.d.ts +18 -1
- package/dist/types/internal-urls/mcp-protocol.d.ts +3 -2
- package/dist/types/internal-urls/parse.d.ts +12 -0
- package/dist/types/internal-urls/router.d.ts +6 -0
- package/dist/types/internal-urls/types.d.ts +6 -0
- package/dist/types/lsp/config.d.ts +1 -0
- package/dist/types/lsp/types.d.ts +2 -0
- package/dist/types/mcp/manager.d.ts +5 -0
- package/dist/types/mcp/tool-bridge.d.ts +13 -0
- package/dist/types/modes/components/custom-editor.d.ts +5 -0
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +1 -1
- package/dist/types/modes/rpc/rpc-mode.d.ts +2 -0
- package/dist/types/sdk.d.ts +3 -1
- package/dist/types/session/agent-session-types.d.ts +8 -6
- package/dist/types/session/agent-session.d.ts +28 -1
- package/dist/types/session/model-controls.d.ts +4 -1
- package/dist/types/session/session-advisors.d.ts +7 -1
- package/dist/types/session/session-tools.d.ts +44 -6
- package/dist/types/session/streaming-output.d.ts +7 -2
- package/dist/types/session/tool-choice-queue.d.ts +6 -4
- package/dist/types/session/turn-recovery.d.ts +5 -3
- package/dist/types/task/index.d.ts +1 -1
- package/dist/types/task/types.d.ts +3 -11
- package/dist/types/thinking.d.ts +21 -2
- package/dist/types/tools/index.d.ts +8 -3
- package/dist/types/tools/output-meta.d.ts +5 -0
- package/dist/types/tools/read.d.ts +9 -1
- package/dist/types/tools/xdev.d.ts +53 -67
- package/dist/types/utils/cpuprofile.d.ts +51 -0
- package/dist/types/utils/inspect-image-mode.d.ts +29 -0
- package/dist/types/utils/profile-tree.d.ts +47 -0
- package/dist/types/utils/sample-profile.d.ts +67 -0
- package/dist/types/utils/title-generator.d.ts +17 -16
- package/package.json +12 -12
- package/src/capability/index.ts +43 -12
- package/src/capability/mcp.ts +21 -0
- package/src/capability/types.ts +20 -1
- package/src/cli/read-cli.ts +44 -2
- package/src/config/settings-schema.ts +42 -2
- package/src/config/settings.ts +35 -0
- package/src/cursor.ts +4 -3
- package/src/discovery/builtin-rules/index.ts +2 -0
- package/src/discovery/builtin-rules/ts-no-local-is-record.md +48 -0
- package/src/eval/py/runner.py +16 -2
- package/src/extensibility/extensions/runner.ts +117 -5
- package/src/extensibility/extensions/types.ts +0 -1
- package/src/extensibility/extensions/wrapper.ts +74 -42
- package/src/extensibility/hooks/tool-wrapper.ts +11 -4
- package/src/extensibility/hooks/types.ts +0 -1
- package/src/extensibility/shared-events.ts +18 -1
- package/src/internal-urls/mcp-protocol.ts +17 -3
- package/src/internal-urls/parse.ts +31 -0
- package/src/internal-urls/router.ts +24 -4
- package/src/internal-urls/types.ts +6 -0
- package/src/live/transport.ts +2 -2
- package/src/lsp/client.ts +2 -2
- package/src/lsp/config.ts +4 -0
- package/src/lsp/types.ts +2 -0
- package/src/mcp/config.ts +26 -14
- package/src/mcp/manager.ts +26 -9
- package/src/mcp/tool-bridge.ts +52 -1
- package/src/memories/index.ts +25 -6
- package/src/modes/components/custom-editor.ts +39 -16
- package/src/modes/components/status-line/segments.ts +3 -1
- package/src/modes/components/tips.txt +2 -1
- package/src/modes/components/tool-execution.ts +8 -7
- package/src/modes/controllers/command-controller.ts +10 -10
- package/src/modes/controllers/extension-ui-controller.ts +7 -7
- package/src/modes/controllers/selector-controller.ts +7 -2
- package/src/modes/rpc/rpc-mode.ts +60 -47
- package/src/prompts/steering/user-interjection.md +2 -5
- package/src/prompts/tools/task.md +4 -2
- package/src/sdk.ts +64 -58
- package/src/session/agent-session-types.ts +8 -5
- package/src/session/agent-session.ts +144 -11
- package/src/session/model-controls.ts +48 -12
- package/src/session/session-advisors.ts +30 -14
- package/src/session/session-listing.ts +66 -4
- package/src/session/session-tools.ts +162 -54
- package/src/session/streaming-output.ts +18 -6
- package/src/session/tool-choice-queue.ts +19 -4
- package/src/session/turn-recovery.ts +25 -6
- package/src/slash-commands/builtin-registry.ts +69 -0
- package/src/task/executor.ts +11 -3
- package/src/task/index.ts +43 -32
- package/src/task/types.ts +12 -17
- package/src/thinking.ts +68 -5
- package/src/tools/bash.ts +16 -9
- package/src/tools/index.ts +36 -16
- package/src/tools/output-meta.ts +20 -0
- package/src/tools/read.ts +89 -15
- package/src/tools/write.ts +16 -7
- package/src/tools/xdev.ts +198 -210
- package/src/utils/cpuprofile.ts +235 -0
- package/src/utils/inspect-image-mode.ts +39 -0
- package/src/utils/profile-tree.ts +111 -0
- package/src/utils/sample-profile.ts +437 -0
- package/src/utils/title-generator.ts +88 -34
- package/dist/types/advisor/__tests__/advisor.test.d.ts +0 -1
- package/dist/types/advisor/__tests__/config.test.d.ts +0 -1
- package/dist/types/advisor/__tests__/emission-guard.test.d.ts +0 -1
- package/dist/types/cli/__tests__/auth-gateway-catalog.test.d.ts +0 -1
- package/dist/types/cli/update-cli.test.d.ts +0 -1
- package/dist/types/config/__tests__/model-registry.test.d.ts +0 -1
- package/dist/types/eval/__tests__/agent-bridge.test.d.ts +0 -1
- package/dist/types/eval/__tests__/bridge-timeout.test.d.ts +0 -1
- package/dist/types/eval/__tests__/budget-bridge.test.d.ts +0 -1
- package/dist/types/eval/__tests__/completion-bridge.test.d.ts +0 -1
- package/dist/types/eval/__tests__/helpers-local-roots.test.d.ts +0 -1
- package/dist/types/eval/__tests__/idle-timeout.test.d.ts +0 -1
- package/dist/types/eval/__tests__/js-context-manager.test.d.ts +0 -1
- package/dist/types/eval/__tests__/julia-prelude.test.d.ts +0 -1
- package/dist/types/eval/__tests__/kernel-spawn.test.d.ts +0 -1
- package/dist/types/eval/__tests__/prelude-agent.test.d.ts +0 -1
- package/dist/types/eval/__tests__/process-entry-import.test.d.ts +0 -1
- package/dist/types/eval/py/__tests__/prelude.test.d.ts +0 -1
- package/dist/types/eval/py/__tests__/runner-shell-output.test.d.ts +0 -1
- package/dist/types/hindsight/client.test.d.ts +0 -1
- package/dist/types/internal-urls/__tests__/agent-protocol-nested.test.d.ts +0 -1
- package/dist/types/internal-urls/__tests__/ssh-protocol.test.d.ts +0 -1
- package/dist/types/launch/broker-list-order.test.d.ts +0 -1
- package/dist/types/launch/broker-output-snapshot.test.d.ts +0 -1
- package/dist/types/launch/protocol.test.d.ts +0 -1
- package/dist/types/launch/spawn-options.test.d.ts +0 -1
- package/dist/types/launch/terminal-output.test.d.ts +0 -1
- package/dist/types/live/protocol.test.d.ts +0 -1
- package/dist/types/mcp/config-writer.test.d.ts +0 -1
- package/dist/types/mcp/smithery-auth.test.d.ts +0 -1
- package/dist/types/mcp/smithery-registry.test.d.ts +0 -1
- package/dist/types/mcp/transports/stdio.test.d.ts +0 -1
- package/dist/types/modes/components/__tests__/dynamic-border.test.d.ts +0 -1
- package/dist/types/modes/components/__tests__/move-overlay.test.d.ts +0 -1
- package/dist/types/modes/components/__tests__/pause-screen.test.d.ts +0 -1
- package/dist/types/modes/components/__tests__/skill-message.test.d.ts +0 -1
- package/dist/types/modes/components/custom-editor-plugin-ctor.test.d.ts +0 -1
- package/dist/types/modes/components/custom-editor.test.d.ts +0 -1
- package/dist/types/modes/components/login-dialog.test.d.ts +0 -1
- package/dist/types/modes/components/status-line/component.jj-cache.test.d.ts +0 -1
- package/dist/types/modes/components/status-line/component.test.d.ts +0 -1
- package/dist/types/modes/components/tool-execution.test.d.ts +0 -1
- package/dist/types/modes/controllers/extension-ui-controller.test.d.ts +0 -1
- package/dist/types/modes/noninteractive-dispose.test.d.ts +0 -1
- package/dist/types/modes/print-mode.test.d.ts +0 -1
- package/dist/types/modes/session-teardown.test.d.ts +0 -1
- package/dist/types/modes/theme/mermaid-rendering.test.d.ts +0 -1
- package/dist/types/modes/utils/transcript-render-helpers.test.d.ts +0 -1
- package/dist/types/modes/warp-events.test.d.ts +0 -1
- package/dist/types/plan-mode/approved-plan-prompt.test.d.ts +0 -1
- package/dist/types/plan-mode/model-transition.test.d.ts +0 -1
- package/dist/types/plan-mode/reentry-prompt.test.d.ts +0 -1
- package/dist/types/session/agent-session-error-log.test.d.ts +0 -1
- package/dist/types/session/blob-store.test.d.ts +0 -1
- package/dist/types/session/messages.test.d.ts +0 -1
- package/dist/types/session/session-context.test.d.ts +0 -1
- package/dist/types/ssh/__tests__/connection-manager-args.test.d.ts +0 -1
- package/dist/types/ssh/__tests__/connection-manager-timeout.test.d.ts +0 -1
- package/dist/types/ssh/__tests__/file-transfer-posix-guard.test.d.ts +0 -1
- package/dist/types/ssh/__tests__/sshfs-mount.test.d.ts +0 -1
- package/dist/types/system-prompt.test.d.ts +0 -1
- package/dist/types/task/render.test.d.ts +0 -1
- package/dist/types/task/spawn-policy.test.d.ts +0 -1
- package/dist/types/tools/__tests__/eval-description.test.d.ts +0 -1
- package/dist/types/tools/__tests__/glob.test.d.ts +0 -1
- package/dist/types/tools/__tests__/json-tree.test.d.ts +0 -1
- package/dist/types/tools/__tests__/vibe-render.test.d.ts +0 -1
- package/dist/types/tools/hub/launch-compat.test.d.ts +0 -1
- package/dist/types/vibe/__tests__/token-rate.test.d.ts +0 -1
- package/src/advisor/__tests__/advisor.test.ts +0 -4889
- package/src/advisor/__tests__/config.test.ts +0 -349
- package/src/advisor/__tests__/emission-guard.test.ts +0 -147
- package/src/cli/__tests__/auth-gateway-catalog.test.ts +0 -111
- package/src/cli/update-cli.test.ts +0 -28
- package/src/config/__tests__/model-registry.test.ts +0 -182
- package/src/eval/__tests__/agent-bridge.test.ts +0 -1509
- package/src/eval/__tests__/bridge-timeout.test.ts +0 -170
- package/src/eval/__tests__/budget-bridge.test.ts +0 -80
- package/src/eval/__tests__/completion-bridge.test.ts +0 -412
- package/src/eval/__tests__/helpers-local-roots.test.ts +0 -55
- package/src/eval/__tests__/idle-timeout.test.ts +0 -80
- package/src/eval/__tests__/js-context-manager.test.ts +0 -456
- package/src/eval/__tests__/julia-prelude.test.ts +0 -66
- package/src/eval/__tests__/kernel-spawn.test.ts +0 -115
- package/src/eval/__tests__/prelude-agent.test.ts +0 -156
- package/src/eval/__tests__/process-entry-import.test.ts +0 -137
- package/src/eval/py/__tests__/prelude.test.ts +0 -104
- package/src/eval/py/__tests__/runner-shell-output.test.ts +0 -157
- package/src/hindsight/client.test.ts +0 -75
- package/src/internal-urls/__tests__/agent-protocol-nested.test.ts +0 -141
- package/src/internal-urls/__tests__/ssh-protocol.test.ts +0 -331
- package/src/launch/broker-list-order.test.ts +0 -89
- package/src/launch/broker-output-snapshot.test.ts +0 -126
- package/src/launch/protocol.test.ts +0 -59
- package/src/launch/spawn-options.test.ts +0 -31
- package/src/launch/terminal-output.test.ts +0 -107
- package/src/live/protocol.test.ts +0 -140
- package/src/mcp/config-writer.test.ts +0 -43
- package/src/mcp/smithery-auth.test.ts +0 -29
- package/src/mcp/smithery-registry.test.ts +0 -51
- package/src/mcp/transports/stdio.test.ts +0 -427
- package/src/modes/components/__tests__/dynamic-border.test.ts +0 -55
- package/src/modes/components/__tests__/move-overlay.test.ts +0 -252
- package/src/modes/components/__tests__/pause-screen.test.ts +0 -143
- package/src/modes/components/__tests__/skill-message.test.ts +0 -94
- package/src/modes/components/custom-editor-plugin-ctor.test.ts +0 -36
- package/src/modes/components/custom-editor.test.ts +0 -510
- package/src/modes/components/login-dialog.test.ts +0 -56
- package/src/modes/components/status-line/component.jj-cache.test.ts +0 -229
- package/src/modes/components/status-line/component.test.ts +0 -84
- package/src/modes/components/tool-execution.test.ts +0 -162
- package/src/modes/controllers/extension-ui-controller.test.ts +0 -250
- package/src/modes/noninteractive-dispose.test.ts +0 -73
- package/src/modes/print-mode.test.ts +0 -71
- package/src/modes/session-teardown.test.ts +0 -219
- package/src/modes/theme/mermaid-rendering.test.ts +0 -53
- package/src/modes/utils/transcript-render-helpers.test.ts +0 -38
- package/src/modes/warp-events.test.ts +0 -794
- package/src/plan-mode/approved-plan-prompt.test.ts +0 -36
- package/src/plan-mode/model-transition.test.ts +0 -60
- package/src/plan-mode/reentry-prompt.test.ts +0 -41
- package/src/session/agent-session-error-log.test.ts +0 -59
- package/src/session/blob-store.test.ts +0 -56
- package/src/session/messages.test.ts +0 -282
- package/src/session/session-context.test.ts +0 -384
- package/src/ssh/__tests__/connection-manager-args.test.ts +0 -191
- package/src/ssh/__tests__/connection-manager-timeout.test.ts +0 -61
- package/src/ssh/__tests__/file-transfer-posix-guard.test.ts +0 -105
- package/src/ssh/__tests__/sshfs-mount.test.ts +0 -13
- package/src/system-prompt.test.ts +0 -236
- package/src/task/render.test.ts +0 -290
- package/src/task/spawn-policy.test.ts +0 -62
- package/src/tools/__tests__/eval-description.test.ts +0 -18
- package/src/tools/__tests__/glob.test.ts +0 -37
- package/src/tools/__tests__/json-tree.test.ts +0 -35
- package/src/tools/__tests__/vibe-render.test.ts +0 -210
- package/src/tools/hub/launch-compat.test.ts +0 -40
- package/src/vibe/__tests__/token-rate.test.ts +0 -96
|
@@ -1,794 +0,0 @@
|
|
|
1
|
-
import { afterEach, describe, expect, it, vi } from "bun:test";
|
|
2
|
-
import * as path from "node:path";
|
|
3
|
-
import * as terminalCapabilities from "@oh-my-pi/pi-tui/terminal-capabilities";
|
|
4
|
-
import { VERSION } from "@oh-my-pi/pi-utils/dirs";
|
|
5
|
-
import type {
|
|
6
|
-
AgentEndEvent,
|
|
7
|
-
AgentStartEvent,
|
|
8
|
-
ExtensionAPI,
|
|
9
|
-
ExtensionContext,
|
|
10
|
-
InputEvent,
|
|
11
|
-
MessageStartEvent,
|
|
12
|
-
SessionBranchEvent,
|
|
13
|
-
SessionStartEvent,
|
|
14
|
-
SessionSwitchEvent,
|
|
15
|
-
ToolApprovalRequestedEvent,
|
|
16
|
-
} from "../extensibility/extensions/types";
|
|
17
|
-
import { SILENT_ABORT_MARKER, SKILL_PROMPT_MESSAGE_TYPE, USER_INTERRUPT_LABEL } from "../session/messages";
|
|
18
|
-
import { createWarpEventBridgeExtension, createWarpEventEmitter } from "./warp-events";
|
|
19
|
-
|
|
20
|
-
const originalTerminalId = terminalCapabilities.TERMINAL.id;
|
|
21
|
-
const originalProtocolVersion = process.env.WARP_CLI_AGENT_PROTOCOL_VERSION;
|
|
22
|
-
const project = path.basename(process.cwd());
|
|
23
|
-
const OSC_PREFIX = "\x1b]777;notify;warp://cli-agent;";
|
|
24
|
-
|
|
25
|
-
type RegisteredHandler = (...args: never[]) => void;
|
|
26
|
-
|
|
27
|
-
function enableWarpProtocol(terminalId = "warp"): void {
|
|
28
|
-
Object.defineProperty(terminalCapabilities.TERMINAL, "id", { value: terminalId, configurable: true });
|
|
29
|
-
process.env.WARP_CLI_AGENT_PROTOCOL_VERSION = "1";
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function restoreProtocolEnvironment(): void {
|
|
33
|
-
Object.defineProperty(terminalCapabilities.TERMINAL, "id", { value: originalTerminalId, configurable: true });
|
|
34
|
-
if (originalProtocolVersion === undefined) {
|
|
35
|
-
delete process.env.WARP_CLI_AGENT_PROTOCOL_VERSION;
|
|
36
|
-
} else {
|
|
37
|
-
process.env.WARP_CLI_AGENT_PROTOCOL_VERSION = originalProtocolVersion;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function createHandlers(): Map<string, RegisteredHandler> {
|
|
42
|
-
const handlers = new Map<string, RegisteredHandler>();
|
|
43
|
-
const api = {
|
|
44
|
-
on(event: string, handler: RegisteredHandler): void {
|
|
45
|
-
handlers.set(event, handler);
|
|
46
|
-
},
|
|
47
|
-
} as never as ExtensionAPI;
|
|
48
|
-
createWarpEventBridgeExtension()(api);
|
|
49
|
-
return handlers;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function parseBodies(write: { mock: { calls: unknown[][] } }): Array<Record<string, unknown>> {
|
|
53
|
-
return write.mock.calls.map(call => {
|
|
54
|
-
const osc = call[0] as string;
|
|
55
|
-
return JSON.parse(osc.slice(OSC_PREFIX.length, osc.length - 1)) as Record<string, unknown>;
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function userMessageStart(text: string, overrides: Partial<MessageStartEvent["message"]> = {}): MessageStartEvent {
|
|
60
|
-
return {
|
|
61
|
-
type: "message_start",
|
|
62
|
-
message: {
|
|
63
|
-
role: "user",
|
|
64
|
-
content: text,
|
|
65
|
-
timestamp: Date.now(),
|
|
66
|
-
...overrides,
|
|
67
|
-
} as MessageStartEvent["message"],
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function skillPromptStart(text: string, attribution: "user" | "agent" = "user"): MessageStartEvent {
|
|
72
|
-
return {
|
|
73
|
-
type: "message_start",
|
|
74
|
-
message: {
|
|
75
|
-
role: "custom",
|
|
76
|
-
customType: SKILL_PROMPT_MESSAGE_TYPE,
|
|
77
|
-
content: text,
|
|
78
|
-
display: true,
|
|
79
|
-
attribution,
|
|
80
|
-
timestamp: Date.now(),
|
|
81
|
-
} as MessageStartEvent["message"],
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function bridgeContext(sessionId = "session-123", cwd = process.cwd()): ExtensionContext {
|
|
86
|
-
return {
|
|
87
|
-
sessionManager: {
|
|
88
|
-
getSessionId: () => sessionId,
|
|
89
|
-
getCwd: () => cwd,
|
|
90
|
-
},
|
|
91
|
-
} as never as ExtensionContext;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
afterEach(() => {
|
|
95
|
-
vi.restoreAllMocks();
|
|
96
|
-
restoreProtocolEnvironment();
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
describe("Warp CLI-agent events", () => {
|
|
100
|
-
it("emits an exact OSC 777 stop event", () => {
|
|
101
|
-
enableWarpProtocol();
|
|
102
|
-
const write = vi.spyOn(process.stdout, "write").mockReturnValue(true);
|
|
103
|
-
vi.spyOn(terminalCapabilities, "isInsideTmux").mockReturnValue(false);
|
|
104
|
-
const emitter = createWarpEventEmitter({ sessionId: "session-123" });
|
|
105
|
-
|
|
106
|
-
emitter?.emit({ event: "stop" });
|
|
107
|
-
|
|
108
|
-
const expectedBody = JSON.stringify({
|
|
109
|
-
event: "stop",
|
|
110
|
-
v: 1,
|
|
111
|
-
agent: "omp",
|
|
112
|
-
session_id: "session-123",
|
|
113
|
-
cwd: process.cwd(),
|
|
114
|
-
project,
|
|
115
|
-
plugin_version: VERSION,
|
|
116
|
-
});
|
|
117
|
-
expect(write).toHaveBeenCalledWith(`${OSC_PREFIX}${expectedBody}\x07`);
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it("wraps OSC output when running inside tmux", () => {
|
|
121
|
-
enableWarpProtocol();
|
|
122
|
-
const write = vi.spyOn(process.stdout, "write").mockReturnValue(true);
|
|
123
|
-
const tmux = vi.spyOn(terminalCapabilities, "isInsideTmux").mockReturnValue(true);
|
|
124
|
-
const wrap = vi.spyOn(terminalCapabilities, "wrapTmuxPassthrough");
|
|
125
|
-
const emitter = createWarpEventEmitter({ sessionId: "session-123" });
|
|
126
|
-
|
|
127
|
-
emitter?.emit({ event: "stop" });
|
|
128
|
-
|
|
129
|
-
expect(tmux).toHaveBeenCalledTimes(1);
|
|
130
|
-
expect(wrap).toHaveBeenCalledWith(expect.stringContaining("warp://cli-agent"));
|
|
131
|
-
const written = write.mock.calls[0]?.[0] as string;
|
|
132
|
-
// Real DCS wrap ends with ST; attention events append outer BEL after it.
|
|
133
|
-
expect(written.startsWith("\x1bPtmux;")).toBe(true);
|
|
134
|
-
expect(written.endsWith("\x1b\\\x07")).toBe(true);
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
const attentionEvents = ["stop", "stop_failure", "permission_request", "question_asked"] as const;
|
|
138
|
-
const nonAttentionEvents = [
|
|
139
|
-
"session_start",
|
|
140
|
-
"prompt_submit",
|
|
141
|
-
"tool_complete",
|
|
142
|
-
"permission_replied",
|
|
143
|
-
"custom_event",
|
|
144
|
-
] as const;
|
|
145
|
-
|
|
146
|
-
for (const eventName of attentionEvents) {
|
|
147
|
-
it(`rings tmux outer BEL for attention event ${eventName}`, () => {
|
|
148
|
-
enableWarpProtocol();
|
|
149
|
-
const write = vi.spyOn(process.stdout, "write").mockReturnValue(true);
|
|
150
|
-
vi.spyOn(terminalCapabilities, "isInsideTmux").mockReturnValue(true);
|
|
151
|
-
const emitter = createWarpEventEmitter({ sessionId: "session-123" });
|
|
152
|
-
|
|
153
|
-
emitter?.emit({ event: eventName });
|
|
154
|
-
|
|
155
|
-
const written = write.mock.calls[0]?.[0] as string;
|
|
156
|
-
// Outer BEL after DCS ST; OSC's own \x07 is interior to the passthrough.
|
|
157
|
-
expect(written.startsWith("\x1bPtmux;")).toBe(true);
|
|
158
|
-
expect(written.endsWith("\x07\x1b\\\x07")).toBe(true);
|
|
159
|
-
expect(written.slice(0, -1).endsWith("\x07\x1b\\")).toBe(true);
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
for (const eventName of nonAttentionEvents) {
|
|
164
|
-
it(`does not ring tmux outer BEL for non-attention event ${eventName}`, () => {
|
|
165
|
-
enableWarpProtocol();
|
|
166
|
-
const write = vi.spyOn(process.stdout, "write").mockReturnValue(true);
|
|
167
|
-
vi.spyOn(terminalCapabilities, "isInsideTmux").mockReturnValue(true);
|
|
168
|
-
const emitter = createWarpEventEmitter({ sessionId: "session-123" });
|
|
169
|
-
|
|
170
|
-
emitter?.emit({ event: eventName });
|
|
171
|
-
|
|
172
|
-
const written = write.mock.calls[0]?.[0] as string;
|
|
173
|
-
// DCS ST only — OSC terminator is inside the wrap, not an outer BEL.
|
|
174
|
-
expect(written.startsWith("\x1bPtmux;")).toBe(true);
|
|
175
|
-
expect(written.endsWith("\x07\x1b\\")).toBe(true);
|
|
176
|
-
expect(written.endsWith("\x07\x1b\\\x07")).toBe(false);
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
it("leaves direct-terminal OSC unchanged without outer BEL after OSC terminator", () => {
|
|
181
|
-
enableWarpProtocol();
|
|
182
|
-
const write = vi.spyOn(process.stdout, "write").mockReturnValue(true);
|
|
183
|
-
vi.spyOn(terminalCapabilities, "isInsideTmux").mockReturnValue(false);
|
|
184
|
-
const wrap = vi.spyOn(terminalCapabilities, "wrapTmuxPassthrough");
|
|
185
|
-
const emitter = createWarpEventEmitter({ sessionId: "session-123" });
|
|
186
|
-
|
|
187
|
-
for (const eventName of [...attentionEvents, ...nonAttentionEvents]) {
|
|
188
|
-
write.mockClear();
|
|
189
|
-
emitter?.emit({ event: eventName });
|
|
190
|
-
const written = write.mock.calls[0]?.[0] as string;
|
|
191
|
-
expect(written.startsWith(OSC_PREFIX)).toBe(true);
|
|
192
|
-
expect(written.endsWith("\x07")).toBe(true);
|
|
193
|
-
// Exactly one trailing BEL (OSC terminator), not an extra attention BEL.
|
|
194
|
-
expect(written.endsWith("\x07\x07")).toBe(false);
|
|
195
|
-
expect(wrap).not.toHaveBeenCalled();
|
|
196
|
-
}
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
it("creates an emitter from protocol version alone even when terminal id is base", () => {
|
|
200
|
-
const write = vi.spyOn(process.stdout, "write").mockReturnValue(true);
|
|
201
|
-
vi.spyOn(terminalCapabilities, "isInsideTmux").mockReturnValue(false);
|
|
202
|
-
enableWarpProtocol("base");
|
|
203
|
-
|
|
204
|
-
const emitter = createWarpEventEmitter({ sessionId: "session-123" });
|
|
205
|
-
expect(emitter).toBeDefined();
|
|
206
|
-
emitter?.emit({ event: "stop" });
|
|
207
|
-
|
|
208
|
-
const body = parseBodies(write)[0];
|
|
209
|
-
expect(body).toMatchObject({
|
|
210
|
-
event: "stop",
|
|
211
|
-
session_id: "session-123",
|
|
212
|
-
project,
|
|
213
|
-
});
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
it("does not emit without a negotiated protocol version", () => {
|
|
217
|
-
const write = vi.spyOn(process.stdout, "write").mockReturnValue(true);
|
|
218
|
-
enableWarpProtocol();
|
|
219
|
-
|
|
220
|
-
delete process.env.WARP_CLI_AGENT_PROTOCOL_VERSION;
|
|
221
|
-
expect(createWarpEventEmitter({ sessionId: "session-123" })).toBeUndefined();
|
|
222
|
-
|
|
223
|
-
process.env.WARP_CLI_AGENT_PROTOCOL_VERSION = "0";
|
|
224
|
-
expect(createWarpEventEmitter({ sessionId: "session-123" })).toBeUndefined();
|
|
225
|
-
|
|
226
|
-
process.env.WARP_CLI_AGENT_PROTOCOL_VERSION = "not-a-number";
|
|
227
|
-
expect(createWarpEventEmitter({ sessionId: "session-123" })).toBeUndefined();
|
|
228
|
-
expect(write).not.toHaveBeenCalled();
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
it("uses session cwd for envelope cwd and project", () => {
|
|
232
|
-
enableWarpProtocol();
|
|
233
|
-
const write = vi.spyOn(process.stdout, "write").mockReturnValue(true);
|
|
234
|
-
vi.spyOn(terminalCapabilities, "isInsideTmux").mockReturnValue(false);
|
|
235
|
-
const sessionCwd = "/tmp/session-project-root";
|
|
236
|
-
const sessionProject = path.basename(sessionCwd);
|
|
237
|
-
|
|
238
|
-
const emitter = createWarpEventEmitter({
|
|
239
|
-
sessionId: "session-123",
|
|
240
|
-
getCwd: () => sessionCwd,
|
|
241
|
-
});
|
|
242
|
-
emitter?.emit({ event: "stop" });
|
|
243
|
-
|
|
244
|
-
const directBody = parseBodies(write)[0];
|
|
245
|
-
expect(directBody).toMatchObject({
|
|
246
|
-
event: "stop",
|
|
247
|
-
cwd: sessionCwd,
|
|
248
|
-
project: sessionProject,
|
|
249
|
-
});
|
|
250
|
-
expect(directBody?.cwd).not.toBe(process.cwd());
|
|
251
|
-
|
|
252
|
-
write.mockClear();
|
|
253
|
-
const handlers = createHandlers();
|
|
254
|
-
const sessionStart = handlers.get("session_start") as never as (
|
|
255
|
-
event: SessionStartEvent,
|
|
256
|
-
context: ExtensionContext,
|
|
257
|
-
) => void;
|
|
258
|
-
sessionStart({ type: "session_start" }, bridgeContext("session-123", sessionCwd));
|
|
259
|
-
const body = parseBodies(write)[0];
|
|
260
|
-
expect(body).toMatchObject({
|
|
261
|
-
event: "session_start",
|
|
262
|
-
cwd: sessionCwd,
|
|
263
|
-
project: sessionProject,
|
|
264
|
-
});
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
it("does not resubmit prompts for agent continuations", () => {
|
|
268
|
-
enableWarpProtocol();
|
|
269
|
-
const write = vi.spyOn(process.stdout, "write").mockReturnValue(true);
|
|
270
|
-
vi.spyOn(terminalCapabilities, "isInsideTmux").mockReturnValue(false);
|
|
271
|
-
const handlers = createHandlers();
|
|
272
|
-
const context = bridgeContext();
|
|
273
|
-
const sessionStart = handlers.get("session_start") as never as (
|
|
274
|
-
event: SessionStartEvent,
|
|
275
|
-
context: ExtensionContext,
|
|
276
|
-
) => void;
|
|
277
|
-
const messageStart = handlers.get("message_start") as never as (event: MessageStartEvent) => void;
|
|
278
|
-
const agentEnd = handlers.get("agent_end") as never as (event: AgentEndEvent) => void;
|
|
279
|
-
|
|
280
|
-
sessionStart({ type: "session_start" }, context);
|
|
281
|
-
write.mockClear();
|
|
282
|
-
|
|
283
|
-
messageStart(userMessageStart("first user prompt"));
|
|
284
|
-
// Continuations re-emit agent_start without a user message_start; the bridge must not listen.
|
|
285
|
-
expect(handlers.has("agent_start")).toBe(false);
|
|
286
|
-
const writesAfterSubmit = write.mock.calls.length;
|
|
287
|
-
const agentStart = handlers.get("agent_start") as never as ((event: AgentStartEvent) => void) | undefined;
|
|
288
|
-
agentStart?.({ type: "agent_start" });
|
|
289
|
-
expect(write.mock.calls.length).toBe(writesAfterSubmit);
|
|
290
|
-
agentEnd({
|
|
291
|
-
type: "agent_end",
|
|
292
|
-
messages: [{ role: "assistant", content: [{ type: "text", text: "done" }] } as never],
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
const bodies = parseBodies(write);
|
|
296
|
-
expect(bodies.filter(body => body.event === "prompt_submit")).toEqual([
|
|
297
|
-
expect.objectContaining({ event: "prompt_submit", query: "first user prompt", project }),
|
|
298
|
-
]);
|
|
299
|
-
expect(bodies.at(-1)).toMatchObject({
|
|
300
|
-
event: "stop",
|
|
301
|
-
query: "first user prompt",
|
|
302
|
-
response: "done",
|
|
303
|
-
project,
|
|
304
|
-
});
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
it("keeps the active query until queued follow-up begins", () => {
|
|
308
|
-
enableWarpProtocol();
|
|
309
|
-
const write = vi.spyOn(process.stdout, "write").mockReturnValue(true);
|
|
310
|
-
vi.spyOn(terminalCapabilities, "isInsideTmux").mockReturnValue(false);
|
|
311
|
-
const handlers = createHandlers();
|
|
312
|
-
const context = bridgeContext();
|
|
313
|
-
const sessionStart = handlers.get("session_start") as never as (
|
|
314
|
-
event: SessionStartEvent,
|
|
315
|
-
context: ExtensionContext,
|
|
316
|
-
) => void;
|
|
317
|
-
const messageStart = handlers.get("message_start") as never as (event: MessageStartEvent) => void;
|
|
318
|
-
const agentEnd = handlers.get("agent_end") as never as (event: AgentEndEvent) => void;
|
|
319
|
-
const input = handlers.get("input") as never as ((event: InputEvent) => void) | undefined;
|
|
320
|
-
|
|
321
|
-
sessionStart({ type: "session_start" }, context);
|
|
322
|
-
write.mockClear();
|
|
323
|
-
|
|
324
|
-
// Early queued input must not overwrite the current response's query.
|
|
325
|
-
messageStart(userMessageStart("prompt A"));
|
|
326
|
-
input?.({ type: "input", text: "prompt B", source: "interactive" });
|
|
327
|
-
agentEnd({
|
|
328
|
-
type: "agent_end",
|
|
329
|
-
messages: [{ role: "assistant", content: [{ type: "text", text: "answer A" }] } as never],
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
let bodies = parseBodies(write);
|
|
333
|
-
expect(bodies).toEqual([
|
|
334
|
-
expect.objectContaining({ event: "prompt_submit", query: "prompt A", project }),
|
|
335
|
-
expect.objectContaining({ event: "stop", query: "prompt A", response: "answer A", project }),
|
|
336
|
-
]);
|
|
337
|
-
|
|
338
|
-
// After A ends, B's real user message_start owns submit/stop.
|
|
339
|
-
write.mockClear();
|
|
340
|
-
messageStart(userMessageStart("prompt B"));
|
|
341
|
-
agentEnd({
|
|
342
|
-
type: "agent_end",
|
|
343
|
-
messages: [{ role: "assistant", content: [{ type: "text", text: "answer B" }] } as never],
|
|
344
|
-
});
|
|
345
|
-
bodies = parseBodies(write);
|
|
346
|
-
expect(bodies).toEqual([
|
|
347
|
-
expect.objectContaining({ event: "prompt_submit", query: "prompt B", project }),
|
|
348
|
-
expect.objectContaining({ event: "stop", query: "prompt B", response: "answer B", project }),
|
|
349
|
-
]);
|
|
350
|
-
|
|
351
|
-
// Normal drain: B's message_start arrives before agent_end, so the final pair is B.
|
|
352
|
-
write.mockClear();
|
|
353
|
-
messageStart(userMessageStart("prompt C"));
|
|
354
|
-
messageStart(userMessageStart("prompt D"));
|
|
355
|
-
agentEnd({
|
|
356
|
-
type: "agent_end",
|
|
357
|
-
messages: [{ role: "assistant", content: [{ type: "text", text: "answer D" }] } as never],
|
|
358
|
-
});
|
|
359
|
-
bodies = parseBodies(write);
|
|
360
|
-
expect(bodies).toEqual([
|
|
361
|
-
expect.objectContaining({ event: "prompt_submit", query: "prompt C", project }),
|
|
362
|
-
expect.objectContaining({ event: "prompt_submit", query: "prompt D", project }),
|
|
363
|
-
expect.objectContaining({ event: "stop", query: "prompt D", response: "answer D", project }),
|
|
364
|
-
]);
|
|
365
|
-
});
|
|
366
|
-
|
|
367
|
-
it("ignores agent-attributed user-role steers", () => {
|
|
368
|
-
enableWarpProtocol();
|
|
369
|
-
const write = vi.spyOn(process.stdout, "write").mockReturnValue(true);
|
|
370
|
-
vi.spyOn(terminalCapabilities, "isInsideTmux").mockReturnValue(false);
|
|
371
|
-
const handlers = createHandlers();
|
|
372
|
-
const context = bridgeContext();
|
|
373
|
-
const sessionStart = handlers.get("session_start") as never as (
|
|
374
|
-
event: SessionStartEvent,
|
|
375
|
-
context: ExtensionContext,
|
|
376
|
-
) => void;
|
|
377
|
-
const messageStart = handlers.get("message_start") as never as (event: MessageStartEvent) => void;
|
|
378
|
-
const agentEnd = handlers.get("agent_end") as never as (event: AgentEndEvent) => void;
|
|
379
|
-
|
|
380
|
-
sessionStart({ type: "session_start" }, context);
|
|
381
|
-
write.mockClear();
|
|
382
|
-
|
|
383
|
-
messageStart(userMessageStart("prompt A"));
|
|
384
|
-
messageStart(userMessageStart("agent steer", { attribution: "agent" }));
|
|
385
|
-
agentEnd({
|
|
386
|
-
type: "agent_end",
|
|
387
|
-
messages: [{ role: "assistant", content: [{ type: "text", text: "answer A" }] } as never],
|
|
388
|
-
});
|
|
389
|
-
|
|
390
|
-
const bodies = parseBodies(write);
|
|
391
|
-
expect(bodies.filter(body => body.event === "prompt_submit")).toEqual([
|
|
392
|
-
expect.objectContaining({ event: "prompt_submit", query: "prompt A", project }),
|
|
393
|
-
]);
|
|
394
|
-
expect(bodies.at(-1)).toMatchObject({
|
|
395
|
-
event: "stop",
|
|
396
|
-
query: "prompt A",
|
|
397
|
-
response: "answer A",
|
|
398
|
-
project,
|
|
399
|
-
});
|
|
400
|
-
});
|
|
401
|
-
|
|
402
|
-
it("treats user-attributed skill prompts as submissions", () => {
|
|
403
|
-
enableWarpProtocol();
|
|
404
|
-
const write = vi.spyOn(process.stdout, "write").mockReturnValue(true);
|
|
405
|
-
vi.spyOn(terminalCapabilities, "isInsideTmux").mockReturnValue(false);
|
|
406
|
-
const handlers = createHandlers();
|
|
407
|
-
const context = bridgeContext();
|
|
408
|
-
const sessionStart = handlers.get("session_start") as never as (
|
|
409
|
-
event: SessionStartEvent,
|
|
410
|
-
context: ExtensionContext,
|
|
411
|
-
) => void;
|
|
412
|
-
const messageStart = handlers.get("message_start") as never as (event: MessageStartEvent) => void;
|
|
413
|
-
const agentEnd = handlers.get("agent_end") as never as (event: AgentEndEvent) => void;
|
|
414
|
-
|
|
415
|
-
sessionStart({ type: "session_start" }, context);
|
|
416
|
-
write.mockClear();
|
|
417
|
-
|
|
418
|
-
messageStart(skillPromptStart("skill body as query", "user"));
|
|
419
|
-
agentEnd({
|
|
420
|
-
type: "agent_end",
|
|
421
|
-
messages: [{ role: "assistant", content: [{ type: "text", text: "skill answer" }] } as never],
|
|
422
|
-
});
|
|
423
|
-
|
|
424
|
-
let bodies = parseBodies(write);
|
|
425
|
-
expect(bodies).toEqual([
|
|
426
|
-
expect.objectContaining({ event: "prompt_submit", query: "skill body as query", project }),
|
|
427
|
-
expect.objectContaining({
|
|
428
|
-
event: "stop",
|
|
429
|
-
query: "skill body as query",
|
|
430
|
-
response: "skill answer",
|
|
431
|
-
project,
|
|
432
|
-
}),
|
|
433
|
-
]);
|
|
434
|
-
|
|
435
|
-
// Agent-attributed skill-shaped custom messages are not user submissions.
|
|
436
|
-
write.mockClear();
|
|
437
|
-
messageStart(userMessageStart("prompt A"));
|
|
438
|
-
messageStart(skillPromptStart("auto skill", "agent"));
|
|
439
|
-
agentEnd({
|
|
440
|
-
type: "agent_end",
|
|
441
|
-
messages: [{ role: "assistant", content: [{ type: "text", text: "answer A" }] } as never],
|
|
442
|
-
});
|
|
443
|
-
bodies = parseBodies(write);
|
|
444
|
-
expect(bodies.filter(body => body.event === "prompt_submit")).toEqual([
|
|
445
|
-
expect.objectContaining({ event: "prompt_submit", query: "prompt A", project }),
|
|
446
|
-
]);
|
|
447
|
-
expect(bodies.at(-1)).toMatchObject({
|
|
448
|
-
event: "stop",
|
|
449
|
-
query: "prompt A",
|
|
450
|
-
response: "answer A",
|
|
451
|
-
project,
|
|
452
|
-
});
|
|
453
|
-
});
|
|
454
|
-
|
|
455
|
-
it("falls back to non-silent assistant errorMessage on empty stop responses", () => {
|
|
456
|
-
enableWarpProtocol();
|
|
457
|
-
const write = vi.spyOn(process.stdout, "write").mockReturnValue(true);
|
|
458
|
-
vi.spyOn(terminalCapabilities, "isInsideTmux").mockReturnValue(false);
|
|
459
|
-
const handlers = createHandlers();
|
|
460
|
-
const context = bridgeContext();
|
|
461
|
-
const sessionStart = handlers.get("session_start") as never as (
|
|
462
|
-
event: SessionStartEvent,
|
|
463
|
-
context: ExtensionContext,
|
|
464
|
-
) => void;
|
|
465
|
-
const messageStart = handlers.get("message_start") as never as (event: MessageStartEvent) => void;
|
|
466
|
-
const agentEnd = handlers.get("agent_end") as never as (event: AgentEndEvent) => void;
|
|
467
|
-
|
|
468
|
-
sessionStart({ type: "session_start" }, context);
|
|
469
|
-
write.mockClear();
|
|
470
|
-
|
|
471
|
-
messageStart(userMessageStart("prompt error"));
|
|
472
|
-
agentEnd({
|
|
473
|
-
type: "agent_end",
|
|
474
|
-
messages: [
|
|
475
|
-
{
|
|
476
|
-
role: "assistant",
|
|
477
|
-
content: [],
|
|
478
|
-
stopReason: "error",
|
|
479
|
-
errorMessage: "rate limited",
|
|
480
|
-
} as never,
|
|
481
|
-
],
|
|
482
|
-
});
|
|
483
|
-
expect(parseBodies(write).at(-1)).toMatchObject({
|
|
484
|
-
event: "stop_failure",
|
|
485
|
-
error_type: "error",
|
|
486
|
-
query: "prompt error",
|
|
487
|
-
response: "rate limited",
|
|
488
|
-
});
|
|
489
|
-
|
|
490
|
-
// Normal text content is preferred over errorMessage; error stopReason still fails the turn.
|
|
491
|
-
write.mockClear();
|
|
492
|
-
messageStart(userMessageStart("prompt text"));
|
|
493
|
-
agentEnd({
|
|
494
|
-
type: "agent_end",
|
|
495
|
-
messages: [
|
|
496
|
-
{
|
|
497
|
-
role: "assistant",
|
|
498
|
-
content: [{ type: "text", text: "visible answer" }],
|
|
499
|
-
stopReason: "error",
|
|
500
|
-
errorMessage: "rate limited",
|
|
501
|
-
} as never,
|
|
502
|
-
],
|
|
503
|
-
});
|
|
504
|
-
expect(parseBodies(write).at(-1)).toMatchObject({
|
|
505
|
-
event: "stop_failure",
|
|
506
|
-
error_type: "error",
|
|
507
|
-
query: "prompt text",
|
|
508
|
-
response: "visible answer",
|
|
509
|
-
});
|
|
510
|
-
|
|
511
|
-
// Silent abort marker must not surface as the stop response or fail the turn.
|
|
512
|
-
write.mockClear();
|
|
513
|
-
messageStart(userMessageStart("prompt silent"));
|
|
514
|
-
agentEnd({
|
|
515
|
-
type: "agent_end",
|
|
516
|
-
messages: [
|
|
517
|
-
{
|
|
518
|
-
role: "assistant",
|
|
519
|
-
content: [],
|
|
520
|
-
stopReason: "aborted",
|
|
521
|
-
errorMessage: SILENT_ABORT_MARKER,
|
|
522
|
-
} as never,
|
|
523
|
-
],
|
|
524
|
-
});
|
|
525
|
-
expect(parseBodies(write).at(-1)).toMatchObject({
|
|
526
|
-
event: "stop",
|
|
527
|
-
query: "prompt silent",
|
|
528
|
-
response: "",
|
|
529
|
-
});
|
|
530
|
-
expect(parseBodies(write).at(-1)).not.toHaveProperty("error_type");
|
|
531
|
-
|
|
532
|
-
// User interrupt labels stay suppressed; non-user abort reasons surface as failures.
|
|
533
|
-
write.mockClear();
|
|
534
|
-
messageStart(userMessageStart("prompt interrupt"));
|
|
535
|
-
agentEnd({
|
|
536
|
-
type: "agent_end",
|
|
537
|
-
messages: [
|
|
538
|
-
{
|
|
539
|
-
role: "assistant",
|
|
540
|
-
content: [],
|
|
541
|
-
stopReason: "aborted",
|
|
542
|
-
errorMessage: USER_INTERRUPT_LABEL,
|
|
543
|
-
} as never,
|
|
544
|
-
],
|
|
545
|
-
});
|
|
546
|
-
expect(parseBodies(write).at(-1)).toMatchObject({
|
|
547
|
-
event: "stop",
|
|
548
|
-
query: "prompt interrupt",
|
|
549
|
-
response: "",
|
|
550
|
-
});
|
|
551
|
-
|
|
552
|
-
write.mockClear();
|
|
553
|
-
messageStart(userMessageStart("prompt aborted"));
|
|
554
|
-
agentEnd({
|
|
555
|
-
type: "agent_end",
|
|
556
|
-
messages: [
|
|
557
|
-
{
|
|
558
|
-
role: "assistant",
|
|
559
|
-
content: [],
|
|
560
|
-
stopReason: "aborted",
|
|
561
|
-
errorMessage: "provider cancelled stream",
|
|
562
|
-
} as never,
|
|
563
|
-
],
|
|
564
|
-
});
|
|
565
|
-
expect(parseBodies(write).at(-1)).toMatchObject({
|
|
566
|
-
event: "stop_failure",
|
|
567
|
-
error_type: "aborted",
|
|
568
|
-
query: "prompt aborted",
|
|
569
|
-
response: "provider cancelled stream",
|
|
570
|
-
});
|
|
571
|
-
});
|
|
572
|
-
|
|
573
|
-
it("suppresses stop OSC when agent_end willContinue", () => {
|
|
574
|
-
enableWarpProtocol();
|
|
575
|
-
const write = vi.spyOn(process.stdout, "write").mockReturnValue(true);
|
|
576
|
-
vi.spyOn(terminalCapabilities, "isInsideTmux").mockReturnValue(false);
|
|
577
|
-
const handlers = createHandlers();
|
|
578
|
-
const context = bridgeContext();
|
|
579
|
-
const sessionStart = handlers.get("session_start") as never as (
|
|
580
|
-
event: SessionStartEvent,
|
|
581
|
-
context: ExtensionContext,
|
|
582
|
-
) => void;
|
|
583
|
-
const messageStart = handlers.get("message_start") as never as (event: MessageStartEvent) => void;
|
|
584
|
-
const agentEnd = handlers.get("agent_end") as never as (event: AgentEndEvent) => void;
|
|
585
|
-
|
|
586
|
-
sessionStart({ type: "session_start" }, context);
|
|
587
|
-
write.mockClear();
|
|
588
|
-
|
|
589
|
-
messageStart(userMessageStart("retry me"));
|
|
590
|
-
const afterSubmit = write.mock.calls.length;
|
|
591
|
-
agentEnd({
|
|
592
|
-
type: "agent_end",
|
|
593
|
-
willContinue: true,
|
|
594
|
-
messages: [
|
|
595
|
-
{
|
|
596
|
-
role: "assistant",
|
|
597
|
-
content: [],
|
|
598
|
-
stopReason: "error",
|
|
599
|
-
errorMessage: "rate limited",
|
|
600
|
-
} as never,
|
|
601
|
-
],
|
|
602
|
-
});
|
|
603
|
-
expect(write.mock.calls.length).toBe(afterSubmit);
|
|
604
|
-
expect(parseBodies(write).some(body => body.event === "stop" || body.event === "stop_failure")).toBe(false);
|
|
605
|
-
|
|
606
|
-
// Without the flag, the same messages still emit a terminal failure stop.
|
|
607
|
-
write.mockClear();
|
|
608
|
-
messageStart(userMessageStart("final error"));
|
|
609
|
-
agentEnd({
|
|
610
|
-
type: "agent_end",
|
|
611
|
-
messages: [
|
|
612
|
-
{
|
|
613
|
-
role: "assistant",
|
|
614
|
-
content: [],
|
|
615
|
-
stopReason: "error",
|
|
616
|
-
errorMessage: "rate limited",
|
|
617
|
-
} as never,
|
|
618
|
-
],
|
|
619
|
-
});
|
|
620
|
-
expect(parseBodies(write).at(-1)).toMatchObject({
|
|
621
|
-
event: "stop_failure",
|
|
622
|
-
query: "final error",
|
|
623
|
-
response: "rate limited",
|
|
624
|
-
});
|
|
625
|
-
});
|
|
626
|
-
|
|
627
|
-
it("caps prompt queries and stop responses at 200 Unicode code points without breaking JSON", () => {
|
|
628
|
-
enableWarpProtocol();
|
|
629
|
-
const write = vi.spyOn(process.stdout, "write").mockReturnValue(true);
|
|
630
|
-
vi.spyOn(terminalCapabilities, "isInsideTmux").mockReturnValue(false);
|
|
631
|
-
const handlers = createHandlers();
|
|
632
|
-
const context = bridgeContext();
|
|
633
|
-
const sessionStart = handlers.get("session_start") as never as (
|
|
634
|
-
event: SessionStartEvent,
|
|
635
|
-
context: ExtensionContext,
|
|
636
|
-
) => void;
|
|
637
|
-
const messageStart = handlers.get("message_start") as never as (event: MessageStartEvent) => void;
|
|
638
|
-
const agentEnd = handlers.get("agent_end") as never as (event: AgentEndEvent) => void;
|
|
639
|
-
|
|
640
|
-
sessionStart({ type: "session_start" }, context);
|
|
641
|
-
write.mockClear();
|
|
642
|
-
|
|
643
|
-
const query = `${"q".repeat(199)}😀tail`;
|
|
644
|
-
const response = `${"a".repeat(199)}😀tail`;
|
|
645
|
-
messageStart(userMessageStart(query));
|
|
646
|
-
agentEnd({
|
|
647
|
-
type: "agent_end",
|
|
648
|
-
messages: [{ role: "assistant", content: [{ type: "text", text: response }] } as never],
|
|
649
|
-
});
|
|
650
|
-
|
|
651
|
-
const bodies = parseBodies(write);
|
|
652
|
-
const promptSubmit = bodies.find(body => body.event === "prompt_submit");
|
|
653
|
-
const stop = bodies.find(body => body.event === "stop");
|
|
654
|
-
expect(promptSubmit?.query).toBe(`${"q".repeat(199)}😀`);
|
|
655
|
-
expect(Array.from(promptSubmit?.query as string)).toHaveLength(200);
|
|
656
|
-
expect(stop?.query).toBe(`${"q".repeat(199)}😀`);
|
|
657
|
-
expect(stop?.response).toBe(`${"a".repeat(199)}😀`);
|
|
658
|
-
expect(Array.from(stop?.response as string)).toHaveLength(200);
|
|
659
|
-
});
|
|
660
|
-
|
|
661
|
-
it("rebuilds the emitter and resets prompt state after a session switch", () => {
|
|
662
|
-
enableWarpProtocol();
|
|
663
|
-
const write = vi.spyOn(process.stdout, "write").mockReturnValue(true);
|
|
664
|
-
vi.spyOn(terminalCapabilities, "isInsideTmux").mockReturnValue(false);
|
|
665
|
-
const handlers = createHandlers();
|
|
666
|
-
let sessionId = "session-old";
|
|
667
|
-
const context = {
|
|
668
|
-
sessionManager: {
|
|
669
|
-
getSessionId: () => sessionId,
|
|
670
|
-
getCwd: () => process.cwd(),
|
|
671
|
-
},
|
|
672
|
-
} as never as ExtensionContext;
|
|
673
|
-
const sessionStart = handlers.get("session_start") as never as (
|
|
674
|
-
event: SessionStartEvent,
|
|
675
|
-
context: ExtensionContext,
|
|
676
|
-
) => void;
|
|
677
|
-
const sessionSwitch = handlers.get("session_switch") as never as (
|
|
678
|
-
event: SessionSwitchEvent,
|
|
679
|
-
context: ExtensionContext,
|
|
680
|
-
) => void;
|
|
681
|
-
const messageStart = handlers.get("message_start") as never as (event: MessageStartEvent) => void;
|
|
682
|
-
const agentEnd = handlers.get("agent_end") as never as (event: AgentEndEvent) => void;
|
|
683
|
-
|
|
684
|
-
sessionStart({ type: "session_start" }, context);
|
|
685
|
-
messageStart(userMessageStart("old prompt"));
|
|
686
|
-
sessionId = "session-new";
|
|
687
|
-
write.mockClear();
|
|
688
|
-
|
|
689
|
-
sessionSwitch({ type: "session_switch", reason: "new", previousSessionFile: undefined }, context);
|
|
690
|
-
agentEnd({
|
|
691
|
-
type: "agent_end",
|
|
692
|
-
messages: [{ role: "assistant", content: [{ type: "text", text: "orphan stop" }] } as never],
|
|
693
|
-
});
|
|
694
|
-
|
|
695
|
-
const bodies = parseBodies(write);
|
|
696
|
-
expect(bodies).toEqual([
|
|
697
|
-
expect.objectContaining({ event: "session_start", session_id: "session-new", project }),
|
|
698
|
-
expect.objectContaining({
|
|
699
|
-
event: "stop",
|
|
700
|
-
session_id: "session-new",
|
|
701
|
-
response: "orphan stop",
|
|
702
|
-
project,
|
|
703
|
-
}),
|
|
704
|
-
]);
|
|
705
|
-
expect(bodies[1]).not.toHaveProperty("query");
|
|
706
|
-
});
|
|
707
|
-
|
|
708
|
-
it("rebuilds the emitter and resets prompt state after a session branch", () => {
|
|
709
|
-
enableWarpProtocol();
|
|
710
|
-
const write = vi.spyOn(process.stdout, "write").mockReturnValue(true);
|
|
711
|
-
vi.spyOn(terminalCapabilities, "isInsideTmux").mockReturnValue(false);
|
|
712
|
-
const handlers = createHandlers();
|
|
713
|
-
let sessionId = "session-old";
|
|
714
|
-
const context = {
|
|
715
|
-
sessionManager: {
|
|
716
|
-
getSessionId: () => sessionId,
|
|
717
|
-
getCwd: () => process.cwd(),
|
|
718
|
-
},
|
|
719
|
-
} as never as ExtensionContext;
|
|
720
|
-
const sessionStart = handlers.get("session_start") as never as (
|
|
721
|
-
event: SessionStartEvent,
|
|
722
|
-
context: ExtensionContext,
|
|
723
|
-
) => void;
|
|
724
|
-
const sessionBranch = handlers.get("session_branch") as never as (
|
|
725
|
-
event: SessionBranchEvent,
|
|
726
|
-
context: ExtensionContext,
|
|
727
|
-
) => void;
|
|
728
|
-
const messageStart = handlers.get("message_start") as never as (event: MessageStartEvent) => void;
|
|
729
|
-
const agentEnd = handlers.get("agent_end") as never as (event: AgentEndEvent) => void;
|
|
730
|
-
|
|
731
|
-
sessionStart({ type: "session_start" }, context);
|
|
732
|
-
messageStart(userMessageStart("old prompt"));
|
|
733
|
-
sessionId = "session-branched";
|
|
734
|
-
write.mockClear();
|
|
735
|
-
|
|
736
|
-
sessionBranch({ type: "session_branch", previousSessionFile: undefined }, context);
|
|
737
|
-
agentEnd({
|
|
738
|
-
type: "agent_end",
|
|
739
|
-
messages: [{ role: "assistant", content: [{ type: "text", text: "orphan stop" }] } as never],
|
|
740
|
-
});
|
|
741
|
-
|
|
742
|
-
const bodies = parseBodies(write);
|
|
743
|
-
expect(bodies).toEqual([
|
|
744
|
-
expect.objectContaining({ event: "session_start", session_id: "session-branched", project }),
|
|
745
|
-
expect.objectContaining({
|
|
746
|
-
event: "stop",
|
|
747
|
-
session_id: "session-branched",
|
|
748
|
-
response: "orphan stop",
|
|
749
|
-
project,
|
|
750
|
-
}),
|
|
751
|
-
]);
|
|
752
|
-
expect(bodies[1]).not.toHaveProperty("query");
|
|
753
|
-
});
|
|
754
|
-
|
|
755
|
-
it("maps approval requests to Warp permission requests", () => {
|
|
756
|
-
enableWarpProtocol();
|
|
757
|
-
const write = vi.spyOn(process.stdout, "write").mockReturnValue(true);
|
|
758
|
-
vi.spyOn(terminalCapabilities, "isInsideTmux").mockReturnValue(false);
|
|
759
|
-
const handlers = createHandlers();
|
|
760
|
-
const sessionStart = handlers.get("session_start") as never as (
|
|
761
|
-
event: SessionStartEvent,
|
|
762
|
-
context: ExtensionContext,
|
|
763
|
-
) => void;
|
|
764
|
-
sessionStart({ type: "session_start" }, bridgeContext());
|
|
765
|
-
write.mockClear();
|
|
766
|
-
|
|
767
|
-
const approvalRequested = handlers.get("tool_approval_requested") as never as (
|
|
768
|
-
event: ToolApprovalRequestedEvent,
|
|
769
|
-
) => void;
|
|
770
|
-
approvalRequested({
|
|
771
|
-
type: "tool_approval_requested",
|
|
772
|
-
sessionId: "session-123",
|
|
773
|
-
toolCallId: "tool-call-123",
|
|
774
|
-
toolName: "bash",
|
|
775
|
-
approvalMode: "always-ask",
|
|
776
|
-
});
|
|
777
|
-
|
|
778
|
-
const osc = write.mock.calls[0]?.[0] as string;
|
|
779
|
-
expect(osc.startsWith(OSC_PREFIX)).toBe(true);
|
|
780
|
-
expect(osc.endsWith("\x07")).toBe(true);
|
|
781
|
-
const body = JSON.parse(osc.slice(OSC_PREFIX.length, osc.length - 1));
|
|
782
|
-
expect(body).toEqual({
|
|
783
|
-
event: "permission_request",
|
|
784
|
-
tool_name: "bash",
|
|
785
|
-
summary: "omp wants to run bash",
|
|
786
|
-
v: 1,
|
|
787
|
-
agent: "omp",
|
|
788
|
-
session_id: "session-123",
|
|
789
|
-
cwd: process.cwd(),
|
|
790
|
-
project,
|
|
791
|
-
plugin_version: VERSION,
|
|
792
|
-
});
|
|
793
|
-
});
|
|
794
|
-
});
|