@wowyuarm/dsh-agent-team 0.1.0 → 0.1.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.
Files changed (81) hide show
  1. package/README.md +14 -2
  2. package/README.zh.md +10 -2
  3. package/package.json +2 -2
  4. package/packages/agent-team/lib/attachments.js +157 -0
  5. package/packages/agent-team/lib/index.js +274 -5
  6. package/packages/agent-team/lib/ledger.js +65 -13
  7. package/packages/agent-team/lib/recovery.js +87 -0
  8. package/packages/agent-team/lib/spec.js +17 -1
  9. package/packages/agent-team/lib/typert.host.js +321 -21
  10. package/packages/agent-team/lib/typert.remote-client.d.ts +9 -1
  11. package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
  12. package/packages/agent-team/lib/typert.remote-client.js +249 -17
  13. package/packages/agent-team/lib/types/attachments.d.ts +69 -0
  14. package/packages/agent-team/lib/types/attachments.d.ts.map +1 -0
  15. package/packages/agent-team/lib/types/index.d.ts +66 -1
  16. package/packages/agent-team/lib/types/index.d.ts.map +1 -1
  17. package/packages/agent-team/lib/types/ledger.d.ts +17 -1
  18. package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
  19. package/packages/agent-team/lib/types/recovery.d.ts +42 -0
  20. package/packages/agent-team/lib/types/recovery.d.ts.map +1 -0
  21. package/packages/agent-team/lib/types/spec.d.ts.map +1 -1
  22. package/packages/agent-team/lib/types/types.d.ts +85 -2
  23. package/packages/agent-team/lib/types/types.d.ts.map +1 -1
  24. package/packages/client-agent-team/README.md +2 -2
  25. package/packages/client-agent-team/README.zh.md +2 -2
  26. package/packages/client-agent-team/lib/client.js +1176 -281
  27. package/packages/client-agent-team/lib/client.js.map +1 -1
  28. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts +6 -2
  29. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
  30. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +104 -58
  31. package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts +5 -1
  32. package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts.map +1 -1
  33. package/packages/client-agent-team/lib/types/client/TeamChannelPage.js +44 -19
  34. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.d.ts.map +1 -1
  35. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +23 -18
  36. package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts +4 -1
  37. package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts.map +1 -1
  38. package/packages/client-agent-team/lib/types/client/TeamComposer.js +40 -4
  39. package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts +1 -1
  40. package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts.map +1 -1
  41. package/packages/client-agent-team/lib/types/client/TeamConversation.js +3 -3
  42. package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts +7 -2
  43. package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts.map +1 -1
  44. package/packages/client-agent-team/lib/types/client/TeamMessage.js +34 -8
  45. package/packages/client-agent-team/lib/types/client/TeamRunDivider.d.ts +10 -0
  46. package/packages/client-agent-team/lib/types/client/TeamRunDivider.d.ts.map +1 -0
  47. package/packages/client-agent-team/lib/types/client/TeamRunDivider.js +12 -0
  48. package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts +3 -0
  49. package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -1
  50. package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +18 -11
  51. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts +1 -1
  52. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts.map +1 -1
  53. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.js +6 -5
  54. package/packages/client-agent-team/lib/types/client/attachment-preview.d.ts +22 -0
  55. package/packages/client-agent-team/lib/types/client/attachment-preview.d.ts.map +1 -0
  56. package/packages/client-agent-team/lib/types/client/attachment-preview.js +35 -0
  57. package/packages/client-agent-team/lib/types/client/drafts.d.ts +41 -0
  58. package/packages/client-agent-team/lib/types/client/drafts.d.ts.map +1 -0
  59. package/packages/client-agent-team/lib/types/client/drafts.js +144 -0
  60. package/packages/client-agent-team/lib/types/client/index.d.ts +2 -0
  61. package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
  62. package/packages/client-agent-team/lib/types/client/index.js +50 -10
  63. package/packages/client-agent-team/lib/types/client/locales.d.ts +28 -0
  64. package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
  65. package/packages/client-agent-team/lib/types/client/locales.js +28 -0
  66. package/packages/client-agent-team/lib/types/client/multi-menu-field.d.ts +33 -0
  67. package/packages/client-agent-team/lib/types/client/multi-menu-field.d.ts.map +1 -0
  68. package/packages/client-agent-team/lib/types/client/multi-menu-field.js +30 -0
  69. package/packages/client-agent-team/lib/types/client/navigation.d.ts +16 -1
  70. package/packages/client-agent-team/lib/types/client/navigation.d.ts.map +1 -1
  71. package/packages/client-agent-team/lib/types/client/navigation.js +45 -8
  72. package/packages/client-agent-team/lib/types/client/slots.d.ts +18 -2
  73. package/packages/client-agent-team/lib/types/client/slots.d.ts.map +1 -1
  74. package/packages/client-agent-team/lib/types/client/team-formatters.d.ts +2 -0
  75. package/packages/client-agent-team/lib/types/client/team-formatters.d.ts.map +1 -1
  76. package/packages/client-agent-team/lib/types/client/team-formatters.js +4 -0
  77. package/packages/client-agent-team/lib/types/client/team-separators.d.ts +8 -0
  78. package/packages/client-agent-team/lib/types/client/team-separators.d.ts.map +1 -1
  79. package/packages/client-agent-team/lib/types/client/team-separators.js +14 -0
  80. package/packages/tool-agent-team/lib/index.js +6 -2
  81. package/packages/tool-agent-team/lib/types/index.d.ts.map +1 -1
@@ -76,8 +76,6 @@ export class AgentTeamLedger {
76
76
  const description = request.description.trim();
77
77
  if (name === '')
78
78
  throw new Error('channel name must not be empty');
79
- if (description === '')
80
- throw new Error('channel description must not be empty');
81
79
  const memberIds = this.normalizeUnique(request.memberIds, 'initial Channel members');
82
80
  for (const memberId of memberIds)
83
81
  this.assertJoinableMember(request.workspaceId, memberId);
@@ -107,8 +105,6 @@ export class AgentTeamLedger {
107
105
  const description = request.description.trim();
108
106
  if (name === '')
109
107
  throw new Error('channel name must not be empty');
110
- if (description === '')
111
- throw new Error('channel description must not be empty');
112
108
  const channel = Object.freeze({ ...this.requireChannel(request.workspaceId, request.channelRef), name, description });
113
109
  const operation = Object.freeze({
114
110
  ...this.operationBase(request, this.nextSequence()), kind: 'team/channel-updated',
@@ -132,13 +128,11 @@ export class AgentTeamLedger {
132
128
  const presetId = request.presetId.trim();
133
129
  if (handle === '')
134
130
  throw new Error('member handle must not be empty');
135
- if (description === '')
136
- throw new Error('member description must not be empty');
137
131
  if (presetId === '')
138
132
  throw new Error('member preset must not be empty');
133
+ // Description and initial Channels are optional: a Member with neither is
134
+ // still drivable through its DM view, and joins Channels later.
139
135
  const channelRefs = this.normalizeUnique(request.channelRefs, 'initial Member Channels');
140
- if (channelRefs.length === 0)
141
- throw new Error('Agent Member requires at least one initial Channel');
142
136
  for (const channelRef of channelRefs)
143
137
  this.requireChannel(request.workspaceId, channelRef);
144
138
  this.assertHandleAvailable(request.workspaceId, handle);
@@ -172,8 +166,6 @@ export class AgentTeamLedger {
172
166
  const description = request.description.trim();
173
167
  if (handle === '')
174
168
  throw new Error('member handle must not be empty');
175
- if (description === '')
176
- throw new Error('member description must not be empty');
177
169
  if (handle !== prior.handle)
178
170
  this.assertHandleAvailable(prior.workspaceId, handle, prior.memberId);
179
171
  this.assertModelSelection(request.model);
@@ -199,6 +191,31 @@ export class AgentTeamLedger {
199
191
  resumeMember(request) {
200
192
  return this.setMemberState(request, 'enabled');
201
193
  }
194
+ /**
195
+ * Audit record of one in-place Member session restart. The operation changes
196
+ * no projection state — identity, transcript, and memory are untouched — so
197
+ * replay treats it as a marker while the Host re-mints the live handle.
198
+ */
199
+ restartMember(request) {
200
+ return this.enqueue(async () => {
201
+ const existing = this.state.byRequest.get(request.requestId);
202
+ if (existing !== undefined) {
203
+ this.assertSameMemberRestart(existing, request);
204
+ return this.resolved(this.memberResult(existing));
205
+ }
206
+ this.assertHumanActor(request.actor);
207
+ const member = this.requireMember(request.memberId);
208
+ if (member.state !== 'enabled')
209
+ throw new Error(`Agent Member '${member.handle}' is ${member.state}; only enabled Members can be restarted`);
210
+ const operation = Object.freeze({
211
+ ...this.operationBase(request, this.nextSequence()), kind: 'team/member-session-restarted',
212
+ data: Object.freeze({ member }),
213
+ });
214
+ await this.table.put(operation.operationId, operation);
215
+ this.apply(operation);
216
+ return this.committed(this.memberResult(operation));
217
+ });
218
+ }
202
219
  getMember(memberId) {
203
220
  return this.state.members.get(memberId);
204
221
  }
@@ -292,7 +309,9 @@ export class AgentTeamLedger {
292
309
  const thread = Object.freeze({ threadRef, taskRef, revision: sequence });
293
310
  const message = Object.freeze({
294
311
  messageRef: this.ref('message'), channelRef: channel.channelRef, threadRef, taskRef,
295
- sender: request.actor.memberId, body, topLevel: true, sequence, occurredAt: base.occurredAt,
312
+ sender: request.actor.memberId, body,
313
+ ...(request.resolvedAttachments === undefined ? {} : { attachments: request.resolvedAttachments }),
314
+ topLevel: true, sequence, occurredAt: base.occurredAt,
296
315
  });
297
316
  const started = [this.startAttention(request.actor.memberId, threadRef, sequence),
298
317
  ...recipients.filter(memberId => this.state.members.has(memberId))
@@ -345,7 +364,9 @@ export class AgentTeamLedger {
345
364
  const base = this.operationBase(request, sequence);
346
365
  const message = Object.freeze({
347
366
  messageRef: this.ref('message'), channelRef: task.channelRef, threadRef: task.threadRef,
348
- taskRef: task.taskRef, sender: request.actor.memberId, body, topLevel: false, sequence, occurredAt: base.occurredAt,
367
+ taskRef: task.taskRef, sender: request.actor.memberId, body,
368
+ ...(request.resolvedAttachments === undefined ? {} : { attachments: request.resolvedAttachments }),
369
+ topLevel: false, sequence, occurredAt: base.occurredAt,
349
370
  });
350
371
  const nextThread = Object.freeze({ ...thread, revision: sequence });
351
372
  const started = unfollowedAgents.map(memberId => this.startAttention(memberId, thread.threadRef, sequence));
@@ -684,6 +705,17 @@ export class AgentTeamLedger {
684
705
  return undefined;
685
706
  }
686
707
  }
708
+ /** Attachment ids referenced by any stored Message — the GC's keep-set oracle. */
709
+ referencedAttachmentIds() {
710
+ const referenced = new Set();
711
+ for (const fact of this.state.orderedFacts) {
712
+ if (fact.kind !== 'message')
713
+ continue;
714
+ for (const attachment of fact.message.attachments ?? [])
715
+ referenced.add(attachment.attachmentId);
716
+ }
717
+ return referenced;
718
+ }
687
719
  threadHistory(actor, request) {
688
720
  const authorized = this.assertActorForWorkspace(actor, request.workspaceId);
689
721
  const task = this.requireTask(request.workspaceId, request.taskRef);
@@ -814,6 +846,7 @@ export class AgentTeamLedger {
814
846
  case 'team/member-added':
815
847
  case 'team/member-suspended':
816
848
  case 'team/member-resumed':
849
+ case 'team/member-session-restarted':
817
850
  case 'team/member-updated':
818
851
  case 'team/member-removed':
819
852
  return [{ kind: 'workspace', workspaceId: operation.data.member.workspaceId }];
@@ -987,6 +1020,14 @@ export class AgentTeamLedger {
987
1020
  throw new Error('invalid Member lifecycle transition');
988
1021
  return;
989
1022
  }
1023
+ if (operation.kind === 'team/member-session-restarted') {
1024
+ assertHuman();
1025
+ const prior = projection.members.get(operation.data.member.memberId);
1026
+ // The restart records the unchanged Member: same identity, still enabled.
1027
+ if (prior === undefined || prior.state !== 'enabled' || !this.sameMemberIdentity(prior, operation.data.member))
1028
+ throw new Error('invalid Member restart');
1029
+ return;
1030
+ }
990
1031
  if (operation.kind === 'team/channel-updated') {
991
1032
  assertHuman();
992
1033
  const prior = projection.channels.get(operation.data.channel.channelRef);
@@ -1367,6 +1408,11 @@ export class AgentTeamLedger {
1367
1408
  target.members.set(operation.data.member.memberId, operation.data.member);
1368
1409
  return;
1369
1410
  }
1411
+ if (operation.kind === 'team/member-session-restarted') {
1412
+ // Audit-only: identity, transcript, and memory are untouched, so the
1413
+ // projection deliberately does not change.
1414
+ return;
1415
+ }
1370
1416
  if (operation.kind === 'team/channel-updated') {
1371
1417
  target.channels.set(operation.data.channel.channelRef, operation.data.channel);
1372
1418
  return;
@@ -1941,6 +1987,11 @@ export class AgentTeamLedger {
1941
1987
  || !this.sameList(operation.data.channelRefs, this.normalizeUnique(request.channelRefs, 'initial Member Channels')))
1942
1988
  this.throwRequestCollision(request.requestId);
1943
1989
  }
1990
+ assertSameMemberRestart(operation, request) {
1991
+ if (operation.kind !== 'team/member-session-restarted' || !this.sameActor(operation.actor, request.actor)
1992
+ || operation.data.member.memberId !== request.memberId)
1993
+ this.throwRequestCollision(request.requestId);
1994
+ }
1944
1995
  assertSameMemberState(operation, request, state) {
1945
1996
  const kind = state === 'suspended' ? 'team/member-suspended' : 'team/member-resumed';
1946
1997
  if (operation.kind !== kind || !this.sameActor(operation.actor, request.actor) || operation.data.member.memberId !== request.memberId)
@@ -1972,7 +2023,8 @@ export class AgentTeamLedger {
1972
2023
  assertSameMessage(operation, request, recipients) {
1973
2024
  if (operation.kind !== 'team/message-sent' || !this.sameActor(operation.actor, request.actor)
1974
2025
  || operation.data.workspaceId !== request.workspaceId || operation.data.message.channelRef !== request.channelRef
1975
- || operation.data.message.body !== request.body.trim() || !this.sameList(operation.data.mentions, recipients))
2026
+ || operation.data.message.body !== request.body.trim() || !this.sameList(operation.data.mentions, recipients)
2027
+ || !this.sameList(operation.data.message.attachments?.map(attachment => attachment.attachmentId) ?? [], request.attachments ?? []))
1976
2028
  this.throwRequestCollision(request.requestId);
1977
2029
  }
1978
2030
  assertSameReply(operation, request, recipients) {
@@ -0,0 +1,87 @@
1
+ const TRANSIENT_NETWORK_PATTERNS = [/fetch failed/i, /econnreset/i, /etimedout/i, /socket hang up/i];
2
+ const RATE_LIMIT_PATTERNS = [/\b429\b/, /rate limit/i, /\b503\b/, /overloaded/i];
3
+ export function classifyRecoverableError(message) {
4
+ if (RATE_LIMIT_PATTERNS.some(pattern => pattern.test(message)))
5
+ return 'rate limiting';
6
+ if (TRANSIENT_NETWORK_PATTERNS.some(pattern => pattern.test(message)))
7
+ return 'transient network';
8
+ return undefined;
9
+ }
10
+ export const RECOVERY_DELAY_MS = 120_000;
11
+ export const RECOVERY_MAX_ATTEMPTS = 3;
12
+ /**
13
+ * Per-member automatic recovery episodes. One episode covers a run of
14
+ * identical errors; a changed error string starts a fresh one, and any clean
15
+ * turn end clears the slate. After `maxAttempts` injections of the same error
16
+ * the coordinator stands down and leaves the Member in error for the operator.
17
+ */
18
+ export class RecoveryCoordinator {
19
+ episodes = new Map();
20
+ inject;
21
+ onStandDown;
22
+ delayMs;
23
+ maxAttempts;
24
+ constructor(options) {
25
+ this.inject = options.inject;
26
+ this.onStandDown = options.onStandDown;
27
+ this.delayMs = options.delayMs ?? RECOVERY_DELAY_MS;
28
+ this.maxAttempts = options.maxAttempts ?? RECOVERY_MAX_ATTEMPTS;
29
+ }
30
+ /** Observe one error occurrence for a Member. */
31
+ onError(memberId, errorMessage) {
32
+ const kind = classifyRecoverableError(errorMessage);
33
+ if (kind === undefined) {
34
+ // A non-retryable failure cancels anything pending: retrying cannot help.
35
+ this.stopTracking(memberId);
36
+ return;
37
+ }
38
+ let episode = this.episodes.get(memberId);
39
+ if (episode === undefined || episode.lastError !== errorMessage) {
40
+ this.cancelTimer(episode);
41
+ episode = { kind, lastError: errorMessage, attempts: 0 };
42
+ this.episodes.set(memberId, episode);
43
+ }
44
+ else if (episode.attempts >= this.maxAttempts) {
45
+ // The same error survived every allowed injection — stand down once.
46
+ if (episode.stoodDown !== true) {
47
+ episode.stoodDown = true;
48
+ this.onStandDown?.(memberId, episode.attempts);
49
+ }
50
+ return;
51
+ }
52
+ if (episode.timer !== undefined)
53
+ return;
54
+ episode.timer = setTimeout(() => {
55
+ episode.timer = undefined;
56
+ episode.attempts += 1;
57
+ try {
58
+ this.inject(memberId, episode.attempts, kind);
59
+ }
60
+ catch {
61
+ this.stopTracking(memberId);
62
+ }
63
+ }, this.delayMs);
64
+ }
65
+ /** A turn ended cleanly (running→idle without an error): the episode is over. */
66
+ onCleanTurnEnd(memberId) {
67
+ this.stopTracking(memberId);
68
+ }
69
+ stopTracking(memberId) {
70
+ const episode = this.episodes.get(memberId);
71
+ if (episode === undefined)
72
+ return;
73
+ this.cancelTimer(episode);
74
+ this.episodes.delete(memberId);
75
+ }
76
+ dispose() {
77
+ for (const episode of this.episodes.values())
78
+ this.cancelTimer(episode);
79
+ this.episodes.clear();
80
+ }
81
+ cancelTimer(episode) {
82
+ if (episode?.timer === undefined)
83
+ return;
84
+ clearTimeout(episode.timer);
85
+ episode.timer = undefined;
86
+ }
87
+ }
@@ -11,6 +11,7 @@ const taskRefSchema = z.string().regex(/^task:[^:]+$/).transform(value => value)
11
11
  const threadRefSchema = z.string().regex(/^thread:[^:]+$/).transform(value => value);
12
12
  const claimRefSchema = z.string().regex(/^claim:[^:]+$/).transform(value => value);
13
13
  const activityRefSchema = z.string().regex(/^activity:[^:]+$/).transform(value => value);
14
+ const attachmentIdSchema = z.string().min(1).transform(value => value);
14
15
  const actorSchema = z.discriminatedUnion('kind', [
15
16
  z.object({ kind: z.literal('human'), memberId: memberIdSchema, handle: z.string().min(1) }).strict(),
16
17
  z.object({ kind: z.literal('member'), memberId: memberIdSchema, handle: z.string().min(1) }).strict(),
@@ -45,7 +46,15 @@ const channelSchema = z.object({
45
46
  description: z.string().min(1),
46
47
  createdAtSequence: z.number().int().positive(),
47
48
  }).strict();
48
- // Ledgers written before message occurredAt existed store bare messages; the union schema below stamps them on load.
49
+ // Ledgers written before message occurredAt or attachments existed store bare
50
+ // messages; the union schema below stamps occurredAt on load and leaves absent
51
+ // attachment metadata undefined, so old ledgers replay unchanged.
52
+ const attachmentSchema = z.object({
53
+ attachmentId: attachmentIdSchema,
54
+ name: z.string().min(1),
55
+ byteSize: z.number().int().nonnegative(),
56
+ mediaType: z.string().min(1),
57
+ }).strict();
49
58
  const messageSchema = z.object({
50
59
  messageRef: messageRefSchema,
51
60
  channelRef: channelRefSchema,
@@ -53,6 +62,7 @@ const messageSchema = z.object({
53
62
  taskRef: taskRefSchema,
54
63
  sender: memberIdSchema,
55
64
  body: z.string().min(1),
65
+ attachments: z.array(attachmentSchema).optional(),
56
66
  topLevel: z.boolean(),
57
67
  sequence: z.number().int().positive(),
58
68
  occurredAt: z.string().datetime().optional(),
@@ -212,6 +222,12 @@ const storedAgentTeamOperationSchema = z.discriminatedUnion('kind', [
212
222
  kind: z.literal('team/member-resumed'),
213
223
  data: z.object({ member: memberSchema }).strict(),
214
224
  }).strict(),
225
+ z.object({
226
+ ...operationBase,
227
+ previousOperationId: operationIdSchema.nullable(),
228
+ kind: z.literal('team/member-session-restarted'),
229
+ data: z.object({ member: memberSchema }).strict(),
230
+ }).strict(),
215
231
  z.object({
216
232
  ...operationBase,
217
233
  previousOperationId: operationIdSchema.nullable(),