dsh-pictor 0.2.2 → 0.2.4

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/README.md CHANGED
@@ -22,7 +22,9 @@ Replace `<profile>` with the target profile name (e.g. `desktop`, `web`) and sta
22
22
 
23
23
  ## Supported dsh version
24
24
 
25
- Pictor is built and tested against **DSH Desktop 0.7.2** (bundled Harness **0.1.2-alpha.1**). The DeepSeek Harness client API moves quickly — the workspace/session services it exposes keep changing — so this plugin intentionally tracks one harness generation. Other versions (older or newer) may work or may break without notice, and are outside our support scope. If something stops working after a DSH update, update dsh to the version above rather than debugging the mismatch.
25
+ Pictor is built and tested against **DSH Desktop ≥0.10** (bundled Harness 0.2.x; verified against 0.2.0-rc.2 and 0.2.1-alpha.1). The DeepSeek Harness client API moves quickly — the workspace/session services it exposes keep changing — so this plugin intentionally tracks one harness generation. Other versions (older or newer) may work or may break without notice, and are outside our support scope. If something stops working after a DSH update, update dsh to the version above rather than debugging the mismatch.
26
+
27
+ Local CI: `bash scripts/ci.sh` runs the full suite (unit + host integration + package integrity + browser e2e) against the dsh version pinned in `scripts/dsh-version`. `npm run hooks:install` wires it to pre-push.
26
28
 
27
29
  ## Usage
28
30
 
package/lib/client.js CHANGED
@@ -398,9 +398,14 @@ function t(key, vars) {
398
398
 
399
399
  // ---------- 工具 ----------
400
400
 
401
+ // RPC 走 webServer 的 /pictor/rpc/* prefix 路由(同源 HTTP,不再经 connection.rpc.call)。
401
402
  function rpc(ctx, endpoint, payload) {
402
403
  // 服务端 schema 要求 payload 字段必须存在(对象);缺省补 {},否则序列化丢键被拒。
403
- return ctx.connection.rpc.call(RPC, endpoint, payload === undefined ? {} : payload).then((result) => {
404
+ return fetch(RPC + '/rpc/' + encodeURIComponent(endpoint), {
405
+ method: 'POST',
406
+ headers: { 'content-type': 'application/json' },
407
+ body: JSON.stringify(payload === undefined ? {} : payload),
408
+ }).then((r) => r.json()).then((result) => {
404
409
  if (result && result.ok === false) {
405
410
  const msg = result.error && result.error.message ? result.error.message : String(result.error || 'RPC 调用失败')
406
411
  throw new Error(msg)
@@ -490,14 +495,27 @@ function sanitizePaste(html) {
490
495
  // 在就调哪个,老版本前端不受影响。
491
496
  function sessionConnectFn(ctx, ws) {
492
497
  let uiWs = null
493
- try { uiWs = ctx.uiWorkspace || ((ctx.get && ctx.get('uiWorkspace')) || null) } catch { /* ignore */ }
498
+ try { uiWs = (ctx.get && ctx.get('uiWorkspace')) || null } catch { /* ignore */ }
494
499
  return (uiWs && typeof uiWs.connectWorkspace === 'function')
495
500
  ? uiWs.connectWorkspace.bind(uiWs)
496
501
  : (ws && typeof ws.connectWorkspace === 'function') ? ws.connectWorkspace.bind(ws) : null
497
502
  }
498
503
 
504
+ /**
505
+ * 从 workspaces.connectWorkspace / sessions.create 的返回里取 sessionId。
506
+ * 0.1.5 起 create/connectWorkspace 返回 {ok, value:{sessionId}} 信封;
507
+ * 早版本直接返回 id 字符串或 {id}。这里全兼容。
508
+ */
509
+ function extractSid(created) {
510
+ if (!created) return null
511
+ if (typeof created === 'string') return created
512
+ return created.sessionId || created.id
513
+ || (created.value && (created.value.sessionId || created.value.id))
514
+ || null
515
+ }
516
+
499
517
  async function pictorWorkspace(ctx) {
500
- const ws = ctx.workspaces || (ctx.get && ctx.get('workspaces'))
518
+ const ws = (ctx.get && ctx.get('workspaces'))
501
519
  const base = await rpc(ctx, 'config.get').then((c) => (c && c.dataRoot) || '')
502
520
  // 这里只用 list/create/delete/rename,新旧 services 都有;connectWorkspace
503
521
  // 的可用性由 driveProjectSession 检查。
@@ -540,13 +558,13 @@ async function pictorWorkspace(ctx) {
540
558
  }
541
559
 
542
560
  async function driveProjectSession(ctx, prompt) {
543
- const ws = ctx.workspaces || (ctx.get && ctx.get('workspaces'))
544
- const sessionsSvc = ctx.sessions || (ctx.get && ctx.get('sessions'))
561
+ const ws = (ctx.get && ctx.get('workspaces'))
562
+ const sessionsSvc = (ctx.get && ctx.get('sessions'))
545
563
  const connect = sessionConnectFn(ctx, ws)
546
564
  const canCreate = !!(sessionsSvc && typeof sessionsSvc.create === 'function')
547
565
  if (!(connect || canCreate) || !(sessionsSvc && typeof sessionsSvc.binding === 'function')) {
548
566
  let uiWs = null
549
- try { uiWs = ctx.uiWorkspace || ((ctx.get && ctx.get('uiWorkspace')) || null) } catch { /* ignore */ }
567
+ try { uiWs = (ctx.get && ctx.get('uiWorkspace')) || null } catch { /* ignore */ }
550
568
  const diag = { ws: !!ws, ses: !!sessionsSvc && typeof sessionsSvc.binding === 'function', uiws: !!uiWs, conn: !!connect, create: canCreate }
551
569
  console.warn('[pictor] session services unavailable', diag)
552
570
  try { window.__pictor_diag = diag } catch { /* ignore */ }
@@ -557,7 +575,7 @@ async function driveProjectSession(ctx, prompt) {
557
575
  let sessionId
558
576
  try {
559
577
  const created = connect ? await connect(wid) : await sessionsSvc.create({ workspaceId: wid })
560
- sessionId = created && typeof created === 'object' && created.id ? created.id : created
578
+ sessionId = extractSid(created)
561
579
  }
562
580
  catch (e) { throw new Error('创建会话失败:' + String((e && e.message) || e)) }
563
581
  const bound = sessionsSvc && typeof sessionsSvc.binding === 'function' ? sessionsSvc.binding(sessionId) : null
@@ -573,7 +591,7 @@ async function driveProjectSession(ctx, prompt) {
573
591
  async function drivePrompt(ctx, id, message) {
574
592
  const p = await rpc(ctx, 'project.prompt', { id, message })
575
593
  if (p && p.sessionId) {
576
- const sessionsSvc = ctx.sessions || (ctx.get && ctx.get('sessions'))
594
+ const sessionsSvc = (ctx.get && ctx.get('sessions'))
577
595
  if (sessionsSvc && typeof sessionsSvc.binding === 'function') {
578
596
  try {
579
597
  const bound = sessionsSvc.binding(p.sessionId)
@@ -1710,9 +1728,9 @@ function selectedLabel(ids) {
1710
1728
  // 调整了客户端 API),host 落盘 ~/.pictor/diag.jsonl,无需 DevTools 即可排查。
1711
1729
  function pictorDiag(ctx, phase) {
1712
1730
  let ws = null, ses = null, ui = null
1713
- try { ws = ctx && (ctx.workspaces || (ctx.get && ctx.get('workspaces'))) } catch { /* ignore */ }
1714
- try { ses = ctx && (ctx.sessions || (ctx.get && ctx.get('sessions'))) } catch { /* ignore */ }
1715
- try { ui = ctx && (ctx.uiWorkspace || (ctx.get && ctx.get('uiWorkspace'))) } catch { /* ignore */ }
1731
+ try { ws = ctx && (ctx.get && ctx.get('workspaces')) } catch { /* ignore */ }
1732
+ try { ses = ctx && (ctx.get && ctx.get('sessions')) } catch { /* ignore */ }
1733
+ try { ui = ctx && (ctx.get && ctx.get('uiWorkspace')) } catch { /* ignore */ }
1716
1734
  const body = {
1717
1735
  phase,
1718
1736
  ws: !!ws, ses: !!ses, ui: !!ui,
@@ -1730,7 +1748,7 @@ function pictorDiag(ctx, phase) {
1730
1748
 
1731
1749
  function apply(ctx) {
1732
1750
  try { pictorDiag(ctx, 'apply') } catch { /* ignore */ }
1733
- console.info('[dsh-pictor] apply 被调用', { hasSlots: Boolean(ctx && ctx.slots) })
1751
+ console.info('[dsh-pictor] apply 被调用', { hasSlots: Boolean(ctx.get && ctx.get('slots')) })
1734
1752
  if (typeof document !== 'undefined') {
1735
1753
  const styleId = 'dsh-pictor-style'
1736
1754
  if (!document.getElementById(styleId)) {
@@ -1740,7 +1758,7 @@ function apply(ctx) {
1740
1758
  document.head.appendChild(style)
1741
1759
  }
1742
1760
  }
1743
- const slots = ctx.slots || (ctx.get && ctx.get('slots'))
1761
+ const slots = (ctx.get && ctx.get('slots'))
1744
1762
  if (!slots) return
1745
1763
 
1746
1764
  const panel = { open: false, subs: new Set() }
package/lib/index.js CHANGED
@@ -106,7 +106,7 @@ function initialPrompt(dir) {
106
106
  return prompts.join("\n");
107
107
  }
108
108
  function apply(ctx, config = {}) {
109
- const connection = ctx.connection;
109
+ const connection = ctx.get?.("connection");
110
110
  const base = config.dataDir || process.env.PICTOR_HOME || join(homedir(), ".pictor");
111
111
  const INDEX_JSON = join(base, "index.json");
112
112
  const CONFIG_JSON = join(base, "pictor-config.json");
@@ -171,7 +171,7 @@ function apply(ctx, config = {}) {
171
171
  }
172
172
  function credentialsService() {
173
173
  try {
174
- return ctx.credentials || ctx.get?.("credentials");
174
+ return ctx.get?.("credentials");
175
175
  } catch {
176
176
  return null;
177
177
  }
@@ -676,20 +676,36 @@ function apply(ctx, config = {}) {
676
676
  throw new Error(`dsh-pictor: \u672A\u77E5\u7AEF\u70B9 ${endpoint}`);
677
677
  }
678
678
  }
679
- if (connection && connection.rpc && connection.rpc.handle) {
680
- connection.rpc.handle("/pictor", async (endpoint, payload) => {
681
- try {
682
- return { ok: true, value: await handle(endpoint, payload || {}) };
683
- } catch (e) {
684
- logError(`rpc:${endpoint}`, e);
685
- return { ok: false, error: { code: "internal", message: String(e?.message || e), details: {} } };
686
- }
687
- }, { authority: "loopback" });
688
- }
689
- const wsA = ctx.webServer || ctx.get?.("webServer");
679
+ const wsA = ctx.get?.("webServer");
690
680
  if (wsA && typeof wsA.register === "function") {
691
681
  const IMG_CACHE = { "content-type": "image/png", "cache-control": "public, max-age=3600" };
692
682
  const WEBP_CACHE = { "content-type": "image/webp", "cache-control": "public, max-age=3600" };
683
+ const RPC_PREFIX = "/pictor/rpc";
684
+ wsA.register({ kind: "prefix", path: RPC_PREFIX, handler: async (req, res) => {
685
+ try {
686
+ const pathname = new URL(req.url || "/", "http://x").pathname;
687
+ const endpoint = decodeURIComponent(pathname.slice(RPC_PREFIX.length + 1));
688
+ let payload = {};
689
+ if (req.method === "POST" || req.method === "PUT") {
690
+ const chunks = [];
691
+ for await (const c of req) chunks.push(Buffer.isBuffer(c) ? c : Buffer.from(c));
692
+ const raw = Buffer.concat(chunks).toString("utf8");
693
+ if (raw) payload = JSON.parse(raw);
694
+ }
695
+ try {
696
+ const value = await handle(endpoint, payload || {});
697
+ res.writeHead(200, { "content-type": "application/json" });
698
+ res.end(JSON.stringify({ ok: true, value }));
699
+ } catch (e) {
700
+ logError(`rpc:${endpoint}`, e);
701
+ res.writeHead(200, { "content-type": "application/json" });
702
+ res.end(JSON.stringify({ ok: false, error: { code: "internal", message: String(e?.message || e), details: {} } }));
703
+ }
704
+ } catch {
705
+ res.writeHead(400);
706
+ res.end("bad request");
707
+ }
708
+ } });
693
709
  wsA.register({ kind: "exact", path: "/pictor/asset/empty-state.png", handler: (_req, res) => {
694
710
  res.writeHead(200, IMG_CACHE);
695
711
  res.end(readFileSync(join(PLUGIN_ROOT, "assets", "empty-state.png")));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-pictor",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "Pictor:文档转信息图工作台。每项目一个连贯 dsh 会话,GUI 为 shell.overlay 工作台(左栏项目、右栏三步:提取/方案/渲染),行为全部交给 DSH 智能运行时。",
5
5
  "type": "module",
6
6
  "repository": {
@@ -30,10 +30,10 @@
30
30
  },
31
31
  "client": {
32
32
  "inject": [
33
- "@deepseek-ai/dsh-client-runtime",
34
33
  "@deepseek-ai/dsh-client-ui-theme",
35
34
  "@deepseek-ai/dsh-client-ui-conversation",
36
- "@deepseek-ai/dsh-client-ui-settings"
35
+ "@deepseek-ai/dsh-client-ui-settings",
36
+ "@deepseek-ai/dsh-client-ui-workspace"
37
37
  ],
38
38
  "platform": "web"
39
39
  }
@@ -65,10 +65,11 @@
65
65
  "dsh-app-dock": "^0.1.2"
66
66
  },
67
67
  "peerDependencies": {
68
- "@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.6",
69
- "@deepseek-ai/dsh-client-ui-conversation": "^0.1.0-rc.6",
70
- "@deepseek-ai/dsh-client-ui-settings": "^0.1.0-rc.6",
71
- "@deepseek-ai/dsh-client-ui-theme": "^0.1.0-rc.6",
68
+ "@deepseek-ai/cordis": "^4.0.2",
69
+ "@deepseek-ai/dsh-client-ui-conversation": "^0.1.0-rc.6 || ^0.2.0-rc.1",
70
+ "@deepseek-ai/dsh-client-ui-settings": "^0.1.0-rc.6 || ^0.2.0-rc.1",
71
+ "@deepseek-ai/dsh-client-ui-theme": "^0.1.0-rc.6 || ^0.2.0-rc.1",
72
+ "@deepseek-ai/dsh-client-ui-workspace": "^0.1.0-rc.6 || ^0.2.0-rc.1",
72
73
  "react": "^18.2.0",
73
74
  "react-dom": "^18.2.0"
74
75
  },
@@ -84,4 +85,4 @@
84
85
  "@playwright/test": "^1.62.1",
85
86
  "esbuild": "^0.28.2"
86
87
  }
87
- }
88
+ }