@aiwayds/dsh-topics-memory 0.16.0 → 0.16.1-next.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/lib/index.js +82 -61
- package/package.json +1 -1
- package/src/index.ts +82 -61
package/lib/index.js
CHANGED
|
@@ -527,6 +527,10 @@ export function apply(ctx) {
|
|
|
527
527
|
if (event.type === 'turn/end')
|
|
528
528
|
dispatchSlowLane(sessionId);
|
|
529
529
|
}));
|
|
530
|
+
// Session-start boot chain dedup: rc.2's creation transaction emits BOTH
|
|
531
|
+
// `agent/session-start` and `agent/created`, so the pair collapses to one
|
|
532
|
+
// chain run per session; teardown clears the entry.
|
|
533
|
+
const bootedSessions = new Set();
|
|
530
534
|
// ---- Session teardown: real cordis events, not session/event types ----
|
|
531
535
|
// dsh-session 0.1.2-alpha.4's SessionEventMap has no `agent/disposed` or
|
|
532
536
|
// `session/disposed` event type — the old `event.type === 'agent/disposed'`
|
|
@@ -551,6 +555,7 @@ export function apply(ctx) {
|
|
|
551
555
|
observer.onSessionEvent(sessionId, name, undefined);
|
|
552
556
|
injectedBySession.delete(sessionId);
|
|
553
557
|
slowLane.clear(sessionId);
|
|
558
|
+
bootedSessions.delete(sessionId);
|
|
554
559
|
// A teardown-triggered run (session-end distill) reads the payload
|
|
555
560
|
// capture lazily: drop the entry here only when no run can still read
|
|
556
561
|
// it. The slow lane's in-flight pipeline reads it too (shared caller);
|
|
@@ -559,74 +564,90 @@ export function apply(ctx) {
|
|
|
559
564
|
}));
|
|
560
565
|
}
|
|
561
566
|
// ---- Sync lifecycle: pull on session start, local commit on dispose ----
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
const run = distiller.request(String(session.id), 'boot-replay');
|
|
590
|
-
if (run !== undefined)
|
|
591
|
-
void run.catch(() => undefined);
|
|
592
|
-
})
|
|
593
|
-
.catch(() => undefined);
|
|
594
|
-
// Consolidation cadence check — after the pull (freshest pool),
|
|
595
|
-
// fully fire-and-forget: cadence/single-flight gates live inside
|
|
596
|
-
// maybeRun, and a no-model boot just skips (no stamp advanced).
|
|
597
|
-
// Failures are NOT silent at the host log: the 2026-09-09 real-host
|
|
598
|
-
// test showed a dead distill route would otherwise hide here with
|
|
599
|
-
// zero observable trace (the state file only advances on success).
|
|
567
|
+
// Session start is an agent-bus event, never a session/event firehose type:
|
|
568
|
+
// the firehose only carries Session.append types. Tarball-verified emission
|
|
569
|
+
// sites: `agent/session-start` (sync, payload { agent, source }) is emitted
|
|
570
|
+
// by dsh-agent-loop up to 0.1.5; 0.1.6-alpha.1 merges the startup
|
|
571
|
+
// notification into the serial `agent/created` announcement (payload
|
|
572
|
+
// { agent, source, signal? }) and drops the old name. So one registration
|
|
573
|
+
// per name — a host that never emits a name simply never calls that
|
|
574
|
+
// listener. Only { agent } is structural; the rest is defensive.
|
|
575
|
+
const runBootChain = (sessionId) => {
|
|
576
|
+
void sync
|
|
577
|
+
.pull()
|
|
578
|
+
.catch(() => undefined)
|
|
579
|
+
.finally(() => store.ensure().catch(() => undefined))
|
|
580
|
+
.then(() => {
|
|
581
|
+
// Boot replay: the observations JSONL is the durable distill queue.
|
|
582
|
+
// Whatever the previous exit skipped (local-only exit since the
|
|
583
|
+
// no-network exit commit) or a killed run left unmarked is still
|
|
584
|
+
// undistilled here. Empty pool and no-model are cheap no-ops inside
|
|
585
|
+
// the distiller; the per-session dedup guards double runs.
|
|
586
|
+
void store
|
|
587
|
+
.undistilledObservations(1)
|
|
588
|
+
.then((pending) => {
|
|
589
|
+
if (pending.length === 0)
|
|
590
|
+
return;
|
|
591
|
+
// Same trigger-time capture the observer callback does: the
|
|
592
|
+
// replay run's caller resolves candidates lazily and a fresh
|
|
593
|
+
// session has no prior capture to lean on.
|
|
600
594
|
try {
|
|
601
|
-
captureFromAgent(agents()?.get(
|
|
595
|
+
captureFromAgent(agents()?.get(sessionId));
|
|
602
596
|
}
|
|
603
597
|
catch {
|
|
604
|
-
// contained — the run below fails with
|
|
598
|
+
// contained — the run below fails with the readable
|
|
599
|
+
// no-adapter detail instead
|
|
600
|
+
}
|
|
601
|
+
const run = distiller.request(sessionId, 'boot-replay');
|
|
602
|
+
if (run !== undefined)
|
|
603
|
+
void run.catch(() => undefined);
|
|
604
|
+
})
|
|
605
|
+
.catch(() => undefined);
|
|
606
|
+
// Consolidation cadence check — after the pull (freshest pool),
|
|
607
|
+
// fully fire-and-forget: cadence/single-flight gates live inside
|
|
608
|
+
// maybeRun, and a no-model boot just skips (no stamp advanced).
|
|
609
|
+
// Failures are NOT silent at the host log: the 2026-09-09 real-host
|
|
610
|
+
// test showed a dead distill route would otherwise hide here with
|
|
611
|
+
// zero observable trace (the state file only advances on success).
|
|
612
|
+
try {
|
|
613
|
+
captureFromAgent(agents()?.get(sessionId));
|
|
614
|
+
}
|
|
615
|
+
catch {
|
|
616
|
+
// contained — the run below fails with a readable no-model detail
|
|
617
|
+
}
|
|
618
|
+
void consolidator
|
|
619
|
+
.maybeRun({ sessionId })
|
|
620
|
+
.then((r) => {
|
|
621
|
+
if (r !== undefined && !r.ok && r.reason !== 'no-clusters') {
|
|
622
|
+
warn(`dsh-topics-memory 整理 lane 未执行:${r.reason ?? 'unknown'}${r.detail !== undefined ? `(${r.detail})` : ''}`);
|
|
605
623
|
}
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
624
|
+
})
|
|
625
|
+
.catch(() => undefined);
|
|
626
|
+
// Deprecated-TTL sweep — pure local rule, no model, runs even when
|
|
627
|
+
// the distill route is unconfigured. Drops are logged: deletion is
|
|
628
|
+
// the one housekeeping action the user should always see happened.
|
|
629
|
+
const ttl = cfgNow().deprecatedTtlDays;
|
|
630
|
+
if (ttl > 0) {
|
|
631
|
+
void dropExpiredDeprecated(service, ttl)
|
|
632
|
+
.then((dropped) => {
|
|
633
|
+
if (dropped.length > 0) {
|
|
634
|
+
warn(`dsh-topics-memory:按 ${ttl} 天 TTL 删除了 ${dropped.length} 条 deprecated topic(${dropped.map((s) => `topics/${s}`).join('、')});git 历史可找回`);
|
|
611
635
|
}
|
|
612
636
|
})
|
|
613
637
|
.catch(() => undefined);
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
});
|
|
628
|
-
}
|
|
629
|
-
}));
|
|
638
|
+
}
|
|
639
|
+
})
|
|
640
|
+
.catch(() => undefined);
|
|
641
|
+
};
|
|
642
|
+
for (const name of ['agent/session-start', 'agent/created']) {
|
|
643
|
+
ctx.on(name, ((payload) => {
|
|
644
|
+
const sessionId = String(payload?.agent?.id ?? '');
|
|
645
|
+
if (sessionId === '' || bootedSessions.has(sessionId))
|
|
646
|
+
return;
|
|
647
|
+
bootedSessions.add(sessionId);
|
|
648
|
+
runBootChain(sessionId);
|
|
649
|
+
}));
|
|
650
|
+
}
|
|
630
651
|
ctx.effect(() => {
|
|
631
652
|
void store.ensure().catch(() => undefined);
|
|
632
653
|
// Async disposer: cordis awaits it during unload (Disposable may be
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiwayds/dsh-topics-memory",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.1-next.1",
|
|
4
4
|
"description": "dsh-plugin: quality-first topic memory for dsh — a topic keeps the starting question, conclusion, impact and dependencies, not the process; local-first git-traced OKF bundle, budgeted LLM-free injection",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
package/src/index.ts
CHANGED
|
@@ -586,6 +586,11 @@ export function apply(ctx: Context): void {
|
|
|
586
586
|
}) as never,
|
|
587
587
|
)
|
|
588
588
|
|
|
589
|
+
// Session-start boot chain dedup: rc.2's creation transaction emits BOTH
|
|
590
|
+
// `agent/session-start` and `agent/created`, so the pair collapses to one
|
|
591
|
+
// chain run per session; teardown clears the entry.
|
|
592
|
+
const bootedSessions = new Set<string>()
|
|
593
|
+
|
|
589
594
|
// ---- Session teardown: real cordis events, not session/event types ----
|
|
590
595
|
// dsh-session 0.1.2-alpha.4's SessionEventMap has no `agent/disposed` or
|
|
591
596
|
// `session/disposed` event type — the old `event.type === 'agent/disposed'`
|
|
@@ -609,6 +614,7 @@ export function apply(ctx: Context): void {
|
|
|
609
614
|
observer.onSessionEvent(sessionId, name, undefined)
|
|
610
615
|
injectedBySession.delete(sessionId)
|
|
611
616
|
slowLane.clear(sessionId)
|
|
617
|
+
bootedSessions.delete(sessionId)
|
|
612
618
|
// A teardown-triggered run (session-end distill) reads the payload
|
|
613
619
|
// capture lazily: drop the entry here only when no run can still read
|
|
614
620
|
// it. The slow lane's in-flight pipeline reads it too (shared caller);
|
|
@@ -618,70 +624,85 @@ export function apply(ctx: Context): void {
|
|
|
618
624
|
}
|
|
619
625
|
|
|
620
626
|
// ---- Sync lifecycle: pull on session start, local commit on dispose ----
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
627
|
+
// Session start is an agent-bus event, never a session/event firehose type:
|
|
628
|
+
// the firehose only carries Session.append types. Tarball-verified emission
|
|
629
|
+
// sites: `agent/session-start` (sync, payload { agent, source }) is emitted
|
|
630
|
+
// by dsh-agent-loop up to 0.1.5; 0.1.6-alpha.1 merges the startup
|
|
631
|
+
// notification into the serial `agent/created` announcement (payload
|
|
632
|
+
// { agent, source, signal? }) and drops the old name. So one registration
|
|
633
|
+
// per name — a host that never emits a name simply never calls that
|
|
634
|
+
// listener. Only { agent } is structural; the rest is defensive.
|
|
635
|
+
const runBootChain = (sessionId: string) => {
|
|
636
|
+
void sync
|
|
637
|
+
.pull()
|
|
638
|
+
.catch(() => undefined)
|
|
639
|
+
.finally(() => store.ensure().catch(() => undefined))
|
|
640
|
+
.then(() => {
|
|
641
|
+
// Boot replay: the observations JSONL is the durable distill queue.
|
|
642
|
+
// Whatever the previous exit skipped (local-only exit since the
|
|
643
|
+
// no-network exit commit) or a killed run left unmarked is still
|
|
644
|
+
// undistilled here. Empty pool and no-model are cheap no-ops inside
|
|
645
|
+
// the distiller; the per-session dedup guards double runs.
|
|
646
|
+
void store
|
|
647
|
+
.undistilledObservations(1)
|
|
648
|
+
.then((pending) => {
|
|
649
|
+
if (pending.length === 0) return
|
|
650
|
+
// Same trigger-time capture the observer callback does: the
|
|
651
|
+
// replay run's caller resolves candidates lazily and a fresh
|
|
652
|
+
// session has no prior capture to lean on.
|
|
653
|
+
try {
|
|
654
|
+
captureFromAgent(agents()?.get(sessionId) as unknown)
|
|
655
|
+
} catch {
|
|
656
|
+
// contained — the run below fails with the readable
|
|
657
|
+
// no-adapter detail instead
|
|
658
|
+
}
|
|
659
|
+
const run = distiller.request(sessionId, 'boot-replay')
|
|
660
|
+
if (run !== undefined) void run.catch(() => undefined)
|
|
661
|
+
})
|
|
662
|
+
.catch(() => undefined)
|
|
663
|
+
// Consolidation cadence check — after the pull (freshest pool),
|
|
664
|
+
// fully fire-and-forget: cadence/single-flight gates live inside
|
|
665
|
+
// maybeRun, and a no-model boot just skips (no stamp advanced).
|
|
666
|
+
// Failures are NOT silent at the host log: the 2026-09-09 real-host
|
|
667
|
+
// test showed a dead distill route would otherwise hide here with
|
|
668
|
+
// zero observable trace (the state file only advances on success).
|
|
669
|
+
try {
|
|
670
|
+
captureFromAgent(agents()?.get(sessionId) as unknown)
|
|
671
|
+
} catch {
|
|
672
|
+
// contained — the run below fails with a readable no-model detail
|
|
673
|
+
}
|
|
674
|
+
void consolidator
|
|
675
|
+
.maybeRun({ sessionId })
|
|
676
|
+
.then((r) => {
|
|
677
|
+
if (r !== undefined && !r.ok && r.reason !== 'no-clusters') {
|
|
678
|
+
warn(`dsh-topics-memory 整理 lane 未执行:${r.reason ?? 'unknown'}${r.detail !== undefined ? `(${r.detail})` : ''}`)
|
|
679
|
+
}
|
|
680
|
+
})
|
|
681
|
+
.catch(() => undefined)
|
|
682
|
+
// Deprecated-TTL sweep — pure local rule, no model, runs even when
|
|
683
|
+
// the distill route is unconfigured. Drops are logged: deletion is
|
|
684
|
+
// the one housekeeping action the user should always see happened.
|
|
685
|
+
const ttl = cfgNow().deprecatedTtlDays
|
|
686
|
+
if (ttl > 0) {
|
|
687
|
+
void dropExpiredDeprecated(service, ttl)
|
|
688
|
+
.then((dropped) => {
|
|
689
|
+
if (dropped.length > 0) {
|
|
690
|
+
warn(`dsh-topics-memory:按 ${ttl} 天 TTL 删除了 ${dropped.length} 条 deprecated topic(${dropped.map((s) => `topics/${s}`).join('、')});git 历史可找回`)
|
|
666
691
|
}
|
|
667
692
|
})
|
|
668
693
|
.catch(() => undefined)
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
}
|
|
682
|
-
})
|
|
683
|
-
}
|
|
684
|
-
}) as never)
|
|
694
|
+
}
|
|
695
|
+
})
|
|
696
|
+
.catch(() => undefined)
|
|
697
|
+
}
|
|
698
|
+
for (const name of ['agent/session-start', 'agent/created'] as const) {
|
|
699
|
+
ctx.on(name as never, ((payload: { agent?: { id?: unknown } }) => {
|
|
700
|
+
const sessionId = String(payload?.agent?.id ?? '')
|
|
701
|
+
if (sessionId === '' || bootedSessions.has(sessionId)) return
|
|
702
|
+
bootedSessions.add(sessionId)
|
|
703
|
+
runBootChain(sessionId)
|
|
704
|
+
}) as never)
|
|
705
|
+
}
|
|
685
706
|
|
|
686
707
|
ctx.effect(
|
|
687
708
|
() => {
|