@dimina-kit/devtools 0.4.0-dev.20260616085026 → 0.4.0-dev.20260618090552

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 (41) hide show
  1. package/README.md +25 -24
  2. package/dist/main/index.bundle.js +219 -14
  3. package/dist/main/ipc/bridge-router.js +44 -1
  4. package/dist/main/runtime/devtools-backend.d.ts +1 -1
  5. package/dist/main/runtime/devtools-backend.js +1 -1
  6. package/dist/main/runtime/miniapp-runtime.d.ts +2 -2
  7. package/dist/main/services/console-forward/index.js +8 -1
  8. package/dist/main/services/elements-forward/index.js +31 -7
  9. package/dist/main/services/layout/index.d.ts +12 -0
  10. package/dist/main/services/layout/index.js +14 -2
  11. package/dist/main/services/projects/types.d.ts +1 -1
  12. package/dist/main/services/projects/types.js +1 -1
  13. package/dist/main/services/service-console/console-api.d.ts +78 -0
  14. package/dist/main/services/service-console/console-api.js +104 -0
  15. package/dist/main/services/service-console/index.d.ts +47 -0
  16. package/dist/main/services/service-console/index.js +139 -0
  17. package/dist/main/services/update/update-manager.d.ts +1 -1
  18. package/dist/main/services/update/update-manager.js +1 -1
  19. package/dist/main/services/views/view-manager.d.ts +7 -0
  20. package/dist/main/services/views/view-manager.js +62 -2
  21. package/dist/main/services/workbench-context.d.ts +7 -6
  22. package/dist/native-host/render/render.js +6 -6
  23. package/dist/renderer/assets/index-DmgWoK8N.js +50 -0
  24. package/dist/renderer/assets/{input-B8NDCYv3.js → input-DiCxYlqY.js} +2 -2
  25. package/dist/renderer/assets/{ipc-transport-DhrajiC5.js → ipc-transport-DSBmaWpJ.js} +1 -1
  26. package/dist/renderer/assets/ipc-transport-DZxf2YoM.css +1 -0
  27. package/dist/renderer/assets/{popover-CI5uE9ZZ.js → popover-CyL_uffj.js} +2 -2
  28. package/dist/renderer/assets/{select-tInleY0z.js → select-BSwvDMtA.js} +2 -2
  29. package/dist/renderer/assets/settings-Ch8XbFxf.js +2 -0
  30. package/dist/renderer/assets/{settings-api-BSKzKiWd.js → settings-api-e9ILe2JO.js} +2 -2
  31. package/dist/renderer/assets/{workbenchSettings-BGxjl25x.js → workbenchSettings-DZsHf1g_.js} +2 -2
  32. package/dist/renderer/entries/main/index.html +6 -6
  33. package/dist/renderer/entries/popover/index.html +5 -5
  34. package/dist/renderer/entries/settings/index.html +11 -5
  35. package/dist/renderer/entries/workbench-settings/index.html +4 -4
  36. package/dist/service-host/preload.cjs +13 -15
  37. package/dist/service-host/sourcemap-rewrite.cjs +21 -12
  38. package/package.json +4 -4
  39. package/dist/renderer/assets/index-BYV0bEvB.js +0 -50
  40. package/dist/renderer/assets/ipc-transport-9agi76dX.css +0 -1
  41. package/dist/renderer/assets/settings-CZJOHt8b.js +0 -2
@@ -173,7 +173,11 @@ function buildDocumentUpdatedScript() {
173
173
  }
174
174
  const DRAIN_INTERVAL_MS = 150;
175
175
  /** Bounded retry for the front-end hook install (front-end boots asynchronously). */
176
- const INSTALL_POLL_TRIES = 80;
176
+ // Per-load install poll window. Must outlast the gap between a devtools
177
+ // front-end `dom-ready` and `InspectorFrontendHost` becoming available on a
178
+ // cold/slow open (pool cold-start). 200×50ms = 10s — generous, and each future
179
+ // front-end load re-arms a fresh window anyway (see the `dom-ready` listener).
180
+ const INSTALL_POLL_TRIES = 200;
177
181
  const INSTALL_POLL_INTERVAL_MS = 50;
178
182
  /**
179
183
  * Install Elements forwarding on a DevTools front-end host wc. Returns a disposer
@@ -504,6 +508,17 @@ export function installElementsForward(deps) {
504
508
  const onReady = () => {
505
509
  if (disposed)
506
510
  return;
511
+ // Re-arm safe: a devtools front-end (re)load re-runs this, so clear any
512
+ // in-flight timers from a prior attempt before starting fresh ones — without
513
+ // this a reload would stack intervals (leak) and double-drain.
514
+ if (installTimer) {
515
+ clearInterval(installTimer);
516
+ installTimer = null;
517
+ }
518
+ if (drainTimer) {
519
+ clearInterval(drainTimer);
520
+ drainTimer = null;
521
+ }
507
522
  let tries = 0;
508
523
  installTimer = setInterval(() => {
509
524
  tries++;
@@ -550,12 +565,17 @@ export function installElementsForward(deps) {
550
565
  .catch(() => { });
551
566
  }, DRAIN_INTERVAL_MS);
552
567
  };
553
- if (devtoolsWc.isLoading()) {
554
- devtoolsWc.once('dom-ready', onReady);
555
- }
556
- else {
557
- onReady();
558
- }
568
+ // Re-install on EVERY devtools front-end load, not once. The hook lives in the
569
+ // front-end's `globalThis` (`__diminaElementsHookInstalled`), so any front-end
570
+ // (re)load wipes it — most importantly a service-host pool SWAP, which re-opens
571
+ // DevTools (`pointNativeDevtoolsAtServiceWc`) and re-bootstraps the front-end.
572
+ // A single-shot `once('dom-ready')` left the hook uninstalled after such a
573
+ // reload, so Elements silently fell back to the native service-host DOM. A
574
+ // PERSISTENT listener re-runs the (idempotent — the sentinel returns 'already')
575
+ // install on each load; the immediate call covers an already-loaded wc and the
576
+ // first `openDevTools` whose `dom-ready` may have fired before this ran.
577
+ devtoolsWc.on('dom-ready', onReady);
578
+ onReady();
559
579
  let devtoolsClosedSub;
560
580
  try {
561
581
  // Devtools front-end wc is never pool-reset → use `'closed'` (fires only on
@@ -577,6 +597,10 @@ export function installElementsForward(deps) {
577
597
  devtoolsClosedSub?.dispose();
578
598
  }
579
599
  catch { /* already gone */ }
600
+ try {
601
+ devtoolsWc.removeListener('dom-ready', onReady);
602
+ }
603
+ catch { /* already gone */ }
580
604
  stop();
581
605
  };
582
606
  }
@@ -26,7 +26,19 @@ export declare function computeNativeSimulatorViewParams(rect: {
26
26
  bounds: Bounds;
27
27
  zoomFactor: number;
28
28
  };
29
+ /** Renderer-side width of the settings PANEL (the opaque right strip). The
30
+ * settings WebContentsView itself spans the full content area (see
31
+ * `computeSettingsBounds`) as a transparent backdrop; the panel is positioned
32
+ * inside it at this width by `settings.tsx`. */
29
33
  export declare const SETTINGS_W = 320;
34
+ /**
35
+ * The settings overlay is a FULL-content-area transparent backdrop (header
36
+ * excluded), identical to the popover region — NOT a right-edge strip. The
37
+ * renderer paints a transparent backdrop over the whole area and an opaque
38
+ * `SETTINGS_W`-wide panel on the right; a click on the transparent backdrop
39
+ * closes the overlay (clicks outside the panel must reach the backdrop, which is
40
+ * only possible when the view spans the whole area rather than just the strip).
41
+ */
30
42
  export declare function computeSettingsBounds(contentWidth: number, contentHeight: number, headerHeight: number): Bounds;
31
43
  export declare function computePopoverBounds(contentWidth: number, contentHeight: number, headerHeight: number): Bounds;
32
44
  //# sourceMappingURL=index.d.ts.map
@@ -23,12 +23,24 @@ export function computeNativeSimulatorViewParams(rect, zoomPercent) {
23
23
  zoomFactor,
24
24
  };
25
25
  }
26
+ /** Renderer-side width of the settings PANEL (the opaque right strip). The
27
+ * settings WebContentsView itself spans the full content area (see
28
+ * `computeSettingsBounds`) as a transparent backdrop; the panel is positioned
29
+ * inside it at this width by `settings.tsx`. */
26
30
  export const SETTINGS_W = 320;
31
+ /**
32
+ * The settings overlay is a FULL-content-area transparent backdrop (header
33
+ * excluded), identical to the popover region — NOT a right-edge strip. The
34
+ * renderer paints a transparent backdrop over the whole area and an opaque
35
+ * `SETTINGS_W`-wide panel on the right; a click on the transparent backdrop
36
+ * closes the overlay (clicks outside the panel must reach the backdrop, which is
37
+ * only possible when the view spans the whole area rather than just the strip).
38
+ */
27
39
  export function computeSettingsBounds(contentWidth, contentHeight, headerHeight) {
28
40
  return {
29
- x: Math.max(0, contentWidth - SETTINGS_W),
41
+ x: 0,
30
42
  y: headerHeight,
31
- width: Math.min(SETTINGS_W, Math.max(1, contentWidth)),
43
+ width: Math.max(1, contentWidth),
32
44
  height: Math.max(1, contentHeight - headerHeight),
33
45
  };
34
46
  }
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Public extensibility surface for the project list panel. Hosts that embed
3
- * dimina-devtools (e.g. qdmp) can inject implementations of these types via
3
+ * dimina-devtools (downstream hosts) can inject implementations of these types via
4
4
  * `WorkbenchAppConfig` to fully take over the project source-of-truth, the
5
5
  * template catalog, and the "新建项目" dialog.
6
6
  */
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Public extensibility surface for the project list panel. Hosts that embed
3
- * dimina-devtools (e.g. qdmp) can inject implementations of these types via
3
+ * dimina-devtools (downstream hosts) can inject implementations of these types via
4
4
  * `WorkbenchAppConfig` to fully take over the project source-of-truth, the
5
5
  * template catalog, and the "新建项目" dialog.
6
6
  */
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Pure helpers for the native-host service-console forwarder.
3
+ *
4
+ * Under native-host the service-host's console used to be captured by a
5
+ * `console.*` monkeypatch in `service-host/preload.cjs`. That wrapper added a
6
+ * stack frame, so Chrome DevTools (attached natively to the service host)
7
+ * attributed EVERY service-layer log to the wrapper line (`preload.cjs:237`)
8
+ * instead of the developer's source. We now capture via CDP
9
+ * `Runtime.consoleAPICalled` instead, which preserves native source attribution.
10
+ *
11
+ * These functions turn the CDP event shape into the `GuestConsoleEntry` shape
12
+ * the existing console fan-out (automation `App.logAdded`) expects, WITHOUT any
13
+ * electron / IO dependency so they are unit-testable in isolation. The async
14
+ * deep-fetch (`Runtime.callFunctionOn`) for object args lives in `index.ts`.
15
+ */
16
+ /**
17
+ * sourceURL stamped on the render→service `[视图]` re-injection script
18
+ * (`console-forward.buildForwardScript`). The CDP capture skips any
19
+ * `consoleAPICalled` whose top frame carries this URL so a forwarded render line
20
+ * is not re-captured and re-broadcast as a service entry (duplicate / loop).
21
+ */
22
+ export declare const RENDER_FORWARD_SOURCE_URL = "dimina://render-console-forward";
23
+ export type ConsoleLevel = 'log' | 'warn' | 'error' | 'info' | 'debug';
24
+ /** Subset of a CDP `Runtime.RemoteObject` we read. */
25
+ export interface RemoteObjectLike {
26
+ type?: string;
27
+ subtype?: string;
28
+ value?: unknown;
29
+ unserializableValue?: string;
30
+ description?: string;
31
+ objectId?: string;
32
+ preview?: {
33
+ properties?: Array<{
34
+ name: string;
35
+ value: string;
36
+ type: string;
37
+ }>;
38
+ subtype?: string;
39
+ overflow?: boolean;
40
+ };
41
+ }
42
+ /** Subset of a CDP `Runtime.consoleAPICalled` params we read. */
43
+ export interface ConsoleApiParamsLike {
44
+ type?: string;
45
+ args?: RemoteObjectLike[];
46
+ stackTrace?: {
47
+ callFrames?: Array<{
48
+ url?: string;
49
+ }>;
50
+ };
51
+ }
52
+ /**
53
+ * CDP `consoleAPICalled.type` → our console level. CDP emits `'warning'` where
54
+ * we use `'warn'`; every other known level passes through; anything else (e.g.
55
+ * `'dir'`, `'table'`, `'trace'`, `undefined`) maps to `'log'`.
56
+ */
57
+ export declare function mapConsoleApiType(type: string | undefined): ConsoleLevel;
58
+ /**
59
+ * A single RemoteObject → a JSON-serializable value WITHOUT a CDP round-trip
60
+ * (shallow). Objects that need their full contents are flagged by
61
+ * {@link needsDeepFetch} and deep-serialized by the caller; this is the
62
+ * inline/best-effort fallback.
63
+ */
64
+ export declare function remoteObjectToValue(ro: RemoteObjectLike): unknown;
65
+ /**
66
+ * Whether this RemoteObject must be deep-fetched via `Runtime.callFunctionOn`
67
+ * (returnByValue) to be fully serialized — i.e. a real object/array referenced
68
+ * by `objectId` with no inline `value`. Functions, primitives, `null`, and
69
+ * already-inlined values do not.
70
+ */
71
+ export declare function needsDeepFetch(ro: RemoteObjectLike): boolean;
72
+ /**
73
+ * True when a `consoleAPICalled` event is the render→service `[视图]`
74
+ * re-injection (its top call frame URL === the sentinel) and must NOT be
75
+ * re-forwarded — the original render entry already reached every consumer.
76
+ */
77
+ export declare function isRenderForwardEvent(params: ConsoleApiParamsLike, sentinelUrl: string): boolean;
78
+ //# sourceMappingURL=console-api.d.ts.map
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Pure helpers for the native-host service-console forwarder.
3
+ *
4
+ * Under native-host the service-host's console used to be captured by a
5
+ * `console.*` monkeypatch in `service-host/preload.cjs`. That wrapper added a
6
+ * stack frame, so Chrome DevTools (attached natively to the service host)
7
+ * attributed EVERY service-layer log to the wrapper line (`preload.cjs:237`)
8
+ * instead of the developer's source. We now capture via CDP
9
+ * `Runtime.consoleAPICalled` instead, which preserves native source attribution.
10
+ *
11
+ * These functions turn the CDP event shape into the `GuestConsoleEntry` shape
12
+ * the existing console fan-out (automation `App.logAdded`) expects, WITHOUT any
13
+ * electron / IO dependency so they are unit-testable in isolation. The async
14
+ * deep-fetch (`Runtime.callFunctionOn`) for object args lives in `index.ts`.
15
+ */
16
+ /**
17
+ * sourceURL stamped on the render→service `[视图]` re-injection script
18
+ * (`console-forward.buildForwardScript`). The CDP capture skips any
19
+ * `consoleAPICalled` whose top frame carries this URL so a forwarded render line
20
+ * is not re-captured and re-broadcast as a service entry (duplicate / loop).
21
+ */
22
+ export const RENDER_FORWARD_SOURCE_URL = 'dimina://render-console-forward';
23
+ const KNOWN_LEVELS = new Set(['log', 'warn', 'error', 'info', 'debug']);
24
+ /**
25
+ * CDP `consoleAPICalled.type` → our console level. CDP emits `'warning'` where
26
+ * we use `'warn'`; every other known level passes through; anything else (e.g.
27
+ * `'dir'`, `'table'`, `'trace'`, `undefined`) maps to `'log'`.
28
+ */
29
+ export function mapConsoleApiType(type) {
30
+ if (type === 'warning')
31
+ return 'warn';
32
+ if (type && KNOWN_LEVELS.has(type))
33
+ return type;
34
+ return 'log';
35
+ }
36
+ const BIGINT_LITERAL = /^-?\d+n$/;
37
+ /**
38
+ * A single RemoteObject → a JSON-serializable value WITHOUT a CDP round-trip
39
+ * (shallow). Objects that need their full contents are flagged by
40
+ * {@link needsDeepFetch} and deep-serialized by the caller; this is the
41
+ * inline/best-effort fallback.
42
+ */
43
+ export function remoteObjectToValue(ro) {
44
+ if (!ro)
45
+ return ro;
46
+ // 1. An inlined value (CDP includes `value` for JSON-serializable primitives
47
+ // and small arrays). Use `in` so falsy values (0, '', false) and an
48
+ // explicit `null` are honoured rather than falling through.
49
+ if ('value' in ro)
50
+ return ro.value;
51
+ // 2. Specials that can't ride in `value`.
52
+ if (typeof ro.unserializableValue === 'string') {
53
+ const u = ro.unserializableValue;
54
+ if (u === 'Infinity')
55
+ return Infinity;
56
+ if (u === '-Infinity')
57
+ return -Infinity;
58
+ if (u === 'NaN')
59
+ return NaN;
60
+ if (u === '-0')
61
+ return -0;
62
+ if (BIGINT_LITERAL.test(u))
63
+ return u;
64
+ return u;
65
+ }
66
+ // 3. Fall back by type.
67
+ switch (ro.type) {
68
+ case 'undefined':
69
+ return undefined;
70
+ case 'function':
71
+ return ro.description ?? '[Function]';
72
+ case 'symbol':
73
+ return ro.description ?? '[Symbol]';
74
+ case 'object':
75
+ if (ro.subtype === 'null')
76
+ return null;
77
+ return ro.description ?? '[Object]';
78
+ default:
79
+ return ro.description ?? '[Unknown]';
80
+ }
81
+ }
82
+ /**
83
+ * Whether this RemoteObject must be deep-fetched via `Runtime.callFunctionOn`
84
+ * (returnByValue) to be fully serialized — i.e. a real object/array referenced
85
+ * by `objectId` with no inline `value`. Functions, primitives, `null`, and
86
+ * already-inlined values do not.
87
+ */
88
+ export function needsDeepFetch(ro) {
89
+ if (!ro)
90
+ return false;
91
+ if ('value' in ro)
92
+ return false;
93
+ return ro.type === 'object' && ro.subtype !== 'null' && typeof ro.objectId === 'string';
94
+ }
95
+ /**
96
+ * True when a `consoleAPICalled` event is the render→service `[视图]`
97
+ * re-injection (its top call frame URL === the sentinel) and must NOT be
98
+ * re-forwarded — the original render entry already reached every consumer.
99
+ */
100
+ export function isRenderForwardEvent(params, sentinelUrl) {
101
+ const top = params?.stackTrace?.callFrames?.[0]?.url;
102
+ return typeof top === 'string' && top === sentinelUrl;
103
+ }
104
+ //# sourceMappingURL=console-api.js.map
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Native-host SERVICE-layer console capture via CDP.
3
+ *
4
+ * The embedded Chrome DevTools front-end is attached natively to the service
5
+ * host, so the service layer's `console.*` already displays there with correct
6
+ * source attribution + sourcemaps — UNLESS something rewrites `console.*`. The
7
+ * old `service-host/preload.cjs` monkeypatch did exactly that (wrapping each
8
+ * level to also post the entry to main), which made DevTools attribute every
9
+ * service log to the wrapper line (`preload.cjs:237`) instead of the developer's
10
+ * source. That monkeypatch is removed; this service replaces its ONE remaining
11
+ * job — feeding service-layer entries to the console fan-out (automation
12
+ * `App.logAdded`) — by capturing `Runtime.consoleAPICalled` over an in-process
13
+ * CDP session instead. Capturing at the CDP layer adds no stack frame, so native
14
+ * attribution in the DevTools console is preserved (verified: an in-process
15
+ * `debugger.attach('1.3')` coexists with the custom-host DevTools front-end on
16
+ * the same wc — Electron multi-client CDP).
17
+ *
18
+ * Lifecycle: installed when the right-panel DevTools is pointed at a service host
19
+ * wc (`view-manager.pointNativeDevtoolsAtServiceWc`) and stopped when that source
20
+ * is closed / swapped (pre-warm pool recycles the service window). `stop()` is
21
+ * idempotent.
22
+ */
23
+ import type { WebContents } from 'electron';
24
+ import type { ConnectionRegistry } from '@dimina-kit/electron-deck/main';
25
+ import type { GuestConsoleEntry } from '../console-forward/index.js';
26
+ export interface ServiceConsoleForwardDeps {
27
+ /** The service host wc whose `console.*` we capture (top-level BrowserWindow wc). */
28
+ serviceWc: WebContents;
29
+ /** Sink for each captured service entry (wired to `consoleForwarder.emit`). */
30
+ emit: (entry: GuestConsoleEntry) => void;
31
+ /** Connection registry — binds teardown to the service wc's destroy ('closed'). */
32
+ connections?: ConnectionRegistry;
33
+ }
34
+ export interface ServiceConsoleForwardHandle {
35
+ /** Detach the CDP session (if we attached it) and stop capturing. Idempotent. */
36
+ stop(): void;
37
+ }
38
+ /**
39
+ * Attach an in-process CDP session to `serviceWc`, enable Runtime, and forward
40
+ * every `Runtime.consoleAPICalled` to `emit` as a `source:'service'` entry —
41
+ * EXCEPT the render→service `[视图]` re-injection (skipped via sentinel so it is
42
+ * not double-broadcast). Object args are deep-serialized via
43
+ * `Runtime.callFunctionOn` on our OWN session (the objectId can't be released by
44
+ * the front-end), falling back to the shallow inline value on failure.
45
+ */
46
+ export declare function installServiceConsoleForward(deps: ServiceConsoleForwardDeps): ServiceConsoleForwardHandle;
47
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,139 @@
1
+ import { RENDER_FORWARD_SOURCE_URL, mapConsoleApiType, needsDeepFetch, remoteObjectToValue, isRenderForwardEvent, } from './console-api.js';
2
+ /**
3
+ * Attach an in-process CDP session to `serviceWc`, enable Runtime, and forward
4
+ * every `Runtime.consoleAPICalled` to `emit` as a `source:'service'` entry —
5
+ * EXCEPT the render→service `[视图]` re-injection (skipped via sentinel so it is
6
+ * not double-broadcast). Object args are deep-serialized via
7
+ * `Runtime.callFunctionOn` on our OWN session (the objectId can't be released by
8
+ * the front-end), falling back to the shallow inline value on failure.
9
+ */
10
+ export function installServiceConsoleForward(deps) {
11
+ const { serviceWc, emit, connections } = deps;
12
+ let disposed = false;
13
+ // Did WE attach the session (vs. it already being attached by someone else)?
14
+ // Only detach what we own.
15
+ let selfAttached = false;
16
+ // Process events in arrival order even though arg resolution is async, so the
17
+ // forwarded sequence matches the order the developer logged.
18
+ let tail = Promise.resolve();
19
+ let closedSub;
20
+ function usableDebugger() {
21
+ try {
22
+ if (serviceWc.isDestroyed())
23
+ return false;
24
+ if (serviceWc.debugger.isAttached())
25
+ return true;
26
+ }
27
+ catch {
28
+ return false;
29
+ }
30
+ try {
31
+ serviceWc.debugger.attach('1.3');
32
+ selfAttached = true;
33
+ return true;
34
+ }
35
+ catch {
36
+ // Race: someone attached between the check and here → still usable.
37
+ try {
38
+ return serviceWc.debugger.isAttached();
39
+ }
40
+ catch {
41
+ return false;
42
+ }
43
+ }
44
+ }
45
+ /** Deep-serialize one arg; best-effort, never throws. */
46
+ async function resolveArg(ro) {
47
+ if (!needsDeepFetch(ro))
48
+ return remoteObjectToValue(ro);
49
+ try {
50
+ const res = (await serviceWc.debugger.sendCommand('Runtime.callFunctionOn', {
51
+ objectId: ro.objectId,
52
+ functionDeclaration: 'function () { return this }',
53
+ returnByValue: true,
54
+ }));
55
+ if (res && res.result && 'value' in res.result)
56
+ return res.result.value;
57
+ }
58
+ catch {
59
+ // Object released / host navigating — fall back to the shallow value.
60
+ }
61
+ return remoteObjectToValue(ro);
62
+ }
63
+ async function handleConsoleApi(params) {
64
+ if (disposed)
65
+ return;
66
+ // Skip the render→service `[视图]` re-injection — the original render entry
67
+ // already reached every consumer; re-forwarding would duplicate it.
68
+ if (isRenderForwardEvent(params, RENDER_FORWARD_SOURCE_URL))
69
+ return;
70
+ const level = mapConsoleApiType(params.type);
71
+ const rawArgs = Array.isArray(params.args) ? params.args : [];
72
+ let args;
73
+ try {
74
+ args = await Promise.all(rawArgs.map((a) => resolveArg(a)));
75
+ }
76
+ catch {
77
+ args = rawArgs.map((a) => remoteObjectToValue(a));
78
+ }
79
+ if (disposed)
80
+ return;
81
+ emit({ source: 'service', level, args, ts: Date.now() });
82
+ }
83
+ const onMessage = (_event, method, params) => {
84
+ if (disposed)
85
+ return;
86
+ if (method !== 'Runtime.consoleAPICalled')
87
+ return;
88
+ const p = params;
89
+ tail = tail.then(() => handleConsoleApi(p)).catch(() => { });
90
+ };
91
+ function stop() {
92
+ if (disposed)
93
+ return;
94
+ disposed = true;
95
+ try {
96
+ closedSub?.dispose();
97
+ }
98
+ catch { /* gone */ }
99
+ try {
100
+ serviceWc.debugger.removeListener('message', onMessage);
101
+ }
102
+ catch { /* gone */ }
103
+ if (selfAttached) {
104
+ try {
105
+ if (!serviceWc.isDestroyed() && serviceWc.debugger.isAttached())
106
+ serviceWc.debugger.detach();
107
+ }
108
+ catch { /* already detached / destroyed */ }
109
+ selfAttached = false;
110
+ }
111
+ }
112
+ if (!usableDebugger()) {
113
+ disposed = true;
114
+ return { stop() { } };
115
+ }
116
+ try {
117
+ serviceWc.debugger.on('message', onMessage);
118
+ }
119
+ catch {
120
+ stop();
121
+ return { stop() { } };
122
+ }
123
+ // Enable Runtime so consoleAPICalled flows. Best-effort — a mid-destroy host
124
+ // rejects, in which case there's simply nothing to capture.
125
+ serviceWc.debugger.sendCommand('Runtime.enable').catch(() => { });
126
+ // Bind teardown to the service wc's real destroy. The service window is
127
+ // pool-recycled, but 'closed' fires only on hard destroy — the right lifetime
128
+ // for a session we attached to THIS wc. `on('closed')` returns a handle whose
129
+ // dispose() removes the listener WITHOUT firing it.
130
+ try {
131
+ if (connections)
132
+ closedSub = connections.acquire(serviceWc).on('closed', stop);
133
+ else
134
+ serviceWc.once('destroyed', stop);
135
+ }
136
+ catch { /* fake/minimal wc in tests */ }
137
+ return { stop };
138
+ }
139
+ //# sourceMappingURL=index.js.map
@@ -26,7 +26,7 @@ export declare class UpdateManager {
26
26
  private downloadedPath;
27
27
  /**
28
28
  * Whether this instance has already emitted `Available` once. The periodic
29
- * check fires forever (interval default 1h), and downstream UI (qdmp shell
29
+ * check fires forever (interval default 1h), and downstream UI (a downstream host shell
30
30
  * renders a native toast / Notification) is not idempotent — every event
31
31
  * stacks. We dedupe at the source: at most one Available per instance
32
32
  * lifetime, regardless of how the upstream `info.version` shifts. The user
@@ -9,7 +9,7 @@ export class UpdateManager {
9
9
  downloadedPath = null;
10
10
  /**
11
11
  * Whether this instance has already emitted `Available` once. The periodic
12
- * check fires forever (interval default 1h), and downstream UI (qdmp shell
12
+ * check fires forever (interval default 1h), and downstream UI (a downstream host shell
13
13
  * renders a native toast / Notification) is not idempotent — every event
14
14
  * stacks. We dedupe at the source: at most one Available per instance
15
15
  * lifetime, regardless of how the upstream `info.version` shifts. The user
@@ -34,6 +34,13 @@ export interface ViewManagerContext {
34
34
  * console line is the fallback). Optional so partial test contexts compile.
35
35
  */
36
36
  networkForward?: WorkbenchContext['networkForward'];
37
+ /**
38
+ * Always-on console fan-out (set by `installBridgeRouter`). The service-console
39
+ * capture feeds service-layer `consoleAPICalled` entries here so they reach
40
+ * automation; mirrors how render entries arrive via the bridge. Optional so
41
+ * partial test contexts compile.
42
+ */
43
+ consoleForwarder?: WorkbenchContext['consoleForwarder'];
37
44
  /**
38
45
  * Per-context registry of host-registered simulator custom APIs. The
39
46
  * native-host simulator is a top-level WebContentsView with no embedder