pikiloom 0.4.26 → 0.4.27

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 (155) hide show
  1. package/dashboard/dist/assets/{AgentTab-BNJ73P0m.js → AgentTab-DIP-y4dl.js} +1 -1
  2. package/dashboard/dist/assets/ConnectionModal-BCfvhkiI.js +1 -0
  3. package/dashboard/dist/assets/{DirBrowser-D-GT-G-w.js → DirBrowser-Dot7ua9-.js} +1 -1
  4. package/dashboard/dist/assets/{ExtensionsTab-BrJMulJR.js → ExtensionsTab-GioMx4S6.js} +1 -1
  5. package/dashboard/dist/assets/{IMAccessTab-auPnx468.js → IMAccessTab-C9_r8jJP.js} +1 -1
  6. package/dashboard/dist/assets/{Modal-lYfO9m-I.js → Modal-M1AP1sgR.js} +1 -1
  7. package/dashboard/dist/assets/{Modals-PRdWOIY9.js → Modals-CCm3UUfJ.js} +1 -1
  8. package/dashboard/dist/assets/{Select-BhbU-rll.js → Select-YubDM16P.js} +1 -1
  9. package/dashboard/dist/assets/{SessionPanel-_0dLWvEb.js → SessionPanel-Cnv9o96n.js} +1 -1
  10. package/dashboard/dist/assets/SystemTab-VgNivIe3.js +1 -0
  11. package/dashboard/dist/assets/index-BY7l20bi.js +23 -0
  12. package/dashboard/dist/assets/index-BlqvzDUL.js +3 -0
  13. package/dashboard/dist/assets/index-CXIN3nTr.css +1 -0
  14. package/dashboard/dist/assets/{shared-C5uXvhZJ.js → shared-C0SAInnT.js} +1 -1
  15. package/dashboard/dist/index.html +2 -2
  16. package/dist/agent/acp-client.js +0 -59
  17. package/dist/agent/artifacts.js +1 -38
  18. package/dist/agent/auto-update.js +2 -78
  19. package/dist/agent/await-resume.js +0 -20
  20. package/dist/agent/cli/auth.js +3 -51
  21. package/dist/agent/cli/catalog.js +0 -3
  22. package/dist/agent/cli/detector.js +1 -19
  23. package/dist/agent/cli/index.js +0 -3
  24. package/dist/agent/cli/registry.js +0 -25
  25. package/dist/agent/driver.js +0 -14
  26. package/dist/agent/drivers/claude-tui.js +2 -769
  27. package/dist/agent/drivers/claude.js +12 -631
  28. package/dist/agent/drivers/codex.js +11 -162
  29. package/dist/agent/drivers/gemini.js +6 -114
  30. package/dist/agent/drivers/hermes.js +0 -128
  31. package/dist/agent/goal.js +0 -45
  32. package/dist/agent/handover.js +1 -46
  33. package/dist/agent/images.js +7 -133
  34. package/dist/agent/index.js +1 -29
  35. package/dist/agent/mcp/bridge.js +1 -197
  36. package/dist/agent/mcp/extensions.js +5 -84
  37. package/dist/agent/mcp/oauth.js +2 -69
  38. package/dist/agent/mcp/registry.js +2 -33
  39. package/dist/agent/mcp/session-server.js +2 -45
  40. package/dist/agent/mcp/tools/ask-user.js +0 -6
  41. package/dist/agent/mcp/tools/await-resume.js +0 -15
  42. package/dist/agent/mcp/tools/goal.js +0 -25
  43. package/dist/agent/mcp/tools/types.js +0 -5
  44. package/dist/agent/mcp/tools/workspace.js +0 -23
  45. package/dist/agent/npm.js +0 -14
  46. package/dist/agent/session.js +86 -196
  47. package/dist/agent/skill-installer.js +3 -70
  48. package/dist/agent/skills.js +0 -43
  49. package/dist/agent/stream.js +5 -148
  50. package/dist/agent/types.js +0 -12
  51. package/dist/agent/utils.js +2 -120
  52. package/dist/bot/bot.js +3 -468
  53. package/dist/bot/command-ui.js +0 -45
  54. package/dist/bot/commands.js +0 -51
  55. package/dist/bot/headless-bot.js +0 -21
  56. package/dist/bot/host.js +1 -5
  57. package/dist/bot/human-loop.js +0 -8
  58. package/dist/bot/menu.js +0 -3
  59. package/dist/bot/orchestration.js +0 -3
  60. package/dist/bot/render-shared.js +0 -63
  61. package/dist/bot/session-hub.js +14 -92
  62. package/dist/bot/session-status.js +1 -5
  63. package/dist/bot/streaming.js +0 -53
  64. package/dist/browser-profile.js +0 -51
  65. package/dist/browser-supervisor.js +0 -75
  66. package/dist/catalog/cli-tools.js +0 -53
  67. package/dist/catalog/index.js +0 -18
  68. package/dist/catalog/local-models.js +0 -18
  69. package/dist/catalog/mcp-servers.js +0 -35
  70. package/dist/catalog/skill-repos.js +0 -32
  71. package/dist/channels/base.js +0 -19
  72. package/dist/channels/dingtalk/bot.js +0 -3
  73. package/dist/channels/dingtalk/channel.js +0 -33
  74. package/dist/channels/discord/bot.js +0 -7
  75. package/dist/channels/discord/channel.js +0 -22
  76. package/dist/channels/feishu/bot.js +0 -71
  77. package/dist/channels/feishu/channel.js +0 -78
  78. package/dist/channels/feishu/markdown.js +0 -5
  79. package/dist/channels/feishu/render.js +0 -45
  80. package/dist/channels/health.js +0 -46
  81. package/dist/channels/slack/bot.js +0 -7
  82. package/dist/channels/slack/channel.js +1 -27
  83. package/dist/channels/states.js +0 -3
  84. package/dist/channels/telegram/bot.js +0 -60
  85. package/dist/channels/telegram/channel.js +2 -99
  86. package/dist/channels/telegram/directory.js +0 -3
  87. package/dist/channels/telegram/live-preview.js +0 -8
  88. package/dist/channels/telegram/render.js +0 -30
  89. package/dist/channels/wecom/bot.js +0 -3
  90. package/dist/channels/wecom/channel.js +0 -58
  91. package/dist/channels/weixin/api.js +0 -9
  92. package/dist/channels/weixin/bot.js +0 -88
  93. package/dist/channels/weixin/channel.js +0 -6
  94. package/dist/cli/autostart.js +1 -44
  95. package/dist/cli/channel-supervisor.js +1 -59
  96. package/dist/cli/channels.js +0 -6
  97. package/dist/cli/main.js +1 -145
  98. package/dist/cli/onboarding.js +0 -6
  99. package/dist/cli/run.js +0 -11
  100. package/dist/cli/setup-wizard.js +0 -3
  101. package/dist/core/config/runtime-config.js +0 -38
  102. package/dist/core/config/user-config.js +0 -63
  103. package/dist/core/config/validation.js +0 -9
  104. package/dist/core/constants.js +0 -268
  105. package/dist/core/git.js +0 -31
  106. package/dist/core/legacy-compat.js +1 -28
  107. package/dist/core/logging.js +0 -3
  108. package/dist/core/platform.js +0 -28
  109. package/dist/core/process-control.js +0 -8
  110. package/dist/core/secrets/index.js +0 -17
  111. package/dist/core/secrets/inline-seal.js +0 -7
  112. package/dist/core/secrets/ref.js +0 -19
  113. package/dist/core/secrets/resolver.js +0 -8
  114. package/dist/core/secrets/store.js +1 -12
  115. package/dist/core/utils.js +1 -37
  116. package/dist/core/version.js +0 -3
  117. package/dist/dashboard/platform.js +1 -35
  118. package/dist/dashboard/routes/agents.js +2 -71
  119. package/dist/dashboard/routes/cli.js +0 -27
  120. package/dist/dashboard/routes/config.js +0 -54
  121. package/dist/dashboard/routes/extensions.js +0 -132
  122. package/dist/dashboard/routes/local-models.js +0 -68
  123. package/dist/dashboard/routes/models.js +0 -46
  124. package/dist/dashboard/routes/sessions.js +0 -100
  125. package/dist/dashboard/runtime.js +1 -58
  126. package/dist/dashboard/server.js +0 -49
  127. package/dist/dashboard/session-control.js +2 -69
  128. package/dist/model/catalog.js +1 -17
  129. package/dist/model/index.js +0 -15
  130. package/dist/model/injector.js +1 -181
  131. package/dist/model/provider-models.js +1 -48
  132. package/dist/model/responses-bridge.js +3 -45
  133. package/dist/model/store.js +0 -17
  134. package/dist/model/types.js +0 -12
  135. package/dist/model/validation.js +1 -18
  136. package/dist/pikichannel/adapter-pikiloom.js +0 -31
  137. package/dist/pikichannel/code.js +0 -12
  138. package/dist/pikichannel/codec.js +0 -14
  139. package/dist/pikichannel/host.js +4 -33
  140. package/dist/pikichannel/protocol.js +0 -52
  141. package/dist/pikichannel/rendezvous-broker.js +5 -26
  142. package/dist/pikichannel/rendezvous-host.js +3 -16
  143. package/dist/pikichannel/server.js +6 -55
  144. package/dist/pikichannel/transport.js +0 -25
  145. package/dist/pikichannel/transports/webrtc-host.js +0 -18
  146. package/dist/pikichannel/transports/webrtc-shared.js +4 -34
  147. package/dist/pikichannel/transports/websocket-host.js +1 -14
  148. package/dist/pikichannel/turn.js +2 -69
  149. package/dist/pikichannel/web/sdk.js +14 -71
  150. package/package.json +1 -1
  151. package/dashboard/dist/assets/ConnectionModal-Dt05H2f9.js +0 -1
  152. package/dashboard/dist/assets/SystemTab-CN_QrRKt.js +0 -1
  153. package/dashboard/dist/assets/index-CP3LYJR2.js +0 -23
  154. package/dashboard/dist/assets/index-C_neLnaO.js +0 -3
  155. package/dashboard/dist/assets/index-CyCspgtc.css +0 -1
@@ -1,27 +1,7 @@
1
- /**
2
- * session-hub.ts — Unified session management service.
3
- *
4
- * THE canonical interface for all session operations across pikiloom.
5
- * Upper-layer code (bot, dashboard, CLI) should import session functions
6
- * from here, not from code-agent.ts directly.
7
- *
8
- * Responsibilities:
9
- * - Cross-agent / workspace-scoped session queries
10
- * - Session metadata management (status, notes, links, classification)
11
- * - Migration, export/import orchestration
12
- * - Workspace registry (delegates to user-config)
13
- */
14
1
  import path from 'node:path';
15
- import { getSessions as _getSessions, getSessionTail as _getSessionTail, getSessionMessages as _getSessionMessages, classifySession as _classifySession, deriveUserStatus as _deriveStatusFromOutcome, exportSession as _exportSession, importSession as _importSession, findPikiloomSession, updateSessionMeta, deleteAgentSession as _deleteAgentSession, collapseSkillPrompt, } from '../agent/index.js';
2
+ import { getSessions as _getSessions, getSessionTail as _getSessionTail, getSessionMessages as _getSessionMessages, classifySession as _classifySession, deriveUserStatus as _deriveStatusFromOutcome, exportSession as _exportSession, importSession as _importSession, findPikiloomSession, updateSessionMeta, resolveCanonicalSessionId, getSessionPromotions, deleteAgentSession as _deleteAgentSession, collapseSkillPrompt, } from '../agent/index.js';
16
3
  import { allDriverIds, hasDriver } from '../agent/driver.js';
17
4
  import { loadWorkspaces, addWorkspace, removeWorkspace, renameWorkspace, reorderWorkspaces, updateWorkspace, findWorkspace, } from '../core/config/user-config.js';
18
- // ---------------------------------------------------------------------------
19
- // Resolve user status
20
- // ---------------------------------------------------------------------------
21
- /**
22
- * Compute the effective user status for a session.
23
- * Priority: explicit userStatus > derived from classification > inbox.
24
- */
25
5
  export function resolveUserStatus(session) {
26
6
  if (session.userStatus)
27
7
  return session.userStatus;
@@ -29,21 +9,12 @@ export function resolveUserStatus(session) {
29
9
  return _deriveStatusFromOutcome(session.classification.outcome);
30
10
  return 'inbox';
31
11
  }
32
- // ---------------------------------------------------------------------------
33
- // Unified session query
34
- // ---------------------------------------------------------------------------
35
12
  function normalizeAgents(agent) {
36
13
  if (!agent)
37
14
  return allDriverIds().filter(a => hasDriver(a));
38
15
  const list = Array.isArray(agent) ? agent : [agent];
39
16
  return list.filter(a => hasDriver(a));
40
17
  }
41
- /**
42
- * Query sessions — the single entry point for all session listing.
43
- *
44
- * Handles single-agent, multi-agent, and all-agent queries with optional
45
- * status filtering and limits. Returns workspace-enriched results.
46
- */
47
18
  export async function querySessions(opts) {
48
19
  const resolvedWorkdir = path.resolve(opts.workdir);
49
20
  const ws = findWorkspace(resolvedWorkdir);
@@ -64,22 +35,18 @@ export async function querySessions(opts) {
64
35
  allSessions.push({ ...session, workspaceName });
65
36
  }
66
37
  }
67
- // Sort by most recent activity
68
38
  allSessions.sort((a, b) => {
69
39
  const aTime = a.runUpdatedAt || a.createdAt || '';
70
40
  const bTime = b.runUpdatedAt || b.createdAt || '';
71
41
  return Date.parse(bTime) - Date.parse(aTime);
72
42
  });
73
- // Filter by userStatus
74
43
  if (opts.userStatus?.length) {
75
44
  const allowed = new Set(opts.userStatus);
76
45
  allSessions = allSessions.filter(s => allowed.has(resolveUserStatus(s)));
77
46
  }
78
- // Apply limit
79
47
  if (opts.limit && opts.limit > 0) {
80
48
  allSessions = allSessions.slice(0, opts.limit);
81
49
  }
82
- // Count statuses
83
50
  const statusCounts = { inbox: 0, active: 0, review: 0, done: 0, parked: 0, unknown: 0 };
84
51
  for (const s of allSessions) {
85
52
  const status = resolveUserStatus(s);
@@ -93,22 +60,14 @@ export async function querySessions(opts) {
93
60
  statusCounts: statusCounts,
94
61
  total: allSessions.length,
95
62
  errors,
63
+ promotions: getSessionPromotions(resolvedWorkdir),
96
64
  };
97
65
  }
98
- // ---------------------------------------------------------------------------
99
- // Session detail queries
100
- // ---------------------------------------------------------------------------
101
66
  const IMAGE_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp', '.svg']);
102
67
  const MIME_BY_EXT = {
103
68
  '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg',
104
69
  '.gif': 'image/gif', '.webp': 'image/webp', '.bmp': 'image/bmp', '.svg': 'image/svg+xml',
105
70
  };
106
- /** Build image MessageBlocks from a session record's `lastUserAttachments`
107
- * (relative paths under `workspacePath`). Used by fallback paths so the
108
- * dashboard can still render the user's image bubble while the agent CLI
109
- * has not yet flushed the turn to its own session file. Non-image
110
- * attachments are skipped — the fallback is text-first and doesn't try to
111
- * reconstruct generic file references. */
112
71
  function imageBlocksFromManagedRecord(record) {
113
72
  const attachments = record.lastUserAttachments;
114
73
  if (!attachments?.length)
@@ -121,8 +80,6 @@ function imageBlocksFromManagedRecord(record) {
121
80
  const abs = path.isAbsolute(rel) ? rel : path.join(record.workspacePath, rel);
122
81
  blocks.push({
123
82
  type: 'image',
124
- // `file://` sentinel — `rewriteAttachmentBlocksForTransport` (dashboard
125
- // response layer) converts it to a proper /attachment URL.
126
83
  content: `file://${abs}`,
127
84
  imagePath: abs,
128
85
  imageMime: MIME_BY_EXT[ext] || 'application/octet-stream',
@@ -166,23 +123,20 @@ function managedFallbackContent(opts) {
166
123
  return null;
167
124
  return { messages, richMessages };
168
125
  }
169
- /** Get recent messages from a session (tail). */
170
126
  export async function querySessionTail(opts) {
171
- const result = await _getSessionTail(opts);
127
+ const opts2 = withCanonicalSessionId(opts);
128
+ const result = await _getSessionTail(opts2);
172
129
  if (!result.ok || !result.messages.length) {
173
- const fallback = tailFallbackFromManagedRecord(opts);
130
+ const fallback = tailFallbackFromManagedRecord(opts2);
174
131
  if (fallback)
175
132
  return fallback;
176
133
  }
177
134
  return result;
178
135
  }
179
- /**
180
- * Replace canonical skill-execution expansions in a user message with the
181
- * `/skillname` shorthand the user originally typed. The expanded text is what
182
- * the agent CLI consumed and persisted; we collapse on read so the dashboard
183
- * chat shows the slash command instead of the long instruction we synthesized
184
- * for dispatch. Non-user messages and non-skill prompts pass through unchanged.
185
- */
136
+ function withCanonicalSessionId(opts) {
137
+ const canonical = resolveCanonicalSessionId(opts.workdir, opts.agent, opts.sessionId);
138
+ return canonical === opts.sessionId ? opts : { ...opts, sessionId: canonical };
139
+ }
186
140
  function collapseSkillPromptsInResult(result) {
187
141
  if (!result.ok)
188
142
  return result;
@@ -198,9 +152,6 @@ function collapseSkillPromptsInResult(result) {
198
152
  const collapsed = collapseSkillPrompt(m.text);
199
153
  if (!collapsed)
200
154
  return m;
201
- // The user's text content lives in one or more `text` blocks; collapse any
202
- // whose individual content also matches the expansion. Non-text blocks
203
- // (images, attachments) pass through untouched.
204
155
  const blocks = m.blocks.map(b => {
205
156
  if (b.type !== 'text')
206
157
  return b;
@@ -211,22 +162,20 @@ function collapseSkillPromptsInResult(result) {
211
162
  });
212
163
  return { ...result, messages, richMessages };
213
164
  }
214
- /** Get full session messages (with optional turn filtering). */
215
165
  export async function querySessionMessages(opts) {
216
- const result = await _getSessionMessages(opts);
166
+ const opts2 = withCanonicalSessionId(opts);
167
+ const result = await _getSessionMessages(opts2);
217
168
  if (!result.ok || !result.messages.length) {
218
169
  const fb = managedFallbackContent({
219
- agent: opts.agent,
220
- sessionId: opts.sessionId,
221
- workdir: opts.workdir,
170
+ agent: opts2.agent,
171
+ sessionId: opts2.sessionId,
172
+ workdir: opts2.workdir,
222
173
  });
223
174
  if (fb) {
224
175
  const totalTurns = fb.messages.filter(m => m.role === 'user').length;
225
176
  return collapseSkillPromptsInResult({
226
177
  ok: true,
227
178
  messages: fb.messages.map(m => ({ role: m.role, text: m.text })),
228
- // Always emit richMessages so the dashboard can render image blocks
229
- // for the first user turn while the agent CLI is still spinning up.
230
179
  richMessages: fb.richMessages,
231
180
  totalTurns,
232
181
  error: null,
@@ -235,16 +184,10 @@ export async function querySessionMessages(opts) {
235
184
  }
236
185
  return collapseSkillPromptsInResult(result);
237
186
  }
238
- // ---------------------------------------------------------------------------
239
- // Workspace overviews
240
- // ---------------------------------------------------------------------------
241
- /** Overview of all registered workspaces — designed for dashboard sidebar. */
242
187
  export async function getWorkspaceOverviews() {
243
188
  const workspaces = loadWorkspaces();
244
189
  const agents = allDriverIds().filter(a => hasDriver(a));
245
190
  return Promise.all(workspaces.map(async (ws) => {
246
- // Fan the agents out in parallel — each _getSessions is independent I/O, and
247
- // running them serially made one slow agent stall the whole workspace card.
248
191
  const summaries = await Promise.all(agents.map(async (agent) => {
249
192
  try {
250
193
  const result = await _getSessions({ agent, workdir: ws.path });
@@ -279,22 +222,12 @@ export async function getWorkspaceOverviews() {
279
222
  return { workspace: ws, attentionCount, agentSummary, lastActivityAt };
280
223
  }));
281
224
  }
282
- // ---------------------------------------------------------------------------
283
- // Session metadata
284
- // ---------------------------------------------------------------------------
285
- /** Update session metadata (status, note, classification, migration links). */
286
225
  export function updateSession(workdir, agent, sessionId, patch) {
287
226
  return updateSessionMeta(workdir, agent, sessionId, patch);
288
227
  }
289
- /**
290
- * Delete a session. Re-exports the agent-layer primitive so dashboard routes
291
- * stay in the bot/ layer for layering consistency. See
292
- * {@link DeleteAgentSessionOpts}.
293
- */
294
228
  export function deleteSession(opts) {
295
229
  return _deleteAgentSession(opts);
296
230
  }
297
- /** Link two sessions together (bidirectional). */
298
231
  export function linkSessions(workdir, a, b) {
299
232
  const updatedA = updateSessionMeta(workdir, a.agent, a.sessionId, {
300
233
  addLink: { agent: b.agent, sessionId: b.sessionId },
@@ -304,23 +237,15 @@ export function linkSessions(workdir, a, b) {
304
237
  });
305
238
  return updatedA || updatedB;
306
239
  }
307
- // ---------------------------------------------------------------------------
308
- // Classification
309
- // ---------------------------------------------------------------------------
310
- /** Auto-classify a session based on stream result. */
311
240
  export function classifySession(result) {
312
241
  return _classifySession(result);
313
242
  }
314
- // ---------------------------------------------------------------------------
315
- // Export / Import / Migration
316
- // ---------------------------------------------------------------------------
317
243
  export function exportSession(opts) {
318
244
  return _exportSession(opts);
319
245
  }
320
246
  export function importSession(opts) {
321
247
  return _importSession(opts);
322
248
  }
323
- /** Build migration context from source session for injection into target agent. */
324
249
  export async function buildMigrationContext(opts) {
325
250
  try {
326
251
  const messagesResult = await _getSessionMessages({
@@ -355,7 +280,4 @@ export async function buildMigrationContext(opts) {
355
280
  return { ok: false, contextInjected: '', messageCount: 0, error: e.message };
356
281
  }
357
282
  }
358
- // ---------------------------------------------------------------------------
359
- // Workspace registry (delegates to user-config)
360
- // ---------------------------------------------------------------------------
361
283
  export { loadWorkspaces, addWorkspace, removeWorkspace, renameWorkspace, reorderWorkspaces, updateWorkspace, findWorkspace };
@@ -1,9 +1,5 @@
1
- /**
2
- * Runtime session status helpers for dashboard polling.
3
- */
4
1
  import { isRunningSessionStale } from '../agent/index.js';
5
- /** Age threshold used when the owning PID is unknown or unverifiable. */
6
- const STALE_RUNNING_AGE_MS = 30 * 60_000; // 30 minutes
2
+ const STALE_RUNNING_AGE_MS = 30 * 60_000;
7
3
  function getSessionRuntime(bot, session) {
8
4
  const sessionId = session.sessionId || null;
9
5
  if (!sessionId)
@@ -1,21 +1,3 @@
1
- /**
2
- * Stream preview parsing helpers for live message updates.
3
- *
4
- * Used by IM channels (Telegram / Feishu / WeChat). The dashboard reads the
5
- * raw `StreamSnapshot.activity` string directly and renders independently —
6
- * none of the compaction in this file flows to it. Keep changes that improve
7
- * IM legibility free to touch this surface; if a change would also alter what
8
- * the dashboard ultimately shows, do it in the dashboard layer instead.
9
- */
10
- /**
11
- * Shrink absolute paths that bloat IM cards on small screens. Any absolute
12
- * path with 4+ segments collapses to `…/<last-two-segments>` so directory
13
- * context is kept while the leading `/Users/…/long/project/root/` noise is
14
- * dropped. Length-based gating made the output inconsistent — borderline
15
- * paths (47 chars) sat next to compacted ones (52 chars), making the activity
16
- * list look broken. Relative paths and short paths (<4 segments, e.g.
17
- * `~/foo`, `/tmp/x.log`) are passed through unchanged.
18
- */
19
1
  function compactActivityPath(token) {
20
2
  if (!token.includes('/'))
21
3
  return token;
@@ -26,9 +8,6 @@ function compactActivityPath(token) {
26
8
  return `…/${tail}`;
27
9
  }
28
10
  function compactPathsInActivityLine(line) {
29
- // Conservative: only target obviously absolute paths starting with `/` or
30
- // `~/`. Inline file:line references (`foo/bar.ts:42`) keep their structure
31
- // since the prefix is short anyway.
32
11
  return line.replace(/(^|\s)([~/][^\s]+)/g, (_match, lead, raw) => {
33
12
  const trailingPunct = raw.match(/[)\],.;!?]+$/)?.[0] ?? '';
34
13
  const path = trailingPunct ? raw.slice(0, -trailingPunct.length) : raw;
@@ -36,11 +15,6 @@ function compactPathsInActivityLine(line) {
36
15
  });
37
16
  }
38
17
  const TOOL_DONE_RE = /^(.+?)\s+(done|failed)$/;
39
- /** "X -> Y" pattern produced by `summarizeClaudeToolResult` for tools whose
40
- * result has body text (im_ask_user, ToolSearch, MCP tools, …). The Y half
41
- * is the tool's response — capturing it lets us collapse the pre-event
42
- * (`Ask user: q`) and the post-event (`Ask user: q -> A: …`) into a single
43
- * line in the narrative instead of leaving both sitting around. */
44
18
  const TOOL_ARROW_RE = /^(.+?)\s*->\s*(.+)$/;
45
19
  const INJECTED_PROMPT_MARKERS = [
46
20
  '\n[Session Workspace]',
@@ -96,10 +70,6 @@ export function parseActivitySummary(activity) {
96
70
  let activeCommands = 0;
97
71
  let completedCommands = 0;
98
72
  const activeClaudeShells = new Map();
99
- // Track narrative indices keyed by their normalized start text so a later
100
- // "X done" / "X failed" line collapses the prior "X" entry instead of
101
- // appending. Avoids the double-line spam in IM cards where each tool call
102
- // shows both its in-progress and completed line.
103
73
  const pendingNarrative = new Map();
104
74
  const pushPending = (key, index) => {
105
75
  const slot = pendingNarrative.get(key);
@@ -165,10 +135,6 @@ export function parseActivitySummary(activity) {
165
135
  failedCommands++;
166
136
  continue;
167
137
  }
168
- // Pair "X" → "X done"/"X failed": rewrite the prior in-progress entry in
169
- // place rather than appending a second line. Falls back to a plain append
170
- // when no matching start exists (e.g. the start line was trimmed off by a
171
- // history window earlier in the run).
172
138
  const doneMatch = line.match(TOOL_DONE_RE);
173
139
  if (doneMatch) {
174
140
  const baseKey = doneMatch[1].trim();
@@ -181,11 +147,6 @@ export function parseActivitySummary(activity) {
181
147
  narrative.push(status === 'failed' ? `${baseKey} failed` : baseKey);
182
148
  continue;
183
149
  }
184
- // Pair "X" → "X -> Y" (im_ask_user, ToolSearch, MCP tools, … — any tool
185
- // whose summarizeClaudeToolResult fell into the arrow branch). Without
186
- // this, the IM card shows the question and the answered form side by
187
- // side. We replace the pending entry with the full arrow form so the
188
- // narrative carries the answer in a single line.
189
150
  const arrowMatch = line.match(TOOL_ARROW_RE);
190
151
  if (arrowMatch) {
191
152
  const baseKey = arrowMatch[1].trim();
@@ -203,14 +164,6 @@ export function parseActivitySummary(activity) {
203
164
  }
204
165
  return { narrative: collapseConsecutiveDuplicates(narrative), failedCommands, completedCommands, activeCommands };
205
166
  }
206
- /**
207
- * Walk the narrative and collapse runs of identical lines into `X ×N`. The
208
- * input narrative often contains repeats when the model calls the same tool
209
- * multiple times in a row (two consecutive `Edit README.md`, three `Read X`,
210
- * …) — listing them N times wastes IM card real estate without adding
211
- * information. Non-adjacent duplicates are preserved to keep the temporal
212
- * order intact.
213
- */
214
167
  function collapseConsecutiveDuplicates(narrative) {
215
168
  const out = [];
216
169
  let runStart = -1;
@@ -282,15 +235,9 @@ export function renderPlanForPreview(plan) {
282
235
  const total = plan.steps.length;
283
236
  const completed = plan.steps.filter(step => step.status === 'completed').length;
284
237
  const lines = [`Plan ${completed}/${total}`];
285
- // Show the most recent / currently-active slice of the plan. Live viewers
286
- // care about the in-progress + upcoming steps; the dozen already-completed
287
- // ones at the top of the list are just visual ballast (the `completed/total`
288
- // header already conveys the overall progress).
289
238
  const WINDOW = 4;
290
239
  let startIdx = 0;
291
240
  if (total > WINDOW) {
292
- // Center the window on the in-progress step when one exists; otherwise
293
- // anchor to the tail so the next pending steps are visible.
294
241
  const inProgressIdx = plan.steps.findIndex(step => step.status === 'inProgress');
295
242
  const anchor = inProgressIdx >= 0 ? inProgressIdx : total - 1;
296
243
  startIdx = Math.max(0, Math.min(total - WINDOW, anchor - Math.floor(WINDOW / 2)));
@@ -1,6 +1,3 @@
1
- /**
2
- * Managed browser profile directory for Playwright integration.
3
- */
4
1
  import fs from 'node:fs';
5
2
  import os from 'node:os';
6
3
  import path from 'node:path';
@@ -11,14 +8,6 @@ const MANAGED_BROWSER_SETUP_STATE_FILENAME = 'managed-browser-setup.json';
11
8
  const MANAGED_BROWSER_SHUTDOWN_TIMEOUT_MS = 5_000;
12
9
  const MANAGED_BROWSER_SHUTDOWN_POLL_MS = 100;
13
10
  const require = createRequire(import.meta.url);
14
- /**
15
- * Read Chrome's `DevToolsActivePort` file from the managed profile dir.
16
- * Chrome writes this file when launched with `--remote-debugging-port=<n>`
17
- * (including `=0`, which lets the OS assign a free port). Format is two lines:
18
- * <port>
19
- * /devtools/browser/<id>
20
- * We use it to discover the actual port rather than hard-coding one.
21
- */
22
11
  function readDevToolsActivePort(profileDir) {
23
12
  try {
24
13
  const raw = fs.readFileSync(path.join(profileDir, 'DevToolsActivePort'), 'utf8');
@@ -40,14 +29,6 @@ function normalizeBrowserCdpEndpoint(endpoint) {
40
29
  return '';
41
30
  return value.replace(/\/+$/, '');
42
31
  }
43
- /**
44
- * The external CDP endpoint configured via {@link PIKILOOM_BROWSER_CDP_URL_ENV},
45
- * normalized (trailing slashes stripped, blank → null). Single source of truth
46
- * for the remote-browser override: when this returns a value, every browser
47
- * codepath attaches to it and pikiloom never launches, probes, or kills a local
48
- * Chrome. Read from `env` (defaults to `process.env`) so callers can inject it
49
- * in tests.
50
- */
51
32
  export function getConfiguredRemoteCdpUrl(env = process.env) {
52
33
  const raw = String(env[PIKILOOM_BROWSER_CDP_URL_ENV] || '').trim();
53
34
  if (!raw)
@@ -162,16 +143,6 @@ export function ensureManagedBrowserProfileDir() {
162
143
  function getPlaywrightMcpConfigPath(outputDir) {
163
144
  return path.join(outputDir, 'playwright-mcp-config.json');
164
145
  }
165
- /**
166
- * Write the playwright/mcp config JSON used by every spawn. Currently it just
167
- * strips Playwright's default `--disable-blink-features=AutomationControlled`
168
- * arg so Chrome doesn't show its "unsupported command-line flag" infobar.
169
- * Playwright adds that flag to hide `navigator.webdriver`; removing it makes
170
- * the managed browser look like a normal session, which is what we want for
171
- * pikiloom's logged-in automation profile.
172
- *
173
- * Idempotent: rewrites only when the content drifts.
174
- */
175
146
  export function ensurePlaywrightMcpConfigFile(outputDir = path.dirname(getManagedBrowserProfileDir())) {
176
147
  const configPath = getPlaywrightMcpConfigPath(outputDir);
177
148
  const desired = JSON.stringify({
@@ -186,7 +157,6 @@ export function ensurePlaywrightMcpConfigFile(outputDir = path.dirname(getManage
186
157
  return configPath;
187
158
  }
188
159
  catch {
189
- // File missing or unreadable — fall through to write.
190
160
  }
191
161
  fs.mkdirSync(outputDir, { recursive: true });
192
162
  fs.writeFileSync(configPath, desired, 'utf8');
@@ -240,9 +210,6 @@ export function getManagedBrowserLaunchArgs(profileDir = getManagedBrowserProfil
240
210
  : ['--start-maximized'];
241
211
  return [
242
212
  `--user-data-dir=${profileDir}`,
243
- // `=0` makes Chrome pick a free port and write it to <profileDir>/DevToolsActivePort,
244
- // which `readDevToolsActivePort` reads back. Hard-coding a port causes collisions
245
- // when another Chrome / debugger is already on it.
246
213
  '--remote-debugging-port=0',
247
214
  '--no-first-run',
248
215
  '--no-default-browser-check',
@@ -427,19 +394,6 @@ async function waitForManagedBrowserCdpEndpoint(profileDir, timeoutMs = 6_000) {
427
394
  }
428
395
  return resolveManagedBrowserCdpEndpoint(profileDir);
429
396
  }
430
- /**
431
- * Hard-reset the managed browser: SIGKILL every root pid (skipping the SIGTERM
432
- * grace window) and wipe Chrome's session-restore files so the relaunch comes
433
- * up with a fresh `about:blank` instead of replaying the tabs that triggered
434
- * the failure. Used as the reactive recovery path when an agent stream sees an
435
- * MCP browser error (Connection closed / Frame has been detached) indicating
436
- * Chrome's CDP layer is wedged; a normal `closeManagedBrowserProcesses`
437
- * round-trip won't help because it would restore the same poisoned tabs.
438
- *
439
- * Trade-off: any unrelated user-opened tabs are dropped. Acceptable here
440
- * because this is invoked only after observing a real failure — the user has
441
- * already lost time, and recovery beats perpetuating the wedge.
442
- */
443
397
  export async function forceCloseManagedBrowser(profileDir = getManagedBrowserProfileDir()) {
444
398
  const tracked = readManagedBrowserSetupState(profileDir);
445
399
  const candidates = new Set(findManagedBrowserRootPids(profileDir));
@@ -487,11 +441,6 @@ async function closeManagedBrowserProcesses(profileDir) {
487
441
  return closedPids;
488
442
  }
489
443
  export async function prepareManagedBrowserForAutomation(profileDir = getManagedBrowserProfileDir(), options = {}) {
490
- // CDP probe is the authoritative signal: if the managed Chrome answers on
491
- // its debug port, attach to it regardless of what the setup-state file or
492
- // the process table say. The ps-based running-state check is heuristic and
493
- // can be wrong after a daemon restart, which would otherwise kill the
494
- // user's live (and signed-in) browser.
495
444
  const reachableEndpoint = await resolveManagedBrowserCdpEndpoint(profileDir);
496
445
  if (reachableEndpoint) {
497
446
  return {
@@ -1,20 +1,3 @@
1
- /**
2
- * Process-level singleton supervisor for the managed browser.
3
- *
4
- * Owns the lifecycle decisions for the managed Chrome instance so that all
5
- * agent streams in this pikiloom process share one browser. Replaces the old
6
- * per-stream `prepareManagedBrowserForAutomation` call inside the MCP bridge,
7
- * which was relaunching Chrome at the start of every task.
8
- *
9
- * Three operations:
10
- * - probe(): non-launching health check; returns the current CDP endpoint
11
- * iff a managed Chrome is already reachable.
12
- * - ensure(): idempotent prepare with singleflight; launches Chrome only
13
- * when no healthy instance is reachable. Caches the result for re-use
14
- * across streams.
15
- * - invalidate(): drop the cache after a confirmed downstream failure
16
- * (e.g. CDP socket closed mid-stream).
17
- */
18
1
  import { forceCloseManagedBrowser, getConfiguredRemoteCdpUrl, getManagedBrowserProfileDir, prepareManagedBrowserForAutomation, } from './browser-profile.js';
19
2
  import { PIKILOOM_BROWSER_CDP_URL_ENV } from './core/constants.js';
20
3
  import { writeScopedLog } from './core/logging.js';
@@ -25,18 +8,7 @@ let inflight = null;
25
8
  function log(message, level = 'debug') {
26
9
  writeScopedLog('browser-supervisor', message, { level, stream: 'stderr' });
27
10
  }
28
- /**
29
- * The user-configured remote endpoint from {@link PIKILOOM_BROWSER_CDP_URL_ENV}.
30
- * When set, every supervisor codepath bypasses local Chrome launching. Aliased
31
- * to the shared `getConfiguredRemoteCdpUrl` so the bridge and supervisor read
32
- * the same normalized value.
33
- */
34
11
  const getRemoteCdpUrl = getConfiguredRemoteCdpUrl;
35
- /**
36
- * Snapshot for the remote-CDP path. The URL is taken on trust as configured —
37
- * health is verified by {@link pingCdpEndpoint} before caching, and by the
38
- * usual freshness check on every reuse.
39
- */
40
12
  async function snapshotRemote(remoteUrl, now) {
41
13
  if (cached?.cdpEndpoint === remoteUrl && now - cached.validatedAt < HEALTH_CACHE_MS) {
42
14
  return snapshotFromCache(cached);
@@ -69,21 +41,6 @@ async function fetchWithTimeout(url, timeoutMs) {
69
41
  clearTimeout(timer);
70
42
  }
71
43
  }
72
- /**
73
- * Two-stage health check.
74
- *
75
- * Stage 1 (`/json/version`) hits Chrome's static info handler — confirms the
76
- * process is alive and the debug HTTP server is bound. Stage 2 (`/json`) reads
77
- * from the target manager, exercising the CDP dispatch loop. Chrome can end up
78
- * in a state where stage 1 still answers but stage 2 hangs, typically after
79
- * many stale CDP sessions accumulate; treating stage-1-only responses as
80
- * healthy lets that state poison every subsequent agent run.
81
- *
82
- * Both stages share the same `CDP_PROBE_TIMEOUT_MS`. This probe does NOT catch
83
- * the case where stage 2 succeeds but the *first page target* is itself stuck
84
- * (e.g. a heavy SPA frozen mid-init); that has to be handled reactively from
85
- * the MCP-tool-error path.
86
- */
87
44
  async function pingCdpEndpoint(endpoint) {
88
45
  if (!endpoint)
89
46
  return false;
@@ -113,10 +70,6 @@ async function freshenCacheIfPossible(now) {
113
70
  cached = null;
114
71
  return null;
115
72
  }
116
- /**
117
- * Non-launching probe. Returns the current CDP endpoint iff a managed Chrome
118
- * is already reachable. Never starts a new Chrome process.
119
- */
120
73
  export async function probeManagedBrowser() {
121
74
  const remoteUrl = getRemoteCdpUrl();
122
75
  if (remoteUrl)
@@ -126,15 +79,6 @@ export async function probeManagedBrowser() {
126
79
  return fresh;
127
80
  return { cdpEndpoint: null, connectionMode: 'unavailable' };
128
81
  }
129
- /**
130
- * Idempotent prepare. Returns a healthy CDP endpoint, launching Chrome only
131
- * when no reachable managed instance is available. Concurrent callers share
132
- * one in-flight preparation promise (singleflight).
133
- *
134
- * When {@link PIKILOOM_BROWSER_CDP_URL_ENV} is set we skip the local-launch
135
- * branch entirely and just verify the remote endpoint — no `findChromeExecutable`
136
- * lookup, no SIGKILL of detected pids on restart.
137
- */
138
82
  export async function ensureManagedBrowser(opts = {}) {
139
83
  const { headless = false, force = false } = opts;
140
84
  const now = Date.now();
@@ -183,19 +127,6 @@ export async function ensureManagedBrowser(opts = {}) {
183
127
  const RESTART_COOLDOWN_MS = 30_000;
184
128
  let lastRestartAt = 0;
185
129
  let restartInflight = null;
186
- /**
187
- * Reactive recovery for a confirmed CDP-layer failure: drops the supervisor
188
- * cache, SIGKILLs Chrome, wipes session-restore. The next `ensureManagedBrowser`
189
- * will detect no CDP endpoint and relaunch Chrome cold.
190
- *
191
- * The current stream is not rescued — its MCP child has already lost stdio,
192
- * and Claude CLI's MCP client won't reconnect mid-tool-call. The benefit lands
193
- * on the next agent turn, which will attach to a fresh browser instead of
194
- * inheriting the wedged one.
195
- *
196
- * Cooldown-throttled and singleflight: a single agent run can spit many
197
- * "Connection closed" lines as the MCP child stays dead; we only act once.
198
- */
199
130
  export function restartManagedBrowser(reason) {
200
131
  const now = Date.now();
201
132
  if (restartInflight)
@@ -205,9 +136,6 @@ export function restartManagedBrowser(reason) {
205
136
  return Promise.resolve();
206
137
  }
207
138
  lastRestartAt = now;
208
- // Remote CDP path: we don't own the Chrome process (it's a sidecar / external
209
- // service), so don't SIGKILL anything — just drop the cache so the next
210
- // ensure re-probes the endpoint.
211
139
  const remoteUrl = getRemoteCdpUrl();
212
140
  if (remoteUrl) {
213
141
  log(`invalidating remote CDP cache (${remoteUrl}): ${reason}`, 'warn');
@@ -230,17 +158,14 @@ export function restartManagedBrowser(reason) {
230
158
  })();
231
159
  return restartInflight;
232
160
  }
233
- /** Drop any cached endpoint, e.g. after a confirmed CDP failure mid-stream. */
234
161
  export function invalidateManagedBrowser() {
235
162
  if (cached)
236
163
  log(`invalidating cached endpoint ${cached.cdpEndpoint}`);
237
164
  cached = null;
238
165
  }
239
- /** Synchronous accessor for the cached endpoint without any I/O. */
240
166
  export function getCachedManagedBrowserEndpoint() {
241
167
  return cached?.cdpEndpoint || null;
242
168
  }
243
- /** Test-only: reset module state. */
244
169
  export function _resetManagedBrowserSupervisor() {
245
170
  cached = null;
246
171
  inflight = null;