lsd-pi 1.3.2 → 1.3.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 (205) hide show
  1. package/dist/cli.js +2 -1
  2. package/dist/lsd-settings-manager.d.ts +2 -0
  3. package/dist/lsd-settings-manager.js +5 -0
  4. package/dist/resource-loader.js +33 -3
  5. package/dist/resources/extensions/browser-tools/tools/codegen.js +5 -5
  6. package/dist/resources/extensions/browser-tools/tools/navigation.js +107 -178
  7. package/dist/resources/extensions/browser-tools/tools/network-mock.js +112 -167
  8. package/dist/resources/extensions/browser-tools/tools/pages.js +182 -234
  9. package/dist/resources/extensions/browser-tools/tools/refs.js +202 -461
  10. package/dist/resources/extensions/browser-tools/tools/session.js +176 -323
  11. package/dist/resources/extensions/browser-tools/tools/state-persistence.js +91 -154
  12. package/dist/resources/extensions/browser-tools/utils.js +1 -1
  13. package/dist/resources/extensions/cache-timer/index.js +3 -2
  14. package/dist/resources/extensions/slash-commands/extension-manifest.json +2 -2
  15. package/dist/resources/extensions/slash-commands/fast.js +73 -0
  16. package/dist/resources/extensions/slash-commands/index.js +2 -0
  17. package/dist/resources/extensions/slash-commands/plan.js +37 -12
  18. package/dist/resources/extensions/subagent/background-job-manager.js +13 -0
  19. package/dist/resources/extensions/subagent/in-process-runner.js +387 -0
  20. package/dist/resources/extensions/subagent/index.js +278 -626
  21. package/dist/resources/extensions/subagent/legacy-runner.js +503 -0
  22. package/dist/resources/extensions/voice/index.js +96 -36
  23. package/dist/resources/extensions/voice/push-to-talk.js +26 -0
  24. package/dist/welcome-screen.js +2 -2
  25. package/package.json +1 -1
  26. package/packages/pi-agent-core/dist/agent.d.ts +19 -0
  27. package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
  28. package/packages/pi-agent-core/dist/agent.js +16 -0
  29. package/packages/pi-agent-core/dist/agent.js.map +1 -1
  30. package/packages/pi-agent-core/src/agent.ts +32 -2
  31. package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts +34 -1
  32. package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts.map +1 -1
  33. package/packages/pi-ai/dist/providers/openai-codex-responses.js +32 -4
  34. package/packages/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
  35. package/packages/pi-ai/dist/providers/openai-codex-responses.test.js +127 -16
  36. package/packages/pi-ai/dist/providers/openai-codex-responses.test.js.map +1 -1
  37. package/packages/pi-ai/dist/providers/openai-responses.d.ts +8 -1
  38. package/packages/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
  39. package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.d.ts +2 -0
  40. package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.d.ts.map +1 -0
  41. package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.js +67 -0
  42. package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.js.map +1 -0
  43. package/packages/pi-ai/dist/providers/openai-responses.js +21 -3
  44. package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
  45. package/packages/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
  46. package/packages/pi-ai/dist/providers/simple-options.js +2 -0
  47. package/packages/pi-ai/dist/providers/simple-options.js.map +1 -1
  48. package/packages/pi-ai/dist/types.d.ts +5 -0
  49. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  50. package/packages/pi-ai/dist/types.js.map +1 -1
  51. package/packages/pi-ai/src/providers/openai-codex-responses.test.ts +143 -20
  52. package/packages/pi-ai/src/providers/openai-codex-responses.ts +47 -4
  53. package/packages/pi-ai/src/providers/openai-responses.fast-mode.test.ts +73 -0
  54. package/packages/pi-ai/src/providers/openai-responses.ts +26 -3
  55. package/packages/pi-ai/src/providers/simple-options.ts +2 -0
  56. package/packages/pi-ai/src/types.ts +5 -0
  57. package/packages/pi-coding-agent/dist/core/keybindings.d.ts +1 -1
  58. package/packages/pi-coding-agent/dist/core/keybindings.d.ts.map +1 -1
  59. package/packages/pi-coding-agent/dist/core/keybindings.js +2 -0
  60. package/packages/pi-coding-agent/dist/core/keybindings.js.map +1 -1
  61. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  62. package/packages/pi-coding-agent/dist/core/sdk.js +4 -2
  63. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  64. package/packages/pi-coding-agent/dist/core/settings-manager.collapse-tool-calls.test.d.ts +2 -0
  65. package/packages/pi-coding-agent/dist/core/settings-manager.collapse-tool-calls.test.d.ts.map +1 -0
  66. package/packages/pi-coding-agent/dist/core/settings-manager.collapse-tool-calls.test.js +35 -0
  67. package/packages/pi-coding-agent/dist/core/settings-manager.collapse-tool-calls.test.js.map +1 -0
  68. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +12 -0
  69. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  70. package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.d.ts +2 -0
  71. package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.d.ts.map +1 -0
  72. package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.js +35 -0
  73. package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.js.map +1 -0
  74. package/packages/pi-coding-agent/dist/core/settings-manager.js +24 -0
  75. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  76. package/packages/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
  77. package/packages/pi-coding-agent/dist/core/slash-commands.js +1 -0
  78. package/packages/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
  79. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  80. package/packages/pi-coding-agent/dist/core/system-prompt.js +6 -1
  81. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  82. package/packages/pi-coding-agent/dist/core/tool-priority.d.ts +4 -0
  83. package/packages/pi-coding-agent/dist/core/tool-priority.d.ts.map +1 -0
  84. package/packages/pi-coding-agent/dist/core/tool-priority.js +18 -0
  85. package/packages/pi-coding-agent/dist/core/tool-priority.js.map +1 -0
  86. package/packages/pi-coding-agent/dist/core/tool-priority.test.d.ts +2 -0
  87. package/packages/pi-coding-agent/dist/core/tool-priority.test.d.ts.map +1 -0
  88. package/packages/pi-coding-agent/dist/core/tool-priority.test.js +27 -0
  89. package/packages/pi-coding-agent/dist/core/tool-priority.test.js.map +1 -0
  90. package/packages/pi-coding-agent/dist/core/tools/edit-diff.d.ts +5 -0
  91. package/packages/pi-coding-agent/dist/core/tools/edit-diff.d.ts.map +1 -1
  92. package/packages/pi-coding-agent/dist/core/tools/edit-diff.js +21 -0
  93. package/packages/pi-coding-agent/dist/core/tools/edit-diff.js.map +1 -1
  94. package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js +16 -1
  95. package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js.map +1 -1
  96. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.d.ts +2 -0
  97. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.d.ts.map +1 -0
  98. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.js +34 -0
  99. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.js.map +1 -0
  100. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.d.ts +45 -0
  101. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.d.ts.map +1 -0
  102. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.js +314 -0
  103. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.js.map +1 -0
  104. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.d.ts +2 -0
  105. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.d.ts.map +1 -0
  106. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.js +122 -0
  107. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.js.map +1 -0
  108. package/packages/pi-coding-agent/dist/modes/interactive/components/diff.d.ts +7 -5
  109. package/packages/pi-coding-agent/dist/modes/interactive/components/diff.d.ts.map +1 -1
  110. package/packages/pi-coding-agent/dist/modes/interactive/components/diff.js +86 -28
  111. package/packages/pi-coding-agent/dist/modes/interactive/components/diff.js.map +1 -1
  112. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts +4 -0
  113. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
  114. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +23 -10
  115. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
  116. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +8 -0
  117. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  118. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +52 -6
  119. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
  120. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +19 -0
  121. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  122. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +127 -14
  123. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  124. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.d.ts +14 -0
  125. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.d.ts.map +1 -0
  126. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.js +93 -0
  127. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.js.map +1 -0
  128. package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.d.ts +2 -0
  129. package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.d.ts.map +1 -0
  130. package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.js +328 -0
  131. package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.js.map +1 -0
  132. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  133. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +123 -0
  134. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  135. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js +1 -1
  136. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js.map +1 -1
  137. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
  138. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +7 -0
  139. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  140. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +4 -0
  141. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
  142. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
  143. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +9 -1
  144. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  145. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +103 -23
  146. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  147. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.d.ts.map +1 -1
  148. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js +41 -0
  149. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js.map +1 -1
  150. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +4 -4
  151. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -1
  152. package/packages/pi-coding-agent/package.json +1 -1
  153. package/packages/pi-coding-agent/src/core/keybindings.ts +4 -1
  154. package/packages/pi-coding-agent/src/core/sdk.ts +4 -2
  155. package/packages/pi-coding-agent/src/core/settings-manager.collapse-tool-calls.test.ts +46 -0
  156. package/packages/pi-coding-agent/src/core/settings-manager.fast-mode.test.ts +46 -0
  157. package/packages/pi-coding-agent/src/core/settings-manager.ts +36 -0
  158. package/packages/pi-coding-agent/src/core/slash-commands.ts +1 -0
  159. package/packages/pi-coding-agent/src/core/system-prompt.ts +6 -1
  160. package/packages/pi-coding-agent/src/core/tool-priority.test.ts +30 -0
  161. package/packages/pi-coding-agent/src/core/tool-priority.ts +17 -0
  162. package/packages/pi-coding-agent/src/core/tools/edit-diff.test.ts +20 -0
  163. package/packages/pi-coding-agent/src/core/tools/edit-diff.ts +26 -0
  164. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-summary-line.test.ts +41 -0
  165. package/packages/pi-coding-agent/src/modes/interactive/components/btw-overlay.test.ts +172 -0
  166. package/packages/pi-coding-agent/src/modes/interactive/components/btw-overlay.ts +402 -0
  167. package/packages/pi-coding-agent/src/modes/interactive/components/diff.ts +105 -28
  168. package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +21 -6
  169. package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +63 -6
  170. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +1262 -1138
  171. package/packages/pi-coding-agent/src/modes/interactive/components/tool-summary-line.ts +120 -0
  172. package/packages/pi-coding-agent/src/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.ts +396 -0
  173. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +530 -398
  174. package/packages/pi-coding-agent/src/modes/interactive/controllers/extension-ui-controller.ts +1 -1
  175. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +7 -0
  176. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +4 -0
  177. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +109 -23
  178. package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +60 -1
  179. package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +4 -4
  180. package/packages/pi-tui/dist/components/editor.js +3 -3
  181. package/packages/pi-tui/dist/components/editor.js.map +1 -1
  182. package/packages/pi-tui/src/components/editor.ts +3 -3
  183. package/pkg/dist/modes/interactive/theme/themes.js +4 -4
  184. package/pkg/dist/modes/interactive/theme/themes.js.map +1 -1
  185. package/pkg/package.json +1 -1
  186. package/src/resources/extensions/browser-tools/tools/codegen.ts +5 -5
  187. package/src/resources/extensions/browser-tools/tools/navigation.ts +118 -196
  188. package/src/resources/extensions/browser-tools/tools/network-mock.ts +114 -205
  189. package/src/resources/extensions/browser-tools/tools/pages.ts +183 -237
  190. package/src/resources/extensions/browser-tools/tools/refs.ts +193 -507
  191. package/src/resources/extensions/browser-tools/tools/session.ts +182 -321
  192. package/src/resources/extensions/browser-tools/tools/state-persistence.ts +94 -172
  193. package/src/resources/extensions/browser-tools/utils.ts +1 -1
  194. package/src/resources/extensions/cache-timer/index.ts +3 -2
  195. package/src/resources/extensions/slash-commands/extension-manifest.json +2 -2
  196. package/src/resources/extensions/slash-commands/fast.ts +89 -0
  197. package/src/resources/extensions/slash-commands/index.ts +2 -0
  198. package/src/resources/extensions/slash-commands/plan.ts +42 -12
  199. package/src/resources/extensions/subagent/background-job-manager.ts +28 -0
  200. package/src/resources/extensions/subagent/in-process-runner.ts +534 -0
  201. package/src/resources/extensions/subagent/index.ts +489 -799
  202. package/src/resources/extensions/subagent/legacy-runner.ts +607 -0
  203. package/src/resources/extensions/voice/index.ts +308 -238
  204. package/src/resources/extensions/voice/push-to-talk.ts +42 -0
  205. package/src/resources/extensions/voice/tests/push-to-talk.test.ts +109 -0
@@ -42,359 +42,220 @@ export function registerSessionTools(pi: ExtensionAPI, deps: ToolDeps): void {
42
42
  async execute(_toolCallId, _params, _signal, _onUpdate, _ctx) {
43
43
  try {
44
44
  await deps.closeBrowser();
45
- return {
46
- content: [{ type: "text", text: "Browser closed." }],
47
- details: {},
48
- };
45
+ return { content: [{ type: "text" as const, text: "Browser closed." }], details: {} };
49
46
  } catch (err: any) {
50
- return {
51
- content: [{ type: "text", text: `Close failed: ${err.message}` }],
52
- details: { error: err.message },
53
- isError: true,
54
- };
47
+ return { content: [{ type: "text" as const, text: `Close failed: ${err.message}` }], details: { error: err.message }, isError: true };
55
48
  }
56
49
  },
57
50
  });
58
51
 
59
52
  // -------------------------------------------------------------------------
60
- // browser_trace_start
53
+ // browser_trace — start, stop, export HAR
61
54
  // -------------------------------------------------------------------------
62
55
  pi.registerTool({
63
- name: "browser_trace_start",
64
- label: "Browser Trace Start",
65
- description: "Start a Playwright trace for the current browser session and persist trace metadata under the session artifact directory.",
56
+ name: "browser_trace",
57
+ label: "Browser Trace",
58
+ description:
59
+ "Manage Playwright tracing and HAR export: start/stop traces, export session HAR. " +
60
+ "Traces capture screenshots, snapshots, and sources for debugging.",
66
61
  parameters: Type.Object({
67
- name: Type.Optional(Type.String({ description: "Optional short trace session name for artifact filenames." })),
68
- title: Type.Optional(Type.String({ description: "Optional trace title recorded in metadata." })),
62
+ action: Type.Union([
63
+ Type.Literal("start"),
64
+ Type.Literal("stop"),
65
+ Type.Literal("export_har"),
66
+ ], { description: "'start' — begin trace, 'stop' — end trace and save, 'export_har' — export network HAR" }),
67
+ name: Type.Optional(Type.String({ description: "Name for the trace file or HAR export." })),
68
+ title: Type.Optional(Type.String({ description: "Trace title (start action only)." })),
69
69
  }),
70
- async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
71
- try {
72
- const { context: browserContext } = await deps.ensureBrowser();
73
- const activeTrace = getActiveTraceSession();
74
- if (activeTrace) {
75
- return {
76
- content: [{ type: "text", text: `Trace already active: ${activeTrace.name}` }],
77
- details: { error: "trace_already_active", activeTraceSession: activeTrace, ...deps.getSessionArtifactMetadata() },
78
- isError: true,
79
- };
80
- }
81
- const startedAt = Date.now();
82
- const name = (params.name?.trim() || `trace-${deps.formatArtifactTimestamp(startedAt)}`).replace(/[^a-zA-Z0-9._-]+/g, "-");
83
- await browserContext.tracing.start({ screenshots: true, snapshots: true, sources: true, title: params.title ?? name });
84
- setActiveTraceSession({ startedAt, name, title: params.title ?? name });
85
- return {
86
- content: [{ type: "text", text: `Trace started: ${name}\nSession dir: ${getSessionArtifactDir()}` }],
87
- details: { activeTraceSession: getActiveTraceSession(), ...deps.getSessionArtifactMetadata() },
88
- };
89
- } catch (err: any) {
90
- return {
91
- content: [{ type: "text", text: `Trace start failed: ${err.message}` }],
92
- details: { error: err.message, ...deps.getSessionArtifactMetadata() },
93
- isError: true,
94
- };
95
- }
96
- },
97
- });
98
70
 
99
- // -------------------------------------------------------------------------
100
- // browser_trace_stop
101
- // -------------------------------------------------------------------------
102
- pi.registerTool({
103
- name: "browser_trace_stop",
104
- label: "Browser Trace Stop",
105
- description: "Stop the active Playwright trace and write the trace zip to disk under the session artifact directory.",
106
- parameters: Type.Object({
107
- name: Type.Optional(Type.String({ description: "Optional artifact basename override for the trace zip." })),
108
- }),
109
71
  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
110
72
  try {
111
- const { context: browserContext } = await deps.ensureBrowser();
112
- const activeTrace = getActiveTraceSession();
113
- if (!activeTrace) {
114
- return {
115
- content: [{ type: "text", text: "No active trace session to stop." }],
116
- details: { error: "trace_not_active", ...deps.getSessionArtifactMetadata() },
117
- isError: true,
118
- };
73
+ if (params.action === "start") {
74
+ return await traceStart(params);
75
+ } else if (params.action === "stop") {
76
+ return await traceStop(params);
77
+ } else {
78
+ return await exportHar({ filename: params.name });
119
79
  }
120
- const traceSession = activeTrace;
121
- const traceName = (params.name?.trim() || traceSession.name).replace(/[^a-zA-Z0-9._-]+/g, "-");
122
- const tracePath = deps.buildSessionArtifactPath(`${traceName}.trace.zip`);
123
- await browserContext.tracing.stop({ path: tracePath });
124
- const fileStat = await stat(tracePath);
125
- setActiveTraceSession(null);
126
- return {
127
- content: [{ type: "text", text: `Trace stopped: ${tracePath}` }],
128
- details: {
129
- path: tracePath,
130
- bytes: fileStat.size,
131
- elapsedMs: Date.now() - traceSession.startedAt,
132
- traceName,
133
- ...deps.getSessionArtifactMetadata(),
134
- },
135
- };
136
80
  } catch (err: any) {
137
- return {
138
- content: [{ type: "text", text: `Trace stop failed: ${err.message}` }],
139
- details: { error: err.message, ...deps.getSessionArtifactMetadata() },
140
- isError: true,
141
- };
81
+ return { content: [{ type: "text" as const, text: `Trace '${params.action}' failed: ${err.message}` }], details: { error: err.message, ...deps.getSessionArtifactMetadata() }, isError: true };
142
82
  }
143
83
  },
144
84
  });
145
85
 
146
86
  // -------------------------------------------------------------------------
147
- // browser_export_har
87
+ // browser_debug — timeline, session summary, debug bundle
148
88
  // -------------------------------------------------------------------------
149
89
  pi.registerTool({
150
- name: "browser_export_har",
151
- label: "Browser Export HAR",
152
- description: "Export the truthfully recorded session HAR from disk to a stable artifact path and return compact metadata.",
90
+ name: "browser_debug",
91
+ label: "Browser Debug",
92
+ description:
93
+ "Browser session introspection: view action timeline, session summary, or write a full debug bundle to disk. " +
94
+ "Use for debugging failing tests or complex browser interactions.",
153
95
  parameters: Type.Object({
154
- filename: Type.Optional(Type.String({ description: "Optional destination filename within the session artifact directory." })),
96
+ action: Type.Union([
97
+ Type.Literal("timeline"),
98
+ Type.Literal("summary"),
99
+ Type.Literal("bundle"),
100
+ ], { description: "'timeline' — action history, 'summary' — session overview, 'bundle' — write full debug bundle to disk" }),
101
+ writeToDisk: Type.Optional(Type.Boolean({ description: "Write timeline JSON to disk (timeline action)." })),
102
+ filename: Type.Optional(Type.String({ description: "Filename for timeline/bundle output." })),
103
+ selector: Type.Optional(Type.String({ description: "CSS selector scope for accessibility snapshot (bundle action)." })),
104
+ name: Type.Optional(Type.String({ description: "Bundle name suffix (bundle action)." })),
155
105
  }),
156
- async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
157
- try {
158
- await deps.ensureBrowser();
159
- const harState = getHarState();
160
- if (!harState.enabled || !harState.configuredAtContextCreation || !harState.path) {
161
- return {
162
- content: [{ type: "text", text: "HAR export unavailable: HAR recording was not enabled at browser context creation." }],
163
- details: { error: "har_not_enabled", ...deps.getSessionArtifactMetadata() },
164
- isError: true,
165
- };
166
- }
167
- const sourcePath = harState.path;
168
- const destinationName = (params.filename?.trim() || `export-${HAR_FILENAME}`).replace(/[^a-zA-Z0-9._-]+/g, "-");
169
- const destinationPath = deps.buildSessionArtifactPath(destinationName);
170
- const exportResult = sourcePath === destinationPath
171
- ? { path: sourcePath, bytes: (await stat(sourcePath)).size }
172
- : await deps.copyArtifactFile(sourcePath, destinationPath);
173
- setHarState({
174
- ...harState,
175
- exportCount: harState.exportCount + 1,
176
- lastExportedPath: exportResult.path,
177
- lastExportedAt: Date.now(),
178
- });
179
- return {
180
- content: [{ type: "text", text: `HAR exported: ${exportResult.path}` }],
181
- details: { path: exportResult.path, bytes: exportResult.bytes, ...deps.getSessionArtifactMetadata() },
182
- };
183
- } catch (err: any) {
184
- return {
185
- content: [{ type: "text", text: `HAR export failed: ${err.message}` }],
186
- details: { error: err.message, ...deps.getSessionArtifactMetadata() },
187
- isError: true,
188
- };
189
- }
190
- },
191
- });
192
106
 
193
- // -------------------------------------------------------------------------
194
- // browser_timeline
195
- // -------------------------------------------------------------------------
196
- pi.registerTool({
197
- name: "browser_timeline",
198
- label: "Browser Timeline",
199
- description: "Return a compact structured summary of the tracked browser action timeline and optional on-disk export path.",
200
- parameters: Type.Object({
201
- writeToDisk: Type.Optional(Type.Boolean({ description: "Write the timeline JSON to disk under the session artifact directory." })),
202
- filename: Type.Optional(Type.String({ description: "Optional JSON filename when writeToDisk is true." })),
203
- }),
204
107
  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
205
108
  try {
206
- await deps.ensureBrowser();
207
- const actionTimeline = getActionTimeline();
208
- const timeline = formatTimelineEntries(actionTimeline.entries, {
209
- limit: actionTimeline.limit,
210
- totalActions: actionTimeline.nextId - 1,
211
- });
212
- let artifact: { path: string; bytes: number } | null = null;
213
- if (params.writeToDisk) {
214
- const filename = (params.filename?.trim() || "timeline.json").replace(/[^a-zA-Z0-9._-]+/g, "-");
215
- artifact = await deps.writeArtifactFile(deps.buildSessionArtifactPath(filename), JSON.stringify(timeline, null, 2));
109
+ if (params.action === "timeline") {
110
+ return await timelineAction(params);
111
+ } else if (params.action === "summary") {
112
+ return await summaryAction();
113
+ } else {
114
+ return await bundleAction(params);
216
115
  }
217
- return {
218
- content: [{ type: "text", text: artifact ? `${timeline.summary}\nArtifact: ${artifact.path}` : timeline.summary }],
219
- details: { ...timeline, artifact, ...deps.getSessionArtifactMetadata() },
220
- };
221
116
  } catch (err: any) {
222
- return {
223
- content: [{ type: "text", text: `Timeline failed: ${err.message}` }],
224
- details: { error: err.message, ...deps.getSessionArtifactMetadata() },
225
- isError: true,
226
- };
117
+ return { content: [{ type: "text" as const, text: `Debug '${params.action}' failed: ${err.message}` }], details: { error: err.message, ...deps.getSessionArtifactMetadata() }, isError: true };
227
118
  }
228
119
  },
229
120
  });
230
121
 
231
- // -------------------------------------------------------------------------
232
- // browser_session_summary
233
- // -------------------------------------------------------------------------
234
- pi.registerTool({
235
- name: "browser_session_summary",
236
- label: "Browser Session Summary",
237
- description: "Return a compact structured summary of the current browser session, including pages, actions, waits/assertions, bounded-history caveats, and trace/HAR state.",
238
- parameters: Type.Object({}),
239
- async execute(_toolCallId, _params, _signal, _onUpdate, _ctx) {
240
- try {
241
- await deps.ensureBrowser();
242
- const pages = await deps.getLivePagesSnapshot();
243
- const actionTimeline = getActionTimeline();
244
- const pageRegistry = getPageRegistry();
245
- const consoleLogs = getConsoleLogs();
246
- const networkLogs = getNetworkLogs();
247
- const dialogLogs = getDialogLogs();
248
- const baseSummary = summarizeBrowserSession({
249
- timeline: actionTimeline,
250
- totalActions: actionTimeline.nextId - 1,
251
- pages,
252
- activePageId: pageRegistry.activePageId,
253
- activeFrame: getActiveFrameMetadata(),
254
- consoleEntries: consoleLogs,
255
- networkEntries: networkLogs,
256
- dialogEntries: dialogLogs,
257
- consoleLimit: 1000,
258
- networkLimit: 1000,
259
- dialogLimit: 1000,
260
- sessionStartedAt: getSessionStartedAt(),
261
- now: Date.now(),
262
- });
263
- const failureHypothesis = buildFailureHypothesis({
264
- timeline: actionTimeline,
265
- consoleEntries: consoleLogs,
266
- networkEntries: networkLogs,
267
- dialogEntries: dialogLogs,
268
- });
269
- const activeTrace = getActiveTraceSession();
270
- const traceState = activeTrace
271
- ? { status: "active", ...activeTrace }
272
- : { status: "inactive", lastTracePath: getSessionArtifactDir() ? deps.buildSessionArtifactPath("*.trace.zip") : null };
273
- const harState = getHarState();
274
- const harSummary = {
275
- enabled: harState.enabled,
276
- configuredAtContextCreation: harState.configuredAtContextCreation,
277
- path: harState.path,
278
- exportCount: harState.exportCount,
279
- lastExportedPath: harState.lastExportedPath,
280
- lastExportedAt: harState.lastExportedAt,
281
- };
282
- return {
283
- content: [{ type: "text", text: `${baseSummary.summary}\nFailure hypothesis: ${failureHypothesis}` }],
284
- details: {
285
- ...baseSummary,
286
- failureHypothesis,
287
- trace: traceState,
288
- har: harSummary,
289
- ...deps.getSessionArtifactMetadata(),
290
- },
291
- };
292
- } catch (err: any) {
293
- return {
294
- content: [{ type: "text", text: `Session summary failed: ${err.message}` }],
295
- details: { error: err.message, ...deps.getSessionArtifactMetadata() },
296
- isError: true,
297
- };
298
- }
299
- },
300
- });
122
+ // ── trace helpers ──
301
123
 
302
- // -------------------------------------------------------------------------
303
- // browser_debug_bundle
304
- // -------------------------------------------------------------------------
305
- pi.registerTool({
306
- name: "browser_debug_bundle",
307
- label: "Browser Debug Bundle",
308
- description: "Write a timestamped debug bundle to disk with screenshot, logs, timeline, pages, session summary, and accessibility output, then return compact paths and counts.",
309
- parameters: Type.Object({
310
- selector: Type.Optional(Type.String({ description: "Optional CSS selector to scope the accessibility snapshot before fallback behavior applies." })),
311
- name: Type.Optional(Type.String({ description: "Optional short bundle name suffix for the output directory." })),
312
- }),
313
- async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
314
- try {
315
- const { page: p } = await deps.ensureBrowser();
316
- const startedAt = Date.now();
317
- const sessionDir = await deps.ensureSessionArtifactDir();
318
- const bundleDir = path.join(ARTIFACT_ROOT, `${deps.formatArtifactTimestamp(startedAt)}-${deps.sanitizeArtifactName(params.name ?? "debug-bundle", "debug-bundle")}`);
319
- await ensureDir(bundleDir);
320
- const pages = await deps.getLivePagesSnapshot();
321
- const actionTimeline = getActionTimeline();
322
- const pageRegistry = getPageRegistry();
323
- const consoleLogs = getConsoleLogs();
324
- const networkLogs = getNetworkLogs();
325
- const dialogLogs = getDialogLogs();
326
- const timeline = formatTimelineEntries(actionTimeline.entries, {
327
- limit: actionTimeline.limit,
328
- totalActions: actionTimeline.nextId - 1,
329
- });
330
- const sessionSummary = summarizeBrowserSession({
331
- timeline: actionTimeline,
332
- totalActions: actionTimeline.nextId - 1,
333
- pages,
334
- activePageId: pageRegistry.activePageId,
335
- activeFrame: getActiveFrameMetadata(),
336
- consoleEntries: consoleLogs,
337
- networkEntries: networkLogs,
338
- dialogEntries: dialogLogs,
339
- consoleLimit: 1000,
340
- networkLimit: 1000,
341
- dialogLimit: 1000,
342
- sessionStartedAt: getSessionStartedAt(),
343
- now: Date.now(),
344
- });
345
- const failureHypothesis = buildFailureHypothesis({
346
- timeline: actionTimeline,
347
- consoleEntries: consoleLogs,
348
- networkEntries: networkLogs,
349
- dialogEntries: dialogLogs,
350
- });
351
- const accessibility = await deps.captureAccessibilityMarkdown(params.selector);
352
- const screenshotPath = path.join(bundleDir, "screenshot.jpg");
353
- await p.screenshot({ path: screenshotPath, type: "jpeg", quality: 80, fullPage: false });
354
- const screenshotStat = await stat(screenshotPath);
355
- const artifacts = {
356
- screenshot: { path: screenshotPath, bytes: screenshotStat.size },
357
- console: await deps.writeArtifactFile(path.join(bundleDir, "console.json"), JSON.stringify(consoleLogs, null, 2)),
358
- network: await deps.writeArtifactFile(path.join(bundleDir, "network.json"), JSON.stringify(networkLogs, null, 2)),
359
- dialog: await deps.writeArtifactFile(path.join(bundleDir, "dialog.json"), JSON.stringify(dialogLogs, null, 2)),
360
- timeline: await deps.writeArtifactFile(path.join(bundleDir, "timeline.json"), JSON.stringify(timeline, null, 2)),
361
- summary: await deps.writeArtifactFile(path.join(bundleDir, "summary.json"), JSON.stringify({
362
- ...sessionSummary,
363
- failureHypothesis,
364
- trace: getActiveTraceSession(),
365
- har: getHarState(),
366
- sessionArtifactDir: sessionDir,
367
- }, null, 2)),
368
- pages: await deps.writeArtifactFile(path.join(bundleDir, "pages.json"), JSON.stringify(pages, null, 2)),
369
- accessibility: await deps.writeArtifactFile(path.join(bundleDir, "accessibility.md"), accessibility.snapshot),
370
- };
371
- return {
372
- content: [{ type: "text", text: `Debug bundle written: ${bundleDir}\n${sessionSummary.summary}\nFailure hypothesis: ${failureHypothesis}` }],
373
- details: {
374
- bundleDir,
375
- artifacts,
376
- accessibilityScope: accessibility.scope,
377
- accessibilitySource: accessibility.source,
378
- counts: {
379
- console: consoleLogs.length,
380
- network: networkLogs.length,
381
- dialog: dialogLogs.length,
382
- actions: timeline.retained,
383
- pages: pages.length,
384
- },
385
- elapsedMs: Date.now() - startedAt,
386
- summary: sessionSummary,
387
- failureHypothesis,
388
- ...deps.getSessionArtifactMetadata(),
389
- },
390
- };
391
- } catch (err: any) {
392
- return {
393
- content: [{ type: "text", text: `Debug bundle failed: ${err.message}` }],
394
- details: { error: err.message, ...deps.getSessionArtifactMetadata() },
395
- isError: true,
396
- };
397
- }
398
- },
399
- });
124
+ async function traceStart(params: { name?: string; title?: string }) {
125
+ const { context: browserContext } = await deps.ensureBrowser();
126
+ const activeTrace = getActiveTraceSession();
127
+ if (activeTrace) {
128
+ return { content: [{ type: "text" as const, text: `Trace already active: ${activeTrace.name}` }], details: { error: "trace_already_active", ...deps.getSessionArtifactMetadata() }, isError: true };
129
+ }
130
+ const startedAt = Date.now();
131
+ const name = (params.name?.trim() || `trace-${deps.formatArtifactTimestamp(startedAt)}`).replace(/[^a-zA-Z0-9._-]+/g, "-");
132
+ await browserContext.tracing.start({ screenshots: true, snapshots: true, sources: true, title: params.title ?? name });
133
+ setActiveTraceSession({ startedAt, name, title: params.title ?? name });
134
+ return {
135
+ content: [{ type: "text" as const, text: `Trace started: ${name}\nSession dir: ${getSessionArtifactDir()}` }],
136
+ details: { activeTraceSession: getActiveTraceSession(), ...deps.getSessionArtifactMetadata() },
137
+ };
138
+ }
139
+
140
+ async function traceStop(params: { name?: string }) {
141
+ const { context: browserContext } = await deps.ensureBrowser();
142
+ const activeTrace = getActiveTraceSession();
143
+ if (!activeTrace) {
144
+ return { content: [{ type: "text" as const, text: "No active trace session." }], details: { error: "trace_not_active", ...deps.getSessionArtifactMetadata() }, isError: true };
145
+ }
146
+ const traceName = (params.name?.trim() || activeTrace.name).replace(/[^a-zA-Z0-9._-]+/g, "-");
147
+ const tracePath = deps.buildSessionArtifactPath(`${traceName}.trace.zip`);
148
+ await browserContext.tracing.stop({ path: tracePath });
149
+ const fileStat = await stat(tracePath);
150
+ setActiveTraceSession(null);
151
+ return {
152
+ content: [{ type: "text" as const, text: `Trace stopped: ${tracePath}` }],
153
+ details: { path: tracePath, bytes: fileStat.size, elapsedMs: Date.now() - activeTrace.startedAt, traceName, ...deps.getSessionArtifactMetadata() },
154
+ };
155
+ }
156
+
157
+ async function exportHar(params: { filename?: string }) {
158
+ await deps.ensureBrowser();
159
+ const harState = getHarState();
160
+ if (!harState.enabled || !harState.configuredAtContextCreation || !harState.path) {
161
+ return { content: [{ type: "text" as const, text: "HAR export unavailable: HAR recording was not enabled at context creation." }], details: { error: "har_not_enabled", ...deps.getSessionArtifactMetadata() }, isError: true };
162
+ }
163
+ const destinationName = (params.filename?.trim() || `export-${HAR_FILENAME}`).replace(/[^a-zA-Z0-9._-]+/g, "-");
164
+ const destinationPath = deps.buildSessionArtifactPath(destinationName);
165
+ const exportResult = harState.path === destinationPath
166
+ ? { path: harState.path, bytes: (await stat(harState.path)).size }
167
+ : await deps.copyArtifactFile(harState.path, destinationPath);
168
+ setHarState({ ...harState, exportCount: harState.exportCount + 1, lastExportedPath: exportResult.path, lastExportedAt: Date.now() });
169
+ return {
170
+ content: [{ type: "text" as const, text: `HAR exported: ${exportResult.path}` }],
171
+ details: { path: exportResult.path, bytes: exportResult.bytes, ...deps.getSessionArtifactMetadata() },
172
+ };
173
+ }
174
+
175
+ // ── debug helpers ──
176
+
177
+ async function timelineAction(params: { writeToDisk?: boolean; filename?: string }) {
178
+ await deps.ensureBrowser();
179
+ const actionTimeline = getActionTimeline();
180
+ const timeline = formatTimelineEntries(actionTimeline.entries, { limit: actionTimeline.limit, totalActions: actionTimeline.nextId - 1 });
181
+ let artifact: { path: string; bytes: number } | null = null;
182
+ if (params.writeToDisk) {
183
+ const filename = (params.filename?.trim() || "timeline.json").replace(/[^a-zA-Z0-9._-]+/g, "-");
184
+ artifact = await deps.writeArtifactFile(deps.buildSessionArtifactPath(filename), JSON.stringify(timeline, null, 2));
185
+ }
186
+ return {
187
+ content: [{ type: "text" as const, text: artifact ? `${timeline.summary}\nArtifact: ${artifact.path}` : timeline.summary }],
188
+ details: { ...timeline, artifact, ...deps.getSessionArtifactMetadata() },
189
+ };
190
+ }
191
+
192
+ async function summaryAction() {
193
+ await deps.ensureBrowser();
194
+ const pages = await deps.getLivePagesSnapshot();
195
+ const actionTimeline = getActionTimeline();
196
+ const pageRegistry = getPageRegistry();
197
+ const consoleLogs = getConsoleLogs();
198
+ const networkLogs = getNetworkLogs();
199
+ const dialogLogs = getDialogLogs();
200
+ const baseSummary = summarizeBrowserSession({
201
+ timeline: actionTimeline, totalActions: actionTimeline.nextId - 1, pages,
202
+ activePageId: pageRegistry.activePageId, activeFrame: getActiveFrameMetadata(),
203
+ consoleEntries: consoleLogs, networkEntries: networkLogs, dialogEntries: dialogLogs,
204
+ consoleLimit: 1000, networkLimit: 1000, dialogLimit: 1000,
205
+ sessionStartedAt: getSessionStartedAt(), now: Date.now(),
206
+ });
207
+ const failureHypothesis = buildFailureHypothesis({ timeline: actionTimeline, consoleEntries: consoleLogs, networkEntries: networkLogs, dialogEntries: dialogLogs });
208
+ const activeTrace = getActiveTraceSession();
209
+ const traceState = activeTrace ? { status: "active", ...activeTrace } : { status: "inactive", lastTracePath: getSessionArtifactDir() ? deps.buildSessionArtifactPath("*.trace.zip") : null };
210
+ const harState = getHarState();
211
+ return {
212
+ content: [{ type: "text" as const, text: `${baseSummary.summary}\nFailure hypothesis: ${failureHypothesis}` }],
213
+ details: { ...baseSummary, failureHypothesis, trace: traceState, har: { enabled: harState.enabled, exportCount: harState.exportCount }, ...deps.getSessionArtifactMetadata() },
214
+ };
215
+ }
216
+
217
+ async function bundleAction(params: { selector?: string; name?: string }) {
218
+ const { page: p } = await deps.ensureBrowser();
219
+ const startedAt = Date.now();
220
+ const sessionDir = await deps.ensureSessionArtifactDir();
221
+ const bundleDir = path.join(ARTIFACT_ROOT, `${deps.formatArtifactTimestamp(startedAt)}-${deps.sanitizeArtifactName(params.name ?? "debug-bundle", "debug-bundle")}`);
222
+ await ensureDir(bundleDir);
223
+ const pages = await deps.getLivePagesSnapshot();
224
+ const actionTimeline = getActionTimeline();
225
+ const pageRegistry = getPageRegistry();
226
+ const consoleLogs = getConsoleLogs();
227
+ const networkLogs = getNetworkLogs();
228
+ const dialogLogs = getDialogLogs();
229
+ const timeline = formatTimelineEntries(actionTimeline.entries, { limit: actionTimeline.limit, totalActions: actionTimeline.nextId - 1 });
230
+ const sessionSummary = summarizeBrowserSession({
231
+ timeline: actionTimeline, totalActions: actionTimeline.nextId - 1, pages,
232
+ activePageId: pageRegistry.activePageId, activeFrame: getActiveFrameMetadata(),
233
+ consoleEntries: consoleLogs, networkEntries: networkLogs, dialogEntries: dialogLogs,
234
+ consoleLimit: 1000, networkLimit: 1000, dialogLimit: 1000,
235
+ sessionStartedAt: getSessionStartedAt(), now: Date.now(),
236
+ });
237
+ const failureHypothesis = buildFailureHypothesis({ timeline: actionTimeline, consoleEntries: consoleLogs, networkEntries: networkLogs, dialogEntries: dialogLogs });
238
+ const accessibility = await deps.captureAccessibilityMarkdown(params.selector);
239
+ const screenshotPath = path.join(bundleDir, "screenshot.jpg");
240
+ await p.screenshot({ path: screenshotPath, type: "jpeg", quality: 80, fullPage: false });
241
+ const screenshotStat = await stat(screenshotPath);
242
+ const artifacts = {
243
+ screenshot: { path: screenshotPath, bytes: screenshotStat.size },
244
+ console: await deps.writeArtifactFile(path.join(bundleDir, "console.json"), JSON.stringify(consoleLogs, null, 2)),
245
+ network: await deps.writeArtifactFile(path.join(bundleDir, "network.json"), JSON.stringify(networkLogs, null, 2)),
246
+ dialog: await deps.writeArtifactFile(path.join(bundleDir, "dialog.json"), JSON.stringify(dialogLogs, null, 2)),
247
+ timeline: await deps.writeArtifactFile(path.join(bundleDir, "timeline.json"), JSON.stringify(timeline, null, 2)),
248
+ summary: await deps.writeArtifactFile(path.join(bundleDir, "summary.json"), JSON.stringify({ ...sessionSummary, failureHypothesis, trace: getActiveTraceSession(), sessionArtifactDir: sessionDir }, null, 2)),
249
+ pages: await deps.writeArtifactFile(path.join(bundleDir, "pages.json"), JSON.stringify(pages, null, 2)),
250
+ accessibility: await deps.writeArtifactFile(path.join(bundleDir, "accessibility.md"), accessibility.snapshot),
251
+ };
252
+ return {
253
+ content: [{ type: "text" as const, text: `Debug bundle written: ${bundleDir}\n${sessionSummary.summary}\nFailure hypothesis: ${failureHypothesis}` }],
254
+ details: {
255
+ bundleDir, artifacts, accessibilityScope: accessibility.scope, accessibilitySource: accessibility.source,
256
+ counts: { console: consoleLogs.length, network: networkLogs.length, dialog: dialogLogs.length, actions: timeline.retained, pages: pages.length },
257
+ elapsedMs: Date.now() - startedAt, summary: sessionSummary, failureHypothesis, ...deps.getSessionArtifactMetadata(),
258
+ },
259
+ };
260
+ }
400
261
  }