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
@@ -3,93 +3,36 @@ import { Type } from "@sinclair/typebox";
3
3
  import type { ToolDeps } from "../state.js";
4
4
 
5
5
  /**
6
- * State persistence tools — save/restore cookies, localStorage, sessionStorage.
6
+ * State persistence — save/restore cookies, localStorage, sessionStorage.
7
7
  */
8
8
 
9
9
  const STATE_DIR = ".gsd/browser-state";
10
10
 
11
11
  export function registerStatePersistenceTools(pi: ExtensionAPI, deps: ToolDeps): void {
12
- // -------------------------------------------------------------------------
13
- // browser_save_state
14
- // -------------------------------------------------------------------------
15
12
  pi.registerTool({
16
- name: "browser_save_state",
17
- label: "Browser Save State",
13
+ name: "browser_state",
14
+ label: "Browser State",
18
15
  description:
19
- "Save cookies, localStorage, and sessionStorage to disk so authenticated sessions survive browser restarts. " +
20
- "State files are written to .gsd/browser-state/ and should be gitignored (may contain auth tokens). " +
21
- "Never displays secret values in output.",
16
+ "Save or restore browser state (cookies, localStorage, sessionStorage) to persist sessions across browser restarts. " +
17
+ "State files written to .gsd/browser-state/ (should be gitignored).",
22
18
  parameters: Type.Object({
23
- name: Type.Optional(
24
- Type.String({ description: "Name for the state file (default: 'default'). Used as the filename stem." }),
25
- ),
19
+ action: Type.Union([
20
+ Type.Literal("save"),
21
+ Type.Literal("restore"),
22
+ ], { description: "'save' — persist current state, 'restore' — load previously saved state" }),
23
+ name: Type.Optional(Type.String({ description: "State file name (default: 'default'). Used as filename stem." })),
26
24
  }),
27
25
 
28
26
  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
29
27
  try {
30
- const { context: ctx, page: p } = await deps.ensureBrowser();
31
- const name = deps.sanitizeArtifactName(params.name ?? "default", "default");
32
-
33
- const { mkdir, writeFile } = await import("node:fs/promises");
34
- const path = await import("node:path");
35
- const stateDir = path.resolve(process.cwd(), STATE_DIR);
36
- await mkdir(stateDir, { recursive: true });
37
-
38
- // 1. Playwright storageState: cookies + localStorage
39
- const storageState = await ctx.storageState();
40
-
41
- // 2. sessionStorage: must be extracted per-origin via page.evaluate
42
- const sessionStorageData: Record<string, Record<string, string>> = {};
43
- try {
44
- const origin = new URL(p.url()).origin;
45
- const ssData = await p.evaluate(() => {
46
- const data: Record<string, string> = {};
47
- for (let i = 0; i < sessionStorage.length; i++) {
48
- const key = sessionStorage.key(i);
49
- if (key) data[key] = sessionStorage.getItem(key) ?? "";
50
- }
51
- return data;
52
- });
53
- if (Object.keys(ssData).length > 0) {
54
- sessionStorageData[origin] = ssData;
55
- }
56
- } catch {
57
- // Page may not have a valid origin (about:blank, etc.)
28
+ if (params.action === "save") {
29
+ return await saveState(deps, params.name ?? "default");
30
+ } else {
31
+ return await restoreState(deps, params.name ?? "default");
58
32
  }
59
-
60
- const combined = {
61
- storageState,
62
- sessionStorage: sessionStorageData,
63
- savedAt: new Date().toISOString(),
64
- url: p.url(),
65
- };
66
-
67
- const filePath = path.join(stateDir, `${name}.json`);
68
- await writeFile(filePath, JSON.stringify(combined, null, 2));
69
-
70
- // Ensure .gitignore covers the state dir
71
- const gitignorePath = path.resolve(process.cwd(), STATE_DIR, ".gitignore");
72
- await writeFile(gitignorePath, "*\n!.gitignore\n").catch(() => { /* best-effort — .gitignore may already exist or dir may be read-only */ });
73
-
74
- const cookieCount = storageState.cookies?.length ?? 0;
75
- const localStorageOrigins = storageState.origins?.length ?? 0;
76
- const sessionStorageOrigins = Object.keys(sessionStorageData).length;
77
-
78
- return {
79
- content: [{
80
- type: "text",
81
- text: `State saved: ${filePath}\nCookies: ${cookieCount}\nlocalStorage origins: ${localStorageOrigins}\nsessionStorage origins: ${sessionStorageOrigins}`,
82
- }],
83
- details: {
84
- path: filePath,
85
- cookieCount,
86
- localStorageOrigins,
87
- sessionStorageOrigins,
88
- },
89
- };
90
33
  } catch (err: any) {
91
34
  return {
92
- content: [{ type: "text", text: `Save state failed: ${err.message}` }],
35
+ content: [{ type: "text" as const, text: `State '${params.action}' failed: ${err.message}` }],
93
36
  details: { error: err.message },
94
37
  isError: true,
95
38
  };
@@ -97,106 +40,85 @@ export function registerStatePersistenceTools(pi: ExtensionAPI, deps: ToolDeps):
97
40
  },
98
41
  });
99
42
 
100
- // -------------------------------------------------------------------------
101
- // browser_restore_state
102
- // -------------------------------------------------------------------------
103
- pi.registerTool({
104
- name: "browser_restore_state",
105
- label: "Browser Restore State",
106
- description:
107
- "Restore cookies, localStorage, and sessionStorage from a previously saved state file. " +
108
- "Injects cookies via context.addCookies() and storage via page.evaluate(). " +
109
- "For full fidelity, restore before navigating to the target site.",
110
- parameters: Type.Object({
111
- name: Type.Optional(
112
- Type.String({ description: "Name of the state file to restore (default: 'default')." }),
113
- ),
114
- }),
115
-
116
- async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
117
- try {
118
- const { context: ctx, page: p } = await deps.ensureBrowser();
119
- const name = deps.sanitizeArtifactName(params.name ?? "default", "default");
120
-
121
- const { readFile } = await import("node:fs/promises");
122
- const path = await import("node:path");
123
- const filePath = path.join(process.cwd(), STATE_DIR, `${name}.json`);
124
-
125
- let raw: string;
126
- try {
127
- raw = await readFile(filePath, "utf-8");
128
- } catch {
129
- return {
130
- content: [{ type: "text", text: `State file not found: ${filePath}` }],
131
- details: { error: "file_not_found", path: filePath },
132
- isError: true,
133
- };
134
- }
135
-
136
- const combined = JSON.parse(raw);
137
- const storageState = combined.storageState;
138
- const sessionStorageData: Record<string, Record<string, string>> = combined.sessionStorage ?? {};
139
-
140
- // 1. Restore cookies
141
- let cookieCount = 0;
142
- if (storageState?.cookies?.length) {
143
- await ctx.addCookies(storageState.cookies);
144
- cookieCount = storageState.cookies.length;
145
- }
146
-
147
- // 2. Restore localStorage via page.evaluate
148
- let localStorageOrigins = 0;
149
- if (storageState?.origins?.length) {
150
- for (const origin of storageState.origins) {
151
- try {
152
- await p.evaluate((items: Array<{ name: string; value: string }>) => {
153
- for (const { name, value } of items) {
154
- localStorage.setItem(name, value);
155
- }
156
- }, origin.localStorage ?? []);
157
- localStorageOrigins++;
158
- } catch {
159
- // Origin mismatch — localStorage can only be set on matching origin
160
- }
161
- }
43
+ async function saveState(deps: ToolDeps, name: string) {
44
+ const { context: ctx, page: p } = await deps.ensureBrowser();
45
+ name = deps.sanitizeArtifactName(name, "default");
46
+ const { mkdir, writeFile } = await import("node:fs/promises");
47
+ const path = await import("node:path");
48
+ const stateDir = path.resolve(process.cwd(), STATE_DIR);
49
+ await mkdir(stateDir, { recursive: true });
50
+
51
+ const storageState = await ctx.storageState();
52
+ const sessionStorageData: Record<string, Record<string, string>> = {};
53
+ try {
54
+ const origin = new URL(p.url()).origin;
55
+ const ssData = await p.evaluate(() => {
56
+ const data: Record<string, string> = {};
57
+ for (let i = 0; i < sessionStorage.length; i++) {
58
+ const key = sessionStorage.key(i);
59
+ if (key) data[key] = sessionStorage.getItem(key) ?? "";
162
60
  }
163
-
164
- // 3. Restore sessionStorage via page.evaluate
165
- let sessionStorageOrigins = 0;
166
- for (const [_origin, data] of Object.entries(sessionStorageData)) {
167
- try {
168
- await p.evaluate((items: Record<string, string>) => {
169
- for (const [key, value] of Object.entries(items)) {
170
- sessionStorage.setItem(key, value);
171
- }
172
- }, data);
173
- sessionStorageOrigins++;
174
- } catch {
175
- // Origin mismatch
176
- }
177
- }
178
-
179
- return {
180
- content: [{
181
- type: "text",
182
- text: `State restored from: ${filePath}\nCookies: ${cookieCount}\nlocalStorage origins: ${localStorageOrigins}\nsessionStorage origins: ${sessionStorageOrigins}\nSaved at: ${combined.savedAt ?? "unknown"}`,
183
- }],
184
- details: {
185
- path: filePath,
186
- cookieCount,
187
- localStorageOrigins,
188
- sessionStorageOrigins,
189
- savedAt: combined.savedAt,
190
- savedUrl: combined.url,
191
- },
192
- };
193
- } catch (err: any) {
194
- return {
195
- content: [{ type: "text", text: `Restore state failed: ${err.message}` }],
196
- details: { error: err.message },
197
- isError: true,
198
- };
61
+ return data;
62
+ });
63
+ if (Object.keys(ssData).length > 0) sessionStorageData[origin] = ssData;
64
+ } catch { /* Page may not have a valid origin */ }
65
+
66
+ const combined = { storageState, sessionStorage: sessionStorageData, savedAt: new Date().toISOString(), url: p.url() };
67
+ const filePath = path.join(stateDir, `${name}.json`);
68
+ await writeFile(filePath, JSON.stringify(combined, null, 2));
69
+
70
+ const gitignorePath = path.resolve(process.cwd(), STATE_DIR, ".gitignore");
71
+ await writeFile(gitignorePath, "*\n!.gitignore\n").catch(() => { /* best-effort */ });
72
+
73
+ return {
74
+ content: [{ type: "text" as const, text: `State saved: ${filePath}\nCookies: ${storageState.cookies?.length ?? 0}\nlocalStorage origins: ${storageState.origins?.length ?? 0}\nsessionStorage origins: ${Object.keys(sessionStorageData).length}` }],
75
+ details: { path: filePath, cookieCount: storageState.cookies?.length ?? 0, localStorageOrigins: storageState.origins?.length ?? 0, sessionStorageOrigins: Object.keys(sessionStorageData).length },
76
+ };
77
+ }
78
+
79
+ async function restoreState(deps: ToolDeps, name: string) {
80
+ const { context: ctx, page: p } = await deps.ensureBrowser();
81
+ name = deps.sanitizeArtifactName(name, "default");
82
+ const { readFile } = await import("node:fs/promises");
83
+ const path = await import("node:path");
84
+ const filePath = path.join(process.cwd(), STATE_DIR, `${name}.json`);
85
+
86
+ let raw: string;
87
+ try { raw = await readFile(filePath, "utf-8"); } catch {
88
+ return { content: [{ type: "text" as const, text: `State file not found: ${filePath}` }], details: { error: "file_not_found", path: filePath }, isError: true };
89
+ }
90
+
91
+ const combined = JSON.parse(raw);
92
+ const storageState = combined.storageState;
93
+ const sessionStorageData: Record<string, Record<string, string>> = combined.sessionStorage ?? {};
94
+
95
+ let cookieCount = 0;
96
+ if (storageState?.cookies?.length) {
97
+ await ctx.addCookies(storageState.cookies);
98
+ cookieCount = storageState.cookies.length;
99
+ }
100
+
101
+ let localStorageOrigins = 0;
102
+ if (storageState?.origins?.length) {
103
+ for (const origin of storageState.origins) {
104
+ try {
105
+ await p.evaluate((items: Array<{ name: string; value: string }>) => { for (const { name, value } of items) localStorage.setItem(name, value); }, origin.localStorage ?? []);
106
+ localStorageOrigins++;
107
+ } catch { /* Origin mismatch */ }
199
108
  }
200
- },
201
- });
109
+ }
110
+
111
+ let sessionStorageOrigins = 0;
112
+ for (const [_origin, data] of Object.entries(sessionStorageData)) {
113
+ try {
114
+ await p.evaluate((items: Record<string, string>) => { for (const [key, value] of Object.entries(items)) sessionStorage.setItem(key, value); }, data);
115
+ sessionStorageOrigins++;
116
+ } catch { /* Origin mismatch */ }
117
+ }
118
+
119
+ return {
120
+ content: [{ type: "text" as const, text: `State restored from: ${filePath}\nCookies: ${cookieCount}\nlocalStorage origins: ${localStorageOrigins}\nsessionStorage origins: ${sessionStorageOrigins}` }],
121
+ details: { path: filePath, cookieCount, localStorageOrigins, sessionStorageOrigins, savedAt: combined.savedAt, savedUrl: combined.url },
122
+ };
123
+ }
202
124
  }
@@ -625,7 +625,7 @@ export function formatVersionedRef(version: number, key: string): string {
625
625
  }
626
626
 
627
627
  export function staleRefGuidance(refDisplay: string, reason: string): string {
628
- return `Ref ${refDisplay} could not be resolved (${reason}). The ref is likely stale after DOM/navigation changes. Call browser_snapshot_refs again to refresh refs.`;
628
+ return `Ref ${refDisplay} could not be resolved (${reason}). The ref is likely stale after DOM/navigation changes. Call browser_ref with action='snapshot' to refresh refs.`;
629
629
  }
630
630
 
631
631
  // ---------------------------------------------------------------------------
@@ -22,6 +22,7 @@ const IS_CACHE_TIMER_FORCED_OFF = process.env.LSD_DISABLE_CACHE_TIMER === "1" ||
22
22
 
23
23
  // ANSI color codes for timer display
24
24
  const ANSI_RESET = "\x1b[0m";
25
+ const ANSI_GREEN = "\x1b[32m";
25
26
  const ANSI_YELLOW = "\x1b[33m";
26
27
  const ANSI_RED = "\x1b[31m";
27
28
 
@@ -68,8 +69,8 @@ function formatElapsed(ms: number): string {
68
69
  // 5–10 minutes: yellow
69
70
  return `${ANSI_YELLOW}⏱ ${time}${ANSI_RESET}`;
70
71
  }
71
- // Under 5 minutes: plain (inherits footer dim styling)
72
- return `⏱ ${time}`;
72
+ // Under 5 minutes: green
73
+ return `${ANSI_GREEN}⏱ ${time}${ANSI_RESET}`;
73
74
  }
74
75
 
75
76
  export default function cacheTimerExtension(pi: ExtensionAPI) {
@@ -2,11 +2,11 @@
2
2
  "id": "slash-commands",
3
3
  "name": "Slash Commands",
4
4
  "version": "1.0.0",
5
- "description": "Bundled slash commands for context inspection, planning, and lazy tool search",
5
+ "description": "Bundled slash commands for context inspection, planning, fast mode, and lazy tool search",
6
6
  "tier": "bundled",
7
7
  "requires": { "platform": ">=2.29.0" },
8
8
  "provides": {
9
- "commands": ["audit", "clear", "context", "plan", "execute", "cancel-plan", "tools"],
9
+ "commands": ["audit", "clear", "context", "fast", "plan", "execute", "cancel-plan", "tools"],
10
10
  "tools": ["tool_search", "tool_enable"],
11
11
  "flags": ["plan"]
12
12
  }
@@ -0,0 +1,89 @@
1
+ import { getAgentDir, SettingsManager, type ExtensionAPI, type ExtensionCommandContext } from "@gsd/pi-coding-agent";
2
+
3
+ type FastCommandAction = "toggle" | "on" | "off" | "status" | "invalid";
4
+
5
+ function parseFastCommandAction(args: string): FastCommandAction {
6
+ const normalized = args.trim().toLowerCase();
7
+ if (!normalized) return "toggle";
8
+ if (normalized === "on") return "on";
9
+ if (normalized === "off") return "off";
10
+ if (normalized === "status") return "status";
11
+ return "invalid";
12
+ }
13
+
14
+ function supportsFastMode(model: ExtensionCommandContext["model"]): boolean {
15
+ if (!model) return false;
16
+ if (model.api === "openai-codex-responses") return true;
17
+ if (model.api !== "openai-responses") return false;
18
+ if (model.provider !== "openai") return false;
19
+ return model.capabilities?.supportsServiceTier === true;
20
+ }
21
+
22
+ function getModelLabel(model: ExtensionCommandContext["model"]): string {
23
+ if (!model) return "no active model";
24
+ return `${model.provider}/${model.id}`;
25
+ }
26
+
27
+ function getSettingsManager(): SettingsManager {
28
+ return SettingsManager.create(process.cwd(), getAgentDir());
29
+ }
30
+
31
+ export const __testing = {
32
+ parseFastCommandAction,
33
+ supportsFastMode,
34
+ };
35
+
36
+ export default function fastCommand(pi: ExtensionAPI) {
37
+ pi.registerCommand("fast", {
38
+ description: "Toggle fast mode for OpenAI/Codex models (service_tier=priority)",
39
+ getArgumentCompletions(prefix: string) {
40
+ const options = [
41
+ { value: "on", label: "on", description: "Enable fast mode" },
42
+ { value: "off", label: "off", description: "Disable fast mode" },
43
+ { value: "status", label: "status", description: "Show current fast-mode status" },
44
+ ];
45
+ const query = prefix.trim().toLowerCase();
46
+ return options.filter((item) => item.value.startsWith(query));
47
+ },
48
+ async handler(args: string, ctx: ExtensionCommandContext) {
49
+ const settings = getSettingsManager() as SettingsManager & {
50
+ getFastMode: () => boolean;
51
+ setFastMode: (enabled: boolean) => void;
52
+ };
53
+ const current = settings.getFastMode();
54
+ const action = parseFastCommandAction(args);
55
+ const model = ctx.model;
56
+ const supported = supportsFastMode(model);
57
+ const modelLabel = getModelLabel(model);
58
+
59
+ if (action === "invalid") {
60
+ ctx.ui.notify("Usage: /fast [on|off|status]", "warning");
61
+ return;
62
+ }
63
+
64
+ if (action === "status") {
65
+ ctx.ui.notify(
66
+ `Fast mode: ${current ? "ON" : "OFF"} · model ${modelLabel} is ${supported ? "supported" : "unsupported"}`,
67
+ "info",
68
+ );
69
+ return;
70
+ }
71
+
72
+ const next = action === "toggle" ? !current : action === "on";
73
+ settings.setFastMode(next);
74
+
75
+ if (!supported) {
76
+ ctx.ui.notify(
77
+ `Fast mode: ${next ? "ON" : "OFF"} (saved). Current model ${modelLabel} does not support fast mode.`,
78
+ "warning",
79
+ );
80
+ return;
81
+ }
82
+
83
+ ctx.ui.notify(
84
+ `Fast mode: ${next ? "ON" : "OFF"} (saved). ${next ? "Requests will include service_tier=priority." : "Requests will omit service_tier."}`,
85
+ "info",
86
+ );
87
+ },
88
+ });
89
+ }
@@ -2,6 +2,7 @@ import type { ExtensionAPI } from "@gsd/pi-coding-agent";
2
2
  import auditCommand from "./audit.js";
3
3
  import clearCommand from "./clear.js";
4
4
  import contextCommand from "./context.js";
5
+ import fastCommand from "./fast.js";
5
6
  import initCommand from "./init.js";
6
7
  import planCommand from "./plan.js";
7
8
  import toolSearchExtension from "./tools.js";
@@ -10,6 +11,7 @@ export default function slashCommands(pi: ExtensionAPI) {
10
11
  auditCommand(pi);
11
12
  clearCommand(pi);
12
13
  contextCommand(pi);
14
+ fastCommand(pi);
13
15
  initCommand(pi);
14
16
  planCommand(pi);
15
17
  toolSearchExtension(pi);
@@ -38,9 +38,8 @@ const BLOCKED_TOOLS = new Set([
38
38
  "async_bash",
39
39
  "bg_shell",
40
40
  "browser_navigate",
41
- "browser_go_back",
42
- "browser_go_forward",
43
- "browser_reload",
41
+ "browser_pages",
42
+ "browser_frames",
44
43
  "browser_click",
45
44
  "browser_drag",
46
45
  "browser_type",
@@ -51,18 +50,13 @@ const BLOCKED_TOOLS = new Set([
51
50
  "browser_select_option",
52
51
  "browser_set_checked",
53
52
  "browser_set_viewport",
54
- "browser_click_ref",
55
- "browser_hover_ref",
56
- "browser_fill_ref",
53
+ "browser_ref",
57
54
  "browser_act",
58
55
  "browser_batch",
59
56
  "browser_fill_form",
60
- "browser_mock_route",
61
- "browser_block_urls",
62
- "browser_clear_routes",
57
+ "browser_network",
63
58
  "browser_emulate_device",
64
- "browser_save_state",
65
- "browser_restore_state",
59
+ "browser_state",
66
60
  "browser_generate_test",
67
61
  "browser_verify",
68
62
  "write",
@@ -80,6 +74,7 @@ const REVISE_LABEL = "Revise plan";
80
74
  const CANCEL_LABEL = "Cancel";
81
75
  const DEFAULT_PLAN_REVIEW_AGENT = "generic";
82
76
  const DEFAULT_PLAN_CODING_AGENT = "worker";
77
+ const MIN_PLAN_CONFIDENCE = 8;
83
78
 
84
79
  type PlanApprovalStatus = "pending" | "approved" | "revising" | "cancelled";
85
80
  type RestorablePermissionMode = Exclude<PermissionMode, "plan">;
@@ -455,6 +450,9 @@ function buildPlanModeSystemPrompt(): string {
455
450
  "You are currently in plan mode.",
456
451
  "Investigate, clarify scope, and produce a persisted execution plan before making source changes.",
457
452
  "If requirements are ambiguous or constraints are missing, ask concise clarifying questions before drafting or saving a plan.",
453
+ `Before writing or updating a plan artifact, make sure your confidence is at least ${MIN_PLAN_CONFIDENCE}/10. If confidence is lower, investigate more or ask clarifying questions first.`,
454
+ "Include an explicit confidence line in every saved plan, for example: \"Confidence: 8/10\" or higher.",
455
+ "When adjusting an existing saved plan, prefer the edit tool for targeted changes. Rewrite the whole file only when the structure changes substantially or an exact edit is impractical.",
458
456
  "Do not modify source files or run side-effect commands while plan mode is active.",
459
457
  "Persist plan artifacts under .lsd/plan/.",
460
458
  ];
@@ -489,7 +487,7 @@ function buildApprovalActionInstructions(): string {
489
487
 
490
488
  return [
491
489
  "Ask for plan approval now via exactly one ask_user_questions tool call.",
492
- `Question 1 (single-select) id \"${PLAN_APPROVAL_ACTION_QUESTION_ID}\": ask what to do next with the plan.`,
490
+ `Question 1 (single-select) id \"${PLAN_APPROVAL_ACTION_QUESTION_ID}\": ask what to do next with the plan. In the question text, tell the user that if they choose \"${REVISE_LABEL}\" they should type exact requested changes in the dialog notes field before submitting.`,
493
491
  `Question 1 options: ${APPROVE_LABEL}, ${REVIEW_LABEL}, ${REVISE_LABEL}. Put "${APPROVE_LABEL}" first with a "(Recommended)" suffix in the description, not in the label.`,
494
492
  `Do not include \"${CANCEL_LABEL}\" as an explicit option — if the user wants to cancel they should choose \"None of the above\" and type \"${CANCEL_LABEL}\" in the note.`,
495
493
  `Question 2 (single-select) id \"${PLAN_APPROVAL_PERMISSION_QUESTION_ID}\": ask which execution mode to use.`,
@@ -560,6 +558,25 @@ function buildReviewSteeringMessage(planPath: string, planMarkdown?: string): st
560
558
  return details.join("\n\n");
561
559
  }
562
560
 
561
+ function buildRevisionSteeringMessage(planPath: string, requestedChanges?: string): string {
562
+ const details = [
563
+ `The user selected \"${REVISE_LABEL}\" for ${planPath}.`,
564
+ "Revise the existing saved plan instead of drafting a fresh replacement unless a full rewrite is genuinely necessary.",
565
+ "Prefer the edit tool for targeted adjustments to the current plan artifact. Use write only if the structure changes substantially or an exact edit is impractical.",
566
+ `Before saving the revised plan, make sure confidence is at least ${MIN_PLAN_CONFIDENCE}/10. If lower, investigate more or ask clarifying questions first.`,
567
+ "Keep an explicit confidence line in the plan, for example: \"Confidence: 8/10\" or higher.",
568
+ ];
569
+
570
+ if (requestedChanges) {
571
+ details.push(`User-requested changes: ${requestedChanges}`);
572
+ } else {
573
+ details.push("No concrete revision note was provided yet. Ask one concise clarifying question about what should change before editing the plan.");
574
+ }
575
+
576
+ details.push("After revising the saved plan artifact, ask for approval again.");
577
+ return details.join("\n\n");
578
+ }
579
+
563
580
  function approvalSelectionToExecutionMode(
564
581
  selected: string | undefined,
565
582
  ): { permissionMode: RestorablePermissionMode; executeWithSubagent: boolean } | undefined {
@@ -589,6 +606,12 @@ function getAnswerValues(answer: AskUserAnswer | undefined): string[] {
589
606
  return values;
590
607
  }
591
608
 
609
+ function getAnswerNote(answer: AskUserAnswer | undefined): string | undefined {
610
+ if (typeof answer?.notes !== "string") return undefined;
611
+ const note = answer.notes.trim();
612
+ return note.length > 0 ? note : undefined;
613
+ }
614
+
592
615
  function selectionRequestsCancel(selected: string[]): boolean {
593
616
  return selected.some((value) => {
594
617
  if (typeof value !== "string") return false;
@@ -612,6 +635,8 @@ export const __testing = {
612
635
  buildApprovalSteeringMessage,
613
636
  buildPlanPreviewMessage,
614
637
  buildReviewSteeringMessage,
638
+ buildRevisionSteeringMessage,
639
+ buildPlanModeSystemPrompt,
615
640
  buildAutoSuggestPlanModeSystemPrompt,
616
641
  readAutoSuggestPlanModeSetting,
617
642
  PLAN_SUGGEST_QUESTION_ID,
@@ -830,9 +855,14 @@ export default function planCommand(pi: ExtensionAPI) {
830
855
  }
831
856
 
832
857
  if (actionSelection.includes(REVISE_LABEL)) {
858
+ const requestedChanges = getAnswerNote(actionAnswer);
833
859
  enablePlanMode(pi, ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined, {
834
860
  approvalStatus: "revising",
835
861
  });
862
+ pi.sendUserMessage(
863
+ buildRevisionSteeringMessage(state.latestPlanPath ?? "the latest plan", requestedChanges),
864
+ { deliverAs: "steer" },
865
+ );
836
866
  }
837
867
  });
838
868
 
@@ -31,6 +31,11 @@ type BackgroundJobMetadata = {
31
31
  model?: string;
32
32
  };
33
33
 
34
+ type InProcessHandleLike = {
35
+ abort: () => void;
36
+ dispose: () => void;
37
+ };
38
+
34
39
  export class BackgroundJobManager {
35
40
  private jobs = new Map<string, BackgroundSubagentJob>();
36
41
  private evictionTimers = new Map<string, ReturnType<typeof setTimeout>>();
@@ -80,6 +85,29 @@ export class BackgroundJobManager {
80
85
  return this.attachJob(agentName, task, cwd, abortController, resultPromise, metadata);
81
86
  }
82
87
 
88
+ /**
89
+ * Adopt an in-process subagent handle into background tracking.
90
+ */
91
+ adoptHandle(
92
+ agentName: string,
93
+ task: string,
94
+ cwd: string,
95
+ handle: InProcessHandleLike,
96
+ resultPromise: Promise<BackgroundJobResult>,
97
+ metadata?: BackgroundJobMetadata,
98
+ ): string {
99
+ const abortController = new AbortController();
100
+ const onAbort = () => handle.abort();
101
+ abortController.signal.addEventListener("abort", onAbort, { once: true });
102
+
103
+ const wrapped = resultPromise.finally(() => {
104
+ abortController.signal.removeEventListener("abort", onAbort);
105
+ handle.dispose();
106
+ });
107
+
108
+ return this.attachJob(agentName, task, cwd, abortController, wrapped, metadata);
109
+ }
110
+
83
111
  /**
84
112
  * Cancel a running job.
85
113
  */