pi-ui-extend 0.1.8 → 0.1.11

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 (191) hide show
  1. package/README.md +57 -2
  2. package/bin/pix.mjs +4 -4
  3. package/dist/app/app.d.ts +4 -0
  4. package/dist/app/app.js +112 -45
  5. package/dist/app/{cli.d.ts → cli/cli.d.ts} +1 -1
  6. package/dist/app/{cli.js → cli/cli.js} +1 -1
  7. package/dist/app/{install.d.ts → cli/install.d.ts} +2 -0
  8. package/dist/app/{install.js → cli/install.js} +18 -3
  9. package/dist/app/{command-controller.d.ts → commands/command-controller.d.ts} +1 -1
  10. package/dist/app/{command-controller.js → commands/command-controller.js} +4 -0
  11. package/dist/app/{command-host.d.ts → commands/command-host.d.ts} +7 -3
  12. package/dist/app/{command-model-actions.d.ts → commands/command-model-actions.d.ts} +6 -1
  13. package/dist/app/{command-model-actions.js → commands/command-model-actions.js} +106 -2
  14. package/dist/app/{command-navigation-actions.d.ts → commands/command-navigation-actions.d.ts} +7 -2
  15. package/dist/app/{command-navigation-actions.js → commands/command-navigation-actions.js} +42 -19
  16. package/dist/app/{command-registry.d.ts → commands/command-registry.d.ts} +5 -1
  17. package/dist/app/{command-registry.js → commands/command-registry.js} +32 -0
  18. package/dist/app/{command-runtime.js → commands/command-runtime.js} +1 -1
  19. package/dist/app/{command-session-actions.d.ts → commands/command-session-actions.d.ts} +1 -0
  20. package/dist/app/{command-session-actions.js → commands/command-session-actions.js} +18 -8
  21. package/dist/app/{shell-controller.d.ts → commands/shell-controller.d.ts} +2 -1
  22. package/dist/app/{shell-controller.js → commands/shell-controller.js} +2 -2
  23. package/dist/app/{slash-commands.d.ts → commands/slash-commands.d.ts} +2 -2
  24. package/dist/app/{slash-commands.js → commands/slash-commands.js} +1 -1
  25. package/dist/app/constants.d.ts +1 -1
  26. package/dist/app/constants.js +1 -1
  27. package/dist/app/{extension-actions-controller.d.ts → extensions/extension-actions-controller.d.ts} +1 -1
  28. package/dist/app/{extension-actions-controller.js → extensions/extension-actions-controller.js} +1 -1
  29. package/dist/app/{extension-ui-controller.d.ts → extensions/extension-ui-controller.d.ts} +3 -3
  30. package/dist/app/{extension-ui-controller.js → extensions/extension-ui-controller.js} +3 -3
  31. package/dist/app/icons.js +1 -1
  32. package/dist/app/input/autocomplete-controller.d.ts +52 -0
  33. package/dist/app/input/autocomplete-controller.js +352 -0
  34. package/dist/app/{input-action-controller.d.ts → input/input-action-controller.d.ts} +8 -7
  35. package/dist/app/{input-action-controller.js → input/input-action-controller.js} +24 -3
  36. package/dist/app/{input-controller.d.ts → input/input-controller.d.ts} +4 -3
  37. package/dist/app/{input-controller.js → input/input-controller.js} +3 -1
  38. package/dist/app/{input-paste-handler.d.ts → input/input-paste-handler.d.ts} +2 -1
  39. package/dist/app/{input-paste-handler.js → input/input-paste-handler.js} +25 -21
  40. package/dist/app/{native-modifiers.js → input/native-modifiers.js} +2 -2
  41. package/dist/app/{prompt-enhancer-controller.d.ts → input/prompt-enhancer-controller.d.ts} +5 -5
  42. package/dist/app/{prompt-enhancer-controller.js → input/prompt-enhancer-controller.js} +3 -3
  43. package/dist/app/{voice-controller.d.ts → input/voice-controller.d.ts} +3 -1
  44. package/dist/app/{voice-controller.js → input/voice-controller.js} +29 -17
  45. package/dist/app/{model-ref.d.ts → model/model-ref.d.ts} +1 -1
  46. package/dist/app/{model-ref.js → model/model-ref.js} +1 -1
  47. package/dist/app/{model-usage-controller.js → model/model-usage-controller.js} +1 -1
  48. package/dist/app/{model-usage-status.d.ts → model/model-usage-status.d.ts} +10 -1
  49. package/dist/app/{model-usage-status.js → model/model-usage-status.js} +125 -35
  50. package/dist/app/{menu-items-controller.d.ts → popup/menu-items-controller.d.ts} +4 -4
  51. package/dist/app/{menu-items-controller.js → popup/menu-items-controller.js} +5 -5
  52. package/dist/app/{popup-action-controller.d.ts → popup/popup-action-controller.d.ts} +4 -4
  53. package/dist/app/{popup-action-controller.js → popup/popup-action-controller.js} +3 -3
  54. package/dist/app/{popup-menu-controller.d.ts → popup/popup-menu-controller.d.ts} +4 -4
  55. package/dist/app/{popup-menu-controller.js → popup/popup-menu-controller.js} +7 -7
  56. package/dist/app/process.d.ts +17 -0
  57. package/dist/app/process.js +68 -0
  58. package/dist/app/{conversation-entry-renderer.d.ts → rendering/conversation-entry-renderer.d.ts} +3 -3
  59. package/dist/app/{conversation-entry-renderer.js → rendering/conversation-entry-renderer.js} +20 -9
  60. package/dist/app/{conversation-shell-renderer.d.ts → rendering/conversation-shell-renderer.d.ts} +1 -1
  61. package/dist/app/{conversation-shell-renderer.js → rendering/conversation-shell-renderer.js} +1 -1
  62. package/dist/app/{conversation-tool-renderer.d.ts → rendering/conversation-tool-renderer.d.ts} +3 -3
  63. package/dist/app/{conversation-tool-renderer.js → rendering/conversation-tool-renderer.js} +10 -9
  64. package/dist/app/{conversation-viewport.d.ts → rendering/conversation-viewport.d.ts} +3 -3
  65. package/dist/app/{dcp-stats.js → rendering/dcp-stats.js} +1 -1
  66. package/dist/app/{editor-layout-renderer.d.ts → rendering/editor-layout-renderer.d.ts} +4 -3
  67. package/dist/app/{editor-layout-renderer.js → rendering/editor-layout-renderer.js} +13 -3
  68. package/dist/app/{editor-panels.d.ts → rendering/editor-panels.d.ts} +2 -2
  69. package/dist/app/{editor-panels.js → rendering/editor-panels.js} +4 -4
  70. package/dist/app/{message-content.d.ts → rendering/message-content.d.ts} +1 -1
  71. package/dist/app/{message-content.js → rendering/message-content.js} +66 -8
  72. package/dist/app/{render-controller.d.ts → rendering/render-controller.d.ts} +6 -6
  73. package/dist/app/{render-controller.js → rendering/render-controller.js} +11 -6
  74. package/dist/app/{render-text.d.ts → rendering/render-text.d.ts} +5 -2
  75. package/dist/app/{render-text.js → rendering/render-text.js} +53 -5
  76. package/dist/app/{status-line-renderer.d.ts → rendering/status-line-renderer.d.ts} +8 -4
  77. package/dist/app/{status-line-renderer.js → rendering/status-line-renderer.js} +73 -29
  78. package/dist/app/{tab-line-renderer.d.ts → rendering/tab-line-renderer.d.ts} +3 -3
  79. package/dist/app/{tab-line-renderer.js → rendering/tab-line-renderer.js} +2 -2
  80. package/dist/app/{toast-controller.d.ts → rendering/toast-controller.d.ts} +1 -1
  81. package/dist/app/{toast-controller.js → rendering/toast-controller.js} +2 -2
  82. package/dist/app/{toast-renderer.d.ts → rendering/toast-renderer.d.ts} +3 -3
  83. package/dist/app/{toast-renderer.js → rendering/toast-renderer.js} +3 -3
  84. package/dist/app/{tool-block-renderer.d.ts → rendering/tool-block-renderer.d.ts} +5 -5
  85. package/dist/app/{tool-block-renderer.js → rendering/tool-block-renderer.js} +15 -33
  86. package/dist/app/runtime.d.ts +6 -1
  87. package/dist/app/runtime.js +35 -2
  88. package/dist/app/{blink-controller.js → screen/blink-controller.js} +1 -1
  89. package/dist/app/{clipboard.d.ts → screen/clipboard.d.ts} +2 -2
  90. package/dist/app/{clipboard.js → screen/clipboard.js} +13 -18
  91. package/dist/app/{image-click-targets.d.ts → screen/image-click-targets.d.ts} +2 -2
  92. package/dist/app/{image-opener.d.ts → screen/image-opener.d.ts} +1 -1
  93. package/dist/app/{mouse-controller.d.ts → screen/mouse-controller.d.ts} +17 -10
  94. package/dist/app/{mouse-controller.js → screen/mouse-controller.js} +72 -29
  95. package/dist/app/{screen-selection.d.ts → screen/screen-selection.d.ts} +1 -1
  96. package/dist/app/{screen-styler.d.ts → screen/screen-styler.d.ts} +6 -3
  97. package/dist/app/{screen-styler.js → screen/screen-styler.js} +7 -6
  98. package/dist/app/{scroll-controller.d.ts → screen/scroll-controller.d.ts} +3 -3
  99. package/dist/app/{scroll-controller.js → screen/scroll-controller.js} +1 -1
  100. package/dist/app/{status-controller.d.ts → screen/status-controller.d.ts} +5 -2
  101. package/dist/app/{status-controller.js → screen/status-controller.js} +24 -9
  102. package/dist/app/{queued-message-controller.d.ts → session/queued-message-controller.d.ts} +9 -3
  103. package/dist/app/{queued-message-controller.js → session/queued-message-controller.js} +34 -23
  104. package/dist/app/{request-history.js → session/request-history.js} +2 -2
  105. package/dist/app/session/resume-session-loader.d.ts +15 -0
  106. package/dist/app/session/resume-session-loader.js +204 -0
  107. package/dist/app/{session-event-controller.d.ts → session/session-event-controller.d.ts} +8 -4
  108. package/dist/app/{session-event-controller.js → session/session-event-controller.js} +75 -8
  109. package/dist/app/{session-history.d.ts → session/session-history.d.ts} +1 -1
  110. package/dist/app/{session-history.js → session/session-history.js} +7 -6
  111. package/dist/app/{session-lifecycle-controller.d.ts → session/session-lifecycle-controller.d.ts} +7 -2
  112. package/dist/app/{session-lifecycle-controller.js → session/session-lifecycle-controller.js} +13 -5
  113. package/dist/app/{session-search.d.ts → session/session-search.d.ts} +1 -1
  114. package/dist/app/{session-search.js → session/session-search.js} +3 -3
  115. package/dist/app/{tabs-controller.d.ts → session/tabs-controller.d.ts} +11 -2
  116. package/dist/app/{tabs-controller.js → session/tabs-controller.js} +105 -9
  117. package/dist/app/{subagents-files.d.ts → subagents/subagents-files.d.ts} +1 -1
  118. package/dist/app/{subagents-files.js → subagents/subagents-files.js} +1 -1
  119. package/dist/app/{subagents-model.d.ts → subagents/subagents-model.d.ts} +1 -1
  120. package/dist/app/{subagents-model.js → subagents/subagents-model.js} +4 -4
  121. package/dist/app/{subagents-widget-controller.d.ts → subagents/subagents-widget-controller.d.ts} +1 -1
  122. package/dist/app/{subagents-widget-controller.js → subagents/subagents-widget-controller.js} +2 -2
  123. package/dist/app/{nerd-font-controller.js → terminal/nerd-font-controller.js} +16 -17
  124. package/dist/app/{terminal-bell-sound-controller.js → terminal/terminal-bell-sound-controller.js} +1 -1
  125. package/dist/app/{terminal-controller.d.ts → terminal/terminal-controller.d.ts} +1 -0
  126. package/dist/app/{terminal-controller.js → terminal/terminal-controller.js} +3 -2
  127. package/dist/app/{todo-model.d.ts → todo/todo-model.d.ts} +1 -1
  128. package/dist/app/{todo-model.js → todo/todo-model.js} +3 -3
  129. package/dist/app/{todo-widget-controller.d.ts → todo/todo-widget-controller.d.ts} +1 -1
  130. package/dist/app/{todo-widget-controller.js → todo/todo-widget-controller.js} +2 -2
  131. package/dist/app/types.d.ts +16 -2
  132. package/dist/app/{workspace-actions-controller.d.ts → workspace/workspace-actions-controller.d.ts} +2 -2
  133. package/dist/app/{workspace-actions-controller.js → workspace/workspace-actions-controller.js} +6 -6
  134. package/dist/app/{workspace-undo.d.ts → workspace/workspace-undo.d.ts} +1 -1
  135. package/dist/app/{workspace-undo.js → workspace/workspace-undo.js} +22 -20
  136. package/dist/config.d.ts +27 -0
  137. package/dist/config.js +174 -1
  138. package/dist/default-pix-config.js +38 -353
  139. package/dist/input-editor.d.ts +7 -1
  140. package/dist/input-editor.js +47 -6
  141. package/dist/main.js +2 -2
  142. package/dist/markdown-format.d.ts +1 -0
  143. package/dist/markdown-format.js +26 -1
  144. package/external/pi-tools-suite/README.md +78 -0
  145. package/external/pi-tools-suite/src/async-subagents/core/agent-strategy.ts +4 -0
  146. package/external/pi-tools-suite/src/async-subagents/core/spawn.ts +6 -1
  147. package/external/pi-tools-suite/src/dcp/compression-blocks.ts +1 -0
  148. package/external/pi-tools-suite/src/dcp/prompts.ts +5 -0
  149. package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +314 -193
  150. package/external/pi-tools-suite/src/index.ts +1 -0
  151. package/external/pi-tools-suite/src/lib/lsp.ts +2 -1
  152. package/external/pi-tools-suite/src/lsp/_shared/output.ts +8 -7
  153. package/external/pi-tools-suite/src/lsp/manager.ts +4 -4
  154. package/external/pi-tools-suite/src/opencode-import/commands.ts +86 -0
  155. package/external/pi-tools-suite/src/opencode-import/importer.ts +208 -0
  156. package/external/pi-tools-suite/src/opencode-import/index.ts +25 -0
  157. package/external/pi-tools-suite/src/repo-discovery/index.ts +49 -2
  158. package/external/pi-tools-suite/src/todo/tool/response-envelope.ts +9 -1
  159. package/external/pi-tools-suite/src/tool-descriptions.ts +1 -1
  160. package/package.json +1 -1
  161. /package/dist/app/{startup-checks.d.ts → cli/startup-checks.d.ts} +0 -0
  162. /package/dist/app/{startup-checks.js → cli/startup-checks.js} +0 -0
  163. /package/dist/app/{startup-info.d.ts → cli/startup-info.d.ts} +0 -0
  164. /package/dist/app/{startup-info.js → cli/startup-info.js} +0 -0
  165. /package/dist/app/{update.d.ts → cli/update.d.ts} +0 -0
  166. /package/dist/app/{update.js → cli/update.js} +0 -0
  167. /package/dist/app/{command-host.js → commands/command-host.js} +0 -0
  168. /package/dist/app/{command-runtime.d.ts → commands/command-runtime.d.ts} +0 -0
  169. /package/dist/app/{shell-command.d.ts → commands/shell-command.d.ts} +0 -0
  170. /package/dist/app/{shell-command.js → commands/shell-command.js} +0 -0
  171. /package/dist/app/{extension-event-bus.d.ts → extensions/extension-event-bus.d.ts} +0 -0
  172. /package/dist/app/{extension-event-bus.js → extensions/extension-event-bus.js} +0 -0
  173. /package/dist/app/{native-modifiers.d.ts → input/native-modifiers.d.ts} +0 -0
  174. /package/dist/app/{terminal-edit-shortcuts.d.ts → input/terminal-edit-shortcuts.d.ts} +0 -0
  175. /package/dist/app/{terminal-edit-shortcuts.js → input/terminal-edit-shortcuts.js} +0 -0
  176. /package/dist/app/{model-usage-controller.d.ts → model/model-usage-controller.d.ts} +0 -0
  177. /package/dist/app/{conversation-viewport.js → rendering/conversation-viewport.js} +0 -0
  178. /package/dist/app/{dcp-stats.d.ts → rendering/dcp-stats.d.ts} +0 -0
  179. /package/dist/app/{blink-controller.d.ts → screen/blink-controller.d.ts} +0 -0
  180. /package/dist/app/{file-link-opener.d.ts → screen/file-link-opener.d.ts} +0 -0
  181. /package/dist/app/{file-link-opener.js → screen/file-link-opener.js} +0 -0
  182. /package/dist/app/{file-links.d.ts → screen/file-links.d.ts} +0 -0
  183. /package/dist/app/{file-links.js → screen/file-links.js} +0 -0
  184. /package/dist/app/{image-click-targets.js → screen/image-click-targets.js} +0 -0
  185. /package/dist/app/{image-opener.js → screen/image-opener.js} +0 -0
  186. /package/dist/app/{screen-selection.js → screen/screen-selection.js} +0 -0
  187. /package/dist/app/{request-history.d.ts → session/request-history.d.ts} +0 -0
  188. /package/dist/app/{nerd-font-controller.d.ts → terminal/nerd-font-controller.d.ts} +0 -0
  189. /package/dist/app/{terminal-bell-sound-controller.d.ts → terminal/terminal-bell-sound-controller.d.ts} +0 -0
  190. /package/dist/app/{terminal-output-buffer.d.ts → terminal/terminal-output-buffer.d.ts} +0 -0
  191. /package/dist/app/{terminal-output-buffer.js → terminal/terminal-output-buffer.js} +0 -0
@@ -0,0 +1,204 @@
1
+ import { open, readdir, readFile, stat } from "node:fs/promises";
2
+ import { join, resolve } from "node:path";
3
+ import { getAgentDir } from "@earendil-works/pi-coding-agent";
4
+ import { isRecord } from "../guards.js";
5
+ const DEFAULT_INITIAL_CHUNK_SIZE = 20;
6
+ const DEFAULT_CHUNK_SIZE = 10;
7
+ export async function loadResumeSessionsInChunks(options) {
8
+ const initialChunkSize = positiveInteger(options.initialChunkSize, DEFAULT_INITIAL_CHUNK_SIZE);
9
+ const chunkSize = positiveInteger(options.chunkSize, DEFAULT_CHUNK_SIZE);
10
+ const files = await listSessionFiles(options.cwd, options.sessionDir);
11
+ const sessions = [];
12
+ if (files.length === 0) {
13
+ options.onChunk([], { loaded: 0, total: 0, done: true });
14
+ return [];
15
+ }
16
+ let index = 0;
17
+ let currentChunkSize = initialChunkSize;
18
+ while (index < files.length) {
19
+ if (options.signal?.aborted)
20
+ break;
21
+ const chunk = files.slice(index, index + currentChunkSize);
22
+ const infos = await Promise.all(chunk.map((file) => buildSessionInfo(file)));
23
+ for (const info of infos) {
24
+ if (info)
25
+ sessions.push(info);
26
+ }
27
+ sessions.sort(compareSessionsByModifiedDesc);
28
+ index += currentChunkSize;
29
+ const done = index >= files.length || options.signal?.aborted === true;
30
+ options.onChunk([...sessions], { loaded: Math.min(index, files.length), total: files.length, done });
31
+ currentChunkSize = chunkSize;
32
+ if (!done)
33
+ await nextTick();
34
+ }
35
+ return [...sessions];
36
+ }
37
+ async function listSessionFiles(cwd, sessionDir) {
38
+ const dir = sessionDir ? resolve(sessionDir) : getDefaultSessionDir(cwd);
39
+ const defaultDir = getDefaultSessionDir(cwd);
40
+ const shouldFilterCwd = sessionDir !== undefined && resolve(dir) !== resolve(defaultDir);
41
+ const resolvedCwd = resolve(cwd);
42
+ try {
43
+ const entries = await readdir(dir, { withFileTypes: true });
44
+ const files = await Promise.all(entries
45
+ .filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl"))
46
+ .map(async (entry) => {
47
+ const path = join(dir, entry.name);
48
+ try {
49
+ const stats = await stat(path);
50
+ if (shouldFilterCwd) {
51
+ const header = await readSessionHeader(path);
52
+ if (!header || !sessionCwdMatches(header.cwd, resolvedCwd))
53
+ return undefined;
54
+ }
55
+ return { path, mtime: stats.mtime };
56
+ }
57
+ catch {
58
+ return undefined;
59
+ }
60
+ }));
61
+ return files
62
+ .filter((file) => file !== undefined)
63
+ .sort((left, right) => right.mtime.getTime() - left.mtime.getTime());
64
+ }
65
+ catch {
66
+ return [];
67
+ }
68
+ }
69
+ async function readSessionHeader(filePath) {
70
+ try {
71
+ const handle = await open(filePath, "r");
72
+ try {
73
+ const buffer = Buffer.alloc(4096);
74
+ const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
75
+ const firstLine = buffer.toString("utf8", 0, bytesRead).split("\n")[0];
76
+ if (!firstLine)
77
+ return undefined;
78
+ const header = JSON.parse(firstLine);
79
+ if (!isRecord(header) || header.type !== "session" || typeof header.id !== "string")
80
+ return undefined;
81
+ return typeof header.cwd === "string" ? { id: header.id, cwd: header.cwd } : { id: header.id };
82
+ }
83
+ finally {
84
+ await handle.close();
85
+ }
86
+ }
87
+ catch {
88
+ return undefined;
89
+ }
90
+ }
91
+ function getDefaultSessionDir(cwd) {
92
+ const resolvedCwd = resolve(cwd);
93
+ const safePath = `--${resolvedCwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`;
94
+ return join(resolve(getAgentDir()), "sessions", safePath);
95
+ }
96
+ async function buildSessionInfo(file) {
97
+ try {
98
+ const content = await readFile(file.path, "utf8");
99
+ const entries = parseJsonLines(content);
100
+ const header = entries[0];
101
+ if (!isRecord(header) || header.type !== "session" || typeof header.id !== "string")
102
+ return undefined;
103
+ let messageCount = 0;
104
+ let firstMessage = "";
105
+ let name;
106
+ const allMessages = [];
107
+ let lastActivityTime;
108
+ for (const entry of entries) {
109
+ if (!isRecord(entry))
110
+ continue;
111
+ if (entry.type === "session_info") {
112
+ name = typeof entry.name === "string" ? entry.name.trim() || undefined : undefined;
113
+ continue;
114
+ }
115
+ if (entry.type !== "message" || !isRecord(entry.message))
116
+ continue;
117
+ messageCount++;
118
+ const message = entry.message;
119
+ const role = message.role;
120
+ if (role !== "user" && role !== "assistant")
121
+ continue;
122
+ const textContent = extractTextContent(message.content);
123
+ if (textContent) {
124
+ allMessages.push(textContent);
125
+ if (!firstMessage && role === "user")
126
+ firstMessage = textContent;
127
+ }
128
+ lastActivityTime = latestTimestamp(lastActivityTime, message.timestamp);
129
+ lastActivityTime = latestTimestamp(lastActivityTime, entry.timestamp);
130
+ }
131
+ const headerTimestamp = typeof header.timestamp === "string" ? new Date(header.timestamp).getTime() : NaN;
132
+ const modified = typeof lastActivityTime === "number"
133
+ ? new Date(lastActivityTime)
134
+ : (!Number.isNaN(headerTimestamp) ? new Date(headerTimestamp) : file.mtime);
135
+ return {
136
+ path: file.path,
137
+ id: header.id,
138
+ cwd: typeof header.cwd === "string" ? header.cwd : "",
139
+ ...(name === undefined ? {} : { name }),
140
+ ...(typeof header.parentSession === "string" ? { parentSessionPath: header.parentSession } : {}),
141
+ created: new Date(typeof header.timestamp === "string" ? header.timestamp : file.mtime),
142
+ modified,
143
+ messageCount,
144
+ firstMessage: firstMessage || "(no messages)",
145
+ allMessagesText: allMessages.join(" "),
146
+ };
147
+ }
148
+ catch {
149
+ return undefined;
150
+ }
151
+ }
152
+ function parseJsonLines(content) {
153
+ const entries = [];
154
+ for (const line of content.trim().split("\n")) {
155
+ if (!line.trim())
156
+ continue;
157
+ try {
158
+ entries.push(JSON.parse(line));
159
+ }
160
+ catch {
161
+ // Ignore malformed lines, matching SDK session listing behavior.
162
+ }
163
+ }
164
+ return entries;
165
+ }
166
+ function extractTextContent(content) {
167
+ if (typeof content === "string")
168
+ return content;
169
+ if (!Array.isArray(content))
170
+ return "";
171
+ return content
172
+ .map((block) => isRecord(block) && block.type === "text" && typeof block.text === "string" ? block.text : "")
173
+ .filter(Boolean)
174
+ .join(" ");
175
+ }
176
+ function latestTimestamp(current, value) {
177
+ let timestamp;
178
+ if (typeof value === "number")
179
+ timestamp = value;
180
+ else if (typeof value === "string") {
181
+ const parsed = new Date(value).getTime();
182
+ if (!Number.isNaN(parsed))
183
+ timestamp = parsed;
184
+ }
185
+ if (timestamp === undefined)
186
+ return current;
187
+ return Math.max(current ?? 0, timestamp);
188
+ }
189
+ function sessionCwdMatches(cwd, resolvedCwd) {
190
+ return cwd !== undefined && cwd !== "" && resolve(cwd) === resolvedCwd;
191
+ }
192
+ function compareSessionsByModifiedDesc(left, right) {
193
+ return right.modified.getTime() - left.modified.getTime();
194
+ }
195
+ function positiveInteger(value, fallback) {
196
+ if (value === undefined || !Number.isFinite(value) || value < 1)
197
+ return fallback;
198
+ return Math.floor(value);
199
+ }
200
+ function nextTick() {
201
+ return new Promise((resolveTick) => {
202
+ setImmediate(resolveTick);
203
+ });
204
+ }
@@ -1,18 +1,18 @@
1
1
  import type { AgentSessionEvent, AgentSessionRuntime } from "@earendil-works/pi-coding-agent";
2
- import type { ConversationViewport } from "./conversation-viewport.js";
3
- import type { Entry, SessionActivity } from "./types.js";
4
- import type { WorkspaceMutation, WorkspaceMutationPreparation } from "./workspace-undo.js";
2
+ import type { ConversationViewport } from "../rendering/conversation-viewport.js";
3
+ import type { Entry, SessionActivity } from "../types.js";
4
+ import type { WorkspaceMutation, WorkspaceMutationPreparation } from "../workspace/workspace-undo.js";
5
5
  export type AppSessionEventControllerHost = {
6
6
  readonly entries: Entry[];
7
7
  runtime(): AgentSessionRuntime | undefined;
8
8
  conversationViewport(): ConversationViewport;
9
9
  isRunning(): boolean;
10
10
  render(): void;
11
+ scheduleRender(): void;
11
12
  setStatus(status: string): void;
12
13
  restoreSessionStatus(): void;
13
14
  setSessionStatus(session: AgentSessionRuntime["session"] | undefined): void;
14
15
  setSessionActivity(activity: SessionActivity): void;
15
- flushDeferredUserMessages(): void;
16
16
  updateQueuedMessageStatus(): void;
17
17
  prepareWorkspaceMutation(toolName: string, args: unknown): WorkspaceMutationPreparation | undefined;
18
18
  recordWorkspaceMutationForUserEntry(entryId: string, mutation: WorkspaceMutation): void;
@@ -39,6 +39,7 @@ export declare class AppSessionEventController {
39
39
  private currentUserEntryId;
40
40
  private currentAssistantEntryId;
41
41
  private currentThinkingEntryId;
42
+ private assistantTextBuffer;
42
43
  constructor(host: AppSessionEventControllerHost);
43
44
  reset(): void;
44
45
  loadSessionHistory(): void;
@@ -62,6 +63,9 @@ export declare class AppSessionEventController {
62
63
  private recordToolWorkspaceMutation;
63
64
  private handleMessageUpdate;
64
65
  private appendAssistantText;
66
+ private flushAssistantTextBuffer;
67
+ private drainAssistantTextBuffer;
68
+ private hasVisibleAssistantText;
65
69
  private appendThinkingText;
66
70
  private finishCurrentThinkingEntry;
67
71
  private upsertToolEntry;
@@ -1,7 +1,9 @@
1
- import { createId } from "./id.js";
2
- import { extractImageContents, renderContent, renderUserMessageContent, stringifyUnknown } from "./message-content.js";
1
+ import { createId } from "../id.js";
2
+ import { extractImageContents, renderContent, renderUserMessageContent, stringifyUnknown } from "../rendering/message-content.js";
3
3
  import { customMessageEntry, loadSessionHistoryEntries, loadSessionHistoryEntriesAsync } from "./session-history.js";
4
- import { isRecord } from "./guards.js";
4
+ import { isRecord } from "../guards.js";
5
+ const DCP_MESSAGE_REFERENCE_PREFIX = "[dcp-id]: # (m";
6
+ const DCP_BLOCK_REFERENCE_PREFIX = "[dcp-block-id]: # (b";
5
7
  export class AppSessionEventController {
6
8
  host;
7
9
  entryRenderVersions = new Map();
@@ -10,6 +12,7 @@ export class AppSessionEventController {
10
12
  currentUserEntryId;
11
13
  currentAssistantEntryId;
12
14
  currentThinkingEntryId;
15
+ assistantTextBuffer = "";
13
16
  constructor(host) {
14
17
  this.host = host;
15
18
  }
@@ -20,6 +23,7 @@ export class AppSessionEventController {
20
23
  this.entryRenderVersions.clear();
21
24
  this.currentAssistantEntryId = undefined;
22
25
  this.currentThinkingEntryId = undefined;
26
+ this.assistantTextBuffer = "";
23
27
  }
24
28
  loadSessionHistory() {
25
29
  const runtime = this.host.runtime();
@@ -64,7 +68,6 @@ export class AppSessionEventController {
64
68
  case "agent_start":
65
69
  this.host.setSessionActivity("running");
66
70
  this.host.setSessionStatus(this.host.runtime()?.session);
67
- this.host.flushDeferredUserMessages();
68
71
  break;
69
72
  case "thinking_level_changed":
70
73
  this.host.setSessionStatus(this.host.runtime()?.session);
@@ -75,7 +78,6 @@ export class AppSessionEventController {
75
78
  this.currentUserEntryId = undefined;
76
79
  this.host.setSessionActivity("idle");
77
80
  this.host.setSessionStatus(this.host.runtime()?.session);
78
- this.host.flushDeferredUserMessages();
79
81
  break;
80
82
  case "queue_update":
81
83
  this.host.updateQueuedMessageStatus();
@@ -85,6 +87,7 @@ export class AppSessionEventController {
85
87
  break;
86
88
  case "tool_execution_start":
87
89
  this.finishCurrentThinkingEntry();
90
+ this.flushAssistantTextBuffer(true);
88
91
  this.currentAssistantEntryId = undefined;
89
92
  this.host.setSessionActivity("running");
90
93
  this.prepareToolWorkspaceMutation(event.toolCallId, event.toolName, event.args);
@@ -134,7 +137,6 @@ export class AppSessionEventController {
134
137
  ? "Compaction cancelled"
135
138
  : event.errorMessage ?? "Compaction failed";
136
139
  this.host.showToast(message, event.result ? "success" : event.aborted ? "info" : "error");
137
- this.host.flushDeferredUserMessages();
138
140
  break;
139
141
  }
140
142
  case "auto_retry_start":
@@ -149,7 +151,7 @@ export class AppSessionEventController {
149
151
  default:
150
152
  break;
151
153
  }
152
- this.host.render();
154
+ this.host.scheduleRender();
153
155
  }
154
156
  addCustomMessageEntry(message) {
155
157
  const entry = customMessageEntry(message);
@@ -214,6 +216,7 @@ export class AppSessionEventController {
214
216
  }
215
217
  if (isRecord(message) && message.role === "assistant") {
216
218
  this.finishCurrentThinkingEntry();
219
+ this.flushAssistantTextBuffer(true);
217
220
  this.clearCurrentAssistantState();
218
221
  this.currentUserEntryId = undefined;
219
222
  }
@@ -258,11 +261,13 @@ export class AppSessionEventController {
258
261
  break;
259
262
  case "done":
260
263
  this.finishCurrentThinkingEntry();
264
+ this.flushAssistantTextBuffer(true);
261
265
  this.clearCurrentAssistantState();
262
266
  this.host.setSessionActivity(this.host.runtime()?.session.isStreaming ? "running" : "idle");
263
267
  break;
264
268
  case "error":
265
269
  this.finishCurrentThinkingEntry();
270
+ this.flushAssistantTextBuffer(true);
266
271
  this.host.setSessionActivity(this.host.runtime()?.session.isStreaming ? "running" : "idle");
267
272
  this.addEntry({ id: createId("error"), kind: "error", text: assistantEvent.error.errorMessage ?? assistantEvent.reason });
268
273
  break;
@@ -271,15 +276,51 @@ export class AppSessionEventController {
271
276
  }
272
277
  }
273
278
  appendAssistantText(delta) {
279
+ this.assistantTextBuffer += delta;
280
+ this.flushAssistantTextBuffer(false);
281
+ }
282
+ flushAssistantTextBuffer(final) {
283
+ const visibleText = this.drainAssistantTextBuffer(final);
284
+ if (!visibleText)
285
+ return;
274
286
  let entry = this.currentAssistantEntryId ? this.findEntry(this.currentAssistantEntryId) : undefined;
275
287
  if (!entry || entry.kind !== "assistant") {
276
288
  entry = { id: createId("assistant"), kind: "assistant", text: "" };
277
289
  this.addEntry(entry);
278
290
  this.currentAssistantEntryId = entry.id;
279
291
  }
280
- entry.text += delta;
292
+ entry.text += visibleText;
281
293
  this.touchEntry(entry);
282
294
  }
295
+ drainAssistantTextBuffer(final) {
296
+ let visibleText = "";
297
+ for (;;) {
298
+ const newlineIndex = this.assistantTextBuffer.indexOf("\n");
299
+ if (newlineIndex === -1)
300
+ break;
301
+ const line = this.assistantTextBuffer.slice(0, newlineIndex);
302
+ this.assistantTextBuffer = this.assistantTextBuffer.slice(newlineIndex + 1);
303
+ if (shouldDropAssistantStreamLine(line, this.hasVisibleAssistantText(visibleText)))
304
+ continue;
305
+ visibleText += `${line}\n`;
306
+ }
307
+ if (!this.assistantTextBuffer)
308
+ return visibleText;
309
+ if (shouldHoldAssistantStreamTail(this.assistantTextBuffer)) {
310
+ if (final)
311
+ this.assistantTextBuffer = "";
312
+ return visibleText;
313
+ }
314
+ visibleText += this.assistantTextBuffer;
315
+ this.assistantTextBuffer = "";
316
+ return visibleText;
317
+ }
318
+ hasVisibleAssistantText(pendingVisibleText) {
319
+ if (pendingVisibleText.length > 0)
320
+ return true;
321
+ const entry = this.currentAssistantEntryId ? this.findEntry(this.currentAssistantEntryId) : undefined;
322
+ return entry?.kind === "assistant" && entry.text.length > 0;
323
+ }
283
324
  appendThinkingText(delta) {
284
325
  let entry = this.currentThinkingEntryId ? this.findEntry(this.currentThinkingEntryId) : undefined;
285
326
  if (!entry || entry.kind !== "thinking") {
@@ -334,5 +375,31 @@ export class AppSessionEventController {
334
375
  clearCurrentAssistantState() {
335
376
  this.currentAssistantEntryId = undefined;
336
377
  this.currentThinkingEntryId = undefined;
378
+ this.assistantTextBuffer = "";
337
379
  }
338
380
  }
381
+ function shouldDropAssistantStreamLine(line, hasVisibleText) {
382
+ if (line.trim().length === 0 && !hasVisibleText)
383
+ return true;
384
+ return isHiddenMarkdownMetadataLine(line);
385
+ }
386
+ function shouldHoldAssistantStreamTail(text) {
387
+ if (text.trim().length === 0)
388
+ return true;
389
+ return isPotentialDcpMetadataLine(text);
390
+ }
391
+ function isHiddenMarkdownMetadataLine(line) {
392
+ return isMarkdownReferenceDefinition(line) || isPotentialDcpMetadataLine(line);
393
+ }
394
+ function isMarkdownReferenceDefinition(line) {
395
+ return /^ {0,3}\[[^\]\n]+\]:[ \t]*\S.*$/u.test(line);
396
+ }
397
+ function isPotentialDcpMetadataLine(line) {
398
+ const content = line.replace(/^ {0,3}/u, "");
399
+ if (content.length === 0)
400
+ return false;
401
+ return isPotentialDcpReference(content, DCP_MESSAGE_REFERENCE_PREFIX) || isPotentialDcpReference(content, DCP_BLOCK_REFERENCE_PREFIX);
402
+ }
403
+ function isPotentialDcpReference(content, markerPrefix) {
404
+ return markerPrefix.startsWith(content) || (content.startsWith(markerPrefix) && /^\d*\)?$/u.test(content.slice(markerPrefix.length)));
405
+ }
@@ -1,4 +1,4 @@
1
- import type { Entry } from "./types.js";
1
+ import type { Entry } from "../types.js";
2
2
  type SubagentsToolResultObserveOptions = {
3
3
  showSnapshot?: boolean;
4
4
  };
@@ -1,6 +1,7 @@
1
- import { isRecord } from "./guards.js";
2
- import { createId } from "./id.js";
3
- import { extractImageContents, renderContent, renderUserMessageContent, stringifyUnknown } from "./message-content.js";
1
+ import { isRecord } from "../guards.js";
2
+ import { createId } from "../id.js";
3
+ import { isOnlyHiddenMetadata } from "../../markdown-format.js";
4
+ import { extractImageContents, renderContent, renderUserMessageContent, stringifyUnknown } from "../rendering/message-content.js";
4
5
  const SYSTEM_CUSTOM_MESSAGE_TYPE = "pix-system";
5
6
  const HISTORICAL_SUBAGENTS_OBSERVATION = { showSnapshot: false };
6
7
  const DEFAULT_HISTORY_CHUNK_SIZE = 50;
@@ -119,10 +120,10 @@ function renderAssistantHistoryMessage(message, toolResults, options) {
119
120
  options.addEntry({ id: createId("thinking"), kind: "thinking", text: thinkingText, expanded: false, status: "done" });
120
121
  thinkingText = "";
121
122
  }
122
- if (assistantText) {
123
+ if (assistantText && !isOnlyHiddenMetadata(assistantText)) {
123
124
  options.addEntry({ id: createId("assistant"), kind: "assistant", text: assistantText });
124
- assistantText = "";
125
125
  }
126
+ assistantText = "";
126
127
  const toolCallId = String(block.id ?? createId("tool"));
127
128
  const result = toolResults.get(toolCallId);
128
129
  const toolName = result?.toolName ?? String(block.name ?? "unknown");
@@ -158,7 +159,7 @@ function renderAssistantHistoryMessage(message, toolResults, options) {
158
159
  if (thinkingText) {
159
160
  options.addEntry({ id: createId("thinking"), kind: "thinking", text: thinkingText, expanded: false, status: "done" });
160
161
  }
161
- if (assistantText) {
162
+ if (assistantText && !isOnlyHiddenMetadata(assistantText)) {
162
163
  options.addEntry({ id: createId("assistant"), kind: "assistant", text: assistantText });
163
164
  }
164
165
  }
@@ -1,6 +1,6 @@
1
1
  import type { AgentSession, AgentSessionEvent, AgentSessionRuntime, ExtensionCommandContextActions, ExtensionError } from "@earendil-works/pi-coding-agent";
2
- import type { InputEditor } from "../input-editor.js";
3
- import type { AppOptions, Entry, PixExtensionUIContext, SessionActivity } from "./types.js";
2
+ import type { InputEditor } from "../../input-editor.js";
3
+ import type { AppOptions, Entry, PixExtensionUIContext, SessionActivity } from "../types.js";
4
4
  export type AppSessionLifecycleHost = {
5
5
  options: AppOptions;
6
6
  createRuntime(): Promise<AgentSessionRuntime>;
@@ -37,6 +37,10 @@ export type AppSessionLifecycleHost = {
37
37
  clearMouseRenderState(): void;
38
38
  scrollReset(): void;
39
39
  loadSessionHistoryEntries(): void;
40
+ loadSessionHistoryEntriesAsync(options: {
41
+ isCancelled: () => boolean;
42
+ render: () => void;
43
+ }): Promise<boolean>;
40
44
  syncUserSessionEntryMetadata(): void;
41
45
  restoreTabsAfterStartup(): Promise<void>;
42
46
  render(): void;
@@ -49,6 +53,7 @@ export declare class AppSessionLifecycleController {
49
53
  bindCurrentSession(): Promise<void>;
50
54
  unsubscribeSession(): void;
51
55
  afterSessionReplacement(message?: string): void;
56
+ private loadReplacementHistory;
52
57
  resetSessionView(): void;
53
58
  loadSessionHistory(): void;
54
59
  requireRuntime(): AgentSessionRuntime;
@@ -1,7 +1,7 @@
1
- import { createId } from "./id.js";
2
- import { stringifyUnknown } from "./message-content.js";
3
- import { collectStartupAvailabilityIssues } from "./startup-checks.js";
4
- import { createStartupInfoMessage, isEmptyStartupSession } from "./startup-info.js";
1
+ import { createId } from "../id.js";
2
+ import { stringifyUnknown } from "../rendering/message-content.js";
3
+ import { collectStartupAvailabilityIssues } from "../cli/startup-checks.js";
4
+ import { createStartupInfoMessage, isEmptyStartupSession } from "../cli/startup-info.js";
5
5
  export class AppSessionLifecycleController {
6
6
  host;
7
7
  unsubscribe;
@@ -84,7 +84,15 @@ export class AppSessionLifecycleController {
84
84
  }
85
85
  afterSessionReplacement(message) {
86
86
  this.resetSessionView();
87
- this.loadSessionHistory();
87
+ void this.loadReplacementHistory(message);
88
+ this.host.render();
89
+ }
90
+ async loadReplacementHistory(message) {
91
+ await this.host.loadSessionHistoryEntriesAsync({
92
+ isCancelled: () => !this.host.isRunning(),
93
+ render: () => this.host.render(),
94
+ });
95
+ this.host.syncUserSessionEntryMetadata();
88
96
  if (message)
89
97
  this.host.addEntry({ id: createId("system"), kind: "system", text: message });
90
98
  const session = this.host.runtime()?.session;
@@ -1,5 +1,5 @@
1
1
  import { type SessionInfo } from "@earendil-works/pi-coding-agent";
2
- import type { Entry, PixMenuItem } from "./types.js";
2
+ import type { Entry, PixMenuItem } from "../types.js";
3
3
  export type SessionSearchMatch = {
4
4
  sessionEntryId?: string;
5
5
  role?: string;
@@ -1,7 +1,7 @@
1
1
  import { SessionManager } from "@earendil-works/pi-coding-agent";
2
- import { isRecord } from "./guards.js";
3
- import { renderContent, renderUserMessageContent } from "./message-content.js";
4
- import { sanitizeText } from "./render-text.js";
2
+ import { isRecord } from "../guards.js";
3
+ import { renderContent, renderUserMessageContent } from "../rendering/message-content.js";
4
+ import { sanitizeText } from "../rendering/render-text.js";
5
5
  const DEFAULT_MAX_SEARCH_RESULTS = 50;
6
6
  const DEFAULT_SNIPPET_LENGTH = 160;
7
7
  const DEFAULT_SCROLL_SAMPLE_LENGTH = 80;
@@ -1,6 +1,6 @@
1
1
  import { type AgentSession, type AgentSessionRuntime } from "@earendil-works/pi-coding-agent";
2
- import type { AppBlinkController } from "./blink-controller.js";
3
- import type { AppOptions, Entry, SessionActivity, SessionTab } from "./types.js";
2
+ import type { AppBlinkController } from "../screen/blink-controller.js";
3
+ import type { AppOptions, Entry, SessionActivity, SessionTab, SubmittedUserMessage } from "../types.js";
4
4
  export type TabInputState = {
5
5
  text: string;
6
6
  cursor: number;
@@ -26,6 +26,8 @@ export type AppTabsControllerHost = {
26
26
  syncUserSessionEntryMetadata(): void;
27
27
  captureInputState(): TabInputState;
28
28
  restoreInputState(state: TabInputState): void;
29
+ captureDeferredUserMessages?(): readonly SubmittedUserMessage[];
30
+ restoreDeferredUserMessages?(messages: readonly SubmittedUserMessage[]): void;
29
31
  addEntry(entry: Entry): void;
30
32
  showToast(message: string, kind: "success" | "error" | "warning" | "info"): void;
31
33
  render(): void;
@@ -36,6 +38,7 @@ export declare class AppTabsController {
36
38
  private readonly runtimesByTabId;
37
39
  private readonly runtimeSubscriptionsByTabId;
38
40
  private readonly inputStatesByTabId;
41
+ private readonly deferredUserMessagesByTabId;
39
42
  private activeTabId;
40
43
  private pendingActiveTabId;
41
44
  private historyLoadGeneration;
@@ -50,6 +53,7 @@ export declare class AppTabsController {
50
53
  setInputStateForTab(tabId: string | undefined, state: TabInputState): Promise<void>;
51
54
  disposeInactiveRuntimes(disposeRuntime?: (runtime: AgentSessionRuntime) => Promise<void>): Promise<void>;
52
55
  saveInputStateForQuit(): Promise<void>;
56
+ persistActiveDeferredUserMessages(): void;
53
57
  syncActiveTabFromRuntime(options?: {
54
58
  save?: boolean;
55
59
  force?: boolean;
@@ -74,13 +78,17 @@ export declare class AppTabsController {
74
78
  private shouldSyncTabFromRuntimeEvent;
75
79
  private syncTabFromObservedRuntime;
76
80
  private storeActiveInputState;
81
+ private storeActiveDeferredUserMessages;
77
82
  private restoreInputState;
83
+ private restoreDeferredUserMessages;
84
+ private cloneSubmittedUserMessage;
78
85
  private runtimeForTab;
79
86
  private findTabForSession;
80
87
  private findTabBySessionPath;
81
88
  private replaceTabs;
82
89
  private removeTab;
83
90
  private restorePersistedInputStates;
91
+ private restorePersistedDeferredUserMessages;
84
92
  private ensureCurrentSessionTab;
85
93
  private tabFromSession;
86
94
  private updateTabFromSession;
@@ -95,6 +103,7 @@ export declare class AppTabsController {
95
103
  private restoredTabs;
96
104
  private loadTabs;
97
105
  private parsePersistedInputState;
106
+ private parsePersistedSubmittedUserMessages;
98
107
  private saveTabs;
99
108
  private filePath;
100
109
  }