@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.
Files changed (244) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/dist/{CHANGELOG-qs3vd6xf.md → CHANGELOG-5k4dq4g6.md} +54 -0
  3. package/dist/cli.js +3342 -3274
  4. package/dist/types/capability/index.d.ts +1 -1
  5. package/dist/types/capability/types.d.ts +23 -1
  6. package/dist/types/config/settings-schema.d.ts +39 -2
  7. package/dist/types/cursor.d.ts +2 -1
  8. package/dist/types/extensibility/extensions/runner.d.ts +4 -0
  9. package/dist/types/extensibility/shared-events.d.ts +18 -1
  10. package/dist/types/internal-urls/mcp-protocol.d.ts +3 -2
  11. package/dist/types/internal-urls/parse.d.ts +12 -0
  12. package/dist/types/internal-urls/router.d.ts +6 -0
  13. package/dist/types/internal-urls/types.d.ts +6 -0
  14. package/dist/types/lsp/config.d.ts +1 -0
  15. package/dist/types/lsp/types.d.ts +2 -0
  16. package/dist/types/mcp/manager.d.ts +5 -0
  17. package/dist/types/mcp/tool-bridge.d.ts +13 -0
  18. package/dist/types/modes/components/custom-editor.d.ts +5 -0
  19. package/dist/types/modes/controllers/extension-ui-controller.d.ts +1 -1
  20. package/dist/types/modes/rpc/rpc-mode.d.ts +2 -0
  21. package/dist/types/sdk.d.ts +3 -1
  22. package/dist/types/session/agent-session-types.d.ts +8 -6
  23. package/dist/types/session/agent-session.d.ts +28 -1
  24. package/dist/types/session/model-controls.d.ts +4 -1
  25. package/dist/types/session/session-advisors.d.ts +7 -1
  26. package/dist/types/session/session-tools.d.ts +44 -6
  27. package/dist/types/session/streaming-output.d.ts +7 -2
  28. package/dist/types/session/tool-choice-queue.d.ts +6 -4
  29. package/dist/types/session/turn-recovery.d.ts +5 -3
  30. package/dist/types/task/index.d.ts +1 -1
  31. package/dist/types/task/types.d.ts +3 -11
  32. package/dist/types/thinking.d.ts +21 -2
  33. package/dist/types/tools/index.d.ts +8 -3
  34. package/dist/types/tools/output-meta.d.ts +5 -0
  35. package/dist/types/tools/read.d.ts +9 -1
  36. package/dist/types/tools/xdev.d.ts +53 -67
  37. package/dist/types/utils/cpuprofile.d.ts +51 -0
  38. package/dist/types/utils/inspect-image-mode.d.ts +29 -0
  39. package/dist/types/utils/profile-tree.d.ts +47 -0
  40. package/dist/types/utils/sample-profile.d.ts +67 -0
  41. package/dist/types/utils/title-generator.d.ts +17 -16
  42. package/package.json +12 -12
  43. package/src/capability/index.ts +43 -12
  44. package/src/capability/mcp.ts +21 -0
  45. package/src/capability/types.ts +20 -1
  46. package/src/cli/read-cli.ts +44 -2
  47. package/src/config/settings-schema.ts +42 -2
  48. package/src/config/settings.ts +35 -0
  49. package/src/cursor.ts +4 -3
  50. package/src/discovery/builtin-rules/index.ts +2 -0
  51. package/src/discovery/builtin-rules/ts-no-local-is-record.md +48 -0
  52. package/src/eval/py/runner.py +16 -2
  53. package/src/extensibility/extensions/runner.ts +117 -5
  54. package/src/extensibility/extensions/types.ts +0 -1
  55. package/src/extensibility/extensions/wrapper.ts +74 -42
  56. package/src/extensibility/hooks/tool-wrapper.ts +11 -4
  57. package/src/extensibility/hooks/types.ts +0 -1
  58. package/src/extensibility/shared-events.ts +18 -1
  59. package/src/internal-urls/mcp-protocol.ts +17 -3
  60. package/src/internal-urls/parse.ts +31 -0
  61. package/src/internal-urls/router.ts +24 -4
  62. package/src/internal-urls/types.ts +6 -0
  63. package/src/live/transport.ts +2 -2
  64. package/src/lsp/client.ts +2 -2
  65. package/src/lsp/config.ts +4 -0
  66. package/src/lsp/types.ts +2 -0
  67. package/src/mcp/config.ts +26 -14
  68. package/src/mcp/manager.ts +26 -9
  69. package/src/mcp/tool-bridge.ts +52 -1
  70. package/src/memories/index.ts +25 -6
  71. package/src/modes/components/custom-editor.ts +39 -16
  72. package/src/modes/components/status-line/segments.ts +3 -1
  73. package/src/modes/components/tips.txt +2 -1
  74. package/src/modes/components/tool-execution.ts +8 -7
  75. package/src/modes/controllers/command-controller.ts +10 -10
  76. package/src/modes/controllers/extension-ui-controller.ts +7 -7
  77. package/src/modes/controllers/selector-controller.ts +7 -2
  78. package/src/modes/rpc/rpc-mode.ts +60 -47
  79. package/src/prompts/steering/user-interjection.md +2 -5
  80. package/src/prompts/tools/task.md +4 -2
  81. package/src/sdk.ts +64 -58
  82. package/src/session/agent-session-types.ts +8 -5
  83. package/src/session/agent-session.ts +144 -11
  84. package/src/session/model-controls.ts +48 -12
  85. package/src/session/session-advisors.ts +30 -14
  86. package/src/session/session-listing.ts +66 -4
  87. package/src/session/session-tools.ts +162 -54
  88. package/src/session/streaming-output.ts +18 -6
  89. package/src/session/tool-choice-queue.ts +19 -4
  90. package/src/session/turn-recovery.ts +25 -6
  91. package/src/slash-commands/builtin-registry.ts +69 -0
  92. package/src/task/executor.ts +11 -3
  93. package/src/task/index.ts +43 -32
  94. package/src/task/types.ts +12 -17
  95. package/src/thinking.ts +68 -5
  96. package/src/tools/bash.ts +16 -9
  97. package/src/tools/index.ts +36 -16
  98. package/src/tools/output-meta.ts +20 -0
  99. package/src/tools/read.ts +89 -15
  100. package/src/tools/write.ts +16 -7
  101. package/src/tools/xdev.ts +198 -210
  102. package/src/utils/cpuprofile.ts +235 -0
  103. package/src/utils/inspect-image-mode.ts +39 -0
  104. package/src/utils/profile-tree.ts +111 -0
  105. package/src/utils/sample-profile.ts +437 -0
  106. package/src/utils/title-generator.ts +88 -34
  107. package/dist/types/advisor/__tests__/advisor.test.d.ts +0 -1
  108. package/dist/types/advisor/__tests__/config.test.d.ts +0 -1
  109. package/dist/types/advisor/__tests__/emission-guard.test.d.ts +0 -1
  110. package/dist/types/cli/__tests__/auth-gateway-catalog.test.d.ts +0 -1
  111. package/dist/types/cli/update-cli.test.d.ts +0 -1
  112. package/dist/types/config/__tests__/model-registry.test.d.ts +0 -1
  113. package/dist/types/eval/__tests__/agent-bridge.test.d.ts +0 -1
  114. package/dist/types/eval/__tests__/bridge-timeout.test.d.ts +0 -1
  115. package/dist/types/eval/__tests__/budget-bridge.test.d.ts +0 -1
  116. package/dist/types/eval/__tests__/completion-bridge.test.d.ts +0 -1
  117. package/dist/types/eval/__tests__/helpers-local-roots.test.d.ts +0 -1
  118. package/dist/types/eval/__tests__/idle-timeout.test.d.ts +0 -1
  119. package/dist/types/eval/__tests__/js-context-manager.test.d.ts +0 -1
  120. package/dist/types/eval/__tests__/julia-prelude.test.d.ts +0 -1
  121. package/dist/types/eval/__tests__/kernel-spawn.test.d.ts +0 -1
  122. package/dist/types/eval/__tests__/prelude-agent.test.d.ts +0 -1
  123. package/dist/types/eval/__tests__/process-entry-import.test.d.ts +0 -1
  124. package/dist/types/eval/py/__tests__/prelude.test.d.ts +0 -1
  125. package/dist/types/eval/py/__tests__/runner-shell-output.test.d.ts +0 -1
  126. package/dist/types/hindsight/client.test.d.ts +0 -1
  127. package/dist/types/internal-urls/__tests__/agent-protocol-nested.test.d.ts +0 -1
  128. package/dist/types/internal-urls/__tests__/ssh-protocol.test.d.ts +0 -1
  129. package/dist/types/launch/broker-list-order.test.d.ts +0 -1
  130. package/dist/types/launch/broker-output-snapshot.test.d.ts +0 -1
  131. package/dist/types/launch/protocol.test.d.ts +0 -1
  132. package/dist/types/launch/spawn-options.test.d.ts +0 -1
  133. package/dist/types/launch/terminal-output.test.d.ts +0 -1
  134. package/dist/types/live/protocol.test.d.ts +0 -1
  135. package/dist/types/mcp/config-writer.test.d.ts +0 -1
  136. package/dist/types/mcp/smithery-auth.test.d.ts +0 -1
  137. package/dist/types/mcp/smithery-registry.test.d.ts +0 -1
  138. package/dist/types/mcp/transports/stdio.test.d.ts +0 -1
  139. package/dist/types/modes/components/__tests__/dynamic-border.test.d.ts +0 -1
  140. package/dist/types/modes/components/__tests__/move-overlay.test.d.ts +0 -1
  141. package/dist/types/modes/components/__tests__/pause-screen.test.d.ts +0 -1
  142. package/dist/types/modes/components/__tests__/skill-message.test.d.ts +0 -1
  143. package/dist/types/modes/components/custom-editor-plugin-ctor.test.d.ts +0 -1
  144. package/dist/types/modes/components/custom-editor.test.d.ts +0 -1
  145. package/dist/types/modes/components/login-dialog.test.d.ts +0 -1
  146. package/dist/types/modes/components/status-line/component.jj-cache.test.d.ts +0 -1
  147. package/dist/types/modes/components/status-line/component.test.d.ts +0 -1
  148. package/dist/types/modes/components/tool-execution.test.d.ts +0 -1
  149. package/dist/types/modes/controllers/extension-ui-controller.test.d.ts +0 -1
  150. package/dist/types/modes/noninteractive-dispose.test.d.ts +0 -1
  151. package/dist/types/modes/print-mode.test.d.ts +0 -1
  152. package/dist/types/modes/session-teardown.test.d.ts +0 -1
  153. package/dist/types/modes/theme/mermaid-rendering.test.d.ts +0 -1
  154. package/dist/types/modes/utils/transcript-render-helpers.test.d.ts +0 -1
  155. package/dist/types/modes/warp-events.test.d.ts +0 -1
  156. package/dist/types/plan-mode/approved-plan-prompt.test.d.ts +0 -1
  157. package/dist/types/plan-mode/model-transition.test.d.ts +0 -1
  158. package/dist/types/plan-mode/reentry-prompt.test.d.ts +0 -1
  159. package/dist/types/session/agent-session-error-log.test.d.ts +0 -1
  160. package/dist/types/session/blob-store.test.d.ts +0 -1
  161. package/dist/types/session/messages.test.d.ts +0 -1
  162. package/dist/types/session/session-context.test.d.ts +0 -1
  163. package/dist/types/ssh/__tests__/connection-manager-args.test.d.ts +0 -1
  164. package/dist/types/ssh/__tests__/connection-manager-timeout.test.d.ts +0 -1
  165. package/dist/types/ssh/__tests__/file-transfer-posix-guard.test.d.ts +0 -1
  166. package/dist/types/ssh/__tests__/sshfs-mount.test.d.ts +0 -1
  167. package/dist/types/system-prompt.test.d.ts +0 -1
  168. package/dist/types/task/render.test.d.ts +0 -1
  169. package/dist/types/task/spawn-policy.test.d.ts +0 -1
  170. package/dist/types/tools/__tests__/eval-description.test.d.ts +0 -1
  171. package/dist/types/tools/__tests__/glob.test.d.ts +0 -1
  172. package/dist/types/tools/__tests__/json-tree.test.d.ts +0 -1
  173. package/dist/types/tools/__tests__/vibe-render.test.d.ts +0 -1
  174. package/dist/types/tools/hub/launch-compat.test.d.ts +0 -1
  175. package/dist/types/vibe/__tests__/token-rate.test.d.ts +0 -1
  176. package/src/advisor/__tests__/advisor.test.ts +0 -4889
  177. package/src/advisor/__tests__/config.test.ts +0 -349
  178. package/src/advisor/__tests__/emission-guard.test.ts +0 -147
  179. package/src/cli/__tests__/auth-gateway-catalog.test.ts +0 -111
  180. package/src/cli/update-cli.test.ts +0 -28
  181. package/src/config/__tests__/model-registry.test.ts +0 -182
  182. package/src/eval/__tests__/agent-bridge.test.ts +0 -1509
  183. package/src/eval/__tests__/bridge-timeout.test.ts +0 -170
  184. package/src/eval/__tests__/budget-bridge.test.ts +0 -80
  185. package/src/eval/__tests__/completion-bridge.test.ts +0 -412
  186. package/src/eval/__tests__/helpers-local-roots.test.ts +0 -55
  187. package/src/eval/__tests__/idle-timeout.test.ts +0 -80
  188. package/src/eval/__tests__/js-context-manager.test.ts +0 -456
  189. package/src/eval/__tests__/julia-prelude.test.ts +0 -66
  190. package/src/eval/__tests__/kernel-spawn.test.ts +0 -115
  191. package/src/eval/__tests__/prelude-agent.test.ts +0 -156
  192. package/src/eval/__tests__/process-entry-import.test.ts +0 -137
  193. package/src/eval/py/__tests__/prelude.test.ts +0 -104
  194. package/src/eval/py/__tests__/runner-shell-output.test.ts +0 -157
  195. package/src/hindsight/client.test.ts +0 -75
  196. package/src/internal-urls/__tests__/agent-protocol-nested.test.ts +0 -141
  197. package/src/internal-urls/__tests__/ssh-protocol.test.ts +0 -331
  198. package/src/launch/broker-list-order.test.ts +0 -89
  199. package/src/launch/broker-output-snapshot.test.ts +0 -126
  200. package/src/launch/protocol.test.ts +0 -59
  201. package/src/launch/spawn-options.test.ts +0 -31
  202. package/src/launch/terminal-output.test.ts +0 -107
  203. package/src/live/protocol.test.ts +0 -140
  204. package/src/mcp/config-writer.test.ts +0 -43
  205. package/src/mcp/smithery-auth.test.ts +0 -29
  206. package/src/mcp/smithery-registry.test.ts +0 -51
  207. package/src/mcp/transports/stdio.test.ts +0 -427
  208. package/src/modes/components/__tests__/dynamic-border.test.ts +0 -55
  209. package/src/modes/components/__tests__/move-overlay.test.ts +0 -252
  210. package/src/modes/components/__tests__/pause-screen.test.ts +0 -143
  211. package/src/modes/components/__tests__/skill-message.test.ts +0 -94
  212. package/src/modes/components/custom-editor-plugin-ctor.test.ts +0 -36
  213. package/src/modes/components/custom-editor.test.ts +0 -510
  214. package/src/modes/components/login-dialog.test.ts +0 -56
  215. package/src/modes/components/status-line/component.jj-cache.test.ts +0 -229
  216. package/src/modes/components/status-line/component.test.ts +0 -84
  217. package/src/modes/components/tool-execution.test.ts +0 -162
  218. package/src/modes/controllers/extension-ui-controller.test.ts +0 -250
  219. package/src/modes/noninteractive-dispose.test.ts +0 -73
  220. package/src/modes/print-mode.test.ts +0 -71
  221. package/src/modes/session-teardown.test.ts +0 -219
  222. package/src/modes/theme/mermaid-rendering.test.ts +0 -53
  223. package/src/modes/utils/transcript-render-helpers.test.ts +0 -38
  224. package/src/modes/warp-events.test.ts +0 -794
  225. package/src/plan-mode/approved-plan-prompt.test.ts +0 -36
  226. package/src/plan-mode/model-transition.test.ts +0 -60
  227. package/src/plan-mode/reentry-prompt.test.ts +0 -41
  228. package/src/session/agent-session-error-log.test.ts +0 -59
  229. package/src/session/blob-store.test.ts +0 -56
  230. package/src/session/messages.test.ts +0 -282
  231. package/src/session/session-context.test.ts +0 -384
  232. package/src/ssh/__tests__/connection-manager-args.test.ts +0 -191
  233. package/src/ssh/__tests__/connection-manager-timeout.test.ts +0 -61
  234. package/src/ssh/__tests__/file-transfer-posix-guard.test.ts +0 -105
  235. package/src/ssh/__tests__/sshfs-mount.test.ts +0 -13
  236. package/src/system-prompt.test.ts +0 -236
  237. package/src/task/render.test.ts +0 -290
  238. package/src/task/spawn-policy.test.ts +0 -62
  239. package/src/tools/__tests__/eval-description.test.ts +0 -18
  240. package/src/tools/__tests__/glob.test.ts +0 -37
  241. package/src/tools/__tests__/json-tree.test.ts +0 -35
  242. package/src/tools/__tests__/vibe-render.test.ts +0 -210
  243. package/src/tools/hub/launch-compat.test.ts +0 -40
  244. 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
- });