mono-pilot 0.2.9 → 0.2.12

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 (158) hide show
  1. package/README.md +270 -7
  2. package/dist/src/agents-paths.js +36 -0
  3. package/dist/src/brief/blocks.js +83 -0
  4. package/dist/src/brief/defaults.js +60 -0
  5. package/dist/src/brief/frontmatter.js +53 -0
  6. package/dist/src/brief/paths.js +10 -0
  7. package/dist/src/brief/reflection.js +27 -0
  8. package/dist/src/cli.js +62 -5
  9. package/dist/src/cluster/bus.js +102 -0
  10. package/dist/src/cluster/follower.js +137 -0
  11. package/dist/src/cluster/init.js +182 -0
  12. package/dist/src/cluster/leader.js +97 -0
  13. package/dist/src/cluster/log.js +49 -0
  14. package/dist/src/cluster/protocol.js +34 -0
  15. package/dist/src/cluster/services/bus.js +243 -0
  16. package/dist/src/cluster/services/embedding.js +12 -0
  17. package/dist/src/cluster/socket.js +86 -0
  18. package/dist/src/cluster/test-bus.js +175 -0
  19. package/dist/src/cluster_v2/connection-lifecycle.js +31 -0
  20. package/dist/src/cluster_v2/connection-lifecycle.test.js +24 -0
  21. package/dist/src/cluster_v2/connection.js +159 -0
  22. package/dist/src/cluster_v2/connection.test.js +55 -0
  23. package/dist/src/cluster_v2/events.js +102 -0
  24. package/dist/src/cluster_v2/index.js +2 -0
  25. package/dist/src/cluster_v2/observability.js +99 -0
  26. package/dist/src/cluster_v2/observability.test.js +46 -0
  27. package/dist/src/cluster_v2/rpc.js +389 -0
  28. package/dist/src/cluster_v2/rpc.test.js +110 -0
  29. package/dist/src/cluster_v2/runtime.failover.integration.test.js +156 -0
  30. package/dist/src/cluster_v2/runtime.js +531 -0
  31. package/dist/src/cluster_v2/runtime.lease-compromise.integration.test.js +91 -0
  32. package/dist/src/cluster_v2/runtime.lifecycle.integration.test.js +225 -0
  33. package/dist/src/cluster_v2/services/bus.integration.test.js +140 -0
  34. package/dist/src/cluster_v2/services/bus.js +450 -0
  35. package/dist/src/cluster_v2/services/discord/auth-store.js +82 -0
  36. package/dist/src/cluster_v2/services/discord/collector.js +569 -0
  37. package/dist/src/cluster_v2/services/discord/index.js +1 -0
  38. package/dist/src/cluster_v2/services/discord/oauth.js +87 -0
  39. package/dist/src/cluster_v2/services/discord/rpc-client.js +325 -0
  40. package/dist/src/cluster_v2/services/embedding.js +66 -0
  41. package/dist/src/cluster_v2/services/registry-cache.js +107 -0
  42. package/dist/src/cluster_v2/services/registry-cache.test.js +66 -0
  43. package/dist/src/cluster_v2/services/registry.js +36 -0
  44. package/dist/src/cluster_v2/services/twitter/collector.js +1055 -0
  45. package/dist/src/cluster_v2/services/twitter/index.js +1 -0
  46. package/dist/src/config/digest.js +78 -0
  47. package/dist/src/config/discord.js +143 -0
  48. package/dist/src/config/image-gen.js +48 -0
  49. package/dist/src/config/mono-pilot.js +31 -0
  50. package/dist/src/config/twitter.js +100 -0
  51. package/dist/src/extensions/cluster.js +311 -0
  52. package/dist/src/extensions/commands/build-memory.js +76 -0
  53. package/dist/src/extensions/commands/digest/backfill.js +779 -0
  54. package/dist/src/extensions/commands/digest/index.js +1133 -0
  55. package/dist/src/extensions/commands/image-model.js +214 -0
  56. package/dist/src/extensions/game/bus-injection.js +47 -0
  57. package/dist/src/extensions/game/identity.js +83 -0
  58. package/dist/src/extensions/game/mailbox.js +61 -0
  59. package/dist/src/extensions/game/system-prompt.js +134 -0
  60. package/dist/src/extensions/game/tools.js +28 -0
  61. package/dist/src/extensions/lifecycle.js +337 -0
  62. package/dist/src/extensions/mode-runtime.js +26 -2
  63. package/dist/src/extensions/mono-game.js +66 -0
  64. package/dist/src/extensions/mono-pilot.js +100 -18
  65. package/dist/src/extensions/nvim.js +47 -0
  66. package/dist/src/extensions/session-hints.js +60 -35
  67. package/dist/src/extensions/sftp.js +897 -0
  68. package/dist/src/extensions/status.js +676 -0
  69. package/dist/src/extensions/system-events.js +478 -0
  70. package/dist/src/extensions/system-prompt.js +24 -14
  71. package/dist/src/extensions/user-message.js +94 -50
  72. package/dist/src/lsp/client.js +235 -0
  73. package/dist/src/lsp/index.js +165 -0
  74. package/dist/src/lsp/runtime.js +67 -0
  75. package/dist/src/lsp/server.js +242 -0
  76. package/dist/src/mcp/config.js +112 -0
  77. package/dist/src/{utils/mcp-client.js → mcp/protocol.js} +1 -100
  78. package/dist/src/mcp/servers.js +90 -0
  79. package/dist/src/memory/build-memory.js +103 -0
  80. package/dist/src/memory/config/defaults.js +55 -0
  81. package/dist/src/memory/config/loader.js +29 -0
  82. package/dist/src/memory/config/paths.js +9 -0
  83. package/dist/src/memory/config/resolve.js +90 -0
  84. package/dist/src/memory/config/types.js +1 -0
  85. package/dist/src/memory/embeddings/batch-runner.js +39 -0
  86. package/dist/src/memory/embeddings/cache.js +47 -0
  87. package/dist/src/memory/embeddings/chunk-limits.js +26 -0
  88. package/dist/src/memory/embeddings/input-limits.js +48 -0
  89. package/dist/src/memory/embeddings/local.js +108 -0
  90. package/dist/src/memory/embeddings/types.js +1 -0
  91. package/dist/src/memory/index-manager.js +552 -0
  92. package/dist/src/memory/indexing/embeddings.js +67 -0
  93. package/dist/src/memory/indexing/files.js +180 -0
  94. package/dist/src/memory/indexing/index-file.js +105 -0
  95. package/dist/src/memory/log.js +38 -0
  96. package/dist/src/memory/paths.js +15 -0
  97. package/dist/src/memory/runtime/index.js +299 -0
  98. package/dist/src/memory/runtime/thread.js +116 -0
  99. package/dist/src/memory/search/fts.js +57 -0
  100. package/dist/src/memory/search/hybrid.js +50 -0
  101. package/dist/src/memory/search/text.js +30 -0
  102. package/dist/src/memory/search/vector.js +43 -0
  103. package/dist/src/memory/session/content-hash.js +7 -0
  104. package/dist/src/memory/session/entry.js +33 -0
  105. package/dist/src/memory/session/flush-policy.js +34 -0
  106. package/dist/src/memory/session/hook.js +191 -0
  107. package/dist/src/memory/session/paths.js +15 -0
  108. package/dist/src/memory/session/session-reader.js +88 -0
  109. package/dist/src/memory/session/transcript/content-hash.js +7 -0
  110. package/dist/src/memory/session/transcript/entry.js +28 -0
  111. package/dist/src/memory/session/transcript/flush.js +56 -0
  112. package/dist/src/memory/session/transcript/paths.js +28 -0
  113. package/dist/src/memory/session/transcript/reader.js +112 -0
  114. package/dist/src/memory/session/transcript/state.js +31 -0
  115. package/dist/src/memory/store/schema.js +89 -0
  116. package/dist/src/memory/store/sqlite.js +89 -0
  117. package/dist/src/memory/types.js +1 -0
  118. package/dist/src/memory/warm.js +25 -0
  119. package/dist/src/rules/discovery.js +41 -0
  120. package/dist/{tools → src/tools}/README.md +29 -3
  121. package/dist/{tools → src/tools}/apply-patch-description.md +8 -2
  122. package/dist/{tools → src/tools}/apply-patch.js +174 -104
  123. package/dist/{tools → src/tools}/apply-patch.test.js +52 -1
  124. package/dist/{tools/ask-question.js → src/tools/ask-user-question.js} +3 -3
  125. package/dist/src/tools/ast-grep.js +357 -0
  126. package/dist/src/tools/brief-write.js +122 -0
  127. package/dist/src/tools/bus-send.js +100 -0
  128. package/dist/{tools → src/tools}/call-mcp-tool.js +40 -124
  129. package/dist/src/tools/codex-apply-patch-description.md +52 -0
  130. package/dist/src/tools/codex-apply-patch.js +540 -0
  131. package/dist/{tools → src/tools}/delete.js +24 -0
  132. package/dist/src/tools/exit-plan-mode.js +83 -0
  133. package/dist/{tools → src/tools}/fetch-mcp-resource.js +56 -100
  134. package/dist/src/tools/generate-image.js +567 -0
  135. package/dist/{tools → src/tools}/glob.js +55 -1
  136. package/dist/{tools → src/tools}/list-mcp-resources.js +46 -57
  137. package/dist/{tools → src/tools}/list-mcp-tools.js +52 -63
  138. package/dist/src/tools/ls.js +48 -0
  139. package/dist/src/tools/lsp-diagnostics.js +67 -0
  140. package/dist/src/tools/lsp-symbols.js +54 -0
  141. package/dist/src/tools/mailbox.js +85 -0
  142. package/dist/src/tools/memory-get.js +90 -0
  143. package/dist/src/tools/memory-search.js +180 -0
  144. package/dist/{tools → src/tools}/plan-mode-reminder.md +3 -4
  145. package/dist/{tools → src/tools}/read-file.js +8 -19
  146. package/dist/{tools → src/tools}/rg.js +10 -20
  147. package/dist/{tools → src/tools}/shell.js +19 -42
  148. package/dist/{tools → src/tools}/subagent.js +255 -6
  149. package/dist/{tools → src/tools}/switch-mode.js +37 -6
  150. package/dist/{tools → src/tools}/web-fetch.js +105 -7
  151. package/dist/{tools → src/tools}/web-search.js +29 -1
  152. package/package.json +21 -9
  153. /package/dist/{tools → src/tools}/ask-mode-reminder.md +0 -0
  154. /package/dist/{tools → src/tools}/rg.test.js +0 -0
  155. /package/dist/{tools → src/tools}/semantic-search-description.md +0 -0
  156. /package/dist/{tools → src/tools}/semantic-search.js +0 -0
  157. /package/dist/{tools → src/tools}/shell-description.md +0 -0
  158. /package/dist/{tools → src/tools}/subagent-description.md +0 -0
@@ -0,0 +1,478 @@
1
+ import { visibleWidth } from "@mariozechner/pi-tui";
2
+ const MAX_EVENTS = 200;
3
+ const COMMAND_DEFAULT_LIMIT = 20;
4
+ const DEDUPE_WINDOW_MS = 10_000;
5
+ const OVERLAY_FALLBACK_TIMEOUT_MS = 120_000;
6
+ const ERROR_OVERLAY_MIN_WIDTH = 44;
7
+ const ERROR_OVERLAY_MAX_WIDTH = 84;
8
+ const ERROR_OVERLAY_MAX_MESSAGE_LINES = 3;
9
+ const OVERLAY_MARGIN_TOP = 1;
10
+ const OVERLAY_MARGIN_RIGHT = 0;
11
+ const OVERLAY_MARGIN_BOTTOM = 0;
12
+ const OVERLAY_MARGIN_LEFT = 2;
13
+ const OVERLAY_OFFSET_X = 2;
14
+ const OVERLAY_OFFSET_Y = -4;
15
+ const OVERLAY_INPUT_TOP_GAP_ROWS = 0;
16
+ const OVERLAY_CLOSE_MARK = "[×]";
17
+ const OVERLAY_CLOSE_GLYPH = "×";
18
+ let lastErrorOverlayKey = "";
19
+ let lastErrorOverlayAtMs = 0;
20
+ const ERROR_OVERLAY_DEDUPE_WINDOW_MS = 1_800;
21
+ let activeOverlay = null;
22
+ let nextId = 1;
23
+ let queue = [];
24
+ let latestContext = null;
25
+ function notify(ctx, message, level) {
26
+ if (ctx?.hasUI && ctx.ui?.notify) {
27
+ ctx.ui.notify(message, level);
28
+ return;
29
+ }
30
+ const prefix = level === "error" ? "[error]" : level === "warning" ? "[warn]" : "[info]";
31
+ console.log(`${prefix} ${message}`);
32
+ }
33
+ function trimMessage(message) {
34
+ const normalized = message.trim();
35
+ return normalized.length > 0 ? normalized : "(empty message)";
36
+ }
37
+ function formatClock(timestampMs) {
38
+ return new Date(timestampMs).toISOString().slice(11, 19);
39
+ }
40
+ function levelTag(level) {
41
+ if (level === "error")
42
+ return "E";
43
+ if (level === "warning")
44
+ return "W";
45
+ return "I";
46
+ }
47
+ function summarizeCounts() {
48
+ let error = 0;
49
+ let warning = 0;
50
+ let info = 0;
51
+ for (const item of queue) {
52
+ if (item.level === "error") {
53
+ error += 1;
54
+ continue;
55
+ }
56
+ if (item.level === "warning") {
57
+ warning += 1;
58
+ continue;
59
+ }
60
+ info += 1;
61
+ }
62
+ return { error, warning, info };
63
+ }
64
+ function resetState() {
65
+ nextId = 1;
66
+ queue = [];
67
+ }
68
+ function shouldMergeWithLast(dedupeKey, now) {
69
+ const last = queue[queue.length - 1];
70
+ if (!last) {
71
+ return undefined;
72
+ }
73
+ if (last.dedupeKey !== dedupeKey) {
74
+ return undefined;
75
+ }
76
+ if (now - last.lastSeenAtMs > DEDUPE_WINDOW_MS) {
77
+ return undefined;
78
+ }
79
+ return last;
80
+ }
81
+ function enqueueEvent(record) {
82
+ queue.push(record);
83
+ if (queue.length > MAX_EVENTS) {
84
+ queue = queue.slice(queue.length - MAX_EVENTS);
85
+ }
86
+ }
87
+ function shouldToast(level, explicit) {
88
+ if (typeof explicit === "boolean") {
89
+ return explicit;
90
+ }
91
+ return level !== "info";
92
+ }
93
+ function wrapFixedWidth(text, width) {
94
+ const normalizedWidth = Math.max(8, width);
95
+ const lines = [];
96
+ for (const segment of text.split(/\r?\n/)) {
97
+ if (segment.length === 0) {
98
+ lines.push("");
99
+ continue;
100
+ }
101
+ let current = "";
102
+ let currentWidth = 0;
103
+ for (const char of segment) {
104
+ const charWidth = Math.max(1, visibleWidth(char));
105
+ if (currentWidth + charWidth > normalizedWidth && current.length > 0) {
106
+ lines.push(current);
107
+ current = "";
108
+ currentWidth = 0;
109
+ }
110
+ current += char;
111
+ currentWidth += charWidth;
112
+ }
113
+ if (current.length > 0) {
114
+ lines.push(current);
115
+ }
116
+ }
117
+ return lines;
118
+ }
119
+ function truncateToDisplayWidth(value, width) {
120
+ const normalizedWidth = Math.max(0, width);
121
+ if (normalizedWidth <= 0) {
122
+ return "";
123
+ }
124
+ let output = "";
125
+ let usedWidth = 0;
126
+ for (const char of value) {
127
+ const charWidth = Math.max(1, visibleWidth(char));
128
+ if (usedWidth + charWidth > normalizedWidth) {
129
+ break;
130
+ }
131
+ output += char;
132
+ usedWidth += charWidth;
133
+ }
134
+ return output;
135
+ }
136
+ function truncateWithEllipsis(value, width) {
137
+ const normalizedWidth = Math.max(0, width);
138
+ if (visibleWidth(value) <= normalizedWidth) {
139
+ return value;
140
+ }
141
+ const ellipsis = "...";
142
+ const ellipsisWidth = visibleWidth(ellipsis);
143
+ if (normalizedWidth <= ellipsisWidth) {
144
+ return truncateToDisplayWidth(ellipsis, normalizedWidth);
145
+ }
146
+ const contentWidth = normalizedWidth - ellipsisWidth;
147
+ return `${truncateToDisplayWidth(value, contentWidth)}${ellipsis}`;
148
+ }
149
+ function padRight(value, width) {
150
+ const normalizedWidth = Math.max(0, width);
151
+ const fitted = truncateToDisplayWidth(value, normalizedWidth);
152
+ const fittedWidth = visibleWidth(fitted);
153
+ if (fittedWidth >= normalizedWidth) {
154
+ return fitted;
155
+ }
156
+ return fitted + " ".repeat(normalizedWidth - fittedWidth);
157
+ }
158
+ function shouldShowOverlay(key, now) {
159
+ if (lastErrorOverlayKey !== key) {
160
+ return true;
161
+ }
162
+ return now - lastErrorOverlayAtMs > ERROR_OVERLAY_DEDUPE_WINDOW_MS;
163
+ }
164
+ function closeActiveOverlay() {
165
+ activeOverlay?.close();
166
+ }
167
+ function clamp(value, min, max) {
168
+ return Math.max(min, Math.min(max, value));
169
+ }
170
+ function asFiniteNumber(value) {
171
+ if (typeof value !== "number" || !Number.isFinite(value)) {
172
+ return undefined;
173
+ }
174
+ return value;
175
+ }
176
+ function resolveEditorTopRow(tuiLike) {
177
+ if (!tuiLike) {
178
+ return undefined;
179
+ }
180
+ const layouts = tuiLike.childLayouts;
181
+ if (!Array.isArray(layouts) || layouts.length < 3) {
182
+ return undefined;
183
+ }
184
+ const editorLayout = layouts[layouts.length - 3];
185
+ const startRow = asFiniteNumber(editorLayout?.startRow);
186
+ if (startRow === undefined) {
187
+ return undefined;
188
+ }
189
+ const viewportTop = asFiniteNumber(tuiLike.getViewportTop?.()) ?? 0;
190
+ return Math.floor(startRow - viewportTop);
191
+ }
192
+ function resolveResponsiveOffsetY(tuiLike) {
193
+ const termHeight = asFiniteNumber(tuiLike?.terminal?.rows);
194
+ const editorTopRow = resolveEditorTopRow(tuiLike);
195
+ if (termHeight === undefined || editorTopRow === undefined) {
196
+ return OVERLAY_OFFSET_Y;
197
+ }
198
+ const targetBottomExclusive = editorTopRow - OVERLAY_INPUT_TOP_GAP_ROWS;
199
+ return targetBottomExclusive - (termHeight - OVERLAY_MARGIN_BOTTOM);
200
+ }
201
+ function isEscapeKey(data) {
202
+ return data === "\u001b";
203
+ }
204
+ function showOverlay(ctx, level, source, message, dedupeKey) {
205
+ if (!ctx.hasUI || !ctx.ui?.custom) {
206
+ return;
207
+ }
208
+ closeActiveOverlay();
209
+ const now = Date.now();
210
+ if (!shouldShowOverlay(dedupeKey, now)) {
211
+ return;
212
+ }
213
+ lastErrorOverlayKey = dedupeKey;
214
+ lastErrorOverlayAtMs = now;
215
+ const overlayId = Symbol("system-overlay");
216
+ let closeRequested = null;
217
+ let resolvedOffsetY = OVERLAY_OFFSET_Y;
218
+ const overlayOptionsState = {
219
+ anchor: "bottom-right",
220
+ offsetX: OVERLAY_OFFSET_X,
221
+ offsetY: resolvedOffsetY,
222
+ margin: {
223
+ top: OVERLAY_MARGIN_TOP,
224
+ right: OVERLAY_MARGIN_RIGHT,
225
+ bottom: OVERLAY_MARGIN_BOTTOM,
226
+ left: OVERLAY_MARGIN_LEFT,
227
+ },
228
+ nonCapturing: true,
229
+ maxHeight: 8,
230
+ width: "40%",
231
+ };
232
+ activeOverlay = {
233
+ id: overlayId,
234
+ close: () => {
235
+ closeRequested?.();
236
+ },
237
+ };
238
+ void ctx.ui
239
+ .custom((tui, _theme, _keybindings, done) => {
240
+ const tuiLike = tui;
241
+ resolvedOffsetY = resolveResponsiveOffsetY(tuiLike);
242
+ overlayOptionsState.offsetY = resolvedOffsetY;
243
+ let closed = false;
244
+ const closeOverlay = () => {
245
+ if (closed) {
246
+ return;
247
+ }
248
+ closed = true;
249
+ done();
250
+ };
251
+ closeRequested = closeOverlay;
252
+ const timer = setTimeout(() => {
253
+ closeOverlay();
254
+ }, OVERLAY_FALLBACK_TIMEOUT_MS);
255
+ const onEsc = ctx.ui?.onTerminalInput?.((data) => {
256
+ if (isEscapeKey(data)) {
257
+ closeOverlay();
258
+ return { consume: true };
259
+ }
260
+ return undefined;
261
+ });
262
+ const layoutState = {
263
+ closeX: -1,
264
+ closeY: -1,
265
+ };
266
+ const removeMouse = tuiLike.addMouseListener?.((event) => {
267
+ if (closed) {
268
+ return false;
269
+ }
270
+ if (event.action !== "press" || event.button !== "left") {
271
+ return false;
272
+ }
273
+ if (event.y !== layoutState.closeY) {
274
+ return false;
275
+ }
276
+ if (event.x !== layoutState.closeX) {
277
+ return false;
278
+ }
279
+ closeOverlay();
280
+ return true;
281
+ });
282
+ return {
283
+ render(width) {
284
+ // Keep overlay anchored to the current editor top as it grows/shrinks.
285
+ resolvedOffsetY = resolveResponsiveOffsetY(tuiLike);
286
+ overlayOptionsState.offsetY = resolvedOffsetY;
287
+ const panelWidth = Math.max(ERROR_OVERLAY_MIN_WIDTH, Math.min(width - 2, ERROR_OVERLAY_MAX_WIDTH));
288
+ const rowWidth = Math.max(24, panelWidth - 2);
289
+ // Reserve one extra trailing cell before the right border so
290
+ // full-width CJK glyphs never land on the border boundary.
291
+ const rowInnerWidth = Math.max(16, rowWidth - 3);
292
+ const messageChunks = wrapFixedWidth(message, rowInnerWidth);
293
+ const visibleChunks = messageChunks.slice(0, ERROR_OVERLAY_MAX_MESSAGE_LINES);
294
+ if (messageChunks.length > visibleChunks.length && visibleChunks.length > 0) {
295
+ const last = visibleChunks[visibleChunks.length - 1] ?? "";
296
+ visibleChunks[visibleChunks.length - 1] = truncateWithEllipsis(last, rowInnerWidth);
297
+ }
298
+ const baseTitle = `${level === "error"
299
+ ? "SYSTEM ERROR"
300
+ : level === "warning"
301
+ ? "SYSTEM WARNING"
302
+ : "SYSTEM INFO"} [${source}]`;
303
+ const titleLabelWidth = Math.max(1, rowInnerWidth - visibleWidth(OVERLAY_CLOSE_MARK) - 1);
304
+ const title = `${padRight(baseTitle, titleLabelWidth)} ${OVERLAY_CLOSE_MARK}`;
305
+ const rows = [
306
+ `┌${"─".repeat(rowWidth)}┐`,
307
+ `│ ${padRight(title, rowInnerWidth)} │`,
308
+ `├${"─".repeat(rowWidth)}┤`,
309
+ ];
310
+ if (visibleChunks.length === 0) {
311
+ rows.push(`│ ${padRight("(no message)", rowInnerWidth)} │`);
312
+ }
313
+ else {
314
+ for (const chunk of visibleChunks) {
315
+ rows.push(`│ ${padRight(chunk, rowInnerWidth)} │`);
316
+ }
317
+ }
318
+ rows.push(`└${"─".repeat(rowWidth)}┘`);
319
+ const termWidth = Math.max(panelWidth, Math.floor(Number(tuiLike.terminal?.columns ?? panelWidth)) || panelWidth);
320
+ const termHeight = Math.max(8, Math.floor(Number(tuiLike.terminal?.rows ?? 24)) || 24);
321
+ const overlayWidth = Math.max(1, Math.floor(width));
322
+ const marginTop = OVERLAY_MARGIN_TOP;
323
+ const marginBottom = OVERLAY_MARGIN_BOTTOM;
324
+ const marginLeft = OVERLAY_MARGIN_LEFT;
325
+ const marginRight = OVERLAY_MARGIN_RIGHT;
326
+ const availWidth = Math.max(1, termWidth - marginLeft - marginRight);
327
+ const availHeight = Math.max(1, termHeight - marginTop - marginBottom);
328
+ const fullHeight = rows.length;
329
+ const fullBaseRow = marginTop + Math.max(0, availHeight - fullHeight);
330
+ const desiredFullRow = fullBaseRow + resolvedOffsetY;
331
+ const visibleStart = Math.max(0, marginTop - desiredFullRow);
332
+ const visibleEnd = Math.min(fullHeight, termHeight - marginBottom - desiredFullRow);
333
+ const clippedRows = visibleEnd > visibleStart ? rows.slice(visibleStart, visibleEnd) : [];
334
+ const clippedFromBottom = Math.max(0, fullHeight - visibleEnd);
335
+ if (clippedRows.length === 0) {
336
+ layoutState.closeX = -1;
337
+ layoutState.closeY = -1;
338
+ return clippedRows;
339
+ }
340
+ // When bottom rows are clipped, anchor math would otherwise shift the
341
+ // remaining rows down and effectively clip one extra line.
342
+ const effectiveOffsetY = resolvedOffsetY - clippedFromBottom;
343
+ overlayOptionsState.offsetY = effectiveOffsetY;
344
+ let row = marginTop + Math.max(0, availHeight - clippedRows.length);
345
+ let col = marginLeft + Math.max(0, availWidth - overlayWidth);
346
+ row += effectiveOffsetY;
347
+ col += OVERLAY_OFFSET_X;
348
+ row = clamp(row, marginTop, termHeight - marginBottom - clippedRows.length);
349
+ col = clamp(col, marginLeft, termWidth - marginRight - overlayWidth);
350
+ const titleFullIndex = 1;
351
+ if (titleFullIndex >= visibleStart && titleFullIndex < visibleEnd) {
352
+ const titleVisibleIndex = titleFullIndex - visibleStart;
353
+ const titleRowText = clippedRows[titleVisibleIndex] ?? "";
354
+ const closeOffset = titleRowText.indexOf(OVERLAY_CLOSE_MARK);
355
+ const glyphOffset = OVERLAY_CLOSE_MARK.indexOf(OVERLAY_CLOSE_GLYPH);
356
+ if (closeOffset >= 0 && glyphOffset >= 0) {
357
+ const beforeGlyph = titleRowText.slice(0, closeOffset + glyphOffset);
358
+ layoutState.closeX = col + 1 + visibleWidth(beforeGlyph);
359
+ layoutState.closeY = row + titleVisibleIndex + 1;
360
+ }
361
+ else {
362
+ layoutState.closeX = -1;
363
+ layoutState.closeY = -1;
364
+ }
365
+ }
366
+ else {
367
+ layoutState.closeX = -1;
368
+ layoutState.closeY = -1;
369
+ }
370
+ return clippedRows;
371
+ },
372
+ invalidate() {
373
+ // Stateless component.
374
+ },
375
+ dispose() {
376
+ clearTimeout(timer);
377
+ onEsc?.();
378
+ removeMouse?.();
379
+ if (activeOverlay?.id === overlayId) {
380
+ activeOverlay = null;
381
+ }
382
+ closeRequested = null;
383
+ },
384
+ };
385
+ }, {
386
+ overlay: true,
387
+ overlayOptions: overlayOptionsState,
388
+ })
389
+ .finally(() => {
390
+ if (activeOverlay?.id === overlayId) {
391
+ activeOverlay = null;
392
+ }
393
+ })
394
+ .catch(() => {
395
+ // Overlay UI is best-effort only.
396
+ });
397
+ }
398
+ export function publishSystemEvent(input) {
399
+ const source = input.source.trim() || "system";
400
+ const message = trimMessage(input.message);
401
+ const now = Date.now();
402
+ const dedupeKey = input.dedupeKey ?? `${source}|${input.level}|${message}`;
403
+ const context = input.ctx ?? latestContext;
404
+ const merged = shouldMergeWithLast(dedupeKey, now);
405
+ if (merged) {
406
+ merged.count += 1;
407
+ merged.lastSeenAtMs = now;
408
+ return;
409
+ }
410
+ const record = {
411
+ id: nextId++,
412
+ source,
413
+ level: input.level,
414
+ message,
415
+ dedupeKey,
416
+ firstSeenAtMs: now,
417
+ lastSeenAtMs: now,
418
+ count: 1,
419
+ };
420
+ enqueueEvent(record);
421
+ if (context) {
422
+ showOverlay(context, record.level, record.source, record.message, record.dedupeKey);
423
+ }
424
+ if (shouldToast(input.level, input.toast)) {
425
+ notify(context, `[${record.source}] ${record.message}`, record.level);
426
+ }
427
+ }
428
+ function parseLimit(raw) {
429
+ if (!raw) {
430
+ return COMMAND_DEFAULT_LIMIT;
431
+ }
432
+ const parsed = Number(raw);
433
+ if (!Number.isFinite(parsed) || parsed <= 0) {
434
+ return COMMAND_DEFAULT_LIMIT;
435
+ }
436
+ return Math.max(1, Math.min(100, Math.floor(parsed)));
437
+ }
438
+ function renderQueue(limit) {
439
+ if (queue.length === 0) {
440
+ return "No system events.";
441
+ }
442
+ const counts = summarizeCounts();
443
+ const lines = queue.slice(-limit).map((item) => {
444
+ const repeated = item.count > 1 ? ` x${item.count}` : "";
445
+ return `#${item.id} ${formatClock(item.lastSeenAtMs)} [${levelTag(item.level)}] [${item.source}] ${item.message}${repeated}`;
446
+ });
447
+ return [`Events total=${queue.length} e=${counts.error} w=${counts.warning} i=${counts.info}`, ...lines].join("\n");
448
+ }
449
+ export function registerSystemEvents(pi) {
450
+ pi.on("session_start", async (_event, ctx) => {
451
+ closeActiveOverlay();
452
+ latestContext = ctx;
453
+ resetState();
454
+ });
455
+ pi.on("session_shutdown", async () => {
456
+ closeActiveOverlay();
457
+ latestContext = null;
458
+ resetState();
459
+ });
460
+ pi.registerCommand("events", {
461
+ description: "System event queue: /events [N|clear]",
462
+ handler: async (args, ctx) => {
463
+ latestContext = ctx;
464
+ const trimmed = args.trim();
465
+ if (!trimmed) {
466
+ notify(ctx, renderQueue(COMMAND_DEFAULT_LIMIT), "info");
467
+ return;
468
+ }
469
+ if (trimmed === "clear") {
470
+ queue = [];
471
+ notify(ctx, "System event queue cleared.", "info");
472
+ return;
473
+ }
474
+ const limit = parseLimit(trimmed);
475
+ notify(ctx, renderQueue(limit), "info");
476
+ },
477
+ });
478
+ }
@@ -3,18 +3,19 @@ import { dirname, join, resolve } from "node:path";
3
3
  import process from "node:process";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { formatSkillsForPrompt, loadSkills } from "@mariozechner/pi-coding-agent";
6
+ import { buildBriefBlock } from "../brief/blocks.js";
6
7
  const NO_DESCRIPTION_PLACEHOLDER = "No short description provided.";
7
8
  const PROJECT_CONTEXT_HEADER = "# Project Context";
8
9
  const CURRENT_DATETIME_PREFIX = "Current date and time:";
9
10
  const CURRENT_WORKING_DIRECTORY_PREFIX = "Current working directory:";
10
11
  const AVAILABLE_TOOLS_TOKEN = "{{AVAILABLE_TOOLS_BULLETS}}";
11
12
  const PROJECT_CONTEXT_TOKEN = "{{PROJECT_CONTEXT_BLOCK}}";
12
- const CURRENT_DATETIME_TOKEN = "{{CURRENT_DATETIME}}";
13
13
  const CURRENT_WORKING_DIRECTORY_TOKEN = "{{CURRENT_WORKING_DIRECTORY}}";
14
14
  const PI_README_PATH_TOKEN = "{{PI_README_PATH}}";
15
15
  const PI_DOCS_PATH_TOKEN = "{{PI_DOCS_PATH}}";
16
16
  const PI_EXAMPLES_PATH_TOKEN = "{{PI_EXAMPLES_PATH}}";
17
17
  const SKILLS_BLOCK_TOKEN = "{{SKILLS_BLOCK}}";
18
+ const BRIEF_BLOCK_TOKEN = "{{BRIEF_BLOCK}}";
18
19
  const UNIFIED_SYSTEM_PROMPT_TEMPLATE = `You are an expert coding assistant operating inside MonoPilot (a pi-coding-agent compatibility harness) on a user's computer.
19
20
  You help users by reading files, executing commands, editing code, and writing new files.
20
21
 
@@ -92,9 +93,13 @@ After substantive edits, use the ReadLints tool to check recently edited files f
92
93
  </pi_docs_policy>
93
94
 
94
95
  <project_context>
96
+ Current working directory: ${CURRENT_WORKING_DIRECTORY_TOKEN}
97
+
95
98
  ${PROJECT_CONTEXT_TOKEN}
96
99
  </project_context>
97
100
 
101
+ ${BRIEF_BLOCK_TOKEN}
102
+
98
103
  <working_with_the_user>
99
104
  ## Working with the user
100
105
 
@@ -161,14 +166,22 @@ You are producing plain text that will later be styled by Cursor. Follow these r
161
166
  </intermediary_updates>
162
167
  </working_with_the_user>
163
168
 
169
+ <soul>
170
+ You are a coding agent, but your core identity is the user's in-repo alter ego.
171
+ Treat the user, the assistant, and the executing agent as one collaborative system with shared ownership.
172
+ Your job is not only to solve immediate tasks together, but also to grow the system together.
173
+ Improving meta-level capabilities (for example workflow clarity, tool quality, and feedback loops) is also valid progress when it serves the current task.
174
+
175
+ - user: defines goals, priorities, and acceptance criteria.
176
+ - assistant: the reasoning/planning layer that translates goals into decisions and communication.
177
+ - executing-agent: the action layer that executes tools, applies changes, and returns runtime evidence.
178
+
179
+ These are distinct roles in function, but unified in intent: solve tasks together and improve the system together.
180
+ </soul>
181
+
164
182
  <main_goal>
165
183
  Your main goal is to follow the USER's instructions at each message, denoted by the <user_query> tag.
166
184
  </main_goal>
167
-
168
- <runtime_context>
169
- Current date and time: ${CURRENT_DATETIME_TOKEN}
170
- Current working directory: ${CURRENT_WORKING_DIRECTORY_TOKEN}
171
- </runtime_context>
172
185
  ${SKILLS_BLOCK_TOKEN}`;
173
186
  function getFirstDescriptionLine(description) {
174
187
  if (!description)
@@ -222,8 +235,6 @@ function renderTemplate(template, values) {
222
235
  .join(values.tools)
223
236
  .split(PROJECT_CONTEXT_TOKEN)
224
237
  .join(values.projectContext)
225
- .split(CURRENT_DATETIME_TOKEN)
226
- .join(values.currentDateTime)
227
238
  .split(CURRENT_WORKING_DIRECTORY_TOKEN)
228
239
  .join(values.cwd)
229
240
  .split(PI_README_PATH_TOKEN)
@@ -233,10 +244,9 @@ function renderTemplate(template, values) {
233
244
  .split(PI_EXAMPLES_PATH_TOKEN)
234
245
  .join(values.piExamplesPath)
235
246
  .split(SKILLS_BLOCK_TOKEN)
236
- .join(values.skillsBlock);
237
- }
238
- function getFallbackDateTimeText() {
239
- return new Date().toString();
247
+ .join(values.skillsBlock)
248
+ .split(BRIEF_BLOCK_TOKEN)
249
+ .join(values.briefBlock);
240
250
  }
241
251
  function resolvePiPackageRoot() {
242
252
  try {
@@ -287,21 +297,21 @@ export default function systemPromptExtension(pi) {
287
297
  const tools = buildActiveToolBullets(pi.getActiveTools(), pi.getAllTools());
288
298
  const piDocsPaths = resolvePiDocsPaths();
289
299
  const projectContext = extractProjectContextBlock(event.systemPrompt) ?? "Project-specific instructions were not provided.";
290
- const currentDateTime = extractValueAfterPrefix(event.systemPrompt, CURRENT_DATETIME_PREFIX) ?? getFallbackDateTimeText();
291
300
  const cwd = extractValueAfterPrefix(event.systemPrompt, CURRENT_WORKING_DIRECTORY_PREFIX) ?? process.cwd();
292
301
  // Load skills directly since pi-mono skips injection when "read" tool
293
302
  // is not in the base tool set (mono-pilot uses "ReadFile" instead).
294
303
  const { skills } = loadSkills({ cwd });
295
304
  const skillsBlock = formatSkillsForPrompt(skills);
305
+ const briefBlock = buildBriefBlock(cwd);
296
306
  const unifiedPrompt = renderTemplate(UNIFIED_SYSTEM_PROMPT_TEMPLATE, {
297
307
  tools,
298
308
  projectContext,
299
- currentDateTime,
300
309
  cwd,
301
310
  piReadmePath: piDocsPaths.readmePath,
302
311
  piDocsPath: piDocsPaths.docsPath,
303
312
  piExamplesPath: piDocsPaths.examplesPath,
304
313
  skillsBlock,
314
+ briefBlock,
305
315
  });
306
316
  if (unifiedPrompt === event.systemPrompt) {
307
317
  return undefined;