@wowyuarm/dsh-agent-team 0.1.11 → 0.1.13

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 (114) hide show
  1. package/README.md +6 -4
  2. package/README.zh.md +6 -4
  3. package/cordis.patch.yml +7 -3
  4. package/package.json +16 -3
  5. package/packages/agent-team/README.md +4 -4
  6. package/packages/agent-team/README.zh.md +4 -4
  7. package/packages/agent-team/lib/context-projection.js +5 -1
  8. package/packages/agent-team/lib/index.js +162 -113
  9. package/packages/agent-team/lib/ledger.js +496 -169
  10. package/packages/agent-team/lib/member-context.js +18 -1
  11. package/packages/agent-team/lib/member-runtime.js +48 -9
  12. package/packages/agent-team/lib/mentions.js +80 -0
  13. package/packages/agent-team/lib/spec.js +20 -0
  14. package/packages/agent-team/lib/typert.host.js +241 -46
  15. package/packages/agent-team/lib/typert.remote-client.d.ts +7 -3
  16. package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
  17. package/packages/agent-team/lib/typert.remote-client.js +178 -28
  18. package/packages/agent-team/lib/types/context-projection.d.ts.map +1 -1
  19. package/packages/agent-team/lib/types/index.d.ts +33 -14
  20. package/packages/agent-team/lib/types/index.d.ts.map +1 -1
  21. package/packages/agent-team/lib/types/ledger.d.ts +120 -18
  22. package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
  23. package/packages/agent-team/lib/types/member-context.d.ts +6 -0
  24. package/packages/agent-team/lib/types/member-context.d.ts.map +1 -1
  25. package/packages/agent-team/lib/types/member-runtime.d.ts +2 -1
  26. package/packages/agent-team/lib/types/member-runtime.d.ts.map +1 -1
  27. package/packages/agent-team/lib/types/mentions.d.ts +47 -0
  28. package/packages/agent-team/lib/types/mentions.d.ts.map +1 -0
  29. package/packages/agent-team/lib/types/spec.d.ts.map +1 -1
  30. package/packages/agent-team/lib/types/types/entities.d.ts +3 -20
  31. package/packages/agent-team/lib/types/types/entities.d.ts.map +1 -1
  32. package/packages/agent-team/lib/types/types/operations.d.ts +37 -1
  33. package/packages/agent-team/lib/types/types/operations.d.ts.map +1 -1
  34. package/packages/agent-team/lib/types/types/requests-results.d.ts +107 -25
  35. package/packages/agent-team/lib/types/types/requests-results.d.ts.map +1 -1
  36. package/packages/agent-team/lib/types/vendor/storage-sqlite/index.d.ts +87 -0
  37. package/packages/agent-team/lib/types/vendor/storage-sqlite/index.d.ts.map +1 -0
  38. package/packages/agent-team/lib/types/vendor/storage-sqlite/schema.d.ts +59 -0
  39. package/packages/agent-team/lib/types/vendor/storage-sqlite/schema.d.ts.map +1 -0
  40. package/packages/agent-team/lib/types/vendor/storage-sqlite/unit.d.ts +57 -0
  41. package/packages/agent-team/lib/types/vendor/storage-sqlite/unit.d.ts.map +1 -0
  42. package/packages/agent-team/lib/vendor/storage-sqlite/index.js +139 -0
  43. package/packages/agent-team/lib/vendor/storage-sqlite/schema.js +120 -0
  44. package/packages/agent-team/lib/vendor/storage-sqlite/unit.js +132 -0
  45. package/packages/agent-team/preset/team-member/agent.cordis.yml +48 -8
  46. package/packages/client-agent-team/README.md +1 -1
  47. package/packages/client-agent-team/README.zh.md +1 -1
  48. package/packages/client-agent-team/lib/client.js +2123 -1406
  49. package/packages/client-agent-team/lib/client.js.map +1 -1
  50. package/packages/client-agent-team/lib/types/client/TeamAgentImport.d.ts +12 -0
  51. package/packages/client-agent-team/lib/types/client/TeamAgentImport.d.ts.map +1 -0
  52. package/packages/client-agent-team/lib/types/client/TeamAgentImport.js +69 -0
  53. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts +3 -1
  54. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
  55. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +31 -19
  56. package/packages/client-agent-team/lib/types/client/TeamAvatarStack.d.ts +18 -0
  57. package/packages/client-agent-team/lib/types/client/TeamAvatarStack.d.ts.map +1 -0
  58. package/packages/client-agent-team/lib/types/client/TeamAvatarStack.js +22 -0
  59. package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts +3 -1
  60. package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts.map +1 -1
  61. package/packages/client-agent-team/lib/types/client/TeamChannelPage.js +111 -24
  62. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.d.ts.map +1 -1
  63. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +10 -3
  64. package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts.map +1 -1
  65. package/packages/client-agent-team/lib/types/client/TeamComposer.js +11 -17
  66. package/packages/client-agent-team/lib/types/client/TeamConversation.js +1 -1
  67. package/packages/client-agent-team/lib/types/client/TeamCountBadge.d.ts +20 -0
  68. package/packages/client-agent-team/lib/types/client/TeamCountBadge.d.ts.map +1 -0
  69. package/packages/client-agent-team/lib/types/client/TeamCountBadge.js +25 -0
  70. package/packages/client-agent-team/lib/types/client/TeamInboxPage.d.ts +9 -5
  71. package/packages/client-agent-team/lib/types/client/TeamInboxPage.d.ts.map +1 -1
  72. package/packages/client-agent-team/lib/types/client/TeamInboxPage.js +92 -23
  73. package/packages/client-agent-team/lib/types/client/TeamMemberRow.d.ts +43 -0
  74. package/packages/client-agent-team/lib/types/client/TeamMemberRow.d.ts.map +1 -0
  75. package/packages/client-agent-team/lib/types/client/TeamMemberRow.js +27 -0
  76. package/packages/client-agent-team/lib/types/client/TeamMembersAction.d.ts +2 -2
  77. package/packages/client-agent-team/lib/types/client/TeamMembersAction.d.ts.map +1 -1
  78. package/packages/client-agent-team/lib/types/client/TeamMembersAction.js +2 -2
  79. package/packages/client-agent-team/lib/types/client/TeamPresenceDot.d.ts +1 -1
  80. package/packages/client-agent-team/lib/types/client/TeamPresenceDot.d.ts.map +1 -1
  81. package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -1
  82. package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +21 -31
  83. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts +1 -1
  84. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts.map +1 -1
  85. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.js +29 -11
  86. package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
  87. package/packages/client-agent-team/lib/types/client/index.js +11 -5
  88. package/packages/client-agent-team/lib/types/client/locales.d.ts +54 -16
  89. package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
  90. package/packages/client-agent-team/lib/types/client/locales.js +54 -16
  91. package/packages/client-agent-team/lib/types/client/slots.d.ts +3 -2
  92. package/packages/client-agent-team/lib/types/client/slots.d.ts.map +1 -1
  93. package/packages/client-agent-team/lib/types/client/team-changes.d.ts +15 -25
  94. package/packages/client-agent-team/lib/types/client/team-changes.d.ts.map +1 -1
  95. package/packages/client-agent-team/lib/types/client/team-changes.js +82 -80
  96. package/packages/client-agent-team/lib/types/client/team-formatters.d.ts +45 -10
  97. package/packages/client-agent-team/lib/types/client/team-formatters.d.ts.map +1 -1
  98. package/packages/client-agent-team/lib/types/client/team-formatters.js +71 -30
  99. package/packages/tool-agent-team/README.md +3 -3
  100. package/packages/tool-agent-team/README.zh.md +3 -3
  101. package/packages/tool-agent-team/lib/host-access.js +19 -0
  102. package/packages/tool-agent-team/lib/index.js +71 -49
  103. package/packages/tool-agent-team/lib/types/host-access.d.ts +10 -0
  104. package/packages/tool-agent-team/lib/types/host-access.d.ts.map +1 -1
  105. package/packages/tool-agent-team/lib/types/index.d.ts.map +1 -1
  106. package/packages/agent-team/lib/progress-nudge.js +0 -328
  107. package/packages/agent-team/lib/types/progress-nudge.d.ts +0 -136
  108. package/packages/agent-team/lib/types/progress-nudge.d.ts.map +0 -1
  109. package/packages/client-agent-team/lib/types/client/TeamSettings.d.ts +0 -3
  110. package/packages/client-agent-team/lib/types/client/TeamSettings.d.ts.map +0 -1
  111. package/packages/client-agent-team/lib/types/client/TeamSettings.js +0 -5
  112. package/packages/client-agent-team/lib/types/client/scope-coverage.d.ts +0 -41
  113. package/packages/client-agent-team/lib/types/client/scope-coverage.d.ts.map +0 -1
  114. package/packages/client-agent-team/lib/types/client/scope-coverage.js +0 -64
@@ -57,14 +57,13 @@ import { carriedInputOf, checkpointByRef, checkpointRefFor, contextProjectionFol
57
57
  import { advanceOwnedSessionEventCursor } from "./session-event-cursor.js";
58
58
  import { AGENT_TEAM_HUMAN_MEMBER_ID, AgentTeamLedger, agentTeamHumanActor } from "./ledger.js";
59
59
  import { AGENT_TEAM_TOOL_NAMES, deepCopyCapabilities, memberMemoryDirectoryName, MemberRuntime } from "./member-runtime.js";
60
- import { ProgressNudgeCoordinator } from "./progress-nudge.js";
61
60
  import { classifyRecoverableError, RecoveryCoordinator, RECOVERY_MAX_CONSECUTIVE_ERRORS } from "./recovery.js";
62
61
  import { SessionRemediation, handoffAlreadyInLog } from "./session-remediation.js";
63
62
  import { StoredSessionReadError, StoredSessionReader, sessionFailureOf } from "./stored-session-reader.js";
64
63
  import { agentTeamDomainSpec } from "./spec.js";
65
64
  import { formatTeamTimestamp } from "./time-format.js";
66
65
  export { agentTeamDomainSpec, agentTeamOperationSchema } from "./spec.js";
67
- export { AGENT_TEAM_HUMAN_MEMBER_ID, AGENT_TEAM_INITIALIZE_REQUEST_ID } from "./ledger.js";
66
+ export { AGENT_TEAM_HUMAN_HANDLE, AGENT_TEAM_HUMAN_MEMBER_ID, AGENT_TEAM_INITIALIZE_REQUEST_ID } from "./ledger.js";
68
67
  export { AGENT_TEAM_TOOL_NAMES } from "./member-runtime.js";
69
68
  /** Process-stable marker carried by the final Team message tool definition. */
70
69
  export const AGENT_TEAM_PRESET_MARKER = Symbol.for('@wowyuarm/dsh-agent-team.preset');
@@ -179,6 +178,8 @@ let AgentTeam = (() => {
179
178
  let _promoteThread_decorators;
180
179
  let _joinChannel_decorators;
181
180
  let _removeChannelMember_decorators;
181
+ let _joinWorkspace_decorators;
182
+ let _leaveWorkspace_decorators;
182
183
  let _sendMessage_decorators;
183
184
  let _putAttachment_decorators;
184
185
  let _getAttachment_decorators;
@@ -194,7 +195,7 @@ let AgentTeam = (() => {
194
195
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
195
196
  _resolveTaskRefs_decorators = [Remote('resolveTaskRefs')];
196
197
  _membersForClient_decorators = [Remote('members')];
197
- _changes_decorators = [Remote('changes')];
198
+ _changes_decorators = [Remote({ mode: 'stream' })];
198
199
  _createChannel_decorators = [Remote('createChannel')];
199
200
  _updateChannel_decorators = [Remote('updateChannel')];
200
201
  _archiveChannel_decorators = [Remote('archiveChannel')];
@@ -207,6 +208,8 @@ let AgentTeam = (() => {
207
208
  _promoteThread_decorators = [Remote('promoteThread')];
208
209
  _joinChannel_decorators = [Remote('joinChannel')];
209
210
  _removeChannelMember_decorators = [Remote('removeChannelMember')];
211
+ _joinWorkspace_decorators = [Remote('joinWorkspace')];
212
+ _leaveWorkspace_decorators = [Remote('leaveWorkspace')];
210
213
  _sendMessage_decorators = [Remote('sendMessage')];
211
214
  _putAttachment_decorators = [Remote('putAttachment')];
212
215
  _getAttachment_decorators = [Remote('getAttachment')];
@@ -232,6 +235,8 @@ let AgentTeam = (() => {
232
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);
233
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);
234
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);
235
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);
236
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);
237
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);
@@ -302,22 +307,6 @@ let AgentTeam = (() => {
302
307
  this.ctx.logger.warn(`agent-team: member '${this.memberLabel(memberId)}' reached ${consecutiveFailures}/${RECOVERY_MAX_CONSECUTIVE_ERRORS} consecutive recoverable failures; leaving it in error for the operator`);
303
308
  },
304
309
  });
305
- /**
306
- * Advisory progress-visibility nudges. Purely in-process: the ledger owns
307
- * eligibility, this coordinator owns counting and notices, and neither ever
308
- * writes Team facts. See docs/team-collaboration.md for the regimes.
309
- */
310
- progressNudge = new ProgressNudgeCoordinator({
311
- agentForMember: memberId => this.handles.get(memberId)?.agent,
312
- targetsForMember: memberId => this.requireLedger().progressNudgeTargets(memberId),
313
- sessionLogForMember: (memberId, sessionId) => {
314
- const handle = this.handles.get(memberId);
315
- if (handle === undefined || handle.agent.session.id !== sessionId)
316
- return undefined;
317
- return { events: handle.agent.session.ownEvents() };
318
- },
319
- log: message => { this.ctx.logger.warn(message); },
320
- });
321
310
  /**
322
311
  * Incremental fold state for {@link contextManagement}'s projection lookup:
323
312
  * one entry per Member, replaced when that Member's Session changes. Keying
@@ -399,7 +388,7 @@ let AgentTeam = (() => {
399
388
  if (kind !== undefined)
400
389
  this.ctx.logger.warn(`agent-team: member '${member.handle}' hit a recoverable ${kind} error; recording a consecutive error occurrence`);
401
390
  this.recovery.onError(member.memberId, message);
402
- this.emitPresenceChanged(member.workspaceId);
391
+ this.emitMemberPresenceChanged(member);
403
392
  });
404
393
  this.ctx.on('agent/status', ({ agent, status }) => {
405
394
  const member = this.memberForAgent(agent);
@@ -411,7 +400,7 @@ let AgentTeam = (() => {
411
400
  const recovered = this.clearMemberFailure(member.memberId, 'runtime');
412
401
  if (recovered)
413
402
  this.notifiedInbox.delete(member.memberId);
414
- this.emitPresenceChanged(member.workspaceId);
403
+ this.emitMemberPresenceChanged(member);
415
404
  // A rollover/recovery in flight delivers its own sequenced
416
405
  // rederived Inbox after the handoff and carried input; a status-driven
417
406
  // steer here would claim the handoff turn's next step and leapfrog
@@ -428,17 +417,16 @@ let AgentTeam = (() => {
428
417
  if (this.memberFailures.get(member.memberId)?.runtime === undefined) {
429
418
  this.recovery.onCleanTurnEnd(member.memberId);
430
419
  }
431
- this.emitPresenceChanged(member.workspaceId);
420
+ this.emitMemberPresenceChanged(member);
432
421
  }
433
422
  });
434
- // Progress nudges count every `tool/call` of each Member Session. The
435
- // store's dispatch carrier is untagged, so a scope-tagged listener inside
436
- // the Agent setup would receive nothing; one root listener that maps the
437
- // session id back to its Member is the seam that works (same shape as the
438
- // `agent/status` listener above). The first turn of a freshly published
439
- // Member cannot race this: `handles.set()` precedes the same activation
440
- // continuation that publishes the Agent, so by the time any tool call
441
- // streams, the map lookup succeeds.
423
+ // The store's dispatch carrier is untagged, so a scope-tagged listener
424
+ // inside the Agent setup would receive nothing; one root listener that
425
+ // maps the session id back to its Member is the seam that works (same
426
+ // shape as the `agent/status` listener above). The first turn of a
427
+ // freshly published Member cannot race this: `handles.set()` precedes the
428
+ // same activation continuation that publishes the Agent, so by the time
429
+ // any tool call streams, the map lookup succeeds.
442
430
  this.ctx.on('session/event', (session, event) => {
443
431
  const memberId = this.memberBySessionId.get(session.id);
444
432
  if (memberId === undefined)
@@ -446,7 +434,6 @@ let AgentTeam = (() => {
446
434
  const handle = this.handles.get(memberId);
447
435
  if (handle === undefined || handle.agent.session.id !== session.id)
448
436
  return;
449
- this.progressNudge.onSessionEvent(memberId, session.id, handle.agent, event);
450
437
  // A successful assistant response ends any open provider-overflow
451
438
  // recovery sequence for this Member.
452
439
  if (event.type === 'assistant/message')
@@ -460,7 +447,6 @@ let AgentTeam = (() => {
460
447
  this.accepting = false;
461
448
  this.remediation = undefined;
462
449
  this.recovery.dispose();
463
- this.progressNudge.dispose();
464
450
  this.contextManagement.dispose();
465
451
  this.pressurePolicy.dispose();
466
452
  if (this.attachmentGcTimer !== undefined)
@@ -497,6 +483,7 @@ let AgentTeam = (() => {
497
483
  // One metadata listing serves every Member restore; per-member list calls
498
484
  // would repeat the same I/O linearly during startup.
499
485
  const persistedSessions = new Set((await this.persistedSessionHeaders()).map(snapshot => snapshot.header.id));
486
+ await this.sweepWorkspaceParticipations(ledger);
500
487
  for (const member of ledger.listMembers()) {
501
488
  if (member.state === 'enabled')
502
489
  await this.activateMember(member, undefined, persistedSessions);
@@ -504,6 +491,31 @@ let AgentTeam = (() => {
504
491
  await this.memberRuntime.cleanupRemovedMember(member);
505
492
  }
506
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
+ }
507
519
  /**
508
520
  * Whether one Member Session has durable persisted content, decided through
509
521
  * {@link SessionPersistence.stat} rather than a bare metadata listing: the
@@ -539,56 +551,52 @@ let AgentTeam = (() => {
539
551
  });
540
552
  return Object.freeze({ resolved: Object.freeze(this.requireLedger().resolveTaskRefs(request.workspaceId, taskRefs)) });
541
553
  }
542
- /** Return only this Workspace's current Member projection to the Client. */
554
+ /** Browser-safe Human roster, optionally filtered to one participation. */
543
555
  membersForClient(request) {
544
- this.requireWorkspace(request.workspaceId);
556
+ if (request.workspaceId !== undefined)
557
+ this.requireWorkspace(request.workspaceId);
558
+ const ledger = this.requireLedger();
545
559
  return this.members()
546
- .filter(status => status.member.workspaceId === request.workspaceId)
547
- .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) }));
548
562
  }
549
- /** Wait for a lightweight projection invalidation without exposing ledger records. */
550
- async changes(request, signal) {
551
- if (!Number.isInteger(request.afterVersion) || request.afterVersion < 0)
552
- 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) {
553
565
  const scope = this.validateChangeScope(request.scope);
554
- const version = this.changeVersionOf(scope);
555
- if (version > request.afterVersion || !this.accepting)
556
- return Object.freeze({ version });
557
- return new Promise((resolve, reject) => {
558
- let settled = false;
559
- const waiter = {
560
- scope,
561
- wake: version => {
562
- if (settled)
563
- return;
564
- settled = true;
565
- clearTimeout(timeout);
566
- signal?.removeEventListener('abort', onAbort);
567
- resolve(Object.freeze({ version }));
568
- },
569
- };
570
- const timeout = setTimeout(() => {
571
- if (settled)
572
- return;
573
- settled = true;
574
- this.changeWaiters.delete(waiter);
575
- resolve(Object.freeze({ version: this.changeVersionOf(scope) }));
576
- }, 25_000);
577
- const onAbort = () => {
578
- 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)
579
588
  return;
580
- settled = true;
581
- clearTimeout(timeout);
582
- this.changeWaiters.delete(waiter);
583
- reject(new Error('changes wait was aborted'));
584
- };
585
- signal?.addEventListener('abort', onAbort, { once: true });
586
- if (signal?.aborted === true) {
587
- onAbort();
588
- return;
589
+ if (pending !== undefined) {
590
+ const version = pending;
591
+ pending = undefined;
592
+ yield { version };
593
+ }
589
594
  }
590
- this.changeWaiters.add(waiter);
591
- });
595
+ }
596
+ finally {
597
+ this.changeWaiters.delete(waiter);
598
+ signal?.removeEventListener('abort', onAbort);
599
+ }
592
600
  }
593
601
  /** Return durable Team status without issuing a model request or a storage write. */
594
602
  status() {
@@ -692,7 +700,7 @@ let AgentTeam = (() => {
692
700
  async recoverMember(request) {
693
701
  this.requireAccepting();
694
702
  const member = this.requireLedger().getMember(request.memberId);
695
- if (member === undefined || member.workspaceId !== request.workspaceId)
703
+ if (member === undefined || !this.requireLedger().participatesIn(request.memberId, request.workspaceId))
696
704
  throw new Error(`unknown Member '${request.memberId}' in workspace '${request.workspaceId}'`);
697
705
  this.recovery.stopTracking(request.memberId);
698
706
  // An orphaned composition cannot be steered: its tools are gone, so a
@@ -750,7 +758,7 @@ let AgentTeam = (() => {
750
758
  this.requireAccepting();
751
759
  this.requireWorkspace(request.workspaceId);
752
760
  const stored = this.requireLedger().getMember(request.memberId);
753
- if (stored === undefined || stored.workspaceId !== request.workspaceId)
761
+ if (stored === undefined || !this.requireLedger().participatesIn(request.memberId, request.workspaceId))
754
762
  throw new Error(`unknown Member '${request.memberId}' in workspace '${request.workspaceId}'`);
755
763
  if (stored.state !== 'enabled')
756
764
  throw new Error(`Agent Member '${stored.handle}' is ${stored.state}; only enabled Members can start from a new context`);
@@ -794,9 +802,6 @@ let AgentTeam = (() => {
794
802
  // Drop the old handle's transient state: pending recovery episodes and
795
803
  // error markers belong to the disposed agent, not to the Member.
796
804
  this.recovery.stopTracking(memberId);
797
- // The fresh Session may re-earn one Claim suggestion per Thread; the
798
- // old Session's one-shot records must not leak into it.
799
- this.progressNudge.stopTracking(memberId);
800
805
  // The context admission gate stays armed through disposal: input racing
801
806
  // the retire window must still be captured for the new generation, and
802
807
  // the coordinator drops its own bookkeeping only after the swap settles.
@@ -899,7 +904,7 @@ let AgentTeam = (() => {
899
904
  reactivated.agent.steer(this.contextManagement.handoffMessageFor(plan));
900
905
  for (const message of carriedInput)
901
906
  reactivated.agent.followup(message);
902
- const notifications = this.requireLedger().notificationFacts(memberId, { workspaceId: rolled.member.workspaceId });
907
+ const notifications = this.requireLedger().notificationFacts(rolled.member.memberId);
903
908
  if (notifications.length > 0)
904
909
  this.notifyMember(reactivated.agent, carriedInput.length > 0);
905
910
  });
@@ -937,7 +942,7 @@ let AgentTeam = (() => {
937
942
  const handle = this.handles.get(member.memberId);
938
943
  if (handle === undefined)
939
944
  throw new Error(`member '${member.handle}' has no active session`);
940
- const notifications = this.requireLedger().notificationFacts(member.memberId, { workspaceId: member.workspaceId });
945
+ const notifications = this.requireLedger().notificationFacts(member.memberId);
941
946
  const body = notifications.length === 0 ? text : `${text}\n\n${this.notificationText(notifications, member.memberId)}`;
942
947
  const hint = createUserMessage({
943
948
  content: [{ type: 'text', text: body }],
@@ -947,9 +952,6 @@ let AgentTeam = (() => {
947
952
  if (this.isInboxNotice(pending))
948
953
  handle.agent.inbox.remove(pending.id);
949
954
  }
950
- // Recovery carries the concrete interrupted work; a queued progress nudge
951
- // is stale next to it and must not survive as a second reminder.
952
- this.progressNudge.revokePendingNotice(member.memberId);
953
955
  handle.agent.steer(hint);
954
956
  }
955
957
  /** Automatic-recovery wakeup; throwing tells the coordinator the target is gone. */
@@ -1071,6 +1073,22 @@ let AgentTeam = (() => {
1071
1073
  this.emitCommitted(result.value.receipt);
1072
1074
  return result.value;
1073
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
+ }
1074
1092
  /** Human top-level Thread start; asTask attaches an optional Task overlay. */
1075
1093
  async sendMessage(request) {
1076
1094
  return this.sendMessageAs(this.humanCall(request.workspaceId), request);
@@ -1211,8 +1229,10 @@ let AgentTeam = (() => {
1211
1229
  }
1212
1230
  inboxForAgent(agent, request) {
1213
1231
  const actor = this.memberActor(agent);
1232
+ if (request.workspaceId === undefined)
1233
+ return this.requireLedger().memberInbox(actor, request);
1214
1234
  this.requireAgentWorkspace(actor, request.workspaceId);
1215
- return this.requireLedger().inbox(actor, request);
1235
+ return this.requireLedger().inbox(actor, { ...request, workspaceId: request.workspaceId });
1216
1236
  }
1217
1237
  async readThreadForAgent(agent, request) {
1218
1238
  const actor = this.memberCall(agent, request.workspaceId);
@@ -1296,7 +1316,7 @@ let AgentTeam = (() => {
1296
1316
  }
1297
1317
  try {
1298
1318
  const message = createUserMessage({
1299
- 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) }],
1300
1320
  source: { kind: 'plugin', plugin: AGENT_TEAM_PLUGIN_ID, form: 'relay' },
1301
1321
  });
1302
1322
  // An idle recipient gets one ordinary turn; a busy one is steered into
@@ -1312,26 +1332,38 @@ let AgentTeam = (() => {
1312
1332
  return result.value;
1313
1333
  }
1314
1334
  /** Relay body: the DM itself plus one bounded line of adjacent context. */
1315
- dmRelayText(senderAgent, recipient, body, occurredAt, excluding) {
1335
+ dmRelayText(senderAgent, recipient, body, occurredAt, excluding, workspaceId) {
1316
1336
  const sender = this.memberForAgent(senderAgent);
1317
1337
  const prior = this.requireLedger().dmHistoryBetween(senderAgent.id, recipient.memberId, excluding);
1318
1338
  const header = `Direct message from @${sender?.handle ?? 'a Team Member'} at ${formatTeamTimestamp(occurredAt)}:`;
1319
1339
  const context = prior === undefined ? '' : `\n\n[most recent prior DM between you: ${prior}]`;
1320
- return `${header}\n\n${body}${context}`;
1340
+ return `${header}\nWorkspace: ${workspaceId}\n\n${body}${context}`;
1321
1341
  }
1322
1342
  threadHistoryForAgent(agent, request) {
1323
1343
  const actor = this.memberActor(agent);
1324
1344
  this.requireAgentWorkspace(actor, request.workspaceId);
1325
1345
  return this.requireLedger().threadHistory(actor, request);
1326
1346
  }
1327
- /** 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. */
1328
1356
  viewForAgent(agent, request) {
1329
1357
  const member = this.memberForAgent(agent);
1330
1358
  if (member === undefined)
1331
1359
  throw new Error('Agent is not an active Team Member');
1332
- if (member.workspaceId !== request.workspaceId)
1360
+ if (!this.requireLedger().participatesIn(member.memberId, request.workspaceId))
1333
1361
  throw new Error('Member cannot view another Workspace');
1334
- 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
+ })) });
1335
1367
  }
1336
1368
  /** Validate the durable ledger against an independently replayed projection. */
1337
1369
  validateLedger() {
@@ -1870,8 +1902,9 @@ let AgentTeam = (() => {
1870
1902
  const source = event.data.source;
1871
1903
  if (source?.kind !== 'plugin')
1872
1904
  continue;
1873
- // Reminder notices never enter attribution — a progress nudge or a
1874
- // recovery instruction is not a Team fact.
1905
+ // Reminder notices never enter attribution — a recovery instruction
1906
+ // (or a historical progress-nudge notice, kept decodable in session
1907
+ // logs recorded before that system was removed) is not a Team fact.
1875
1908
  if (source.form === 'notice' && source.summary !== undefined && isReminderNoticeSummary(source.summary))
1876
1909
  continue;
1877
1910
  const text = event.data.content.filter(block => block.type === 'text').map(block => block.text ?? '').join('\n');
@@ -2065,9 +2098,10 @@ let AgentTeam = (() => {
2065
2098
  this.requireWorkspace(workspaceId);
2066
2099
  return agentTeamHumanActor();
2067
2100
  }
2068
- /** Fence one Member call: accepting Host, live Member binding, matching Workspace. */
2101
+ /** Fence one Member call: accepting Host, live Member binding, matching Workspace participation. */
2069
2102
  memberCall(agent, workspaceId) {
2070
2103
  this.requireAccepting();
2104
+ this.requireWorkspace(workspaceId);
2071
2105
  const actor = this.memberActor(agent);
2072
2106
  this.requireAgentWorkspace(actor, workspaceId);
2073
2107
  return actor;
@@ -2079,7 +2113,7 @@ let AgentTeam = (() => {
2079
2113
  return Object.freeze({ kind: 'member', memberId: member.memberId, handle: member.handle });
2080
2114
  }
2081
2115
  requireAgentWorkspace(actor, workspaceId) {
2082
- if (this.requireLedger().getMember(actor.memberId)?.workspaceId !== workspaceId)
2116
+ if (!this.requireLedger().participatesIn(actor.memberId, workspaceId))
2083
2117
  throw new Error('Member cannot mutate another Workspace');
2084
2118
  }
2085
2119
  /**
@@ -2338,7 +2372,7 @@ let AgentTeam = (() => {
2338
2372
  }
2339
2373
  finally {
2340
2374
  // Activation only changes this Workspace's presence projection.
2341
- this.emitPresenceChanged(member.workspaceId);
2375
+ this.emitMemberPresenceChanged(member);
2342
2376
  }
2343
2377
  }
2344
2378
  /**
@@ -2511,10 +2545,25 @@ let AgentTeam = (() => {
2511
2545
  }
2512
2546
  const ledger = this.requireLedger();
2513
2547
  this.emitChanged(ledger.changeScopesOf(operation));
2514
- // Nudge reconciliation precedes notifyMember: a committed reply both
2515
- // resets the author's silence and cancels any of its queued nudge before
2516
- // the Inbox notice (higher priority) is considered.
2517
- this.progressNudge.onCommitted(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
+ }
2518
2567
  for (const memberId of ledger.affectedMembersOf(operation)) {
2519
2568
  const handle = this.handles.get(memberId);
2520
2569
  if (handle !== undefined)
@@ -2550,10 +2599,8 @@ let AgentTeam = (() => {
2550
2599
  .map((job) => `${job.name ?? job.id}`);
2551
2600
  }
2552
2601
  emitAutoCompactionChanged(memberId) {
2553
- const workspaceId = this.ledger?.getMember(memberId)?.workspaceId;
2554
- if (workspaceId === undefined)
2555
- return;
2556
- this.emitPresenceChanged(workspaceId);
2602
+ for (const workspaceId of this.ledger?.workspacesOf(memberId) ?? [])
2603
+ this.emitPresenceChanged(workspaceId);
2557
2604
  }
2558
2605
  /**
2559
2606
  * Presence-only invalidation: Agent running/idle/activation/failure changes
@@ -2563,12 +2610,17 @@ let AgentTeam = (() => {
2563
2610
  emitPresenceChanged(workspaceId) {
2564
2611
  this.emitChanged([{ kind: 'presence', workspaceId }]);
2565
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
+ }
2566
2618
  /** Wake from durable unread state with bounded facts for direct and state-changing work. */
2567
2619
  notifyMember(agent, sequenced = false) {
2568
2620
  const member = this.memberForAgent(agent);
2569
2621
  if (member === undefined || member.state !== 'enabled')
2570
2622
  return;
2571
- const notifications = this.requireLedger().notificationFacts(member.memberId, { workspaceId: member.workspaceId });
2623
+ const notifications = this.requireLedger().notificationFacts(member.memberId);
2572
2624
  if (notifications.length === 0) {
2573
2625
  this.notifiedInbox.delete(member.memberId);
2574
2626
  return;
@@ -2594,9 +2646,6 @@ let AgentTeam = (() => {
2594
2646
  const existingInboxHint = pending.find(message => this.isInboxNotice(message));
2595
2647
  if (existingInboxHint !== undefined)
2596
2648
  agent.inbox.remove(existingInboxHint.id);
2597
- // A durable Inbox notice outranks a queued progress nudge; revoke it so
2598
- // the model reads concrete unread work instead of a generic reminder.
2599
- this.progressNudge.revokePendingNotice(member.memberId);
2600
2649
  const hint = createUserMessage({
2601
2650
  content: [{ type: 'text', text: this.notificationText(notifications, member.memberId) }],
2602
2651
  source: { kind: 'plugin', plugin: AGENT_TEAM_PLUGIN_ID, form: 'notice', summary: INBOX_NOTICE_SUMMARY },
@@ -2644,6 +2693,8 @@ let AgentTeam = (() => {
2644
2693
  return true;
2645
2694
  };
2646
2695
  for (const { item, facts } of notifications.slice(0, 8)) {
2696
+ if (!append(`Workspace: ${item.workspaceId}\nChannel: ${item.channelRef}`))
2697
+ break;
2647
2698
  for (const { fact, direct } of facts) {
2648
2699
  if (detailedFactCount >= 20) {
2649
2700
  omitted = true;
@@ -2673,7 +2724,7 @@ let AgentTeam = (() => {
2673
2724
  }
2674
2725
  if (omitted)
2675
2726
  sections.push('More unread work remains in team_inbox; the automatic context is bounded.');
2676
- 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.');
2677
2728
  return sections.join('\n\n');
2678
2729
  }
2679
2730
  boundedNotificationBody(body) {
@@ -2734,7 +2785,6 @@ let AgentTeam = (() => {
2734
2785
  }
2735
2786
  clearMemberRecoveryState(member) {
2736
2787
  this.recovery.stopTracking(member.memberId);
2737
- this.progressNudge.stopTracking(member.memberId);
2738
2788
  this.clearMemberFailure(member.memberId, 'runtime');
2739
2789
  this.clearMemberNotificationState(member.memberId);
2740
2790
  }
@@ -2764,7 +2814,6 @@ let AgentTeam = (() => {
2764
2814
  const waiterScope = waiter.scope;
2765
2815
  if (scopes !== undefined && (waiterScope === undefined ? !touchesProjection : !scopes.some(scope => sameChangeScope(scope, waiterScope))))
2766
2816
  continue;
2767
- this.changeWaiters.delete(waiter);
2768
2817
  waiter.wake(this.changeVersionOf(waiterScope));
2769
2818
  }
2770
2819
  }