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.
- package/dashboard/dist/assets/{AgentTab-BNJ73P0m.js → AgentTab-DIP-y4dl.js} +1 -1
- package/dashboard/dist/assets/ConnectionModal-BCfvhkiI.js +1 -0
- package/dashboard/dist/assets/{DirBrowser-D-GT-G-w.js → DirBrowser-Dot7ua9-.js} +1 -1
- package/dashboard/dist/assets/{ExtensionsTab-BrJMulJR.js → ExtensionsTab-GioMx4S6.js} +1 -1
- package/dashboard/dist/assets/{IMAccessTab-auPnx468.js → IMAccessTab-C9_r8jJP.js} +1 -1
- package/dashboard/dist/assets/{Modal-lYfO9m-I.js → Modal-M1AP1sgR.js} +1 -1
- package/dashboard/dist/assets/{Modals-PRdWOIY9.js → Modals-CCm3UUfJ.js} +1 -1
- package/dashboard/dist/assets/{Select-BhbU-rll.js → Select-YubDM16P.js} +1 -1
- package/dashboard/dist/assets/{SessionPanel-_0dLWvEb.js → SessionPanel-Cnv9o96n.js} +1 -1
- package/dashboard/dist/assets/SystemTab-VgNivIe3.js +1 -0
- package/dashboard/dist/assets/index-BY7l20bi.js +23 -0
- package/dashboard/dist/assets/index-BlqvzDUL.js +3 -0
- package/dashboard/dist/assets/index-CXIN3nTr.css +1 -0
- package/dashboard/dist/assets/{shared-C5uXvhZJ.js → shared-C0SAInnT.js} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/dist/agent/acp-client.js +0 -59
- package/dist/agent/artifacts.js +1 -38
- package/dist/agent/auto-update.js +2 -78
- package/dist/agent/await-resume.js +0 -20
- package/dist/agent/cli/auth.js +3 -51
- package/dist/agent/cli/catalog.js +0 -3
- package/dist/agent/cli/detector.js +1 -19
- package/dist/agent/cli/index.js +0 -3
- package/dist/agent/cli/registry.js +0 -25
- package/dist/agent/driver.js +0 -14
- package/dist/agent/drivers/claude-tui.js +2 -769
- package/dist/agent/drivers/claude.js +12 -631
- package/dist/agent/drivers/codex.js +11 -162
- package/dist/agent/drivers/gemini.js +6 -114
- package/dist/agent/drivers/hermes.js +0 -128
- package/dist/agent/goal.js +0 -45
- package/dist/agent/handover.js +1 -46
- package/dist/agent/images.js +7 -133
- package/dist/agent/index.js +1 -29
- package/dist/agent/mcp/bridge.js +1 -197
- package/dist/agent/mcp/extensions.js +5 -84
- package/dist/agent/mcp/oauth.js +2 -69
- package/dist/agent/mcp/registry.js +2 -33
- package/dist/agent/mcp/session-server.js +2 -45
- package/dist/agent/mcp/tools/ask-user.js +0 -6
- package/dist/agent/mcp/tools/await-resume.js +0 -15
- package/dist/agent/mcp/tools/goal.js +0 -25
- package/dist/agent/mcp/tools/types.js +0 -5
- package/dist/agent/mcp/tools/workspace.js +0 -23
- package/dist/agent/npm.js +0 -14
- package/dist/agent/session.js +86 -196
- package/dist/agent/skill-installer.js +3 -70
- package/dist/agent/skills.js +0 -43
- package/dist/agent/stream.js +5 -148
- package/dist/agent/types.js +0 -12
- package/dist/agent/utils.js +2 -120
- package/dist/bot/bot.js +3 -468
- package/dist/bot/command-ui.js +0 -45
- package/dist/bot/commands.js +0 -51
- package/dist/bot/headless-bot.js +0 -21
- package/dist/bot/host.js +1 -5
- package/dist/bot/human-loop.js +0 -8
- package/dist/bot/menu.js +0 -3
- package/dist/bot/orchestration.js +0 -3
- package/dist/bot/render-shared.js +0 -63
- package/dist/bot/session-hub.js +14 -92
- package/dist/bot/session-status.js +1 -5
- package/dist/bot/streaming.js +0 -53
- package/dist/browser-profile.js +0 -51
- package/dist/browser-supervisor.js +0 -75
- package/dist/catalog/cli-tools.js +0 -53
- package/dist/catalog/index.js +0 -18
- package/dist/catalog/local-models.js +0 -18
- package/dist/catalog/mcp-servers.js +0 -35
- package/dist/catalog/skill-repos.js +0 -32
- package/dist/channels/base.js +0 -19
- package/dist/channels/dingtalk/bot.js +0 -3
- package/dist/channels/dingtalk/channel.js +0 -33
- package/dist/channels/discord/bot.js +0 -7
- package/dist/channels/discord/channel.js +0 -22
- package/dist/channels/feishu/bot.js +0 -71
- package/dist/channels/feishu/channel.js +0 -78
- package/dist/channels/feishu/markdown.js +0 -5
- package/dist/channels/feishu/render.js +0 -45
- package/dist/channels/health.js +0 -46
- package/dist/channels/slack/bot.js +0 -7
- package/dist/channels/slack/channel.js +1 -27
- package/dist/channels/states.js +0 -3
- package/dist/channels/telegram/bot.js +0 -60
- package/dist/channels/telegram/channel.js +2 -99
- package/dist/channels/telegram/directory.js +0 -3
- package/dist/channels/telegram/live-preview.js +0 -8
- package/dist/channels/telegram/render.js +0 -30
- package/dist/channels/wecom/bot.js +0 -3
- package/dist/channels/wecom/channel.js +0 -58
- package/dist/channels/weixin/api.js +0 -9
- package/dist/channels/weixin/bot.js +0 -88
- package/dist/channels/weixin/channel.js +0 -6
- package/dist/cli/autostart.js +1 -44
- package/dist/cli/channel-supervisor.js +1 -59
- package/dist/cli/channels.js +0 -6
- package/dist/cli/main.js +1 -145
- package/dist/cli/onboarding.js +0 -6
- package/dist/cli/run.js +0 -11
- package/dist/cli/setup-wizard.js +0 -3
- package/dist/core/config/runtime-config.js +0 -38
- package/dist/core/config/user-config.js +0 -63
- package/dist/core/config/validation.js +0 -9
- package/dist/core/constants.js +0 -268
- package/dist/core/git.js +0 -31
- package/dist/core/legacy-compat.js +1 -28
- package/dist/core/logging.js +0 -3
- package/dist/core/platform.js +0 -28
- package/dist/core/process-control.js +0 -8
- package/dist/core/secrets/index.js +0 -17
- package/dist/core/secrets/inline-seal.js +0 -7
- package/dist/core/secrets/ref.js +0 -19
- package/dist/core/secrets/resolver.js +0 -8
- package/dist/core/secrets/store.js +1 -12
- package/dist/core/utils.js +1 -37
- package/dist/core/version.js +0 -3
- package/dist/dashboard/platform.js +1 -35
- package/dist/dashboard/routes/agents.js +2 -71
- package/dist/dashboard/routes/cli.js +0 -27
- package/dist/dashboard/routes/config.js +0 -54
- package/dist/dashboard/routes/extensions.js +0 -132
- package/dist/dashboard/routes/local-models.js +0 -68
- package/dist/dashboard/routes/models.js +0 -46
- package/dist/dashboard/routes/sessions.js +0 -100
- package/dist/dashboard/runtime.js +1 -58
- package/dist/dashboard/server.js +0 -49
- package/dist/dashboard/session-control.js +2 -69
- package/dist/model/catalog.js +1 -17
- package/dist/model/index.js +0 -15
- package/dist/model/injector.js +1 -181
- package/dist/model/provider-models.js +1 -48
- package/dist/model/responses-bridge.js +3 -45
- package/dist/model/store.js +0 -17
- package/dist/model/types.js +0 -12
- package/dist/model/validation.js +1 -18
- package/dist/pikichannel/adapter-pikiloom.js +0 -31
- package/dist/pikichannel/code.js +0 -12
- package/dist/pikichannel/codec.js +0 -14
- package/dist/pikichannel/host.js +4 -33
- package/dist/pikichannel/protocol.js +0 -52
- package/dist/pikichannel/rendezvous-broker.js +5 -26
- package/dist/pikichannel/rendezvous-host.js +3 -16
- package/dist/pikichannel/server.js +6 -55
- package/dist/pikichannel/transport.js +0 -25
- package/dist/pikichannel/transports/webrtc-host.js +0 -18
- package/dist/pikichannel/transports/webrtc-shared.js +4 -34
- package/dist/pikichannel/transports/websocket-host.js +1 -14
- package/dist/pikichannel/turn.js +2 -69
- package/dist/pikichannel/web/sdk.js +14 -71
- package/package.json +1 -1
- package/dashboard/dist/assets/ConnectionModal-Dt05H2f9.js +0 -1
- package/dashboard/dist/assets/SystemTab-CN_QrRKt.js +0 -1
- package/dashboard/dist/assets/index-CP3LYJR2.js +0 -23
- package/dashboard/dist/assets/index-C_neLnaO.js +0 -3
- package/dashboard/dist/assets/index-CyCspgtc.css +0 -1
package/dist/bot/session-hub.js
CHANGED
|
@@ -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
|
|
127
|
+
const opts2 = withCanonicalSessionId(opts);
|
|
128
|
+
const result = await _getSessionTail(opts2);
|
|
172
129
|
if (!result.ok || !result.messages.length) {
|
|
173
|
-
const fallback = tailFallbackFromManagedRecord(
|
|
130
|
+
const fallback = tailFallbackFromManagedRecord(opts2);
|
|
174
131
|
if (fallback)
|
|
175
132
|
return fallback;
|
|
176
133
|
}
|
|
177
134
|
return result;
|
|
178
135
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
|
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:
|
|
220
|
-
sessionId:
|
|
221
|
-
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
|
-
|
|
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)
|
package/dist/bot/streaming.js
CHANGED
|
@@ -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)));
|
package/dist/browser-profile.js
CHANGED
|
@@ -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;
|