@wowyuarm/dsh-agent-team 0.1.12 → 0.1.14-alpha.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 (86) hide show
  1. package/README.md +3 -1
  2. package/README.zh.md +3 -1
  3. package/cordis.patch.yml +7 -3
  4. package/package.json +57 -45
  5. package/packages/agent-team/README.md +2 -2
  6. package/packages/agent-team/README.zh.md +2 -2
  7. package/packages/agent-team/lib/index.js +151 -69
  8. package/packages/agent-team/lib/ledger.js +249 -31
  9. package/packages/agent-team/lib/member-context.js +18 -1
  10. package/packages/agent-team/lib/member-runtime.js +5 -4
  11. package/packages/agent-team/lib/mentions.js +48 -24
  12. package/packages/agent-team/lib/spec.js +19 -0
  13. package/packages/agent-team/lib/typert.host.js +187 -41
  14. package/packages/agent-team/lib/typert.remote-client.d.ts +7 -3
  15. package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
  16. package/packages/agent-team/lib/typert.remote-client.js +132 -27
  17. package/packages/agent-team/lib/types/index.d.ts +32 -7
  18. package/packages/agent-team/lib/types/index.d.ts.map +1 -1
  19. package/packages/agent-team/lib/types/ledger.d.ts +46 -2
  20. package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
  21. package/packages/agent-team/lib/types/member-context.d.ts +6 -0
  22. package/packages/agent-team/lib/types/member-context.d.ts.map +1 -1
  23. package/packages/agent-team/lib/types/mentions.d.ts +20 -7
  24. package/packages/agent-team/lib/types/mentions.d.ts.map +1 -1
  25. package/packages/agent-team/lib/types/spec.d.ts.map +1 -1
  26. package/packages/agent-team/lib/types/types/entities.d.ts +3 -2
  27. package/packages/agent-team/lib/types/types/entities.d.ts.map +1 -1
  28. package/packages/agent-team/lib/types/types/operations.d.ts +37 -1
  29. package/packages/agent-team/lib/types/types/operations.d.ts.map +1 -1
  30. package/packages/agent-team/lib/types/types/requests-results.d.ts +50 -10
  31. package/packages/agent-team/lib/types/types/requests-results.d.ts.map +1 -1
  32. package/packages/agent-team/lib/types/vendor/storage-sqlite/index.d.ts +87 -0
  33. package/packages/agent-team/lib/types/vendor/storage-sqlite/index.d.ts.map +1 -0
  34. package/packages/agent-team/lib/types/vendor/storage-sqlite/schema.d.ts +59 -0
  35. package/packages/agent-team/lib/types/vendor/storage-sqlite/schema.d.ts.map +1 -0
  36. package/packages/agent-team/lib/types/vendor/storage-sqlite/unit.d.ts +57 -0
  37. package/packages/agent-team/lib/types/vendor/storage-sqlite/unit.d.ts.map +1 -0
  38. package/packages/agent-team/lib/vendor/storage-sqlite/index.js +139 -0
  39. package/packages/agent-team/lib/vendor/storage-sqlite/schema.js +120 -0
  40. package/packages/agent-team/lib/vendor/storage-sqlite/unit.js +132 -0
  41. package/packages/client-agent-team/README.md +1 -1
  42. package/packages/client-agent-team/README.zh.md +1 -1
  43. package/packages/client-agent-team/lib/client.js +604 -344
  44. package/packages/client-agent-team/lib/client.js.map +1 -1
  45. package/packages/client-agent-team/lib/types/client/TeamAgentImport.d.ts +12 -0
  46. package/packages/client-agent-team/lib/types/client/TeamAgentImport.d.ts.map +1 -0
  47. package/packages/client-agent-team/lib/types/client/TeamAgentImport.js +69 -0
  48. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts +3 -1
  49. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
  50. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +29 -17
  51. package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts.map +1 -1
  52. package/packages/client-agent-team/lib/types/client/TeamChannelPage.js +19 -44
  53. package/packages/client-agent-team/lib/types/client/TeamComposer.js +2 -2
  54. package/packages/client-agent-team/lib/types/client/TeamInboxPage.d.ts +2 -2
  55. package/packages/client-agent-team/lib/types/client/TeamInboxPage.d.ts.map +1 -1
  56. package/packages/client-agent-team/lib/types/client/TeamInboxPage.js +15 -14
  57. package/packages/client-agent-team/lib/types/client/TeamPresenceDot.d.ts +1 -1
  58. package/packages/client-agent-team/lib/types/client/TeamPresenceDot.d.ts.map +1 -1
  59. package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -1
  60. package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +15 -29
  61. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts +1 -1
  62. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts.map +1 -1
  63. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.js +2 -2
  64. package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
  65. package/packages/client-agent-team/lib/types/client/index.js +8 -1
  66. package/packages/client-agent-team/lib/types/client/locales.d.ts +18 -0
  67. package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
  68. package/packages/client-agent-team/lib/types/client/locales.js +18 -0
  69. package/packages/client-agent-team/lib/types/client/slots.d.ts +3 -1
  70. package/packages/client-agent-team/lib/types/client/slots.d.ts.map +1 -1
  71. package/packages/client-agent-team/lib/types/client/team-changes.d.ts +15 -31
  72. package/packages/client-agent-team/lib/types/client/team-changes.d.ts.map +1 -1
  73. package/packages/client-agent-team/lib/types/client/team-changes.js +82 -111
  74. package/packages/client-agent-team/lib/types/client/team-formatters.d.ts +13 -11
  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 +29 -35
  77. package/packages/tool-agent-team/README.md +1 -1
  78. package/packages/tool-agent-team/README.zh.md +1 -1
  79. package/packages/tool-agent-team/lib/host-access.js +19 -0
  80. package/packages/tool-agent-team/lib/index.js +42 -33
  81. package/packages/tool-agent-team/lib/types/host-access.d.ts +10 -0
  82. package/packages/tool-agent-team/lib/types/host-access.d.ts.map +1 -1
  83. package/packages/tool-agent-team/lib/types/index.d.ts.map +1 -1
  84. package/packages/client-agent-team/lib/types/client/scope-coverage.d.ts +0 -41
  85. package/packages/client-agent-team/lib/types/client/scope-coverage.d.ts.map +0 -1
  86. package/packages/client-agent-team/lib/types/client/scope-coverage.js +0 -64
@@ -178,6 +178,8 @@ let AgentTeam = (() => {
178
178
  let _promoteThread_decorators;
179
179
  let _joinChannel_decorators;
180
180
  let _removeChannelMember_decorators;
181
+ let _joinWorkspace_decorators;
182
+ let _leaveWorkspace_decorators;
181
183
  let _sendMessage_decorators;
182
184
  let _putAttachment_decorators;
183
185
  let _getAttachment_decorators;
@@ -193,7 +195,7 @@ let AgentTeam = (() => {
193
195
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
194
196
  _resolveTaskRefs_decorators = [Remote('resolveTaskRefs')];
195
197
  _membersForClient_decorators = [Remote('members')];
196
- _changes_decorators = [Remote('changes')];
198
+ _changes_decorators = [Remote({ mode: 'stream' })];
197
199
  _createChannel_decorators = [Remote('createChannel')];
198
200
  _updateChannel_decorators = [Remote('updateChannel')];
199
201
  _archiveChannel_decorators = [Remote('archiveChannel')];
@@ -206,6 +208,8 @@ let AgentTeam = (() => {
206
208
  _promoteThread_decorators = [Remote('promoteThread')];
207
209
  _joinChannel_decorators = [Remote('joinChannel')];
208
210
  _removeChannelMember_decorators = [Remote('removeChannelMember')];
211
+ _joinWorkspace_decorators = [Remote('joinWorkspace')];
212
+ _leaveWorkspace_decorators = [Remote('leaveWorkspace')];
209
213
  _sendMessage_decorators = [Remote('sendMessage')];
210
214
  _putAttachment_decorators = [Remote('putAttachment')];
211
215
  _getAttachment_decorators = [Remote('getAttachment')];
@@ -231,6 +235,8 @@ let AgentTeam = (() => {
231
235
  __esDecorate(this, null, _promoteThread_decorators, { kind: "method", name: "promoteThread", static: false, private: false, access: { has: obj => "promoteThread" in obj, get: obj => obj.promoteThread }, metadata: _metadata }, null, _instanceExtraInitializers);
232
236
  __esDecorate(this, null, _joinChannel_decorators, { kind: "method", name: "joinChannel", static: false, private: false, access: { has: obj => "joinChannel" in obj, get: obj => obj.joinChannel }, metadata: _metadata }, null, _instanceExtraInitializers);
233
237
  __esDecorate(this, null, _removeChannelMember_decorators, { kind: "method", name: "removeChannelMember", static: false, private: false, access: { has: obj => "removeChannelMember" in obj, get: obj => obj.removeChannelMember }, metadata: _metadata }, null, _instanceExtraInitializers);
238
+ __esDecorate(this, null, _joinWorkspace_decorators, { kind: "method", name: "joinWorkspace", static: false, private: false, access: { has: obj => "joinWorkspace" in obj, get: obj => obj.joinWorkspace }, metadata: _metadata }, null, _instanceExtraInitializers);
239
+ __esDecorate(this, null, _leaveWorkspace_decorators, { kind: "method", name: "leaveWorkspace", static: false, private: false, access: { has: obj => "leaveWorkspace" in obj, get: obj => obj.leaveWorkspace }, metadata: _metadata }, null, _instanceExtraInitializers);
234
240
  __esDecorate(this, null, _sendMessage_decorators, { kind: "method", name: "sendMessage", static: false, private: false, access: { has: obj => "sendMessage" in obj, get: obj => obj.sendMessage }, metadata: _metadata }, null, _instanceExtraInitializers);
235
241
  __esDecorate(this, null, _putAttachment_decorators, { kind: "method", name: "putAttachment", static: false, private: false, access: { has: obj => "putAttachment" in obj, get: obj => obj.putAttachment }, metadata: _metadata }, null, _instanceExtraInitializers);
236
242
  __esDecorate(this, null, _getAttachment_decorators, { kind: "method", name: "getAttachment", static: false, private: false, access: { has: obj => "getAttachment" in obj, get: obj => obj.getAttachment }, metadata: _metadata }, null, _instanceExtraInitializers);
@@ -382,7 +388,7 @@ let AgentTeam = (() => {
382
388
  if (kind !== undefined)
383
389
  this.ctx.logger.warn(`agent-team: member '${member.handle}' hit a recoverable ${kind} error; recording a consecutive error occurrence`);
384
390
  this.recovery.onError(member.memberId, message);
385
- this.emitPresenceChanged(member.workspaceId);
391
+ this.emitMemberPresenceChanged(member);
386
392
  });
387
393
  this.ctx.on('agent/status', ({ agent, status }) => {
388
394
  const member = this.memberForAgent(agent);
@@ -394,7 +400,7 @@ let AgentTeam = (() => {
394
400
  const recovered = this.clearMemberFailure(member.memberId, 'runtime');
395
401
  if (recovered)
396
402
  this.notifiedInbox.delete(member.memberId);
397
- this.emitPresenceChanged(member.workspaceId);
403
+ this.emitMemberPresenceChanged(member);
398
404
  // A rollover/recovery in flight delivers its own sequenced
399
405
  // rederived Inbox after the handoff and carried input; a status-driven
400
406
  // steer here would claim the handoff turn's next step and leapfrog
@@ -411,7 +417,7 @@ let AgentTeam = (() => {
411
417
  if (this.memberFailures.get(member.memberId)?.runtime === undefined) {
412
418
  this.recovery.onCleanTurnEnd(member.memberId);
413
419
  }
414
- this.emitPresenceChanged(member.workspaceId);
420
+ this.emitMemberPresenceChanged(member);
415
421
  }
416
422
  });
417
423
  // The store's dispatch carrier is untagged, so a scope-tagged listener
@@ -477,6 +483,7 @@ let AgentTeam = (() => {
477
483
  // One metadata listing serves every Member restore; per-member list calls
478
484
  // would repeat the same I/O linearly during startup.
479
485
  const persistedSessions = new Set((await this.persistedSessionHeaders()).map(snapshot => snapshot.header.id));
486
+ await this.sweepWorkspaceParticipations(ledger);
480
487
  for (const member of ledger.listMembers()) {
481
488
  if (member.state === 'enabled')
482
489
  await this.activateMember(member, undefined, persistedSessions);
@@ -484,6 +491,31 @@ let AgentTeam = (() => {
484
491
  await this.memberRuntime.cleanupRemovedMember(member);
485
492
  }
486
493
  }
494
+ /**
495
+ * Lazy Workspace-deletion handling: a Workspace that no longer exists can
496
+ * only be detected through the registry — there is no deletion event. A
497
+ * dead non-default participation is withdrawn here (its Claims and
498
+ * Attention release through the normal leave path); a dead default
499
+ * Workspace is left alone — activation fails on its missing cwd and the
500
+ * Member surfaces as unavailable for Human attention.
501
+ */
502
+ async sweepWorkspaceParticipations(ledger) {
503
+ for (const member of ledger.listMembers()) {
504
+ if (member.state === 'inactive' || member.state === 'archived')
505
+ continue;
506
+ for (const workspaceId of ledger.workspacesOf(member.memberId)) {
507
+ if (workspaceId === member.workspaceId || this.ctx.workspaceRegistry.get(workspaceId) !== undefined)
508
+ continue;
509
+ this.ctx.logger.warn(`agent-team: workspace '${workspaceId}' is gone; withdrawing member '${member.handle}' from it`);
510
+ try {
511
+ await ledger.leaveWorkspace({ requestId: randomUUID(), workspaceId, memberId: member.memberId, actor: agentTeamHumanActor() });
512
+ }
513
+ catch (error) {
514
+ this.ctx.logger.warn(`agent-team: failed to withdraw member '${member.handle}' from deleted workspace '${workspaceId}': ${error instanceof Error ? error.message : String(error)}`);
515
+ }
516
+ }
517
+ }
518
+ }
487
519
  /**
488
520
  * Whether one Member Session has durable persisted content, decided through
489
521
  * {@link SessionPersistence.stat} rather than a bare metadata listing: the
@@ -519,56 +551,52 @@ let AgentTeam = (() => {
519
551
  });
520
552
  return Object.freeze({ resolved: Object.freeze(this.requireLedger().resolveTaskRefs(request.workspaceId, taskRefs)) });
521
553
  }
522
- /** Return only this Workspace's current Member projection to the Client. */
554
+ /** Browser-safe Human roster, optionally filtered to one participation. */
523
555
  membersForClient(request) {
524
- this.requireWorkspace(request.workspaceId);
556
+ if (request.workspaceId !== undefined)
557
+ this.requireWorkspace(request.workspaceId);
558
+ const ledger = this.requireLedger();
525
559
  return this.members()
526
- .filter(status => status.member.workspaceId === request.workspaceId)
527
- .map(({ member: { privateMemoryPath: _privateMemoryPath, ...member }, ...status }) => Object.freeze({ ...status, member: Object.freeze(member) }));
560
+ .filter(status => request.workspaceId === undefined || ledger.participatesIn(status.member.memberId, request.workspaceId))
561
+ .map(({ member: { privateMemoryPath: _privateMemoryPath, ...member }, ...status }) => Object.freeze({ ...status, member: Object.freeze(member), workspaceIds: ledger.workspacesOf(member.memberId) }));
528
562
  }
529
- /** Wait for a lightweight projection invalidation without exposing ledger records. */
530
- async changes(request, signal) {
531
- if (!Number.isInteger(request.afterVersion) || request.afterVersion < 0)
532
- throw new Error('afterVersion must be a non-negative integer');
563
+ /** Emit a current baseline, then coalesced invalidations until canceled. */
564
+ async *changes(request, signal) {
533
565
  const scope = this.validateChangeScope(request.scope);
534
- const version = this.changeVersionOf(scope);
535
- if (version > request.afterVersion || !this.accepting)
536
- return Object.freeze({ version });
537
- return new Promise((resolve, reject) => {
538
- let settled = false;
539
- const waiter = {
540
- scope,
541
- wake: version => {
542
- if (settled)
543
- return;
544
- settled = true;
545
- clearTimeout(timeout);
546
- signal?.removeEventListener('abort', onAbort);
547
- resolve(Object.freeze({ version }));
548
- },
549
- };
550
- const timeout = setTimeout(() => {
551
- if (settled)
552
- return;
553
- settled = true;
554
- this.changeWaiters.delete(waiter);
555
- resolve(Object.freeze({ version: this.changeVersionOf(scope) }));
556
- }, 25_000);
557
- const onAbort = () => {
558
- if (settled)
566
+ let pending;
567
+ let resume;
568
+ const waiter = {
569
+ scope,
570
+ wake: version => {
571
+ pending = version;
572
+ resume?.();
573
+ },
574
+ };
575
+ const onAbort = () => { resume?.(); };
576
+ if (signal?.aborted || !this.accepting)
577
+ return;
578
+ this.changeWaiters.add(waiter);
579
+ signal?.addEventListener('abort', onAbort, { once: true });
580
+ try {
581
+ // Listen before yielding so a commit during the consumer's read is kept.
582
+ yield { version: this.changeVersionOf(scope) };
583
+ while (!signal?.aborted && this.accepting) {
584
+ if (pending === undefined)
585
+ await new Promise(resolve => { resume = resolve; });
586
+ resume = undefined;
587
+ if (signal?.aborted || !this.accepting)
559
588
  return;
560
- settled = true;
561
- clearTimeout(timeout);
562
- this.changeWaiters.delete(waiter);
563
- reject(new Error('changes wait was aborted'));
564
- };
565
- signal?.addEventListener('abort', onAbort, { once: true });
566
- if (signal?.aborted === true) {
567
- onAbort();
568
- return;
589
+ if (pending !== undefined) {
590
+ const version = pending;
591
+ pending = undefined;
592
+ yield { version };
593
+ }
569
594
  }
570
- this.changeWaiters.add(waiter);
571
- });
595
+ }
596
+ finally {
597
+ this.changeWaiters.delete(waiter);
598
+ signal?.removeEventListener('abort', onAbort);
599
+ }
572
600
  }
573
601
  /** Return durable Team status without issuing a model request or a storage write. */
574
602
  status() {
@@ -672,7 +700,7 @@ let AgentTeam = (() => {
672
700
  async recoverMember(request) {
673
701
  this.requireAccepting();
674
702
  const member = this.requireLedger().getMember(request.memberId);
675
- if (member === undefined || member.workspaceId !== request.workspaceId)
703
+ if (member === undefined || !this.requireLedger().participatesIn(request.memberId, request.workspaceId))
676
704
  throw new Error(`unknown Member '${request.memberId}' in workspace '${request.workspaceId}'`);
677
705
  this.recovery.stopTracking(request.memberId);
678
706
  // An orphaned composition cannot be steered: its tools are gone, so a
@@ -730,7 +758,7 @@ let AgentTeam = (() => {
730
758
  this.requireAccepting();
731
759
  this.requireWorkspace(request.workspaceId);
732
760
  const stored = this.requireLedger().getMember(request.memberId);
733
- if (stored === undefined || stored.workspaceId !== request.workspaceId)
761
+ if (stored === undefined || !this.requireLedger().participatesIn(request.memberId, request.workspaceId))
734
762
  throw new Error(`unknown Member '${request.memberId}' in workspace '${request.workspaceId}'`);
735
763
  if (stored.state !== 'enabled')
736
764
  throw new Error(`Agent Member '${stored.handle}' is ${stored.state}; only enabled Members can start from a new context`);
@@ -876,7 +904,7 @@ let AgentTeam = (() => {
876
904
  reactivated.agent.steer(this.contextManagement.handoffMessageFor(plan));
877
905
  for (const message of carriedInput)
878
906
  reactivated.agent.followup(message);
879
- const notifications = this.requireLedger().notificationFacts(memberId, { workspaceId: rolled.member.workspaceId });
907
+ const notifications = this.requireLedger().notificationFacts(rolled.member.memberId);
880
908
  if (notifications.length > 0)
881
909
  this.notifyMember(reactivated.agent, carriedInput.length > 0);
882
910
  });
@@ -914,7 +942,7 @@ let AgentTeam = (() => {
914
942
  const handle = this.handles.get(member.memberId);
915
943
  if (handle === undefined)
916
944
  throw new Error(`member '${member.handle}' has no active session`);
917
- const notifications = this.requireLedger().notificationFacts(member.memberId, { workspaceId: member.workspaceId });
945
+ const notifications = this.requireLedger().notificationFacts(member.memberId);
918
946
  const body = notifications.length === 0 ? text : `${text}\n\n${this.notificationText(notifications, member.memberId)}`;
919
947
  const hint = createUserMessage({
920
948
  content: [{ type: 'text', text: body }],
@@ -1045,6 +1073,22 @@ let AgentTeam = (() => {
1045
1073
  this.emitCommitted(result.value.receipt);
1046
1074
  return result.value;
1047
1075
  }
1076
+ /** Human-only Workspace participation grant; a pure relation — no Session moves. */
1077
+ async joinWorkspace(request) {
1078
+ const actor = this.humanCall(request.workspaceId);
1079
+ const result = await this.requireLedger().joinWorkspace({ ...request, actor });
1080
+ if (result.committed)
1081
+ this.emitCommitted(result.value.receipt);
1082
+ return result.value;
1083
+ }
1084
+ /** Human-only Workspace participation withdrawal and Workspace-scoped cleanup. */
1085
+ async leaveWorkspace(request) {
1086
+ const actor = this.humanCall(request.workspaceId);
1087
+ const result = await this.requireLedger().leaveWorkspace({ ...request, actor });
1088
+ if (result.committed)
1089
+ this.emitCommitted(result.value.receipt);
1090
+ return result.value;
1091
+ }
1048
1092
  /** Human top-level Thread start; asTask attaches an optional Task overlay. */
1049
1093
  async sendMessage(request) {
1050
1094
  return this.sendMessageAs(this.humanCall(request.workspaceId), request);
@@ -1185,8 +1229,10 @@ let AgentTeam = (() => {
1185
1229
  }
1186
1230
  inboxForAgent(agent, request) {
1187
1231
  const actor = this.memberActor(agent);
1232
+ if (request.workspaceId === undefined)
1233
+ return this.requireLedger().memberInbox(actor, request);
1188
1234
  this.requireAgentWorkspace(actor, request.workspaceId);
1189
- return this.requireLedger().inbox(actor, request);
1235
+ return this.requireLedger().inbox(actor, { ...request, workspaceId: request.workspaceId });
1190
1236
  }
1191
1237
  async readThreadForAgent(agent, request) {
1192
1238
  const actor = this.memberCall(agent, request.workspaceId);
@@ -1270,7 +1316,7 @@ let AgentTeam = (() => {
1270
1316
  }
1271
1317
  try {
1272
1318
  const message = createUserMessage({
1273
- content: [{ type: 'text', text: this.dmRelayText(agent, recipient, request.body.trim(), result.value.receipt.occurredAt, result.value.receipt.operationId) }],
1319
+ content: [{ type: 'text', text: this.dmRelayText(agent, recipient, request.body.trim(), result.value.receipt.occurredAt, result.value.receipt.operationId, request.workspaceId) }],
1274
1320
  source: { kind: 'plugin', plugin: AGENT_TEAM_PLUGIN_ID, form: 'relay' },
1275
1321
  });
1276
1322
  // An idle recipient gets one ordinary turn; a busy one is steered into
@@ -1286,26 +1332,38 @@ let AgentTeam = (() => {
1286
1332
  return result.value;
1287
1333
  }
1288
1334
  /** Relay body: the DM itself plus one bounded line of adjacent context. */
1289
- dmRelayText(senderAgent, recipient, body, occurredAt, excluding) {
1335
+ dmRelayText(senderAgent, recipient, body, occurredAt, excluding, workspaceId) {
1290
1336
  const sender = this.memberForAgent(senderAgent);
1291
1337
  const prior = this.requireLedger().dmHistoryBetween(senderAgent.id, recipient.memberId, excluding);
1292
1338
  const header = `Direct message from @${sender?.handle ?? 'a Team Member'} at ${formatTeamTimestamp(occurredAt)}:`;
1293
1339
  const context = prior === undefined ? '' : `\n\n[most recent prior DM between you: ${prior}]`;
1294
- return `${header}\n\n${body}${context}`;
1340
+ return `${header}\nWorkspace: ${workspaceId}\n\n${body}${context}`;
1295
1341
  }
1296
1342
  threadHistoryForAgent(agent, request) {
1297
1343
  const actor = this.memberActor(agent);
1298
1344
  this.requireAgentWorkspace(actor, request.workspaceId);
1299
1345
  return this.requireLedger().threadHistory(actor, request);
1300
1346
  }
1301
- /** Agent-only bounded discovery projection. */
1347
+ /** Live participation addresses; paths remain owned by the Harness registry. */
1348
+ workspacesForAgent(agent) {
1349
+ const actor = this.memberActor(agent);
1350
+ const member = this.requireLedger().getMember(actor.memberId);
1351
+ return Object.freeze(this.requireLedger().workspacesOf(actor.memberId).map(workspaceId => Object.freeze({
1352
+ workspaceId, path: this.ctx.workspaceRegistry.get(workspaceId)?.path, default: workspaceId === member.workspaceId,
1353
+ })));
1354
+ }
1355
+ /** Agent-only bounded discovery projection; participation list carries registry titles when known. */
1302
1356
  viewForAgent(agent, request) {
1303
1357
  const member = this.memberForAgent(agent);
1304
1358
  if (member === undefined)
1305
1359
  throw new Error('Agent is not an active Team Member');
1306
- if (member.workspaceId !== request.workspaceId)
1360
+ if (!this.requireLedger().participatesIn(member.memberId, request.workspaceId))
1307
1361
  throw new Error('Member cannot view another Workspace');
1308
- return this.requireLedger().view(request, member.memberId);
1362
+ const view = this.requireLedger().view(request, member.memberId);
1363
+ return Object.freeze({ ...view, workspaces: Object.freeze(view.workspaces.map(participation => {
1364
+ const title = this.ctx.workspaceRegistry.get(participation.workspaceId)?.title;
1365
+ return title === undefined ? participation : Object.freeze({ ...participation, title });
1366
+ })) });
1309
1367
  }
1310
1368
  /** Validate the durable ledger against an independently replayed projection. */
1311
1369
  validateLedger() {
@@ -2040,9 +2098,10 @@ let AgentTeam = (() => {
2040
2098
  this.requireWorkspace(workspaceId);
2041
2099
  return agentTeamHumanActor();
2042
2100
  }
2043
- /** Fence one Member call: accepting Host, live Member binding, matching Workspace. */
2101
+ /** Fence one Member call: accepting Host, live Member binding, matching Workspace participation. */
2044
2102
  memberCall(agent, workspaceId) {
2045
2103
  this.requireAccepting();
2104
+ this.requireWorkspace(workspaceId);
2046
2105
  const actor = this.memberActor(agent);
2047
2106
  this.requireAgentWorkspace(actor, workspaceId);
2048
2107
  return actor;
@@ -2054,7 +2113,7 @@ let AgentTeam = (() => {
2054
2113
  return Object.freeze({ kind: 'member', memberId: member.memberId, handle: member.handle });
2055
2114
  }
2056
2115
  requireAgentWorkspace(actor, workspaceId) {
2057
- if (this.requireLedger().getMember(actor.memberId)?.workspaceId !== workspaceId)
2116
+ if (!this.requireLedger().participatesIn(actor.memberId, workspaceId))
2058
2117
  throw new Error('Member cannot mutate another Workspace');
2059
2118
  }
2060
2119
  /**
@@ -2313,7 +2372,7 @@ let AgentTeam = (() => {
2313
2372
  }
2314
2373
  finally {
2315
2374
  // Activation only changes this Workspace's presence projection.
2316
- this.emitPresenceChanged(member.workspaceId);
2375
+ this.emitMemberPresenceChanged(member);
2317
2376
  }
2318
2377
  }
2319
2378
  /**
@@ -2486,6 +2545,25 @@ let AgentTeam = (() => {
2486
2545
  }
2487
2546
  const ledger = this.requireLedger();
2488
2547
  this.emitChanged(ledger.changeScopesOf(operation));
2548
+ if (operation.kind === 'team/member-workspace-joined' || operation.kind === 'team/member-workspace-left') {
2549
+ const agent = this.handles.get(operation.data.memberId)?.agent;
2550
+ if (agent !== undefined) {
2551
+ const path = this.ctx.workspaceRegistry.get(operation.data.workspaceId)?.path;
2552
+ const text = `Team participation changed: you have ${operation.kind === 'team/member-workspace-joined' ? 'joined' : 'left'} Workspace ${operation.data.workspaceId}${path === undefined ? ' (path unavailable)' : ` (${JSON.stringify(path)})`}.\nCurrent Workspace ids: ${ledger.workspacesOf(operation.data.memberId).join(', ')}. This replaces earlier participation information. Your Session and cwd have not moved.`;
2553
+ const notice = createUserMessage({ content: [{ type: 'text', text }],
2554
+ source: { kind: 'plugin', plugin: AGENT_TEAM_PLUGIN_ID, form: 'notice', summary: 'Team Workspace participation changed' } });
2555
+ try {
2556
+ if (agent.status === 'idle' || agent.inbox.nextTurn.some(message => message.source.kind === 'user'))
2557
+ agent.followup(notice);
2558
+ else
2559
+ agent.steer(notice);
2560
+ }
2561
+ catch (error) {
2562
+ // Notification delivery cannot roll back a committed participation.
2563
+ this.ctx.logger.warn(`agent-team: participation notice not delivered: ${error instanceof Error ? error.message : String(error)}`);
2564
+ }
2565
+ }
2566
+ }
2489
2567
  for (const memberId of ledger.affectedMembersOf(operation)) {
2490
2568
  const handle = this.handles.get(memberId);
2491
2569
  if (handle !== undefined)
@@ -2521,10 +2599,8 @@ let AgentTeam = (() => {
2521
2599
  .map((job) => `${job.name ?? job.id}`);
2522
2600
  }
2523
2601
  emitAutoCompactionChanged(memberId) {
2524
- const workspaceId = this.ledger?.getMember(memberId)?.workspaceId;
2525
- if (workspaceId === undefined)
2526
- return;
2527
- this.emitPresenceChanged(workspaceId);
2602
+ for (const workspaceId of this.ledger?.workspacesOf(memberId) ?? [])
2603
+ this.emitPresenceChanged(workspaceId);
2528
2604
  }
2529
2605
  /**
2530
2606
  * Presence-only invalidation: Agent running/idle/activation/failure changes
@@ -2534,12 +2610,17 @@ let AgentTeam = (() => {
2534
2610
  emitPresenceChanged(workspaceId) {
2535
2611
  this.emitChanged([{ kind: 'presence', workspaceId }]);
2536
2612
  }
2613
+ /** Presence invalidation in every Workspace the Member participates in — each panel listing it must refresh. */
2614
+ emitMemberPresenceChanged(member) {
2615
+ for (const workspaceId of this.requireLedger().workspacesOf(member.memberId))
2616
+ this.emitPresenceChanged(workspaceId);
2617
+ }
2537
2618
  /** Wake from durable unread state with bounded facts for direct and state-changing work. */
2538
2619
  notifyMember(agent, sequenced = false) {
2539
2620
  const member = this.memberForAgent(agent);
2540
2621
  if (member === undefined || member.state !== 'enabled')
2541
2622
  return;
2542
- const notifications = this.requireLedger().notificationFacts(member.memberId, { workspaceId: member.workspaceId });
2623
+ const notifications = this.requireLedger().notificationFacts(member.memberId);
2543
2624
  if (notifications.length === 0) {
2544
2625
  this.notifiedInbox.delete(member.memberId);
2545
2626
  return;
@@ -2612,6 +2693,8 @@ let AgentTeam = (() => {
2612
2693
  return true;
2613
2694
  };
2614
2695
  for (const { item, facts } of notifications.slice(0, 8)) {
2696
+ if (!append(`Workspace: ${item.workspaceId}\nChannel: ${item.channelRef}`))
2697
+ break;
2615
2698
  for (const { fact, direct } of facts) {
2616
2699
  if (detailedFactCount >= 20) {
2617
2700
  omitted = true;
@@ -2641,7 +2724,7 @@ let AgentTeam = (() => {
2641
2724
  }
2642
2725
  if (omitted)
2643
2726
  sections.push('More unread work remains in team_inbox; the automatic context is bounded.');
2644
- sections.push('Use team_thread read with the relevant threadRef before acting or replying. Use team_inbox only when you need to triage the remaining Threads.');
2727
+ sections.push('Use team_thread read with the relevant workspace and threadRef before acting or replying. Use team_inbox only when you need to triage the remaining Threads.');
2645
2728
  return sections.join('\n\n');
2646
2729
  }
2647
2730
  boundedNotificationBody(body) {
@@ -2731,7 +2814,6 @@ let AgentTeam = (() => {
2731
2814
  const waiterScope = waiter.scope;
2732
2815
  if (scopes !== undefined && (waiterScope === undefined ? !touchesProjection : !scopes.some(scope => sameChangeScope(scope, waiterScope))))
2733
2816
  continue;
2734
- this.changeWaiters.delete(waiter);
2735
2817
  waiter.wake(this.changeVersionOf(waiterScope));
2736
2818
  }
2737
2819
  }