cc-viewer 1.9.5 → 1.9.6
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/dist/assets/{App-Bj_-wfrB.js → App-GhvyKB0E.js} +2 -2
- package/dist/assets/{MdxEditorPanel-DD2IrRnW.js → MdxEditorPanel-DwwKGPIQ.js} +1 -1
- package/dist/assets/{Mobile-BQBQkr2V.js → Mobile-CGiDFGRO.js} +1 -1
- package/dist/assets/{ProxyStatsModal-8VYl6n39.js → ProxyStatsModal-CI5PkF_B.js} +1 -1
- package/dist/assets/{index-CloXv0sm.js → index-D0_0_HIv.js} +2 -2
- package/dist/assets/seqResourceLoaders-DHr1E1Ye.js +2 -0
- package/dist/index.html +1 -1
- package/package.json +1 -1
- package/server/lib/sdk-manager.js +151 -31
- package/server/lib/sdk-session-index.js +25 -0
- package/server/lib/view-root.js +30 -10
- package/server/lib/ws-mux.js +27 -8
- package/server/routes/events.js +5 -2
- package/server/routes/project-meta.js +11 -4
- package/server/routes/proxy-stats.js +8 -4
- package/server/routes/resume.js +47 -10
- package/server/server.js +18 -8
- package/dist/assets/seqResourceLoaders-M4XGblXm.js +0 -2
|
@@ -19,10 +19,11 @@ import { listLivePtys } from '../pty-manager.js';
|
|
|
19
19
|
// not become a presence oracle over LOG_DIR — `?project=` is accepted ONLY
|
|
20
20
|
// when it resolves to a registered workspace / live PTY (or the bound
|
|
21
21
|
// project), never an arbitrary sanitized name.
|
|
22
|
-
function _resolveStatsName(projectParam, res) {
|
|
23
|
-
if (!projectParam) return _projectName || '';
|
|
22
|
+
function _resolveStatsName(projectParam, instanceParam, res) {
|
|
23
|
+
if (!projectParam && !instanceParam) return _projectName || '';
|
|
24
24
|
const r = resolveViewRoot({
|
|
25
25
|
projectParam,
|
|
26
|
+
instanceParam,
|
|
26
27
|
boundCwd: process.env.CCV_PROJECT_DIR || process.cwd(),
|
|
27
28
|
loadWorkspaces,
|
|
28
29
|
listLivePtys,
|
|
@@ -32,7 +33,9 @@ function _resolveStatsName(projectParam, res) {
|
|
|
32
33
|
res.end(JSON.stringify({ error: r.error }));
|
|
33
34
|
return null;
|
|
34
35
|
}
|
|
35
|
-
|
|
36
|
+
// Same contract as project-meta: the store name derives from the resolver's
|
|
37
|
+
// RAW matched path, never the raw request name nor the realpath'd root.
|
|
38
|
+
return sanitizePathComponent(r.projectName);
|
|
36
39
|
}
|
|
37
40
|
|
|
38
41
|
// GET /api/proxy-stats — returns the VIEWED project's proxy retry stats
|
|
@@ -41,7 +44,8 @@ function _resolveStatsName(projectParam, res) {
|
|
|
41
44
|
function getProxyStats(req, res, parsedUrl) {
|
|
42
45
|
try {
|
|
43
46
|
const projectParam = parsedUrl && parsedUrl.searchParams ? parsedUrl.searchParams.get('project') : null;
|
|
44
|
-
const
|
|
47
|
+
const instanceParam = parsedUrl && parsedUrl.searchParams ? parsedUrl.searchParams.get('instance') : null;
|
|
48
|
+
const name = _resolveStatsName(projectParam, instanceParam, res);
|
|
45
49
|
if (name === null) return; // error already replied
|
|
46
50
|
if (!name) {
|
|
47
51
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
package/server/routes/resume.js
CHANGED
|
@@ -54,6 +54,7 @@ import { isAdminReq } from '../lib/is-admin.js';
|
|
|
54
54
|
import { isSameOriginBrowserRequest } from '../lib/same-origin.js';
|
|
55
55
|
import { sseWrite } from '../lib/wire-compress.js';
|
|
56
56
|
import { reportSwallowed } from '@ccv/core/error-report';
|
|
57
|
+
import { findSdkSessionByInstance, listSdkSessionInstances } from '../lib/sdk-session-index.js';
|
|
57
58
|
|
|
58
59
|
function sendJson(res, code, obj) {
|
|
59
60
|
res.writeHead(code, { 'Content-Type': 'application/json' });
|
|
@@ -128,21 +129,22 @@ async function getLiveProcessesHandler(req, res, parsedUrl) {
|
|
|
128
129
|
// first launch's PTY-less list collapsed to just the new project. Enumerate the per-cwd SDK
|
|
129
130
|
// session registry (multi-session) as `kind:'main'` entries so each launched workspace shows
|
|
130
131
|
// up exactly like a PTY project. `active` marks the session the SDK composer is bound to;
|
|
131
|
-
//
|
|
132
|
+
// a cwd-stable instanceKey drives exact view routing and the per-session × close path.
|
|
132
133
|
if (process.env.CCV_SDK_MODE === '1') {
|
|
133
134
|
try {
|
|
134
135
|
const sdk = await import('../lib/sdk-manager.js');
|
|
135
136
|
for (const e of sdk.listSdkSessions()) {
|
|
136
137
|
if (!e || !e.cwd) continue;
|
|
137
138
|
if (processes.some((p) => p && p.cwd === e.cwd)) continue; // a real PTY already covers this cwd
|
|
139
|
+
const boundCwd = process.env.CCV_PROJECT_DIR || process.cwd();
|
|
140
|
+
if (!currentInstanceKey && e.instanceKey && e.cwd === boundCwd) currentInstanceKey = e.instanceKey;
|
|
138
141
|
processes.push({
|
|
139
142
|
kind: 'main',
|
|
140
143
|
project: projectKeyForCwd(e.cwd),
|
|
141
144
|
cwd: e.cwd,
|
|
142
145
|
pid: null,
|
|
143
146
|
active: e.active === true,
|
|
144
|
-
instanceKey: null,
|
|
145
|
-
closable: false, // SDK resident query — killed via session switch, not the × close path
|
|
147
|
+
instanceKey: e.instanceKey || null,
|
|
146
148
|
});
|
|
147
149
|
}
|
|
148
150
|
} catch (err) { reportSwallowed('resume-route.live-sdk', err); }
|
|
@@ -176,6 +178,25 @@ function postLiveProcessAttachHandler(req, res, parsedUrl, isLocal, deps) {
|
|
|
176
178
|
return;
|
|
177
179
|
}
|
|
178
180
|
try {
|
|
181
|
+
const sdkAnchor = instanceKey && findSdkSessionByInstance(instanceKey)
|
|
182
|
+
? { instanceKey }
|
|
183
|
+
: (!instanceKey && listSdkSessionInstances().length ? { project: project || undefined } : null);
|
|
184
|
+
if (sdkAnchor) {
|
|
185
|
+
const sdk = await import('../lib/sdk-manager.js');
|
|
186
|
+
const r = sdk.attachSdkSession(sdkAnchor);
|
|
187
|
+
if (r.ok) {
|
|
188
|
+
sendJson(res, 200, { ok: true, project, instanceKey: r.instanceKey, running: true, ptyKind: 'sdk', switched: r.switched });
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (r.reason === 'ambiguous') {
|
|
192
|
+
sendJson(res, 409, { ok: false, reason: 'ambiguous', candidates: r.candidates || [] });
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (instanceKey) {
|
|
196
|
+
sendJson(res, 404, { ok: false, reason: 'not-found' });
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
179
200
|
const { attachPtyFor } = await import('../pty-manager.js');
|
|
180
201
|
const r = attachPtyFor({ project: project || undefined, instanceKey: instanceKey || undefined });
|
|
181
202
|
if (!r.ok) {
|
|
@@ -238,8 +259,22 @@ function postLiveProcessCloseHandler(req, res, parsedUrl, isLocal, deps) {
|
|
|
238
259
|
return;
|
|
239
260
|
}
|
|
240
261
|
try {
|
|
241
|
-
const
|
|
242
|
-
|
|
262
|
+
const sdkByInstance = instanceKey ? findSdkSessionByInstance(instanceKey) : null;
|
|
263
|
+
let r;
|
|
264
|
+
let survivorRecords = null;
|
|
265
|
+
if (sdkByInstance) {
|
|
266
|
+
const sdk = await import('../lib/sdk-manager.js');
|
|
267
|
+
r = sdk.closeSdkSession({ instanceKey });
|
|
268
|
+
survivorRecords = listSdkSessionInstances().map((s) => ({ key: s.instanceKey, cwd: s.cwd, isActive: s.active === true }));
|
|
269
|
+
} else {
|
|
270
|
+
const { killPtyFor } = await import('../pty-manager.js');
|
|
271
|
+
r = killPtyFor({ project: project || undefined, instanceKey: instanceKey || undefined });
|
|
272
|
+
if (!r.ok && r.reason === 'not-found' && listSdkSessionInstances().length) {
|
|
273
|
+
const sdk = await import('../lib/sdk-manager.js');
|
|
274
|
+
r = sdk.closeSdkSession({ project: project || undefined, instanceKey: instanceKey || undefined });
|
|
275
|
+
if (r.ok) survivorRecords = listSdkSessionInstances().map((s) => ({ key: s.instanceKey, cwd: s.cwd, isActive: s.active === true }));
|
|
276
|
+
}
|
|
277
|
+
}
|
|
243
278
|
if (!r.ok) {
|
|
244
279
|
// Multi-instance: an ambiguous project-name-only close (2+ live same-basename
|
|
245
280
|
// instances) is a 409 with the live candidates, so the client re-issues with an
|
|
@@ -260,7 +295,7 @@ function postLiveProcessCloseHandler(req, res, parsedUrl, isLocal, deps) {
|
|
|
260
295
|
// CCV_PROJECT_DIR — never by basename, or a same-name twin close would false-hit.
|
|
261
296
|
let rebound = null;
|
|
262
297
|
try {
|
|
263
|
-
rebound = _maybeRebindAfterClose(r, deps);
|
|
298
|
+
rebound = _maybeRebindAfterClose(r, deps, survivorRecords);
|
|
264
299
|
} catch (err) { reportSwallowed('resume-route.close-rebind', err); }
|
|
265
300
|
// A PTY killed here fires no Stop POST, so its scope's streaming/turn
|
|
266
301
|
// bucket would stay `active:true` forever — the heartbeat/streaming timer
|
|
@@ -311,7 +346,7 @@ function postLiveProcessCloseHandler(req, res, parsedUrl, isLocal, deps) {
|
|
|
311
346
|
}
|
|
312
347
|
}
|
|
313
348
|
} catch (err) { reportSwallowed('resume-route.close-kill-scratch', err); }
|
|
314
|
-
sendJson(res, 200, { ok: true, project, instanceKey: r.key || instanceKey || null, killedActive: r.killedActive === true, rebound });
|
|
349
|
+
sendJson(res, 200, { ok: true, project, instanceKey: r.instanceKey || r.key || instanceKey || null, killedActive: r.killedActive === true, rebound });
|
|
315
350
|
} catch (err) {
|
|
316
351
|
reportSwallowed('resume-route.close', err);
|
|
317
352
|
sendJson(res, 500, { ok: false, reason: 'close-failed' });
|
|
@@ -339,7 +374,7 @@ function postLiveProcessCloseHandler(req, res, parsedUrl, isLocal, deps) {
|
|
|
339
374
|
// request can observe a half-applied rebind, and a throwing side-effect can no
|
|
340
375
|
// longer strand the "binding moved but nobody was told" state (the broadcast
|
|
341
376
|
// has already gone out by then).
|
|
342
|
-
function _maybeRebindAfterClose(killResult, deps) {
|
|
377
|
+
function _maybeRebindAfterClose(killResult, deps, survivorRecords = null) {
|
|
343
378
|
// Electron multi-tab: the manager process does NOT own log init (the tab
|
|
344
379
|
// workers do); mirroring workspaces.js:56, skip rebind entirely there.
|
|
345
380
|
if (process.env.CCV_ELECTRON_MULTITAB === '1') return null;
|
|
@@ -355,7 +390,7 @@ function _maybeRebindAfterClose(killResult, deps) {
|
|
|
355
390
|
// terminal attachment to (reattachedTo), then the terminal's live attachment
|
|
356
391
|
// (isActive — preserves the launch-time invariant bound == attached), else
|
|
357
392
|
// the first live claude record in Map order.
|
|
358
|
-
const live = listLivePtys().filter((p) => p && p.cwd && (!p.ptyKind || p.ptyKind === 'claude'));
|
|
393
|
+
const live = survivorRecords || listLivePtys().filter((p) => p && p.cwd && (!p.ptyKind || p.ptyKind === 'claude'));
|
|
359
394
|
if (!live.length) return null;
|
|
360
395
|
// A same-cwd TWIN of the closed bound project still lives → the bound
|
|
361
396
|
// identity (same project name, same log dir) is already correct for it;
|
|
@@ -492,7 +527,9 @@ function postResumeSessionHandler(req, res, parsedUrl, isLocal, deps) {
|
|
|
492
527
|
const sdk = await import('../lib/sdk-manager.js');
|
|
493
528
|
// Multi-session (2026-10-12): honor the requested workspace — resume the VIEWED
|
|
494
529
|
// session, not always the active one. project is the workspace name from the client.
|
|
495
|
-
const anchor = (
|
|
530
|
+
const anchor = (project || instanceKey)
|
|
531
|
+
? { project: project || undefined, instanceKey: instanceKey || undefined }
|
|
532
|
+
: undefined;
|
|
496
533
|
const r = sdk.switchToSession(sessionUuid, anchor);
|
|
497
534
|
if (r.ok) { sendJson(res, 200, { ok: true }); return; }
|
|
498
535
|
if (r.reason === 'busy') { sendJson(res, 409, { ok: false, reason: 'busy' }); return; }
|
package/server/server.js
CHANGED
|
@@ -327,6 +327,9 @@ const _editorCleanupTimer = setInterval(() => {
|
|
|
327
327
|
}, 60000);
|
|
328
328
|
_editorCleanupTimer.unref(); // Don't keep process alive for cleanup
|
|
329
329
|
let terminalWss = null; // WebSocketServer reference for broadcasting
|
|
330
|
+
// Test seam: inject a stubbed client set so broadcastWsMessage's instance-narrowing
|
|
331
|
+
// predicate is directly unit-testable (ws-instance-isolation.test.js).
|
|
332
|
+
export function setTerminalWssForTests(wss) { terminalWss = wss; }
|
|
330
333
|
let streamMux = null; // /ws/mux dialogue-channel stream mux (SSE stays the fallback)
|
|
331
334
|
let _writeToPty = null; // PTY write function reference (set by setupTerminalWebSocket)
|
|
332
335
|
let _onPtyData = null; // PTY data listener registration (set by setupTerminalWebSocket)
|
|
@@ -2060,6 +2063,7 @@ async function setupTerminalWebSocket(httpServer) {
|
|
|
2060
2063
|
// 新视图的 sdk-session-state/审批/队列帧会被静默丢弃。
|
|
2061
2064
|
if (msg.type === 'attach') {
|
|
2062
2065
|
try { ws._ccvViewProject = (typeof msg.project === 'string' && msg.project) || null; } catch {}
|
|
2066
|
+
try { ws._ccvViewInstance = (typeof msg.instanceKey === 'string' && msg.instanceKey) || null; } catch {}
|
|
2063
2067
|
if (isSdkMode) return;
|
|
2064
2068
|
} else if (isSdkMode && (msg.type === 'input' || msg.type === 'input-sequential'
|
|
2065
2069
|
|| msg.type === 'resync-request' || msg.type === 'resize')) {
|
|
@@ -2413,7 +2417,7 @@ async function setupTerminalWebSocket(httpServer) {
|
|
|
2413
2417
|
// User message in SDK mode — relay to sdk-manager, addressed to the viewed
|
|
2414
2418
|
// workspace's session (multi-session). msg.project is the workspace basename.
|
|
2415
2419
|
if (_sdkSendUserMessage && msg.text) {
|
|
2416
|
-
const anchor = (typeof msg.project === 'string' && msg.project) ? { project: msg.project } : undefined;
|
|
2420
|
+
const anchor = ((typeof msg.project === 'string' && msg.project) || (typeof msg.instanceKey === 'string' && msg.instanceKey)) ? { project: msg.project, instanceKey: typeof msg.instanceKey === 'string' ? msg.instanceKey : undefined } : undefined;
|
|
2417
2421
|
_sdkSendUserMessage(msg.text, anchor).catch(err => {
|
|
2418
2422
|
console.error('[SDK] sendUserMessage error:', err.message);
|
|
2419
2423
|
});
|
|
@@ -2424,7 +2428,7 @@ async function setupTerminalWebSocket(httpServer) {
|
|
|
2424
2428
|
// fetch hook → streamingState → SSE), same as PTY mode; sdk-manager
|
|
2425
2429
|
// no longer emits its own streaming_status.
|
|
2426
2430
|
if (isSdkMode && _sdkInterruptTurn) {
|
|
2427
|
-
const anchor = (typeof msg.project === 'string' && msg.project) ? { project: msg.project } : undefined;
|
|
2431
|
+
const anchor = ((typeof msg.project === 'string' && msg.project) || (typeof msg.instanceKey === 'string' && msg.instanceKey)) ? { project: msg.project, instanceKey: typeof msg.instanceKey === 'string' ? msg.instanceKey : undefined } : undefined;
|
|
2428
2432
|
const cancelled = _sdkInterruptTurn(anchor) || [];
|
|
2429
2433
|
_broadcastApprovalCloses(cancelled, 'Turn interrupted');
|
|
2430
2434
|
} else {
|
|
@@ -2441,7 +2445,7 @@ async function setupTerminalWebSocket(httpServer) {
|
|
|
2441
2445
|
if (typeof msg.text === 'string' && msg.text) {
|
|
2442
2446
|
if (isSdkMode) {
|
|
2443
2447
|
if (_sdkSendUserMessage) {
|
|
2444
|
-
const anchor = (typeof msg.project === 'string' && msg.project) ? { project: msg.project } : undefined;
|
|
2448
|
+
const anchor = ((typeof msg.project === 'string' && msg.project) || (typeof msg.instanceKey === 'string' && msg.instanceKey)) ? { project: msg.project, instanceKey: typeof msg.instanceKey === 'string' ? msg.instanceKey : undefined } : undefined;
|
|
2445
2449
|
_sdkSendUserMessage(msg.text, anchor).catch(err => {
|
|
2446
2450
|
console.error('[SDK] queue-message relay error:', err.message);
|
|
2447
2451
|
});
|
|
@@ -2460,7 +2464,7 @@ async function setupTerminalWebSocket(httpServer) {
|
|
|
2460
2464
|
if (_sdkSendQueuedNow) {
|
|
2461
2465
|
// Multi-session: address the bubble's own workspace (the viewed project), not
|
|
2462
2466
|
// the global active session — bubbles are cwd-scoped, the action must match.
|
|
2463
|
-
const qAnchor = (typeof msg.project === 'string' && msg.project) ? { project: msg.project } : undefined;
|
|
2467
|
+
const qAnchor = ((typeof msg.project === 'string' && msg.project) || (typeof msg.instanceKey === 'string' && msg.instanceKey)) ? { project: msg.project, instanceKey: typeof msg.instanceKey === 'string' ? msg.instanceKey : undefined } : undefined;
|
|
2464
2468
|
try { _broadcastApprovalCloses(_sdkSendQueuedNow(qid, qAnchor) || [], 'Queued message sent now'); }
|
|
2465
2469
|
catch (err) { reportSwallowed('server.queue-send-now', err); }
|
|
2466
2470
|
}
|
|
@@ -2472,7 +2476,7 @@ async function setupTerminalWebSocket(httpServer) {
|
|
|
2472
2476
|
const qid = (typeof msg.id === 'string' && /^[a-zA-Z0-9_-]{1,128}$/.test(msg.id)) ? msg.id : null;
|
|
2473
2477
|
if (qid) {
|
|
2474
2478
|
if (isSdkMode) {
|
|
2475
|
-
if (_sdkRemoveQueued) { const qAnchor = (typeof msg.project === 'string' && msg.project) ? { project: msg.project } : undefined; try { _sdkRemoveQueued(qid, qAnchor); } catch (err) { reportSwallowed('server.queue-remove', err); } }
|
|
2479
|
+
if (_sdkRemoveQueued) { const qAnchor = ((typeof msg.project === 'string' && msg.project) || (typeof msg.instanceKey === 'string' && msg.instanceKey)) ? { project: msg.project, instanceKey: typeof msg.instanceKey === 'string' ? msg.instanceKey : undefined } : undefined; try { _sdkRemoveQueued(qid, qAnchor); } catch (err) { reportSwallowed('server.queue-remove', err); } }
|
|
2476
2480
|
} else {
|
|
2477
2481
|
try { chatQueue.remove(qid); } catch (err) { reportSwallowed('server.queue-remove', err); }
|
|
2478
2482
|
}
|
|
@@ -2481,7 +2485,7 @@ async function setupTerminalWebSocket(httpServer) {
|
|
|
2481
2485
|
// Session/workspace switch: drop the parked queue WITHOUT interrupting a running
|
|
2482
2486
|
// turn. (Stop semantics live elsewhere: PTY queue-suppress, SDK sdk-interrupt.)
|
|
2483
2487
|
if (isSdkMode) {
|
|
2484
|
-
if (_sdkClearQueued) { const qAnchor = (typeof msg.project === 'string' && msg.project) ? { project: msg.project } : undefined; try { _sdkClearQueued(qAnchor); } catch (err) { reportSwallowed('server.queue-clear', err); } }
|
|
2488
|
+
if (_sdkClearQueued) { const qAnchor = ((typeof msg.project === 'string' && msg.project) || (typeof msg.instanceKey === 'string' && msg.instanceKey)) ? { project: msg.project, instanceKey: typeof msg.instanceKey === 'string' ? msg.instanceKey : undefined } : undefined; try { _sdkClearQueued(qAnchor); } catch (err) { reportSwallowed('server.queue-clear', err); } }
|
|
2485
2489
|
} else {
|
|
2486
2490
|
try { chatQueue.clear(); } catch (err) { reportSwallowed('server.queue-clear', err); }
|
|
2487
2491
|
}
|
|
@@ -3226,8 +3230,7 @@ async function _doStop() {
|
|
|
3226
3230
|
// are intentionally no pushSdkEntry/setSdkStreamingState shortcuts anymore.
|
|
3227
3231
|
|
|
3228
3232
|
/** Broadcast a message to all terminal WS clients (for SDK canUseTool). */
|
|
3229
|
-
export function broadcastWsMessage(msg) {
|
|
3230
|
-
if (terminalWss) {
|
|
3233
|
+
export function broadcastWsMessage(msg) { if (terminalWss) {
|
|
3231
3234
|
// SDK multi-session (2026-10-12): a cwd-tagged frame belongs to one workspace's session.
|
|
3232
3235
|
// Route it only to clients viewing that workspace (or the bound view with no ?project=),
|
|
3233
3236
|
// so a background workspace's approval/queue/spinner never bleeds into the foreground.
|
|
@@ -3238,10 +3241,17 @@ export function broadcastWsMessage(msg) {
|
|
|
3238
3241
|
// — every frame for such a workspace would be silently dropped for its viewer.
|
|
3239
3242
|
let frameProj = null;
|
|
3240
3243
|
if (frameCwd) frameProj = projectKeyForCwd(frameCwd) || null;
|
|
3244
|
+
// Same-name twin isolation (review P2): a frame tagged with an instance key narrows
|
|
3245
|
+
// delivery to clients viewing THAT instance — otherwise two same-basename workspaces
|
|
3246
|
+
// (same frameProj) would cross-deliver (e.g. a close teardown's idle/queue-state wipe
|
|
3247
|
+
// killing the surviving twin viewer's spinner and queue bubbles). Untagged/cwd-only
|
|
3248
|
+
// frames keep the historical project-level behavior.
|
|
3249
|
+
const frameInstance = (msg && typeof msg === 'object' && typeof msg.instanceKey === 'string' && msg.instanceKey) ? msg.instanceKey : null;
|
|
3241
3250
|
const str = typeof msg === 'string' ? msg : JSON.stringify(msg);
|
|
3242
3251
|
terminalWss.clients.forEach((c) => {
|
|
3243
3252
|
if (c.readyState !== 1) return;
|
|
3244
3253
|
if (frameProj && c._ccvViewProject && c._ccvViewProject !== frameProj) return; // other workspace's frame
|
|
3254
|
+
if (frameInstance && c._ccvViewInstance && c._ccvViewInstance !== frameInstance) return; // other instance of the same project
|
|
3245
3255
|
try { c.send(str); } catch {}
|
|
3246
3256
|
});
|
|
3247
3257
|
}
|