@dsh-workbench/shared 0.3.1 → 0.3.2

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/dist/components/MockStrip.css +1 -11
  2. package/dist/host/browser-find.js +2 -2
  3. package/dist/host/browser-read.d.ts +2 -1
  4. package/dist/host/browser-read.js +3 -2
  5. package/dist/lib/lint-probe.d.ts +1 -0
  6. package/dist/lib/lint-probe.js +6 -0
  7. package/package.json +2 -2
  8. package/src/host/__tests__/browser-find.test.ts +11 -0
  9. package/src/host/__tests__/browser-read.test.ts +33 -2
  10. package/src/host/browser-find.ts +2 -2
  11. package/src/host/browser-read.ts +3 -2
  12. package/dist/components/StateDot.d.ts +0 -5
  13. package/dist/components/StateDot.js +0 -10
  14. package/dist/host/__tests__/fakes.d.ts +0 -41
  15. package/dist/host/__tests__/fakes.js +0 -76
  16. package/dist/host/__tests__/preflight-fixtures.d.ts +0 -19
  17. package/dist/host/__tests__/preflight-fixtures.js +0 -47
  18. package/dist/host/context-inject.d.ts +0 -24
  19. package/dist/host/context-inject.js +0 -74
  20. package/dist/host/events.d.ts +0 -9
  21. package/dist/host/events.js +0 -70
  22. package/dist/host/mount-units.d.ts +0 -52
  23. package/dist/host/mount-units.js +0 -186
  24. package/dist/host/preflight.d.ts +0 -58
  25. package/dist/host/preflight.js +0 -233
  26. package/dist/host/probe-cache.d.ts +0 -6
  27. package/dist/host/probe-cache.js +0 -19
  28. package/dist/host/probe-libs.d.ts +0 -45
  29. package/dist/host/probe-libs.js +0 -61
  30. package/dist/host/probe-units.d.ts +0 -17
  31. package/dist/host/probe-units.js +0 -102
  32. package/dist/host/probe-verdict.d.ts +0 -7
  33. package/dist/host/probe-verdict.js +0 -143
  34. package/dist/host/tool-scope.d.ts +0 -32
  35. package/dist/host/tool-scope.js +0 -84
  36. package/dist/host/unit-readiness.d.ts +0 -7
  37. package/dist/host/unit-readiness.js +0 -5
  38. package/dist/host/unit-registry.d.ts +0 -9
  39. package/dist/host/unit-registry.js +0 -33
  40. package/dist/host/version-range.d.ts +0 -2
  41. package/dist/host/version-range.js +0 -73
@@ -36,14 +36,4 @@
36
36
  border-color: transparent;
37
37
  background: var(--dsw-alias-brand-primary);
38
38
  color: var(--dsw-alias-label-primary-foreground);
39
- }
40
-
41
- .wb-mockChipOn:hover {
42
- border-color: transparent;
43
- background: var(--dsw-alias-brand-primary);
44
- color: var(--dsw-alias-label-primary-foreground);
45
- }
46
-
47
- .wb-panelBody.wb-panelMany .wb-mockStrip {
48
- display: none;
49
- }
39
+ }
@@ -1,5 +1,5 @@
1
1
  import { parseJsonLoose } from '../lib/text.js';
2
- import { BROWSER_BRIDGE_SESSION } from './browser-read.js';
2
+ import { BROWSER_READ_SESSION } from './browser-read.js';
3
3
  import { backgroundLine } from './opencli-window.js';
4
4
  import { runShell } from './shell.js';
5
5
  const DEFAULT_TIMEOUT_MS = 25000;
@@ -152,7 +152,7 @@ async function openOnce(shell, command, session, url, timeoutMs) {
152
152
  export async function browserFind(options) {
153
153
  const { shell, css, url, open = true } = options;
154
154
  const command = options.command ?? 'opencli';
155
- const session = options.session ?? BROWSER_BRIDGE_SESSION;
155
+ const session = options.session ?? BROWSER_READ_SESSION;
156
156
  const limit = options.limit ?? DEFAULT_LIMIT;
157
157
  const textMax = options.textMax ?? DEFAULT_TEXT_MAX;
158
158
  const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
@@ -1,5 +1,6 @@
1
1
  import type { DshShell } from '../api/dsh.js';
2
- export declare const BROWSER_BRIDGE_SESSION = "workbench";
2
+ export declare const BROWSER_SCREEN_SESSION = "workbench";
3
+ export declare const BROWSER_READ_SESSION = "workbench-read";
3
4
  export interface BrowserReadResult {
4
5
  readonly ok: boolean;
5
6
  readonly code: string;
@@ -1,7 +1,8 @@
1
1
  import { parseJsonLoose, tailLines } from '../lib/text.js';
2
2
  import { backgroundLine } from './opencli-window.js';
3
3
  import { runShell } from './shell.js';
4
- export const BROWSER_BRIDGE_SESSION = 'workbench';
4
+ export const BROWSER_SCREEN_SESSION = 'workbench';
5
+ export const BROWSER_READ_SESSION = 'workbench-read';
5
6
  const DEFAULT_TIMEOUT_MS = 25000;
6
7
  const DEFAULT_RETRY_MS = 1200;
7
8
  const DEFAULT_SETTLE_MS = 900;
@@ -158,7 +159,7 @@ async function openOnce(shell, command, session, url, timeoutMs) {
158
159
  export async function browserRead(options) {
159
160
  const { shell, script, url, loginUrl, open = true } = options;
160
161
  const command = options.command ?? 'opencli';
161
- const session = options.session ?? BROWSER_BRIDGE_SESSION;
162
+ const session = options.session ?? BROWSER_READ_SESSION;
162
163
  const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
163
164
  const retryMs = options.retryMs ?? DEFAULT_RETRY_MS;
164
165
  const settleMs = options.settleMs ?? DEFAULT_SETTLE_MS;
@@ -0,0 +1 @@
1
+ export declare function useLintProbe(): number;
@@ -0,0 +1,6 @@
1
+ import { useRef } from './react';
2
+ export function useLintProbe() {
3
+ const probeRef = useRef(0);
4
+ probeRef.current = 1;
5
+ return probeRef.current;
6
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dsh-workbench/shared",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "工作台与模块共享的纯代码与契约类型:类型、纯逻辑、哑组件、平台解析。宿主侧的东西从 /host 进(不影响浏览器产物)。",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -40,7 +40,7 @@
40
40
  },
41
41
  "scripts": {
42
42
  "build": "tsc -p tsconfig.build.json && node scripts/copy-css.mjs",
43
- "typecheck": "tsc --noEmit",
43
+ "typecheck": "tsc --noEmit --incremental --tsBuildInfoFile node_modules/.cache/tsconfig.tsbuildinfo",
44
44
  "test": "vitest run",
45
45
  "coverage": "vitest run --coverage",
46
46
  "barrels": "node scripts/barrels.mjs",
@@ -1,4 +1,5 @@
1
1
  import { describe, expect, it } from 'vitest';
2
+ import { BROWSER_SCREEN_SESSION } from '../browser-read.js';
2
3
  import {
3
4
  browserFind,
4
5
  cellRows,
@@ -317,3 +318,13 @@ describe('browserFind · 后台静默(问页 + 读 + 打开都不许抢焦点
317
318
  expect(backgroundless(commands)).toEqual([]);
318
319
  });
319
320
  });
321
+
322
+ describe('browserFind · 读有它自己那条会话(不许碰屏那条)', () => {
323
+ it('默认那条命令里一条都不带屏那条会话名', async () => {
324
+ const { shell, commands } = fakeShell({ url: ['about:blank', SITE], find: [{ stdout: MATCH }] });
325
+ await browserFind({ shell, css: CSS, url: SITE, ...FAST });
326
+
327
+ expect(commands.length).toBeGreaterThan(0);
328
+ expect(commands.some((one) => one.includes(`'${BROWSER_SCREEN_SESSION}'`))).toBe(false);
329
+ });
330
+ });
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from 'vitest';
2
- import { browserRead } from '../browser-read.js';
2
+ import { BROWSER_READ_SESSION, BROWSER_SCREEN_SESSION, browserRead } from '../browser-read.js';
3
3
 
4
4
  interface Step {
5
5
  readonly stdout?: string;
@@ -45,7 +45,7 @@ describe('browserRead · 读成功', () => {
45
45
  expect(out.ok).toBe(true);
46
46
  expect(out.code).toBe('ok');
47
47
  expect(out.value).toEqual({ ok: true, n: 3 });
48
- expect(commands[0]).toContain("'browser' 'workbench' 'eval'");
48
+ expect(commands[0]).toContain(`'browser' '${BROWSER_READ_SESSION}' 'eval'`);
49
49
  });
50
50
 
51
51
  it('脚本没回 ok 字段(例如直接回一个数)也算读到了', async () => {
@@ -123,6 +123,37 @@ describe('browserRead · 两拍(不在站点上就先打开)', () => {
123
123
  });
124
124
  });
125
125
 
126
+ describe('browserRead · 读有它自己那条会话(不许碰屏那条)', () => {
127
+ it('默认那条不是工坊绑用户当前页用的那条', () => {
128
+ expect(BROWSER_READ_SESSION).not.toBe(BROWSER_SCREEN_SESSION);
129
+ });
130
+
131
+ it('两拍三条命令里一条都不带屏那条会话名', async () => {
132
+ const { shell, commands } = fakeShell([
133
+ { stdout: JSON.stringify({ ok: false, code: 'not-on-site' }) },
134
+ { stdout: 'opened' },
135
+ { stdout: JSON.stringify({ ok: true, n: 1 }) },
136
+ ]);
137
+ const out = await browserRead({ shell, script, url: URL, ...FAST });
138
+
139
+ expect(out.ok).toBe(true);
140
+ expect(commands.some((one) => one.includes(`'${BROWSER_SCREEN_SESSION}'`))).toBe(false);
141
+ });
142
+
143
+ it('调用点自己给的 session 说了算(模块可以自己开一条)', async () => {
144
+ const { shell, commands } = fakeShell([
145
+ { stdout: JSON.stringify({ ok: false, code: 'not-on-site' }) },
146
+ { stdout: 'opened' },
147
+ { stdout: JSON.stringify({ ok: true }) },
148
+ ]);
149
+ const out = await browserRead({ shell, script, url: URL, session: 'llm-wallet', ...FAST });
150
+
151
+ expect(out.ok).toBe(true);
152
+ expect(commands.every((one) => one.includes("'llm-wallet'"))).toBe(true);
153
+ expect(commands.some((one) => one.includes(`'${BROWSER_SCREEN_SESSION}'`))).toBe(false);
154
+ });
155
+ });
156
+
126
157
  describe('browserRead · 命令这一层的问题', () => {
127
158
  it('刚重启时第一次失败:等一下再读一次就成功(实测的那个坑)', async () => {
128
159
  const { shell, commands } = fakeShell([
@@ -1,6 +1,6 @@
1
1
  import type { DshShell } from '../api/dsh.js';
2
2
  import { parseJsonLoose } from '../lib/text.js';
3
- import { BROWSER_BRIDGE_SESSION } from './browser-read.js';
3
+ import { BROWSER_READ_SESSION } from './browser-read.js';
4
4
  import { backgroundLine } from './opencli-window.js';
5
5
  import { runShell } from './shell.js';
6
6
 
@@ -215,7 +215,7 @@ async function openOnce(
215
215
  export async function browserFind(options: BrowserFindOptions): Promise<BrowserFindResult> {
216
216
  const { shell, css, url, open = true } = options;
217
217
  const command = options.command ?? 'opencli';
218
- const session = options.session ?? BROWSER_BRIDGE_SESSION;
218
+ const session = options.session ?? BROWSER_READ_SESSION;
219
219
  const limit = options.limit ?? DEFAULT_LIMIT;
220
220
  const textMax = options.textMax ?? DEFAULT_TEXT_MAX;
221
221
  const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
@@ -3,7 +3,8 @@ import { parseJsonLoose, tailLines } from '../lib/text.js';
3
3
  import { backgroundLine } from './opencli-window.js';
4
4
  import { runShell } from './shell.js';
5
5
 
6
- export const BROWSER_BRIDGE_SESSION = 'workbench';
6
+ export const BROWSER_SCREEN_SESSION = 'workbench';
7
+ export const BROWSER_READ_SESSION = 'workbench-read';
7
8
 
8
9
  const DEFAULT_TIMEOUT_MS = 25000;
9
10
  const DEFAULT_RETRY_MS = 1200;
@@ -226,7 +227,7 @@ async function openOnce(
226
227
  export async function browserRead(options: BrowserReadOptions): Promise<BrowserReadResult> {
227
228
  const { shell, script, url, loginUrl, open = true } = options;
228
229
  const command = options.command ?? 'opencli';
229
- const session = options.session ?? BROWSER_BRIDGE_SESSION;
230
+ const session = options.session ?? BROWSER_READ_SESSION;
230
231
  const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
231
232
  const retryMs = options.retryMs ?? DEFAULT_RETRY_MS;
232
233
  const settleMs = options.settleMs ?? DEFAULT_SETTLE_MS;
@@ -1,5 +0,0 @@
1
- export type DotState = 'ongoing' | 'warning' | 'done' | 'idle' | string;
2
- export declare function StateDot({ state, size }: {
3
- state: DotState;
4
- size: number;
5
- }): JSX.Element;
@@ -1,10 +0,0 @@
1
- import { h } from '../lib/react.js';
2
- export function StateDot({ state, size }) {
3
- if (state === 'ongoing') {
4
- return (h("svg", { className: "wb-loading", viewBox: "0 0 24 24", width: size, height: size, "aria-hidden": "true", style: { display: 'block' } },
5
- h("g", { className: "wb-loadingMotion" },
6
- h("circle", { className: "wb-loadingTrack", cx: 12, cy: 12, r: 9.5 }),
7
- h("circle", { className: "wb-loadingArc", cx: 12, cy: 12, r: 9.5 }))));
8
- }
9
- return (h("span", { className: "wb-stateDot", "data-state": state, style: { width: size, height: size }, "aria-hidden": "true" }));
10
- }
@@ -1,41 +0,0 @@
1
- import type { DshShell } from '../../api/dsh.js';
2
- import type { Opencli } from '../../host/opencli.js';
3
- export interface ShellCall {
4
- command: string;
5
- workdir?: string;
6
- timeoutMs?: number;
7
- onExpiry?: string;
8
- stdoutMaxBytes?: number;
9
- }
10
- export interface ShellReply {
11
- stdout?: string;
12
- stderr?: string;
13
- exitCode?: number | null;
14
- timedOut?: boolean;
15
- aborted?: boolean;
16
- failWith?: Error;
17
- }
18
- export declare function createFakeShell(replies?: ShellReply[] | ((call: ShellCall, index: number) => ShellReply)): {
19
- shell: DshShell;
20
- calls: ShellCall[];
21
- peak: () => number;
22
- commands: () => string[];
23
- };
24
- export interface OpencliCall {
25
- args: readonly string[];
26
- key?: string;
27
- workdir?: string;
28
- refresh?: boolean;
29
- }
30
- export declare function createFakeOpencli(replies?: Array<unknown | Error> | ((call: OpencliCall, index: number) => unknown | Error)): {
31
- opencli: Opencli;
32
- calls: OpencliCall[];
33
- keys: () => (string | undefined)[];
34
- };
35
- export declare function createFakeLogger(): {
36
- logger: {
37
- info: () => void;
38
- warn: (message: string) => void;
39
- };
40
- warnings: string[];
41
- };
@@ -1,76 +0,0 @@
1
- import { setTimeout as delay } from 'node:timers/promises';
2
- const tick = () => delay(0);
3
- export function createFakeShell(replies = []) {
4
- const calls = [];
5
- let running = 0;
6
- let peak = 0;
7
- const replyAt = (index) => typeof replies === 'function' ? replies(calls[index], index) : (replies[index] ?? {});
8
- const shell = {
9
- resolve(request) {
10
- calls.push(request);
11
- return request;
12
- },
13
- async execute() {
14
- const index = calls.length - 1;
15
- const reply = replyAt(index);
16
- if (reply.failWith !== undefined)
17
- throw reply.failWith;
18
- running += 1;
19
- peak = Math.max(peak, running);
20
- return {
21
- async result() {
22
- await tick();
23
- running -= 1;
24
- return {
25
- exitCode: reply.exitCode ?? 0,
26
- timedOut: reply.timedOut ?? false,
27
- aborted: reply.aborted ?? false,
28
- stdout: { text: reply.stdout ?? '', truncated: false },
29
- stderr: { text: reply.stderr ?? '', truncated: false },
30
- };
31
- },
32
- };
33
- },
34
- };
35
- return {
36
- shell,
37
- calls,
38
- peak: () => peak,
39
- commands: () => calls.map((call) => call.command),
40
- };
41
- }
42
- export function createFakeOpencli(replies = []) {
43
- const calls = [];
44
- const opencli = {
45
- async json(args, options = {}) {
46
- const call = {
47
- args,
48
- ...(options.key === undefined ? {} : { key: options.key }),
49
- ...(options.workdir === undefined ? {} : { workdir: options.workdir }),
50
- };
51
- calls.push(call);
52
- const index = calls.length - 1;
53
- const reply = typeof replies === 'function' ? replies(call, index) : (replies[index] ?? []);
54
- if (reply instanceof Error)
55
- throw reply;
56
- return { value: reply, at: Date.now() };
57
- },
58
- };
59
- return {
60
- opencli,
61
- calls,
62
- keys: () => calls.map((call) => call.key),
63
- };
64
- }
65
- export function createFakeLogger() {
66
- const warnings = [];
67
- return {
68
- logger: {
69
- info: () => { },
70
- warn: (message) => {
71
- warnings.push(message);
72
- },
73
- },
74
- warnings,
75
- };
76
- }
@@ -1,19 +0,0 @@
1
- import { createFakeShell } from './fakes';
2
- export declare const GOOD_BRIDGE_REPORT: string;
3
- export declare const ALL_OK: {
4
- stdout: string;
5
- }[];
6
- export declare const STALE_DAEMON_BRIDGE: string;
7
- export declare const BROKEN_BRIDGE: string;
8
- export declare function build(replies: Parameters<typeof createFakeShell>[0], install?: () => Promise<{
9
- ok: boolean;
10
- detail: string;
11
- }>, warmReads?: () => readonly (readonly string[])[]): {
12
- fake: {
13
- shell: import("../..").DshShell;
14
- calls: import("./fakes").ShellCall[];
15
- peak: () => number;
16
- commands: () => string[];
17
- };
18
- preflight: import("../preflight").Preflight;
19
- };
@@ -1,47 +0,0 @@
1
- import { createPreflight } from '../preflight';
2
- import { createFakeShell } from './fakes';
3
- export const GOOD_BRIDGE_REPORT = [
4
- 'opencli v1.8.7 doctor (node v24.15.0)',
5
- '',
6
- '[OK] Daemon: running on port 19825 (v1.8.7)',
7
- '[OK] Extension: connected (v1.0.24)',
8
- '[OK] Connectivity: connected in 0.0s',
9
- '',
10
- 'Everything looks good!',
11
- ].join('\n');
12
- export const ALL_OK = [{ stdout: '1.8.8\n' }, { stdout: GOOD_BRIDGE_REPORT }];
13
- export const STALE_DAEMON_BRIDGE = [
14
- 'opencli v1.8.7 doctor (node v24.15.0)',
15
- '',
16
- '[STALE] Daemon: running on port 19825 (v1.8.8 != CLI v1.8.7)',
17
- '[OK] Extension: connected (v1.0.24)',
18
- '[OK] Connectivity: connected in 0.0s',
19
- '',
20
- 'Issues:',
21
- ' • Stale daemon detected: daemon v1.8.8 != CLI v1.8.7.',
22
- ' Run: opencli daemon restart',
23
- ].join('\n');
24
- export const BROKEN_BRIDGE = [
25
- 'opencli v1.8.7 doctor (node v24.15.0)',
26
- '',
27
- '[MISSING] Daemon: not running',
28
- '[MISSING] Extension: not connected',
29
- '[FAIL] Connectivity: failed (daemon not reachable)',
30
- '',
31
- 'Issues:',
32
- " • Daemon is not running. It should start automatically when you run an opencli browser command.",
33
- ' • Daemon is running but the Chrome/Chromium extension is not connected.',
34
- 'If the extension is not installed:',
35
- ' 1. Download from https://github.com/jackwener/opencli/releases',
36
- ' 2. Open chrome://extensions/ → Enable Developer Mode',
37
- ' 3. Click "Load unpacked" → select the extension folder',
38
- ].join('\n');
39
- export function build(replies, install, warmReads) {
40
- const fake = createFakeShell(replies);
41
- const preflight = createPreflight({
42
- shell: fake.shell,
43
- ...(install === undefined ? {} : { install }),
44
- ...(warmReads === undefined ? {} : { warmReads }),
45
- });
46
- return { fake, preflight };
47
- }
@@ -1,24 +0,0 @@
1
- import { type ContextRef } from '@dsh-workbench/shared';
2
- import type { ContextRenderer } from './unit-face';
3
- export declare const CONTEXT_SOURCE_KIND = "workbench";
4
- export interface ScannableMessage {
5
- readonly source?: {
6
- readonly kind?: string;
7
- };
8
- readonly content?: readonly {
9
- readonly type?: string;
10
- readonly text?: string;
11
- }[];
12
- }
13
- export type { ContextRenderer };
14
- export declare function collectContextRefs(messages: readonly ScannableMessage[]): ContextRef[];
15
- export declare function buildContextMessage(text: string, summary: string): unknown;
16
- export interface ContextInjector {
17
- inject(messages: readonly ScannableMessage[], agent: unknown): Promise<unknown | undefined>;
18
- }
19
- export declare function createContextInjector(options: {
20
- renderers: ReadonlyMap<string, ContextRenderer>;
21
- logger?: {
22
- warn?(message: string): void;
23
- } | undefined;
24
- }): ContextInjector;
@@ -1,74 +0,0 @@
1
- import { randomUUID } from 'node:crypto';
2
- import { encodeRef, parseMarkers } from '@dsh-workbench/shared';
3
- export const CONTEXT_SOURCE_KIND = 'workbench';
4
- export function collectContextRefs(messages) {
5
- const seen = new Set();
6
- const refs = [];
7
- for (const message of messages) {
8
- if (message.source?.kind !== 'user')
9
- continue;
10
- for (const block of message.content ?? []) {
11
- if (block.type !== 'text' || typeof block.text !== 'string')
12
- continue;
13
- for (const ref of parseMarkers(block.text)) {
14
- const key = encodeRef(ref);
15
- if (seen.has(key))
16
- continue;
17
- seen.add(key);
18
- refs.push(ref);
19
- }
20
- }
21
- }
22
- return refs;
23
- }
24
- export function buildContextMessage(text, summary) {
25
- return {
26
- role: 'user',
27
- id: randomUUID(),
28
- content: [{ type: 'text', text }],
29
- source: { kind: CONTEXT_SOURCE_KIND, form: 'notice', summary },
30
- };
31
- }
32
- export function createContextInjector(options) {
33
- const done = new WeakMap();
34
- return {
35
- async inject(messages, agent) {
36
- const refs = collectContextRefs(messages);
37
- if (refs.length === 0)
38
- return undefined;
39
- const key = typeof agent === 'object' && agent !== null ? agent : undefined;
40
- const seen = key === undefined ? undefined : (done.get(key) ?? new Set());
41
- if (key !== undefined && seen !== undefined)
42
- done.set(key, seen);
43
- const fresh = [];
44
- for (const ref of refs) {
45
- const encoded = encodeRef(ref);
46
- if (seen?.has(encoded) === true)
47
- continue;
48
- seen?.add(encoded);
49
- fresh.push(ref);
50
- }
51
- if (fresh.length === 0)
52
- return undefined;
53
- const texts = [];
54
- for (const ref of fresh) {
55
- const render = options.renderers.get(ref.kind);
56
- if (render === undefined)
57
- continue;
58
- let text;
59
- try {
60
- text = await render(ref);
61
- }
62
- catch (error) {
63
- options.logger?.warn?.(`[dsh-workbench] 渲染对话引用失败:${String(error)}`);
64
- text = `工作台面板引用了一条内容(${encodeRef(ref)}),但当前渲染不出来(项目未选中或数据源不可用)。`;
65
- }
66
- if (text !== '')
67
- texts.push(text);
68
- }
69
- if (texts.length === 0)
70
- return undefined;
71
- return buildContextMessage(texts.join('\n\n'), `工作台引用:${fresh.length} 条`);
72
- },
73
- };
74
- }
@@ -1,9 +0,0 @@
1
- import type { DshHostContext, DshRequest, DshResponse } from '@dsh-workbench/shared';
2
- import type { FactLog } from './facts';
3
- export interface FactStream {
4
- handler(req: DshRequest, res: DshResponse): void;
5
- closeAll(): void;
6
- size(): number;
7
- }
8
- export declare function createFactStream(log: FactLog): FactStream;
9
- export declare function registerFactStream(ctx: DshHostContext, log: FactLog): () => void;
@@ -1,70 +0,0 @@
1
- import { EVENTS_PATH } from '@dsh-workbench/shared';
2
- const HEARTBEAT_MS = 25_000;
3
- function frame(payload) {
4
- return `data: ${JSON.stringify(payload)}\n\n`;
5
- }
6
- export function createFactStream(log) {
7
- const connections = new Set();
8
- const cleanups = new Map();
9
- const drop = (res) => {
10
- cleanups.get(res)?.();
11
- cleanups.delete(res);
12
- connections.delete(res);
13
- };
14
- return {
15
- size: () => connections.size,
16
- closeAll() {
17
- for (const res of [...connections]) {
18
- drop(res);
19
- res.end?.();
20
- }
21
- },
22
- handler(req, res) {
23
- if (req.method !== undefined && req.method !== 'GET') {
24
- res.statusCode = 405;
25
- res.end();
26
- return;
27
- }
28
- const url = req.url ?? '/';
29
- const cut = url.indexOf('?');
30
- const since = Number(new URLSearchParams(cut === -1 ? '' : url.slice(cut + 1)).get('since') ?? 0) || 0;
31
- const opening = log.since(since);
32
- if (typeof res.write !== 'function') {
33
- res.statusCode = 200;
34
- res.setHeader('content-type', 'application/json; charset=utf-8');
35
- res.end(JSON.stringify({ ok: true, ...opening }));
36
- return;
37
- }
38
- const headers = {
39
- 'content-type': 'text/event-stream; charset=utf-8',
40
- 'cache-control': 'no-store, no-transform',
41
- connection: 'keep-alive',
42
- };
43
- if (typeof res.writeHead === 'function')
44
- res.writeHead(200, headers);
45
- else {
46
- res.statusCode = 200;
47
- for (const [name, value] of Object.entries(headers))
48
- res.setHeader(name, value);
49
- }
50
- res.write(': connected\n\n');
51
- res.write(frame({ type: 'sync', ...opening }));
52
- connections.add(res);
53
- const timer = setInterval(() => res.write?.(': ping\n\n'), HEARTBEAT_MS);
54
- const unsubscribe = log.subscribe((change) => res.write?.(frame({ type: 'bump', ...change })));
55
- cleanups.set(res, () => {
56
- clearInterval(timer);
57
- unsubscribe();
58
- });
59
- res.on?.('close', () => drop(res));
60
- },
61
- };
62
- }
63
- export function registerFactStream(ctx, log) {
64
- const stream = createFactStream(log);
65
- const unregister = ctx.webServer.register({ kind: 'exact', path: EVENTS_PATH, handler: stream.handler });
66
- return () => {
67
- stream.closeAll();
68
- unregister();
69
- };
70
- }
@@ -1,52 +0,0 @@
1
- import type { DshHostContext } from '@dsh-workbench/shared';
2
- import type { UnitManifest } from '@dsh-workbench/shared';
3
- import { type RouteTable } from './http';
4
- import type { ContextRenderer, UnitHostFace, UnitMountDeps, UnitMountResult, UnitCall } from './unit-face';
5
- import { type UnitReadiness } from './unit-readiness';
6
- export interface MountedUnit {
7
- readonly face: UnitHostFace;
8
- readonly result: UnitMountResult;
9
- readonly readiness: UnitReadiness;
10
- }
11
- export interface UnitsPlan {
12
- readonly mounted: readonly MountedUnit[];
13
- readonly routes: RouteTable;
14
- readonly gate: UnitMountResult['gate'];
15
- readonly prefs: ReadonlyArray<{
16
- readonly resource: string;
17
- readonly unit: string;
18
- handle(payload: unknown): unknown;
19
- }>;
20
- readonly actions: ReadonlyArray<{
21
- readonly resource: string;
22
- readonly unit: string;
23
- handle(payload: unknown): unknown;
24
- }>;
25
- readonly provision: UnitMountResult['provision'];
26
- readonly contextRenderers: ReadonlyMap<string, ContextRenderer>;
27
- readonly owners: ReadonlyMap<string, string>;
28
- readonly routesOfUnit: ReadonlyMap<string, readonly string[]>;
29
- readonly factsOf: ReadonlyMap<string, (call: UnitCall) => readonly string[] | undefined>;
30
- readonly skipped: ReadonlyArray<{
31
- readonly id: string;
32
- readonly missing: readonly string[];
33
- }>;
34
- readonly warmReads: () => readonly (readonly string[])[];
35
- readonly disposeLongTasks: () => void;
36
- }
37
- export declare function mountUnits(faces: readonly UnitHostFace[], deps: {
38
- ctx: DshHostContext;
39
- logger?: {
40
- info?(m: string): void;
41
- warn?(m: string): void;
42
- } | undefined;
43
- manifests: () => readonly UnitManifest[];
44
- opencliCommand?: string;
45
- probe?: UnitMountDeps['probe'];
46
- store?: UnitMountDeps['store'];
47
- values?: UnitMountDeps['values'];
48
- longTasks?: {
49
- readonly scope: string;
50
- readonly bump: (facts: readonly string[]) => void;
51
- } | undefined;
52
- }): UnitsPlan;