@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.
Files changed (47) hide show
  1. package/README.md +23 -0
  2. package/README_ZH.md +14 -0
  3. package/assets/agent-teams/action-celebrating-v2.png +0 -0
  4. package/assets/agent-teams/action-reporting-v2.png +0 -0
  5. package/assets/agent-teams/action-sending-v2.png +0 -0
  6. package/assets/agent-teams/action-sleeping-v2.png +0 -0
  7. package/assets/agent-teams/action-thinking-v2.png +0 -0
  8. package/assets/agent-teams/action-working-v2.png +0 -0
  9. package/assets/agent-teams/member-data-v2.png +0 -0
  10. package/assets/agent-teams/member-designer-v2.png +0 -0
  11. package/assets/agent-teams/member-docs-v2.png +0 -0
  12. package/assets/agent-teams/member-engineer-v2.png +0 -0
  13. package/assets/agent-teams/member-operator-v2.png +0 -0
  14. package/assets/agent-teams/member-qa-v2.png +0 -0
  15. package/assets/agent-teams/member-researcher-v2.png +0 -0
  16. package/assets/agent-teams/member-security-v2.png +0 -0
  17. package/assets/agent-teams/team-lead-v2.png +0 -0
  18. package/lib/client/ActivityPanel.js +11 -42
  19. package/lib/client/AgentTeamsCard.js +10 -32
  20. package/lib/client/activity-monitor.js +178 -0
  21. package/lib/client/artwork.js +15 -12
  22. package/lib/client/index.js +0 -1
  23. package/lib/client.js +205 -74
  24. package/lib/client.js.map +1 -1
  25. package/lib/command.js +127 -0
  26. package/lib/index.js +28 -7
  27. package/lib/types/client/ActivityPanel.d.ts +0 -40
  28. package/lib/types/client/AgentTeamsCard.d.ts +1 -2
  29. package/lib/types/client/activity-monitor.d.ts +106 -0
  30. package/lib/types/client/artwork.d.ts +1 -1
  31. package/lib/types/command.d.ts +71 -0
  32. package/lib/types/index.d.ts +6 -0
  33. package/package.json +6 -1
  34. package/assets/agent-teams/action-celebrating.png +0 -0
  35. package/assets/agent-teams/action-reporting.png +0 -0
  36. package/assets/agent-teams/action-sending.png +0 -0
  37. package/assets/agent-teams/action-sleeping.png +0 -0
  38. package/assets/agent-teams/action-thinking.png +0 -0
  39. package/assets/agent-teams/action-working.png +0 -0
  40. package/assets/agent-teams/data-analyst.png +0 -0
  41. package/assets/agent-teams/designer.png +0 -0
  42. package/assets/agent-teams/docs-coordinator.png +0 -0
  43. package/assets/agent-teams/engineer.png +0 -0
  44. package/assets/agent-teams/qa-engineer.png +0 -0
  45. package/assets/agent-teams/researcher.png +0 -0
  46. package/assets/agent-teams/security-reviewer.png +0 -0
  47. 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
- /** Whale role artwork per role keyword. */
324
+ /** V2 whale role artwork per role keyword. */
148
325
  const ROLE_ART = [
149
- [/resear|analys|investig|explor|data|study|研究|分析|数据|调查|探索|调研/, "researcher.png"],
150
- [/engineer|dev\b|server|backend|\bapi\b|runtime|watcher|contract|工程|后端|服务|接口|开发|代码|编程/, "engineer.png"],
151
- [/\bqa\b|test|verif|quality|测试|质量/, "qa-engineer.png"],
152
- [/design|\bui\b|\bux\b|front|theme|accessib|设计|前端|主题/, "designer.png"],
153
- [/secur|audit|risk|threat|review|安全|审计|审查|风险/, "security-reviewer.png"],
154
- [/docs|writer|product|spec|coordin|撰写|文案|写作|文档|协调/, "docs-coordinator.png"],
155
- [/release|\bbuild\b|deploy|\bops\b|\bci\b|ship|发布|构建|部署/, "engineer.png"]
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, currentSessionId }) {
405
+ function AgentTeamsCard({ node, openSession, sessionId }) {
228
406
  const data = node.data;
229
- const owner = data.captainSessionId || currentSessionId() || "";
230
- const [snapshot, setSnapshot] = (0, react.useState)();
407
+ const owner = data.captainSessionId || sessionId;
408
+ const { teams, archivedTeams } = (0, react.useSyncExternalStore)(subscribeActivitySnapshots, getActivitySnapshotsSnapshot);
231
409
  (0, react.useEffect)(() => {
232
- let cancelled = false;
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
- let cancelled = false;
1090
- let inFlight = false;
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
- cancelled = true;
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
- openSession: (id) => {
1363
- ctx.sessions.open(id);
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