@wowyuarm/dsh-agent-team 0.1.14-alpha.2 → 0.1.14

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 (127) hide show
  1. package/README.md +5 -3
  2. package/README.zh.md +5 -3
  3. package/cordis.patch.yml +2 -185
  4. package/package.json +52 -46
  5. package/packages/agent-team/README.md +1 -1
  6. package/packages/agent-team/README.zh.md +1 -1
  7. package/packages/agent-team/core-skills/member-memory-manager/SKILL.md +30 -0
  8. package/packages/agent-team/lib/context-continuity-host.js +91 -0
  9. package/packages/agent-team/lib/context-projection.js +266 -489
  10. package/packages/agent-team/lib/context-source.js +17 -106
  11. package/packages/agent-team/lib/human-avatar.js +61 -0
  12. package/packages/agent-team/lib/human-profile.js +46 -0
  13. package/packages/agent-team/lib/human-update-check.js +119 -0
  14. package/packages/agent-team/lib/index.js +408 -264
  15. package/packages/agent-team/lib/ledger.js +120 -15
  16. package/packages/agent-team/lib/member-context.js +9 -8
  17. package/packages/agent-team/lib/member-runtime.js +1 -1
  18. package/packages/agent-team/lib/member-time-context.js +2 -5
  19. package/packages/agent-team/lib/preset-roster.js +15 -0
  20. package/packages/agent-team/lib/pressure-policy.js +9 -6
  21. package/packages/agent-team/lib/session-remediation.js +493 -0
  22. package/packages/agent-team/lib/stored-session-reader.js +2 -1
  23. package/packages/agent-team/lib/typert.host.js +605 -291
  24. package/packages/agent-team/lib/typert.remote-client.d.ts +15 -6
  25. package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
  26. package/packages/agent-team/lib/typert.remote-client.js +367 -244
  27. package/packages/agent-team/lib/types/context-continuity-host.d.ts +84 -0
  28. package/packages/agent-team/lib/types/context-continuity-host.d.ts.map +1 -0
  29. package/packages/agent-team/lib/types/context-projection.d.ts +130 -185
  30. package/packages/agent-team/lib/types/context-projection.d.ts.map +1 -1
  31. package/packages/agent-team/lib/types/context-source.d.ts +17 -81
  32. package/packages/agent-team/lib/types/context-source.d.ts.map +1 -1
  33. package/packages/agent-team/lib/types/human-avatar.d.ts +29 -0
  34. package/packages/agent-team/lib/types/human-avatar.d.ts.map +1 -0
  35. package/packages/agent-team/lib/types/human-profile.d.ts +44 -0
  36. package/packages/agent-team/lib/types/human-profile.d.ts.map +1 -0
  37. package/packages/agent-team/lib/types/human-update-check.d.ts +73 -0
  38. package/packages/agent-team/lib/types/human-update-check.d.ts.map +1 -0
  39. package/packages/agent-team/lib/types/index.d.ts +112 -38
  40. package/packages/agent-team/lib/types/index.d.ts.map +1 -1
  41. package/packages/agent-team/lib/types/ledger.d.ts +31 -2
  42. package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
  43. package/packages/agent-team/lib/types/member-context.d.ts +0 -14
  44. package/packages/agent-team/lib/types/member-context.d.ts.map +1 -1
  45. package/packages/agent-team/lib/types/member-time-context.d.ts +0 -15
  46. package/packages/agent-team/lib/types/member-time-context.d.ts.map +1 -1
  47. package/packages/agent-team/lib/types/preset-roster.d.ts +5 -0
  48. package/packages/agent-team/lib/types/preset-roster.d.ts.map +1 -0
  49. package/packages/agent-team/lib/types/pressure-policy.d.ts +6 -0
  50. package/packages/agent-team/lib/types/pressure-policy.d.ts.map +1 -1
  51. package/packages/agent-team/lib/types/session-remediation.d.ts +172 -0
  52. package/packages/agent-team/lib/types/session-remediation.d.ts.map +1 -0
  53. package/packages/agent-team/lib/types/stored-session-reader.d.ts +2 -1
  54. package/packages/agent-team/lib/types/stored-session-reader.d.ts.map +1 -1
  55. package/packages/agent-team/lib/types/types/entities.d.ts +20 -3
  56. package/packages/agent-team/lib/types/types/entities.d.ts.map +1 -1
  57. package/packages/agent-team/lib/types/types/requests-results.d.ts +63 -0
  58. package/packages/agent-team/lib/types/types/requests-results.d.ts.map +1 -1
  59. package/packages/agent-team/preset/team-member/agent.cordis.yml +166 -0
  60. package/packages/client-agent-team/lib/client.js +1667 -516
  61. package/packages/client-agent-team/lib/client.js.map +1 -1
  62. package/packages/client-agent-team/lib/types/client/HumanSettingsSection.d.ts +15 -0
  63. package/packages/client-agent-team/lib/types/client/HumanSettingsSection.d.ts.map +1 -0
  64. package/packages/client-agent-team/lib/types/client/HumanSettingsSection.js +115 -0
  65. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
  66. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +12 -8
  67. package/packages/client-agent-team/lib/types/client/TeamAvatarStack.d.ts +22 -1
  68. package/packages/client-agent-team/lib/types/client/TeamAvatarStack.d.ts.map +1 -1
  69. package/packages/client-agent-team/lib/types/client/TeamAvatarStack.js +25 -2
  70. package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts +6 -1
  71. package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts.map +1 -1
  72. package/packages/client-agent-team/lib/types/client/TeamChannelPage.js +69 -31
  73. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +4 -4
  74. package/packages/client-agent-team/lib/types/client/TeamComposer.js +3 -3
  75. package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts +1 -1
  76. package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts.map +1 -1
  77. package/packages/client-agent-team/lib/types/client/TeamConversation.js +12 -5
  78. package/packages/client-agent-team/lib/types/client/TeamFooterAction.js +2 -2
  79. package/packages/client-agent-team/lib/types/client/TeamInboxPage.d.ts +4 -1
  80. package/packages/client-agent-team/lib/types/client/TeamInboxPage.d.ts.map +1 -1
  81. package/packages/client-agent-team/lib/types/client/TeamInboxPage.js +22 -7
  82. package/packages/client-agent-team/lib/types/client/TeamMemberEditor.d.ts +11 -1
  83. package/packages/client-agent-team/lib/types/client/TeamMemberEditor.d.ts.map +1 -1
  84. package/packages/client-agent-team/lib/types/client/TeamMemberEditor.js +69 -8
  85. package/packages/client-agent-team/lib/types/client/TeamMembersAction.js +2 -2
  86. package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts +18 -3
  87. package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts.map +1 -1
  88. package/packages/client-agent-team/lib/types/client/TeamMessage.js +213 -67
  89. package/packages/client-agent-team/lib/types/client/TeamRowMenu.d.ts.map +1 -1
  90. package/packages/client-agent-team/lib/types/client/TeamRowMenu.js +2 -2
  91. package/packages/client-agent-team/lib/types/client/TeamSidebarSection.js +2 -2
  92. package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts +5 -0
  93. package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -1
  94. package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +55 -29
  95. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.js +3 -3
  96. package/packages/client-agent-team/lib/types/client/TeamWorkspaceSelector.js +2 -2
  97. package/packages/client-agent-team/lib/types/client/avatar-image.d.ts +21 -0
  98. package/packages/client-agent-team/lib/types/client/avatar-image.d.ts.map +1 -0
  99. package/packages/client-agent-team/lib/types/client/avatar-image.js +9 -0
  100. package/packages/client-agent-team/lib/types/client/human-identity.d.ts +94 -0
  101. package/packages/client-agent-team/lib/types/client/human-identity.d.ts.map +1 -0
  102. package/packages/client-agent-team/lib/types/client/human-identity.js +117 -0
  103. package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
  104. package/packages/client-agent-team/lib/types/client/index.js +112 -41
  105. package/packages/client-agent-team/lib/types/client/locales.d.ts +72 -2
  106. package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
  107. package/packages/client-agent-team/lib/types/client/locales.js +78 -2
  108. package/packages/client-agent-team/lib/types/client/multi-menu-field.js +2 -2
  109. package/packages/client-agent-team/lib/types/client/refs.d.ts +71 -0
  110. package/packages/client-agent-team/lib/types/client/refs.d.ts.map +1 -0
  111. package/packages/client-agent-team/lib/types/client/refs.js +181 -0
  112. package/packages/client-agent-team/lib/types/client/slots.d.ts +8 -2
  113. package/packages/client-agent-team/lib/types/client/slots.d.ts.map +1 -1
  114. package/packages/client-agent-team/lib/types/client/team-formatters.d.ts +57 -9
  115. package/packages/client-agent-team/lib/types/client/team-formatters.d.ts.map +1 -1
  116. package/packages/client-agent-team/lib/types/client/team-formatters.js +85 -17
  117. package/packages/tool-agent-team/README.md +1 -1
  118. package/packages/tool-agent-team/README.zh.md +1 -1
  119. package/packages/tool-agent-team/lib/context-tools.js +20 -6
  120. package/packages/tool-agent-team/lib/index.js +2 -2
  121. package/packages/tool-agent-team/lib/types/context-tools.d.ts.map +1 -1
  122. package/packages/agent-team/lib/context-management.js +0 -371
  123. package/packages/agent-team/lib/types/context-management.d.ts +0 -154
  124. package/packages/agent-team/lib/types/context-management.d.ts.map +0 -1
  125. package/packages/client-agent-team/lib/types/client/task-refs.d.ts +0 -31
  126. package/packages/client-agent-team/lib/types/client/task-refs.d.ts.map +0 -1
  127. package/packages/client-agent-team/lib/types/client/task-refs.js +0 -99
@@ -50,19 +50,25 @@ import { Session, SessionId, SessionLogOffset } from '@deepseek-ai/dsh-session';
50
50
  import { setSandboxMode } from '@deepseek-ai/dsh-sandbox-policy';
51
51
  import { Remote, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
52
52
  import { ATTACHMENT_MAX_BYTES, attachmentPayloadPath, attachmentsRoot, copyPathAttachment, newAttachmentId, readAttachment, sanitizeMediaType, sweepAttachmentCache, validatePathAttachment, writeAttachment } from "./attachments.js";
53
+ import { HUMAN_PROFILE_DEFAULT_NAME, HUMAN_PROFILE_REPO_URL, HUMAN_PROFILE_SETTINGS_NAMESPACE, HUMAN_PROFILE_SETTINGS_SCHEMA, HUMAN_PROFILE_VERSION, assertValidHumanName, normalizeHumanName } from "./human-profile.js";
54
+ import { humanAvatarsRoot, readHumanAvatar, removeHumanAvatar, writeHumanAvatar } from "./human-avatar.js";
55
+ import { createHumanUpdateChecker } from "./human-update-check.js";
53
56
  import { PressurePolicyCoordinator } from "./pressure-policy.js";
54
- import { ContextManagementCoordinator } from "./context-management.js";
55
- import { AGENT_TEAM_PLUGIN_ID, createHandoffMessage, isAgentTeamSource, isAgentTeamSourceKind } from "./context-source.js";
56
- import { carriedInputOf, checkpointByRef, checkpointRefFor, contextProjectionFold, foldContextProjection, isReminderNoticeSummary, timelineCandidates } from "./context-projection.js";
57
- import { advanceOwnedSessionEventCursor } from "./session-event-cursor.js";
57
+ import { CONTEXT_CONTINUITY_PROJECTION_KEY, readContextTimeline } from '@wowyuarm/dsh-context-continuity';
58
+ import { createTeamContextManagement, TEAM_CONTEXT_CODEC } from "./context-continuity-host.js";
59
+ import { AGENT_TEAM_PLUGIN_ID } from "./context-source.js";
60
+ import { boundaryByRef, carriedInputOf, checkpointByRef, checkpointRefFor, createTeamContextProjectionConfig, createTeamContextProjectionDefinition, foldTeamContextProjection, retainedTopicsThrough, TeamContextProjectionHost } from "./context-projection.js";
58
61
  import { AGENT_TEAM_HUMAN_MEMBER_ID, AgentTeamLedger, agentTeamHumanActor } from "./ledger.js";
59
62
  import { AGENT_TEAM_TOOL_NAMES, deepCopyCapabilities, memberMemoryDirectoryName, MemberRuntime } from "./member-runtime.js";
60
63
  import { classifyRecoverableError, RecoveryCoordinator, RECOVERY_MAX_CONSECUTIVE_ERRORS } from "./recovery.js";
64
+ import { SessionRemediation, handoffAlreadyInLog } from "./session-remediation.js";
61
65
  import { StoredSessionReadError, StoredSessionReader, sessionFailureOf } from "./stored-session-reader.js";
62
66
  import { agentTeamDomainSpec } from "./spec.js";
63
67
  import { formatTeamTimestamp } from "./time-format.js";
64
68
  export { agentTeamDomainSpec, agentTeamOperationSchema } from "./spec.js";
65
69
  export { AGENT_TEAM_HUMAN_HANDLE, AGENT_TEAM_HUMAN_MEMBER_ID, AGENT_TEAM_INITIALIZE_REQUEST_ID } from "./ledger.js";
70
+ export { HUMAN_PROFILE_DEFAULT_NAME, HUMAN_PROFILE_REPO_URL, HUMAN_PROFILE_SETTINGS_NAMESPACE, HUMAN_PROFILE_SETTINGS_SCHEMA, HUMAN_PROFILE_VERSION, assertValidHumanName, normalizeHumanName } from "./human-profile.js";
71
+ export { humanAvatarsRoot } from "./human-avatar.js";
66
72
  export { AGENT_TEAM_TOOL_NAMES } from "./member-runtime.js";
67
73
  /** Process-stable marker carried by the final Team message tool definition. */
68
74
  export const AGENT_TEAM_PRESET_MARKER = Symbol.for('@wowyuarm/dsh-agent-team.preset');
@@ -85,7 +91,12 @@ const MAX_CHECKPOINT_NAME_CHARS = 120;
85
91
  /** Default and maximum number of timeline items one query returns. */
86
92
  const DEFAULT_TIMELINE_LIMIT = 12;
87
93
  const MAX_TIMELINE_LIMIT = 24;
88
- /** Deepest ancestor lineage the timeline and seed resolution walk. */
94
+ /**
95
+ * Archived generations the timeline and seed resolution walk: the timeline
96
+ * reads this many ancestors behind the current generation, and the seed guard
97
+ * resolves a cited ref through the same depth, so the two surfaces cannot
98
+ * disagree about where history ends.
99
+ */
89
100
  const MAX_TIMELINE_ANCESTORS = 8;
90
101
  /** Product pressure budget constants (see docs/team-collaboration.md). */
91
102
  const CONTEXT_HARD_LIMIT_CAP = 256_000;
@@ -163,6 +174,7 @@ let AgentTeam = (() => {
163
174
  let _classSuper = TypertRemoteService;
164
175
  let _instanceExtraInitializers = [];
165
176
  let _resolveTaskRefs_decorators;
177
+ let _resolveThreadRefs_decorators;
166
178
  let _membersForClient_decorators;
167
179
  let _changes_decorators;
168
180
  let _createChannel_decorators;
@@ -182,6 +194,10 @@ let AgentTeam = (() => {
182
194
  let _sendMessage_decorators;
183
195
  let _putAttachment_decorators;
184
196
  let _getAttachment_decorators;
197
+ let _humanProfileForClient_decorators;
198
+ let _putHumanAvatar_decorators;
199
+ let _getHumanAvatar_decorators;
200
+ let _removeHumanAvatar_decorators;
185
201
  let _reply_decorators;
186
202
  let _changeAttention_decorators;
187
203
  let _inbox_decorators;
@@ -193,6 +209,7 @@ let AgentTeam = (() => {
193
209
  static {
194
210
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
195
211
  _resolveTaskRefs_decorators = [Remote('resolveTaskRefs')];
212
+ _resolveThreadRefs_decorators = [Remote('resolveThreadRefs')];
196
213
  _membersForClient_decorators = [Remote('members')];
197
214
  _changes_decorators = [Remote({ mode: 'stream' })];
198
215
  _createChannel_decorators = [Remote('createChannel')];
@@ -212,6 +229,10 @@ let AgentTeam = (() => {
212
229
  _sendMessage_decorators = [Remote('sendMessage')];
213
230
  _putAttachment_decorators = [Remote('putAttachment')];
214
231
  _getAttachment_decorators = [Remote('getAttachment')];
232
+ _humanProfileForClient_decorators = [Remote('humanProfile')];
233
+ _putHumanAvatar_decorators = [Remote('putHumanAvatar')];
234
+ _getHumanAvatar_decorators = [Remote('getHumanAvatar')];
235
+ _removeHumanAvatar_decorators = [Remote('removeHumanAvatar')];
215
236
  _reply_decorators = [Remote('reply')];
216
237
  _changeAttention_decorators = [Remote('changeAttention')];
217
238
  _inbox_decorators = [Remote('inbox')];
@@ -220,6 +241,7 @@ let AgentTeam = (() => {
220
241
  _threadHistory_decorators = [Remote('threadHistory')];
221
242
  _view_decorators = [Remote('view')];
222
243
  __esDecorate(this, null, _resolveTaskRefs_decorators, { kind: "method", name: "resolveTaskRefs", static: false, private: false, access: { has: obj => "resolveTaskRefs" in obj, get: obj => obj.resolveTaskRefs }, metadata: _metadata }, null, _instanceExtraInitializers);
244
+ __esDecorate(this, null, _resolveThreadRefs_decorators, { kind: "method", name: "resolveThreadRefs", static: false, private: false, access: { has: obj => "resolveThreadRefs" in obj, get: obj => obj.resolveThreadRefs }, metadata: _metadata }, null, _instanceExtraInitializers);
223
245
  __esDecorate(this, null, _membersForClient_decorators, { kind: "method", name: "membersForClient", static: false, private: false, access: { has: obj => "membersForClient" in obj, get: obj => obj.membersForClient }, metadata: _metadata }, null, _instanceExtraInitializers);
224
246
  __esDecorate(this, null, _changes_decorators, { kind: "method", name: "changes", static: false, private: false, access: { has: obj => "changes" in obj, get: obj => obj.changes }, metadata: _metadata }, null, _instanceExtraInitializers);
225
247
  __esDecorate(this, null, _createChannel_decorators, { kind: "method", name: "createChannel", static: false, private: false, access: { has: obj => "createChannel" in obj, get: obj => obj.createChannel }, metadata: _metadata }, null, _instanceExtraInitializers);
@@ -239,6 +261,10 @@ let AgentTeam = (() => {
239
261
  __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);
240
262
  __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);
241
263
  __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);
264
+ __esDecorate(this, null, _humanProfileForClient_decorators, { kind: "method", name: "humanProfileForClient", static: false, private: false, access: { has: obj => "humanProfileForClient" in obj, get: obj => obj.humanProfileForClient }, metadata: _metadata }, null, _instanceExtraInitializers);
265
+ __esDecorate(this, null, _putHumanAvatar_decorators, { kind: "method", name: "putHumanAvatar", static: false, private: false, access: { has: obj => "putHumanAvatar" in obj, get: obj => obj.putHumanAvatar }, metadata: _metadata }, null, _instanceExtraInitializers);
266
+ __esDecorate(this, null, _getHumanAvatar_decorators, { kind: "method", name: "getHumanAvatar", static: false, private: false, access: { has: obj => "getHumanAvatar" in obj, get: obj => obj.getHumanAvatar }, metadata: _metadata }, null, _instanceExtraInitializers);
267
+ __esDecorate(this, null, _removeHumanAvatar_decorators, { kind: "method", name: "removeHumanAvatar", static: false, private: false, access: { has: obj => "removeHumanAvatar" in obj, get: obj => obj.removeHumanAvatar }, metadata: _metadata }, null, _instanceExtraInitializers);
242
268
  __esDecorate(this, null, _reply_decorators, { kind: "method", name: "reply", static: false, private: false, access: { has: obj => "reply" in obj, get: obj => obj.reply }, metadata: _metadata }, null, _instanceExtraInitializers);
243
269
  __esDecorate(this, null, _changeAttention_decorators, { kind: "method", name: "changeAttention", static: false, private: false, access: { has: obj => "changeAttention" in obj, get: obj => obj.changeAttention }, metadata: _metadata }, null, _instanceExtraInitializers);
244
270
  __esDecorate(this, null, _inbox_decorators, { kind: "method", name: "inbox", static: false, private: false, access: { has: obj => "inbox" in obj, get: obj => obj.inbox }, metadata: _metadata }, null, _instanceExtraInitializers);
@@ -256,6 +282,7 @@ let AgentTeam = (() => {
256
282
  'agentPresets',
257
283
  'tools',
258
284
  'sessionPersistence',
285
+ 'sessionProjections',
259
286
  ];
260
287
  domain = __runInitializers(this, _instanceExtraInitializers);
261
288
  ledger;
@@ -297,6 +324,18 @@ let AgentTeam = (() => {
297
324
  pressurePolicy;
298
325
  notifiedInbox = new Map();
299
326
  attachmentGcTimer;
327
+ /**
328
+ * Current human profile source, wired to the `agent-team-human` settings
329
+ * namespace when a settings service is present. Falls back to the historic
330
+ * default so team_view and @ matching keep working without settings.
331
+ */
332
+ humanProfileSource = () => ({ name: HUMAN_PROFILE_DEFAULT_NAME });
333
+ /**
334
+ * New-release check behind the settings footnote. Memory-only and
335
+ * background-refreshed, so the profile read path never waits on the
336
+ * network and every failure settles as "no update known".
337
+ */
338
+ humanUpdateCheck = createHumanUpdateChecker({ currentVersion: HUMAN_PROFILE_VERSION });
300
339
  recovery = new RecoveryCoordinator({
301
340
  wake: memberId => {
302
341
  this.ctx.logger.info(`agent-team: automatic recovery wakeup for member '${this.memberLabel(memberId)}' after consecutive recoverable failures`);
@@ -307,36 +346,35 @@ let AgentTeam = (() => {
307
346
  },
308
347
  });
309
348
  /**
310
- * Incremental fold state for {@link contextManagement}'s projection lookup:
311
- * one entry per Member, replaced when that Member's Session changes. Keying
312
- * by Member rather than by Session id is what bounds the map — a rollover
313
- * that leaves the previous generation's projection behind retains it for the
314
- * life of the process, and only the current Session can ever read it.
349
+ * Team's domain half of the continuity projection: the durable ref naming,
350
+ * the Team-notice rule, and the boundary judgement (committed messages,
351
+ * claim changes, first Thread arrivals). Its claim attribution resolves the
352
+ * Task's Thread through the ledger, which is why the resolver reads
353
+ * `this.ledger` lazily — the fold may run before the domain is open, and an
354
+ * unattributed boundary is still a valid anchor.
315
355
  */
316
- contextCursors = new Map();
356
+ contextProjectionHost = new TeamContextProjectionHost({
357
+ threadForTask: taskRef => this.ledger?.threadForTask(taskRef),
358
+ });
317
359
  /**
318
360
  * Context self-management: the one deep module that turns a Member's
319
361
  * successful `context_rollover` tool result into its next private context
320
- * generation. The ledger owns the binding audit, the Session projection
321
- * owns intent, and this coordinator owns only reconstructible process
362
+ * generation. The ledger owns the binding audit, the engine's projection
363
+ * unit owns intent, and this coordinator owns only reconstructible process
322
364
  * state. See docs/architecture.md and docs/team-collaboration.md.
323
365
  */
324
- contextManagement = new ContextManagementCoordinator({
366
+ contextManagement = createTeamContextManagement({
325
367
  agentForMember: memberId => this.handles.get(memberId)?.agent,
326
368
  memberForAgent: agent => this.memberForAgent(agent),
327
369
  projectionForMember: (memberId, sessionId) => {
328
370
  const handle = this.handles.get(memberId);
329
371
  if (handle === undefined || handle.agent.session.id !== sessionId)
330
372
  return undefined;
331
- const session = handle.agent.session;
332
- // The projection is read on every successful tool result and turn end, so
333
- // it folds incrementally. The entry is keyed by Member and carries the
334
- // Session it was built from: this Session's log keeps growing, a rollover
335
- // replaces the entry instead of resuming from its predecessor's cursor,
336
- // and a rewritten log falls back to a cold fold inside the cursor.
337
- const owned = advanceOwnedSessionEventCursor(this.contextCursors.get(memberId), session.id, contextProjectionFold(session.id), session.ownEvents(), session.inheritedEventCount);
338
- this.contextCursors.set(memberId, owned);
339
- return owned.cursor.value;
373
+ // The engine's registered unit folds this Session's durable log (replay
374
+ // on attach, then incrementally per committed event) and keys every ref
375
+ // to the Session that recorded it; the registry materializes the cell
376
+ // lazily, so a Member that has not folded yet is built on this read.
377
+ return this.ctx.sessionProjections.stateOf(handle.agent.session, CONTEXT_CONTINUITY_PROJECTION_KEY);
340
378
  },
341
379
  executeTransition: (memberId, plan) => this.executeMemberTransition(memberId, plan),
342
380
  log: message => { this.ctx.logger.warn(`agent-team: ${message}`); },
@@ -350,6 +388,12 @@ let AgentTeam = (() => {
350
388
  */
351
389
  presenceEpoch = 0;
352
390
  changeWaiters = new Set();
391
+ /**
392
+ * The startup-opened remediation instance, held for the restart heal: the
393
+ * completion-cache domain may only be opened once per plugin lifecycle, so
394
+ * the restart path reuses this instance instead of opening its own.
395
+ */
396
+ remediation;
353
397
  constructor(ctx) {
354
398
  super(ctx, 'agentTeam');
355
399
  this.pressurePolicy = new PressurePolicyCoordinator({
@@ -368,9 +412,72 @@ let AgentTeam = (() => {
368
412
  },
369
413
  log: message => { this.ctx.logger.warn(`agent-team: ${message}`); },
370
414
  });
415
+ // Human profile settings: the composition entry is the historic default;
416
+ // the settings provider overlays the user document section when present.
417
+ // Reads stay live through the source closure, so team_view and @ matching
418
+ // follow a rename without a Host restart.
419
+ this.ctx.inject(['settings'], settingsCtx => {
420
+ settingsCtx.settings.installSection(this.ctx, HUMAN_PROFILE_SETTINGS_NAMESPACE, HUMAN_PROFILE_SETTINGS_SCHEMA, { name: HUMAN_PROFILE_DEFAULT_NAME }, {
421
+ setSource: (current) => {
422
+ this.humanProfileSource = current;
423
+ this.syncHumanHandle();
424
+ },
425
+ validate: (value) => {
426
+ this.validateHumanProfile(value);
427
+ },
428
+ onChange: () => {
429
+ this.syncHumanHandle();
430
+ },
431
+ });
432
+ });
433
+ }
434
+ /** Current human display name; the single source for team_view and @ matching. */
435
+ humanHandle() {
436
+ const name = normalizeHumanName(this.humanProfileSource().name);
437
+ return name === '' ? HUMAN_PROFILE_DEFAULT_NAME : name;
438
+ }
439
+ /** Current human profile reference held in settings (name + avatarRef). */
440
+ humanProfile() {
441
+ const current = this.humanProfileSource();
442
+ const name = this.humanHandle();
443
+ return Object.freeze({ name, ...(current.avatarRef === undefined ? {} : { avatarRef: current.avatarRef }) });
444
+ }
445
+ /** Push the current settings name into the ledger's runtime @ handle. */
446
+ syncHumanHandle() {
447
+ try {
448
+ this.ledger?.setHumanDisplayHandle(this.humanHandle());
449
+ }
450
+ catch {
451
+ // The ledger is absent before Service.init; the post-open sync covers it.
452
+ }
453
+ }
454
+ /**
455
+ * Settings-level validation for the human profile: same non-empty floor as
456
+ * Member handles plus global uniqueness against live Members. Runs inside
457
+ * the settings write path, so a colliding rename rejects before persisting.
458
+ */
459
+ validateHumanProfile(value) {
460
+ const name = assertValidHumanName(value.name);
461
+ const ledger = this.ledger;
462
+ if (ledger === undefined)
463
+ return;
464
+ const normalized = name.normalize('NFKC').trim().toLowerCase();
465
+ for (const member of ledger.listMembers()) {
466
+ if (member.state === 'inactive' || member.state === 'archived')
467
+ continue;
468
+ if (member.handle.normalize('NFKC').trim().toLowerCase() === normalized) {
469
+ throw new Error(`human name '${name}' collides with an existing Member handle`);
470
+ }
471
+ }
371
472
  }
372
473
  /** Open the durable ledger and restore every enabled Member independently. */
373
474
  async [Service.init]() {
475
+ // The continuity projection registers once per Host: the framework keeps
476
+ // one unit per projection key and drives it for every Session, so Team's
477
+ // fold is session-agnostic and the state carries the identity it folds.
478
+ // The registration rides this plugin's fiber — unloading Team removes the
479
+ // key (and its cached cells) from later drives and snapshots.
480
+ this.ctx.effect(() => this.ctx.root.sessionProjections.register(createTeamContextProjectionDefinition(this.contextProjectionHost)), 'agentTeam.contextProjection');
374
481
  this.ctx.on('agent/error', ({ agent, error }) => {
375
482
  const member = this.memberForAgent(agent);
376
483
  if (member === undefined)
@@ -438,6 +545,7 @@ let AgentTeam = (() => {
438
545
  const domain = await this.ctx.storageDomain.open(agentTeamDomainSpec);
439
546
  this.ctx.effect(() => async () => {
440
547
  this.accepting = false;
548
+ this.remediation = undefined;
441
549
  this.recovery.dispose();
442
550
  this.contextManagement.dispose();
443
551
  this.pressurePolicy.dispose();
@@ -456,10 +564,25 @@ let AgentTeam = (() => {
456
564
  this.domain = domain;
457
565
  const ledger = new AgentTeamLedger(domain.table('operations'));
458
566
  this.ledger = ledger;
567
+ // The settings wiring in the constructor may have fired before the ledger
568
+ // existed; sync once here so @ matching starts from the stored name.
569
+ this.syncHumanHandle();
459
570
  const initialization = await ledger.initialize();
460
571
  if (initialization.committed)
461
572
  this.emitCommitted(initialization.value);
462
573
  this.startAttachmentGc(ledger);
574
+ // Legacy-artifact remediation runs before any Member activation: no write
575
+ // lease exists yet, so publishing sibling generations for refused Session
576
+ // logs cannot race a live writer. Remediation failure never blocks
577
+ // startup — the next start retries exactly what the cache does not cover.
578
+ try {
579
+ const remediation = new SessionRemediation(this.ctx, this.ctx.sessionPersistence, await SessionRemediation.open(this.ctx));
580
+ this.remediation = remediation;
581
+ await remediation.remediateEnabledMembers(ledger.listMembers());
582
+ }
583
+ catch (error) {
584
+ this.ctx.logger.warn(`agent-team: legacy Session remediation did not run to completion (it will retry on the next start): ${error instanceof Error ? error.message : String(error)}`);
585
+ }
463
586
  // One metadata listing serves every Member restore; per-member list calls
464
587
  // would repeat the same I/O linearly during startup.
465
588
  const persistedSessions = new Set((await this.persistedSessionHeaders()).map(snapshot => snapshot.header.id));
@@ -531,6 +654,18 @@ let AgentTeam = (() => {
531
654
  });
532
655
  return Object.freeze({ resolved: Object.freeze(this.requireLedger().resolveTaskRefs(request.workspaceId, taskRefs)) });
533
656
  }
657
+ /** Read-only navigation lookup for branded Thread refs found in message bodies. */
658
+ resolveThreadRefs(request) {
659
+ this.requireWorkspace(request.workspaceId);
660
+ const seen = new Set();
661
+ const threadRefs = request.threadRefs.filter(threadRef => {
662
+ if (seen.has(threadRef))
663
+ return false;
664
+ seen.add(threadRef);
665
+ return true;
666
+ });
667
+ return Object.freeze({ resolved: Object.freeze(this.requireLedger().resolveThreadRefs(request.workspaceId, threadRefs)) });
668
+ }
534
669
  /** Browser-safe Human roster, optionally filtered to one participation. */
535
670
  membersForClient(request) {
536
671
  if (request.workspaceId !== undefined)
@@ -634,13 +769,16 @@ let AgentTeam = (() => {
634
769
  privateMemoryPath: dshHomePath('agent-team', 'members', memberMemoryDirectoryName(memberId)),
635
770
  state: 'enabled',
636
771
  });
637
- const result = await this.requireLedger().addMember({ ...request, actor: agentTeamHumanActor(), member });
772
+ const ledger = this.requireLedger();
773
+ const result = await ledger.addMember({ ...request, actor: agentTeamHumanActor(), member });
638
774
  if (result.committed)
639
775
  this.emitCommitted(result.value.receipt);
640
776
  const stored = result.value.member;
641
777
  if (!this.handles.has(stored.memberId))
642
778
  await this.activateMember(stored, workspace.path);
643
- return Object.freeze({ receipt: result.value.receipt, status: this.memberStatus(stored) });
779
+ // Creation seeds exactly the creation Workspace participation; attach it
780
+ // here (like membersForClient) so the Client never synthesizes it.
781
+ return Object.freeze({ receipt: result.value.receipt, status: this.memberStatus(stored), workspaceIds: ledger.workspacesOf(stored.memberId) });
644
782
  });
645
783
  }
646
784
  /** Commit suspended intent, then wait for the owned AgentHandle to become quiescent. */
@@ -698,10 +836,23 @@ let AgentTeam = (() => {
698
836
  if (member.state !== 'enabled')
699
837
  throw new Error(`Agent Member '${member.handle}' is ${member.state}; only enabled Members can be restarted`);
700
838
  this.ctx.logger.info(`agent-team: restarting member '${member.handle}' after a failed activation`);
701
- // There is no write-side repair pass anymore: dsh 0.1.7 converts the
702
- // released V3 history at read time, and this bundle no longer authors
703
- // the old wrapper shape, so a refusal stays a deterministic failure the
704
- // retry reports again rather than something the restart heals.
839
+ // A deterministic session refusal may be repairable in place: run the
840
+ // same bounded startup remediation for this one Member before retrying
841
+ // activation, so the restart heals instead of replaying the failure.
842
+ const activation = this.memberFailures.get(request.memberId)?.activation;
843
+ if (activation !== undefined && activation.class === 'session-refused' && this.remediation !== undefined) {
844
+ const outcome = await this.remediation.remediateMember(member);
845
+ if (outcome.repaired > 0) {
846
+ this.ctx.logger.info(`agent-team: repaired ${outcome.repaired} refused Session artifact(s) for member '${member.handle}'; retrying activation`);
847
+ }
848
+ else if (outcome.completed) {
849
+ // The walk finished and nothing was provably this plugin's to fix:
850
+ // a retry would fail identically. Mark the refusal non-remediable
851
+ // so the surface stops offering restart and says why.
852
+ this.markRefusalNonRemediable(request.memberId);
853
+ return Object.freeze({ status: this.memberStatus(member) });
854
+ }
855
+ }
705
856
  await this.reactivateMember(request.memberId);
706
857
  return Object.freeze({ status: this.memberStatus(member) });
707
858
  }
@@ -820,6 +971,9 @@ let AgentTeam = (() => {
820
971
  throw new Error(`the context rollover is refused: this Member now owns jobs that would not survive the switch (${blockingJobs.join(', ')}); collect or stop them, then retry`);
821
972
  }
822
973
  const rolled = await this.rolloverSessionForAgent(active.agent, {
974
+ // The engine plan carries the host-derived request id as a plain
975
+ // string; Team's rollover operation vocabulary brands it, and the
976
+ // context-continuity host adapter is its only producer.
823
977
  requestId: plan.requestId,
824
978
  workspaceId: stored.workspaceId,
825
979
  memberId,
@@ -836,8 +990,9 @@ let AgentTeam = (() => {
836
990
  // to override the fold.
837
991
  const preRetireCapture = this.contextManagement.drainCapturedInput(memberId);
838
992
  await this.retireMemberGeneration(memberId, active, plan.previousSessionId);
839
- const finalState = foldContextProjection(active.agent.session.ownEvents(), active.agent.session.inheritedEventCount, active.agent.session.id);
840
- const foldedCarried = carriedInputOf(finalState);
993
+ const finalEvents = active.agent.session.ownEvents();
994
+ const finalState = foldTeamContextProjection(finalEvents, { sessionId: active.agent.session.id, inheritedEventCount: active.agent.session.inheritedEventCount }, this.contextProjectionHost);
995
+ const foldedCarried = carriedInputOf(finalState, finalEvents);
841
996
  const carriedById = new Map(plan.carriedInput.map(message => [message.id, message]));
842
997
  for (const message of foldedCarried)
843
998
  carriedById.set(message.id, message);
@@ -913,7 +1068,7 @@ let AgentTeam = (() => {
913
1068
  const body = notifications.length === 0 ? text : `${text}\n\n${this.notificationText(notifications, member.memberId)}`;
914
1069
  const hint = createUserMessage({
915
1070
  content: [{ type: 'text', text: body }],
916
- source: { kind: AGENT_TEAM_PLUGIN_ID, form: 'notice', summary: RECOVERY_NOTICE_SUMMARY },
1071
+ source: { kind: 'plugin', plugin: AGENT_TEAM_PLUGIN_ID, form: 'notice', summary: RECOVERY_NOTICE_SUMMARY },
917
1072
  });
918
1073
  for (const pending of [...handle.agent.inbox.nextStep, ...handle.agent.inbox.nextTurn]) {
919
1074
  if (this.isInboxNotice(pending))
@@ -1119,6 +1274,50 @@ let AgentTeam = (() => {
1119
1274
  throw new Error(`attachment '${request.attachmentId}' is no longer cached`);
1120
1275
  return Object.freeze({ name: stored.name, mediaType: stored.mediaType, byteSize: stored.byteSize, bytesBase64: stored.bytes.toString('base64') });
1121
1276
  }
1277
+ /**
1278
+ * Human profile read for the settings page and footnote: name + avatar
1279
+ * reference from settings plus version facts. Human-scoped (the Web
1280
+ * Client calls it); agent tools never receive avatar bytes, only the name
1281
+ * through team_view. The new-release check stays best-effort and cached —
1282
+ * `updateAvailable` is false until a background refresh actually observes a
1283
+ * newer published release.
1284
+ */
1285
+ humanProfileForClient(_request) {
1286
+ const profile = this.humanProfile();
1287
+ const update = this.humanUpdateCheck.snapshot();
1288
+ return Object.freeze({
1289
+ name: profile.name,
1290
+ ...(profile.avatarRef === undefined ? {} : { avatarRef: profile.avatarRef }),
1291
+ version: HUMAN_PROFILE_VERSION,
1292
+ repoUrl: HUMAN_PROFILE_REPO_URL,
1293
+ updateAvailable: update.updateAvailable,
1294
+ ...(update.latestVersion === undefined ? {} : { latestVersion: update.latestVersion }),
1295
+ });
1296
+ }
1297
+ /**
1298
+ * Upload one human avatar image into the persistent store. Human-only by
1299
+ * construction: only the Web Client calls this Remote, never agent tools.
1300
+ * The caller stores the returned ref in settings; bytes never enter the
1301
+ * TTL-bound attachment cache.
1302
+ */
1303
+ async putHumanAvatar(request) {
1304
+ this.requireAccepting();
1305
+ const bytes = Buffer.from(request.bytesBase64, 'base64');
1306
+ return Object.freeze(await writeHumanAvatar(humanAvatarsRoot(), request.name, request.mediaType ?? 'application/octet-stream', bytes));
1307
+ }
1308
+ /** Read one human avatar back; removed entries throw and the UI falls back to hue/initial. */
1309
+ async getHumanAvatar(request) {
1310
+ const stored = await readHumanAvatar(humanAvatarsRoot(), request.avatarRef);
1311
+ if (stored === undefined)
1312
+ throw new Error(`human avatar '${request.avatarRef}' is no longer stored`);
1313
+ return Object.freeze({ name: stored.name, mediaType: stored.mediaType, byteSize: stored.byteSize, bytesBase64: stored.bytes.toString('base64') });
1314
+ }
1315
+ /** Remove one human avatar entry; the UI falls back to hue/initial afterwards. */
1316
+ async removeHumanAvatar(request) {
1317
+ this.requireAccepting();
1318
+ await removeHumanAvatar(humanAvatarsRoot(), request.avatarRef);
1319
+ return Object.freeze({ removed: true });
1320
+ }
1122
1321
  /** Human existing-Thread reply; unread and revision conflicts are business outcomes. */
1123
1322
  async reply(request) {
1124
1323
  return this.replyAs(this.humanCall(request.workspaceId), request);
@@ -1284,7 +1483,7 @@ let AgentTeam = (() => {
1284
1483
  try {
1285
1484
  const message = createUserMessage({
1286
1485
  content: [{ type: 'text', text: this.dmRelayText(agent, recipient, request.body.trim(), result.value.receipt.occurredAt, result.value.receipt.operationId, request.workspaceId) }],
1287
- source: { kind: AGENT_TEAM_PLUGIN_ID, form: 'relay' },
1486
+ source: { kind: 'plugin', plugin: AGENT_TEAM_PLUGIN_ID, form: 'relay' },
1288
1487
  });
1289
1488
  // An idle recipient gets one ordinary turn; a busy one is steered into
1290
1489
  // its current turn — the same wake split subagent continuations use.
@@ -1429,7 +1628,7 @@ let AgentTeam = (() => {
1429
1628
  let sessionId = agent.session.id;
1430
1629
  let live = true;
1431
1630
  let guard = 0;
1432
- while (sessionId !== undefined && guard++ < MAX_TIMELINE_ANCESTORS) {
1631
+ while (sessionId !== undefined && guard++ <= MAX_TIMELINE_ANCESTORS) {
1433
1632
  let events;
1434
1633
  let inheritedEventCount;
1435
1634
  let parentSession;
@@ -1454,19 +1653,19 @@ let AgentTeam = (() => {
1454
1653
  // Fold the source with its inherited cut respected: inherited events
1455
1654
  // are resolved history in that source, never fresh intent; checkpoints
1456
1655
  // recorded in this source's own span are the selectable targets.
1457
- const state = foldContextProjection(events, inheritedEventCount, sessionId);
1656
+ const state = foldTeamContextProjection(events, { sessionId, inheritedEventCount }, this.contextProjectionHost);
1458
1657
  // A Team-boundary default checkpoint: the boundary's completed-turn
1459
1658
  // anchor is the seed cut, and it is selectable exactly when one
1460
1659
  // Thread's facts entered the context through it — the same proof the
1461
1660
  // timeline requires, revalidated here because the model may cite a
1462
1661
  // boundary the timeline never surfaced.
1463
1662
  const boundary = checkpointRef.startsWith('team-boundary-')
1464
- ? state.boundaries.find(entry => entry.key === checkpointRef)
1663
+ ? boundaryByRef(state, checkpointRef)
1465
1664
  : undefined;
1466
1665
  const entry = checkpointByRef(state, checkpointRef);
1467
1666
  const anchorTurnEndSeq = boundary !== undefined ? boundary.turnEndSeq : entry?.turnEndSeq;
1468
1667
  if (anchorTurnEndSeq !== undefined && anchorTurnEndSeq !== -1) {
1469
- if (boundary !== undefined && boundary.source !== 'team-boundary') {
1668
+ if (boundary !== undefined && boundary.kind !== 'team-boundary') {
1470
1669
  throw new Error(`checkpoint '${checkpointRef}' is not a restorable boundary`);
1471
1670
  }
1472
1671
  // The seed is the exact contiguous prefix through the anchor's
@@ -1474,7 +1673,12 @@ let AgentTeam = (() => {
1474
1673
  const throughSeq = anchorTurnEndSeq + 1;
1475
1674
  const prefix = events.slice(0, throughSeq);
1476
1675
  if (boundary !== undefined) {
1477
- const threads = this.threadsEnteringContext(events, anchorTurnEndSeq);
1676
+ // The seed must stay inside one Thread's context: the retained
1677
+ // prefix through the anchor holds exactly one Thread's facts, read
1678
+ // from the fold's own boundary attributions — the same accumulated
1679
+ // set the timeline shows, so a ref the timeline offered is never
1680
+ // refused here for a reason it did not state.
1681
+ const threads = retainedTopicsThrough(state, anchorTurnEndSeq);
1478
1682
  if (threads.length !== 1) {
1479
1683
  throw new Error(threads.length === 0
1480
1684
  ? `boundary '${checkpointRef}' has no single attributable Thread; write a fresh handoff instead`
@@ -1535,11 +1739,11 @@ let AgentTeam = (() => {
1535
1739
  return;
1536
1740
  }
1537
1741
  const inspection = previousRead.inspection;
1538
- const state = foldContextProjection(inspection.events, inspection.inheritedEventCount, previousSessionId);
1742
+ const state = foldTeamContextProjection(inspection.events, { sessionId: previousSessionId, inheritedEventCount: inspection.inheritedEventCount }, this.contextProjectionHost);
1539
1743
  if (state.pending === null)
1540
1744
  return;
1541
1745
  const pending = state.pending;
1542
- const message = createHandoffMessage({
1746
+ const message = TEAM_CONTEXT_CODEC.createHandoffMessage({
1543
1747
  handoff: pending.handoff,
1544
1748
  previousSessionId,
1545
1749
  newSessionId: agent.session.id,
@@ -1573,9 +1777,9 @@ let AgentTeam = (() => {
1573
1777
  if (!Number.isSafeInteger(through) || through < 0 || through > inspection.events.length) {
1574
1778
  throw new Error(`the recorded checkpoint-return seed cut ${through} is not a valid prefix of Session '${seed.sourceSessionId}'`);
1575
1779
  }
1576
- const state = foldContextProjection(inspection.events, inspection.inheritedEventCount, seed.sourceSessionId);
1780
+ const state = foldTeamContextProjection(inspection.events, { sessionId: seed.sourceSessionId, inheritedEventCount: inspection.inheritedEventCount }, this.contextProjectionHost);
1577
1781
  const anchorProven = checkpointByRef(state, seed.checkpointRef) !== undefined
1578
- || state.boundaries.some(boundary => boundary.key === seed.checkpointRef);
1782
+ || boundaryByRef(state, seed.checkpointRef) !== undefined;
1579
1783
  if (!anchorProven) {
1580
1784
  throw new Error(`the recorded checkpoint-return anchor '${seed.checkpointRef}' no longer resolves in Session '${seed.sourceSessionId}'`);
1581
1785
  }
@@ -1641,8 +1845,8 @@ let AgentTeam = (() => {
1641
1845
  }
1642
1846
  inspectionEvents = previousRead.inspection.events;
1643
1847
  inspectionInherited = previousRead.inspection.inheritedEventCount;
1644
- const state = foldContextProjection(inspectionEvents, inspectionInherited, transition.previousSessionId);
1645
- const carried = carriedInputOf(state);
1848
+ const state = foldTeamContextProjection(inspectionEvents, { sessionId: transition.previousSessionId, inheritedEventCount: inspectionInherited }, this.contextProjectionHost);
1849
+ const carried = carriedInputOf(state, inspectionEvents);
1646
1850
  if (carried.length === 0)
1647
1851
  return 0;
1648
1852
  const known = new Set();
@@ -1693,9 +1897,11 @@ let AgentTeam = (() => {
1693
1897
  * Agent-only bounded structural timeline: resolved checkpoints plus Team
1694
1898
  * delivery, handoff, and compaction boundaries across the current
1695
1899
  * generation and its archived ancestor lineage. Structural only — no
1696
- * transcript content. The meter prices retained/discarded tokens; entries
1697
- * the Host cannot prove restorable carry a rejection reason instead of
1698
- * silently disappearing.
1900
+ * transcript content. The walk, the per-source folds, the pricing, and the
1901
+ * anchor rules are the engine's `readContextTimeline`; Team contributes the
1902
+ * fold configuration, the measurement, and the one judgement the engine
1903
+ * leaves to its host — which Threads a boundary's retained prefix holds — so
1904
+ * a ref this list offers is a ref `context_rollover` accepts.
1699
1905
  */
1700
1906
  async contextTimelineForAgent(agent, request) {
1701
1907
  const member = this.memberForAgent(agent);
@@ -1710,70 +1916,111 @@ let AgentTeam = (() => {
1710
1916
  const limits = await this.routeLimitsForAgent(agent);
1711
1917
  const hardLimit = limits?.hardLimit ?? CONTEXT_HARD_LIMIT_CAP;
1712
1918
  const handoffAt = limits?.handoffAt ?? CONTEXT_HANDOFF_AT_CAP;
1713
- // Fold the current generation, then walk archived ancestors through their
1714
- // persisted logs; the same projection definition folds every source.
1715
- const items = [];
1716
- const seen = new Set();
1717
- let incompleteFrom;
1718
- let sessionId = member.sessionId;
1719
- let live = true;
1720
- let guard = 0;
1721
- while (sessionId !== undefined && guard++ < MAX_TIMELINE_ANCESTORS) {
1722
- let state;
1723
- let sourceSessionId = sessionId;
1724
- let sourceEvents = [];
1725
- // Capture this iteration's source identity BEFORE the branch advances
1726
- // the lineage flag: measurement and discard pricing both key on it.
1727
- const sourceIsCurrent = live;
1728
- if (live) {
1729
- state = foldContextProjection(agent.session.ownEvents(), agent.session.inheritedEventCount, agent.session.id);
1730
- sourceEvents = agent.session.snapshotEvents();
1731
- sessionId = agent.session.header.parentSession;
1732
- live = false;
1919
+ // One measurement per lineage source, memoized by Session: the engine
1920
+ // measures a source once before pricing its anchors, and the boundary
1921
+ // overlay below reads the same map to tell an unprovable budget (never
1922
+ // priced as free) from a real budget rejection.
1923
+ const measurements = new Map();
1924
+ const timeline = await readContextTimeline({
1925
+ current: {
1926
+ sessionId: agent.session.id,
1927
+ header: agent.session.header,
1928
+ inheritedEventCount: agent.session.inheritedEventCount,
1929
+ events: agent.session.snapshotEvents(),
1930
+ },
1931
+ config: this.contextFoldConfig(),
1932
+ readAncestor: sessionId => this.sessionReader.read(sessionId),
1933
+ measureSource: source => {
1934
+ const key = String(source.sessionId);
1935
+ if (!measurements.has(key))
1936
+ measurements.set(key, this.measureContextSourceForAgent(agent, source));
1937
+ return measurements.get(key);
1938
+ },
1939
+ currentUsageTokens: usageTokens,
1940
+ handoffAt,
1941
+ limit,
1942
+ // Archived ancestors the engine walks; the seed guard resolves a cited
1943
+ // ref through the same depth, so the timeline never offers a ref the
1944
+ // guard cannot reach.
1945
+ maxAncestors: MAX_TIMELINE_ANCESTORS,
1946
+ });
1947
+ return {
1948
+ usageTokens,
1949
+ hardLimit,
1950
+ handoffAt,
1951
+ items: timeline.items.map(item => this.teamTimelineItemFor(item, agent.session.id, handoffAt, measurements)),
1952
+ ...(timeline.incompleteFrom === undefined ? {} : { incompleteFrom: timeline.incompleteFrom }),
1953
+ };
1954
+ }
1955
+ /**
1956
+ * Map one engine timeline item onto the model-facing Team item. The engine
1957
+ * decided the walk, the fold, the pricing, and the head, checkpoint, and
1958
+ * measurable-source verdicts; Team restates exactly one policy of its own: a
1959
+ * Team boundary is selectable only when the RETAINED PREFIX through it stays
1960
+ * inside one Thread — the same proof the seed guard revalidates before it
1961
+ * swaps a generation. The engine judges a boundary by its OWN attribution
1962
+ * instead, so a boundary that arrived after a second Thread's facts would be
1963
+ * offered here and refused by `context_rollover`; Team's stricter rule is
1964
+ * what keeps the two surfaces answering one question.
1965
+ */
1966
+ teamTimelineItemFor(item, currentSessionId, handoffAt, measurements) {
1967
+ const source = item.source === 'boundary'
1968
+ ? item.kind === 'handoff' || item.kind === 'compaction' ? item.kind : 'team-boundary'
1969
+ : item.source === 'checkpoint' ? 'agent' : 'head';
1970
+ const affectedThreads = item.affectedTopics;
1971
+ let restorable = item.restorable;
1972
+ let reason = item.reason;
1973
+ // A boundary's verdict is Team's to make, but only once its source is
1974
+ // measurable: "the budget cannot be proven" is the engine's first
1975
+ // rejection and stays first — an unmeasurable boundary is not selectable
1976
+ // even when its prefix holds exactly one Thread.
1977
+ if (item.source === 'boundary' && measurements.get(String(item.sourceSessionId ?? currentSessionId)) !== undefined) {
1978
+ if (source === 'handoff' || source === 'compaction') {
1979
+ // A handoff opens a generation and a compaction rewrites the visible
1980
+ // surface: rewinding into either is not a proven-safe target. The
1981
+ // engine has no vocabulary for that and would answer "no single topic
1982
+ // is attributable", which misdescribes why.
1983
+ restorable = false;
1984
+ reason = `source '${source}' is not a restorable checkpoint`;
1733
1985
  }
1734
- else {
1735
- const read = await this.sessionReader.read(sessionId);
1736
- if (!read.ok) {
1737
- // An unreadable ancestor ends the lineage walk here — recorded in
1738
- // the result, never silent, and never a Member-availability fact.
1739
- incompleteFrom = { sessionId, reason: `${read.failure.kind}: ${read.failure.detail}` };
1740
- sessionId = undefined;
1741
- }
1742
- else {
1743
- state = foldContextProjection(read.inspection.events, read.inspection.inheritedEventCount, sessionId);
1744
- sourceEvents = read.inspection.events;
1745
- sourceSessionId = sessionId;
1746
- sessionId = read.inspection.header.parentSession;
1747
- }
1986
+ else if (affectedThreads.length !== 1) {
1987
+ restorable = false;
1988
+ reason = affectedThreads.length === 0
1989
+ ? 'no single Thread is attributable to this boundary'
1990
+ : 'multiple Threads entered the context through this boundary; write a fresh handoff instead';
1748
1991
  }
1749
- if (state === undefined)
1750
- break;
1751
- // Price every candidate of this source against the SOURCE's own
1752
- // replayed measurement — a small current generation never shrinks a
1753
- // large ancestor's real seed cost. An unmeasurable source (no meter,
1754
- // unreadable ancestor) prices as UNKNOWN, never as zero: the timeline
1755
- // marks its candidates unprovable and the return guard rejects them.
1756
- const sourceUsage = await this.sourceUsageTokens(sourceSessionId, sourceIsCurrent, agent);
1757
- for (const candidate of timelineCandidates(state, limit)) {
1758
- if (seen.has(candidate.ref))
1759
- continue;
1760
- seen.add(candidate.ref);
1761
- items.push(this.timelineItemFor(candidate, sourceUsage, usageTokens, hardLimit, handoffAt, sourceSessionId, sourceIsCurrent, sourceEvents));
1762
- if (items.length >= limit)
1763
- break;
1992
+ else if (item.retainedTokens >= handoffAt) {
1993
+ restorable = false;
1994
+ reason = 'retained context would not materially shrink the working set';
1995
+ }
1996
+ else {
1997
+ restorable = true;
1998
+ reason = undefined;
1764
1999
  }
1765
- if (items.length >= limit)
1766
- break;
1767
2000
  }
1768
- return { usageTokens, hardLimit, handoffAt, items, ...(incompleteFrom === undefined ? {} : { incompleteFrom }) };
2001
+ return {
2002
+ checkpointRef: item.ref,
2003
+ name: item.label,
2004
+ source,
2005
+ retainedTokens: item.retainedTokens,
2006
+ discardedTokens: item.discardedTokens,
2007
+ affectedThreads,
2008
+ restorable,
2009
+ ...(reason === undefined ? {} : { reason }),
2010
+ // Team's field semantics, unchanged: every item that is not a
2011
+ // checkpoint names the Session it anchors in — an ancestor generation's
2012
+ // boundary is how a lineage reads — and a checkpoint is keyed to its own
2013
+ // Session by its ref already.
2014
+ ...(source === 'agent' ? {} : { sourceSessionId: item.sourceSessionId ?? currentSessionId }),
2015
+ };
1769
2016
  }
1770
2017
  /**
1771
2018
  * Replayed measurement of one lineage source: the live current Session
1772
- * measures directly; an archived ancestor measures through a borrowed
1773
- * prepared Session, so a seed's retained cost is priced in the SOURCE's
1774
- * own tokens — never the current generation's. Returns undefined when no
1775
- * meter is available or the source cannot be borrowed; callers fail
1776
- * closed on the unknown.
2019
+ * measures directly; an archived ancestor measures through a detached
2020
+ * Session rebuilt from the source's own log, so a seed's retained cost is
2021
+ * priced in the SOURCE's own tokens — never the current generation's.
2022
+ * Returns undefined when no meter is available or the source cannot be
2023
+ * replayed; callers fail closed on the unknown.
1777
2024
  */
1778
2025
  async sourceUsageTokens(sessionId, live, agent) {
1779
2026
  const meter = agent.ctx.get('tokenMeter');
@@ -1788,14 +2035,50 @@ let AgentTeam = (() => {
1788
2035
  const read = await this.sessionReader.read(sessionId);
1789
2036
  if (!read.ok)
1790
2037
  return undefined;
2038
+ return this.measureDetachedSource(agent, {
2039
+ sessionId,
2040
+ header: read.inspection.header,
2041
+ inheritedEventCount: read.inspection.inheritedEventCount,
2042
+ events: read.inspection.events,
2043
+ });
2044
+ }
2045
+ /**
2046
+ * One already-read source's replayed measurement: an archived generation is
2047
+ * rebuilt as a detached Session and measured in its own tokens. Undefined
2048
+ * means unmeasurable, never free.
2049
+ */
2050
+ measureDetachedSource(agent, source) {
2051
+ const meter = agent.ctx.get('tokenMeter');
2052
+ if (meter === undefined)
2053
+ return undefined;
1791
2054
  try {
1792
- const session = Session.create(sessionId, read.inspection.events, read.inspection.header, read.inspection.inheritedEventCount);
2055
+ const session = Session.create(source.sessionId, source.events, source.header, source.inheritedEventCount);
1793
2056
  return meter.measure(session)?.totalTokens;
1794
2057
  }
1795
2058
  catch {
1796
2059
  return undefined;
1797
2060
  }
1798
2061
  }
2062
+ /**
2063
+ * One source's replayed measurement, in that source's own tokens, for a
2064
+ * caller that already holds the source: the live generation measures
2065
+ * directly, an archived one is rebuilt from the log it came with. Undefined
2066
+ * means unmeasurable — a caller must never price an unknown source as free.
2067
+ */
2068
+ measureContextSourceForAgent(agent, source) {
2069
+ if (String(source.sessionId) === String(agent.session.id)) {
2070
+ return agent.ctx.get('tokenMeter')?.measure(agent.session)?.totalTokens;
2071
+ }
2072
+ return this.measureDetachedSource(agent, source);
2073
+ }
2074
+ /**
2075
+ * The one fold configuration the registered projection unit and every cold
2076
+ * fold use, so a generation read back for the timeline reads exactly as the
2077
+ * projection folded it — same codec, same boundary attribution.
2078
+ */
2079
+ contextFoldConfig() {
2080
+ return createTeamContextProjectionConfig(this.contextProjectionHost);
2081
+ }
1799
2082
  /**
1800
2083
  * Monotonic anchor-share estimate of a seed's retained cost, priced in the
1801
2084
  * SOURCE Session's own measurement: the fraction of the source log the
@@ -1811,155 +2094,6 @@ let AgentTeam = (() => {
1811
2094
  const share = Math.min(1, Math.max(0, (anchorTurnEndSeq + 1) / sourceLength));
1812
2095
  return Math.round(sourceUsageTokens * share);
1813
2096
  }
1814
- /**
1815
- * Threads whose facts entered this Session's model context by the given
1816
- * seq: delivered Team notices (their bodies quote `Thread: <ref>`
1817
- * structurally) and successful Team-claim mutations (their task overlays
1818
- * resolve to Threads through the ledger). Never from unread ledger
1819
- * activity — a Thread the Member never saw did not enter its context.
1820
- * Order-stable, deduplicated.
1821
- */
1822
- threadsEnteringContext(events, throughSeq) {
1823
- const refs = [];
1824
- const openAttributions = new Map();
1825
- const push = (ref) => {
1826
- if (ref !== undefined && !refs.includes(ref))
1827
- refs.push(ref);
1828
- };
1829
- for (const event of events) {
1830
- if (event.seq > throughSeq)
1831
- break;
1832
- if (event.type === 'tool/call' && (event.data.name === 'team_claim' || event.data.name === 'team_message')) {
1833
- openAttributions.set(event.data.callId, { name: event.data.name, arguments: event.data.arguments });
1834
- }
1835
- else if (event.type === 'tool/result') {
1836
- const result = event.data.message;
1837
- if (result.isError !== true) {
1838
- const recorded = openAttributions.get(result.toolCallId);
1839
- if (recorded !== undefined) {
1840
- openAttributions.delete(result.toolCallId);
1841
- try {
1842
- const args = JSON.parse(recorded.arguments);
1843
- // A claim mutation resolves its Task overlay through the ledger;
1844
- // a team_message committed reply names its Thread in its call
1845
- // arguments; a committed start's Thread is born in the result
1846
- // and is read from the durable presentation meta.
1847
- if (recorded.name === 'team_claim' && typeof args.taskRef === 'string' && args.taskRef !== '') {
1848
- push(this.requireLedger().threadForTask(args.taskRef));
1849
- }
1850
- else if (recorded.name === 'team_message') {
1851
- const meta = event.data.meta;
1852
- if (meta !== undefined && typeof meta === 'object' && meta.kind === 'committed') {
1853
- const metaThreadRef = meta.threadRef;
1854
- if (typeof metaThreadRef === 'string' && metaThreadRef !== '')
1855
- push(metaThreadRef);
1856
- }
1857
- else if (typeof args.threadRef === 'string' && args.threadRef !== '') {
1858
- push(args.threadRef);
1859
- }
1860
- }
1861
- }
1862
- catch {
1863
- // Malformed call arguments contribute no attribution.
1864
- }
1865
- }
1866
- }
1867
- }
1868
- else if (event.type === 'user/message') {
1869
- const source = event.data.source;
1870
- // Only this plugin's own delivered notices attribute Threads, by exact
1871
- // kind identity — the shape written now and the read-time conversion's
1872
- // rename of the released V3 history. A `plugin:` prefix test would
1873
- // claim third-party producers' rows as Team facts.
1874
- if (source === undefined || !isAgentTeamSourceKind(source.kind))
1875
- continue;
1876
- // Reminder notices never enter attribution — a recovery instruction
1877
- // (or a historical progress-nudge notice, kept decodable in session
1878
- // logs recorded before that system was removed) is not a Team fact.
1879
- if (source.form === 'notice' && source.summary !== undefined && isReminderNoticeSummary(source.summary))
1880
- continue;
1881
- const text = event.data.content.filter(block => block.type === 'text').map(block => block.text ?? '').join('\n');
1882
- for (const match of text.matchAll(/Thread: (thread:[0-9a-f-]{6,})/g)) {
1883
- push(match[1]);
1884
- }
1885
- }
1886
- }
1887
- return refs;
1888
- }
1889
- /**
1890
- * Price and annotate one timeline candidate without mutating anything.
1891
- * Retained prices in the SOURCE Session's own replayed measurement (the
1892
- * monotonic anchor-share of the source log); discarded is what a return
1893
- * replaces — for a current-generation anchor, the measured usage beyond
1894
- * the anchor; for an ancestor anchor, the current generation's whole
1895
- * usage (an approximation: the ancestor's own suffix is not part of this
1896
- * generation). A small current child therefore discards little but may
1897
- * still retain a large ancestor seed, and both numbers say so honestly.
1898
- */
1899
- timelineItemFor(candidate, sourceUsage, currentUsage, _hardLimit, handoffAt, sourceSessionId, sourceIsCurrent, sourceEvents) {
1900
- const retainedTokens = candidate.source === 'head'
1901
- ? currentUsage
1902
- : this.retainedEstimate(sourceUsage ?? 0, sourceEvents.length, candidate.turnEndSeq);
1903
- const discardedTokens = candidate.source === 'head'
1904
- ? 0
1905
- : sourceIsCurrent
1906
- ? Math.max(0, currentUsage - retainedTokens)
1907
- : currentUsage;
1908
- const affectedThreads = candidate.turnEndSeq === -1 ? [] : this.threadsEnteringContext(sourceEvents, candidate.turnEndSeq);
1909
- let restorable = candidate.turnEndSeq !== -1;
1910
- let reason;
1911
- if (candidate.source === 'head') {
1912
- // The head is the current working set: returning to it discards
1913
- // nothing and is never a meaningful return target.
1914
- restorable = false;
1915
- reason = 'the head is the current working set; returning to it discards nothing';
1916
- }
1917
- else if (sourceUsage === undefined) {
1918
- // The source's cost cannot be measured (no meter, or the archived
1919
- // ancestor cannot be borrowed): the budget cannot be proven, so the
1920
- // candidate is not selectable. Never price an unknown as zero.
1921
- restorable = false;
1922
- reason = 'the source Session\'s context cost cannot be measured, so the return budget cannot be proven';
1923
- }
1924
- else if (candidate.source === 'handoff' || candidate.source === 'compaction') {
1925
- // A handoff starts a generation and a compaction rewrites the visible
1926
- // surface: rewinding into them is not a proven-safe V1 target.
1927
- restorable = false;
1928
- reason = `source '${candidate.source}' is not a restorable checkpoint`;
1929
- }
1930
- else if (candidate.source === 'team-boundary') {
1931
- // A Team delivery is a selectable default checkpoint exactly when the
1932
- // Host can prove it stays inside one Thread's context: the boundary
1933
- // resolved at a completed turn AND exactly one Thread's facts entered
1934
- // the Session context through it. Multi-Thread or unattributable
1935
- // boundaries document why they are not selectable.
1936
- if (affectedThreads.length !== 1) {
1937
- restorable = false;
1938
- reason = affectedThreads.length === 0
1939
- ? 'no single Thread is attributable to this boundary'
1940
- : 'multiple Threads entered the context through this boundary; write a fresh handoff instead';
1941
- }
1942
- else if (retainedTokens >= handoffAt) {
1943
- restorable = false;
1944
- reason = 'retained context would not materially shrink the working set';
1945
- }
1946
- }
1947
- else if (candidate.source === 'agent' && retainedTokens >= handoffAt) {
1948
- restorable = false;
1949
- reason = 'retained context would be at or above the handoff budget';
1950
- }
1951
- return {
1952
- checkpointRef: candidate.ref,
1953
- name: candidate.label,
1954
- source: candidate.source,
1955
- retainedTokens,
1956
- discardedTokens,
1957
- affectedThreads,
1958
- restorable,
1959
- ...(reason === undefined ? {} : { reason }),
1960
- ...(candidate.source === 'agent' ? {} : { sourceSessionId }),
1961
- };
1962
- }
1963
2097
  /**
1964
2098
  * Agent-only rollover request validation: the tool calls this to check its
1965
2099
  * Member binding, exclusivity, and checkpoint ownership. It performs no
@@ -2284,7 +2418,8 @@ let AgentTeam = (() => {
2284
2418
  // old Session; the projection still carries the intent, so finish the
2285
2419
  // transition (or keep waiting for the containing turn) from here.
2286
2420
  if (persisted) {
2287
- const state = foldContextProjection(created.agent.session.ownEvents(), created.agent.session.inheritedEventCount, created.agent.session.id);
2421
+ const inheritedEventCount = created.agent.session.inheritedEventCount;
2422
+ const state = foldTeamContextProjection(created.agent.session.ownEvents(), { sessionId: created.agent.session.id, inheritedEventCount }, this.contextProjectionHost);
2288
2423
  if (state.pending !== null)
2289
2424
  this.contextManagement.recoverPendingTransition(member.memberId, created.agent, member.sessionId);
2290
2425
  // A restart between one checkpoint's durable result and its quiet
@@ -2295,11 +2430,12 @@ let AgentTeam = (() => {
2295
2430
  // delivered activates the new Session with no handoff in its own
2296
2431
  // log — never treat that as an ordinary blank Member Session. The
2297
2432
  // operation's recorded previous Session (the lineage parent) still
2298
- // holds the intent; rebuild the handoff from it. Presence is judged
2299
- // by the projection boundary alone: every handoff this Host ever
2300
- // published classifies under the context source's recognizer,
2301
- // including the history the read-time conversion renamed.
2302
- if (!state.boundaries.some(boundary => boundary.source === 'handoff')) {
2433
+ // holds the intent; rebuild the handoff from it. "No handoff in its
2434
+ // own log" is judged over both shapes: a generation rescued from the
2435
+ // retired custom kinds carries its handoff as a source the projection
2436
+ // does not classify, and rebuilding on top of it would inject the same
2437
+ // handoff twice.
2438
+ if (!handoffAlreadyInLog(state.boundaries, created.agent.session.ownEvents())) {
2303
2439
  await this.reconstructMissingHandoff(member, created.agent);
2304
2440
  }
2305
2441
  // Carried input redelivery binds to the committed transition target —
@@ -2473,6 +2609,14 @@ let AgentTeam = (() => {
2473
2609
  }
2474
2610
  return { class: 'activation', detail: error instanceof Error ? error.message : String(error) };
2475
2611
  }
2612
+ /** Mark a session-refused activation diagnostic as proven non-remediable. */
2613
+ markRefusalNonRemediable(memberId) {
2614
+ const failures = this.memberFailures.get(memberId);
2615
+ const activation = failures?.activation;
2616
+ if (activation === undefined || activation.class !== 'session-refused')
2617
+ return;
2618
+ failures.activation = Object.freeze({ ...activation, remediable: false });
2619
+ }
2476
2620
  clearMemberFailure(memberId, slot) {
2477
2621
  const failures = this.memberFailures.get(memberId);
2478
2622
  if (failures === undefined || failures[slot] === undefined)
@@ -2513,7 +2657,7 @@ let AgentTeam = (() => {
2513
2657
  const path = this.ctx.workspaceRegistry.get(operation.data.workspaceId)?.path;
2514
2658
  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.`;
2515
2659
  const notice = createUserMessage({ content: [{ type: 'text', text }],
2516
- source: { kind: AGENT_TEAM_PLUGIN_ID, form: 'notice', summary: 'Team Workspace participation changed' } });
2660
+ source: { kind: 'plugin', plugin: AGENT_TEAM_PLUGIN_ID, form: 'notice', summary: 'Team Workspace participation changed' } });
2517
2661
  try {
2518
2662
  if (agent.status === 'idle' || agent.inbox.nextTurn.some(message => message.source.kind === 'user'))
2519
2663
  agent.followup(notice);
@@ -2610,7 +2754,7 @@ let AgentTeam = (() => {
2610
2754
  agent.inbox.remove(existingInboxHint.id);
2611
2755
  const hint = createUserMessage({
2612
2756
  content: [{ type: 'text', text: this.notificationText(notifications, member.memberId) }],
2613
- source: { kind: AGENT_TEAM_PLUGIN_ID, form: 'notice', summary: INBOX_NOTICE_SUMMARY },
2757
+ source: { kind: 'plugin', plugin: AGENT_TEAM_PLUGIN_ID, form: 'notice', summary: INBOX_NOTICE_SUMMARY },
2614
2758
  });
2615
2759
  this.notifiedInbox.set(member.memberId, signature);
2616
2760
  try {
@@ -2631,12 +2775,12 @@ let AgentTeam = (() => {
2631
2775
  }
2632
2776
  isInboxNotice(message) {
2633
2777
  const source = message.source;
2634
- return isAgentTeamSource(source)
2778
+ return source.kind === 'plugin' && source.plugin === AGENT_TEAM_PLUGIN_ID
2635
2779
  && source.form === 'notice' && source.summary === INBOX_NOTICE_SUMMARY;
2636
2780
  }
2637
2781
  isRecoveryNotice(message) {
2638
2782
  const source = message.source;
2639
- return isAgentTeamSource(source)
2783
+ return source.kind === 'plugin' && source.plugin === AGENT_TEAM_PLUGIN_ID
2640
2784
  && source.form === 'notice' && source.summary === RECOVERY_NOTICE_SUMMARY;
2641
2785
  }
2642
2786
  notificationText(notifications, readerId) {