@heixui/dsh-session-suite 0.6.0 → 0.6.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/index.js CHANGED
@@ -45,7 +45,7 @@ export const name = 'session-suite'
45
45
  * Everything else stays optional: the HTTP carrier is still the only hard requirement,
46
46
  * and a Host without `agents` degrades to no implicit extraction rather than failing.
47
47
  */
48
- export const inject = ['connection', 'agents']
48
+ export const inject = ['connection', 'agents', 'timer']
49
49
 
50
50
  /**
51
51
  * @param {any} ctx plugin context
@@ -288,13 +288,21 @@ export function installMemory({ ctx, root, config, log }) {
288
288
  // One listener per agent; the context disposes it with the agent.
289
289
  agentCtx.on('agent/turn-stopping', () => {
290
290
  const sessionId = agent?.sessionId
291
- if (typeof sessionId !== 'string' || !sessionId) return
291
+ if (typeof sessionId !== 'string' || !sessionId) {
292
+ extractionTrace.reason = `turn-stopping fired but agent.sessionId was ${typeof sessionId}`
293
+ publishExtractionTrace(engine)
294
+ return
295
+ }
292
296
  // The model choice lives in settings.json, not in the patch config.
293
297
  void engine
294
298
  .settings()
295
299
  .then(settings => {
296
300
  const chosen = settings?.implicitModel ?? config?.implicitModel
297
- if (!chosen || chosen === 'off') return undefined
301
+ if (!chosen || chosen === 'off') {
302
+ extractionTrace.reason = 'no implicit model is selected in settings'
303
+ publishExtractionTrace(engine)
304
+ return undefined
305
+ }
298
306
  const cwd = resolveCwd(ctx, agent)
299
307
  return extractAfterTurn({ ctx, engine, sessionId, cwd, log, config })
300
308
  })
@@ -308,16 +316,48 @@ export function installMemory({ ctx, root, config, log }) {
308
316
  })
309
317
  }
310
318
 
311
- // Mount for agents that already exist, and for every one created later.
312
- for (const agent of agents.list?.() ?? []) mountFor(agent)
313
- if (typeof ctx.on === 'function') {
314
- ctx.on('agent/created', payload => {
319
+ // Mount by POLLING, not by an event.
320
+ //
321
+ // The first version listened for `agent/created`, which never delivered a usable
322
+ // agent — the hook stayed unmounted and the trace stayed null, the same symptom as
323
+ // every earlier attempt. dsh-fish-memory does not use an event either: it runs
324
+ // `ctx.effect(() => ctx.interval(tick, 500))` and calls `agents.list()` every half
325
+ // second, mounting on any agent it has not seen yet. That is what makes its version
326
+ // work, and its source notes the tradeoff — the tick is cheap because `agents.list()`
327
+ // reads memory.
328
+ //
329
+ // Polling also handles the case an event cannot: the agent instance is REPLACED when
330
+ // a session is resumed, a plugin reloads, or the Host rebuilds it. A one-shot
331
+ // listener dies with the old instance; a poll re-mounts on the new one.
332
+ const mounted = new WeakSet()
333
+ const tick = () => {
334
+ let live = []
335
+ try {
336
+ live = agents.list?.() ?? []
337
+ } catch {
338
+ return
339
+ }
340
+ for (const agent of live) {
341
+ if (!agent || mounted.has(agent)) continue
342
+ // Subagents only do one job; a memory store for them is wasted work.
343
+ if (agent.origin === 'subagent' || agent.delegationDepth > 0) continue
344
+ mounted.add(agent)
315
345
  try {
316
- mountFor(payload?.agent ?? payload)
317
- } catch {
318
- /* one bad agent must not stop the rest */
346
+ mountFor(agent)
347
+ } catch (error) {
348
+ log('could not mount the extraction hook on an agent', error)
319
349
  }
320
- })
350
+ }
351
+ }
352
+ // First pass immediately, so an already-running session is covered without waiting.
353
+ tick()
354
+ // Then every half second. `ctx.interval` is the Cordis timer facility; fall back to
355
+ // setInterval when it is absent so the feature still works on a leaner Host.
356
+ if (typeof ctx.interval === 'function') ctx.effect(() => ctx.interval(tick, 500))
357
+ else if (typeof setInterval === 'function') {
358
+ const handle = setInterval(tick, 500)
359
+ handle.unref?.()
360
+ ctx.effect(() => () => clearInterval(handle))
321
361
  }
322
362
  })
323
363
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heixui/dsh-session-suite",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "Session checkpoints for DSH: rewind a conversation with its files and memory, permanent session deletion, and shareable exports",
5
5
  "type": "module",
6
6
  "main": "./index.js",