@peng272/dsh-wechat-ilink 0.7.0 → 0.7.1

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/CHANGELOG.md CHANGED
@@ -15,6 +15,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
15
15
  > documentation this project has: every one of them was an assumed DSH contract
16
16
  > that turned out to be wrong.
17
17
 
18
+ ## [0.7.1]
19
+
20
+ ### Fixed
21
+
22
+ - **A fresh conversation could never start.** Every turn passed
23
+ `--session-id <sessionId>`, but `dsh --profile headless --session-id` requires
24
+ that session to already exist. On a new install the recorded id
25
+ (`wechat-clawbot`) does not exist yet, so **every** inbound message failed with
26
+
27
+ ```
28
+ dsh: session "wechat-clawbot" does not exist; omit --session-id to start a new Session
29
+ ```
30
+
31
+ and produced no reply. The first turn now omits the flag (which creates the
32
+ session), the id the driver reports is persisted to `<stateDir>/session.json`,
33
+ and later turns resume it. A recorded session that has since disappeared is
34
+ detected and transparently restarted.
35
+
36
+ Found by reading the channel log of a live install; the failure was silent from
37
+ WeChat's side (the message simply got no answer).
38
+
39
+ - Driver-level failures emitted as a `{"type":"error"}` frame are now surfaced in
40
+ the log instead of being reported only as `reason=incomplete`.
41
+
42
+ ### Changed
43
+
44
+ - The package is published as **`@peng272/dsh-wechat-ilink`**. The bare name
45
+ `dsh-wechat-ilink` belongs to another maintainer and cannot be updated from
46
+ this account.
18
47
  ## [0.7.0]
19
48
 
20
49
  A reimplementation of the channel internals. The **runtime identities are
package/lib/index.js CHANGED
@@ -102,6 +102,7 @@ export function parseHeadlessJson(stdout) {
102
102
  let sessionId
103
103
  let text = ''
104
104
  let reason
105
+ let directError
105
106
  for (const line of String(stdout ?? '').split('\n')) {
106
107
  const trimmed = line.trim()
107
108
  if (!trimmed) continue
@@ -114,12 +115,15 @@ export function parseHeadlessJson(stdout) {
114
115
  if (event.type === 'session' && typeof event.sessionId === 'string') sessionId = event.sessionId
115
116
  else if (event.type === 'final' && typeof event.text === 'string') text = event.text
116
117
  else if (event.type === 'status' && event.phase === 'turn_end' && event.reason) reason = event.reason
118
+ // A driver-level failure (for example an unknown --session-id) is emitted as
119
+ // its own frame, not as a turn_end reason.
120
+ else if (event.type === 'error' && typeof event.message === 'string') directError = event.message
117
121
  }
118
122
  return {
119
123
  sessionId,
120
124
  text,
121
125
  reason: reason ? reason.kind : 'incomplete',
122
- error: reason?.error?.message,
126
+ error: reason?.error?.message ?? directError,
123
127
  }
124
128
  }
125
129
 
@@ -215,9 +219,39 @@ export function apply(ctx, rawConfig) {
215
219
  let sessionId = config.sessionId?.trim() || 'wechat-clawbot'
216
220
  let chain = Promise.resolve()
217
221
 
218
- function runHeadless(prompt) {
219
- if (!dshPrefix) return Promise.resolve({ text: '', reason: 'no-dsh-prefix' })
220
- const argv = [...dshPrefix, '--profile', 'headless', '--json', '--session-id', sessionId, prompt]
222
+ // `dsh --profile headless --session-id <id>` requires that session to ALREADY
223
+ // exist. Passing it for a fresh conversation fails every single turn with
224
+ // session "<id>" does not exist; omit --session-id to start a new Session
225
+ // so the first run omits the flag and the id it reports is remembered for the
226
+ // turns after it. A recorded session that has since disappeared is detected
227
+ // from stderr and retried without the flag.
228
+ const sessionStateFile = path.join(stateDir, 'session.json')
229
+ let sessionEstablished = false
230
+ try {
231
+ if (fs.existsSync(sessionStateFile)) {
232
+ const saved = JSON.parse(fs.readFileSync(sessionStateFile, 'utf-8'))
233
+ if (typeof saved.id === 'string' && saved.id) sessionId = saved.id
234
+ sessionEstablished = saved.established === true
235
+ }
236
+ } catch {
237
+ /* start a fresh conversation */
238
+ }
239
+
240
+ function rememberSession(id, established) {
241
+ if (id) sessionId = id
242
+ sessionEstablished = established
243
+ try {
244
+ fs.mkdirSync(stateDir, { recursive: true })
245
+ fs.writeFileSync(sessionStateFile, JSON.stringify({ id: sessionId, established }, null, 2), 'utf-8')
246
+ } catch (error) {
247
+ log('error', `could not persist session state: ${error?.message ?? error}`)
248
+ }
249
+ }
250
+
251
+ function spawnHeadless(prompt, useSessionId) {
252
+ const argv = [...dshPrefix, '--profile', 'headless', '--json']
253
+ if (useSessionId) argv.push('--session-id', sessionId)
254
+ argv.push(prompt)
221
255
  return new Promise((resolve) => {
222
256
  let child
223
257
  try {
@@ -256,11 +290,23 @@ export function apply(ctx, rawConfig) {
256
290
  if (parsed.reason === 'incomplete' && stderr.trim()) {
257
291
  log('error', `headless exited code=${code}: ${stderr.trim().slice(0, 400)}`)
258
292
  }
259
- finish(parsed)
293
+ finish({ ...parsed, stderr, exitCode: code })
260
294
  })
261
295
  })
262
296
  }
263
297
 
298
+ async function runHeadless(prompt) {
299
+ if (!dshPrefix) return { text: '', reason: 'no-dsh-prefix' }
300
+ let result = await spawnHeadless(prompt, sessionEstablished)
301
+ if (result.reason === 'incomplete' && /does not exist|omit --session-id/i.test(result.stderr || '')) {
302
+ log('error', 'the recorded session is gone; starting a new conversation')
303
+ rememberSession(sessionId, false)
304
+ result = await spawnHeadless(prompt, false)
305
+ }
306
+ if (result.sessionId) rememberSession(result.sessionId, true)
307
+ return result
308
+ }
309
+
264
310
  // ---- inbound ----
265
311
  function extractText(msg) {
266
312
  const items = Array.isArray(msg?.item_list) ? msg.item_list : []
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peng272/dsh-wechat-ilink",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "description": "WeChat ClawBot (Tencent iLink) channel for DeepSeek Harness 0.2.0-rc.2 — talk to DSH from WeChat.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",