@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.
- package/package.json +2 -1
- package/packages/agent-team/README.md +5 -3
- package/packages/agent-team/README.zh.md +5 -3
- package/packages/agent-team/core-skills/member-skill-manager/SKILL.md +2 -0
- package/packages/agent-team/lib/attachments.js +14 -4
- package/packages/agent-team/lib/context-management.js +379 -0
- package/packages/agent-team/lib/context-projection.js +574 -0
- package/packages/agent-team/lib/context-source.js +72 -0
- package/packages/agent-team/lib/index.js +1258 -54
- package/packages/agent-team/lib/ledger.js +357 -26
- package/packages/agent-team/lib/member-context.js +7 -3
- package/packages/agent-team/lib/member-runtime.js +159 -4
- package/packages/agent-team/lib/pressure-policy.js +200 -0
- package/packages/agent-team/lib/progress-nudge.js +320 -0
- package/packages/agent-team/lib/spec.js +18 -0
- package/packages/agent-team/lib/typert.host.js +124 -42
- package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
- package/packages/agent-team/lib/typert.remote-client.js +39 -25
- package/packages/agent-team/lib/types/attachments.d.ts +1 -1
- package/packages/agent-team/lib/types/attachments.d.ts.map +1 -1
- package/packages/agent-team/lib/types/context-management.d.ts +142 -0
- package/packages/agent-team/lib/types/context-management.d.ts.map +1 -0
- package/packages/agent-team/lib/types/context-projection.d.ts +219 -0
- package/packages/agent-team/lib/types/context-projection.d.ts.map +1 -0
- package/packages/agent-team/lib/types/context-source.d.ts +83 -0
- package/packages/agent-team/lib/types/context-source.d.ts.map +1 -0
- package/packages/agent-team/lib/types/index.d.ts +283 -2
- package/packages/agent-team/lib/types/index.d.ts.map +1 -1
- package/packages/agent-team/lib/types/ledger.d.ts +120 -7
- package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
- package/packages/agent-team/lib/types/member-context.d.ts.map +1 -1
- package/packages/agent-team/lib/types/member-runtime.d.ts +30 -2
- package/packages/agent-team/lib/types/member-runtime.d.ts.map +1 -1
- package/packages/agent-team/lib/types/pressure-policy.d.ts +106 -0
- package/packages/agent-team/lib/types/pressure-policy.d.ts.map +1 -0
- package/packages/agent-team/lib/types/progress-nudge.d.ts +136 -0
- package/packages/agent-team/lib/types/progress-nudge.d.ts.map +1 -0
- package/packages/agent-team/lib/types/spec.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types/entities.d.ts +32 -1
- package/packages/agent-team/lib/types/types/entities.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types/operations.d.ts +38 -3
- package/packages/agent-team/lib/types/types/operations.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types/requests-results.d.ts +54 -1
- package/packages/agent-team/lib/types/types/requests-results.d.ts.map +1 -1
- package/packages/agent-team/preset/team-member/agent.cordis.yml +10 -2
- package/packages/client-agent-team/lib/client.js +283 -398
- package/packages/client-agent-team/lib/client.js.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts +1 -2
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +61 -41
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.js +3 -7
- package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts +3 -1
- package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamComposer.js +37 -27
- package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts +1 -1
- package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamConversation.js +2 -2
- package/packages/client-agent-team/lib/types/client/TeamMemberAvatar.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamMemberAvatar.js +3 -2
- package/packages/client-agent-team/lib/types/client/TeamMessage.js +5 -0
- package/packages/client-agent-team/lib/types/client/TeamPresenceDot.d.ts +2 -2
- package/packages/client-agent-team/lib/types/client/TeamPresenceDot.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamPresenceDot.js +4 -6
- package/packages/client-agent-team/lib/types/client/TeamStateDot.d.ts +14 -0
- package/packages/client-agent-team/lib/types/client/TeamStateDot.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamStateDot.js +15 -0
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts +1 -0
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +41 -16
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts +1 -1
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.js +2 -2
- package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/index.js +36 -78
- package/packages/client-agent-team/lib/types/client/locales.d.ts +0 -16
- package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/locales.js +0 -16
- package/packages/client-agent-team/lib/types/client/sidebar-drag.d.ts +1 -2
- package/packages/client-agent-team/lib/types/client/sidebar-drag.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/slots.d.ts +8 -1
- package/packages/client-agent-team/lib/types/client/slots.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/task-refs.d.ts +7 -1
- package/packages/client-agent-team/lib/types/client/task-refs.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/task-refs.js +25 -3
- package/packages/client-agent-team/lib/types/client/team-formatters.d.ts +11 -10
- package/packages/client-agent-team/lib/types/client/team-formatters.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/team-formatters.js +14 -11
- package/packages/tool-agent-team/README.md +8 -5
- package/packages/tool-agent-team/README.zh.md +8 -5
- package/packages/tool-agent-team/lib/context-tools.js +179 -0
- package/packages/tool-agent-team/lib/index.js +144 -25
- package/packages/tool-agent-team/lib/types/context-tools.d.ts +16 -0
- package/packages/tool-agent-team/lib/types/context-tools.d.ts.map +1 -0
- package/packages/tool-agent-team/lib/types/index.d.ts.map +1 -1
- package/packages/agent-team/lib/auto-compaction.js +0 -201
- package/packages/agent-team/lib/types/auto-compaction.d.ts +0 -47
- package/packages/agent-team/lib/types/auto-compaction.d.ts.map +0 -1
- package/packages/client-agent-team/lib/types/client/TeamMemberDock.d.ts +0 -12
- package/packages/client-agent-team/lib/types/client/TeamMemberDock.d.ts.map +0 -1
- package/packages/client-agent-team/lib/types/client/TeamMemberDock.js +0 -13
- package/packages/client-agent-team/lib/types/client/member-session-input.d.ts +0 -20
- package/packages/client-agent-team/lib/types/client/member-session-input.d.ts.map +0 -1
- 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,
|
|
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
|
|
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
|
-
|
|
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
|
-
*
|
|
2141
|
-
*
|
|
2142
|
-
*
|
|
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,
|
|
2145
|
-
if (
|
|
2421
|
+
acceptThreadInbox(activity, threadRef, acceptedOwners) {
|
|
2422
|
+
if (acceptedOwners.length === 0)
|
|
2146
2423
|
return this.inboxDelta();
|
|
2147
|
-
const recipients = [...new Set(
|
|
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,
|
|
2153
|
-
if (
|
|
2429
|
+
acceptThreadInboxFrom(projection, activity, threadRef, claimRefs) {
|
|
2430
|
+
if (claimRefs.length === 0)
|
|
2154
2431
|
return this.inboxDelta();
|
|
2155
|
-
const owners =
|
|
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
|
-
/**
|
|
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
|
-
|
|
2439
|
-
|
|
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
|
-
|
|
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
|
|
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(`${
|
|
29
|
+
memory = renderMemberMemory(await readFile(`${memoryPath}/memory.md`), memoryPath);
|
|
26
30
|
}
|
|
27
31
|
catch (error) {
|
|
28
|
-
memory = renderUnavailableMemory(
|
|
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}`;
|