dsh-remote-wecom 0.1.2 → 0.1.3

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/docs/DESIGN.md CHANGED
@@ -55,7 +55,7 @@ DeepSeek Harness.app ── host 进程
55
55
 
56
56
  事件(`ctx.on(name, fn, { global: true })`):
57
57
 
58
- - `session/event (session, event)`:`turn/start{turn}`、`user/message{source.kind, source.rpcId}`(`source.kind` 除 `user` 外还有注入类 `runtime-context` / `skill-catalog` / `time-context`,以及宿主唤起的 `schedule` / `goal`)、`assistant/message{message.content[]}`、`tool/call{name, arguments}`、`tool/result{message.isError, meta.path}`、`turn/end{reason.kind: completed|aborted|error|max-tokens, reason.error.message}`、`session/title{title, source}`、`model/selection{provider, model, reasoningEffort}`(只在切模型时发)、`request/header{header.config{provider, model, reasoningEffort}}`(每次 LLM 请求,新会话首轮靠它拿模型,此时 `modelSelection` 投影还是空的)。`user/message` 的 data 就是消息本身(`data.source`、`data.content`),混有 runtime-context 等系统注入消息,只认 `source.kind === 'user'`。一轮的顺序:`agent/inbox/spliced` → `api-session/status [sid, true]` → `turn/start` → `user/message` → … → `turn/end`。标题先来一条 `source.kind:'fallback'`(首条消息截断),模型起的标题随后再来一条。
58
+ - `session/event (session, event)`:`turn/start{turn}`、`user/message{source.kind, source.rpcId}`(`source.kind` 除 `user` 外还有注入类 `runtime-context` / `skill-catalog` / `time-context`,以及宿主唤起的 `schedule` / `goal`)、`assistant/message{message.content[]}`、`tool/call{name, arguments}`、`tool/result{message.isError, meta.path}`、`turn/end{reason.kind: completed|aborted|error|max-tokens, reason.error.message}`、`session/title{title, source}`、`model/selection{provider, model, reasoningEffort}`(只在切模型时发)、`request/header{header.config{provider, model, reasoningEffort}}`(每次 LLM 请求,新会话首轮靠它拿模型,此时 `modelSelection` 投影还是空的;还没发首条时按 `modelCatalog.default` 显示)。`user/message` 的 data 就是消息本身(`data.source`、`data.content`),混有 runtime-context 等系统注入消息,只认 `source.kind === 'user'`。一轮的顺序:`agent/inbox/spliced` → `api-session/status [sid, true]` → `turn/start` → `user/message` → … → `turn/end`。标题先来一条 `source.kind:'fallback'`(首条消息截断),模型起的标题随后再来一条。
59
59
  - `api-session/added (summary)`、`api-session/removed (sessionId)`、`api-session/status (sessionId, running)`。
60
60
  - `approval/request (req, next)`,注册时加 `prepend: true`:`req.{toolName, callId, reason, displayReason.zh, agent, signal}`,返回 `'allowed-once' | 'rejected' | 'cancelled' | 'unavailable'`。会话 id 取 `req.agent.session.id ?? req.agent.id`。
61
61
  - `user-questions/request (req, next)`:`req.questions[{id, question, header, options[{label, description}], multiSelect}]`,返回 `{answers:[{id, selected:[label], custom?}]}`。
@@ -665,6 +665,15 @@ export function createRemote({ host, dir, config, log: echo = () => { }, createT
665
665
  catch (e) {
666
666
  log(`dsh[${this.key}] projections 失败(沿用上次信息):${e?.message || e}`);
667
667
  }
668
+ // 新会话跑完首轮前 modelSelection 投影为空,按部署默认模型显示。
669
+ if (!this.model) {
670
+ const d = await host.modelCatalog().then((c) => c?.default, () => null);
671
+ if (d?.model) {
672
+ this.model = String(d.model);
673
+ this.modelProvider = String(d.provider || "");
674
+ this.thinkingLevel = d.reasoningEffort ? String(d.reasoningEffort) : "";
675
+ }
676
+ }
668
677
  }
669
678
  async stats() {
670
679
  await this.refreshMeta();
@@ -76,8 +76,14 @@ export function takeLaunched(dir, now = Date.now()) {
76
76
  return Number.isFinite(at) && now - at <= SPOOL_TTL_MS;
77
77
  }
78
78
 
79
+ /** 看门狗跑在 ELECTRON_RUN_AS_NODE 下;`open` 会把环境传给 Desktop,不去掉它 Desktop 会以 Node 模式启动后立即退出。 */
80
+ export function desktopEnv(env = process.env) {
81
+ const {ELECTRON_RUN_AS_NODE, ...rest} = env;
82
+ return rest;
83
+ }
84
+
79
85
  export function launchDesktop() {
80
- return new Promise((resolve, reject) => execFile('open', ['-g', '-j', '-b', DESKTOP_BUNDLE_ID], {timeout: 15_000},
86
+ return new Promise((resolve, reject) => execFile('open', ['-g', '-j', '-b', DESKTOP_BUNDLE_ID], {timeout: 15_000, env: desktopEnv()},
81
87
  e => e ? reject(e) : resolve()));
82
88
  }
83
89
 
@@ -353,6 +353,11 @@ export class WeComTransport {
353
353
  async flush() {
354
354
  if (!this.connected || this.closed || this.working) return;
355
355
  this.working = true;
356
+ // 回执超时时服务端多半已送达(常见于电脑睡眠),重发只会重复,按已送达处理;明确报错才重试。
357
+ const deliver = async request => {
358
+ try { await request(); }
359
+ catch (e) { if (!/ack timeout/i.test(String(e?.message || ''))) throw e; this.log('企微回执超时,按已送达处理'); }
360
+ };
356
361
  try {
357
362
  for (const name of this.queueFiles()) {
358
363
  if (!this.connected || this.closed) break;
@@ -363,10 +368,10 @@ export class WeComTransport {
363
368
  try {
364
369
  for (; packet.cursor < packet.steps.length; packet.cursor++) {
365
370
  const step = packet.steps[packet.cursor];
366
- if (step.kind === 'text') await this.client.sendMessage(packet.recipient, {msgtype: 'markdown', markdown: {content: step.text}});
371
+ if (step.kind === 'text') await deliver(() => this.client.sendMessage(packet.recipient, {msgtype: 'markdown', markdown: {content: step.text}}));
367
372
  if (step.kind === 'card') {
368
373
  try {
369
- await this.client.sendMessage(packet.recipient, {msgtype: 'template_card', template_card: step.card});
374
+ await deliver(() => this.client.sendMessage(packet.recipient, {msgtype: 'template_card', template_card: step.card}));
370
375
  this.rememberCard(step.card);
371
376
  }
372
377
  catch (e) {
@@ -380,7 +385,7 @@ export class WeComTransport {
380
385
  }
381
386
  if (step.kind === 'file') {
382
387
  const media = await this.client.uploadMedia(fs.readFileSync(step.file), {type: 'file', filename: path.basename(step.file)});
383
- await this.client.sendMediaMessage(packet.recipient, 'file', media.media_id);
388
+ await deliver(() => this.client.sendMediaMessage(packet.recipient, 'file', media.media_id));
384
389
  }
385
390
  writeJson(file, {...packet, cursor: packet.cursor + 1});
386
391
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-remote-wecom",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "DeepSeek Harness plugin: control Desktop sessions from WeCom, with session cards, attachments, approvals and task results.",
5
5
  "author": "lukeinx",
6
6
  "repository": {