@nanmicoder/dsh-agent-teams 0.1.7 → 0.1.8
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/README.md +23 -0
- package/README_ZH.md +14 -0
- package/lib/client/ActivityPanel.js +11 -42
- package/lib/client/AgentTeamsCard.js +10 -32
- package/lib/client/activity-monitor.js +178 -0
- package/lib/client/index.js +0 -1
- package/lib/client.js +192 -62
- package/lib/client.js.map +1 -1
- package/lib/command.js +127 -0
- package/lib/index.js +19 -1
- package/lib/types/client/ActivityPanel.d.ts +0 -40
- package/lib/types/client/AgentTeamsCard.d.ts +1 -2
- package/lib/types/client/activity-monitor.d.ts +106 -0
- package/lib/types/command.d.ts +71 -0
- package/lib/types/index.d.ts +6 -0
- package/package.json +6 -1
package/lib/client.js
CHANGED
|
@@ -135,6 +135,183 @@ window.__ModuleLoader__.load({
|
|
|
135
135
|
return related;
|
|
136
136
|
}
|
|
137
137
|
//#endregion
|
|
138
|
+
//#region lib/client/activity-monitor.js
|
|
139
|
+
/** Shared, demand-driven state for the AgentTeams browser monitor. */
|
|
140
|
+
const targets = /* @__PURE__ */ new Map();
|
|
141
|
+
const targetListeners = /* @__PURE__ */ new Set();
|
|
142
|
+
const snapshotListeners = /* @__PURE__ */ new Set();
|
|
143
|
+
let targetSnapshot = [];
|
|
144
|
+
let activitySnapshots = {
|
|
145
|
+
teams: [],
|
|
146
|
+
archivedTeams: []
|
|
147
|
+
};
|
|
148
|
+
function targetKey(sessionId, teamId) {
|
|
149
|
+
return `${sessionId}\u0000${teamId}`;
|
|
150
|
+
}
|
|
151
|
+
function publishTargets() {
|
|
152
|
+
targetSnapshot = [...targets.values()].filter((target) => target.active).map(({ key, sessionId, teamId }) => ({
|
|
153
|
+
key,
|
|
154
|
+
sessionId,
|
|
155
|
+
teamId
|
|
156
|
+
}));
|
|
157
|
+
for (const listener of targetListeners) listener();
|
|
158
|
+
}
|
|
159
|
+
/** Subscribe to the active monitor-target list (React external-store shape). */
|
|
160
|
+
function subscribeActivityMonitorTargets(listener) {
|
|
161
|
+
targetListeners.add(listener);
|
|
162
|
+
return () => {
|
|
163
|
+
targetListeners.delete(listener);
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/** Read the stable active-target snapshot. */
|
|
167
|
+
function getActivityMonitorTargetsSnapshot() {
|
|
168
|
+
return targetSnapshot;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Register one successful AgentTeams card as a monitoring demand.
|
|
172
|
+
*
|
|
173
|
+
* The returned cleanup is reference-counted so multiple cards and React
|
|
174
|
+
* StrictMode remounts cannot stop another card's monitor.
|
|
175
|
+
*/
|
|
176
|
+
function monitorAgentTeam(sessionId, teamId) {
|
|
177
|
+
const owner = sessionId.trim();
|
|
178
|
+
const id = teamId.trim();
|
|
179
|
+
if (owner === "" || id === "") return () => {};
|
|
180
|
+
const key = targetKey(owner, id);
|
|
181
|
+
const existing = targets.get(key);
|
|
182
|
+
if (existing === void 0) {
|
|
183
|
+
targets.set(key, {
|
|
184
|
+
key,
|
|
185
|
+
sessionId: owner,
|
|
186
|
+
teamId: id,
|
|
187
|
+
refs: 1,
|
|
188
|
+
active: true
|
|
189
|
+
});
|
|
190
|
+
publishTargets();
|
|
191
|
+
} else {
|
|
192
|
+
existing.refs += 1;
|
|
193
|
+
if (!existing.active) {
|
|
194
|
+
existing.active = true;
|
|
195
|
+
publishTargets();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
let released = false;
|
|
199
|
+
return () => {
|
|
200
|
+
if (released) return;
|
|
201
|
+
released = true;
|
|
202
|
+
const current = targets.get(key);
|
|
203
|
+
if (current === void 0) return;
|
|
204
|
+
current.refs -= 1;
|
|
205
|
+
if (current.refs <= 0) {
|
|
206
|
+
targets.delete(key);
|
|
207
|
+
if (current.active) publishTargets();
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
/** Stop polling targets whose final archived snapshot has been captured. */
|
|
212
|
+
function settleActivityMonitorTargets(keys) {
|
|
213
|
+
let changed = false;
|
|
214
|
+
for (const key of keys) {
|
|
215
|
+
const target = targets.get(key);
|
|
216
|
+
if (target?.active !== true) continue;
|
|
217
|
+
target.active = false;
|
|
218
|
+
changed = true;
|
|
219
|
+
}
|
|
220
|
+
if (changed) publishTargets();
|
|
221
|
+
}
|
|
222
|
+
/** Subscribe to the shared live/archive snapshot. */
|
|
223
|
+
function subscribeActivitySnapshots(listener) {
|
|
224
|
+
snapshotListeners.add(listener);
|
|
225
|
+
return () => {
|
|
226
|
+
snapshotListeners.delete(listener);
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
/** Read the stable shared live/archive snapshot. */
|
|
230
|
+
function getActivitySnapshotsSnapshot() {
|
|
231
|
+
return activitySnapshots;
|
|
232
|
+
}
|
|
233
|
+
/** Publish one or both successful state-route responses. */
|
|
234
|
+
function updateActivitySnapshots(update) {
|
|
235
|
+
const next = {
|
|
236
|
+
teams: update.teams ?? activitySnapshots.teams,
|
|
237
|
+
archivedTeams: update.archivedTeams ?? activitySnapshots.archivedTeams
|
|
238
|
+
};
|
|
239
|
+
if (next.teams === activitySnapshots.teams && next.archivedTeams === activitySnapshots.archivedTeams) return;
|
|
240
|
+
activitySnapshots = next;
|
|
241
|
+
for (const listener of snapshotListeners) listener();
|
|
242
|
+
}
|
|
243
|
+
/** Poll cadence for the live host snapshot route. */
|
|
244
|
+
const ACTIVITY_POLL_MS = 1e3;
|
|
245
|
+
/** Host route serving live and archived team snapshots. */
|
|
246
|
+
const ACTIVITY_STATE_URL = "/plugins/dsh-agent-teams/state";
|
|
247
|
+
/**
|
|
248
|
+
* Start the single polling loop for the current session's requested targets.
|
|
249
|
+
*
|
|
250
|
+
* With no targets this is deliberately inert: installing the plugin must not
|
|
251
|
+
* touch the state route. Live state is polled at the normal cadence; archive
|
|
252
|
+
* state is fetched only as a one-time fallback for targets no longer live.
|
|
253
|
+
*/
|
|
254
|
+
function startActivityPolling(monitorTargets, runtime = {}) {
|
|
255
|
+
if (monitorTargets.length === 0) return {
|
|
256
|
+
firstTick: Promise.resolve(),
|
|
257
|
+
stop: () => {}
|
|
258
|
+
};
|
|
259
|
+
const fetchState = runtime.fetchState ?? ((url, init) => fetch(url, init));
|
|
260
|
+
const schedule = runtime.schedule ?? ((callback, intervalMs) => setInterval(callback, intervalMs));
|
|
261
|
+
const cancel = runtime.cancel ?? ((timer) => {
|
|
262
|
+
clearInterval(timer);
|
|
263
|
+
});
|
|
264
|
+
const publishSnapshots = runtime.publishSnapshots ?? updateActivitySnapshots;
|
|
265
|
+
const settleTargets = runtime.settleTargets ?? settleActivityMonitorTargets;
|
|
266
|
+
let cancelled = false;
|
|
267
|
+
let inFlight = false;
|
|
268
|
+
let controller;
|
|
269
|
+
const tick = async () => {
|
|
270
|
+
if (inFlight || cancelled) return;
|
|
271
|
+
inFlight = true;
|
|
272
|
+
controller = new AbortController();
|
|
273
|
+
try {
|
|
274
|
+
const liveResponse = await fetchState(ACTIVITY_STATE_URL, {
|
|
275
|
+
cache: "no-store",
|
|
276
|
+
signal: controller.signal
|
|
277
|
+
});
|
|
278
|
+
if (!liveResponse.ok) return;
|
|
279
|
+
const body = await liveResponse.json();
|
|
280
|
+
if (cancelled || !Array.isArray(body.teams)) return;
|
|
281
|
+
const liveTeams = body.teams;
|
|
282
|
+
publishSnapshots({ teams: liveTeams });
|
|
283
|
+
const missing = monitorTargets.filter((target) => !liveTeams.some((team) => team.captainSessionId === target.sessionId && team.teamId === target.teamId));
|
|
284
|
+
if (missing.length === 0) return;
|
|
285
|
+
const archivedResponse = await fetchState(`${ACTIVITY_STATE_URL}?archived=1`, {
|
|
286
|
+
cache: "no-store",
|
|
287
|
+
signal: controller.signal
|
|
288
|
+
});
|
|
289
|
+
if (!archivedResponse.ok) return;
|
|
290
|
+
const archivedBody = await archivedResponse.json();
|
|
291
|
+
if (cancelled || !Array.isArray(archivedBody.teams)) return;
|
|
292
|
+
publishSnapshots({ archivedTeams: archivedBody.teams });
|
|
293
|
+
settleTargets(new Set(missing.map((target) => target.key)));
|
|
294
|
+
} catch (error) {
|
|
295
|
+
if (error?.name === "AbortError") return;
|
|
296
|
+
} finally {
|
|
297
|
+
inFlight = false;
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
const firstTick = tick();
|
|
301
|
+
const timer = schedule(() => {
|
|
302
|
+
tick();
|
|
303
|
+
}, ACTIVITY_POLL_MS);
|
|
304
|
+
return {
|
|
305
|
+
firstTick,
|
|
306
|
+
stop: () => {
|
|
307
|
+
if (cancelled) return;
|
|
308
|
+
cancelled = true;
|
|
309
|
+
controller?.abort();
|
|
310
|
+
cancel(timer);
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
//#endregion
|
|
138
315
|
//#region lib/client/artwork.js
|
|
139
316
|
/**
|
|
140
317
|
* Shared whale artwork lookup for the activity panel and the conversation
|
|
@@ -224,33 +401,14 @@ window.__ModuleLoader__.load({
|
|
|
224
401
|
} }));
|
|
225
402
|
}
|
|
226
403
|
/** Render one durable team as a compact conversation card. */
|
|
227
|
-
function AgentTeamsCard({ node, openSession,
|
|
404
|
+
function AgentTeamsCard({ node, openSession, sessionId }) {
|
|
228
405
|
const data = node.data;
|
|
229
|
-
const owner = data.captainSessionId ||
|
|
230
|
-
const
|
|
406
|
+
const owner = data.captainSessionId || sessionId;
|
|
407
|
+
const { teams, archivedTeams } = (0, react.useSyncExternalStore)(subscribeActivitySnapshots, getActivitySnapshotsSnapshot);
|
|
231
408
|
(0, react.useEffect)(() => {
|
|
232
|
-
|
|
233
|
-
const tick = async () => {
|
|
234
|
-
for (const url of ["/plugins/dsh-agent-teams/state", "/plugins/dsh-agent-teams/state?archived=1"]) try {
|
|
235
|
-
const response = await fetch(url, { cache: "no-store" });
|
|
236
|
-
if (!response.ok) continue;
|
|
237
|
-
const body = await response.json();
|
|
238
|
-
const found = Array.isArray(body.teams) ? body.teams.find((team) => team.teamId === data.teamId && (owner === "" || team.captainSessionId === owner)) : void 0;
|
|
239
|
-
if (found !== void 0) {
|
|
240
|
-
if (!cancelled) setSnapshot(found);
|
|
241
|
-
return;
|
|
242
|
-
}
|
|
243
|
-
} catch {}
|
|
244
|
-
};
|
|
245
|
-
tick();
|
|
246
|
-
const timer = setInterval(() => {
|
|
247
|
-
tick();
|
|
248
|
-
}, 1500);
|
|
249
|
-
return () => {
|
|
250
|
-
cancelled = true;
|
|
251
|
-
clearInterval(timer);
|
|
252
|
-
};
|
|
409
|
+
return monitorAgentTeam(owner, data.teamId);
|
|
253
410
|
}, [data.teamId, owner]);
|
|
411
|
+
const snapshot = teams.find((team) => team.teamId === data.teamId && (owner === "" || team.captainSessionId === owner)) ?? archivedTeams.find((team) => team.teamId === data.teamId && (owner === "" || team.captainSessionId === owner));
|
|
254
412
|
const resolved = (0, react.useMemo)(() => ({
|
|
255
413
|
...data,
|
|
256
414
|
captainSessionId: snapshot?.captainSessionId ?? owner,
|
|
@@ -441,8 +599,6 @@ window.__ModuleLoader__.load({
|
|
|
441
599
|
* removed in favor of this always-available monitor.
|
|
442
600
|
* @module dsh-agent-teams/client/activity
|
|
443
601
|
*/
|
|
444
|
-
/** Poll cadence for the host snapshot route. */
|
|
445
|
-
const POLL_MS = 1e3;
|
|
446
602
|
/** Grace before the panel collapses once no team remains. */
|
|
447
603
|
const AUTOCLOSE_GRACE_MS = 2e3;
|
|
448
604
|
/**
|
|
@@ -451,8 +607,6 @@ window.__ModuleLoader__.load({
|
|
|
451
607
|
* right after load. New activity after this window auto-expands as usual.
|
|
452
608
|
*/
|
|
453
609
|
const AUTO_OPEN_SETTLE_MS = 4e3;
|
|
454
|
-
/** Host route serving team snapshots. */
|
|
455
|
-
const STATE_URL = "/plugins/dsh-agent-teams/state";
|
|
456
610
|
/** Root marker shared with the panel CSS while the portal is expanded. */
|
|
457
611
|
const PANEL_OPEN_ATTRIBUTE = "data-agent-teams-panel-open";
|
|
458
612
|
/** Initial-letter fallback for unmatched roles. */
|
|
@@ -1056,14 +1210,15 @@ window.__ModuleLoader__.load({
|
|
|
1056
1210
|
setWasActive(false);
|
|
1057
1211
|
openSession(id);
|
|
1058
1212
|
};
|
|
1059
|
-
const [teams, setTeams] = (0, react.useState)([]);
|
|
1060
|
-
const [archivedTeams, setArchivedTeams] = (0, react.useState)([]);
|
|
1061
1213
|
const [open, setOpen] = (0, react.useState)(false);
|
|
1062
1214
|
const [openOwner, setOpenOwner] = (0, react.useState)();
|
|
1063
1215
|
const [autoOpened, setAutoOpened] = (0, react.useState)(false);
|
|
1064
1216
|
const [wasActive, setWasActive] = (0, react.useState)(false);
|
|
1065
1217
|
const [historic, setHistoric] = (0, react.useState)(/* @__PURE__ */ new Map());
|
|
1066
1218
|
const current = (0, react.useSyncExternalStore)(sessionsList.subscribe, sessionsList.getSnapshot).current;
|
|
1219
|
+
const monitorTargets = (0, react.useSyncExternalStore)(subscribeActivityMonitorTargets, getActivityMonitorTargetsSnapshot);
|
|
1220
|
+
const { teams, archivedTeams } = (0, react.useSyncExternalStore)(subscribeActivitySnapshots, getActivitySnapshotsSnapshot);
|
|
1221
|
+
const currentTargets = (0, react.useMemo)(() => current === void 0 ? [] : monitorTargets.filter((target) => target.sessionId === current), [current, monitorTargets]);
|
|
1067
1222
|
const currentRef = (0, react.useRef)(current);
|
|
1068
1223
|
(0, react.useEffect)(() => {
|
|
1069
1224
|
currentRef.current = current;
|
|
@@ -1086,34 +1241,12 @@ window.__ModuleLoader__.load({
|
|
|
1086
1241
|
};
|
|
1087
1242
|
}, [expanded]);
|
|
1088
1243
|
(0, react.useEffect)(() => {
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
const tick = async () => {
|
|
1092
|
-
if (inFlight || cancelled) return;
|
|
1093
|
-
inFlight = true;
|
|
1094
|
-
try {
|
|
1095
|
-
const [liveResponse, archivedResponse] = await Promise.all([fetch(STATE_URL, { cache: "no-store" }), fetch(`${STATE_URL}?archived=1`, { cache: "no-store" })]);
|
|
1096
|
-
if (liveResponse.ok) {
|
|
1097
|
-
const body = await liveResponse.json();
|
|
1098
|
-
if (!cancelled && Array.isArray(body.teams)) setTeams(body.teams);
|
|
1099
|
-
}
|
|
1100
|
-
if (archivedResponse.ok) {
|
|
1101
|
-
const body = await archivedResponse.json();
|
|
1102
|
-
if (!cancelled && Array.isArray(body.teams)) setArchivedTeams(body.teams);
|
|
1103
|
-
}
|
|
1104
|
-
} catch {} finally {
|
|
1105
|
-
inFlight = false;
|
|
1106
|
-
}
|
|
1107
|
-
};
|
|
1108
|
-
tick();
|
|
1109
|
-
const timer = setInterval(() => {
|
|
1110
|
-
tick();
|
|
1111
|
-
}, POLL_MS);
|
|
1244
|
+
if (currentTargets.length === 0) return;
|
|
1245
|
+
const controller = startActivityPolling(currentTargets);
|
|
1112
1246
|
return () => {
|
|
1113
|
-
|
|
1114
|
-
clearInterval(timer);
|
|
1247
|
+
controller.stop();
|
|
1115
1248
|
};
|
|
1116
|
-
}, []);
|
|
1249
|
+
}, [currentTargets]);
|
|
1117
1250
|
(0, react.useEffect)(() => {
|
|
1118
1251
|
const onOpenPanel = (event) => {
|
|
1119
1252
|
const activeSession = currentRef.current;
|
|
@@ -1358,12 +1491,9 @@ window.__ModuleLoader__.load({
|
|
|
1358
1491
|
ctx.slots.inject("conversation.chat.node", () => ctx.slots.register({
|
|
1359
1492
|
name: "conversation.chat.node",
|
|
1360
1493
|
key: "agent-teams",
|
|
1361
|
-
inject: () => ({
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
},
|
|
1365
|
-
currentSessionId: () => ctx.sessions.list.getSnapshot().current
|
|
1366
|
-
})
|
|
1494
|
+
inject: () => ({ openSession: (id) => {
|
|
1495
|
+
ctx.sessions.open(id);
|
|
1496
|
+
} })
|
|
1367
1497
|
}, AgentTeamsCard));
|
|
1368
1498
|
}
|
|
1369
1499
|
//#endregion
|