@nanmicoder/dsh-agent-teams 0.1.7 → 0.1.9
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/assets/agent-teams/action-celebrating-v2.png +0 -0
- package/assets/agent-teams/action-reporting-v2.png +0 -0
- package/assets/agent-teams/action-sending-v2.png +0 -0
- package/assets/agent-teams/action-sleeping-v2.png +0 -0
- package/assets/agent-teams/action-thinking-v2.png +0 -0
- package/assets/agent-teams/action-working-v2.png +0 -0
- package/assets/agent-teams/member-data-v2.png +0 -0
- package/assets/agent-teams/member-designer-v2.png +0 -0
- package/assets/agent-teams/member-docs-v2.png +0 -0
- package/assets/agent-teams/member-engineer-v2.png +0 -0
- package/assets/agent-teams/member-operator-v2.png +0 -0
- package/assets/agent-teams/member-qa-v2.png +0 -0
- package/assets/agent-teams/member-researcher-v2.png +0 -0
- package/assets/agent-teams/member-security-v2.png +0 -0
- package/assets/agent-teams/team-lead-v2.png +0 -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/artwork.js +15 -12
- package/lib/client/index.js +0 -1
- package/lib/client.js +205 -74
- package/lib/client.js.map +1 -1
- package/lib/command.js +127 -0
- package/lib/index.js +28 -7
- 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/client/artwork.d.ts +1 -1
- package/lib/types/command.d.ts +71 -0
- package/lib/types/index.d.ts +6 -0
- package/package.json +6 -1
- package/assets/agent-teams/action-celebrating.png +0 -0
- package/assets/agent-teams/action-reporting.png +0 -0
- package/assets/agent-teams/action-sending.png +0 -0
- package/assets/agent-teams/action-sleeping.png +0 -0
- package/assets/agent-teams/action-thinking.png +0 -0
- package/assets/agent-teams/action-working.png +0 -0
- package/assets/agent-teams/data-analyst.png +0 -0
- package/assets/agent-teams/designer.png +0 -0
- package/assets/agent-teams/docs-coordinator.png +0 -0
- package/assets/agent-teams/engineer.png +0 -0
- package/assets/agent-teams/qa-engineer.png +0 -0
- package/assets/agent-teams/researcher.png +0 -0
- package/assets/agent-teams/security-reviewer.png +0 -0
- package/assets/agent-teams/team-lead.png +0 -0
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
|
|
@@ -144,23 +321,24 @@ window.__ModuleLoader__.load({
|
|
|
144
321
|
*/
|
|
145
322
|
/** Artwork route prefix served by the plugin host half. */
|
|
146
323
|
const ART_BASE = "/plugins/dsh-agent-teams/assets/";
|
|
147
|
-
/**
|
|
324
|
+
/** V2 whale role artwork per role keyword. */
|
|
148
325
|
const ROLE_ART = [
|
|
149
|
-
[/
|
|
150
|
-
[/
|
|
151
|
-
[/\bqa\b|test|verif|quality
|
|
152
|
-
[/
|
|
153
|
-
[/
|
|
154
|
-
[/
|
|
155
|
-
[/
|
|
326
|
+
[/data|analys|metric|performance|数据|分析|指标|性能/, "member-data-v2.png"],
|
|
327
|
+
[/resear|investig|explor|study|研究|调查|探索|调研/, "member-researcher-v2.png"],
|
|
328
|
+
[/\bqa\b|test|verif|quality|测试|质量|验证/, "member-qa-v2.png"],
|
|
329
|
+
[/engineer|dev\b|server|backend|\bapi\b|runtime|watcher|contract|工程|后端|服务|接口|开发|代码|编程/, "member-engineer-v2.png"],
|
|
330
|
+
[/design|\bui\b|\bux\b|front|theme|accessib|设计|前端|主题|无障碍/, "member-designer-v2.png"],
|
|
331
|
+
[/secur|audit|risk|threat|review|安全|审计|审查|风险/, "member-security-v2.png"],
|
|
332
|
+
[/docs|writer|product|spec|撰写|文案|写作|文档|规范/, "member-docs-v2.png"],
|
|
333
|
+
[/release|\bbuild\b|deploy|\bops\b|\bci\b|ship|coordin|发布|构建|部署|运维|协调/, "member-operator-v2.png"]
|
|
156
334
|
];
|
|
157
335
|
/** Captain artwork (always the lead whale). */
|
|
158
|
-
const LEAD_ART = `${ART_BASE}team-lead.png`;
|
|
336
|
+
const LEAD_ART = `${ART_BASE}team-lead-v2.png`;
|
|
159
337
|
/** Status action artwork per member activity. */
|
|
160
338
|
const ACTION_ART = {
|
|
161
|
-
working: `${ART_BASE}action-working.png`,
|
|
162
|
-
idle: `${ART_BASE}action-sleeping.png`,
|
|
163
|
-
unknown: `${ART_BASE}action-thinking.png`
|
|
339
|
+
working: `${ART_BASE}action-working-v2.png`,
|
|
340
|
+
idle: `${ART_BASE}action-sleeping-v2.png`,
|
|
341
|
+
unknown: `${ART_BASE}action-thinking-v2.png`
|
|
164
342
|
};
|
|
165
343
|
/**
|
|
166
344
|
* Member artwork URL, or null when no role matches (initial-letter fallback).
|
|
@@ -224,33 +402,14 @@ window.__ModuleLoader__.load({
|
|
|
224
402
|
} }));
|
|
225
403
|
}
|
|
226
404
|
/** Render one durable team as a compact conversation card. */
|
|
227
|
-
function AgentTeamsCard({ node, openSession,
|
|
405
|
+
function AgentTeamsCard({ node, openSession, sessionId }) {
|
|
228
406
|
const data = node.data;
|
|
229
|
-
const owner = data.captainSessionId ||
|
|
230
|
-
const
|
|
407
|
+
const owner = data.captainSessionId || sessionId;
|
|
408
|
+
const { teams, archivedTeams } = (0, react.useSyncExternalStore)(subscribeActivitySnapshots, getActivitySnapshotsSnapshot);
|
|
231
409
|
(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
|
-
};
|
|
410
|
+
return monitorAgentTeam(owner, data.teamId);
|
|
253
411
|
}, [data.teamId, owner]);
|
|
412
|
+
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
413
|
const resolved = (0, react.useMemo)(() => ({
|
|
255
414
|
...data,
|
|
256
415
|
captainSessionId: snapshot?.captainSessionId ?? owner,
|
|
@@ -441,8 +600,6 @@ window.__ModuleLoader__.load({
|
|
|
441
600
|
* removed in favor of this always-available monitor.
|
|
442
601
|
* @module dsh-agent-teams/client/activity
|
|
443
602
|
*/
|
|
444
|
-
/** Poll cadence for the host snapshot route. */
|
|
445
|
-
const POLL_MS = 1e3;
|
|
446
603
|
/** Grace before the panel collapses once no team remains. */
|
|
447
604
|
const AUTOCLOSE_GRACE_MS = 2e3;
|
|
448
605
|
/**
|
|
@@ -451,8 +608,6 @@ window.__ModuleLoader__.load({
|
|
|
451
608
|
* right after load. New activity after this window auto-expands as usual.
|
|
452
609
|
*/
|
|
453
610
|
const AUTO_OPEN_SETTLE_MS = 4e3;
|
|
454
|
-
/** Host route serving team snapshots. */
|
|
455
|
-
const STATE_URL = "/plugins/dsh-agent-teams/state";
|
|
456
611
|
/** Root marker shared with the panel CSS while the portal is expanded. */
|
|
457
612
|
const PANEL_OPEN_ATTRIBUTE = "data-agent-teams-panel-open";
|
|
458
613
|
/** Initial-letter fallback for unmatched roles. */
|
|
@@ -1056,14 +1211,15 @@ window.__ModuleLoader__.load({
|
|
|
1056
1211
|
setWasActive(false);
|
|
1057
1212
|
openSession(id);
|
|
1058
1213
|
};
|
|
1059
|
-
const [teams, setTeams] = (0, react.useState)([]);
|
|
1060
|
-
const [archivedTeams, setArchivedTeams] = (0, react.useState)([]);
|
|
1061
1214
|
const [open, setOpen] = (0, react.useState)(false);
|
|
1062
1215
|
const [openOwner, setOpenOwner] = (0, react.useState)();
|
|
1063
1216
|
const [autoOpened, setAutoOpened] = (0, react.useState)(false);
|
|
1064
1217
|
const [wasActive, setWasActive] = (0, react.useState)(false);
|
|
1065
1218
|
const [historic, setHistoric] = (0, react.useState)(/* @__PURE__ */ new Map());
|
|
1066
1219
|
const current = (0, react.useSyncExternalStore)(sessionsList.subscribe, sessionsList.getSnapshot).current;
|
|
1220
|
+
const monitorTargets = (0, react.useSyncExternalStore)(subscribeActivityMonitorTargets, getActivityMonitorTargetsSnapshot);
|
|
1221
|
+
const { teams, archivedTeams } = (0, react.useSyncExternalStore)(subscribeActivitySnapshots, getActivitySnapshotsSnapshot);
|
|
1222
|
+
const currentTargets = (0, react.useMemo)(() => current === void 0 ? [] : monitorTargets.filter((target) => target.sessionId === current), [current, monitorTargets]);
|
|
1067
1223
|
const currentRef = (0, react.useRef)(current);
|
|
1068
1224
|
(0, react.useEffect)(() => {
|
|
1069
1225
|
currentRef.current = current;
|
|
@@ -1086,34 +1242,12 @@ window.__ModuleLoader__.load({
|
|
|
1086
1242
|
};
|
|
1087
1243
|
}, [expanded]);
|
|
1088
1244
|
(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);
|
|
1245
|
+
if (currentTargets.length === 0) return;
|
|
1246
|
+
const controller = startActivityPolling(currentTargets);
|
|
1112
1247
|
return () => {
|
|
1113
|
-
|
|
1114
|
-
clearInterval(timer);
|
|
1248
|
+
controller.stop();
|
|
1115
1249
|
};
|
|
1116
|
-
}, []);
|
|
1250
|
+
}, [currentTargets]);
|
|
1117
1251
|
(0, react.useEffect)(() => {
|
|
1118
1252
|
const onOpenPanel = (event) => {
|
|
1119
1253
|
const activeSession = currentRef.current;
|
|
@@ -1358,12 +1492,9 @@ window.__ModuleLoader__.load({
|
|
|
1358
1492
|
ctx.slots.inject("conversation.chat.node", () => ctx.slots.register({
|
|
1359
1493
|
name: "conversation.chat.node",
|
|
1360
1494
|
key: "agent-teams",
|
|
1361
|
-
inject: () => ({
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
},
|
|
1365
|
-
currentSessionId: () => ctx.sessions.list.getSnapshot().current
|
|
1366
|
-
})
|
|
1495
|
+
inject: () => ({ openSession: (id) => {
|
|
1496
|
+
ctx.sessions.open(id);
|
|
1497
|
+
} })
|
|
1367
1498
|
}, AgentTeamsCard));
|
|
1368
1499
|
}
|
|
1369
1500
|
//#endregion
|