@wowyuarm/dsh-agent-team 0.1.6 → 0.1.7

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 (85) hide show
  1. package/README.md +11 -5
  2. package/README.zh.md +11 -5
  3. package/package.json +44 -42
  4. package/packages/agent-team/README.md +2 -0
  5. package/packages/agent-team/README.zh.md +2 -0
  6. package/packages/agent-team/lib/index.js +82 -204
  7. package/packages/agent-team/lib/ledger.js +301 -13
  8. package/packages/agent-team/lib/member-context.js +5 -1
  9. package/packages/agent-team/lib/member-runtime.js +229 -0
  10. package/packages/agent-team/lib/preset-roster.js +1 -0
  11. package/packages/agent-team/lib/spec.js +30 -1
  12. package/packages/agent-team/lib/typert.host.js +253 -74
  13. package/packages/agent-team/lib/typert.remote-client.d.ts +5 -1
  14. package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
  15. package/packages/agent-team/lib/typert.remote-client.js +172 -43
  16. package/packages/agent-team/lib/types/entities.js +1 -0
  17. package/packages/agent-team/lib/types/index.d.ts +20 -44
  18. package/packages/agent-team/lib/types/index.d.ts.map +1 -1
  19. package/packages/agent-team/lib/types/ledger.d.ts +34 -1
  20. package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
  21. package/packages/agent-team/lib/types/member-context.d.ts.map +1 -1
  22. package/packages/agent-team/lib/types/member-runtime.d.ts +108 -0
  23. package/packages/agent-team/lib/types/member-runtime.d.ts.map +1 -0
  24. package/packages/agent-team/lib/types/operations.js +1 -0
  25. package/packages/agent-team/lib/types/preset-roster.d.ts.map +1 -1
  26. package/packages/agent-team/lib/types/requests-results.js +1 -0
  27. package/packages/agent-team/lib/types/spec.d.ts.map +1 -1
  28. package/packages/agent-team/lib/types/types/entities.d.ts +339 -0
  29. package/packages/agent-team/lib/types/types/entities.d.ts.map +1 -0
  30. package/packages/agent-team/lib/types/types/operations.d.ts +303 -0
  31. package/packages/agent-team/lib/types/types/operations.d.ts.map +1 -0
  32. package/packages/agent-team/lib/types/types/requests-results.d.ts +527 -0
  33. package/packages/agent-team/lib/types/types/requests-results.d.ts.map +1 -0
  34. package/packages/agent-team/lib/types/types.d.ts +10 -1070
  35. package/packages/agent-team/lib/types/types.d.ts.map +1 -1
  36. package/packages/agent-team/preset/team-member/agent.cordis.yml +4 -2
  37. package/packages/client-agent-team/README.md +3 -1
  38. package/packages/client-agent-team/README.zh.md +3 -1
  39. package/packages/client-agent-team/lib/client.js +444 -298
  40. package/packages/client-agent-team/lib/client.js.map +1 -1
  41. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts +3 -2
  42. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
  43. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +30 -7
  44. package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts +1 -1
  45. package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts.map +1 -1
  46. package/packages/client-agent-team/lib/types/client/TeamChannelPage.js +2 -2
  47. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.d.ts +2 -1
  48. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.d.ts.map +1 -1
  49. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +31 -5
  50. package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts.map +1 -1
  51. package/packages/client-agent-team/lib/types/client/TeamComposer.js +2 -1
  52. package/packages/client-agent-team/lib/types/client/TeamMemberDock.d.ts +12 -0
  53. package/packages/client-agent-team/lib/types/client/TeamMemberDock.d.ts.map +1 -0
  54. package/packages/client-agent-team/lib/types/client/TeamMemberDock.js +13 -0
  55. package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts.map +1 -1
  56. package/packages/client-agent-team/lib/types/client/TeamMessage.js +10 -3
  57. package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts +1 -1
  58. package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -1
  59. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts +1 -1
  60. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts.map +1 -1
  61. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.js +2 -2
  62. package/packages/client-agent-team/lib/types/client/TeamWorkspaceRow.d.ts +1 -1
  63. package/packages/client-agent-team/lib/types/client/TeamWorkspaceRow.d.ts.map +1 -1
  64. package/packages/client-agent-team/lib/types/client/index.d.ts +1 -1
  65. package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
  66. package/packages/client-agent-team/lib/types/client/index.js +25 -28
  67. package/packages/client-agent-team/lib/types/client/locales.d.ts +28 -0
  68. package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
  69. package/packages/client-agent-team/lib/types/client/locales.js +28 -0
  70. package/packages/client-agent-team/lib/types/client/member-session-input.d.ts +1 -3
  71. package/packages/client-agent-team/lib/types/client/member-session-input.d.ts.map +1 -1
  72. package/packages/client-agent-team/lib/types/client/member-session-input.js +18 -6
  73. package/packages/client-agent-team/lib/types/client/navigation.d.ts +2 -1
  74. package/packages/client-agent-team/lib/types/client/navigation.d.ts.map +1 -1
  75. package/packages/client-agent-team/lib/types/client/sidebar-order.d.ts +1 -1
  76. package/packages/client-agent-team/lib/types/client/sidebar-order.d.ts.map +1 -1
  77. package/packages/client-agent-team/lib/types/client/sidebar-sections.d.ts +1 -1
  78. package/packages/client-agent-team/lib/types/client/sidebar-sections.d.ts.map +1 -1
  79. package/packages/client-agent-team/lib/types/client/slots.d.ts +3 -1
  80. package/packages/client-agent-team/lib/types/client/slots.d.ts.map +1 -1
  81. package/packages/client-agent-team/lib/types/client/team-membership.d.ts +1 -1
  82. package/packages/client-agent-team/lib/types/client/team-membership.d.ts.map +1 -1
  83. package/packages/client-agent-team/lib/types/client/TeamMemberSessionComposer.d.ts +0 -17
  84. package/packages/client-agent-team/lib/types/client/TeamMemberSessionComposer.d.ts.map +0 -1
  85. package/packages/client-agent-team/lib/types/client/TeamMemberSessionComposer.js +0 -135
@@ -97,7 +97,7 @@ export class AgentTeamLedger {
97
97
  this.assertJoinableMember(request.workspaceId, memberId);
98
98
  const sequence = this.nextSequence();
99
99
  const channel = Object.freeze({
100
- channelRef: this.ref('channel'), workspaceId: request.workspaceId, name, description, createdAtSequence: sequence,
100
+ channelRef: this.ref('channel'), workspaceId: request.workspaceId, name, description, createdAtSequence: sequence, state: 'active',
101
101
  });
102
102
  const operation = Object.freeze({
103
103
  ...this.operationBase(request, sequence), kind: 'team/channel-created',
@@ -121,7 +121,7 @@ export class AgentTeamLedger {
121
121
  const description = request.description.trim();
122
122
  if (name === '')
123
123
  throw new Error('channel name must not be empty');
124
- const channel = Object.freeze({ ...this.requireChannel(request.workspaceId, request.channelRef), name, description });
124
+ const channel = Object.freeze({ ...this.requireActiveChannel(request.workspaceId, request.channelRef), name, description });
125
125
  const operation = Object.freeze({
126
126
  ...this.operationBase(request, this.nextSequence()), kind: 'team/channel-updated',
127
127
  data: Object.freeze({ workspaceId: request.workspaceId, channel }),
@@ -150,7 +150,7 @@ export class AgentTeamLedger {
150
150
  // still drivable through its DM view, and joins Channels later.
151
151
  const channelRefs = this.normalizeUnique(request.channelRefs, 'initial Member Channels');
152
152
  for (const channelRef of channelRefs)
153
- this.requireChannel(request.workspaceId, channelRef);
153
+ this.requireActiveChannel(request.workspaceId, channelRef);
154
154
  this.assertHandleAvailable(request.workspaceId, handle);
155
155
  this.assertModelSelection(request.member.model);
156
156
  this.assertCapabilities(request.member.capabilities);
@@ -180,6 +180,8 @@ export class AgentTeamLedger {
180
180
  const prior = this.requireMember(request.memberId);
181
181
  if (prior.state === 'inactive')
182
182
  throw new Error(`Agent Member '${prior.memberId}' is inactive and can no longer be edited`);
183
+ if (prior.state === 'archived')
184
+ throw new Error(`Agent Member '${prior.memberId}' is archived and can no longer be edited`);
183
185
  const handle = request.handle.trim();
184
186
  const description = request.description.trim();
185
187
  if (handle === '')
@@ -255,7 +257,7 @@ export class AgentTeamLedger {
255
257
  return this.resolved(this.joinResult(existing));
256
258
  }
257
259
  this.assertHumanActor(request.actor);
258
- const channel = this.requireChannel(request.workspaceId, request.channelRef);
260
+ const channel = this.requireActiveChannel(request.workspaceId, request.channelRef);
259
261
  const member = this.requireMember(request.memberId);
260
262
  if (member.workspaceId !== request.workspaceId)
261
263
  throw new Error('Member and Channel must belong to one Workspace');
@@ -307,6 +309,48 @@ export class AgentTeamLedger {
307
309
  return this.committed(this.channelMemberRemovalResult(operation));
308
310
  });
309
311
  }
312
+ /**
313
+ * Archive one Channel: hidden from every surface, facts kept recoverable.
314
+ * Every active Claim on the Channel's Threads releases with one public
315
+ * Activity per (owner, Thread), and every Member's Attention and markers
316
+ * for those Threads clear — a hidden Channel must not leave Tasks stuck in
317
+ * progress behind it or phantom unread counts.
318
+ */
319
+ archiveChannel(request) {
320
+ return this.enqueue(async () => {
321
+ const existing = this.state.byRequest.get(request.requestId);
322
+ if (existing !== undefined) {
323
+ this.assertSameChannelArchival(existing, request);
324
+ return this.resolved(this.channelArchivalResult(existing));
325
+ }
326
+ this.assertHumanActor(request.actor);
327
+ const channel = this.requireChannel(request.workspaceId, request.channelRef);
328
+ if (channel.state === 'archived')
329
+ throw new Error(`Channel '${channel.channelRef}' is already archived`);
330
+ const nextChannel = Object.freeze({ ...channel, state: 'archived' });
331
+ const threadRefs = this.channelThreadRefs(channel.channelRef);
332
+ const releasedClaims = [...this.state.claims.values()]
333
+ .filter(claim => claim.state === 'active' && threadRefs.has(claim.threadRef))
334
+ .map(claim => Object.freeze({ ...claim, state: 'released' }));
335
+ const nextClaims = new Map(this.state.claims);
336
+ for (const claim of releasedClaims)
337
+ nextClaims.set(claim.claimRef, claim);
338
+ const sequence = this.nextSequence();
339
+ const owners = [...new Set(releasedClaims.map(claim => claim.owner))].sort();
340
+ const activities = Object.freeze(owners.flatMap(owner => this.releaseSummaries(releasedClaims.filter(claim => claim.owner === owner), owner, sequence)));
341
+ const threads = this.threadsForActivities(activities);
342
+ const tasks = this.tasksForClaims(releasedClaims, nextClaims);
343
+ const inbox = this.channelArchivalInbox(threadRefs);
344
+ const operation = Object.freeze({
345
+ ...this.operationBase(request, sequence), kind: 'team/channel-archived',
346
+ data: Object.freeze({ workspaceId: request.workspaceId, channel: nextChannel,
347
+ claims: Object.freeze(releasedClaims), activities, tasks, threads, inbox }),
348
+ });
349
+ await this.table.put(operation.operationId, operation);
350
+ this.apply(operation);
351
+ return this.committed(this.channelArchivalResult(operation));
352
+ });
353
+ }
310
354
  sendMessage(request) {
311
355
  return this.enqueue(async () => {
312
356
  const recipients = this.normalizeRecipients(request.actor, request.recipients);
@@ -316,7 +360,7 @@ export class AgentTeamLedger {
316
360
  return this.resolved(this.messageResult(existing));
317
361
  }
318
362
  const actor = this.assertActorForWorkspace(request.actor, request.workspaceId);
319
- const channel = this.requireChannel(request.workspaceId, request.channelRef);
363
+ const channel = this.requireActiveChannel(request.workspaceId, request.channelRef);
320
364
  if (actor.kind === 'member')
321
365
  this.requireMemberChannel(this.requireMember(actor.memberId), channel.channelRef);
322
366
  const body = request.body.trim();
@@ -589,7 +633,9 @@ export class AgentTeamLedger {
589
633
  const activities = this.releaseSummaries(releasedClaims, member.memberId, sequence);
590
634
  const threads = this.threadsForActivities(activities);
591
635
  const tasks = this.tasksForClaims(releasedClaims, projectedClaims);
592
- const threadRefs = new Set([...this.state.tasks.values()].map(task => task.threadRef));
636
+ // The Member leaves entirely, so Attention and markers clear on every
637
+ // Thread — taskless ones too — matching the replay validator's scope.
638
+ const threadRefs = new Set(this.state.threads.keys());
593
639
  const inbox = this.removeMemberThreadInbox(member.memberId, threadRefs);
594
640
  const operation = Object.freeze({
595
641
  ...this.operationBase(request, sequence), kind: 'team/member-removed',
@@ -601,6 +647,49 @@ export class AgentTeamLedger {
601
647
  return this.committed(this.removalResult(operation));
602
648
  });
603
649
  }
650
+ /**
651
+ * Archive one Member: hidden from every surface, data kept recoverable.
652
+ * The release shape matches removal — active Claims release with public
653
+ * Activities and the Member's Attention/markers clear — because a hidden
654
+ * Member must not leave Tasks stuck in progress or phantom unread counts.
655
+ */
656
+ archiveMember(request) {
657
+ return this.enqueue(async () => {
658
+ const existing = this.state.byRequest.get(request.requestId);
659
+ if (existing !== undefined) {
660
+ this.assertSameArchival(existing, request);
661
+ return this.resolved(this.archivalResult(existing));
662
+ }
663
+ this.assertHumanActor(request.actor);
664
+ const member = this.requireMember(request.memberId);
665
+ if (member.state === 'inactive')
666
+ throw new Error(`Agent Member '${member.memberId}' is already inactive`);
667
+ if (member.state === 'archived')
668
+ throw new Error(`Agent Member '${member.memberId}' is already archived`);
669
+ const nextMember = Object.freeze({ ...member, state: 'archived' });
670
+ const releasedClaims = [...this.state.claims.values()].filter(claim => claim.owner === member.memberId && claim.state === 'active')
671
+ .map(claim => Object.freeze({ ...claim, state: 'released' }));
672
+ const projectedClaims = new Map(this.state.claims);
673
+ for (const claim of releasedClaims)
674
+ projectedClaims.set(claim.claimRef, claim);
675
+ const sequence = this.nextSequence();
676
+ const activities = this.releaseSummaries(releasedClaims, member.memberId, sequence);
677
+ const threads = this.threadsForActivities(activities);
678
+ const tasks = this.tasksForClaims(releasedClaims, projectedClaims);
679
+ // The Member leaves entirely, so Attention and markers clear on every
680
+ // Thread — taskless ones too — matching the replay validator's scope.
681
+ const threadRefs = new Set(this.state.threads.keys());
682
+ const inbox = this.removeMemberThreadInbox(member.memberId, threadRefs);
683
+ const operation = Object.freeze({
684
+ ...this.operationBase(request, sequence), kind: 'team/member-archived',
685
+ data: Object.freeze({ member: nextMember, claims: Object.freeze(releasedClaims), activities, tasks, threads, inbox }),
686
+ });
687
+ await this.table.put(operation.operationId, operation);
688
+ this.apply(operation);
689
+ this.confirmations.clear();
690
+ return this.committed(this.archivalResult(operation));
691
+ });
692
+ }
604
693
  changeAttention(request) {
605
694
  return this.enqueue(async () => {
606
695
  const existing = this.state.byRequest.get(request.requestId);
@@ -858,7 +947,11 @@ export class AgentTeamLedger {
858
947
  const resolved = [];
859
948
  for (const taskRef of taskRefs) {
860
949
  const task = this.state.tasks.get(taskRef);
861
- if (task === undefined || this.state.channels.get(task.channelRef)?.workspaceId !== workspaceId)
950
+ const channel = task === undefined ? undefined : this.state.channels.get(task.channelRef);
951
+ // Archived Channels do not exist on Team API surfaces: their Tasks stop
952
+ // resolving, so live message bodies render the refs as plain
953
+ // non-navigable text through the existing unknown-ref path.
954
+ if (task === undefined || channel?.workspaceId !== workspaceId || channel?.state === 'archived')
862
955
  continue;
863
956
  resolved.push(Object.freeze({ taskRef: task.taskRef, channelRef: task.channelRef, threadRef: task.threadRef, taskNumber: numbers.get(task.taskRef) ?? 0 }));
864
957
  }
@@ -880,7 +973,7 @@ export class AgentTeamLedger {
880
973
  throw new Error('Member cannot view another Workspace');
881
974
  }
882
975
  if (request.channelRef !== undefined) {
883
- this.requireChannel(request.workspaceId, request.channelRef);
976
+ this.requireActiveChannel(request.workspaceId, request.channelRef);
884
977
  if (memberId !== undefined && !this.isChannelMember(request.channelRef, memberId))
885
978
  throw new Error(`Agent Member '${memberId}' is not authorized for Channel '${request.channelRef}'`);
886
979
  }
@@ -891,12 +984,14 @@ export class AgentTeamLedger {
891
984
  throw new Error(`unknown Thread ref '${request.threadRef}'`);
892
985
  if (this.state.channels.get(channelRef)?.workspaceId !== request.workspaceId)
893
986
  throw new Error(`Thread '${request.threadRef}' does not belong to Workspace '${request.workspaceId}'`);
987
+ this.assertThreadChannelActive(channelRef);
894
988
  if (request.channelRef !== undefined && channelRef !== request.channelRef)
895
989
  throw new Error(`Thread '${request.threadRef}' does not belong to Channel '${request.channelRef}'`);
896
990
  if (memberId !== undefined && !this.isChannelMember(channelRef, memberId))
897
991
  throw new Error(`Agent Member '${memberId}' is not authorized for Channel '${channelRef}'`);
898
992
  }
899
993
  const channels = [...this.state.channels.values()].filter(channel => channel.workspaceId === request.workspaceId
994
+ && channel.state !== 'archived'
900
995
  && (memberId === undefined || this.isChannelMember(channel.channelRef, memberId)));
901
996
  const channelRefs = new Set(channels.map(channel => channel.channelRef));
902
997
  const allFacts = this.state.orderedFacts.filter(fact => {
@@ -931,7 +1026,10 @@ export class AgentTeamLedger {
931
1026
  return Object.freeze({
932
1027
  humanMemberId: initialization.data.humanMemberId,
933
1028
  channels: Object.freeze(channels),
934
- members: Object.freeze([...this.state.memberships.entries()].filter(([channelRef]) => channelRefs.has(channelRef)).flatMap(([channelRef, ids]) => [...ids].filter(id => this.state.members.get(id)?.state !== 'inactive').sort().map(memberId => Object.freeze({ channelRef, memberId })))),
1029
+ members: Object.freeze([...this.state.memberships.entries()].filter(([channelRef]) => channelRefs.has(channelRef)).flatMap(([channelRef, ids]) => [...ids].filter(id => {
1030
+ const state = this.state.members.get(id)?.state;
1031
+ return state !== 'inactive' && state !== 'archived';
1032
+ }).sort().map(memberId => Object.freeze({ channelRef, memberId })))),
935
1033
  tasks: Object.freeze(visibleTasks),
936
1034
  threads: Object.freeze([...this.state.threads.values()].filter(thread => {
937
1035
  const channelRef = this.channelRefForThread(thread.threadRef);
@@ -981,6 +1079,20 @@ export class AgentTeamLedger {
981
1079
  case 'team/member-updated':
982
1080
  case 'team/member-removed':
983
1081
  return [{ kind: 'workspace', workspaceId: operation.data.member.workspaceId }];
1082
+ case 'team/member-archived': {
1083
+ // Claim releases are thread-visible facts: open Channel and Thread
1084
+ // pages refetch alongside the workspace-wide roster change.
1085
+ const channelByTask = new Map(operation.data.tasks.map(task => [task.taskRef, task.channelRef]));
1086
+ const scopes = [{ kind: 'workspace', workspaceId: operation.data.member.workspaceId }];
1087
+ for (const activity of operation.data.activities) {
1088
+ scopes.push({ kind: 'thread', threadRef: activity.threadRef });
1089
+ const channelRef = channelByTask.get(activity.taskRef);
1090
+ if (channelRef !== undefined && !scopes.some(scope => scope.kind === 'channel' && scope.channelRef === channelRef)) {
1091
+ scopes.push({ kind: 'channel', channelRef });
1092
+ }
1093
+ }
1094
+ return scopes;
1095
+ }
984
1096
  case 'team/channel-member-added':
985
1097
  return [{ kind: 'workspace', workspaceId: operation.data.workspaceId }, { kind: 'channel', channelRef: operation.data.channelRef }];
986
1098
  case 'team/channel-member-removed': {
@@ -998,6 +1110,15 @@ export class AgentTeamLedger {
998
1110
  }
999
1111
  return scopes;
1000
1112
  }
1113
+ case 'team/channel-archived': {
1114
+ const scopes = [
1115
+ { kind: 'workspace', workspaceId: operation.data.workspaceId },
1116
+ { kind: 'channel', channelRef: operation.data.channel.channelRef },
1117
+ ];
1118
+ for (const activity of operation.data.activities)
1119
+ scopes.push({ kind: 'thread', threadRef: activity.threadRef });
1120
+ return scopes;
1121
+ }
1001
1122
  case 'team/message-sent':
1002
1123
  case 'team/thread-replied':
1003
1124
  return [{ kind: 'channel', channelRef: operation.data.message.channelRef }, { kind: 'thread', threadRef: operation.data.message.threadRef }];
@@ -1062,7 +1183,9 @@ export class AgentTeamLedger {
1062
1183
  case 'team/thread-attention-changed':
1063
1184
  return [operation.data.thread.threadRef];
1064
1185
  case 'team/channel-member-removed':
1186
+ case 'team/channel-archived':
1065
1187
  case 'team/member-removed':
1188
+ case 'team/member-archived':
1066
1189
  return operation.data.activities.map(activity => activity.threadRef);
1067
1190
  case 'team/initialized':
1068
1191
  case 'team/channel-created':
@@ -1209,7 +1332,8 @@ export class AgentTeamLedger {
1209
1332
  assertHuman();
1210
1333
  const prior = projection.channels.get(operation.data.channel.channelRef);
1211
1334
  if (prior === undefined || prior.workspaceId !== operation.data.workspaceId
1212
- || operation.data.channel.workspaceId !== prior.workspaceId || operation.data.channel.createdAtSequence !== prior.createdAtSequence) {
1335
+ || operation.data.channel.workspaceId !== prior.workspaceId || operation.data.channel.createdAtSequence !== prior.createdAtSequence
1336
+ || operation.data.channel.state !== prior.state) {
1213
1337
  throw new Error('invalid Channel update');
1214
1338
  }
1215
1339
  return;
@@ -1217,7 +1341,7 @@ export class AgentTeamLedger {
1217
1341
  if (operation.kind === 'team/member-updated') {
1218
1342
  assertHuman();
1219
1343
  const prior = projection.members.get(operation.data.member.memberId);
1220
- if (prior === undefined || prior.state === 'inactive' || operation.data.member.state !== prior.state
1344
+ if (prior === undefined || prior.state === 'inactive' || prior.state === 'archived' || operation.data.member.state !== prior.state
1221
1345
  || operation.data.member.sessionId !== prior.sessionId || operation.data.member.workspaceId !== prior.workspaceId
1222
1346
  || operation.data.member.presetId !== prior.presetId
1223
1347
  || operation.data.member.privateMemoryPath !== prior.privateMemoryPath)
@@ -1250,6 +1374,18 @@ export class AgentTeamLedger {
1250
1374
  this.validateReleaseCleanup(operation.data, projection, member.memberId, threadRefs, operation.sequence, refs);
1251
1375
  return;
1252
1376
  }
1377
+ if (operation.kind === 'team/channel-archived') {
1378
+ assertHuman();
1379
+ const prior = projection.channels.get(operation.data.channel.channelRef);
1380
+ if (prior === undefined || prior.state !== 'active' || operation.data.channel.state !== 'archived'
1381
+ || operation.data.channel.workspaceId !== prior.workspaceId || operation.data.workspaceId !== prior.workspaceId
1382
+ || operation.data.channel.name !== prior.name || operation.data.channel.description !== prior.description
1383
+ || operation.data.channel.createdAtSequence !== prior.createdAtSequence)
1384
+ throw new Error('invalid Channel archival');
1385
+ const threadRefs = new Set([...projection.threads.keys()].filter(threadRef => this.channelRefForThreadFrom(projection, threadRef) === prior.channelRef));
1386
+ this.validateChannelArchivalCleanup(operation.data, projection, threadRefs, operation.sequence, refs);
1387
+ return;
1388
+ }
1253
1389
  if (operation.kind === 'team/member-removed') {
1254
1390
  assertHuman();
1255
1391
  const prior = projection.members.get(operation.data.member.memberId);
@@ -1259,6 +1395,15 @@ export class AgentTeamLedger {
1259
1395
  this.validateReleaseCleanup(operation.data, projection, prior.memberId, threadRefs, operation.sequence, refs);
1260
1396
  return;
1261
1397
  }
1398
+ if (operation.kind === 'team/member-archived') {
1399
+ assertHuman();
1400
+ const prior = projection.members.get(operation.data.member.memberId);
1401
+ if (prior === undefined || (prior.state !== 'enabled' && prior.state !== 'suspended') || operation.data.member.state !== 'archived' || !this.sameMemberIdentity(prior, operation.data.member))
1402
+ throw new Error('invalid Member archival');
1403
+ const threadRefs = new Set(projection.threads.keys());
1404
+ this.validateReleaseCleanup(operation.data, projection, prior.memberId, threadRefs, operation.sequence, refs);
1405
+ return;
1406
+ }
1262
1407
  if (operation.kind === 'team/thread-attention-changed') {
1263
1408
  const actor = operation.actor.kind === 'member' ? assertMember() : (assertHuman(), undefined);
1264
1409
  if (operation.data.memberId !== operation.actor.memberId)
@@ -1581,6 +1726,68 @@ export class AgentTeamLedger {
1581
1726
  throw new Error('invalid Member inbox cleanup');
1582
1727
  this.validateInboxDelta(data.inbox, projection, refs);
1583
1728
  }
1729
+ /** Replay validation of one Channel archival's release snapshot across every owner. */
1730
+ validateChannelArchivalCleanup(data, projection, threadRefs, sequence, refs) {
1731
+ const releasedClaims = [...projection.claims.values()]
1732
+ .filter(claim => claim.state === 'active' && threadRefs.has(claim.threadRef))
1733
+ .map(claim => Object.freeze({ ...claim, state: 'released' }));
1734
+ if (data.claims.length !== releasedClaims.length || data.claims.some((claim, index) => {
1735
+ const expected = releasedClaims[index];
1736
+ return expected === undefined || !this.sameClaim(expected, claim);
1737
+ }))
1738
+ throw new Error('invalid released Claim projection');
1739
+ // One activity per (owner, Thread), owners sorted like the commit path.
1740
+ const byOwner = new Map();
1741
+ for (const claim of releasedClaims) {
1742
+ const byThread = byOwner.get(claim.owner) ?? new Map();
1743
+ byOwner.set(claim.owner, byThread);
1744
+ byThread.set(claim.threadRef, [...(byThread.get(claim.threadRef) ?? []), claim]);
1745
+ }
1746
+ const expectedActivities = [...byOwner.keys()].sort().flatMap(owner => [...byOwner.get(owner).entries()].map(([threadRef, claims]) => ({
1747
+ kind: 'claims_released', taskRef: claims[0].taskRef, threadRef, actor: owner, sequence,
1748
+ claimRefs: claims.map(claim => claim.claimRef).sort(),
1749
+ })));
1750
+ if (data.activities.length !== expectedActivities.length || data.activities.some((activity, index) => {
1751
+ const expected = expectedActivities[index];
1752
+ return expected === undefined || activity.kind !== expected.kind || activity.taskRef !== expected.taskRef
1753
+ || activity.threadRef !== expected.threadRef || activity.actor !== expected.actor || activity.sequence !== expected.sequence
1754
+ || !this.sameList(activity.claimRefs, expected.claimRefs);
1755
+ }))
1756
+ throw new Error('invalid released Claim activities');
1757
+ for (const claim of data.claims) {
1758
+ const prior = projection.claims.get(claim.claimRef);
1759
+ if (prior === undefined)
1760
+ throw new Error('invalid released Claim reference');
1761
+ }
1762
+ for (const activity of data.activities)
1763
+ this.addRef(refs, activity.activityRef);
1764
+ const projectedClaims = new Map(projection.claims);
1765
+ for (const claim of releasedClaims)
1766
+ projectedClaims.set(claim.claimRef, claim);
1767
+ const expectedTasks = [...new Set(releasedClaims.map(claim => claim.taskRef))].map(taskRef => {
1768
+ const task = projection.tasks.get(taskRef);
1769
+ return Object.freeze({ ...task, status: this.deriveResolvedTaskStatus(task, projectedClaims.values()) });
1770
+ });
1771
+ const expectedThreads = expectedActivities.map(activity => {
1772
+ const thread = projection.threads.get(activity.threadRef);
1773
+ return Object.freeze({ ...thread, revision: sequence });
1774
+ });
1775
+ if (!isDeepStrictEqual(data.tasks, expectedTasks) || !isDeepStrictEqual(data.threads, expectedThreads)) {
1776
+ throw new Error('invalid released Claim Task or Thread projection');
1777
+ }
1778
+ // Attention and marker cleanup covers EVERY Member on the archived Threads.
1779
+ const expectedAttention = [...projection.attention.values()]
1780
+ .filter(attention => threadRefs.has(attention.threadRef))
1781
+ .map(attention => ({ memberId: attention.memberId, threadRef: attention.threadRef }));
1782
+ const expectedMarkers = [...projection.directMarkers.values()]
1783
+ .filter(marker => threadRefs.has(marker.threadRef));
1784
+ const expectedActivityMarkers = [...projection.activityMarkers.values()]
1785
+ .filter(marker => threadRefs.has(marker.threadRef));
1786
+ const expectedInbox = this.inboxDelta([], expectedAttention, [], expectedMarkers, [], expectedActivityMarkers);
1787
+ if (!isDeepStrictEqual(data.inbox, expectedInbox))
1788
+ throw new Error('invalid Channel archival inbox cleanup');
1789
+ this.validateInboxDelta(data.inbox, projection, refs);
1790
+ }
1584
1791
  validateMessageInbox(operation, projection) {
1585
1792
  const { message, mentions } = operation.data;
1586
1793
  const mentionedAgents = mentions.filter(memberId => projection.members.has(memberId));
@@ -1612,7 +1819,7 @@ export class AgentTeamLedger {
1612
1819
  if (memberId === AGENT_TEAM_HUMAN_MEMBER_ID)
1613
1820
  return true;
1614
1821
  const member = projection.members.get(memberId);
1615
- return member !== undefined && member.state !== 'inactive'
1822
+ return member !== undefined && member.state !== 'inactive' && member.state !== 'archived'
1616
1823
  && projection.channels.get(channelRef)?.workspaceId === member.workspaceId
1617
1824
  && this.isChannelMemberFrom(projection, channelRef, memberId);
1618
1825
  }
@@ -1679,6 +1886,22 @@ export class AgentTeamLedger {
1679
1886
  this.applyInboxDelta(target, operation.data.inbox);
1680
1887
  return;
1681
1888
  }
1889
+ if (operation.kind === 'team/channel-archived') {
1890
+ // Archival keeps Memberships: hidden state, not departure — a future
1891
+ // restore returns every Member to the Channel. Visibility filters by
1892
+ // channel state instead.
1893
+ target.channels.set(operation.data.channel.channelRef, operation.data.channel);
1894
+ for (const claim of operation.data.claims)
1895
+ target.claims.set(claim.claimRef, claim);
1896
+ for (const activity of operation.data.activities)
1897
+ this.appendActivityFact(target, activity);
1898
+ for (const task of operation.data.tasks)
1899
+ target.tasks.set(task.taskRef, task);
1900
+ for (const thread of operation.data.threads)
1901
+ target.threads.set(thread.threadRef, thread);
1902
+ this.applyInboxDelta(target, operation.data.inbox);
1903
+ return;
1904
+ }
1682
1905
  if (operation.kind === 'team/member-removed') {
1683
1906
  target.members.set(operation.data.member.memberId, operation.data.member);
1684
1907
  for (const membership of target.memberships.values())
@@ -1694,6 +1917,22 @@ export class AgentTeamLedger {
1694
1917
  this.applyInboxDelta(target, operation.data.inbox);
1695
1918
  return;
1696
1919
  }
1920
+ if (operation.kind === 'team/member-archived') {
1921
+ // Archival keeps Memberships: hidden state, not departure — a future
1922
+ // restore returns the Member to its Channels. Visibility filters by
1923
+ // member state instead.
1924
+ target.members.set(operation.data.member.memberId, operation.data.member);
1925
+ for (const claim of operation.data.claims)
1926
+ target.claims.set(claim.claimRef, claim);
1927
+ for (const activity of operation.data.activities)
1928
+ this.appendActivityFact(target, activity);
1929
+ for (const task of operation.data.tasks)
1930
+ target.tasks.set(task.taskRef, task);
1931
+ for (const thread of operation.data.threads)
1932
+ target.threads.set(thread.threadRef, thread);
1933
+ this.applyInboxDelta(target, operation.data.inbox);
1934
+ return;
1935
+ }
1697
1936
  if (operation.kind === 'team/thread-promoted') {
1698
1937
  this.appendActivityFact(target, operation.data.activity);
1699
1938
  target.tasks.set(operation.data.task.taskRef, operation.data.task);
@@ -1944,6 +2183,14 @@ export class AgentTeamLedger {
1944
2183
  const activityMarkers = [...this.state.activityMarkers.values()].filter(marker => marker.memberId === memberId && threadRefs.has(marker.threadRef));
1945
2184
  return this.inboxDelta([], removed, [], markers, [], activityMarkers);
1946
2185
  }
2186
+ /** Attention and marker cleanup for EVERY Member on the given Threads. */
2187
+ channelArchivalInbox(threadRefs) {
2188
+ const removed = [...this.state.attention.values()].filter(attention => threadRefs.has(attention.threadRef))
2189
+ .map(attention => Object.freeze({ memberId: attention.memberId, threadRef: attention.threadRef }));
2190
+ const markers = [...this.state.directMarkers.values()].filter(marker => threadRefs.has(marker.threadRef));
2191
+ const activityMarkers = [...this.state.activityMarkers.values()].filter(marker => threadRefs.has(marker.threadRef));
2192
+ return this.inboxDelta([], removed, [], markers, [], activityMarkers);
2193
+ }
1947
2194
  inboxDelta(set = [], removed = [], addedMarkers = [], removedMarkers = [], addedActivityMarkers = [], removedActivityMarkers = []) {
1948
2195
  return Object.freeze({
1949
2196
  attention: Object.freeze({ set: Object.freeze(set), removed: Object.freeze(removed) }),
@@ -2019,7 +2266,7 @@ export class AgentTeamLedger {
2019
2266
  if (memberId === AGENT_TEAM_HUMAN_MEMBER_ID)
2020
2267
  continue;
2021
2268
  const member = this.requireMember(memberId);
2022
- if (member.state === 'inactive' || member.workspaceId !== channel.workspaceId || !this.isChannelMember(channel.channelRef, memberId)) {
2269
+ if (member.state === 'inactive' || member.state === 'archived' || member.workspaceId !== channel.workspaceId || !this.isChannelMember(channel.channelRef, memberId)) {
2023
2270
  throw new Error(`Agent Member '${memberId}' is not authorized for Channel '${channel.channelRef}'`);
2024
2271
  }
2025
2272
  }
@@ -2070,6 +2317,9 @@ export class AgentTeamLedger {
2070
2317
  threadForActor(actor, workspaceId, taskRef) {
2071
2318
  const task = this.requireTask(workspaceId, taskRef);
2072
2319
  const thread = this.requireThread(task.threadRef);
2320
+ // Archived Channels do not exist on Team API surfaces: their Tasks and
2321
+ // Claims are unreachable for reads (listClaims) as for writes.
2322
+ this.assertThreadChannelActive(task.channelRef);
2073
2323
  if (actor.kind === 'member')
2074
2324
  this.requireMemberChannel(this.requireMember(actor.memberId), task.channelRef);
2075
2325
  return { task, thread };
@@ -2245,6 +2495,13 @@ export class AgentTeamLedger {
2245
2495
  throw new Error(`Thread '${thread.threadRef}' has no Channel`);
2246
2496
  if (projection.channels.get(channelRef)?.workspaceId !== workspaceId)
2247
2497
  throw new Error(`Thread '${thread.threadRef}' does not belong to Workspace '${workspaceId}'`);
2498
+ // Archived Channels do not exist on Team API surfaces: their Threads are
2499
+ // unreachable by ref, for reads as for writes. Replay stays honest —
2500
+ // operations predating the archival resolve against the then-active
2501
+ // Channel because replay applies in sequence.
2502
+ if (projection.channels.get(channelRef)?.state === 'archived') {
2503
+ throw new Error(`Channel '${channelRef}' is archived and no longer accepts Team work`);
2504
+ }
2248
2505
  if (task !== undefined && task.channelRef !== channelRef)
2249
2506
  throw new Error(`Task '${task.taskRef}' does not belong to Thread '${thread.threadRef}'`);
2250
2507
  return { ...(task === undefined ? {} : { task }), thread, channelRef };
@@ -2257,6 +2514,19 @@ export class AgentTeamLedger {
2257
2514
  throw new Error(`Channel '${channelRef}' does not belong to Workspace '${workspaceId}'`);
2258
2515
  return channel;
2259
2516
  }
2517
+ /** Guard for every mutating or Channel-scoped surface flow: archived Channels reject. */
2518
+ requireActiveChannel(workspaceId, channelRef) {
2519
+ const channel = this.requireChannel(workspaceId, channelRef);
2520
+ if (channel.state === 'archived')
2521
+ throw new Error(`Channel '${channelRef}' is archived and no longer accepts Team work`);
2522
+ return channel;
2523
+ }
2524
+ /** Thread-mutation guard: the Channel owning the Thread must still be active. */
2525
+ assertThreadChannelActive(channelRef) {
2526
+ if (this.state.channels.get(channelRef)?.state === 'archived') {
2527
+ throw new Error(`Channel '${channelRef}' is archived and no longer accepts Team work`);
2528
+ }
2529
+ }
2260
2530
  requireMember(memberId) {
2261
2531
  const member = this.state.members.get(memberId);
2262
2532
  if (member === undefined)
@@ -2393,6 +2663,11 @@ export class AgentTeamLedger {
2393
2663
  || operation.data.workspaceId !== request.workspaceId || operation.data.channelRef !== request.channelRef || operation.data.memberId !== request.memberId)
2394
2664
  this.throwRequestCollision(request.requestId);
2395
2665
  }
2666
+ assertSameChannelArchival(operation, request) {
2667
+ if (operation.kind !== 'team/channel-archived' || !this.sameActor(operation.actor, request.actor)
2668
+ || operation.data.workspaceId !== request.workspaceId || operation.data.channel.channelRef !== request.channelRef)
2669
+ this.throwRequestCollision(request.requestId);
2670
+ }
2396
2671
  assertSameDm(operation, request) {
2397
2672
  if (operation.kind !== 'team/dm-sent' || !this.sameActor(operation.actor, request.actor)
2398
2673
  || operation.data.workspaceId !== request.workspaceId
@@ -2448,6 +2723,11 @@ export class AgentTeamLedger {
2448
2723
  || operation.data.member.memberId !== request.memberId)
2449
2724
  this.throwRequestCollision(request.requestId);
2450
2725
  }
2726
+ assertSameArchival(operation, request) {
2727
+ if (operation.kind !== 'team/member-archived' || !this.sameActor(operation.actor, request.actor)
2728
+ || operation.data.member.memberId !== request.memberId)
2729
+ this.throwRequestCollision(request.requestId);
2730
+ }
2451
2731
  assertSameAttention(operation, request) {
2452
2732
  if (operation.kind !== 'team/thread-attention-changed' || !this.sameActor(operation.actor, request.actor)
2453
2733
  || operation.data.workspaceId !== request.workspaceId
@@ -2538,6 +2818,10 @@ export class AgentTeamLedger {
2538
2818
  return Object.freeze({ receipt: this.receipt(operation), channelRef: operation.data.channelRef, memberId: operation.data.memberId,
2539
2819
  releasedClaims: operation.data.claims, removedAttention: operation.data.inbox.attention.removed });
2540
2820
  }
2821
+ channelArchivalResult(operation) {
2822
+ return Object.freeze({ receipt: this.receipt(operation), channel: operation.data.channel,
2823
+ releasedClaims: operation.data.claims });
2824
+ }
2541
2825
  messageResult(operation) {
2542
2826
  return Object.freeze(this.committedMessageResult(operation));
2543
2827
  }
@@ -2578,6 +2862,10 @@ export class AgentTeamLedger {
2578
2862
  return Object.freeze({ receipt: this.receipt(operation), member: operation.data.member,
2579
2863
  releasedClaims: operation.data.claims, removedAttention: operation.data.inbox.attention.removed });
2580
2864
  }
2865
+ archivalResult(operation) {
2866
+ return Object.freeze({ receipt: this.receipt(operation), member: operation.data.member,
2867
+ releasedClaims: operation.data.claims, removedAttention: operation.data.inbox.attention.removed });
2868
+ }
2581
2869
  operationBase(request, sequence) {
2582
2870
  return { sequence, operationId: this.createOperationId(), requestId: request.requestId, occurredAt: this.createOccurredAt(),
2583
2871
  actor: Object.freeze({ ...request.actor }), previousOperationId: this.state.ordered.at(-1)?.operationId ?? null };
@@ -1,5 +1,6 @@
1
1
  import { readFile } from 'node:fs/promises';
2
2
  import { createUserMessage } from '@deepseek-ai/dsh-llm';
3
+ import { SessionLogOffset } from '@deepseek-ai/dsh-session';
3
4
  export const name = 'wowyuarm-agent-team-member-context';
4
5
  const MAX_MEMORY_BYTES = 8 * 1024;
5
6
  const BEGIN = '<team-member-private-memory>';
@@ -30,7 +31,10 @@ export function apply(ctx) {
30
31
  }
31
32
  const text = `${renderMemberIdentity(member)}\n\n${memory}`;
32
33
  const latestText = agent.session.surface.nodes.toReversed().flatMap(sequence => {
33
- const event = agent.session.events[sequence];
34
+ // `nodes` are event identities (SessionSeq); snapshotEvents takes log
35
+ // offsets. Re-entering through the validating constructor keeps the two
36
+ // number domains explicit (the seq = log.length contiguity contract).
37
+ const event = agent.session.snapshotEvents(SessionLogOffset(sequence), SessionLogOffset(sequence + 1))[0];
34
38
  return event?.type === 'user/message'
35
39
  && event.data.source.kind === 'plugin'
36
40
  && event.data.source.plugin === name