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
@@ -1,180 +1,117 @@
1
1
  import { Type } from "@sinclair/typebox";
2
2
  /**
3
- * State persistence tools — save/restore cookies, localStorage, sessionStorage.
3
+ * State persistence — save/restore cookies, localStorage, sessionStorage.
4
4
  */
5
5
  const STATE_DIR = ".gsd/browser-state";
6
6
  export function registerStatePersistenceTools(pi, deps) {
7
- // -------------------------------------------------------------------------
8
- // browser_save_state
9
- // -------------------------------------------------------------------------
10
7
  pi.registerTool({
11
- name: "browser_save_state",
12
- label: "Browser Save State",
13
- description: "Save cookies, localStorage, and sessionStorage to disk so authenticated sessions survive browser restarts. " +
14
- "State files are written to .gsd/browser-state/ and should be gitignored (may contain auth tokens). " +
15
- "Never displays secret values in output.",
8
+ name: "browser_state",
9
+ label: "Browser State",
10
+ description: "Save or restore browser state (cookies, localStorage, sessionStorage) to persist sessions across browser restarts. " +
11
+ "State files written to .gsd/browser-state/ (should be gitignored).",
16
12
  parameters: Type.Object({
17
- name: Type.Optional(Type.String({ description: "Name for the state file (default: 'default'). Used as the filename stem." })),
13
+ action: Type.Union([
14
+ Type.Literal("save"),
15
+ Type.Literal("restore"),
16
+ ], { description: "'save' — persist current state, 'restore' — load previously saved state" }),
17
+ name: Type.Optional(Type.String({ description: "State file name (default: 'default'). Used as filename stem." })),
18
18
  }),
19
19
  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
20
20
  try {
21
- const { context: ctx, page: p } = await deps.ensureBrowser();
22
- const name = deps.sanitizeArtifactName(params.name ?? "default", "default");
23
- const { mkdir, writeFile } = await import("node:fs/promises");
24
- const path = await import("node:path");
25
- const stateDir = path.resolve(process.cwd(), STATE_DIR);
26
- await mkdir(stateDir, { recursive: true });
27
- // 1. Playwright storageState: cookies + localStorage
28
- const storageState = await ctx.storageState();
29
- // 2. sessionStorage: must be extracted per-origin via page.evaluate
30
- const sessionStorageData = {};
31
- try {
32
- const origin = new URL(p.url()).origin;
33
- const ssData = await p.evaluate(() => {
34
- const data = {};
35
- for (let i = 0; i < sessionStorage.length; i++) {
36
- const key = sessionStorage.key(i);
37
- if (key)
38
- data[key] = sessionStorage.getItem(key) ?? "";
39
- }
40
- return data;
41
- });
42
- if (Object.keys(ssData).length > 0) {
43
- sessionStorageData[origin] = ssData;
44
- }
21
+ if (params.action === "save") {
22
+ return await saveState(deps, params.name ?? "default");
45
23
  }
46
- catch {
47
- // Page may not have a valid origin (about:blank, etc.)
24
+ else {
25
+ return await restoreState(deps, params.name ?? "default");
48
26
  }
49
- const combined = {
50
- storageState,
51
- sessionStorage: sessionStorageData,
52
- savedAt: new Date().toISOString(),
53
- url: p.url(),
54
- };
55
- const filePath = path.join(stateDir, `${name}.json`);
56
- await writeFile(filePath, JSON.stringify(combined, null, 2));
57
- // Ensure .gitignore covers the state dir
58
- const gitignorePath = path.resolve(process.cwd(), STATE_DIR, ".gitignore");
59
- await writeFile(gitignorePath, "*\n!.gitignore\n").catch(() => { });
60
- const cookieCount = storageState.cookies?.length ?? 0;
61
- const localStorageOrigins = storageState.origins?.length ?? 0;
62
- const sessionStorageOrigins = Object.keys(sessionStorageData).length;
63
- return {
64
- content: [{
65
- type: "text",
66
- text: `State saved: ${filePath}\nCookies: ${cookieCount}\nlocalStorage origins: ${localStorageOrigins}\nsessionStorage origins: ${sessionStorageOrigins}`,
67
- }],
68
- details: {
69
- path: filePath,
70
- cookieCount,
71
- localStorageOrigins,
72
- sessionStorageOrigins,
73
- },
74
- };
75
27
  }
76
28
  catch (err) {
77
29
  return {
78
- content: [{ type: "text", text: `Save state failed: ${err.message}` }],
30
+ content: [{ type: "text", text: `State '${params.action}' failed: ${err.message}` }],
79
31
  details: { error: err.message },
80
32
  isError: true,
81
33
  };
82
34
  }
83
35
  },
84
36
  });
85
- // -------------------------------------------------------------------------
86
- // browser_restore_state
87
- // -------------------------------------------------------------------------
88
- pi.registerTool({
89
- name: "browser_restore_state",
90
- label: "Browser Restore State",
91
- description: "Restore cookies, localStorage, and sessionStorage from a previously saved state file. " +
92
- "Injects cookies via context.addCookies() and storage via page.evaluate(). " +
93
- "For full fidelity, restore before navigating to the target site.",
94
- parameters: Type.Object({
95
- name: Type.Optional(Type.String({ description: "Name of the state file to restore (default: 'default')." })),
96
- }),
97
- async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
98
- try {
99
- const { context: ctx, page: p } = await deps.ensureBrowser();
100
- const name = deps.sanitizeArtifactName(params.name ?? "default", "default");
101
- const { readFile } = await import("node:fs/promises");
102
- const path = await import("node:path");
103
- const filePath = path.join(process.cwd(), STATE_DIR, `${name}.json`);
104
- let raw;
105
- try {
106
- raw = await readFile(filePath, "utf-8");
107
- }
108
- catch {
109
- return {
110
- content: [{ type: "text", text: `State file not found: ${filePath}` }],
111
- details: { error: "file_not_found", path: filePath },
112
- isError: true,
113
- };
114
- }
115
- const combined = JSON.parse(raw);
116
- const storageState = combined.storageState;
117
- const sessionStorageData = combined.sessionStorage ?? {};
118
- // 1. Restore cookies
119
- let cookieCount = 0;
120
- if (storageState?.cookies?.length) {
121
- await ctx.addCookies(storageState.cookies);
122
- cookieCount = storageState.cookies.length;
37
+ async function saveState(deps, name) {
38
+ const { context: ctx, page: p } = await deps.ensureBrowser();
39
+ name = deps.sanitizeArtifactName(name, "default");
40
+ const { mkdir, writeFile } = await import("node:fs/promises");
41
+ const path = await import("node:path");
42
+ const stateDir = path.resolve(process.cwd(), STATE_DIR);
43
+ await mkdir(stateDir, { recursive: true });
44
+ const storageState = await ctx.storageState();
45
+ const sessionStorageData = {};
46
+ try {
47
+ const origin = new URL(p.url()).origin;
48
+ const ssData = await p.evaluate(() => {
49
+ const data = {};
50
+ for (let i = 0; i < sessionStorage.length; i++) {
51
+ const key = sessionStorage.key(i);
52
+ if (key)
53
+ data[key] = sessionStorage.getItem(key) ?? "";
123
54
  }
124
- // 2. Restore localStorage via page.evaluate
125
- let localStorageOrigins = 0;
126
- if (storageState?.origins?.length) {
127
- for (const origin of storageState.origins) {
128
- try {
129
- await p.evaluate((items) => {
130
- for (const { name, value } of items) {
131
- localStorage.setItem(name, value);
132
- }
133
- }, origin.localStorage ?? []);
134
- localStorageOrigins++;
135
- }
136
- catch {
137
- // Origin mismatch localStorage can only be set on matching origin
138
- }
139
- }
140
- }
141
- // 3. Restore sessionStorage via page.evaluate
142
- let sessionStorageOrigins = 0;
143
- for (const [_origin, data] of Object.entries(sessionStorageData)) {
144
- try {
145
- await p.evaluate((items) => {
146
- for (const [key, value] of Object.entries(items)) {
147
- sessionStorage.setItem(key, value);
148
- }
149
- }, data);
150
- sessionStorageOrigins++;
151
- }
152
- catch {
153
- // Origin mismatch
154
- }
55
+ return data;
56
+ });
57
+ if (Object.keys(ssData).length > 0)
58
+ sessionStorageData[origin] = ssData;
59
+ }
60
+ catch { /* Page may not have a valid origin */ }
61
+ const combined = { storageState, sessionStorage: sessionStorageData, savedAt: new Date().toISOString(), url: p.url() };
62
+ const filePath = path.join(stateDir, `${name}.json`);
63
+ await writeFile(filePath, JSON.stringify(combined, null, 2));
64
+ const gitignorePath = path.resolve(process.cwd(), STATE_DIR, ".gitignore");
65
+ await writeFile(gitignorePath, "*\n!.gitignore\n").catch(() => { });
66
+ return {
67
+ content: [{ type: "text", text: `State saved: ${filePath}\nCookies: ${storageState.cookies?.length ?? 0}\nlocalStorage origins: ${storageState.origins?.length ?? 0}\nsessionStorage origins: ${Object.keys(sessionStorageData).length}` }],
68
+ details: { path: filePath, cookieCount: storageState.cookies?.length ?? 0, localStorageOrigins: storageState.origins?.length ?? 0, sessionStorageOrigins: Object.keys(sessionStorageData).length },
69
+ };
70
+ }
71
+ async function restoreState(deps, name) {
72
+ const { context: ctx, page: p } = await deps.ensureBrowser();
73
+ name = deps.sanitizeArtifactName(name, "default");
74
+ const { readFile } = await import("node:fs/promises");
75
+ const path = await import("node:path");
76
+ const filePath = path.join(process.cwd(), STATE_DIR, `${name}.json`);
77
+ let raw;
78
+ try {
79
+ raw = await readFile(filePath, "utf-8");
80
+ }
81
+ catch {
82
+ return { content: [{ type: "text", text: `State file not found: ${filePath}` }], details: { error: "file_not_found", path: filePath }, isError: true };
83
+ }
84
+ const combined = JSON.parse(raw);
85
+ const storageState = combined.storageState;
86
+ const sessionStorageData = combined.sessionStorage ?? {};
87
+ let cookieCount = 0;
88
+ if (storageState?.cookies?.length) {
89
+ await ctx.addCookies(storageState.cookies);
90
+ cookieCount = storageState.cookies.length;
91
+ }
92
+ let localStorageOrigins = 0;
93
+ if (storageState?.origins?.length) {
94
+ for (const origin of storageState.origins) {
95
+ try {
96
+ await p.evaluate((items) => { for (const { name, value } of items)
97
+ localStorage.setItem(name, value); }, origin.localStorage ?? []);
98
+ localStorageOrigins++;
155
99
  }
156
- return {
157
- content: [{
158
- type: "text",
159
- text: `State restored from: ${filePath}\nCookies: ${cookieCount}\nlocalStorage origins: ${localStorageOrigins}\nsessionStorage origins: ${sessionStorageOrigins}\nSaved at: ${combined.savedAt ?? "unknown"}`,
160
- }],
161
- details: {
162
- path: filePath,
163
- cookieCount,
164
- localStorageOrigins,
165
- sessionStorageOrigins,
166
- savedAt: combined.savedAt,
167
- savedUrl: combined.url,
168
- },
169
- };
100
+ catch { /* Origin mismatch */ }
170
101
  }
171
- catch (err) {
172
- return {
173
- content: [{ type: "text", text: `Restore state failed: ${err.message}` }],
174
- details: { error: err.message },
175
- isError: true,
176
- };
102
+ }
103
+ let sessionStorageOrigins = 0;
104
+ for (const [_origin, data] of Object.entries(sessionStorageData)) {
105
+ try {
106
+ await p.evaluate((items) => { for (const [key, value] of Object.entries(items))
107
+ sessionStorage.setItem(key, value); }, data);
108
+ sessionStorageOrigins++;
177
109
  }
178
- },
179
- });
110
+ catch { /* Origin mismatch */ }
111
+ }
112
+ return {
113
+ content: [{ type: "text", text: `State restored from: ${filePath}\nCookies: ${cookieCount}\nlocalStorage origins: ${localStorageOrigins}\nsessionStorage origins: ${sessionStorageOrigins}` }],
114
+ details: { path: filePath, cookieCount, localStorageOrigins, sessionStorageOrigins, savedAt: combined.savedAt, savedUrl: combined.url },
115
+ };
116
+ }
180
117
  }
@@ -463,7 +463,7 @@ export function formatVersionedRef(version, key) {
463
463
  return `@v${version}:${key}`;
464
464
  }
465
465
  export function staleRefGuidance(refDisplay, reason) {
466
- 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.`;
466
+ 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.`;
467
467
  }
468
468
  // ---------------------------------------------------------------------------
469
469
  // Compact state summary formatting
@@ -18,6 +18,7 @@ const IS_MEMORY_MAINTENANCE_WORKER = process.env.LSD_MEMORY_EXTRACT === "1" || p
18
18
  const IS_CACHE_TIMER_FORCED_OFF = process.env.LSD_DISABLE_CACHE_TIMER === "1" || process.env.GSD_DISABLE_CACHE_TIMER === "1";
19
19
  // ANSI color codes for timer display
20
20
  const ANSI_RESET = "\x1b[0m";
21
+ const ANSI_GREEN = "\x1b[32m";
21
22
  const ANSI_YELLOW = "\x1b[33m";
22
23
  const ANSI_RED = "\x1b[31m";
23
24
  function getSettingsPath() {
@@ -63,8 +64,8 @@ function formatElapsed(ms) {
63
64
  // 5–10 minutes: yellow
64
65
  return `${ANSI_YELLOW}⏱ ${time}${ANSI_RESET}`;
65
66
  }
66
- // Under 5 minutes: plain (inherits footer dim styling)
67
- return `⏱ ${time}`;
67
+ // Under 5 minutes: green
68
+ return `${ANSI_GREEN}⏱ ${time}${ANSI_RESET}`;
68
69
  }
69
70
  export default function cacheTimerExtension(pi) {
70
71
  if (IS_MEMORY_MAINTENANCE_WORKER || IS_CACHE_TIMER_FORCED_OFF) {
@@ -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,73 @@
1
+ import { getAgentDir, SettingsManager } from "@gsd/pi-coding-agent";
2
+ function parseFastCommandAction(args) {
3
+ const normalized = args.trim().toLowerCase();
4
+ if (!normalized)
5
+ return "toggle";
6
+ if (normalized === "on")
7
+ return "on";
8
+ if (normalized === "off")
9
+ return "off";
10
+ if (normalized === "status")
11
+ return "status";
12
+ return "invalid";
13
+ }
14
+ function supportsFastMode(model) {
15
+ if (!model)
16
+ return false;
17
+ if (model.api === "openai-codex-responses")
18
+ return true;
19
+ if (model.api !== "openai-responses")
20
+ return false;
21
+ if (model.provider !== "openai")
22
+ return false;
23
+ return model.capabilities?.supportsServiceTier === true;
24
+ }
25
+ function getModelLabel(model) {
26
+ if (!model)
27
+ return "no active model";
28
+ return `${model.provider}/${model.id}`;
29
+ }
30
+ function getSettingsManager() {
31
+ return SettingsManager.create(process.cwd(), getAgentDir());
32
+ }
33
+ export const __testing = {
34
+ parseFastCommandAction,
35
+ supportsFastMode,
36
+ };
37
+ export default function fastCommand(pi) {
38
+ pi.registerCommand("fast", {
39
+ description: "Toggle fast mode for OpenAI/Codex models (service_tier=priority)",
40
+ getArgumentCompletions(prefix) {
41
+ const options = [
42
+ { value: "on", label: "on", description: "Enable fast mode" },
43
+ { value: "off", label: "off", description: "Disable fast mode" },
44
+ { value: "status", label: "status", description: "Show current fast-mode status" },
45
+ ];
46
+ const query = prefix.trim().toLowerCase();
47
+ return options.filter((item) => item.value.startsWith(query));
48
+ },
49
+ async handler(args, ctx) {
50
+ const settings = getSettingsManager();
51
+ const current = settings.getFastMode();
52
+ const action = parseFastCommandAction(args);
53
+ const model = ctx.model;
54
+ const supported = supportsFastMode(model);
55
+ const modelLabel = getModelLabel(model);
56
+ if (action === "invalid") {
57
+ ctx.ui.notify("Usage: /fast [on|off|status]", "warning");
58
+ return;
59
+ }
60
+ if (action === "status") {
61
+ ctx.ui.notify(`Fast mode: ${current ? "ON" : "OFF"} · model ${modelLabel} is ${supported ? "supported" : "unsupported"}`, "info");
62
+ return;
63
+ }
64
+ const next = action === "toggle" ? !current : action === "on";
65
+ settings.setFastMode(next);
66
+ if (!supported) {
67
+ ctx.ui.notify(`Fast mode: ${next ? "ON" : "OFF"} (saved). Current model ${modelLabel} does not support fast mode.`, "warning");
68
+ return;
69
+ }
70
+ ctx.ui.notify(`Fast mode: ${next ? "ON" : "OFF"} (saved). ${next ? "Requests will include service_tier=priority." : "Requests will omit service_tier."}`, "info");
71
+ },
72
+ });
73
+ }
@@ -1,6 +1,7 @@
1
1
  import auditCommand from "./audit.js";
2
2
  import clearCommand from "./clear.js";
3
3
  import contextCommand from "./context.js";
4
+ import fastCommand from "./fast.js";
4
5
  import initCommand from "./init.js";
5
6
  import planCommand from "./plan.js";
6
7
  import toolSearchExtension from "./tools.js";
@@ -8,6 +9,7 @@ export default function slashCommands(pi) {
8
9
  auditCommand(pi);
9
10
  clearCommand(pi);
10
11
  contextCommand(pi);
12
+ fastCommand(pi);
11
13
  initCommand(pi);
12
14
  planCommand(pi);
13
15
  toolSearchExtension(pi);
@@ -28,9 +28,8 @@ const BLOCKED_TOOLS = new Set([
28
28
  "async_bash",
29
29
  "bg_shell",
30
30
  "browser_navigate",
31
- "browser_go_back",
32
- "browser_go_forward",
33
- "browser_reload",
31
+ "browser_pages",
32
+ "browser_frames",
34
33
  "browser_click",
35
34
  "browser_drag",
36
35
  "browser_type",
@@ -41,18 +40,13 @@ const BLOCKED_TOOLS = new Set([
41
40
  "browser_select_option",
42
41
  "browser_set_checked",
43
42
  "browser_set_viewport",
44
- "browser_click_ref",
45
- "browser_hover_ref",
46
- "browser_fill_ref",
43
+ "browser_ref",
47
44
  "browser_act",
48
45
  "browser_batch",
49
46
  "browser_fill_form",
50
- "browser_mock_route",
51
- "browser_block_urls",
52
- "browser_clear_routes",
47
+ "browser_network",
53
48
  "browser_emulate_device",
54
- "browser_save_state",
55
- "browser_restore_state",
49
+ "browser_state",
56
50
  "browser_generate_test",
57
51
  "browser_verify",
58
52
  "write",
@@ -70,6 +64,7 @@ const REVISE_LABEL = "Revise plan";
70
64
  const CANCEL_LABEL = "Cancel";
71
65
  const DEFAULT_PLAN_REVIEW_AGENT = "generic";
72
66
  const DEFAULT_PLAN_CODING_AGENT = "worker";
67
+ const MIN_PLAN_CONFIDENCE = 8;
73
68
  const INITIAL_STATE = {
74
69
  active: false,
75
70
  task: "",
@@ -366,6 +361,9 @@ function buildPlanModeSystemPrompt() {
366
361
  "You are currently in plan mode.",
367
362
  "Investigate, clarify scope, and produce a persisted execution plan before making source changes.",
368
363
  "If requirements are ambiguous or constraints are missing, ask concise clarifying questions before drafting or saving a plan.",
364
+ `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.`,
365
+ "Include an explicit confidence line in every saved plan, for example: \"Confidence: 8/10\" or higher.",
366
+ "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.",
369
367
  "Do not modify source files or run side-effect commands while plan mode is active.",
370
368
  "Persist plan artifacts under .lsd/plan/.",
371
369
  ];
@@ -400,7 +398,7 @@ function buildApprovalActionInstructions() {
400
398
  const newSessionLabel = buildNewSessionOptionLabel();
401
399
  return [
402
400
  "Ask for plan approval now via exactly one ask_user_questions tool call.",
403
- `Question 1 (single-select) id \"${PLAN_APPROVAL_ACTION_QUESTION_ID}\": ask what to do next with the plan.`,
401
+ `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.`,
404
402
  `Question 1 options: ${APPROVE_LABEL}, ${REVIEW_LABEL}, ${REVISE_LABEL}. Put "${APPROVE_LABEL}" first with a "(Recommended)" suffix in the description, not in the label.`,
405
403
  `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.`,
406
404
  `Question 2 (single-select) id \"${PLAN_APPROVAL_PERMISSION_QUESTION_ID}\": ask which execution mode to use.`,
@@ -454,6 +452,23 @@ function buildReviewSteeringMessage(planPath, planMarkdown) {
454
452
  details.push("After the subagent responds, summarize its feedback for the user, present the current plan again, and then ask for approval again.", buildApprovalActionInstructions());
455
453
  return details.join("\n\n");
456
454
  }
455
+ function buildRevisionSteeringMessage(planPath, requestedChanges) {
456
+ const details = [
457
+ `The user selected \"${REVISE_LABEL}\" for ${planPath}.`,
458
+ "Revise the existing saved plan instead of drafting a fresh replacement unless a full rewrite is genuinely necessary.",
459
+ "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.",
460
+ `Before saving the revised plan, make sure confidence is at least ${MIN_PLAN_CONFIDENCE}/10. If lower, investigate more or ask clarifying questions first.`,
461
+ "Keep an explicit confidence line in the plan, for example: \"Confidence: 8/10\" or higher.",
462
+ ];
463
+ if (requestedChanges) {
464
+ details.push(`User-requested changes: ${requestedChanges}`);
465
+ }
466
+ else {
467
+ details.push("No concrete revision note was provided yet. Ask one concise clarifying question about what should change before editing the plan.");
468
+ }
469
+ details.push("After revising the saved plan artifact, ask for approval again.");
470
+ return details.join("\n\n");
471
+ }
457
472
  function approvalSelectionToExecutionMode(selected) {
458
473
  if (!selected)
459
474
  return undefined;
@@ -482,6 +497,12 @@ function getAnswerValues(answer) {
482
497
  }
483
498
  return values;
484
499
  }
500
+ function getAnswerNote(answer) {
501
+ if (typeof answer?.notes !== "string")
502
+ return undefined;
503
+ const note = answer.notes.trim();
504
+ return note.length > 0 ? note : undefined;
505
+ }
485
506
  function selectionRequestsCancel(selected) {
486
507
  return selected.some((value) => {
487
508
  if (typeof value !== "string")
@@ -506,6 +527,8 @@ export const __testing = {
506
527
  buildApprovalSteeringMessage,
507
528
  buildPlanPreviewMessage,
508
529
  buildReviewSteeringMessage,
530
+ buildRevisionSteeringMessage,
531
+ buildPlanModeSystemPrompt,
509
532
  buildAutoSuggestPlanModeSystemPrompt,
510
533
  readAutoSuggestPlanModeSetting,
511
534
  PLAN_SUGGEST_QUESTION_ID,
@@ -696,9 +719,11 @@ export default function planCommand(pi) {
696
719
  return;
697
720
  }
698
721
  if (actionSelection.includes(REVISE_LABEL)) {
722
+ const requestedChanges = getAnswerNote(actionAnswer);
699
723
  enablePlanMode(pi, ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined, {
700
724
  approvalStatus: "revising",
701
725
  });
726
+ pi.sendUserMessage(buildRevisionSteeringMessage(state.latestPlanPath ?? "the latest plan", requestedChanges), { deliverAs: "steer" });
702
727
  }
703
728
  });
704
729
  pi.registerCommand("plan", {
@@ -38,6 +38,19 @@ export class BackgroundJobManager {
38
38
  adoptRunning(agentName, task, cwd, abortController, resultPromise, metadata) {
39
39
  return this.attachJob(agentName, task, cwd, abortController, resultPromise, metadata);
40
40
  }
41
+ /**
42
+ * Adopt an in-process subagent handle into background tracking.
43
+ */
44
+ adoptHandle(agentName, task, cwd, handle, resultPromise, metadata) {
45
+ const abortController = new AbortController();
46
+ const onAbort = () => handle.abort();
47
+ abortController.signal.addEventListener("abort", onAbort, { once: true });
48
+ const wrapped = resultPromise.finally(() => {
49
+ abortController.signal.removeEventListener("abort", onAbort);
50
+ handle.dispose();
51
+ });
52
+ return this.attachJob(agentName, task, cwd, abortController, wrapped, metadata);
53
+ }
41
54
  /**
42
55
  * Cancel a running job.
43
56
  */