@goodandready/dsh-plugin-notify 0.4.4 → 0.4.5
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 +8 -0
- package/lib/client.js +51 -11
- package/lib/index.js +89 -7
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.4.5] - 2026-10-19
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
- **Detached Logger Binding** (#62, GitHub #3): Guard logger method invocations with `safeLog(ctx, message)` binding receiver `this` (`fn.call(logger, message)`) to prevent `TypeError: this is not a function` in Cordis 0.2 `LoggerService`.
|
|
11
|
+
- **Object Settings Persistence** (#62, GitHub #3): Decompose non-leaf object configuration fields (`webhooks`, `dnd`, `webPush`) into individual leaf paths (`['webhooks', key]`) and save atomically via `scope.mutate(ops)` with fallback to `scope.set`, passing `dsh-settings` volatile path validation.
|
|
12
|
+
- **Session Events & Title Cascade** (#62, GitHub #3): Add `eventsOf(session)` supporting append-only `session.log`, `session.eventsSnapshot`, and `session.snapshotEvents()`; add `cachedTitle(session)` reading projected title cache from disk (`storages/session_projcache/sessions/<id>.json`), preventing fallback to raw UUIDs.
|
|
13
|
+
- **Session Navigation Priority** (#62, GitHub #3): Target `uiWorkspace.openSession(sessionId)` as the primary navigation service in `navigateToSession` with graceful fallbacks.
|
|
14
|
+
|
|
7
15
|
## [0.4.4] - 2026-10-15
|
|
8
16
|
|
|
9
17
|
### Fixed
|
package/lib/client.js
CHANGED
|
@@ -260,15 +260,33 @@ window.__ModuleLoader__.load({
|
|
|
260
260
|
function navigateToSession(ctx, sessionId) {
|
|
261
261
|
if (!sessionId) return
|
|
262
262
|
try {
|
|
263
|
-
|
|
264
|
-
|
|
263
|
+
const ws = (ctx && typeof ctx.get === 'function' ? ctx.get('uiWorkspace') : null) || ctx?.uiWorkspace
|
|
264
|
+
if (ws && typeof ws.openSession === 'function') {
|
|
265
|
+
ws.openSession(sessionId)
|
|
265
266
|
return
|
|
266
267
|
}
|
|
267
268
|
} catch (_err) {
|
|
268
|
-
// best effort:
|
|
269
|
+
// best effort: uiWorkspace navigation fallback
|
|
270
|
+
}
|
|
271
|
+
try {
|
|
272
|
+
const s = (ctx && typeof ctx.get === 'function' ? ctx.get('sessions') : null) || ctx?.sessions
|
|
273
|
+
if (s && typeof s.activate === 'function') {
|
|
274
|
+
s.activate(sessionId)
|
|
275
|
+
return
|
|
276
|
+
}
|
|
277
|
+
if (s && typeof s.open === 'function') {
|
|
278
|
+
s.open(sessionId)
|
|
279
|
+
return
|
|
280
|
+
}
|
|
281
|
+
} catch (_err) {
|
|
282
|
+
// best effort: session service fallback
|
|
269
283
|
}
|
|
270
284
|
if (typeof window !== 'undefined' && window.location) {
|
|
271
|
-
|
|
285
|
+
try {
|
|
286
|
+
window.location.hash = `session-${sessionId}`
|
|
287
|
+
} catch (_err) {
|
|
288
|
+
// best effort: location hash update fallback
|
|
289
|
+
}
|
|
272
290
|
}
|
|
273
291
|
}
|
|
274
292
|
|
|
@@ -371,11 +389,9 @@ window.__ModuleLoader__.load({
|
|
|
371
389
|
const timer = setInterval(() => {
|
|
372
390
|
tries++
|
|
373
391
|
try {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
return
|
|
378
|
-
}
|
|
392
|
+
navigateToSession(ctx, sessionId)
|
|
393
|
+
clearInterval(timer)
|
|
394
|
+
return
|
|
379
395
|
} catch (_err) {
|
|
380
396
|
// retry until the budget is exhausted
|
|
381
397
|
}
|
|
@@ -742,9 +758,33 @@ window.__ModuleLoader__.load({
|
|
|
742
758
|
includeDuration: !!draft.includeDuration,
|
|
743
759
|
excludeSessionPrefixes: splitList(draft.excludePrefixes),
|
|
744
760
|
}
|
|
761
|
+
const ops = []
|
|
745
762
|
for (const [k, v] of Object.entries(payload)) {
|
|
746
|
-
|
|
747
|
-
|
|
763
|
+
if (v !== null && typeof v === 'object' && !Array.isArray(v)) {
|
|
764
|
+
for (const [subKey, subVal] of Object.entries(v)) {
|
|
765
|
+
ops.push({ op: 'set', path: [k, subKey], value: subVal })
|
|
766
|
+
}
|
|
767
|
+
} else {
|
|
768
|
+
ops.push({ op: 'set', path: [k], value: v })
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
if (scope && typeof scope.mutate === 'function') {
|
|
773
|
+
try {
|
|
774
|
+
const ok = await scope.mutate(ops)
|
|
775
|
+
if (ok === false) broken.push('mutate refused by host')
|
|
776
|
+
} catch (e) {
|
|
777
|
+
broken.push(e && e.message || String(e))
|
|
778
|
+
}
|
|
779
|
+
} else if (scope && typeof scope.set === 'function') {
|
|
780
|
+
for (const op of ops) {
|
|
781
|
+
try {
|
|
782
|
+
if (op.path.length === 1) await scope.set(op.path[0], op.value)
|
|
783
|
+
else await scope.set(op.path.join('.'), op.value)
|
|
784
|
+
} catch (e) {
|
|
785
|
+
broken.push(op.path.join('.') + ': ' + (e && e.message || String(e)))
|
|
786
|
+
}
|
|
787
|
+
}
|
|
748
788
|
}
|
|
749
789
|
setSaving(false)
|
|
750
790
|
if (broken.length) { setErr(t('settings.saveFailed') + broken.join('; ')); return }
|
package/lib/index.js
CHANGED
|
@@ -741,6 +741,18 @@ function mountWebServer(targetCtx, hostCtx, getConfig) {
|
|
|
741
741
|
return cleanup;
|
|
742
742
|
}
|
|
743
743
|
|
|
744
|
+
export function safeLog(ctx, message) {
|
|
745
|
+
try {
|
|
746
|
+
const logger = ctx?.logger;
|
|
747
|
+
const fn = logger?.debug ?? logger?.info;
|
|
748
|
+
if (typeof fn === 'function') {
|
|
749
|
+
fn.call(logger, message);
|
|
750
|
+
}
|
|
751
|
+
} catch {
|
|
752
|
+
// best effort: logger call must never throw or disrupt execution
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
744
756
|
export function apply(ctx, config = {}) {
|
|
745
757
|
let getConfig = () => unwrapConfig(config ?? {});
|
|
746
758
|
|
|
@@ -827,7 +839,7 @@ export function apply(ctx, config = {}) {
|
|
|
827
839
|
const cfg = unwrapConfig(getConfig());
|
|
828
840
|
const dnd = cfg.dnd;
|
|
829
841
|
if (inDnd(dnd)) {
|
|
830
|
-
(ctx
|
|
842
|
+
safeLog(ctx, `[plugin-notify] ${n.kind} · ${n.title} · session ${n.sessionId} · DND (${dnd?.start}-${dnd?.end}), logged only`);
|
|
831
843
|
return;
|
|
832
844
|
}
|
|
833
845
|
const timeoutMs = cfg.timeoutMs ?? 5000;
|
|
@@ -863,7 +875,7 @@ export function apply(ctx, config = {}) {
|
|
|
863
875
|
ctx?.logger?.warn?.(`[plugin-notify] web push dispatch failed: ${String(error)}`);
|
|
864
876
|
});
|
|
865
877
|
} else {
|
|
866
|
-
(ctx
|
|
878
|
+
safeLog(ctx, `[plugin-notify] ${n.kind} · session ${n.sessionId} · web push skipped (clients connected)`);
|
|
867
879
|
}
|
|
868
880
|
}
|
|
869
881
|
};
|
|
@@ -926,7 +938,7 @@ function send(ctx, n, webhooks, timeoutMs, local, includeSession, includeDuratio
|
|
|
926
938
|
const text = renderText(n, includeSession, includeDuration);
|
|
927
939
|
const signal = AbortSignal.timeout(timeoutMs);
|
|
928
940
|
const channels = Object.keys(webhooks);
|
|
929
|
-
(ctx
|
|
941
|
+
safeLog(ctx, `[plugin-notify] ${n.kind} · ${n.title} · session ${n.sessionId} · channels ${channels.join(',') || 'none'} · local ${local}`);
|
|
930
942
|
const post = (channel, url, body) => {
|
|
931
943
|
fetch(url, {
|
|
932
944
|
method: 'POST',
|
|
@@ -1034,20 +1046,90 @@ export function textOf(content) {
|
|
|
1034
1046
|
return out;
|
|
1035
1047
|
}
|
|
1036
1048
|
|
|
1049
|
+
export function eventsOf(session) {
|
|
1050
|
+
const pick = (v) => Array.isArray(v) ? v : (v && Array.isArray(v.entries) ? v.entries : null);
|
|
1051
|
+
try {
|
|
1052
|
+
for (const key of ['events', 'log', 'eventsSnapshot', 'history']) {
|
|
1053
|
+
const raw = session?.[key];
|
|
1054
|
+
const direct = pick(raw);
|
|
1055
|
+
if (direct) return direct;
|
|
1056
|
+
if (typeof raw === 'function') {
|
|
1057
|
+
const got = pick(raw.call(session));
|
|
1058
|
+
if (got) return got;
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
if (typeof session?.snapshotEvents === 'function') {
|
|
1062
|
+
const got = pick(session.snapshotEvents());
|
|
1063
|
+
if (got) return got;
|
|
1064
|
+
}
|
|
1065
|
+
} catch (_err) {
|
|
1066
|
+
// best effort: session events resolution fallback
|
|
1067
|
+
}
|
|
1068
|
+
return [];
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
export function cachedTitle(session) {
|
|
1072
|
+
const id = String(session?.id ?? '');
|
|
1073
|
+
if (!id) return '';
|
|
1074
|
+
try {
|
|
1075
|
+
const home = process.env.DSH_HOME || path.join(process.env.USERPROFILE || process.env.HOME || '', '.dsh');
|
|
1076
|
+
const projPath = path.join(home, 'storages', 'session_projcache', 'sessions', `${id}.json`);
|
|
1077
|
+
if (fs.existsSync(projPath)) {
|
|
1078
|
+
const content = fs.readFileSync(projPath, 'utf8');
|
|
1079
|
+
const doc = JSON.parse(content);
|
|
1080
|
+
const raw = doc?.record?.rows?.title ?? doc?.rows?.title;
|
|
1081
|
+
const val = typeof raw === 'string' ? raw : (raw && typeof raw.val === 'string' ? raw.val : '');
|
|
1082
|
+
if (val && typeof val === 'string') return val.trim();
|
|
1083
|
+
}
|
|
1084
|
+
} catch (_err) {
|
|
1085
|
+
// best effort: session projcache file read fallback
|
|
1086
|
+
}
|
|
1087
|
+
return '';
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1037
1090
|
export function sessionTitle(session) {
|
|
1038
|
-
|
|
1091
|
+
if (typeof session?.title === 'string' && session.title.trim()) {
|
|
1092
|
+
return session.title.trim();
|
|
1093
|
+
}
|
|
1094
|
+
const headerTitle = session?.header?.title;
|
|
1095
|
+
if (typeof headerTitle === 'string' && headerTitle.trim()) {
|
|
1096
|
+
return headerTitle.trim();
|
|
1097
|
+
}
|
|
1098
|
+
if (headerTitle && typeof headerTitle.val === 'string' && headerTitle.val.trim()) {
|
|
1099
|
+
return headerTitle.val.trim();
|
|
1100
|
+
}
|
|
1101
|
+
const rowsTitle = session?.rows?.title;
|
|
1102
|
+
if (typeof rowsTitle === 'string' && rowsTitle.trim()) {
|
|
1103
|
+
return rowsTitle.trim();
|
|
1104
|
+
}
|
|
1105
|
+
if (rowsTitle && typeof rowsTitle.val === 'string' && rowsTitle.val.trim()) {
|
|
1106
|
+
return rowsTitle.val.trim();
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
const events = eventsOf(session);
|
|
1110
|
+
for (const event of events) {
|
|
1111
|
+
if (event && event.type === 'session/title' && event.data?.title) {
|
|
1112
|
+
const t = String(event.data.title).replace(/\s+/g, ' ').trim();
|
|
1113
|
+
if (t) return t.length > 60 ? `${t.slice(0, 60)}…` : t;
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1039
1116
|
for (const event of events) {
|
|
1040
1117
|
if (event && event.type === 'user/message') {
|
|
1041
1118
|
const text = textOf(event.data?.content).replace(/\s+/g, ' ').trim();
|
|
1042
|
-
if (text)
|
|
1043
|
-
return text.length > 60 ? `${text.slice(0, 60)}…` : text;
|
|
1119
|
+
if (text) return text.length > 60 ? `${text.slice(0, 60)}…` : text;
|
|
1044
1120
|
}
|
|
1045
1121
|
}
|
|
1122
|
+
|
|
1123
|
+
const cached = cachedTitle(session);
|
|
1124
|
+
if (cached) {
|
|
1125
|
+
return cached.length > 60 ? `${cached.slice(0, 60)}…` : cached;
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1046
1128
|
return String(session?.id ?? 'session');
|
|
1047
1129
|
}
|
|
1048
1130
|
|
|
1049
1131
|
export function summarizeTurn(session, turn) {
|
|
1050
|
-
const events =
|
|
1132
|
+
const events = eventsOf(session);
|
|
1051
1133
|
let toolCalls = 0;
|
|
1052
1134
|
let lastText = '';
|
|
1053
1135
|
// Reverse iteration: events for current turn are at the end of the events array
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-plugin-notify",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.5",
|
|
4
4
|
"description": "DSH plugin: audio chimes, cross-session toasts, desktop push, IM webhooks, and web push (PWA) for turn completion, errors, and approvals.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -40,14 +40,14 @@
|
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
42
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
43
|
-
"@deepseek-ai/dsh-session": "^0.1.7-rc.2 || ^0.2.0-rc.1",
|
|
44
|
-
"@deepseek-ai/dsh-credentials": "^0.1.7-rc.2 || ^0.2.0-rc.1",
|
|
43
|
+
"@deepseek-ai/dsh-session": "^0.1.7-rc.2 || ^0.2.0-rc.1 || ^0.2.0-rc.2",
|
|
44
|
+
"@deepseek-ai/dsh-credentials": "^0.1.7-rc.2 || ^0.2.0-rc.1 || ^0.2.0-rc.2",
|
|
45
45
|
"@deepseek-ai/schemastery": "^3.18.4"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
49
|
-
"@deepseek-ai/dsh-session": "^0.1.
|
|
50
|
-
"@deepseek-ai/dsh-credentials": "^0.1.
|
|
49
|
+
"@deepseek-ai/dsh-session": "^0.1.7-rc.2",
|
|
50
|
+
"@deepseek-ai/dsh-credentials": "^0.1.7-rc.2",
|
|
51
51
|
"@deepseek-ai/schemastery": "^3.18.4",
|
|
52
52
|
"web-push": "^3.6.7"
|
|
53
53
|
},
|