@wowyuarm/dsh-agent-team 0.1.7 → 0.1.9

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 (104) hide show
  1. package/package.json +2 -1
  2. package/packages/agent-team/README.md +5 -3
  3. package/packages/agent-team/README.zh.md +5 -3
  4. package/packages/agent-team/core-skills/member-skill-manager/SKILL.md +2 -0
  5. package/packages/agent-team/lib/attachments.js +14 -4
  6. package/packages/agent-team/lib/context-management.js +379 -0
  7. package/packages/agent-team/lib/context-projection.js +574 -0
  8. package/packages/agent-team/lib/context-source.js +72 -0
  9. package/packages/agent-team/lib/index.js +1258 -54
  10. package/packages/agent-team/lib/ledger.js +357 -26
  11. package/packages/agent-team/lib/member-context.js +7 -3
  12. package/packages/agent-team/lib/member-runtime.js +159 -4
  13. package/packages/agent-team/lib/pressure-policy.js +200 -0
  14. package/packages/agent-team/lib/progress-nudge.js +320 -0
  15. package/packages/agent-team/lib/spec.js +18 -0
  16. package/packages/agent-team/lib/typert.host.js +124 -42
  17. package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
  18. package/packages/agent-team/lib/typert.remote-client.js +39 -25
  19. package/packages/agent-team/lib/types/attachments.d.ts +1 -1
  20. package/packages/agent-team/lib/types/attachments.d.ts.map +1 -1
  21. package/packages/agent-team/lib/types/context-management.d.ts +142 -0
  22. package/packages/agent-team/lib/types/context-management.d.ts.map +1 -0
  23. package/packages/agent-team/lib/types/context-projection.d.ts +219 -0
  24. package/packages/agent-team/lib/types/context-projection.d.ts.map +1 -0
  25. package/packages/agent-team/lib/types/context-source.d.ts +83 -0
  26. package/packages/agent-team/lib/types/context-source.d.ts.map +1 -0
  27. package/packages/agent-team/lib/types/index.d.ts +283 -2
  28. package/packages/agent-team/lib/types/index.d.ts.map +1 -1
  29. package/packages/agent-team/lib/types/ledger.d.ts +120 -7
  30. package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
  31. package/packages/agent-team/lib/types/member-context.d.ts.map +1 -1
  32. package/packages/agent-team/lib/types/member-runtime.d.ts +30 -2
  33. package/packages/agent-team/lib/types/member-runtime.d.ts.map +1 -1
  34. package/packages/agent-team/lib/types/pressure-policy.d.ts +106 -0
  35. package/packages/agent-team/lib/types/pressure-policy.d.ts.map +1 -0
  36. package/packages/agent-team/lib/types/progress-nudge.d.ts +136 -0
  37. package/packages/agent-team/lib/types/progress-nudge.d.ts.map +1 -0
  38. package/packages/agent-team/lib/types/spec.d.ts.map +1 -1
  39. package/packages/agent-team/lib/types/types/entities.d.ts +32 -1
  40. package/packages/agent-team/lib/types/types/entities.d.ts.map +1 -1
  41. package/packages/agent-team/lib/types/types/operations.d.ts +38 -3
  42. package/packages/agent-team/lib/types/types/operations.d.ts.map +1 -1
  43. package/packages/agent-team/lib/types/types/requests-results.d.ts +54 -1
  44. package/packages/agent-team/lib/types/types/requests-results.d.ts.map +1 -1
  45. package/packages/agent-team/preset/team-member/agent.cordis.yml +10 -2
  46. package/packages/client-agent-team/lib/client.js +283 -398
  47. package/packages/client-agent-team/lib/client.js.map +1 -1
  48. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts +1 -2
  49. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
  50. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +61 -41
  51. package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts.map +1 -1
  52. package/packages/client-agent-team/lib/types/client/TeamChannelPage.js +3 -7
  53. package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts +3 -1
  54. package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts.map +1 -1
  55. package/packages/client-agent-team/lib/types/client/TeamComposer.js +37 -27
  56. package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts +1 -1
  57. package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts.map +1 -1
  58. package/packages/client-agent-team/lib/types/client/TeamConversation.js +2 -2
  59. package/packages/client-agent-team/lib/types/client/TeamMemberAvatar.d.ts.map +1 -1
  60. package/packages/client-agent-team/lib/types/client/TeamMemberAvatar.js +3 -2
  61. package/packages/client-agent-team/lib/types/client/TeamMessage.js +5 -0
  62. package/packages/client-agent-team/lib/types/client/TeamPresenceDot.d.ts +2 -2
  63. package/packages/client-agent-team/lib/types/client/TeamPresenceDot.d.ts.map +1 -1
  64. package/packages/client-agent-team/lib/types/client/TeamPresenceDot.js +4 -6
  65. package/packages/client-agent-team/lib/types/client/TeamStateDot.d.ts +14 -0
  66. package/packages/client-agent-team/lib/types/client/TeamStateDot.d.ts.map +1 -0
  67. package/packages/client-agent-team/lib/types/client/TeamStateDot.js +15 -0
  68. package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts +1 -0
  69. package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -1
  70. package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +41 -16
  71. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts +1 -1
  72. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts.map +1 -1
  73. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.js +2 -2
  74. package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
  75. package/packages/client-agent-team/lib/types/client/index.js +36 -78
  76. package/packages/client-agent-team/lib/types/client/locales.d.ts +0 -16
  77. package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
  78. package/packages/client-agent-team/lib/types/client/locales.js +0 -16
  79. package/packages/client-agent-team/lib/types/client/sidebar-drag.d.ts +1 -2
  80. package/packages/client-agent-team/lib/types/client/sidebar-drag.d.ts.map +1 -1
  81. package/packages/client-agent-team/lib/types/client/slots.d.ts +8 -1
  82. package/packages/client-agent-team/lib/types/client/slots.d.ts.map +1 -1
  83. package/packages/client-agent-team/lib/types/client/task-refs.d.ts +7 -1
  84. package/packages/client-agent-team/lib/types/client/task-refs.d.ts.map +1 -1
  85. package/packages/client-agent-team/lib/types/client/task-refs.js +25 -3
  86. package/packages/client-agent-team/lib/types/client/team-formatters.d.ts +11 -10
  87. package/packages/client-agent-team/lib/types/client/team-formatters.d.ts.map +1 -1
  88. package/packages/client-agent-team/lib/types/client/team-formatters.js +14 -11
  89. package/packages/tool-agent-team/README.md +8 -5
  90. package/packages/tool-agent-team/README.zh.md +8 -5
  91. package/packages/tool-agent-team/lib/context-tools.js +179 -0
  92. package/packages/tool-agent-team/lib/index.js +144 -25
  93. package/packages/tool-agent-team/lib/types/context-tools.d.ts +16 -0
  94. package/packages/tool-agent-team/lib/types/context-tools.d.ts.map +1 -0
  95. package/packages/tool-agent-team/lib/types/index.d.ts.map +1 -1
  96. package/packages/agent-team/lib/auto-compaction.js +0 -201
  97. package/packages/agent-team/lib/types/auto-compaction.d.ts +0 -47
  98. package/packages/agent-team/lib/types/auto-compaction.d.ts.map +0 -1
  99. package/packages/client-agent-team/lib/types/client/TeamMemberDock.d.ts +0 -12
  100. package/packages/client-agent-team/lib/types/client/TeamMemberDock.d.ts.map +0 -1
  101. package/packages/client-agent-team/lib/types/client/TeamMemberDock.js +0 -13
  102. package/packages/client-agent-team/lib/types/client/member-session-input.d.ts +0 -20
  103. package/packages/client-agent-team/lib/types/client/member-session-input.d.ts.map +0 -1
  104. package/packages/client-agent-team/lib/types/client/member-session-input.js +0 -110
@@ -17,8 +17,12 @@ function emptyProjection() {
17
17
  return { byRequest: new Map(), byOperation: new Map(), ordered: [], channels: new Map(), members: new Map(), memberships: new Map(),
18
18
  claims: new Map(), messages: [], tasks: new Map(), threads: new Map(), attention: new Map(), directMarkers: new Map(), activityMarkers: new Map(),
19
19
  orderedFacts: [], factsByThread: new Map(), channelRefByThread: new Map(), mentionsByMessage: new Map(), messageCountByThread: new Map(),
20
- attentionByThread: new Map() };
20
+ attentionByThread: new Map(), previousSessions: new Map(), rolloverSeeds: new Map() };
21
21
  }
22
+ const EMPTY_PROGRESS_NUDGE_TARGETS = Object.freeze({
23
+ progress: Object.freeze([]),
24
+ claim: Object.freeze([]),
25
+ });
22
26
  /** Exhaustiveness guard for the closed operation union: adding a kind must update every dispatch. */
23
27
  function assertUnhandledKind(operation) {
24
28
  throw new Error(`agent-team ledger does not handle operation kind '${operation.kind}'`);
@@ -243,9 +247,211 @@ export class AgentTeamLedger {
243
247
  return this.committed(this.memberResult(operation));
244
248
  });
245
249
  }
250
+ /**
251
+ * Move one enabled Member onto its next context generation as itself. The
252
+ * actor is the calling Member — the Host executes the transition but is
253
+ * never the business actor — and the durable data records only the
254
+ * verifiable envelope (previous/new Session anchors, the successful handoff
255
+ * tool-result seq, checkpoint seed lineage, trigger). The private handoff
256
+ * prose stays in the Member's own Session log. An exact retry resolves to
257
+ * the recorded outcome; the same requestId with different data collides.
258
+ */
259
+ rolloverMemberSession(request) {
260
+ return this.enqueue(async () => {
261
+ const existing = this.state.byRequest.get(request.requestId);
262
+ if (existing !== undefined) {
263
+ this.assertSameMemberSessionRolledOver(existing, request);
264
+ return this.resolved(this.memberResult(existing));
265
+ }
266
+ if (request.actor.kind !== 'member')
267
+ throw new Error('Member session rollover requires Member authority');
268
+ const prior = this.requireMember(request.memberId);
269
+ if (request.actor.memberId !== request.memberId)
270
+ throw new Error(`Agent Member '${prior.handle}' cannot roll over another Member's Session`);
271
+ if (prior.state !== 'enabled')
272
+ throw new Error(`Agent Member '${prior.handle}' is ${prior.state}; only enabled Members can roll over their Session`);
273
+ if (prior.sessionId === request.newSessionId)
274
+ throw new Error(`Agent Member '${prior.handle}' already runs Session '${request.newSessionId}'`);
275
+ if (prior.sessionId !== request.previousSessionId)
276
+ throw new Error(`Agent Member '${prior.handle}' is no longer bound to Session '${request.previousSessionId}'`);
277
+ if ((request.checkpointRef !== undefined) !== (request.sourceSessionId !== undefined))
278
+ throw new Error('rollover checkpoint fields must appear together');
279
+ if (request.sourceSessionId !== undefined && request.sourceThroughSeq === undefined)
280
+ throw new Error('rollover checkpoint seed requires a through sequence');
281
+ if (request.sourceThroughSeq !== undefined && request.sourceSessionId === undefined)
282
+ throw new Error('rollover through sequence requires a source Session');
283
+ const member = Object.freeze({ ...prior, sessionId: request.newSessionId });
284
+ const operation = Object.freeze({
285
+ ...this.operationBase(request, this.nextSequence()), kind: 'team/member-session-rolled-over',
286
+ data: Object.freeze({
287
+ member,
288
+ previousSessionId: request.previousSessionId,
289
+ newSessionId: request.newSessionId,
290
+ handoffEventSeq: request.handoffEventSeq,
291
+ trigger: request.trigger,
292
+ ...(request.sourceSessionId === undefined ? {} : { sourceSessionId: request.sourceSessionId }),
293
+ ...(request.sourceThroughSeq === undefined ? {} : { sourceThroughSeq: request.sourceThroughSeq }),
294
+ ...(request.checkpointRef === undefined ? {} : { checkpointRef: request.checkpointRef }),
295
+ }),
296
+ });
297
+ await this.table.put(operation.operationId, operation);
298
+ this.apply(operation);
299
+ return this.committed(this.memberResult(operation));
300
+ });
301
+ }
246
302
  getMember(memberId) {
247
303
  return this.state.members.get(memberId);
248
304
  }
305
+ /**
306
+ * The retired Session this Member most recently left through a renewal or
307
+ * rollover — the lineage parent a crash between the durable binding commit
308
+ * and the new Session's activation must reconstruct a handoff from. Read
309
+ * from the audit projection; the Member record itself only names the
310
+ * current Session.
311
+ */
312
+ previousSessionForMember(memberId) {
313
+ return this.state.previousSessions.get(memberId);
314
+ }
315
+ /**
316
+ * The latest session transition this Member committed: the retired Session
317
+ * and the target it moved onto, through either a renewal or a rollover.
318
+ * Crash recovery binds carried-input redelivery to the recorded target —
319
+ * the CURRENT Session being that target proves the old generation's
320
+ * unconsumed input still belongs here, regardless of whether the handoff
321
+ * itself already landed.
322
+ */
323
+ lastTransitionForMember(memberId) {
324
+ const previousSessionId = this.state.previousSessions.get(memberId);
325
+ const member = this.state.members.get(memberId);
326
+ if (previousSessionId === undefined || member === undefined)
327
+ return undefined;
328
+ return { previousSessionId, targetSessionId: member.sessionId };
329
+ }
330
+ /**
331
+ * The Thread a Task overlay lives on, or undefined for an unknown or
332
+ * archived-Channel Task. Read-only input for attributing claim-mutation
333
+ * boundaries to the Thread whose context they entered.
334
+ */
335
+ threadForTask(taskRef) {
336
+ const task = this.state.tasks.get(taskRef);
337
+ if (task === undefined)
338
+ return undefined;
339
+ return this.state.channels.get(task.channelRef)?.state === 'archived' ? undefined : task.threadRef;
340
+ }
341
+ /**
342
+ * The seed envelope of this Member's most recent rollover, when that
343
+ * rollover was a checkpoint return: the recorded source Session, the
344
+ * exclusive end of its seeded prefix, and the checkpoint ref. A fresh
345
+ * rollover clears the envelope. Crash recovery reads this to rebuild the
346
+ * child generation from the recorded seed instead of an empty context.
347
+ */
348
+ rolloverSeedForMember(memberId, targetSessionId) {
349
+ const seed = this.state.rolloverSeeds.get(memberId);
350
+ // Bind the envelope to its recorded target: a later renewal or fresh
351
+ // rollover that reused or replaced the binding must never consume an
352
+ // older generation's seed.
353
+ return seed?.targetSessionId === targetSessionId ? seed : undefined;
354
+ }
355
+ /**
356
+ * Count one Member's active Claims on open Tasks in active Channels — the
357
+ * read-only input to the checkpoint single-Thread coverage guard: with
358
+ * more than one, a rewind cannot be proven to stay inside one Thread.
359
+ */
360
+ activeClaimCountForMember(memberId) {
361
+ return this.activeClaimsForMember(memberId).length;
362
+ }
363
+ /**
364
+ * One Member's active Claims on open Tasks in active Channels, oldest
365
+ * first — the read-only input for the pressure notice's Claim labels.
366
+ */
367
+ activeClaimsForMember(memberId) {
368
+ const active = [];
369
+ for (const claim of this.state.claims.values()) {
370
+ if (claim.owner !== memberId || claim.state !== 'active')
371
+ continue;
372
+ const task = this.state.tasks.get(claim.taskRef);
373
+ if (task === undefined || task.resolution !== 'open')
374
+ continue;
375
+ if (this.state.channels.get(task.channelRef)?.state === 'archived')
376
+ continue;
377
+ active.push(claim);
378
+ }
379
+ return active.sort((a, b) => a.claimRef.localeCompare(b.claimRef));
380
+ }
381
+ /**
382
+ * Current progress-nudge candidacy for one Member: threads whose Thread
383
+ * progress reminder applies (active Claim, or following a taskless Thread)
384
+ * and tasks where the Claim reminder applies (following a still-`todo` Task
385
+ * with no Claim history for this Member). A deep read-only projection — the
386
+ * caller never re-derives attention, claim, task-resolution, or channel
387
+ * archive state. Archived channels and resolved tasks produce no candidacy;
388
+ * results are ordered by Thread revision descending, then threadRef.
389
+ */
390
+ progressNudgeTargets(memberId) {
391
+ const member = this.state.members.get(memberId);
392
+ if (member === undefined || member.state !== 'enabled')
393
+ return EMPTY_PROGRESS_NUDGE_TARGETS;
394
+ const progress = [];
395
+ const claim = [];
396
+ const seenProgress = new Set();
397
+ for (const claimRecord of this.state.claims.values()) {
398
+ if (claimRecord.owner !== memberId || claimRecord.state !== 'active')
399
+ continue;
400
+ const task = this.state.tasks.get(claimRecord.taskRef);
401
+ const thread = this.state.threads.get(claimRecord.threadRef);
402
+ if (task === undefined || thread === undefined || task.resolution !== 'open')
403
+ continue;
404
+ if (this.state.channels.get(task.channelRef)?.state === 'archived')
405
+ continue;
406
+ if (seenProgress.has(claimRecord.threadRef))
407
+ continue;
408
+ seenProgress.add(claimRecord.threadRef);
409
+ progress.push({ reason: 'active-claim', threadRef: claimRecord.threadRef, taskRef: claimRecord.taskRef });
410
+ }
411
+ for (const [threadRef, followers] of this.state.attentionByThread) {
412
+ if (!followers.has(memberId))
413
+ continue;
414
+ const thread = this.state.threads.get(threadRef);
415
+ if (thread === undefined)
416
+ continue;
417
+ const channelRef = this.state.channelRefByThread.get(threadRef);
418
+ if (channelRef === undefined || this.state.channels.get(channelRef)?.state === 'archived')
419
+ continue;
420
+ if (thread.taskRef === undefined) {
421
+ if (!seenProgress.has(threadRef)) {
422
+ seenProgress.add(threadRef);
423
+ progress.push({ reason: 'taskless-follower', threadRef });
424
+ }
425
+ continue;
426
+ }
427
+ // Claim reminder: only still-`todo` tasks with zero lifetime claims by
428
+ // this Member; once claimed (any state) the Member moved into the
429
+ // progress-reminder regime.
430
+ if (this.hasLifetimeClaimOnTask(memberId, thread.taskRef))
431
+ continue;
432
+ const task = this.state.tasks.get(thread.taskRef);
433
+ if (task === undefined || task.resolution !== 'open' || this.deriveTaskStatus(task.taskRef, this.state.claims.values()) !== 'todo')
434
+ continue;
435
+ claim.push({ threadRef, taskRef: thread.taskRef });
436
+ }
437
+ const byThreadRevision = (left, right) => {
438
+ const leftRevision = this.state.threads.get(left.threadRef)?.revision ?? 0;
439
+ const rightRevision = this.state.threads.get(right.threadRef)?.revision ?? 0;
440
+ return rightRevision - leftRevision || (left.threadRef < right.threadRef ? -1 : left.threadRef > right.threadRef ? 1 : 0);
441
+ };
442
+ return Object.freeze({
443
+ progress: Object.freeze(progress.sort(byThreadRevision)),
444
+ claim: Object.freeze(claim.sort(byThreadRevision)),
445
+ });
446
+ }
447
+ /** Whether this Member has ever held a Claim on the Task, in any state. */
448
+ hasLifetimeClaimOnTask(memberId, taskRef) {
449
+ for (const claim of this.state.claims.values()) {
450
+ if (claim.owner === memberId && claim.taskRef === taskRef)
451
+ return true;
452
+ }
453
+ return false;
454
+ }
249
455
  listMembers() {
250
456
  return Object.freeze([...this.state.members.values()]);
251
457
  }
@@ -518,7 +724,7 @@ export class AgentTeamLedger {
518
724
  else {
519
725
  if (request.direction !== undefined)
520
726
  throw new Error(`${request.action} action does not accept direction`);
521
- const previous = request.claimRef === undefined ? undefined : this.state.claims.get(request.claimRef);
727
+ const previous = request.claimRef === undefined ? undefined : this.state.claims.get(this.requireRefKey(this.state.claims, request.claimRef, 'claim', 'Claim'));
522
728
  if (previous === undefined) {
523
729
  throw new Error(`unknown Claim '${request.claimRef ?? ''}'${request.claimRef === undefined ? '' : this.unknownRefHint(request.claimRef, 'claim', 'Claim')}`);
524
730
  }
@@ -588,6 +794,15 @@ export class AgentTeamLedger {
588
794
  : claim);
589
795
  const releasedClaims = claims.filter(claim => claim.state === 'released' && this.state.claims.get(claim.claimRef)?.state === 'active');
590
796
  const completedClaims = claims.filter(claim => claim.state === 'done' && this.state.claims.get(claim.claimRef)?.state === 'active');
797
+ // Every done Claim at commit time — pre-finished and atomically
798
+ // completed alike — is the acceptance discriminator: the one list
799
+ // accept markers deliver from, so a normal accept no longer stays
800
+ // silent about the Members whose work it accepted. Sorted lexically
801
+ // so replay validation compares stably regardless of claim-ref
802
+ // generation order.
803
+ const acceptedClaims = request.action === 'accept'
804
+ ? claims.filter(claim => claim.state === 'done').sort((left, right) => left.claimRef.localeCompare(right.claimRef))
805
+ : [];
591
806
  const resolution = request.action === 'reopen' ? 'open' : request.action === 'accept' ? 'accepted' : 'closed';
592
807
  const status = resolution === 'accepted' ? 'done' : resolution === 'closed' ? 'closed'
593
808
  : this.deriveTaskStatus(task.taskRef, claims);
@@ -596,12 +811,13 @@ export class AgentTeamLedger {
596
811
  const activity = Object.freeze({ activityRef: this.ref('activity'), kind: request.action,
597
812
  taskRef: task.taskRef, threadRef: task.threadRef, actor: request.actor.memberId, sequence,
598
813
  ...(releasedClaims.length === 0 ? {} : { releasedClaimRefs: Object.freeze(releasedClaims.map(claim => claim.claimRef)) }),
599
- ...(completedClaims.length === 0 ? {} : { completedClaimRefs: Object.freeze(completedClaims.map(claim => claim.claimRef)) }) });
814
+ ...(completedClaims.length === 0 ? {} : { completedClaimRefs: Object.freeze(completedClaims.map(claim => claim.claimRef)) }),
815
+ ...(request.action === 'accept' ? { acceptedClaimRefs: Object.freeze(acceptedClaims.map(claim => claim.claimRef)) } : {}) });
600
816
  const inbox = request.action === 'close'
601
817
  ? this.closeThreadInbox(activity, thread.threadRef)
602
818
  : request.action === 'reopen'
603
819
  ? this.reopenThreadInbox(activity, thread.threadRef)
604
- : this.acceptThreadInbox(activity, thread.threadRef, completedClaims.map(claim => claim.owner));
820
+ : this.acceptThreadInbox(activity, thread.threadRef, acceptedClaims.map(claim => claim.owner));
605
821
  const operation = Object.freeze({
606
822
  ...this.operationBase(request, sequence), kind: 'team/task-changed',
607
823
  data: Object.freeze({ workspaceId: request.workspaceId, baseRevision: request.baseRevision,
@@ -897,7 +1113,7 @@ export class AgentTeamLedger {
897
1113
  memberId: next.memberId, action: 'follow' }));
898
1114
  }
899
1115
  }
900
- return Object.freeze({ items: Object.freeze(observations.slice(-limit)) });
1116
+ return Object.freeze({ items: Object.freeze(observations.slice(-limit)), followers: Object.freeze([...current.keys()]) });
901
1117
  }
902
1118
  /** Every operation carrying an inbox delta drives the Inbox projection; the payload shape decides, not a per-kind list. */
903
1119
  attentionDelta(operation) {
@@ -946,7 +1162,8 @@ export class AgentTeamLedger {
946
1162
  const numbers = this.taskNumbers(workspaceId);
947
1163
  const resolved = [];
948
1164
  for (const taskRef of taskRefs) {
949
- const task = this.state.tasks.get(taskRef);
1165
+ const key = this.uniqueRefKey(this.state.tasks, taskRef, 'task');
1166
+ const task = key === undefined ? undefined : this.state.tasks.get(key);
950
1167
  const channel = task === undefined ? undefined : this.state.channels.get(task.channelRef);
951
1168
  // Archived Channels do not exist on Team API surfaces: their Tasks stop
952
1169
  // resolving, so live message bodies render the refs as plain
@@ -1076,6 +1293,7 @@ export class AgentTeamLedger {
1076
1293
  case 'team/member-session-restarted':
1077
1294
  case 'team/member-context-cleared':
1078
1295
  case 'team/member-session-renewed':
1296
+ case 'team/member-session-rolled-over':
1079
1297
  case 'team/member-updated':
1080
1298
  case 'team/member-removed':
1081
1299
  return [{ kind: 'workspace', workspaceId: operation.data.member.workspaceId }];
@@ -1196,6 +1414,7 @@ export class AgentTeamLedger {
1196
1414
  case 'team/member-session-restarted':
1197
1415
  case 'team/member-context-cleared':
1198
1416
  case 'team/member-session-renewed':
1417
+ case 'team/member-session-rolled-over':
1199
1418
  case 'team/member-updated':
1200
1419
  case 'team/channel-member-added':
1201
1420
  case 'team/thread-read':
@@ -1328,6 +1547,29 @@ export class AgentTeamLedger {
1328
1547
  throw new Error('invalid Member session renewal');
1329
1548
  return;
1330
1549
  }
1550
+ if (operation.kind === 'team/member-session-rolled-over') {
1551
+ // The rollover's business actor is the calling Member itself; the Host
1552
+ // only executes the recorded transition. Authorization is self-scoped:
1553
+ // the actor must be the enabled target Member on its currently bound
1554
+ // Session, and the recorded transition moves exactly the sessionId.
1555
+ const actorMember = assertMember();
1556
+ const prior = projection.members.get(operation.data.member.memberId);
1557
+ if (actorMember.memberId !== operation.data.member.memberId)
1558
+ throw new Error('rolled-over Member must match its actor');
1559
+ if (prior === undefined || prior.state !== 'enabled'
1560
+ || prior.sessionId !== operation.data.previousSessionId
1561
+ || operation.data.member.sessionId !== operation.data.newSessionId
1562
+ || operation.data.member.sessionId === operation.data.previousSessionId
1563
+ || !this.sameMemberFacts(prior, operation.data.member))
1564
+ throw new Error('invalid Member session rollover');
1565
+ if (operation.data.sourceSessionId !== undefined && operation.data.sourceThroughSeq === undefined)
1566
+ throw new Error('rolled-over checkpoint seed requires a through sequence');
1567
+ if (operation.data.sourceThroughSeq !== undefined && operation.data.sourceSessionId === undefined)
1568
+ throw new Error('rolled-over through sequence requires a source Session');
1569
+ if ((operation.data.checkpointRef !== undefined) !== (operation.data.sourceSessionId !== undefined))
1570
+ throw new Error('rolled-over checkpoint fields must appear together');
1571
+ return;
1572
+ }
1331
1573
  if (operation.kind === 'team/channel-updated') {
1332
1574
  assertHuman();
1333
1575
  const prior = projection.channels.get(operation.data.channel.channelRef);
@@ -1561,13 +1803,24 @@ export class AgentTeamLedger {
1561
1803
  const expectedCompletedClaimRefs = operation.data.claims
1562
1804
  .filter(claim => projection.claims.get(claim.claimRef)?.state === 'active' && claim.state === 'done')
1563
1805
  .map(claim => claim.claimRef);
1806
+ // New-shape accepts carry the acceptance discriminator: every done
1807
+ // Claim at commit time, sorted. Legacy accepts predate it; the
1808
+ // recorded wake semantics for them stay completed-only (the old rule)
1809
+ // so every existing ledger replays unchanged.
1810
+ const expectedAcceptedClaimRefs = operation.data.claims
1811
+ .filter(claim => claim.state === 'done')
1812
+ .map(claim => claim.claimRef)
1813
+ .sort((left, right) => left.localeCompare(right));
1564
1814
  if (task.resolution !== expectedResolution || task.status !== expectedStatus
1565
1815
  || (activity.kind !== 'close' && activity.releasedClaimRefs !== undefined)
1566
1816
  || (activity.kind !== 'accept' && activity.completedClaimRefs !== undefined)
1817
+ || (activity.kind !== 'accept' && activity.acceptedClaimRefs !== undefined)
1567
1818
  || (activity.kind === 'close' && !this.sameList(activity.releasedClaimRefs ?? [], operation.data.claims
1568
1819
  .filter(claim => projection.claims.get(claim.claimRef)?.state === 'active' && claim.state === 'released')
1569
1820
  .map(claim => claim.claimRef)))
1570
- || !this.sameList(activity.completedClaimRefs ?? [], expectedCompletedClaimRefs)) {
1821
+ || !this.sameList(activity.completedClaimRefs ?? [], expectedCompletedClaimRefs)
1822
+ || (activity.kind === 'accept' && activity.acceptedClaimRefs !== undefined
1823
+ && !this.sameList(activity.acceptedClaimRefs, expectedAcceptedClaimRefs))) {
1571
1824
  throw new Error('invalid Task state transition');
1572
1825
  }
1573
1826
  const priorClaims = [...projection.claims.values()].filter(claim => claim.taskRef === task.taskRef).sort((left, right) => left.claimRef.localeCompare(right.claimRef));
@@ -1591,7 +1844,10 @@ export class AgentTeamLedger {
1591
1844
  ? this.closeThreadInboxFrom(projection, activity, thread.threadRef)
1592
1845
  : activity.kind === 'reopen'
1593
1846
  ? this.reopenThreadInboxFrom(projection, activity, thread.threadRef)
1594
- : this.acceptThreadInboxFrom(projection, activity, thread.threadRef, expectedCompletedClaimRefs);
1847
+ // Presence of the discriminator picks the wake rule: new accepts
1848
+ // wake every done owner; legacy records keep the completed-only
1849
+ // delta exactly as recorded.
1850
+ : this.acceptThreadInboxFrom(projection, activity, thread.threadRef, activity.acceptedClaimRefs !== undefined ? activity.acceptedClaimRefs : expectedCompletedClaimRefs);
1595
1851
  if (!isDeepStrictEqual(operation.data.inbox, expectedInbox))
1596
1852
  throw new Error('invalid Task inbox projection');
1597
1853
  this.validateInboxDelta(operation.data.inbox, projection, refs, [], [], [activity]);
@@ -1859,6 +2115,25 @@ export class AgentTeamLedger {
1859
2115
  }
1860
2116
  if (operation.kind === 'team/member-session-renewed') {
1861
2117
  target.members.set(operation.data.member.memberId, operation.data.member);
2118
+ target.previousSessions.set(operation.data.member.memberId, operation.data.previousSessionId);
2119
+ // A Human-side renewal is always a fresh start: a stale checkpoint
2120
+ // seed from an earlier rollover must never re-seed it.
2121
+ target.rolloverSeeds.delete(operation.data.member.memberId);
2122
+ return;
2123
+ }
2124
+ if (operation.kind === 'team/member-session-rolled-over') {
2125
+ target.members.set(operation.data.member.memberId, operation.data.member);
2126
+ target.previousSessions.set(operation.data.member.memberId, operation.data.previousSessionId);
2127
+ // A checkpoint return records its seed envelope: a crash between this
2128
+ // commit and the new Session's activation rebuilds the child from the
2129
+ // recorded source instead of an empty context.
2130
+ const { sourceSessionId, sourceThroughSeq, checkpointRef } = operation.data;
2131
+ if (sourceSessionId !== undefined && sourceThroughSeq !== undefined && checkpointRef !== undefined) {
2132
+ target.rolloverSeeds.set(operation.data.member.memberId, { targetSessionId: operation.data.newSessionId, sourceSessionId, sourceThroughSeq, checkpointRef });
2133
+ }
2134
+ else {
2135
+ target.rolloverSeeds.delete(operation.data.member.memberId);
2136
+ }
1862
2137
  return;
1863
2138
  }
1864
2139
  if (operation.kind === 'team/channel-updated') {
@@ -2137,22 +2412,24 @@ export class AgentTeamLedger {
2137
2412
  return this.inboxDelta([], removed, [], [...projection.directMarkers.values()].filter(marker => marker.threadRef === threadRef), activityMarkers, [...projection.activityMarkers.values()].filter(marker => marker.threadRef === threadRef));
2138
2413
  }
2139
2414
  /**
2140
- * Early acceptance completes open Claims inside the accept operation; their
2141
- * owners learn about it through activity markers regardless of whether they
2142
- * still follow the Thread. Plain accepts (no completed Claims) stay silent.
2415
+ * Acceptance notifies every done Claim owner pre-finished (the normal
2416
+ * flow) and atomically completed (early acceptance) alike through
2417
+ * activity markers regardless of whether they still follow the Thread.
2418
+ * The actor never notifies itself. Legacy records without the acceptance
2419
+ * discriminator keep the old completed-only wake semantics on replay.
2143
2420
  */
2144
- acceptThreadInbox(activity, threadRef, completedOwners) {
2145
- if (completedOwners.length === 0)
2421
+ acceptThreadInbox(activity, threadRef, acceptedOwners) {
2422
+ if (acceptedOwners.length === 0)
2146
2423
  return this.inboxDelta();
2147
- const recipients = [...new Set(completedOwners)].filter(memberId => memberId !== activity.actor);
2424
+ const recipients = [...new Set(acceptedOwners)].filter(memberId => memberId !== activity.actor);
2148
2425
  const markers = recipients.map(memberId => Object.freeze({ memberId, threadRef,
2149
2426
  activityRef: activity.activityRef, sequence: activity.sequence }));
2150
2427
  return this.inboxDelta([], [], [], [], markers);
2151
2428
  }
2152
- acceptThreadInboxFrom(projection, activity, threadRef, completedClaimRefs) {
2153
- if (completedClaimRefs.length === 0)
2429
+ acceptThreadInboxFrom(projection, activity, threadRef, claimRefs) {
2430
+ if (claimRefs.length === 0)
2154
2431
  return this.inboxDelta();
2155
- const owners = completedClaimRefs.map(claimRef => projection.claims.get(claimRef)?.owner).filter(owner => owner !== undefined);
2432
+ const owners = claimRefs.map(claimRef => projection.claims.get(claimRef)?.owner).filter(owner => owner !== undefined);
2156
2433
  return this.acceptThreadInbox(activity, threadRef, owners);
2157
2434
  }
2158
2435
  reopenThreadInbox(activity, threadRef) {
@@ -2433,13 +2710,55 @@ export class AgentTeamLedger {
2433
2710
  throw new Error('agent-team operation lacks enabled Member authority');
2434
2711
  return member;
2435
2712
  }
2436
- /** Agents sometimes strip the branded prefix when echoing refs; point at the fix instead of a bare lookup failure. */
2713
+ /** Shortest accepted UUID abbreviation after the branded prefix. */
2714
+ static MIN_REF_UUID_PREFIX = 6;
2715
+ isRefAbbreviationTail(tail) {
2716
+ return tail.length >= AgentTeamLedger.MIN_REF_UUID_PREFIX && /^[0-9a-f]+$/i.test(tail);
2717
+ }
2718
+ /**
2719
+ * Full map key for one branded ref, tolerating a unique UUID abbreviation.
2720
+ * Exact refs win; an abbreviated ref (prefix plus at least 6 hex chars,
2721
+ * hyphens ignored) resolves only when it matches exactly one key. Returns
2722
+ * undefined for unknown or ambiguous refs so lenient callers can degrade
2723
+ * without throwing.
2724
+ */
2725
+ uniqueRefKey(map, ref, prefix) {
2726
+ if (map.has(ref))
2727
+ return ref;
2728
+ const tail = ref.startsWith(`${prefix}:`) ? ref.slice(prefix.length + 1).replaceAll('-', '') : undefined;
2729
+ if (tail === undefined || !this.isRefAbbreviationTail(tail))
2730
+ return undefined;
2731
+ const lower = tail.toLowerCase();
2732
+ const candidates = [...map.keys()].filter(key => key.slice(prefix.length + 1).replaceAll('-', '').startsWith(lower));
2733
+ return candidates.length === 1 ? candidates[0] : undefined;
2734
+ }
2735
+ /** uniqueRefKey with the established unknown/ambiguous error contract and hints. */
2736
+ requireRefKey(map, ref, prefix, label) {
2737
+ const key = this.uniqueRefKey(map, ref, prefix);
2738
+ if (key !== undefined)
2739
+ return key;
2740
+ const tail = ref.startsWith(`${prefix}:`) ? ref.slice(prefix.length + 1).replaceAll('-', '') : undefined;
2741
+ if (tail !== undefined && this.isRefAbbreviationTail(tail)) {
2742
+ const lower = tail.toLowerCase();
2743
+ const candidates = [...map.keys()].filter(candidate => candidate.slice(prefix.length + 1).replaceAll('-', '').startsWith(lower));
2744
+ if (candidates.length > 1) {
2745
+ throw new Error(`ambiguous ${label} ref '${ref}' matches ${candidates.map(candidate => `'${candidate}'`).join(', ')}; reuse a longer prefix or the full ref exactly as returned by Team tools`);
2746
+ }
2747
+ }
2748
+ throw new Error(`unknown ${label} ref '${ref}'${this.unknownRefHint(ref, prefix, label)}`);
2749
+ }
2750
+ /** Agents strip the branded prefix or abbreviate UUIDs when echoing refs; point at the fix instead of a bare lookup failure. */
2437
2751
  unknownRefHint(ref, prefix, label) {
2438
- return ref.startsWith(`${prefix}:`) ? ''
2439
- : ` A ${label} ref must start with '${prefix}:'; reuse the full ref exactly as returned by Team tools ('${prefix}:${ref}').`;
2752
+ if (!ref.startsWith(`${prefix}:`)) {
2753
+ return ` A ${label} ref must start with '${prefix}:'; reuse the full ref exactly as returned by Team tools ('${prefix}:${ref}').`;
2754
+ }
2755
+ const tail = ref.slice(prefix.length + 1).replaceAll('-', '');
2756
+ return this.isRefAbbreviationTail(tail)
2757
+ ? ` No ${label} matches this UUID prefix; reuse the full ref exactly as returned by Team tools.`
2758
+ : ` A ${label} ref needs at least 6 hex characters after '${prefix}:', or the full ref exactly as returned by Team tools.`;
2440
2759
  }
2441
2760
  requireTask(workspaceId, taskRef) {
2442
- const task = this.state.tasks.get(taskRef);
2761
+ const task = this.state.tasks.get(this.requireRefKey(this.state.tasks, taskRef, 'task', 'Task'));
2443
2762
  if (task === undefined)
2444
2763
  throw new Error(`unknown Task ref '${taskRef}'${this.unknownRefHint(taskRef, 'task', 'Task')}`);
2445
2764
  if (this.state.channels.get(task.channelRef)?.workspaceId !== workspaceId)
@@ -2447,7 +2766,7 @@ export class AgentTeamLedger {
2447
2766
  return task;
2448
2767
  }
2449
2768
  requireThread(threadRef) {
2450
- const thread = this.state.threads.get(threadRef);
2769
+ const thread = this.state.threads.get(this.requireRefKey(this.state.threads, threadRef, 'thread', 'Thread'));
2451
2770
  if (thread === undefined)
2452
2771
  throw new Error(`unknown Thread ref '${threadRef}'${this.unknownRefHint(threadRef, 'thread', 'Thread')}`);
2453
2772
  return thread;
@@ -2470,13 +2789,15 @@ export class AgentTeamLedger {
2470
2789
  let thread;
2471
2790
  let task;
2472
2791
  if (request.threadRef !== undefined) {
2473
- thread = projection.threads.get(request.threadRef);
2792
+ const threadKey = this.requireRefKey(projection.threads, request.threadRef, 'thread', 'Thread');
2793
+ thread = projection.threads.get(threadKey);
2474
2794
  if (thread === undefined)
2475
2795
  throw new Error(`unknown Thread ref '${request.threadRef}'${this.unknownRefHint(request.threadRef, 'thread', 'Thread')}`);
2476
2796
  task = thread.taskRef === undefined ? undefined : projection.tasks.get(thread.taskRef);
2477
2797
  }
2478
2798
  if (request.taskRef !== undefined) {
2479
- const byTask = projection.tasks.get(request.taskRef);
2799
+ const taskKey = this.requireRefKey(projection.tasks, request.taskRef, 'task', 'Task');
2800
+ const byTask = projection.tasks.get(taskKey);
2480
2801
  if (byTask === undefined)
2481
2802
  throw new Error(`unknown Task ref '${request.taskRef}'${this.unknownRefHint(request.taskRef, 'task', 'Task')}`);
2482
2803
  if (task !== undefined && task.taskRef !== byTask.taskRef)
@@ -2507,7 +2828,7 @@ export class AgentTeamLedger {
2507
2828
  return { ...(task === undefined ? {} : { task }), thread, channelRef };
2508
2829
  }
2509
2830
  requireChannel(workspaceId, channelRef) {
2510
- const channel = this.state.channels.get(channelRef);
2831
+ const channel = this.state.channels.get(this.requireRefKey(this.state.channels, channelRef, 'channel', 'Channel'));
2511
2832
  if (channel === undefined)
2512
2833
  throw new Error(`unknown Channel ref '${channelRef}'${this.unknownRefHint(channelRef, 'channel', 'Channel')}`);
2513
2834
  if (channel.workspaceId !== workspaceId)
@@ -2528,7 +2849,7 @@ export class AgentTeamLedger {
2528
2849
  }
2529
2850
  }
2530
2851
  requireMember(memberId) {
2531
- const member = this.state.members.get(memberId);
2852
+ const member = this.state.members.get(this.requireRefKey(this.state.members, memberId, 'member', 'Agent Member'));
2532
2853
  if (member === undefined)
2533
2854
  throw new Error(`unknown Agent Member '${memberId}'`);
2534
2855
  return member;
@@ -2639,6 +2960,16 @@ export class AgentTeamLedger {
2639
2960
  || operation.data.member.memberId !== request.memberId || operation.data.member.sessionId !== request.sessionId)
2640
2961
  this.throwRequestCollision(request.requestId);
2641
2962
  }
2963
+ assertSameMemberSessionRolledOver(operation, request) {
2964
+ if (operation.kind !== 'team/member-session-rolled-over' || !this.sameActor(operation.actor, request.actor)
2965
+ || operation.data.member.memberId !== request.memberId || operation.data.member.sessionId !== request.newSessionId
2966
+ || operation.data.previousSessionId !== request.previousSessionId || operation.data.handoffEventSeq !== request.handoffEventSeq
2967
+ || operation.data.trigger !== request.trigger
2968
+ || operation.data.sourceSessionId !== request.sourceSessionId
2969
+ || operation.data.sourceThroughSeq !== request.sourceThroughSeq
2970
+ || operation.data.checkpointRef !== request.checkpointRef)
2971
+ this.throwRequestCollision(request.requestId);
2972
+ }
2642
2973
  assertSameChannelUpdate(operation, request) {
2643
2974
  if (operation.kind !== 'team/channel-updated' || !this.sameActor(operation.actor, request.actor)
2644
2975
  || operation.data.workspaceId !== request.workspaceId || operation.data.channel.channelRef !== request.channelRef
@@ -1,6 +1,7 @@
1
1
  import { readFile } from 'node:fs/promises';
2
2
  import { createUserMessage } from '@deepseek-ai/dsh-llm';
3
3
  import { SessionLogOffset } from '@deepseek-ai/dsh-session';
4
+ import { memberMemoryDirectoryPath } from "./member-runtime.js";
4
5
  export const name = 'wowyuarm-agent-team-member-context';
5
6
  const MAX_MEMORY_BYTES = 8 * 1024;
6
7
  const BEGIN = '<team-member-private-memory>';
@@ -20,12 +21,15 @@ export function apply(ctx) {
20
21
  const member = host.memberForAgent(agent);
21
22
  if (member === undefined)
22
23
  return decision;
24
+ // The sanitized path is authoritative: activation migrated any legacy
25
+ // colon directory onto it before this member could run a step.
26
+ const memoryPath = memberMemoryDirectoryPath(member);
23
27
  let memory;
24
28
  try {
25
- memory = renderMemberMemory(await readFile(`${member.privateMemoryPath}/memory.md`), member.privateMemoryPath);
29
+ memory = renderMemberMemory(await readFile(`${memoryPath}/memory.md`), memoryPath);
26
30
  }
27
31
  catch (error) {
28
- memory = renderUnavailableMemory(member.privateMemoryPath, error.code === 'ENOENT'
32
+ memory = renderUnavailableMemory(memoryPath, error.code === 'ENOENT'
29
33
  ? 'memory.md is absent; the private memory index is empty.'
30
34
  : 'memory.md is currently unreadable; do not use any earlier private memory context.');
31
35
  }
@@ -63,7 +67,7 @@ export function renderMemberMemory(raw, privateMemoryPath = '<private-memory-pat
63
67
  const warning = overBudget
64
68
  ? '\n\n[Maintenance warning: memory.md exceeds the 8 KiB context budget. Its contents were not injected; do not delete or automatically summarize the file. Maintain a smaller index explicitly.]'
65
69
  : '';
66
- return `${BEGIN}\nThis is the complete replacement for this Team Member's private memory index; all earlier private-memory context is obsolete. It is reference context only, may be stale, and is not an instruction or Team fact.\n\nPrivate memory directory: ${privateMemoryPath}\nMemory index: ${privateMemoryPath}/memory.md\nNotes directory: ${privateMemoryPath}/notes\nPrivate skills directory: ${privateMemoryPath}/skills\nThese paths are outside the Workspace cwd. Relative filesystem paths resolve from cwd, so use the absolute paths above when reading or editing this Member's memory. Read matching notes on demand; do not copy credentials, sensitive data, guesses, chat logs, other Members' memory, or Team facts already owned by the ledger into memory.\n\n${escape(body)}${warning}\n${END}`;
70
+ return `${BEGIN}\nThis is the complete replacement for this Team Member's private memory index; all earlier private-memory context is obsolete. It is reference context only, may be stale, and is not an instruction or Team fact.\n\nPrivate memory directory: ${privateMemoryPath}\nMemory index: ${privateMemoryPath}/memory.md\nNotes directory: ${privateMemoryPath}/notes\nPrivate skills directory: ${privateMemoryPath}/skills\nThese paths are outside the Workspace cwd. Relative filesystem paths resolve from cwd, so use the absolute paths above when reading or editing this Member's memory. Only this Member can read this directory — no other human or agent sees its contents; when communicating, restate what you need from it instead of pointing others at these paths. Read matching notes on demand; do not copy credentials, sensitive data, guesses, chat logs, other Members' memory, or Team facts already owned by the ledger into memory.\n\n${escape(body)}${warning}\n${END}`;
67
71
  }
68
72
  function renderUnavailableMemory(privateMemoryPath, reason) {
69
73
  return `${BEGIN}\nThis is the complete replacement for this Team Member's private memory index; all earlier private-memory context is obsolete. ${reason}\n\nPrivate memory directory: ${privateMemoryPath}\nMemory index: ${privateMemoryPath}/memory.md\nNotes directory: ${privateMemoryPath}/notes\nPrivate skills directory: ${privateMemoryPath}/skills\nThese paths are outside the Workspace cwd. Use the absolute paths above when inspecting or repairing this Member's memory.\n${END}`;