@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 +29 -0
- package/lib/index.js +51 -5
- package/package.json +1 -1
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
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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