pi-orchestrator-config 4.1.5

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 (175) hide show
  1. package/README.md +808 -0
  2. package/agents/api-documenter.md +32 -0
  3. package/agents/bash-expert.md +53 -0
  4. package/agents/code-reviewer-docs.md +145 -0
  5. package/agents/code-reviewer-guidelines.md +97 -0
  6. package/agents/code-reviewer-quality.md +142 -0
  7. package/agents/code-reviewer-security.md +107 -0
  8. package/agents/code-reviewer-spec.md +164 -0
  9. package/agents/debugger.md +63 -0
  10. package/agents/docker-expert.md +40 -0
  11. package/agents/docs-fetcher.md +48 -0
  12. package/agents/git-expert.md +63 -0
  13. package/agents/github-expert.md +83 -0
  14. package/agents/go-expert.md +62 -0
  15. package/agents/issue-reviewer-feasibility.md +60 -0
  16. package/agents/issue-reviewer-scope.md +63 -0
  17. package/agents/issue-reviewer-spec.md +77 -0
  18. package/agents/java-expert.md +31 -0
  19. package/agents/jenkins-expert.md +38 -0
  20. package/agents/kubernetes-expert.md +40 -0
  21. package/agents/planner.md +42 -0
  22. package/agents/python-expert.md +61 -0
  23. package/agents/reviewer.md +26 -0
  24. package/agents/scout.md +40 -0
  25. package/agents/security-auditor.md +254 -0
  26. package/agents/technical-documentation-writer.md +37 -0
  27. package/agents/test-automator.md +55 -0
  28. package/agents/test-runner.md +47 -0
  29. package/agents/ts-expert.md +23 -0
  30. package/agents/worker.md +30 -0
  31. package/extensions/acpx-provider/index.ts +104 -0
  32. package/extensions/acpx-provider/load-runtime.ts +99 -0
  33. package/extensions/acpx-provider/runtime-models.ts +40 -0
  34. package/extensions/cli-provider/agents/claude.ts +128 -0
  35. package/extensions/cli-provider/agents/cursor.ts +84 -0
  36. package/extensions/cli-provider/agents/gemini.ts +100 -0
  37. package/extensions/cli-provider/discover.ts +77 -0
  38. package/extensions/cli-provider/index.ts +57 -0
  39. package/extensions/cli-provider/parsers.ts +304 -0
  40. package/extensions/cli-provider/providers.ts +58 -0
  41. package/extensions/cli-provider/runner.ts +225 -0
  42. package/extensions/cli-provider/runtime-models.ts +32 -0
  43. package/extensions/cli-provider/session-reaper.ts +122 -0
  44. package/extensions/cli-provider/sessions.ts +533 -0
  45. package/extensions/cli-provider/shared/discover-cache.ts +235 -0
  46. package/extensions/cli-provider/types.ts +48 -0
  47. package/extensions/coms/coms-net-server.ts +1642 -0
  48. package/extensions/coms/coms-net-wrapper.ts +521 -0
  49. package/extensions/coms/coms-net.ts +1589 -0
  50. package/extensions/coms/coms-p2p.ts +1879 -0
  51. package/extensions/coms/coms-shared.ts +510 -0
  52. package/extensions/coms/coms-wrapper.ts +138 -0
  53. package/extensions/coms/index.ts +20 -0
  54. package/extensions/coms/themeMap.ts +147 -0
  55. package/extensions/image-gen/image-gen.ts +332 -0
  56. package/extensions/image-gen/index.ts +14 -0
  57. package/extensions/orchestrator/agents.ts +134 -0
  58. package/extensions/orchestrator/ask-user.ts +293 -0
  59. package/extensions/orchestrator/async-agents.ts +1037 -0
  60. package/extensions/orchestrator/async-capability.ts +129 -0
  61. package/extensions/orchestrator/async-runner.ts +324 -0
  62. package/extensions/orchestrator/async-status-parse.ts +31 -0
  63. package/extensions/orchestrator/async-status-ui.ts +201 -0
  64. package/extensions/orchestrator/async-wait.ts +36 -0
  65. package/extensions/orchestrator/btw.ts +210 -0
  66. package/extensions/orchestrator/cron-status-format.ts +126 -0
  67. package/extensions/orchestrator/cron-status-ui.ts +132 -0
  68. package/extensions/orchestrator/cron.ts +730 -0
  69. package/extensions/orchestrator/dreaming.ts +355 -0
  70. package/extensions/orchestrator/enforcement-helpers.ts +530 -0
  71. package/extensions/orchestrator/enforcement-rules.ts +329 -0
  72. package/extensions/orchestrator/enforcement.ts +828 -0
  73. package/extensions/orchestrator/extended-autocomplete.ts +593 -0
  74. package/extensions/orchestrator/git-helpers.ts +420 -0
  75. package/extensions/orchestrator/github-autocomplete.ts +250 -0
  76. package/extensions/orchestrator/icons.ts +17 -0
  77. package/extensions/orchestrator/index.ts +100 -0
  78. package/extensions/orchestrator/memory-embeddings.ts +281 -0
  79. package/extensions/orchestrator/memory-promotion.ts +449 -0
  80. package/extensions/orchestrator/memory-provenance.ts +80 -0
  81. package/extensions/orchestrator/memory-query-class.ts +90 -0
  82. package/extensions/orchestrator/memory-scoring.ts +434 -0
  83. package/extensions/orchestrator/memory-tools.ts +883 -0
  84. package/extensions/orchestrator/memory-tree.ts +212 -0
  85. package/extensions/orchestrator/nvim.ts +169 -0
  86. package/extensions/orchestrator/overlay-dashboard-utils.ts +25 -0
  87. package/extensions/orchestrator/overlay-dashboard.ts +512 -0
  88. package/extensions/orchestrator/pi-config-review-state.ts +303 -0
  89. package/extensions/orchestrator/preference-extractor.ts +106 -0
  90. package/extensions/orchestrator/project-settings.ts +468 -0
  91. package/extensions/orchestrator/promotion-queue.ts +311 -0
  92. package/extensions/orchestrator/resolve-agent-model.ts +32 -0
  93. package/extensions/orchestrator/review-ui.ts +204 -0
  94. package/extensions/orchestrator/rule-placeholders.ts +12 -0
  95. package/extensions/orchestrator/rules.ts +481 -0
  96. package/extensions/orchestrator/session-search.ts +243 -0
  97. package/extensions/orchestrator/session-validation.ts +374 -0
  98. package/extensions/orchestrator/situation-report.ts +370 -0
  99. package/extensions/orchestrator/status-bar.ts +31 -0
  100. package/extensions/orchestrator/status-line.ts +192 -0
  101. package/extensions/orchestrator/status.ts +110 -0
  102. package/extensions/orchestrator/subagent-tool.ts +1748 -0
  103. package/extensions/orchestrator/task-lifecycle.ts +88 -0
  104. package/extensions/orchestrator/utils.ts +227 -0
  105. package/extensions/pidash/index.ts +15 -0
  106. package/extensions/pidash/pidash.ts +941 -0
  107. package/extensions/pidiff/index.ts +13 -0
  108. package/extensions/pidiff/pidiff.ts +369 -0
  109. package/extensions/providers/acpx-driver.ts +479 -0
  110. package/extensions/providers/built-in-drivers.ts +52 -0
  111. package/extensions/providers/claude-driver.ts +319 -0
  112. package/extensions/providers/cursor-acpx-driver.ts +398 -0
  113. package/extensions/providers/cursor-cli-driver.ts +484 -0
  114. package/extensions/providers/gemini-driver.ts +302 -0
  115. package/extensions/providers/index.ts +513 -0
  116. package/extensions/shared/build-system-prompt.ts +57 -0
  117. package/extensions/shared/create-runtime-provider.ts +190 -0
  118. package/extensions/shared/daemon-manager.ts +248 -0
  119. package/extensions/shared/file-logger.ts +115 -0
  120. package/extensions/shared/managed-refresh.ts +211 -0
  121. package/extensions/shared/provider-driver.ts +295 -0
  122. package/extensions/shared/provider-errors.ts +305 -0
  123. package/extensions/shared/provider-registry.ts +388 -0
  124. package/extensions/shared/stream-builder.ts +275 -0
  125. package/extensions/shared/types.ts +20 -0
  126. package/extensions/shared/ui/README.md +10 -0
  127. package/extensions/shared/ui/badge.tsx +52 -0
  128. package/extensions/shared/ui/button.tsx +58 -0
  129. package/extensions/shared/ui/collapsible.tsx +21 -0
  130. package/extensions/shared/ui/dropdown-menu.tsx +266 -0
  131. package/extensions/shared/ui/input.tsx +20 -0
  132. package/extensions/shared/ui/popover.tsx +114 -0
  133. package/extensions/shared/ui/resizable.tsx +48 -0
  134. package/extensions/shared/ui/scroll-area.tsx +53 -0
  135. package/extensions/shared/ui/select.tsx +201 -0
  136. package/extensions/shared/ui/separator.tsx +25 -0
  137. package/extensions/shared/ui/tooltip.tsx +64 -0
  138. package/extensions/shared/ws-client.ts +74 -0
  139. package/package.json +59 -0
  140. package/prompts/coderabbit-rate-limit.md +95 -0
  141. package/prompts/create-coms-feature-manager.md +186 -0
  142. package/prompts/create-skill.md +128 -0
  143. package/prompts/domain-model.md +109 -0
  144. package/prompts/external-ai.md +665 -0
  145. package/prompts/implement-and-review.md +25 -0
  146. package/prompts/implement.md +26 -0
  147. package/prompts/issue-review.md +256 -0
  148. package/prompts/pr-review.md +753 -0
  149. package/prompts/query-db.md +154 -0
  150. package/prompts/refine-review.md +208 -0
  151. package/prompts/release.md +235 -0
  152. package/prompts/remember.md +33 -0
  153. package/prompts/review-handler-status.md +27 -0
  154. package/prompts/review-handler.md +677 -0
  155. package/prompts/review-local.md +159 -0
  156. package/prompts/scout-and-plan.md +21 -0
  157. package/rules/00-orchestrator-core.md +42 -0
  158. package/rules/05-issue-first-workflow.md +119 -0
  159. package/rules/10-agent-routing.md +58 -0
  160. package/rules/15-mcp-launchpad.md +46 -0
  161. package/rules/20-code-review-loop.md +234 -0
  162. package/rules/25-documentation-updates.md +15 -0
  163. package/rules/30-prompt-templates.md +19 -0
  164. package/rules/35-memory.md +240 -0
  165. package/rules/40-critical-rules.md +124 -0
  166. package/rules/45-file-preview.md +29 -0
  167. package/rules/50-agent-bug-reporting.md +90 -0
  168. package/rules/55-coms-protocol.md +63 -0
  169. package/rules/60-task-tracking.md +66 -0
  170. package/skills/conflict-resolver/SKILL.md +213 -0
  171. package/skills/skill-creator/SKILL.md +85 -0
  172. package/skills/systematic-debugging/SKILL.md +353 -0
  173. package/skills/test-driven-development/SKILL.md +335 -0
  174. package/skills/update-agents-md/SKILL.md +282 -0
  175. package/templates/coms-feature-manager-prompt.md +313 -0
@@ -0,0 +1,355 @@
1
+ /**
2
+ * Memory dreaming — background memory consolidation on a timer.
3
+ *
4
+ * Inspired by OpenClaw's dreaming system (v2026.4.5).
5
+ * See: https://docs.openclaw.ai/concepts/dreaming
6
+ *
7
+ * When enabled, spawns a worker agent every 3 hours (and on session quit)
8
+ * that reads the session file, extracts memories, and writes topic files directly.
9
+ * No CLI calls — the worker reads/writes files using read/write tools.
10
+ * Users toggle with /dream-auto on|off.
11
+ */
12
+
13
+ import { spawn } from "node:child_process";
14
+ import * as fs from "node:fs";
15
+ import * as os from "node:os";
16
+ import * as path from "node:path";
17
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
18
+ import { decideAsyncLlmDispatch } from "./async-capability.js";
19
+ import { getSetting } from "./project-settings.js";
20
+ import { discoverAgents } from "./agents.js";
21
+ import { ICON_DREAM } from "./icons.js";
22
+ import { setSlot } from "./status-bar.js";
23
+ import { rebuildAndOrganize } from "./situation-report.js";
24
+ import { runPromotionPass } from "./memory-promotion.js";
25
+ import { mergeProvenancePending } from "./memory-provenance.js";
26
+ import { getProjectTmpDir } from "./utils.js";
27
+ import { dreamingLog } from "../shared/file-logger.js";
28
+
29
+ // Default: 3 hours. Override with PI_DREAM_INTERVAL_HOURS env var (0.5–24).
30
+ const _rawHours = parseFloat(process.env.PI_DREAM_INTERVAL_HOURS || "3");
31
+ const DREAM_INTERVAL_HOURS = Number.isFinite(_rawHours) && _rawHours >= 0.5 && _rawHours <= 24 ? _rawHours : 3;
32
+ const DREAM_INTERVAL_MS = DREAM_INTERVAL_HOURS * 60 * 60 * 1000;
33
+
34
+ // Scoring rebuild runs every 30 minutes (cheap, no LLM — just rescores and reorganizes)
35
+ const REBUILD_INTERVAL_MS = 30 * 60 * 1000;
36
+
37
+ export function registerDreaming(
38
+ pi: ExtensionAPI,
39
+ spawnAsyncAgent: (
40
+ agentName: string,
41
+ task: string,
42
+ cwd: string,
43
+ agents: any[],
44
+ options?: {
45
+ fireAndForget?: boolean;
46
+ name?: string;
47
+ parentModelId?: string;
48
+ parentProvider?: string;
49
+ onComplete?: () => void;
50
+ },
51
+ ) => { id: string; error?: string },
52
+ ): void {
53
+ // Only the orchestrator (top-level pi) runs dreaming — skip in subagent children
54
+ if (process.env.PI_SUBAGENT_CHILD === "1") return;
55
+
56
+ let dreamTimer: ReturnType<typeof setInterval> | null = null;
57
+ let enabled = true;
58
+ let lastCwd = "";
59
+ let lastCtx: any = null;
60
+
61
+ let dreamInFlight = false;
62
+ let currentDreamId = "";
63
+
64
+ function updateDreamStatus() {
65
+ try {
66
+ if (!lastCtx?.ui) return;
67
+ if (dreamInFlight) {
68
+ setSlot("dream", lastCtx.ui.theme.fg("warning", ICON_DREAM), lastCtx);
69
+ } else {
70
+ setSlot("dream", lastCtx.ui.theme.fg("muted", ICON_DREAM), lastCtx);
71
+ }
72
+ } catch { /* stale UI context — ignore */ }
73
+ }
74
+
75
+ function runDreamAsync(cwd: string, lastSessionFile?: string) {
76
+ if (dreamInFlight) return; // Prevent concurrent dreams
77
+
78
+ const parentProvider = lastCtx?.model?.provider as string | undefined;
79
+ let dreamModelId: string | undefined;
80
+ let dreamProvider: string | undefined;
81
+ const dreamDispatch = decideAsyncLlmDispatch({
82
+ parentProvider,
83
+ cwd,
84
+ mustAsync: true,
85
+ });
86
+ if (dreamDispatch.action === "skip") {
87
+ dreamingLog("warn", dreamDispatch.note || "dream skipped (no async LLM path)");
88
+ try {
89
+ lastCtx?.ui?.notify?.(
90
+ "Dream skipped: set async_llm_provider and async_llm_model for acpx sessions",
91
+ "warning",
92
+ );
93
+ } catch { /* stale UI */ }
94
+ return;
95
+ }
96
+ if (dreamDispatch.action === "sidecar-async") {
97
+ dreamProvider = dreamDispatch.sidecar.provider;
98
+ dreamModelId = dreamDispatch.sidecar.model;
99
+ }
100
+
101
+ dreamInFlight = true;
102
+ updateDreamStatus();
103
+ currentDreamId = ""; // Reset until we get the ID from spawnAsyncAgent
104
+ const { agents } = discoverAgents(cwd, "user");
105
+ const topicsDir = path.join(cwd, ".pi", "memory", "topics");
106
+ const { id } = spawnAsyncAgent(
107
+ "worker",
108
+ `Memory dreaming — analyze session and maintain topic files.\n` +
109
+ `Session file: ${lastSessionFile || "none"}\n` +
110
+ `Topics directory: ${topicsDir}\n\n` +
111
+ `Topic files are the source of truth for project memory. Each file holds entries for one category.\n\n` +
112
+ `Steps:\n` +
113
+ `1. Read all existing topic files in ${topicsDir}/ (lessons.md, preferences.md, patterns.md, decisions.md, completions.md, mistakes.md).\n` +
114
+ ` If the directory doesn't exist, create it.\n` +
115
+ `2. QUALITY GATE: Before extracting from any session, assess its quality:\n` +
116
+ ` - Score depth (substantive exchanges > 100 chars? decisions made? corrections?)\n` +
117
+ ` - Skip sessions that are only greetings, trivial Q&A, or < 3 exchanges\n` +
118
+ ` - Only extract from sessions with real decisions, corrections, or completed work\n` +
119
+ `3. If a session file is provided, read it and extract things worth remembering:\n` +
120
+ ` - User corrections → [lesson] → lessons.md\n` +
121
+ ` - User preferences → [preference] → preferences.md\n` +
122
+ ` - Mistakes or repeated fix attempts → [mistake] → mistakes.md\n` +
123
+ ` - Completed features/PRs merged → [done] → completions.md\n` +
124
+ ` - Patterns or conventions → [pattern] → patterns.md\n` +
125
+ ` - Architectural/design decisions → [decision] → decisions.md\n` +
126
+ ` Do NOT add duplicates of existing entries.\n` +
127
+ `4. Scan past session files for unprocessed knowledge. Check if ${topicsDir}/../.dream-watermark exists.\n` +
128
+ ` If it does, read the timestamp — only process sessions newer than that.\n` +
129
+ ` Session directory: find .jsonl files under the pi sessions directory.\n` +
130
+ ` For each unprocessed session, extract durable knowledge (same categories as step 3).\n` +
131
+ ` Limit: process at most 5 sessions per dream cycle to avoid overload.\n` +
132
+ `5. Reorganize each topic file:\n` +
133
+ ` - Remove duplicate or near-duplicate entries\n` +
134
+ ` - Remove stale/useless entries\n` +
135
+ ` - Keep each file at a reasonable size (aim for under 20 entries per topic)\n` +
136
+ ` - NEVER remove or modify entries marked with *(pinned)*\n` +
137
+ ` - NEVER remove or modify entries marked with *(enforced)* — these have code-enforced triggers/actions that are keyed by text hash. ANY text change destroys the enforcement binding.\n` +
138
+ `6. Write each updated topic file with this format:\n` +
139
+ ` # TopicName\n` +
140
+ ` \n` +
141
+ ` - [category] summary *(pinned)* (if pinned)\n` +
142
+ ` - [category] summary *(enforced)* (if enforced)\n` +
143
+ ` - [category] summary *(pinned)* *(enforced)* (if both)\n` +
144
+ ` - [category] summary (if neither)\n` +
145
+ `7. Auto-generate skills: if you notice a multi-step workflow pattern across entries,\n` +
146
+ ` create a skill file at .pi/skills/<name>/SKILL.md (project-level, NOT global ~/.agents/).\n` +
147
+ ` The SKILL.md MUST start with YAML frontmatter:\n` +
148
+ ` ---\n` +
149
+ ` name: <name>\n` +
150
+ ` description: "What this skill does and when to use it"\n` +
151
+ ` ---\n` +
152
+ ` Only create skills for workflows with 3+ steps that are likely to recur.\n` +
153
+ `8. Write the current timestamp to ${topicsDir}/../.dream-watermark to track progress.\n` +
154
+ `9. Memory rules: one line per entry, max ~100 chars, specific and actionable, no fluff.\n` +
155
+ `10. Promotion destinations: append candidates to ${topicsDir}/../promotions.md using this block format:\n` +
156
+ ` ### <12-char-id>\n` +
157
+ ` - destination: memory|skill|enforcement|project_rule|discard\n` +
158
+ ` - status: proposed\n` +
159
+ ` - category: <category>\n` +
160
+ ` - text: <exact topic entry text>\n` +
161
+ ` - reason: <why this should graduate>\n` +
162
+ ` - created: <ISO timestamp>\n` +
163
+ ` Optional fields: evidence_count, trigger, action, verifier, skill_name, skill_created.\n` +
164
+ ` Rules:\n` +
165
+ ` - skill: create .pi/skills/ when confident; set skill_created: true\n` +
166
+ ` - enforcement: propose trigger/action when mechanical (never/always + command); do NOT invent run_after\n` +
167
+ ` - project_rule: propose only — NEVER write rules/ or .pi/rules/\n` +
168
+ ` - discard: stale or superseded noise\n` +
169
+ ` - Do not reopen entries already marked applied or rejected in promotions.md\n` +
170
+ `11. Provenance sidecar (optional): for newly extracted entries, write\n` +
171
+ ` ${topicsDir}/../provenance-pending.json as JSON:\n` +
172
+ ` {"entries":[{"category":"lesson","text":"<exact topic text>","sourceSession":"<session basename>","derivedFrom":"<optional>","informs":["optional"]}]}\n` +
173
+ ` Do NOT edit memory-scores.json yourself — onComplete merges the sidecar.`,
174
+ cwd,
175
+ agents,
176
+ {
177
+ fireAndForget: true,
178
+ name: "Dream",
179
+ parentModelId: dreamModelId,
180
+ parentProvider: dreamProvider,
181
+ onComplete: () => {
182
+ dreamInFlight = false;
183
+ updateDreamStatus();
184
+ // File log only — console.* leaks into the chat text box.
185
+ try {
186
+ rebuildAndOrganize(cwd);
187
+ } catch (err) {
188
+ dreamingLog("error", "rebuildAndOrganize failed", err);
189
+ }
190
+ try {
191
+ const n = mergeProvenancePending(cwd);
192
+ if (n > 0) dreamingLog("info", `merged provenance for ${n} entries`);
193
+ } catch (err) {
194
+ dreamingLog("error", "provenance merge failed", err);
195
+ }
196
+ try {
197
+ runPromotionPass(cwd);
198
+ } catch (err) {
199
+ dreamingLog("error", "promotion pass failed", err);
200
+ }
201
+ },
202
+ },
203
+ );
204
+ // Dream runs as fireAndForget async agent.
205
+ // onComplete callback triggers rebuildAndOrganize when dream finishes.
206
+ currentDreamId = id;
207
+ if (!id) {
208
+ dreamInFlight = false;
209
+ updateDreamStatus();
210
+ } else {
211
+ // Safety fallback: if onComplete never fires (runner crash/hang),
212
+ // reset dreamInFlight after 30 min so future dreams aren't blocked.
213
+ // Capture the current dream ID to avoid cross-run races.
214
+ const dreamId = id;
215
+ const fallbackTimer = setTimeout(() => {
216
+ if (dreamInFlight && currentDreamId === dreamId) {
217
+ dreamInFlight = false;
218
+ updateDreamStatus();
219
+ dreamingLog(
220
+ "warn",
221
+ "fallback: reset dreamInFlight after 30 min (onComplete never fired)",
222
+ );
223
+ }
224
+ }, 30 * 60 * 1000);
225
+ if (fallbackTimer.unref) fallbackTimer.unref();
226
+ }
227
+ }
228
+
229
+ let rebuildTimer: ReturnType<typeof setInterval> | null = null;
230
+
231
+ let currentIntervalMs = 0;
232
+
233
+ function startTimer(cwd: string) {
234
+ lastCwd = cwd;
235
+
236
+ // Resolve dream interval from project settings
237
+ const projectHours = getSetting(cwd, "dream_interval_hours");
238
+ const clampedHours = Math.max(0.5, Math.min(24, projectHours));
239
+ const effectiveMs = clampedHours * 60 * 60 * 1000;
240
+
241
+ // Restart timer if interval changed (or first start)
242
+ if (dreamTimer && currentIntervalMs === effectiveMs) return;
243
+ if (dreamTimer) clearInterval(dreamTimer);
244
+ currentIntervalMs = effectiveMs;
245
+
246
+ // LLM dreaming
247
+ dreamTimer = setInterval(() => {
248
+ if (enabled && lastCwd) runDreamAsync(lastCwd);
249
+ }, effectiveMs);
250
+ if (dreamTimer.unref) dreamTimer.unref();
251
+
252
+ // Scoring rebuild — every 30 min (cheap, no LLM, just rescores + reorganizes topics)
253
+ if (!rebuildTimer) {
254
+ rebuildTimer = setInterval(() => {
255
+ if (enabled && lastCwd) {
256
+ try {
257
+ rebuildAndOrganize(lastCwd);
258
+ } catch (err) {
259
+ dreamingLog("error", "rebuildAndOrganize failed", err);
260
+ }
261
+ }
262
+ }, REBUILD_INTERVAL_MS);
263
+ if (rebuildTimer.unref) rebuildTimer.unref();
264
+ }
265
+ }
266
+
267
+ function stopTimer() {
268
+ if (dreamTimer) {
269
+ clearInterval(dreamTimer);
270
+ dreamTimer = null;
271
+ }
272
+ if (rebuildTimer) {
273
+ clearInterval(rebuildTimer);
274
+ rebuildTimer = null;
275
+ }
276
+ }
277
+
278
+ // /dream-auto command — toggle auto-dreaming
279
+ pi.registerCommand("dream-auto", {
280
+ description: "Toggle automatic memory dreaming (every 3h + session end)",
281
+ handler: async (args, ctx) => {
282
+ const arg = (args || "").trim().toLowerCase();
283
+
284
+ if (arg === "on") {
285
+ enabled = true;
286
+ lastCwd = ctx.cwd;
287
+ lastCtx = ctx;
288
+ startTimer(ctx.cwd);
289
+ ctx.ui.notify("🌙 Auto-dreaming enabled (every 3h + session end)", "info");
290
+ } else if (arg === "off") {
291
+ enabled = false;
292
+ stopTimer();
293
+ ctx.ui.notify("Auto-dreaming disabled", "info");
294
+ } else {
295
+ const status = enabled ? "enabled" : "disabled";
296
+ ctx.ui.notify(`Auto-dreaming is ${status}. Use: /dream-auto on|off`, "info");
297
+ }
298
+ },
299
+ });
300
+
301
+ // /dream command — manual trigger for memory consolidation
302
+ pi.registerCommand("dream", {
303
+ description: "Run memory consolidation now (background, non-blocking)",
304
+ handler: async (_args, ctx) => {
305
+ if (dreamInFlight) {
306
+ ctx.ui.notify("Dream already running — wait for it to finish", "warning");
307
+ return;
308
+ }
309
+ lastCwd = ctx.cwd;
310
+ lastCtx = ctx;
311
+ runDreamAsync(ctx.cwd);
312
+ },
313
+ });
314
+
315
+ // Set initial dream status on first agent start (fires on /reload too, unlike session_start)
316
+ let initialized = false;
317
+ pi.on("before_agent_start", (_event, ctx) => {
318
+ if (!initialized) {
319
+ initialized = true;
320
+ lastCtx = ctx;
321
+ updateDreamStatus();
322
+ }
323
+ });
324
+
325
+ // Update cwd on session start
326
+ pi.on("session_start", (_event, ctx) => {
327
+ lastCwd = ctx.cwd;
328
+ lastCtx = ctx;
329
+ dreamInFlight = false; // Reset — previous session's dream state doesn't carry over
330
+ // Skip dreaming in one-shot modes (print/json)
331
+ if (ctx.mode === "print" || ctx.mode === "json") return;
332
+ updateDreamStatus();
333
+ if (enabled) startTimer(ctx.cwd);
334
+ });
335
+
336
+ // Fire-and-forget dream on session shutdown.
337
+ // Uses detached spawn (not async agent) because the session is ending —
338
+ // async agents need the pi process alive to deliver results.
339
+ pi.on("session_shutdown", (event) => {
340
+ stopTimer();
341
+ if (!enabled || !lastCwd || dreamInFlight) return;
342
+
343
+ // Only dream on quit — skip for fork/new/resume/reload since the
344
+ // session continues or transitions, not ending meaningfully.
345
+ if ((event as any).reason && (event as any).reason !== "quit") return;
346
+
347
+ // On shutdown, run a lightweight dream via detached async runner
348
+ // (can't use spawnAsyncAgent since the session is ending)
349
+ try {
350
+ runDreamAsync(lastCwd);
351
+ } catch (err) {
352
+ dreamingLog("error", "shutdown dream failed", err);
353
+ }
354
+ });
355
+ }