pikiloop 0.4.0

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 (154) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +353 -0
  3. package/README.v2.md +287 -0
  4. package/README.zh-CN.md +352 -0
  5. package/dashboard/dist/assets/AgentTab-UZPIhlkr.js +1 -0
  6. package/dashboard/dist/assets/DirBrowser-Ckcmi-Pi.js +1 -0
  7. package/dashboard/dist/assets/ExtensionsTab-KZhEDrdu.js +1 -0
  8. package/dashboard/dist/assets/IMAccessTab-Bd_IY1GQ.js +1 -0
  9. package/dashboard/dist/assets/Modal-CTeL0y7P.js +1 -0
  10. package/dashboard/dist/assets/Modals-axftHasy.js +1 -0
  11. package/dashboard/dist/assets/Select-C8tOdPhe.js +1 -0
  12. package/dashboard/dist/assets/SessionPanel-C1geSRxw.js +1 -0
  13. package/dashboard/dist/assets/SystemTab-DBDkaPiO.js +1 -0
  14. package/dashboard/dist/assets/anthropic-BAdojD7P.ico +0 -0
  15. package/dashboard/dist/assets/codex-DYadqqp0.png +0 -0
  16. package/dashboard/dist/assets/deepseek-BeYNZEk0.ico +0 -0
  17. package/dashboard/dist/assets/doubao-DloFDuFR.png +0 -0
  18. package/dashboard/dist/assets/feishu-C4OMrjCW.ico +0 -0
  19. package/dashboard/dist/assets/gemini-BYkEpiWr.svg +1 -0
  20. package/dashboard/dist/assets/hermes-BAarh-tH.png +0 -0
  21. package/dashboard/dist/assets/index-CpM4CqZJ.js +23 -0
  22. package/dashboard/dist/assets/index-DXSohzrE.js +3 -0
  23. package/dashboard/dist/assets/index-reSbuley.css +1 -0
  24. package/dashboard/dist/assets/markdown-DxQYQFeH.js +29 -0
  25. package/dashboard/dist/assets/minimax-PuEGTfrF.ico +0 -0
  26. package/dashboard/dist/assets/mlx-DhWwjtMw.png +0 -0
  27. package/dashboard/dist/assets/ollama-Bt9O-2K_.png +0 -0
  28. package/dashboard/dist/assets/openrouter-CsJ_bD5Q.ico +0 -0
  29. package/dashboard/dist/assets/playwright-BldPFZgC.ico +0 -0
  30. package/dashboard/dist/assets/qwen-xykkX0_y.png +0 -0
  31. package/dashboard/dist/assets/react-vendor-C7Sl8SE7.js +9 -0
  32. package/dashboard/dist/assets/router-DHISdpPk.js +3 -0
  33. package/dashboard/dist/assets/shared-BIP_4k4I.js +1 -0
  34. package/dashboard/dist/favicon.svg +28 -0
  35. package/dashboard/dist/index.html +17 -0
  36. package/dist/agent/acp-client.js +261 -0
  37. package/dist/agent/auto-update.js +432 -0
  38. package/dist/agent/await-resume.js +50 -0
  39. package/dist/agent/cli/auth.js +325 -0
  40. package/dist/agent/cli/catalog.js +40 -0
  41. package/dist/agent/cli/detector.js +136 -0
  42. package/dist/agent/cli/index.js +7 -0
  43. package/dist/agent/cli/registry.js +33 -0
  44. package/dist/agent/driver.js +39 -0
  45. package/dist/agent/drivers/claude-tui.js +2297 -0
  46. package/dist/agent/drivers/claude.js +2689 -0
  47. package/dist/agent/drivers/codex.js +2210 -0
  48. package/dist/agent/drivers/gemini.js +1059 -0
  49. package/dist/agent/drivers/hermes.js +795 -0
  50. package/dist/agent/goal.js +274 -0
  51. package/dist/agent/handover.js +130 -0
  52. package/dist/agent/images.js +355 -0
  53. package/dist/agent/index.js +50 -0
  54. package/dist/agent/mcp/bridge.js +791 -0
  55. package/dist/agent/mcp/extensions.js +637 -0
  56. package/dist/agent/mcp/oauth.js +353 -0
  57. package/dist/agent/mcp/registry.js +119 -0
  58. package/dist/agent/mcp/session-server.js +229 -0
  59. package/dist/agent/mcp/tools/ask-user.js +113 -0
  60. package/dist/agent/mcp/tools/await-resume.js +77 -0
  61. package/dist/agent/mcp/tools/goal.js +144 -0
  62. package/dist/agent/mcp/tools/types.js +12 -0
  63. package/dist/agent/mcp/tools/workspace.js +212 -0
  64. package/dist/agent/npm.js +31 -0
  65. package/dist/agent/session.js +1206 -0
  66. package/dist/agent/skill-installer.js +160 -0
  67. package/dist/agent/skills.js +257 -0
  68. package/dist/agent/stream.js +743 -0
  69. package/dist/agent/types.js +13 -0
  70. package/dist/agent/utils.js +687 -0
  71. package/dist/bot/bot.js +2499 -0
  72. package/dist/bot/command-ui.js +633 -0
  73. package/dist/bot/commands.js +513 -0
  74. package/dist/bot/headless-bot.js +36 -0
  75. package/dist/bot/host.js +192 -0
  76. package/dist/bot/human-loop.js +168 -0
  77. package/dist/bot/menu.js +48 -0
  78. package/dist/bot/orchestration.js +79 -0
  79. package/dist/bot/render-shared.js +309 -0
  80. package/dist/bot/session-hub.js +361 -0
  81. package/dist/bot/session-status.js +55 -0
  82. package/dist/bot/streaming.js +309 -0
  83. package/dist/browser-profile.js +579 -0
  84. package/dist/browser-supervisor.js +249 -0
  85. package/dist/catalog/cli-tools.js +421 -0
  86. package/dist/catalog/index.js +21 -0
  87. package/dist/catalog/local-models.js +94 -0
  88. package/dist/catalog/mcp-servers.js +315 -0
  89. package/dist/catalog/skill-repos.js +173 -0
  90. package/dist/channels/base.js +55 -0
  91. package/dist/channels/dingtalk/bot.js +549 -0
  92. package/dist/channels/dingtalk/channel.js +268 -0
  93. package/dist/channels/discord/bot.js +552 -0
  94. package/dist/channels/discord/channel.js +245 -0
  95. package/dist/channels/feishu/bot.js +1275 -0
  96. package/dist/channels/feishu/channel.js +911 -0
  97. package/dist/channels/feishu/markdown.js +91 -0
  98. package/dist/channels/feishu/render.js +619 -0
  99. package/dist/channels/health.js +109 -0
  100. package/dist/channels/slack/bot.js +554 -0
  101. package/dist/channels/slack/channel.js +283 -0
  102. package/dist/channels/states.js +6 -0
  103. package/dist/channels/telegram/bot.js +1310 -0
  104. package/dist/channels/telegram/channel.js +820 -0
  105. package/dist/channels/telegram/directory.js +111 -0
  106. package/dist/channels/telegram/live-preview.js +220 -0
  107. package/dist/channels/telegram/render.js +384 -0
  108. package/dist/channels/wecom/bot.js +558 -0
  109. package/dist/channels/wecom/channel.js +479 -0
  110. package/dist/channels/weixin/api.js +520 -0
  111. package/dist/channels/weixin/bot.js +1000 -0
  112. package/dist/channels/weixin/channel.js +222 -0
  113. package/dist/cli/autostart.js +262 -0
  114. package/dist/cli/channel-supervisor.js +313 -0
  115. package/dist/cli/channels.js +54 -0
  116. package/dist/cli/main.js +726 -0
  117. package/dist/cli/onboarding.js +227 -0
  118. package/dist/cli/run.js +308 -0
  119. package/dist/cli/setup-wizard.js +235 -0
  120. package/dist/core/config/runtime-config.js +201 -0
  121. package/dist/core/config/user-config.js +510 -0
  122. package/dist/core/config/validation.js +521 -0
  123. package/dist/core/constants.js +400 -0
  124. package/dist/core/git.js +145 -0
  125. package/dist/core/legacy-compat.js +60 -0
  126. package/dist/core/logging.js +101 -0
  127. package/dist/core/platform.js +59 -0
  128. package/dist/core/process-control.js +315 -0
  129. package/dist/core/secrets/index.js +42 -0
  130. package/dist/core/secrets/inline-seal.js +60 -0
  131. package/dist/core/secrets/ref.js +33 -0
  132. package/dist/core/secrets/resolver.js +65 -0
  133. package/dist/core/secrets/store.js +63 -0
  134. package/dist/core/utils.js +233 -0
  135. package/dist/core/version.js +15 -0
  136. package/dist/dashboard/platform.js +219 -0
  137. package/dist/dashboard/routes/agents.js +450 -0
  138. package/dist/dashboard/routes/cli.js +174 -0
  139. package/dist/dashboard/routes/config.js +523 -0
  140. package/dist/dashboard/routes/extensions.js +745 -0
  141. package/dist/dashboard/routes/local-models.js +290 -0
  142. package/dist/dashboard/routes/models.js +324 -0
  143. package/dist/dashboard/routes/sessions.js +838 -0
  144. package/dist/dashboard/runtime.js +410 -0
  145. package/dist/dashboard/server.js +237 -0
  146. package/dist/dashboard/session-control.js +347 -0
  147. package/dist/model/catalog.js +104 -0
  148. package/dist/model/index.js +20 -0
  149. package/dist/model/injector.js +272 -0
  150. package/dist/model/provider-models.js +112 -0
  151. package/dist/model/store.js +212 -0
  152. package/dist/model/types.js +13 -0
  153. package/dist/model/validation.js +203 -0
  154. package/package.json +82 -0
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Telegram workdir browser for interactive directory navigation.
3
+ */
4
+ import path from 'node:path';
5
+ import { listSubdirs } from '../../bot/bot.js';
6
+ import { buildCompactSelectionTitle, compactCode, truncateMiddle } from './render.js';
7
+ class PathRegistry {
8
+ pathToId = new Map();
9
+ idToPath = new Map();
10
+ nextId = 1;
11
+ register(dirPath) {
12
+ let id = this.pathToId.get(dirPath);
13
+ if (id != null)
14
+ return id;
15
+ id = this.nextId++;
16
+ this.pathToId.set(dirPath, id);
17
+ this.idToPath.set(id, dirPath);
18
+ if (this.pathToId.size > 500) {
19
+ const oldest = [...this.pathToId.entries()].slice(0, 200);
20
+ for (const [oldPath, oldId] of oldest) {
21
+ this.pathToId.delete(oldPath);
22
+ this.idToPath.delete(oldId);
23
+ }
24
+ }
25
+ return id;
26
+ }
27
+ resolve(id) {
28
+ return this.idToPath.get(id);
29
+ }
30
+ }
31
+ const pathRegistry = new PathRegistry();
32
+ const DIR_PAGE_SIZE = 8;
33
+ function buildDirKeyboard(browsePath, page) {
34
+ const dirs = listSubdirs(browsePath);
35
+ const totalPages = Math.max(1, Math.ceil(dirs.length / DIR_PAGE_SIZE));
36
+ const currentPage = Math.min(Math.max(0, page), totalPages - 1);
37
+ const slice = dirs.slice(currentPage * DIR_PAGE_SIZE, (currentPage + 1) * DIR_PAGE_SIZE);
38
+ const rows = [];
39
+ for (let i = 0; i < slice.length; i += 2) {
40
+ const row = [];
41
+ for (let j = i; j < Math.min(i + 2, slice.length); j++) {
42
+ const fullPath = path.join(browsePath, slice[j]);
43
+ const id = pathRegistry.register(fullPath);
44
+ row.push({ text: slice[j], callback_data: `sw:n:${id}:0` });
45
+ }
46
+ rows.push(row);
47
+ }
48
+ const navRow = [];
49
+ const parent = path.dirname(browsePath);
50
+ if (parent !== browsePath) {
51
+ navRow.push({ text: '⬆ ..', callback_data: `sw:n:${pathRegistry.register(parent)}:0` });
52
+ }
53
+ if (totalPages > 1) {
54
+ const browseId = pathRegistry.register(browsePath);
55
+ if (currentPage > 0)
56
+ navRow.push({ text: `◀ ${currentPage}/${totalPages}`, callback_data: `sw:n:${browseId}:${currentPage - 1}` });
57
+ if (currentPage < totalPages - 1)
58
+ navRow.push({ text: `${currentPage + 2}/${totalPages} ▶`, callback_data: `sw:n:${browseId}:${currentPage + 1}` });
59
+ }
60
+ if (navRow.length)
61
+ rows.push(navRow);
62
+ rows.push([{ text: 'Use This', callback_data: `sw:s:${pathRegistry.register(browsePath)}` }]);
63
+ return { inline_keyboard: rows };
64
+ }
65
+ export function buildSwitchWorkdirView(currentWorkdir, browsePath, page = 0, opts = {}) {
66
+ const lines = [buildCompactSelectionTitle('Workdir')];
67
+ lines.push(`● ${compactCode(currentWorkdir, 42)}`);
68
+ if (browsePath !== currentWorkdir)
69
+ lines.push(`○ ${compactCode(browsePath, 42)}`);
70
+ if (opts.savedWorkspaceCount && opts.savedWorkspaceCount > 0) {
71
+ lines.push('', `<i>Tip: ${opts.savedWorkspaceCount} saved workspace${opts.savedWorkspaceCount === 1 ? '' : 's'} — use /workspaces for one-tap switching.</i>`);
72
+ }
73
+ return {
74
+ text: lines.join('\n'),
75
+ keyboard: buildDirKeyboard(browsePath, page),
76
+ };
77
+ }
78
+ export function resolveRegisteredPath(id) {
79
+ return pathRegistry.resolve(id);
80
+ }
81
+ const WORKSPACES_PAGE_SIZE = 10;
82
+ export function buildWorkspacesView(data, page = 0) {
83
+ const { workspaces, currentWorkdir } = data;
84
+ const lines = [buildCompactSelectionTitle('Workspaces')];
85
+ if (workspaces.length === 0) {
86
+ lines.push('No saved workspaces yet.', '', 'Add workspaces from the Dashboard (Sessions → Add Workspace), then come back to switch with one tap.', '', 'You can still browse the file system with /switch.');
87
+ return { text: lines.join('\n'), keyboard: { inline_keyboard: [] } };
88
+ }
89
+ lines.push(`● ${compactCode(currentWorkdir, 42)}`);
90
+ const totalPages = Math.max(1, Math.ceil(workspaces.length / WORKSPACES_PAGE_SIZE));
91
+ const currentPage = Math.min(Math.max(0, page), totalPages - 1);
92
+ const slice = workspaces.slice(currentPage * WORKSPACES_PAGE_SIZE, (currentPage + 1) * WORKSPACES_PAGE_SIZE);
93
+ const rows = [];
94
+ for (const ws of slice) {
95
+ const marker = ws.isCurrent ? '✓ ' : ws.exists ? '' : '⚠ ';
96
+ const id = pathRegistry.register(ws.path);
97
+ const label = `${marker}${truncateMiddle(ws.name, 40)}`;
98
+ rows.push([{ text: label, callback_data: `wsp:s:${id}` }]);
99
+ }
100
+ if (totalPages > 1) {
101
+ const navRow = [];
102
+ if (currentPage > 0)
103
+ navRow.push({ text: `◀ ${currentPage}/${totalPages}`, callback_data: `wsp:p:${currentPage - 1}` });
104
+ if (currentPage < totalPages - 1)
105
+ navRow.push({ text: `${currentPage + 2}/${totalPages} ▶`, callback_data: `wsp:p:${currentPage + 1}` });
106
+ if (navRow.length)
107
+ rows.push(navRow);
108
+ }
109
+ lines.push('', `Tap a workspace to switch. ${workspaces.length} saved.`);
110
+ return { text: lines.join('\n'), keyboard: { inline_keyboard: rows } };
111
+ }
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Channel-agnostic live preview controller for streaming updates.
3
+ */
4
+ import { hasPreviewMeta, samePreviewMeta, samePreviewPlan } from '../../bot/streaming.js';
5
+ import { STREAM_PREVIEW_TIMEOUTS } from '../../core/constants.js';
6
+ const STREAM_PREVIEW_HEARTBEAT_MS = STREAM_PREVIEW_TIMEOUTS.heartbeat;
7
+ const STREAM_TYPING_HEARTBEAT_MS = STREAM_PREVIEW_TIMEOUTS.typing;
8
+ const STREAM_STALLED_NOTICE_MS = STREAM_PREVIEW_TIMEOUTS.stalledNotice;
9
+ // ---------------------------------------------------------------------------
10
+ // LivePreview — generic streaming preview controller
11
+ // ---------------------------------------------------------------------------
12
+ export class LivePreview {
13
+ initialText;
14
+ agent;
15
+ chatId;
16
+ placeholderMessageId;
17
+ channel;
18
+ renderer;
19
+ streamEditIntervalMs;
20
+ startTimeMs;
21
+ canEditMessages;
22
+ canSendTyping;
23
+ messageThreadId;
24
+ parseMode;
25
+ keyboard;
26
+ model;
27
+ effort;
28
+ log;
29
+ heartbeatTimer = null;
30
+ typingTimer = null;
31
+ flushTimer = null;
32
+ editChain = Promise.resolve();
33
+ previewVersion = 0;
34
+ editCount = 0;
35
+ lastEditAt = 0;
36
+ lastProgressAt;
37
+ lastPreview;
38
+ latestText = '';
39
+ latestThinking = '';
40
+ latestActivity = '';
41
+ latestMeta = null;
42
+ latestPlan = null;
43
+ consecutiveEditFailures = 0;
44
+ placeholderAbandoned = false;
45
+ static MAX_CONSECUTIVE_EDIT_FAILURES = 3;
46
+ constructor(options) {
47
+ this.agent = options.agent;
48
+ this.chatId = options.chatId;
49
+ this.placeholderMessageId = options.placeholderMessageId;
50
+ this.channel = options.channel;
51
+ this.renderer = options.renderer;
52
+ this.streamEditIntervalMs = options.streamEditIntervalMs;
53
+ this.startTimeMs = options.startTimeMs;
54
+ this.canEditMessages = options.canEditMessages;
55
+ this.canSendTyping = options.canSendTyping;
56
+ this.messageThreadId = options.messageThreadId;
57
+ this.parseMode = options.parseMode ?? 'HTML';
58
+ this.keyboard = options.keyboard;
59
+ this.model = options.model ?? null;
60
+ this.effort = options.effort ?? null;
61
+ this.log = options.log ?? (() => { });
62
+ this.initialText = this.renderer.renderInitial(this.agent, this.model, this.effort);
63
+ this.lastPreview = this.initialText;
64
+ this.lastProgressAt = this.startTimeMs;
65
+ }
66
+ start() {
67
+ this.sendTypingPulse();
68
+ if (this.canEditMessages) {
69
+ this.heartbeatTimer = setInterval(() => {
70
+ const idleMs = Date.now() - this.lastProgressAt;
71
+ const recentlyEdited = Date.now() - this.lastEditAt < STREAM_PREVIEW_HEARTBEAT_MS - 250;
72
+ if (recentlyEdited && idleMs < STREAM_STALLED_NOTICE_MS)
73
+ return;
74
+ this.queuePreviewEdit(true);
75
+ }, STREAM_PREVIEW_HEARTBEAT_MS);
76
+ this.heartbeatTimer.unref?.();
77
+ }
78
+ if (this.canSendTyping) {
79
+ this.typingTimer = setInterval(() => this.sendTypingPulse(), STREAM_TYPING_HEARTBEAT_MS);
80
+ this.typingTimer.unref?.();
81
+ }
82
+ }
83
+ update(text, thinking, activity = '', meta, plan) {
84
+ const nextMeta = hasPreviewMeta(meta) ? meta : null;
85
+ const nextPlan = plan?.steps?.length ? plan : null;
86
+ const changed = text !== this.latestText
87
+ || thinking !== this.latestThinking
88
+ || activity !== this.latestActivity
89
+ || !samePreviewMeta(nextMeta, this.latestMeta)
90
+ || !samePreviewPlan(nextPlan, this.latestPlan);
91
+ this.latestText = text;
92
+ this.latestThinking = thinking;
93
+ this.latestActivity = activity;
94
+ this.latestMeta = nextMeta;
95
+ this.latestPlan = nextPlan;
96
+ if (changed)
97
+ this.lastProgressAt = Date.now();
98
+ if (!text.trim() && !thinking.trim() && !activity.trim() && !nextMeta && !nextPlan)
99
+ return;
100
+ this.schedulePreviewEdit();
101
+ }
102
+ async settle() {
103
+ this.stopFeedback();
104
+ await this.flushPreviewEdits();
105
+ }
106
+ dispose() {
107
+ this.stopFeedback();
108
+ if (this.flushTimer) {
109
+ clearTimeout(this.flushTimer);
110
+ this.flushTimer = null;
111
+ }
112
+ this.previewVersion++;
113
+ }
114
+ getEditCount() {
115
+ return this.editCount;
116
+ }
117
+ getRenderedPreview() {
118
+ return this.lastPreview;
119
+ }
120
+ stopFeedback() {
121
+ if (this.heartbeatTimer) {
122
+ clearInterval(this.heartbeatTimer);
123
+ this.heartbeatTimer = null;
124
+ }
125
+ if (this.typingTimer) {
126
+ clearInterval(this.typingTimer);
127
+ this.typingTimer = null;
128
+ }
129
+ }
130
+ sendTypingPulse() {
131
+ if (!this.canSendTyping)
132
+ return;
133
+ void this.channel.sendTyping(this.chatId, { messageThreadId: this.messageThreadId }).catch(() => { });
134
+ }
135
+ renderPreview() {
136
+ return this.renderer.renderStream({
137
+ agent: this.agent,
138
+ elapsedMs: Date.now() - this.startTimeMs,
139
+ bodyText: this.latestText,
140
+ thinking: this.latestThinking,
141
+ activity: this.latestActivity,
142
+ meta: this.latestMeta,
143
+ plan: this.latestPlan,
144
+ model: this.model,
145
+ effort: this.effort,
146
+ });
147
+ }
148
+ schedulePreviewEdit() {
149
+ if (!this.canEditMessages)
150
+ return;
151
+ const wait = this.streamEditIntervalMs - (Date.now() - this.lastEditAt);
152
+ if (wait <= 0) {
153
+ if (this.flushTimer) {
154
+ clearTimeout(this.flushTimer);
155
+ this.flushTimer = null;
156
+ }
157
+ this.queuePreviewEdit();
158
+ return;
159
+ }
160
+ if (this.flushTimer)
161
+ return;
162
+ this.flushTimer = setTimeout(() => {
163
+ this.flushTimer = null;
164
+ this.queuePreviewEdit();
165
+ }, wait);
166
+ }
167
+ queuePreviewEdit(force = false) {
168
+ if (!this.canEditMessages || this.placeholderMessageId == null)
169
+ return;
170
+ if (this.placeholderAbandoned)
171
+ return;
172
+ const placeholderMessageId = this.placeholderMessageId;
173
+ const preview = this.renderPreview();
174
+ if (!preview)
175
+ return;
176
+ if (!force && preview === this.lastPreview)
177
+ return;
178
+ this.lastPreview = preview;
179
+ const version = ++this.previewVersion;
180
+ this.editCount++;
181
+ this.lastEditAt = Date.now();
182
+ this.editChain = this.editChain
183
+ .catch(() => { })
184
+ .then(async () => {
185
+ if (version !== this.previewVersion)
186
+ return;
187
+ if (this.placeholderAbandoned)
188
+ return;
189
+ try {
190
+ await this.channel.editMessage(this.chatId, placeholderMessageId, preview, { parseMode: this.parseMode, keyboard: this.keyboard });
191
+ this.consecutiveEditFailures = 0;
192
+ }
193
+ catch (error) {
194
+ this.consecutiveEditFailures++;
195
+ this.log(`stream edit err (${this.consecutiveEditFailures}/${LivePreview.MAX_CONSECUTIVE_EDIT_FAILURES}): ${error?.message || error}`);
196
+ if (this.consecutiveEditFailures >= LivePreview.MAX_CONSECUTIVE_EDIT_FAILURES) {
197
+ this.placeholderAbandoned = true;
198
+ this.log(`placeholder abandoned after ${this.consecutiveEditFailures} consecutive failures — finalReply will fall back to a fresh card`);
199
+ }
200
+ }
201
+ });
202
+ }
203
+ /** True when streaming gave up on the placeholder (e.g. Feishu rejected too many
204
+ * consecutive edits). The owner should send the final reply as a fresh card. */
205
+ isPlaceholderAbandoned() {
206
+ return this.placeholderAbandoned;
207
+ }
208
+ async flushPreviewEdits() {
209
+ if (!this.canEditMessages)
210
+ return;
211
+ if (this.flushTimer) {
212
+ clearTimeout(this.flushTimer);
213
+ this.flushTimer = null;
214
+ }
215
+ if (this.editCount > 0 || this.latestText.trim() || this.latestThinking.trim() || this.latestActivity.trim()) {
216
+ this.queuePreviewEdit(true);
217
+ }
218
+ await this.editChain.catch(() => { });
219
+ }
220
+ }