lsd-pi 1.1.2 → 1.1.3

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 (183) hide show
  1. package/README.md +2 -1
  2. package/dist/bedrock-auth.d.ts +25 -0
  3. package/dist/bedrock-auth.js +59 -0
  4. package/dist/headless.js +8 -3
  5. package/dist/loader.js +1 -0
  6. package/dist/onboarding-llm.d.ts +37 -0
  7. package/dist/onboarding-llm.js +64 -0
  8. package/dist/onboarding.d.ts +2 -14
  9. package/dist/onboarding.js +146 -71
  10. package/dist/pi-migration.js +1 -0
  11. package/dist/resources/extensions/memory/auto-extract.js +21 -3
  12. package/dist/resources/extensions/memory/dream.js +703 -0
  13. package/dist/resources/extensions/memory/extension-manifest.json +2 -2
  14. package/dist/resources/extensions/memory/index.js +115 -8
  15. package/dist/resources/extensions/slash-commands/extension-manifest.json +10 -10
  16. package/dist/resources/extensions/slash-commands/index.js +0 -4
  17. package/dist/resources/extensions/slash-commands/plan.js +181 -45
  18. package/dist/resources/extensions/subagent/agents.js +14 -1
  19. package/dist/resources/extensions/subagent/configured-model.js +3 -2
  20. package/dist/resources/extensions/subagent/index.js +34 -28
  21. package/dist/resources/extensions/subagent/launch-helpers.js +24 -0
  22. package/dist/resources/extensions/subagent/model-resolution.js +41 -3
  23. package/dist/resources/extensions/usage/extension-manifest.json +11 -0
  24. package/dist/resources/extensions/usage/index.js +346 -0
  25. package/{src/resources/skills/create-gsd-extension → dist/resources/skills/create-lsd-extension}/SKILL.md +6 -6
  26. package/{src/resources/skills/create-gsd-extension → dist/resources/skills/create-lsd-extension}/references/custom-tools.md +1 -1
  27. package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/references/extension-lifecycle.md +2 -2
  28. package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/references/extensioncontext-reference.md +1 -1
  29. package/{src/resources/skills/create-gsd-extension → dist/resources/skills/create-lsd-extension}/references/key-rules-gotchas.md +4 -4
  30. package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/references/packaging-distribution.md +6 -6
  31. package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/workflows/create-extension.md +3 -3
  32. package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/workflows/debug-extension.md +5 -5
  33. package/dist/resources/skills/teams-debug/SKILL.md +5 -6
  34. package/dist/resources/skills/teams-document/SKILL.md +1 -2
  35. package/dist/resources/skills/teams-plan/SKILL.md +3 -4
  36. package/dist/resources/skills/teams-run/SKILL.md +3 -4
  37. package/dist/resources/skills/teams-verify/SKILL.md +4 -5
  38. package/dist/startup-model-validation.js +1 -0
  39. package/dist/welcome-screen.js +13 -11
  40. package/dist/wizard.js +12 -0
  41. package/package.json +1 -1
  42. package/packages/pi-ai/dist/models.generated.d.ts +688 -409
  43. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  44. package/packages/pi-ai/dist/models.generated.js +761 -488
  45. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  46. package/packages/pi-ai/scripts/generate-models.ts +40 -18
  47. package/packages/pi-ai/src/models.generated.ts +759 -486
  48. package/packages/pi-coding-agent/dist/cli/config-selector.js +1 -1
  49. package/packages/pi-coding-agent/dist/cli/config-selector.js.map +1 -1
  50. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +1 -2
  51. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  52. package/packages/pi-coding-agent/dist/core/agent-session.js +6 -30
  53. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  54. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +6 -0
  55. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  56. package/packages/pi-coding-agent/dist/core/settings-manager.js +44 -1
  57. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  58. package/packages/pi-coding-agent/dist/core/skill-tool.test.js +9 -5
  59. package/packages/pi-coding-agent/dist/core/skill-tool.test.js.map +1 -1
  60. package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
  61. package/packages/pi-coding-agent/dist/core/skills.js +3 -2
  62. package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
  63. package/packages/pi-coding-agent/dist/index.d.ts +1 -1
  64. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  65. package/packages/pi-coding-agent/dist/index.js +1 -1
  66. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  67. package/packages/pi-coding-agent/dist/main.js +1 -1
  68. package/packages/pi-coding-agent/dist/main.js.map +1 -1
  69. package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.js +2 -2
  70. package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.js.map +1 -1
  71. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
  72. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +15 -12
  73. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
  74. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +6 -0
  75. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  76. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +25 -1
  77. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
  78. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +1 -1
  79. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  80. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  81. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +10 -0
  82. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  83. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js +1 -1
  84. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js.map +1 -1
  85. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  86. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +31 -22
  87. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  88. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts +18 -5
  89. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  90. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js +139 -20
  91. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js.map +1 -1
  92. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.d.ts.map +1 -1
  93. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +4 -0
  94. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -1
  95. package/packages/pi-coding-agent/package.json +1 -1
  96. package/packages/pi-coding-agent/src/cli/config-selector.ts +1 -1
  97. package/packages/pi-coding-agent/src/core/agent-session.ts +5 -28
  98. package/packages/pi-coding-agent/src/core/settings-manager.ts +52 -1
  99. package/packages/pi-coding-agent/src/core/skill-tool.test.ts +18 -5
  100. package/packages/pi-coding-agent/src/core/skills.ts +3 -2
  101. package/packages/pi-coding-agent/src/index.ts +1 -1
  102. package/packages/pi-coding-agent/src/main.ts +1 -1
  103. package/packages/pi-coding-agent/src/modes/interactive/components/bash-execution.ts +2 -2
  104. package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +12 -13
  105. package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +39 -1
  106. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +1 -1
  107. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +10 -0
  108. package/packages/pi-coding-agent/src/modes/interactive/controllers/extension-ui-controller.ts +1 -1
  109. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +46 -20
  110. package/packages/pi-coding-agent/src/modes/interactive/theme/theme.ts +171 -20
  111. package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +4 -0
  112. package/packages/pi-tui/dist/components/editor.d.ts +1 -0
  113. package/packages/pi-tui/dist/components/editor.d.ts.map +1 -1
  114. package/packages/pi-tui/dist/components/editor.js +23 -0
  115. package/packages/pi-tui/dist/components/editor.js.map +1 -1
  116. package/packages/pi-tui/src/components/editor.ts +23 -0
  117. package/pkg/dist/modes/interactive/theme/theme.d.ts +18 -5
  118. package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  119. package/pkg/dist/modes/interactive/theme/theme.js +139 -20
  120. package/pkg/dist/modes/interactive/theme/theme.js.map +1 -1
  121. package/pkg/dist/modes/interactive/theme/themes.d.ts.map +1 -1
  122. package/pkg/dist/modes/interactive/theme/themes.js +4 -0
  123. package/pkg/dist/modes/interactive/theme/themes.js.map +1 -1
  124. package/pkg/package.json +1 -1
  125. package/src/resources/extensions/memory/auto-extract.ts +23 -3
  126. package/src/resources/extensions/memory/dream.ts +814 -0
  127. package/src/resources/extensions/memory/extension-manifest.json +2 -2
  128. package/src/resources/extensions/memory/index.ts +134 -13
  129. package/src/resources/extensions/memory/tests/auto-extract.test.ts +10 -2
  130. package/src/resources/extensions/memory/tests/dream.test.ts +142 -0
  131. package/src/resources/extensions/slash-commands/extension-manifest.json +10 -10
  132. package/src/resources/extensions/slash-commands/index.ts +3 -7
  133. package/src/resources/extensions/slash-commands/plan.ts +192 -46
  134. package/src/resources/extensions/subagent/agents.ts +11 -1
  135. package/src/resources/extensions/subagent/configured-model.ts +3 -2
  136. package/src/resources/extensions/subagent/index.ts +38 -30
  137. package/src/resources/extensions/subagent/launch-helpers.ts +30 -0
  138. package/src/resources/extensions/subagent/model-resolution.ts +40 -3
  139. package/src/resources/extensions/usage/extension-manifest.json +11 -0
  140. package/src/resources/extensions/usage/index.ts +441 -0
  141. package/{dist/resources/skills/create-gsd-extension → src/resources/skills/create-lsd-extension}/SKILL.md +6 -6
  142. package/{dist/resources/skills/create-gsd-extension → src/resources/skills/create-lsd-extension}/references/custom-tools.md +1 -1
  143. package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/references/extension-lifecycle.md +2 -2
  144. package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/references/extensioncontext-reference.md +1 -1
  145. package/{dist/resources/skills/create-gsd-extension → src/resources/skills/create-lsd-extension}/references/key-rules-gotchas.md +4 -4
  146. package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/references/packaging-distribution.md +6 -6
  147. package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/workflows/create-extension.md +3 -3
  148. package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/workflows/debug-extension.md +5 -5
  149. package/src/resources/skills/teams-debug/SKILL.md +5 -6
  150. package/src/resources/skills/teams-document/SKILL.md +1 -2
  151. package/src/resources/skills/teams-plan/SKILL.md +3 -4
  152. package/src/resources/skills/teams-run/SKILL.md +3 -4
  153. package/src/resources/skills/teams-verify/SKILL.md +4 -5
  154. package/dist/resources/extensions/slash-commands/create-extension.js +0 -264
  155. package/dist/resources/extensions/slash-commands/create-slash-command.js +0 -208
  156. package/src/resources/extensions/slash-commands/create-extension.ts +0 -297
  157. package/src/resources/extensions/slash-commands/create-slash-command.ts +0 -234
  158. /package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/references/compaction-session-control.md +0 -0
  159. /package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/references/custom-commands.md +0 -0
  160. /package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/references/custom-rendering.md +0 -0
  161. /package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/references/custom-ui.md +0 -0
  162. /package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/references/events-reference.md +0 -0
  163. /package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/references/extensionapi-reference.md +0 -0
  164. /package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/references/mode-behavior.md +0 -0
  165. /package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/references/model-provider-management.md +0 -0
  166. /package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/references/remote-execution-overrides.md +0 -0
  167. /package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/references/state-management.md +0 -0
  168. /package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/references/system-prompt-modification.md +0 -0
  169. /package/dist/resources/skills/{create-gsd-extension → create-lsd-extension}/workflows/add-capability.md +0 -0
  170. /package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/references/compaction-session-control.md +0 -0
  171. /package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/references/custom-commands.md +0 -0
  172. /package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/references/custom-rendering.md +0 -0
  173. /package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/references/custom-ui.md +0 -0
  174. /package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/references/events-reference.md +0 -0
  175. /package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/references/extensionapi-reference.md +0 -0
  176. /package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/references/mode-behavior.md +0 -0
  177. /package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/references/model-provider-management.md +0 -0
  178. /package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/references/remote-execution-overrides.md +0 -0
  179. /package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/references/state-management.md +0 -0
  180. /package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/references/system-prompt-modification.md +0 -0
  181. /package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/templates/extension-skeleton.ts +0 -0
  182. /package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/templates/stateful-tool-skeleton.ts +0 -0
  183. /package/src/resources/skills/{create-gsd-extension → create-lsd-extension}/workflows/add-capability.md +0 -0
@@ -5,7 +5,7 @@
5
5
  "description": "Persistent file-based memory across sessions",
6
6
  "tier": "bundled",
7
7
  "provides": {
8
- "hooks": ["session_start", "before_agent_start", "session_shutdown"],
9
- "commands": ["memories", "remember", "forget"]
8
+ "hooks": ["session_start", "before_agent_start", "turn_end", "tool_call", "session_shutdown"],
9
+ "commands": ["memories", "remember", "forget", "dream", "auto-dream"]
10
10
  }
11
11
  }
@@ -7,15 +7,15 @@
7
7
  * Memory files live under ~/.lsd/memory/<sanitized-project-path>/ and are
8
8
  * indexed by a MEMORY.md entrypoint that is always loaded into context.
9
9
  */
10
+ import { isToolCallEventType, } from '@gsd/pi-coding-agent';
10
11
  import { readFileSync, existsSync, writeFileSync } from 'node:fs';
11
12
  import { getMemoryDir, getMemoryEntrypoint, ensureMemoryDir } from './memory-paths.js';
12
13
  import { MEMORY_FRONTMATTER_EXAMPLE, TYPES_SECTION, WHAT_NOT_TO_SAVE_SECTION, WHEN_TO_ACCESS_SECTION, TRUSTING_RECALL_SECTION, } from './memory-types.js';
13
14
  import { scanMemoryFiles } from './memory-scan.js';
14
15
  import { memoryAge } from './memory-age.js';
15
16
  import { extractMemories } from './auto-extract.js';
17
+ import { formatDreamStatus, isMaintenanceModeToolAllowed, maybeStartAutoDream, readAutoDreamSettings, setProjectAutoDreamEnabled, startDream, } from './dream.js';
16
18
  // ── Constants ────────────────────────────────────────────────────────
17
- /** Name of the entrypoint file that indexes all memories. */
18
- const ENTRYPOINT_NAME = 'MEMORY.md';
19
19
  /** Maximum number of lines loaded from MEMORY.md into context. */
20
20
  const MAX_ENTRYPOINT_LINES = 200;
21
21
  /** Maximum byte size loaded from MEMORY.md into context. */
@@ -109,14 +109,17 @@ ${MEMORY_FRONTMATTER_EXAMPLE.join('\n')}
109
109
  * Memory extension for LSD.
110
110
  *
111
111
  * Lifecycle:
112
- * session_start → bootstrap memory directory & MEMORY.md
112
+ * session_start → bootstrap memory directory & MEMORY.md
113
113
  * before_agent_start → inject memory system prompt
114
+ * turn_end → check auto-dream gates and start background consolidation
114
115
  * session_shutdown → fire-and-forget auto-extract of new memories
115
116
  *
116
117
  * Commands:
117
- * /memories — list all saved memories
118
- * /remember — save a memory immediately
119
- * /forget — remove a memory by topic
118
+ * /memories — list all saved memories
119
+ * /remember — save a memory immediately
120
+ * /forget — remove a memory by topic
121
+ * /dream — run memory consolidation now or show dream status
122
+ * /auto-dream — enable/disable/show auto-dream status
120
123
  */
121
124
  export default function memoryExtension(pi) {
122
125
  let memoryCwd = '';
@@ -153,12 +156,49 @@ export default function memoryExtension(pi) {
153
156
  systemPrompt: event.systemPrompt + '\n\n' + prompt,
154
157
  };
155
158
  });
159
+ // ── turn_end: check auto-dream gates ───────────────────────────────
160
+ pi.on('turn_end', async (_event, ctx) => {
161
+ if (!memoryCwd)
162
+ return;
163
+ const result = maybeStartAutoDream(ctx);
164
+ if (result.started) {
165
+ pi.sendMessage({
166
+ customType: 'memory:auto-dream',
167
+ content: result.message,
168
+ display: true,
169
+ });
170
+ }
171
+ });
172
+ // ── tool_call: restrict background maintenance workers ─────────────
173
+ pi.on('tool_call', async (event, ctx) => {
174
+ if (!(process.env.LSD_MEMORY_EXTRACT === '1' || process.env.LSD_MEMORY_DREAM === '1'))
175
+ return;
176
+ if (isMaintenanceModeToolAllowed(event.toolName, event.input, ctx.cwd)) {
177
+ return;
178
+ }
179
+ if (isToolCallEventType('write', event) || isToolCallEventType('edit', event)) {
180
+ return {
181
+ block: true,
182
+ reason: `Memory maintenance workers may only write inside the memory directory. Blocked path: ${event.input.path}`,
183
+ };
184
+ }
185
+ if (isToolCallEventType('bash', event)) {
186
+ return {
187
+ block: true,
188
+ reason: 'Memory maintenance workers may only run read-only bash commands.',
189
+ };
190
+ }
191
+ return {
192
+ block: true,
193
+ reason: `Tool ${event.toolName} is blocked for memory maintenance workers.`,
194
+ };
195
+ });
156
196
  // ── session_shutdown: trigger auto-extract ────────────────────────
157
197
  pi.on('session_shutdown', async (_event, ctx) => {
158
198
  if (!memoryCwd)
159
199
  return;
160
- // Don't extract if this IS the extraction agent
161
- if (process.env.LSD_MEMORY_EXTRACT === '1')
200
+ // Don't extract if this IS the extraction or dream worker
201
+ if (process.env.LSD_MEMORY_EXTRACT === '1' || process.env.LSD_MEMORY_DREAM === '1')
162
202
  return;
163
203
  try {
164
204
  extractMemories(ctx, memoryCwd);
@@ -220,4 +260,71 @@ export default function memoryExtension(pi) {
220
260
  pi.sendUserMessage(`Please find and remove any memories about: ${topic}`);
221
261
  },
222
262
  });
263
+ /**
264
+ * /dream — run a consolidation pass now, or show dream status.
265
+ */
266
+ pi.registerCommand('dream', {
267
+ description: 'Run memory consolidation now or show dream status',
268
+ handler: async (args, ctx) => {
269
+ if (!memoryCwd)
270
+ return;
271
+ const subcommand = args.trim().toLowerCase();
272
+ if (subcommand === 'status') {
273
+ pi.sendMessage({
274
+ customType: 'memory:dream-status',
275
+ content: formatDreamStatus(ctx),
276
+ display: true,
277
+ });
278
+ return;
279
+ }
280
+ const result = startDream(ctx, { trigger: 'manual' });
281
+ pi.sendMessage({
282
+ customType: 'memory:dream',
283
+ content: result.message,
284
+ display: true,
285
+ });
286
+ },
287
+ });
288
+ /**
289
+ * /auto-dream — enable, disable, or inspect project auto-dream.
290
+ */
291
+ pi.registerCommand('auto-dream', {
292
+ description: 'Enable, disable, or show project auto-dream status',
293
+ handler: async (args, ctx) => {
294
+ if (!memoryCwd)
295
+ return;
296
+ const subcommand = args.trim().toLowerCase();
297
+ if (subcommand === 'on') {
298
+ const settings = setProjectAutoDreamEnabled(memoryCwd, true);
299
+ pi.sendMessage({
300
+ customType: 'memory:auto-dream-settings',
301
+ content: `Auto-dream enabled for this project. Thresholds: ${settings.minHours}h / ${settings.minSessions} sessions.`,
302
+ display: true,
303
+ });
304
+ return;
305
+ }
306
+ if (subcommand === 'off') {
307
+ const settings = setProjectAutoDreamEnabled(memoryCwd, false);
308
+ pi.sendMessage({
309
+ customType: 'memory:auto-dream-settings',
310
+ content: `Auto-dream disabled for this project. Thresholds remain ${settings.minHours}h / ${settings.minSessions} sessions.`,
311
+ display: true,
312
+ });
313
+ return;
314
+ }
315
+ const settings = readAutoDreamSettings(memoryCwd);
316
+ pi.sendMessage({
317
+ customType: 'memory:auto-dream-status',
318
+ content: [
319
+ 'Auto-Dream Settings',
320
+ '',
321
+ `- Enabled: ${settings.enabled ? 'yes' : 'no'}`,
322
+ `- Thresholds: ${settings.minHours}h / ${settings.minSessions} sessions`,
323
+ '',
324
+ formatDreamStatus(ctx),
325
+ ].join('\n'),
326
+ display: true,
327
+ });
328
+ },
329
+ });
223
330
  }
@@ -1,12 +1,12 @@
1
1
  {
2
- "id": "slash-commands",
3
- "name": "Slash Commands",
4
- "version": "1.0.0",
5
- "description": "Boilerplate generators for slash commands, extensions, and audit tools",
6
- "tier": "bundled",
7
- "requires": { "platform": ">=2.29.0" },
8
- "provides": {
9
- "commands": ["create-slash-command", "create-extension", "audit", "clear", "plan", "execute", "cancel-plan"],
10
- "flags": ["plan"]
11
- }
2
+ "id": "slash-commands",
3
+ "name": "Slash Commands",
4
+ "version": "1.0.0",
5
+ "description": "Bundled slash commands for audit, clearing state, and plan workflows",
6
+ "tier": "bundled",
7
+ "requires": { "platform": ">=2.29.0" },
8
+ "provides": {
9
+ "commands": ["audit", "clear", "plan", "execute", "cancel-plan"],
10
+ "flags": ["plan"]
11
+ }
12
12
  }
@@ -1,11 +1,7 @@
1
- import createSlashCommand from "./create-slash-command.js";
2
- import createExtension from "./create-extension.js";
3
1
  import auditCommand from "./audit.js";
4
2
  import clearCommand from "./clear.js";
5
3
  import planCommand from "./plan.js";
6
4
  export default function slashCommands(pi) {
7
- createSlashCommand(pi);
8
- createExtension(pi);
9
5
  auditCommand(pi);
10
6
  clearCommand(pi);
11
7
  planCommand(pi);
@@ -1,6 +1,6 @@
1
- import { mkdirSync } from "node:fs";
1
+ import { existsSync, mkdirSync, readFileSync } from "node:fs";
2
+ import { getAgentDir, getPermissionMode, isToolCallEventType, setPermissionMode } from "@gsd/pi-coding-agent";
2
3
  import { join } from "node:path";
3
- import { getPermissionMode, isToolCallEventType, setPermissionMode } from "@gsd/pi-coding-agent";
4
4
  const PLAN_ENTRY_TYPE = "plan-mode-state";
5
5
  const PLAN_APPROVAL_QUESTION_ID = "plan_mode_approval";
6
6
  const PLAN_DIR_RE = /(^|[/\\])\.(?:lsd|gsd)[/\\]plan([/\\]|$)/;
@@ -56,15 +56,64 @@ const BLOCKED_TOOLS = new Set([
56
56
  "write",
57
57
  "edit",
58
58
  ]);
59
- let state = {
59
+ const DEFAULT_APPROVAL_PERMISSION_MODE = "auto";
60
+ const APPROVE_AUTO_LABEL = "Approve & switch to Auto mode";
61
+ const APPROVE_BYPASS_LABEL = "Approve & switch to Bypass mode";
62
+ const REVISE_LABEL = "Revise plan";
63
+ const CANCEL_LABEL = "Cancel";
64
+ const INITIAL_STATE = {
60
65
  active: false,
61
66
  task: "",
67
+ latestPlanPath: undefined,
62
68
  approvalStatus: "cancelled",
69
+ previousMode: undefined,
70
+ preplanModel: undefined,
71
+ targetPermissionMode: undefined,
63
72
  };
73
+ let state = { ...INITIAL_STATE };
64
74
  let startedFromFlag = false;
65
75
  function isPlanModeActive() {
66
76
  return getPermissionMode() === "plan";
67
77
  }
78
+ function parseQualifiedModelRef(value) {
79
+ if (typeof value !== "string")
80
+ return undefined;
81
+ const trimmed = value.trim();
82
+ if (!trimmed)
83
+ return undefined;
84
+ const parts = trimmed.split("/");
85
+ if (parts.length !== 2)
86
+ return undefined;
87
+ const [provider, id] = parts.map((part) => part.trim());
88
+ if (!provider || !id)
89
+ return undefined;
90
+ return { provider, id };
91
+ }
92
+ export function readPlanModeReasoningModel() {
93
+ try {
94
+ const settingsPath = join(getAgentDir(), "settings.json");
95
+ if (!existsSync(settingsPath))
96
+ return undefined;
97
+ const raw = readFileSync(settingsPath, "utf-8");
98
+ const parsed = JSON.parse(raw);
99
+ const model = parseQualifiedModelRef(parsed.planModeReasoningModel);
100
+ return model ? `${model.provider}/${model.id}` : undefined;
101
+ }
102
+ catch {
103
+ return undefined;
104
+ }
105
+ }
106
+ function sameModel(left, right) {
107
+ return !!left && !!right && left.provider === right.provider && left.id === right.id;
108
+ }
109
+ function resolveModelFromContext(ctx, modelRef) {
110
+ const allModels = typeof ctx?.modelRegistry?.getAll === "function" ? ctx.modelRegistry.getAll() : [];
111
+ return allModels.find((model) => model.provider === modelRef.provider && model.id === modelRef.id);
112
+ }
113
+ function setPermissionModeAndEnv(mode) {
114
+ setPermissionMode(mode);
115
+ process.env.LUCENT_CODE_PERMISSION_MODE = mode;
116
+ }
68
117
  function saveState(pi) {
69
118
  pi.appendEntry(PLAN_ENTRY_TYPE, { ...state });
70
119
  }
@@ -72,15 +121,11 @@ function setState(pi, next) {
72
121
  state = next;
73
122
  saveState(pi);
74
123
  }
75
- function clearState(pi) {
76
- state = {
77
- active: false,
78
- task: "",
79
- latestPlanPath: undefined,
80
- approvalStatus: "cancelled",
81
- previousMode: undefined,
82
- };
83
- saveState(pi);
124
+ function resetState(pi, overrides = {}) {
125
+ setState(pi, {
126
+ ...INITIAL_STATE,
127
+ ...overrides,
128
+ });
84
129
  }
85
130
  function ensurePlanDir() {
86
131
  const dir = join(process.cwd(), ".lsd", "plan");
@@ -102,32 +147,63 @@ function restoreStateFromSession(ctx) {
102
147
  // Best-effort restore only.
103
148
  }
104
149
  }
105
- function enablePlanMode(pi, next = {}) {
150
+ function enablePlanMode(pi, currentModel, next = {}) {
106
151
  const currentMode = getPermissionMode();
107
- const previousMode = currentMode === "plan"
108
- ? (state.previousMode ?? "accept-on-edit")
109
- : currentMode;
110
- setPermissionMode("plan");
111
- process.env.LUCENT_CODE_PERMISSION_MODE = "plan";
152
+ const enteringPlanMode = currentMode !== "plan";
153
+ const previousMode = enteringPlanMode
154
+ ? currentMode
155
+ : (state.previousMode ?? "accept-on-edit");
156
+ setPermissionModeAndEnv("plan");
112
157
  setState(pi, {
113
158
  active: true,
114
159
  task: next.task ?? state.task,
115
160
  latestPlanPath: next.latestPlanPath ?? state.latestPlanPath,
116
161
  approvalStatus: next.approvalStatus ?? state.approvalStatus ?? "pending",
117
162
  previousMode: next.previousMode ?? previousMode,
163
+ preplanModel: next.preplanModel ?? (enteringPlanMode ? (currentModel ?? state.preplanModel) : state.preplanModel),
164
+ targetPermissionMode: next.targetPermissionMode ?? state.targetPermissionMode,
118
165
  });
119
166
  }
120
- function disablePlanMode(pi, approvalStatus, clearTask = false) {
121
- const restoreMode = state.previousMode ?? "accept-on-edit";
122
- setPermissionMode(restoreMode);
123
- process.env.LUCENT_CODE_PERMISSION_MODE = restoreMode;
167
+ function leavePlanMode(pi, approvalStatus, nextPermissionMode, clearTask = false) {
168
+ setPermissionModeAndEnv(nextPermissionMode);
124
169
  setState(pi, {
125
170
  active: false,
126
171
  task: clearTask ? "" : state.task,
127
172
  latestPlanPath: state.latestPlanPath,
128
173
  approvalStatus,
129
- previousMode: restoreMode,
174
+ previousMode: state.previousMode,
175
+ preplanModel: state.preplanModel,
176
+ targetPermissionMode: state.targetPermissionMode ?? nextPermissionMode,
130
177
  });
178
+ return nextPermissionMode;
179
+ }
180
+ async function setModelIfNeeded(pi, ctx, modelRef) {
181
+ if (!modelRef)
182
+ return;
183
+ const currentModel = parseQualifiedModelRef(ctx?.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined);
184
+ if (sameModel(currentModel, modelRef))
185
+ return;
186
+ const model = resolveModelFromContext(ctx, modelRef);
187
+ if (!model)
188
+ return;
189
+ await pi.setModel(model, { persist: false });
190
+ }
191
+ async function approvePlan(pi, ctx, permissionMode) {
192
+ const reasoningModel = parseQualifiedModelRef(readPlanModeReasoningModel());
193
+ if (reasoningModel) {
194
+ await setModelIfNeeded(pi, ctx, reasoningModel);
195
+ }
196
+ state = {
197
+ ...state,
198
+ targetPermissionMode: permissionMode,
199
+ };
200
+ leavePlanMode(pi, "approved", permissionMode);
201
+ }
202
+ async function cancelPlan(pi, ctx, clearTask = true) {
203
+ const restoreMode = state.previousMode ?? "accept-on-edit";
204
+ await setModelIfNeeded(pi, ctx, state.preplanModel);
205
+ leavePlanMode(pi, "cancelled", restoreMode, clearTask);
206
+ resetState(pi, { approvalStatus: "cancelled" });
131
207
  return restoreMode;
132
208
  }
133
209
  function buildPlanModeSystemPrompt() {
@@ -143,6 +219,49 @@ function buildPlanModeSystemPrompt() {
143
219
  details.push(`Latest plan artifact: ${state.latestPlanPath}`);
144
220
  return details.join(" ");
145
221
  }
222
+ function buildApprovalSteeringMessage(planPath) {
223
+ return [
224
+ `Plan artifact saved at ${planPath}.`,
225
+ "Present approval options now using ask_user_questions with exactly one single-select question.",
226
+ `Use question id \"${PLAN_APPROVAL_QUESTION_ID}\" and ask the user what to do next.`,
227
+ "Important: ask_user_questions single-select supports only 2-3 explicit options.",
228
+ "Use exactly these 3 options:",
229
+ `1. ${APPROVE_AUTO_LABEL} (Recommended)`,
230
+ `2. ${APPROVE_BYPASS_LABEL}`,
231
+ `3. ${REVISE_LABEL}`,
232
+ `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 free-text note.`,
233
+ "If the dialog is dismissed or the user gives no answer, continue planning.",
234
+ ].join(" ");
235
+ }
236
+ function approvalSelectionToPermissionMode(selected) {
237
+ if (selected.includes(APPROVE_AUTO_LABEL))
238
+ return "auto";
239
+ if (selected.includes(APPROVE_BYPASS_LABEL))
240
+ return "danger-full-access";
241
+ return undefined;
242
+ }
243
+ function selectionRequestsCancel(selected) {
244
+ const values = Array.isArray(selected) ? selected : [selected];
245
+ return values.some((value) => {
246
+ if (typeof value !== "string")
247
+ return false;
248
+ if (value.includes(CANCEL_LABEL))
249
+ return true;
250
+ const normalized = value.replace(/^user_note:\s*/i, "").trim().toLowerCase();
251
+ return normalized === "cancel" || normalized.includes("cancel plan");
252
+ });
253
+ }
254
+ export const __testing = {
255
+ getState() {
256
+ return { ...state };
257
+ },
258
+ resetState() {
259
+ state = { ...INITIAL_STATE };
260
+ startedFromFlag = false;
261
+ },
262
+ parseQualifiedModelRef,
263
+ approvalSelectionToPermissionMode,
264
+ };
146
265
  export default function planCommand(pi) {
147
266
  pi.registerFlag("plan", {
148
267
  description: "Start the session in plan mode and require a persisted .lsd/plan markdown plan before execution",
@@ -152,8 +271,7 @@ export default function planCommand(pi) {
152
271
  restoreStateFromSession(ctx);
153
272
  startedFromFlag = false;
154
273
  if (state.active) {
155
- setPermissionMode("plan");
156
- process.env.LUCENT_CODE_PERMISSION_MODE = "plan";
274
+ setPermissionModeAndEnv("plan");
157
275
  }
158
276
  });
159
277
  pi.on("before_agent_start", async () => {
@@ -163,16 +281,18 @@ export default function planCommand(pi) {
163
281
  systemPrompt: buildPlanModeSystemPrompt(),
164
282
  };
165
283
  });
166
- pi.on("input", async (event) => {
284
+ pi.on("input", async (event, ctx) => {
167
285
  const planFlag = pi.getFlag("plan");
168
286
  if (startedFromFlag || planFlag !== true || event.source !== "interactive") {
169
287
  return { action: "continue" };
170
288
  }
171
289
  startedFromFlag = true;
172
290
  ensurePlanDir();
173
- enablePlanMode(pi, {
291
+ enablePlanMode(pi, ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined, {
174
292
  task: event.text.trim(),
175
293
  approvalStatus: "pending",
294
+ latestPlanPath: undefined,
295
+ targetPermissionMode: undefined,
176
296
  });
177
297
  return { action: "continue" };
178
298
  });
@@ -204,16 +324,22 @@ export default function planCommand(pi) {
204
324
  };
205
325
  }
206
326
  });
207
- pi.on("tool_result", async (event) => {
327
+ pi.on("tool_result", async (event, ctx) => {
208
328
  if (event.toolName === "write" || event.toolName === "edit") {
209
329
  const input = event.input;
210
330
  const path = input?.path;
211
- if (path && PLAN_DIR_RE.test(path)) {
331
+ if (path && PLAN_DIR_RE.test(path) && isPlanModeActive()) {
212
332
  setState(pi, {
213
333
  ...state,
214
334
  latestPlanPath: path,
215
- approvalStatus: state.approvalStatus === "revising" ? "pending" : state.approvalStatus,
335
+ approvalStatus: "pending",
336
+ targetPermissionMode: undefined,
216
337
  });
338
+ if (!ctx.hasUI) {
339
+ await approvePlan(pi, ctx, DEFAULT_APPROVAL_PERMISSION_MODE);
340
+ return;
341
+ }
342
+ pi.sendUserMessage(buildApprovalSteeringMessage(path), { deliverAs: "steer" });
217
343
  }
218
344
  return;
219
345
  }
@@ -225,35 +351,43 @@ export default function planCommand(pi) {
225
351
  const answer = details.response.answers[PLAN_APPROVAL_QUESTION_ID];
226
352
  if (!answer)
227
353
  return;
228
- const selected = Array.isArray(answer.selected) ? answer.selected[0] : answer.selected;
229
- if (typeof selected !== "string")
354
+ const selected = Array.isArray(answer.selected) ? answer.selected : [answer.selected];
355
+ if (!selected.every((value) => typeof value === "string"))
230
356
  return;
231
- if (selected.includes("Approve plan")) {
232
- disablePlanMode(pi, "approved");
357
+ const targetPermissionMode = approvalSelectionToPermissionMode(selected[0]);
358
+ if (targetPermissionMode) {
359
+ state = {
360
+ ...state,
361
+ targetPermissionMode,
362
+ };
363
+ await approvePlan(pi, ctx, targetPermissionMode);
233
364
  return;
234
365
  }
235
- if (selected.includes("Revise plan")) {
236
- enablePlanMode(pi, { approvalStatus: "revising" });
366
+ if (selected.some((value) => value.includes(REVISE_LABEL))) {
367
+ enablePlanMode(pi, ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined, {
368
+ approvalStatus: "revising",
369
+ });
237
370
  return;
238
371
  }
239
- if (selected.includes("Cancel")) {
240
- disablePlanMode(pi, "cancelled", true);
241
- clearState(pi);
372
+ if (selectionRequestsCancel(selected)) {
373
+ await cancelPlan(pi, ctx, true);
242
374
  }
243
375
  });
244
376
  pi.registerCommand("plan", {
245
377
  description: "Toggle plan mode. While active, only investigative tools and writes under .lsd/plan/ are allowed",
246
378
  async handler(args, ctx) {
247
379
  if (isPlanModeActive()) {
248
- disablePlanMode(pi, "cancelled");
380
+ await cancelPlan(pi, ctx, true);
249
381
  ctx.ui.notify("Plan mode disabled.", "info");
250
382
  return;
251
383
  }
252
384
  ensurePlanDir();
253
385
  const task = args.trim();
254
- enablePlanMode(pi, {
386
+ enablePlanMode(pi, ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined, {
255
387
  task,
388
+ latestPlanPath: undefined,
256
389
  approvalStatus: "pending",
390
+ targetPermissionMode: undefined,
257
391
  });
258
392
  ctx.ui.notify(task
259
393
  ? `Plan mode enabled. Current task: ${task}`
@@ -267,17 +401,19 @@ export default function planCommand(pi) {
267
401
  ctx.ui.notify("Plan mode is not active.", "info");
268
402
  return;
269
403
  }
270
- const restoreMode = disablePlanMode(pi, "approved");
271
- ctx.ui.notify(`Plan mode disabled. Permission mode restored to ${restoreMode}.`, "info");
404
+ await approvePlan(pi, ctx, DEFAULT_APPROVAL_PERMISSION_MODE);
405
+ ctx.ui.notify(`Plan approved. Permission mode switched to ${DEFAULT_APPROVAL_PERMISSION_MODE}.`, "info");
272
406
  },
273
407
  });
274
408
  pi.registerCommand("cancel-plan", {
275
409
  description: "Cancel the current plan-mode session without executing",
276
410
  async handler(_args, ctx) {
277
411
  if (isPlanModeActive()) {
278
- disablePlanMode(pi, "cancelled", true);
412
+ await cancelPlan(pi, ctx, true);
413
+ }
414
+ else {
415
+ resetState(pi, { approvalStatus: "cancelled" });
279
416
  }
280
- clearState(pi);
281
417
  ctx.ui.notify("Plan mode cancelled.", "info");
282
418
  },
283
419
  });
@@ -5,6 +5,19 @@ import * as fs from "node:fs";
5
5
  import * as path from "node:path";
6
6
  import { getAgentDir, parseFrontmatter } from "@gsd/pi-coding-agent";
7
7
  const PROJECT_AGENT_DIR_CANDIDATES = [".lsd", ".gsd", ".pi"];
8
+ function normalizeAgentModel(model) {
9
+ const trimmed = model?.trim();
10
+ if (!trimmed)
11
+ return undefined;
12
+ if (trimmed === "$budget_model")
13
+ return trimmed;
14
+ if (trimmed.includes(" "))
15
+ return undefined;
16
+ if (!trimmed.includes("/"))
17
+ return trimmed;
18
+ const parts = trimmed.split("/");
19
+ return parts.length === 2 && parts.every(Boolean) ? trimmed : undefined;
20
+ }
8
21
  function loadAgentsFromDir(dir, source) {
9
22
  const agents = [];
10
23
  if (!fs.existsSync(dir)) {
@@ -42,7 +55,7 @@ function loadAgentsFromDir(dir, source) {
42
55
  name: frontmatter.name,
43
56
  description: frontmatter.description,
44
57
  tools: tools && tools.length > 0 ? tools : undefined,
45
- model: frontmatter.model,
58
+ model: normalizeAgentModel(frontmatter.model),
46
59
  systemPrompt: body,
47
60
  source,
48
61
  filePath,
@@ -1,9 +1,10 @@
1
+ import { normalizeSubagentModel } from "./model-resolution.js";
1
2
  export function resolveConfiguredSubagentModel(agent, preferences, settingsBudgetModel) {
2
3
  const configuredModel = agent.model?.trim();
3
4
  if (!configuredModel)
4
5
  return undefined;
5
6
  if (configuredModel === "$budget_model") {
6
- return settingsBudgetModel?.trim() || preferences?.subagent?.budget_model?.trim() || undefined;
7
+ return normalizeSubagentModel(settingsBudgetModel) ?? normalizeSubagentModel(preferences?.subagent?.budget_model);
7
8
  }
8
- return configuredModel;
9
+ return normalizeSubagentModel(configuredModel);
9
10
  }