@wowyuarm/dsh-agent-team 0.1.12 → 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/README.zh.md +2 -0
- package/cordis.patch.yml +7 -3
- package/package.json +14 -2
- package/packages/agent-team/README.md +2 -2
- package/packages/agent-team/README.zh.md +2 -2
- package/packages/agent-team/lib/index.js +151 -69
- package/packages/agent-team/lib/ledger.js +249 -31
- package/packages/agent-team/lib/member-context.js +18 -1
- package/packages/agent-team/lib/member-runtime.js +5 -4
- package/packages/agent-team/lib/mentions.js +48 -24
- package/packages/agent-team/lib/spec.js +19 -0
- package/packages/agent-team/lib/typert.host.js +187 -41
- package/packages/agent-team/lib/typert.remote-client.d.ts +7 -3
- package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
- package/packages/agent-team/lib/typert.remote-client.js +132 -27
- package/packages/agent-team/lib/types/index.d.ts +32 -7
- package/packages/agent-team/lib/types/index.d.ts.map +1 -1
- package/packages/agent-team/lib/types/ledger.d.ts +46 -2
- package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
- package/packages/agent-team/lib/types/member-context.d.ts +6 -0
- package/packages/agent-team/lib/types/member-context.d.ts.map +1 -1
- package/packages/agent-team/lib/types/mentions.d.ts +20 -7
- package/packages/agent-team/lib/types/mentions.d.ts.map +1 -1
- package/packages/agent-team/lib/types/spec.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types/entities.d.ts +3 -2
- package/packages/agent-team/lib/types/types/entities.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types/operations.d.ts +37 -1
- package/packages/agent-team/lib/types/types/operations.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types/requests-results.d.ts +50 -10
- package/packages/agent-team/lib/types/types/requests-results.d.ts.map +1 -1
- package/packages/agent-team/lib/types/vendor/storage-sqlite/index.d.ts +87 -0
- package/packages/agent-team/lib/types/vendor/storage-sqlite/index.d.ts.map +1 -0
- package/packages/agent-team/lib/types/vendor/storage-sqlite/schema.d.ts +59 -0
- package/packages/agent-team/lib/types/vendor/storage-sqlite/schema.d.ts.map +1 -0
- package/packages/agent-team/lib/types/vendor/storage-sqlite/unit.d.ts +57 -0
- package/packages/agent-team/lib/types/vendor/storage-sqlite/unit.d.ts.map +1 -0
- package/packages/agent-team/lib/vendor/storage-sqlite/index.js +139 -0
- package/packages/agent-team/lib/vendor/storage-sqlite/schema.js +120 -0
- package/packages/agent-team/lib/vendor/storage-sqlite/unit.js +132 -0
- package/packages/client-agent-team/README.md +1 -1
- package/packages/client-agent-team/README.zh.md +1 -1
- package/packages/client-agent-team/lib/client.js +603 -343
- package/packages/client-agent-team/lib/client.js.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentImport.d.ts +12 -0
- package/packages/client-agent-team/lib/types/client/TeamAgentImport.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamAgentImport.js +69 -0
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts +3 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +29 -17
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.js +19 -44
- package/packages/client-agent-team/lib/types/client/TeamInboxPage.d.ts +2 -2
- package/packages/client-agent-team/lib/types/client/TeamInboxPage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamInboxPage.js +15 -14
- package/packages/client-agent-team/lib/types/client/TeamPresenceDot.d.ts +1 -1
- package/packages/client-agent-team/lib/types/client/TeamPresenceDot.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +15 -29
- 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 +8 -1
- package/packages/client-agent-team/lib/types/client/locales.d.ts +18 -0
- package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/locales.js +18 -0
- package/packages/client-agent-team/lib/types/client/slots.d.ts +3 -1
- package/packages/client-agent-team/lib/types/client/slots.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/team-changes.d.ts +15 -31
- package/packages/client-agent-team/lib/types/client/team-changes.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/team-changes.js +82 -111
- package/packages/client-agent-team/lib/types/client/team-formatters.d.ts +13 -11
- 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 +29 -35
- package/packages/tool-agent-team/README.md +1 -1
- package/packages/tool-agent-team/README.zh.md +1 -1
- package/packages/tool-agent-team/lib/host-access.js +19 -0
- package/packages/tool-agent-team/lib/index.js +42 -33
- package/packages/tool-agent-team/lib/types/host-access.d.ts +10 -0
- package/packages/tool-agent-team/lib/types/host-access.d.ts.map +1 -1
- package/packages/tool-agent-team/lib/types/index.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/scope-coverage.d.ts +0 -41
- package/packages/client-agent-team/lib/types/client/scope-coverage.d.ts.map +0 -1
- package/packages/client-agent-team/lib/types/client/scope-coverage.js +0 -64
|
@@ -22,7 +22,7 @@ export function agentTeamHumanActor() {
|
|
|
22
22
|
return HUMAN_ACTOR;
|
|
23
23
|
}
|
|
24
24
|
function emptyProjection() {
|
|
25
|
-
return { byRequest: new Map(), byOperation: new Map(), ordered: [], channels: new Map(), members: new Map(), memberships: new Map(),
|
|
25
|
+
return { byRequest: new Map(), byOperation: new Map(), ordered: [], channels: new Map(), members: new Map(), participations: new Map(), memberships: new Map(),
|
|
26
26
|
claims: new Map(), tasks: new Map(), threads: new Map(), attention: new Map(), directMarkers: new Map(), activityMarkers: new Map(),
|
|
27
27
|
orderedFacts: [], factsByThread: new Map(), channelRefByThread: new Map(), mentionsByMessage: new Map(), messageCountByThread: new Map(),
|
|
28
28
|
messagesByRef: new Map(),
|
|
@@ -265,7 +265,8 @@ export class AgentTeamLedger {
|
|
|
265
265
|
if (handle === '')
|
|
266
266
|
throw new Error('member handle must not be empty');
|
|
267
267
|
if (handle !== prior.handle)
|
|
268
|
-
|
|
268
|
+
for (const workspaceId of this.workspacesOf(prior.memberId))
|
|
269
|
+
this.assertHandleAvailable(workspaceId, handle, prior.memberId);
|
|
269
270
|
this.assertModelSelection(request.model);
|
|
270
271
|
this.assertCapabilities(request.capabilities);
|
|
271
272
|
// An absent model or capabilities field must CLEAR any override
|
|
@@ -465,8 +466,8 @@ export class AgentTeamLedger {
|
|
|
465
466
|
this.assertHumanActor(request.actor);
|
|
466
467
|
const channel = this.requireActiveChannel(request.workspaceId, request.channelRef);
|
|
467
468
|
const member = this.requireMember(request.memberId);
|
|
468
|
-
if (member.
|
|
469
|
-
throw new Error('Member
|
|
469
|
+
if (!this.participatesIn(member.memberId, request.workspaceId))
|
|
470
|
+
throw new Error('Agent Member does not participate in the Channel\'s Workspace');
|
|
470
471
|
if (member.state !== 'enabled')
|
|
471
472
|
throw new Error(`Agent Member '${member.memberId}' is ${member.state}; only enabled Members can join a Channel`);
|
|
472
473
|
if (this.isChannelMember(channel.channelRef, member.memberId))
|
|
@@ -490,7 +491,7 @@ export class AgentTeamLedger {
|
|
|
490
491
|
this.assertHumanActor(request.actor);
|
|
491
492
|
const channel = this.requireChannel(request.workspaceId, request.channelRef);
|
|
492
493
|
const member = this.requireMember(request.memberId);
|
|
493
|
-
if (member.
|
|
494
|
+
if (!this.participatesIn(member.memberId, channel.workspaceId) || !this.isChannelMember(channel.channelRef, member.memberId)) {
|
|
494
495
|
throw new Error(`Agent Member '${member.memberId}' is not a member of Channel '${channel.channelRef}'`);
|
|
495
496
|
}
|
|
496
497
|
const threadRefs = this.channelThreadRefs(channel.channelRef);
|
|
@@ -515,6 +516,86 @@ export class AgentTeamLedger {
|
|
|
515
516
|
return this.committed(this.channelMemberRemovalResult(operation));
|
|
516
517
|
});
|
|
517
518
|
}
|
|
519
|
+
/**
|
|
520
|
+
* Join one Agent Member to one additional Workspace. Participation is a
|
|
521
|
+
* pure relation — no Session is created or moved; the Member's Session
|
|
522
|
+
* stays rooted in its default Workspace and collaboration in the joined
|
|
523
|
+
* Workspace is ledger work addressed by the workspaceId.
|
|
524
|
+
*/
|
|
525
|
+
joinWorkspace(request) {
|
|
526
|
+
return this.enqueue(async () => {
|
|
527
|
+
const existing = this.state.byRequest.get(request.requestId);
|
|
528
|
+
if (existing !== undefined) {
|
|
529
|
+
this.assertSameWorkspaceJoin(existing, request);
|
|
530
|
+
return this.resolved(this.workspaceJoinResult(existing));
|
|
531
|
+
}
|
|
532
|
+
this.assertHumanActor(request.actor);
|
|
533
|
+
const member = this.requireMember(request.memberId);
|
|
534
|
+
if (member.state === 'inactive')
|
|
535
|
+
throw new Error(`Agent Member '${member.memberId}' is inactive and cannot join a Workspace`);
|
|
536
|
+
if (member.state === 'archived')
|
|
537
|
+
throw new Error(`Agent Member '${member.memberId}' is archived and cannot join a Workspace`);
|
|
538
|
+
if (this.participatesIn(member.memberId, request.workspaceId))
|
|
539
|
+
throw new Error(`Agent Member '${member.memberId}' already participates in Workspace '${request.workspaceId}'`);
|
|
540
|
+
// Handles resolve per Workspace: a second live Member with the same
|
|
541
|
+
// handle in the target would make mentions ambiguous there.
|
|
542
|
+
this.assertHandleAvailable(request.workspaceId, member.handle);
|
|
543
|
+
const operation = Object.freeze({
|
|
544
|
+
...this.operationBase(request, this.nextSequence()), kind: 'team/member-workspace-joined',
|
|
545
|
+
data: Object.freeze({ workspaceId: request.workspaceId, memberId: member.memberId }),
|
|
546
|
+
});
|
|
547
|
+
await this.table.put(operation.operationId, operation);
|
|
548
|
+
this.apply(operation);
|
|
549
|
+
return this.committed(this.workspaceJoinResult(operation));
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Withdraw one Agent Member from one of its non-default Workspaces. The
|
|
554
|
+
* default Workspace cannot be left — ending it is the archive path. Every
|
|
555
|
+
* active Claim the Member holds on the Workspace's Threads releases with
|
|
556
|
+
* public Activities, its Attention and markers on those Threads clear, and
|
|
557
|
+
* its Channel memberships there end; identity, Session, and remaining
|
|
558
|
+
* participations are untouched.
|
|
559
|
+
*/
|
|
560
|
+
leaveWorkspace(request) {
|
|
561
|
+
return this.enqueue(async () => {
|
|
562
|
+
const existing = this.state.byRequest.get(request.requestId);
|
|
563
|
+
if (existing !== undefined) {
|
|
564
|
+
this.assertSameWorkspaceLeave(existing, request);
|
|
565
|
+
return this.resolved(this.workspaceLeaveResult(existing));
|
|
566
|
+
}
|
|
567
|
+
this.assertHumanActor(request.actor);
|
|
568
|
+
const member = this.requireMember(request.memberId);
|
|
569
|
+
if (member.state === 'inactive')
|
|
570
|
+
throw new Error(`Agent Member '${member.memberId}' is already inactive`);
|
|
571
|
+
if (member.state === 'archived')
|
|
572
|
+
throw new Error(`Agent Member '${member.memberId}' is already archived`);
|
|
573
|
+
if (request.workspaceId === member.workspaceId)
|
|
574
|
+
throw new Error(`Agent Member '${member.memberId}' cannot leave its default Workspace '${request.workspaceId}'; archive the Member instead`);
|
|
575
|
+
if (!this.participatesIn(member.memberId, request.workspaceId))
|
|
576
|
+
throw new Error(`Agent Member '${member.memberId}' does not participate in Workspace '${request.workspaceId}'`);
|
|
577
|
+
const threadRefs = this.workspaceThreadRefs(request.workspaceId);
|
|
578
|
+
const releasedClaims = [...this.state.claims.values()]
|
|
579
|
+
.filter(claim => claim.owner === member.memberId && claim.state === 'active' && threadRefs.has(claim.threadRef))
|
|
580
|
+
.map(claim => Object.freeze({ ...claim, state: 'released' }));
|
|
581
|
+
const projectedClaims = new Map(this.state.claims);
|
|
582
|
+
for (const claim of releasedClaims)
|
|
583
|
+
projectedClaims.set(claim.claimRef, claim);
|
|
584
|
+
const sequence = this.nextSequence();
|
|
585
|
+
const activities = this.releaseSummaries(releasedClaims, member.memberId, sequence);
|
|
586
|
+
const threads = this.threadsForActivities(activities);
|
|
587
|
+
const tasks = this.tasksForClaims(releasedClaims, projectedClaims);
|
|
588
|
+
const inbox = this.removeMemberThreadInbox(member.memberId, threadRefs);
|
|
589
|
+
const operation = Object.freeze({
|
|
590
|
+
...this.operationBase(request, sequence), kind: 'team/member-workspace-left',
|
|
591
|
+
data: Object.freeze({ workspaceId: request.workspaceId, memberId: member.memberId,
|
|
592
|
+
claims: Object.freeze(releasedClaims), activities, tasks, threads, inbox }),
|
|
593
|
+
});
|
|
594
|
+
await this.table.put(operation.operationId, operation);
|
|
595
|
+
this.apply(operation);
|
|
596
|
+
return this.committed(this.workspaceLeaveResult(operation));
|
|
597
|
+
});
|
|
598
|
+
}
|
|
518
599
|
/**
|
|
519
600
|
* Archive one Channel: hidden from every surface, facts kept recoverable.
|
|
520
601
|
* Every active Claim on the Channel's Threads releases with one public
|
|
@@ -975,7 +1056,7 @@ export class AgentTeamLedger {
|
|
|
975
1056
|
if (body === '')
|
|
976
1057
|
throw new Error('DM body must not be empty');
|
|
977
1058
|
const recipient = this.requireMember(request.recipientMemberId);
|
|
978
|
-
if (recipient.
|
|
1059
|
+
if (!this.participatesIn(recipient.memberId, request.workspaceId))
|
|
979
1060
|
throw new Error(`Agent Member '${recipient.memberId}' is not in Workspace '${request.workspaceId}'`);
|
|
980
1061
|
if (recipient.state !== 'enabled')
|
|
981
1062
|
throw new Error(`Agent Member '${recipient.memberId}' is ${recipient.state}; DM delivery requires an enabled Member`);
|
|
@@ -1030,7 +1111,16 @@ export class AgentTeamLedger {
|
|
|
1030
1111
|
}
|
|
1031
1112
|
inbox(actor, request) {
|
|
1032
1113
|
const authorized = this.assertActorForWorkspace(actor, request.workspaceId);
|
|
1033
|
-
|
|
1114
|
+
return this.inboxForWorkspaces(authorized, [request.workspaceId], request.limit);
|
|
1115
|
+
}
|
|
1116
|
+
memberInbox(actor, request) {
|
|
1117
|
+
const workspaces = this.workspacesOf(actor.memberId);
|
|
1118
|
+
for (const workspaceId of workspaces)
|
|
1119
|
+
this.assertActorForWorkspace(actor, workspaceId);
|
|
1120
|
+
return this.inboxForWorkspaces(actor, workspaces, request.limit);
|
|
1121
|
+
}
|
|
1122
|
+
inboxForWorkspaces(authorized, workspaceIds, requestedLimit) {
|
|
1123
|
+
const limit = requestedLimit ?? 50;
|
|
1034
1124
|
if (!Number.isInteger(limit) || limit < 1 || limit > 100)
|
|
1035
1125
|
throw new Error('inbox limit must be an integer between 1 and 100');
|
|
1036
1126
|
// One unread slice serves every reader: Threads the reader follows or
|
|
@@ -1044,7 +1134,7 @@ export class AgentTeamLedger {
|
|
|
1044
1134
|
// A Human reader additionally receives the separate 「最近活跃」 slice below;
|
|
1045
1135
|
// `items` stays exactly the unread queue for every reader, so the badge
|
|
1046
1136
|
// totals and the agent-facing `team_inbox` result cannot drift with it.
|
|
1047
|
-
const taskNumbers = this.taskNumbers(
|
|
1137
|
+
const taskNumbers = new Map(workspaceIds.flatMap(workspaceId => [...this.taskNumbers(workspaceId)]));
|
|
1048
1138
|
const items = [];
|
|
1049
1139
|
const unreadThreads = new Set();
|
|
1050
1140
|
for (const threadRef of this.inboxCandidateThreads(authorized.memberId)) {
|
|
@@ -1054,8 +1144,14 @@ export class AgentTeamLedger {
|
|
|
1054
1144
|
const channelRef = this.channelRefForThread(thread.threadRef);
|
|
1055
1145
|
if (channelRef === undefined)
|
|
1056
1146
|
continue;
|
|
1057
|
-
|
|
1147
|
+
const channel = this.state.channels.get(channelRef);
|
|
1148
|
+
if (channel === undefined || !workspaceIds.includes(channel.workspaceId))
|
|
1149
|
+
continue;
|
|
1150
|
+
// Archived Channels do not exist on Team API surfaces: their Threads
|
|
1151
|
+
// reach neither Inbox slice, matching ref resolution and the feed.
|
|
1152
|
+
if (channel.state === 'archived')
|
|
1058
1153
|
continue;
|
|
1154
|
+
const workspaceId = channel.workspaceId;
|
|
1059
1155
|
if (authorized.kind === 'member' && !this.isChannelMember(channelRef, authorized.memberId))
|
|
1060
1156
|
continue;
|
|
1061
1157
|
const unread = this.unreadFor(authorized.memberId, thread.threadRef);
|
|
@@ -1070,7 +1166,7 @@ export class AgentTeamLedger {
|
|
|
1070
1166
|
// observe a different commit between the two reads.
|
|
1071
1167
|
const newest = unread.at(-1).fact;
|
|
1072
1168
|
const taskNumber = task === undefined ? undefined : taskNumbers.get(task.taskRef);
|
|
1073
|
-
items.push(Object.freeze({ channelRef,
|
|
1169
|
+
items.push(Object.freeze({ workspaceId, channelRef,
|
|
1074
1170
|
channelName: this.state.channels.get(channelRef)?.name ?? '',
|
|
1075
1171
|
...(task === undefined ? {} : { task }), ...(taskNumber === undefined ? {} : { taskNumber }), thread,
|
|
1076
1172
|
unreadCount: unread.length, directCount,
|
|
@@ -1082,7 +1178,7 @@ export class AgentTeamLedger {
|
|
|
1082
1178
|
items.sort((left, right) => right.directCount - left.directCount || right.newestSequence - left.newestSequence || left.thread.threadRef.localeCompare(right.thread.threadRef));
|
|
1083
1179
|
const selected = items.slice(0, limit);
|
|
1084
1180
|
const recent = authorized.kind === 'human'
|
|
1085
|
-
? this.recentInboxItems(authorized.memberId,
|
|
1181
|
+
? this.recentInboxItems(authorized.memberId, workspaceIds[0], unreadThreads, taskNumbers)
|
|
1086
1182
|
: Object.freeze([]);
|
|
1087
1183
|
return Object.freeze({ items: Object.freeze(selected), recent,
|
|
1088
1184
|
totalUnreadCount: items.reduce((sum, item) => sum + item.unreadCount, 0),
|
|
@@ -1147,7 +1243,12 @@ export class AgentTeamLedger {
|
|
|
1147
1243
|
const channelRef = this.channelRefForThread(thread.threadRef);
|
|
1148
1244
|
if (channelRef === undefined)
|
|
1149
1245
|
continue;
|
|
1150
|
-
|
|
1246
|
+
const channel = this.state.channels.get(channelRef);
|
|
1247
|
+
if (channel === undefined || channel.workspaceId !== workspaceId)
|
|
1248
|
+
continue;
|
|
1249
|
+
// Archived Channels do not exist on Team API surfaces, so their Threads
|
|
1250
|
+
// are no way back into work either — participation ends at archival.
|
|
1251
|
+
if (channel.state === 'archived')
|
|
1151
1252
|
continue;
|
|
1152
1253
|
const facts = this.state.factsByThread.get(thread.threadRef) ?? [];
|
|
1153
1254
|
const newest = facts.at(-1);
|
|
@@ -1155,7 +1256,7 @@ export class AgentTeamLedger {
|
|
|
1155
1256
|
continue;
|
|
1156
1257
|
const task = thread.taskRef === undefined ? undefined : this.state.tasks.get(thread.taskRef);
|
|
1157
1258
|
const taskNumber = task === undefined ? undefined : taskNumbers.get(task.taskRef);
|
|
1158
|
-
recent.push(Object.freeze({ channelRef,
|
|
1259
|
+
recent.push(Object.freeze({ workspaceId, channelRef,
|
|
1159
1260
|
channelName: this.state.channels.get(channelRef)?.name ?? '',
|
|
1160
1261
|
...(task === undefined ? {} : { task }), ...(taskNumber === undefined ? {} : { taskNumber }), thread,
|
|
1161
1262
|
unreadCount: 0, directCount: 0,
|
|
@@ -1183,9 +1284,10 @@ export class AgentTeamLedger {
|
|
|
1183
1284
|
/** Model-visible notification material derived from the recipient's current durable unread state. */
|
|
1184
1285
|
notificationFacts(memberId, request) {
|
|
1185
1286
|
const member = this.requireMember(memberId);
|
|
1186
|
-
if (
|
|
1287
|
+
if (request !== undefined && !this.participatesIn(memberId, request.workspaceId))
|
|
1187
1288
|
throw new Error('Member cannot inspect another Workspace');
|
|
1188
|
-
const
|
|
1289
|
+
const actor = { kind: 'member', memberId, handle: member.handle };
|
|
1290
|
+
const inbox = request === undefined ? this.memberInbox(actor, {}) : this.inbox(actor, request);
|
|
1189
1291
|
return Object.freeze(inbox.items.map(item => Object.freeze({ item,
|
|
1190
1292
|
facts: this.unreadFor(memberId, item.thread.threadRef) })));
|
|
1191
1293
|
}
|
|
@@ -1307,7 +1409,7 @@ export class AgentTeamLedger {
|
|
|
1307
1409
|
throw new Error('cursor must be a non-negative integer sequence');
|
|
1308
1410
|
if (memberId !== undefined) {
|
|
1309
1411
|
const member = this.requireMember(memberId);
|
|
1310
|
-
if (member.
|
|
1412
|
+
if (!this.participatesIn(member.memberId, request.workspaceId))
|
|
1311
1413
|
throw new Error('Member cannot view another Workspace');
|
|
1312
1414
|
}
|
|
1313
1415
|
if (request.channelRef !== undefined) {
|
|
@@ -1390,6 +1492,7 @@ export class AgentTeamLedger {
|
|
|
1390
1492
|
const task = thread.taskRef === undefined ? undefined : this.state.tasks.get(thread.taskRef);
|
|
1391
1493
|
const facts = this.state.factsByThread.get(thread.threadRef) ?? [];
|
|
1392
1494
|
return Object.freeze({ message, mentions: fact.mentions, ...(task === undefined ? {} : { task, taskNumber: taskNumbers.get(task.taskRef) ?? 0 }), thread,
|
|
1495
|
+
claimOwners: this.liveClaimOwners(task),
|
|
1393
1496
|
messageCount: this.state.messageCountByThread.get(thread.threadRef) ?? 0,
|
|
1394
1497
|
lastActivityAt: facts.at(-1)?.occurredAt ?? '' });
|
|
1395
1498
|
});
|
|
@@ -1397,6 +1500,8 @@ export class AgentTeamLedger {
|
|
|
1397
1500
|
const nextCursor = selected.length === 0 ? cursor : direction === 'before' ? selected[0].sequence : selected.at(-1).sequence;
|
|
1398
1501
|
return Object.freeze({
|
|
1399
1502
|
humanMemberId: initialization.data.humanMemberId,
|
|
1503
|
+
workspaces: Object.freeze(memberId === undefined ? [] : this.workspacesOfFrom(this.state, memberId)
|
|
1504
|
+
.map(workspaceId => Object.freeze({ workspaceId, default: workspaceId === this.state.members.get(memberId)?.workspaceId }))),
|
|
1400
1505
|
channels: Object.freeze(channels),
|
|
1401
1506
|
members: Object.freeze([...this.state.memberships.entries()].filter(([channelRef]) => channelRefs.has(channelRef)).flatMap(([channelRef, ids]) => [...ids].filter(id => {
|
|
1402
1507
|
const state = this.state.members.get(id)?.state;
|
|
@@ -1475,6 +1580,10 @@ export class AgentTeamLedger {
|
|
|
1475
1580
|
}
|
|
1476
1581
|
return scopes;
|
|
1477
1582
|
}
|
|
1583
|
+
/** One workspace change scope per Workspace the Member participates in — member-level commits wake every panel that lists it. */
|
|
1584
|
+
memberWorkspaceScopes(memberId) {
|
|
1585
|
+
return [...(this.state.participations.get(memberId) ?? [])].map(workspaceId => ({ kind: 'workspace', workspaceId }));
|
|
1586
|
+
}
|
|
1478
1587
|
/**
|
|
1479
1588
|
* Durable position of the newest shared-projection commit: the version every
|
|
1480
1589
|
* change waiter outside the presence scope observes. It only moves when a
|
|
@@ -1503,9 +1612,13 @@ export class AgentTeamLedger {
|
|
|
1503
1612
|
case 'team/member-session-rolled-over':
|
|
1504
1613
|
case 'team/member-updated':
|
|
1505
1614
|
case 'team/member-removed':
|
|
1506
|
-
return
|
|
1615
|
+
return this.memberWorkspaceScopes(operation.data.member.memberId);
|
|
1507
1616
|
case 'team/member-archived':
|
|
1508
|
-
return this.withReleasedActivityScopes(
|
|
1617
|
+
return this.withReleasedActivityScopes(this.memberWorkspaceScopes(operation.data.member.memberId), operation.data.tasks, operation.data.activities);
|
|
1618
|
+
case 'team/member-workspace-joined':
|
|
1619
|
+
return [{ kind: 'workspace', workspaceId: operation.data.workspaceId }];
|
|
1620
|
+
case 'team/member-workspace-left':
|
|
1621
|
+
return this.withReleasedActivityScopes([{ kind: 'workspace', workspaceId: operation.data.workspaceId }], operation.data.tasks, operation.data.activities);
|
|
1509
1622
|
case 'team/channel-member-added':
|
|
1510
1623
|
return [{ kind: 'workspace', workspaceId: operation.data.workspaceId }, { kind: 'channel', channelRef: operation.data.channelRef }];
|
|
1511
1624
|
case 'team/channel-member-removed':
|
|
@@ -1568,6 +1681,8 @@ export class AgentTeamLedger {
|
|
|
1568
1681
|
for (const follower of this.state.attentionByThread.get(threadRef) ?? [])
|
|
1569
1682
|
members.add(follower);
|
|
1570
1683
|
}
|
|
1684
|
+
if (operation.kind === 'team/member-workspace-joined' || operation.kind === 'team/member-workspace-left')
|
|
1685
|
+
members.add(operation.data.memberId);
|
|
1571
1686
|
if (operation.actor.kind === 'member')
|
|
1572
1687
|
members.add(operation.actor.memberId);
|
|
1573
1688
|
return [...members];
|
|
@@ -1589,11 +1704,13 @@ export class AgentTeamLedger {
|
|
|
1589
1704
|
case 'team/channel-archived':
|
|
1590
1705
|
case 'team/member-removed':
|
|
1591
1706
|
case 'team/member-archived':
|
|
1707
|
+
case 'team/member-workspace-left':
|
|
1592
1708
|
return operation.data.activities.map(activity => activity.threadRef);
|
|
1593
1709
|
case 'team/initialized':
|
|
1594
1710
|
case 'team/channel-created':
|
|
1595
1711
|
case 'team/channel-updated':
|
|
1596
1712
|
case 'team/member-added':
|
|
1713
|
+
case 'team/member-workspace-joined':
|
|
1597
1714
|
case 'team/member-suspended':
|
|
1598
1715
|
case 'team/member-resumed':
|
|
1599
1716
|
case 'team/member-session-restarted':
|
|
@@ -1675,7 +1792,7 @@ export class AgentTeamLedger {
|
|
|
1675
1792
|
throw new Error('invalid initial Channel members');
|
|
1676
1793
|
for (const memberId of memberIds) {
|
|
1677
1794
|
const member = projection.members.get(memberId);
|
|
1678
|
-
if (member === undefined || member.
|
|
1795
|
+
if (member === undefined || !this.participatesInFrom(projection, member.memberId, channel.workspaceId) || member.state !== 'enabled')
|
|
1679
1796
|
throw new Error('invalid initial Channel Member');
|
|
1680
1797
|
}
|
|
1681
1798
|
return;
|
|
@@ -1773,10 +1890,10 @@ export class AgentTeamLedger {
|
|
|
1773
1890
|
|| operation.data.member.presetId !== prior.presetId
|
|
1774
1891
|
|| operation.data.member.privateMemoryPath !== prior.privateMemoryPath)
|
|
1775
1892
|
throw new Error('invalid Member update');
|
|
1776
|
-
// The renamed handle must stay unique among the
|
|
1893
|
+
// The renamed handle must stay unique among the live Members sharing any Workspace participation.
|
|
1777
1894
|
const normalized = operation.data.member.handle.normalize('NFKC').trim().toLowerCase();
|
|
1778
1895
|
for (const other of projection.members.values()) {
|
|
1779
|
-
if (other.memberId !== prior.memberId && other.state !== 'inactive' && other.
|
|
1896
|
+
if (other.memberId !== prior.memberId && other.state !== 'inactive' && this.participationOverlapFrom(projection, other.memberId, prior.memberId)
|
|
1780
1897
|
&& other.handle.normalize('NFKC').trim().toLowerCase() === normalized)
|
|
1781
1898
|
throw new Error('invalid Member update handle');
|
|
1782
1899
|
}
|
|
@@ -1786,7 +1903,7 @@ export class AgentTeamLedger {
|
|
|
1786
1903
|
assertHuman();
|
|
1787
1904
|
const channel = projection.channels.get(operation.data.channelRef);
|
|
1788
1905
|
const member = projection.members.get(operation.data.memberId);
|
|
1789
|
-
if (channel === undefined || member === undefined || member.
|
|
1906
|
+
if (channel === undefined || member === undefined || !this.participatesInFrom(projection, member.memberId, channel.workspaceId) || operation.data.workspaceId !== channel.workspaceId || projection.memberships.get(channel.channelRef)?.has(member.memberId))
|
|
1790
1907
|
throw new Error('invalid Channel membership');
|
|
1791
1908
|
return;
|
|
1792
1909
|
}
|
|
@@ -1795,12 +1912,32 @@ export class AgentTeamLedger {
|
|
|
1795
1912
|
const channel = projection.channels.get(operation.data.channelRef);
|
|
1796
1913
|
const member = projection.members.get(operation.data.memberId);
|
|
1797
1914
|
if (channel === undefined || member === undefined || operation.data.workspaceId !== channel.workspaceId
|
|
1798
|
-
|| member.
|
|
1915
|
+
|| !this.participatesInFrom(projection, member.memberId, channel.workspaceId) || !projection.memberships.get(channel.channelRef)?.has(member.memberId))
|
|
1799
1916
|
throw new Error('invalid Channel membership removal');
|
|
1800
1917
|
const threadRefs = new Set([...projection.threads.keys()].filter(threadRef => this.channelRefForThreadFrom(projection, threadRef) === channel.channelRef));
|
|
1801
1918
|
this.validateReleaseCleanup(operation.data, projection, member.memberId, threadRefs, operation.sequence, refs);
|
|
1802
1919
|
return;
|
|
1803
1920
|
}
|
|
1921
|
+
if (operation.kind === 'team/member-workspace-joined') {
|
|
1922
|
+
assertHuman();
|
|
1923
|
+
const member = projection.members.get(operation.data.memberId);
|
|
1924
|
+
if (member === undefined || member.state === 'inactive' || member.state === 'archived'
|
|
1925
|
+
|| this.participatesInFrom(projection, member.memberId, operation.data.workspaceId))
|
|
1926
|
+
throw new Error('invalid Workspace join');
|
|
1927
|
+
this.assertHandleAvailableFrom(projection, operation.data.workspaceId, member.handle);
|
|
1928
|
+
return;
|
|
1929
|
+
}
|
|
1930
|
+
if (operation.kind === 'team/member-workspace-left') {
|
|
1931
|
+
assertHuman();
|
|
1932
|
+
const member = projection.members.get(operation.data.memberId);
|
|
1933
|
+
if (member === undefined || member.state === 'inactive' || member.state === 'archived'
|
|
1934
|
+
|| operation.data.workspaceId === member.workspaceId
|
|
1935
|
+
|| !this.participatesInFrom(projection, member.memberId, operation.data.workspaceId))
|
|
1936
|
+
throw new Error('invalid Workspace leave');
|
|
1937
|
+
const threadRefs = this.workspaceThreadRefsFrom(projection, operation.data.workspaceId);
|
|
1938
|
+
this.validateReleaseCleanup(operation.data, projection, member.memberId, threadRefs, operation.sequence, refs);
|
|
1939
|
+
return;
|
|
1940
|
+
}
|
|
1804
1941
|
if (operation.kind === 'team/channel-archived') {
|
|
1805
1942
|
assertHuman();
|
|
1806
1943
|
const prior = projection.channels.get(operation.data.channel.channelRef);
|
|
@@ -2062,7 +2199,7 @@ export class AgentTeamLedger {
|
|
|
2062
2199
|
if (operation.kind === 'team/dm-sent') {
|
|
2063
2200
|
const sender = assertMember();
|
|
2064
2201
|
const recipient = projection.members.get(operation.data.recipientMemberId);
|
|
2065
|
-
if (recipient === undefined || recipient.
|
|
2202
|
+
if (recipient === undefined || !this.participatesInFrom(projection, recipient.memberId, operation.data.workspaceId)
|
|
2066
2203
|
|| recipient.state !== 'enabled' || recipient.memberId === AGENT_TEAM_HUMAN_MEMBER_ID
|
|
2067
2204
|
|| operation.data.senderMemberId !== sender.memberId
|
|
2068
2205
|
|| operation.data.recipientMemberId === sender.memberId
|
|
@@ -2253,8 +2390,9 @@ export class AgentTeamLedger {
|
|
|
2253
2390
|
if (memberId === AGENT_TEAM_HUMAN_MEMBER_ID)
|
|
2254
2391
|
return true;
|
|
2255
2392
|
const member = projection.members.get(memberId);
|
|
2393
|
+
const channelWorkspaceId = projection.channels.get(channelRef)?.workspaceId;
|
|
2256
2394
|
return member !== undefined && member.state !== 'inactive' && member.state !== 'archived'
|
|
2257
|
-
&&
|
|
2395
|
+
&& channelWorkspaceId !== undefined && this.participatesInFrom(projection, memberId, channelWorkspaceId)
|
|
2258
2396
|
&& this.isChannelMemberFrom(projection, channelRef, memberId);
|
|
2259
2397
|
}
|
|
2260
2398
|
apply(operation) {
|
|
@@ -2279,6 +2417,9 @@ export class AgentTeamLedger {
|
|
|
2279
2417
|
}
|
|
2280
2418
|
if (operation.kind === 'team/member-added') {
|
|
2281
2419
|
target.members.set(operation.data.member.memberId, operation.data.member);
|
|
2420
|
+
// Creation seeds the first Workspace participation; member.workspaceId
|
|
2421
|
+
// stays the default Workspace the Session roots in, immutable after add.
|
|
2422
|
+
target.participations.set(operation.data.member.memberId, new Set([operation.data.member.workspaceId]));
|
|
2282
2423
|
for (const channelRef of operation.data.channelRefs)
|
|
2283
2424
|
this.addMembership(target, channelRef, operation.data.member.memberId);
|
|
2284
2425
|
return;
|
|
@@ -2337,6 +2478,23 @@ export class AgentTeamLedger {
|
|
|
2337
2478
|
this.applyReleaseSnapshot(target, operation, operation.data, operation.occurredAt);
|
|
2338
2479
|
return;
|
|
2339
2480
|
}
|
|
2481
|
+
if (operation.kind === 'team/member-workspace-joined') {
|
|
2482
|
+
const workspaces = target.participations.get(operation.data.memberId) ?? new Set();
|
|
2483
|
+
workspaces.add(operation.data.workspaceId);
|
|
2484
|
+
target.participations.set(operation.data.memberId, workspaces);
|
|
2485
|
+
return;
|
|
2486
|
+
}
|
|
2487
|
+
if (operation.kind === 'team/member-workspace-left') {
|
|
2488
|
+
target.participations.get(operation.data.memberId)?.delete(operation.data.workspaceId);
|
|
2489
|
+
// Departure ends Channel memberships inside the left Workspace — unlike
|
|
2490
|
+
// archival, which keeps them hidden for a possible restore.
|
|
2491
|
+
for (const channel of target.channels.values()) {
|
|
2492
|
+
if (channel.workspaceId === operation.data.workspaceId)
|
|
2493
|
+
target.memberships.get(channel.channelRef)?.delete(operation.data.memberId);
|
|
2494
|
+
}
|
|
2495
|
+
this.applyReleaseSnapshot(target, operation, operation.data, operation.occurredAt);
|
|
2496
|
+
return;
|
|
2497
|
+
}
|
|
2340
2498
|
if (operation.kind === 'team/channel-archived') {
|
|
2341
2499
|
// Archival keeps Memberships: hidden state, not departure — a future
|
|
2342
2500
|
// restore returns every Member to the Channel. Visibility filters by
|
|
@@ -2898,7 +3056,7 @@ export class AgentTeamLedger {
|
|
|
2898
3056
|
if (memberId === AGENT_TEAM_HUMAN_MEMBER_ID)
|
|
2899
3057
|
continue;
|
|
2900
3058
|
const member = this.requireMember(memberId);
|
|
2901
|
-
if (member.state === 'inactive' || member.state === 'archived' || member.
|
|
3059
|
+
if (member.state === 'inactive' || member.state === 'archived' || !this.participatesIn(member.memberId, channel.workspaceId) || !this.isChannelMember(channel.channelRef, memberId)) {
|
|
2902
3060
|
throw new Error(`Agent Member '${memberId}' is not authorized for Channel '${channel.channelRef}'`);
|
|
2903
3061
|
}
|
|
2904
3062
|
}
|
|
@@ -3043,7 +3201,7 @@ export class AgentTeamLedger {
|
|
|
3043
3201
|
return actor;
|
|
3044
3202
|
}
|
|
3045
3203
|
const member = this.assertMemberActor(actor);
|
|
3046
|
-
if (member.
|
|
3204
|
+
if (!this.participatesIn(member.memberId, workspaceId))
|
|
3047
3205
|
throw new Error('Member cannot mutate another Workspace');
|
|
3048
3206
|
return actor;
|
|
3049
3207
|
}
|
|
@@ -3220,10 +3378,50 @@ export class AgentTeamLedger {
|
|
|
3220
3378
|
channelThreadRefsFrom(projection, channelRef) {
|
|
3221
3379
|
return new Set([...projection.threads.keys()].filter(threadRef => this.channelRefForThreadFrom(projection, threadRef) === channelRef));
|
|
3222
3380
|
}
|
|
3381
|
+
/** The Workspace authorization question: does this Member participate in this Workspace. */
|
|
3382
|
+
participatesIn(memberId, workspaceId) {
|
|
3383
|
+
return this.participatesInFrom(this.state, memberId, workspaceId);
|
|
3384
|
+
}
|
|
3385
|
+
participatesInFrom(projection, memberId, workspaceId) {
|
|
3386
|
+
return projection.participations.get(memberId)?.has(workspaceId) === true;
|
|
3387
|
+
}
|
|
3388
|
+
/** Every Workspace the Member participates in: the default first, then the rest sorted. */
|
|
3389
|
+
workspacesOf(memberId) {
|
|
3390
|
+
return Object.freeze(this.workspacesOfFrom(this.state, memberId));
|
|
3391
|
+
}
|
|
3392
|
+
workspacesOfFrom(projection, memberId) {
|
|
3393
|
+
const member = projection.members.get(memberId);
|
|
3394
|
+
if (member === undefined)
|
|
3395
|
+
return [];
|
|
3396
|
+
const rest = [...(projection.participations.get(memberId) ?? new Set())]
|
|
3397
|
+
.filter(workspaceId => workspaceId !== member.workspaceId).sort();
|
|
3398
|
+
return [member.workspaceId, ...rest];
|
|
3399
|
+
}
|
|
3400
|
+
/** Whether two Members share at least one Workspace participation — the handle-uniqueness scope. */
|
|
3401
|
+
participationOverlapFrom(projection, leftMemberId, rightMemberId) {
|
|
3402
|
+
const left = projection.participations.get(leftMemberId);
|
|
3403
|
+
const right = projection.participations.get(rightMemberId);
|
|
3404
|
+
if (left === undefined || right === undefined)
|
|
3405
|
+
return false;
|
|
3406
|
+
for (const workspaceId of left)
|
|
3407
|
+
if (right.has(workspaceId))
|
|
3408
|
+
return true;
|
|
3409
|
+
return false;
|
|
3410
|
+
}
|
|
3411
|
+
/** Every Thread in the Workspace — the scope a Workspace leave replays its cleanup against. */
|
|
3412
|
+
workspaceThreadRefs(workspaceId) {
|
|
3413
|
+
return this.workspaceThreadRefsFrom(this.state, workspaceId);
|
|
3414
|
+
}
|
|
3415
|
+
workspaceThreadRefsFrom(projection, workspaceId) {
|
|
3416
|
+
return new Set([...projection.threads.keys()].filter(threadRef => {
|
|
3417
|
+
const channelRef = this.channelRefForThreadFrom(projection, threadRef);
|
|
3418
|
+
return channelRef !== undefined && projection.channels.get(channelRef)?.workspaceId === workspaceId;
|
|
3419
|
+
}));
|
|
3420
|
+
}
|
|
3223
3421
|
assertJoinableMember(workspaceId, memberId) {
|
|
3224
3422
|
const member = this.requireMember(memberId);
|
|
3225
|
-
if (
|
|
3226
|
-
throw new Error(`Agent Member '${memberId}' does not
|
|
3423
|
+
if (!this.participatesIn(memberId, workspaceId))
|
|
3424
|
+
throw new Error(`Agent Member '${memberId}' does not participate in Workspace '${workspaceId}'`);
|
|
3227
3425
|
if (member.state !== 'enabled')
|
|
3228
3426
|
throw new Error(`Agent Member '${memberId}' is ${member.state}; only enabled Members can join a Channel`);
|
|
3229
3427
|
}
|
|
@@ -3305,9 +3503,12 @@ export class AgentTeamLedger {
|
|
|
3305
3503
|
return direction.normalize('NFKC').trim().replace(/\s+/gu, ' ').toLowerCase();
|
|
3306
3504
|
}
|
|
3307
3505
|
assertHandleAvailable(workspaceId, handle, exceptMemberId) {
|
|
3506
|
+
this.assertHandleAvailableFrom(this.state, workspaceId, handle, exceptMemberId);
|
|
3507
|
+
}
|
|
3508
|
+
assertHandleAvailableFrom(projection, workspaceId, handle, exceptMemberId) {
|
|
3308
3509
|
const normalized = handle.normalize('NFKC').trim().toLowerCase();
|
|
3309
|
-
if ([...
|
|
3310
|
-
&& member.
|
|
3510
|
+
if ([...projection.members.values()].some(member => member.memberId !== exceptMemberId && member.state !== 'inactive'
|
|
3511
|
+
&& this.participatesInFrom(projection, member.memberId, workspaceId)
|
|
3311
3512
|
&& member.handle.normalize('NFKC').trim().toLowerCase() === normalized)) {
|
|
3312
3513
|
throw new Error(`Agent Member handle '${handle}' is already active in Workspace '${workspaceId}'`);
|
|
3313
3514
|
}
|
|
@@ -3409,6 +3610,16 @@ export class AgentTeamLedger {
|
|
|
3409
3610
|
|| operation.data.workspaceId !== request.workspaceId || operation.data.channelRef !== request.channelRef || operation.data.memberId !== request.memberId)
|
|
3410
3611
|
this.throwRequestCollision(request.requestId);
|
|
3411
3612
|
}
|
|
3613
|
+
assertSameWorkspaceJoin(operation, request) {
|
|
3614
|
+
if (operation.kind !== 'team/member-workspace-joined' || !this.sameActor(operation.actor, request.actor)
|
|
3615
|
+
|| operation.data.workspaceId !== request.workspaceId || operation.data.memberId !== request.memberId)
|
|
3616
|
+
this.throwRequestCollision(request.requestId);
|
|
3617
|
+
}
|
|
3618
|
+
assertSameWorkspaceLeave(operation, request) {
|
|
3619
|
+
if (operation.kind !== 'team/member-workspace-left' || !this.sameActor(operation.actor, request.actor)
|
|
3620
|
+
|| operation.data.workspaceId !== request.workspaceId || operation.data.memberId !== request.memberId)
|
|
3621
|
+
this.throwRequestCollision(request.requestId);
|
|
3622
|
+
}
|
|
3412
3623
|
assertSameChannelArchival(operation, request) {
|
|
3413
3624
|
if (operation.kind !== 'team/channel-archived' || !this.sameActor(operation.actor, request.actor)
|
|
3414
3625
|
|| operation.data.workspaceId !== request.workspaceId || operation.data.channel.channelRef !== request.channelRef)
|
|
@@ -3564,6 +3775,13 @@ export class AgentTeamLedger {
|
|
|
3564
3775
|
return Object.freeze({ receipt: this.receipt(operation), channelRef: operation.data.channelRef, memberId: operation.data.memberId,
|
|
3565
3776
|
releasedClaims: operation.data.claims, removedAttention: operation.data.inbox.attention.removed });
|
|
3566
3777
|
}
|
|
3778
|
+
workspaceJoinResult(operation) {
|
|
3779
|
+
return Object.freeze({ receipt: this.receipt(operation), memberId: operation.data.memberId, workspaceId: operation.data.workspaceId });
|
|
3780
|
+
}
|
|
3781
|
+
workspaceLeaveResult(operation) {
|
|
3782
|
+
return Object.freeze({ receipt: this.receipt(operation), memberId: operation.data.memberId, workspaceId: operation.data.workspaceId,
|
|
3783
|
+
releasedClaims: operation.data.claims, removedAttention: operation.data.inbox.attention.removed });
|
|
3784
|
+
}
|
|
3567
3785
|
channelArchivalResult(operation) {
|
|
3568
3786
|
return Object.freeze({ receipt: this.receipt(operation), channel: operation.data.channel,
|
|
3569
3787
|
releasedClaims: operation.data.claims });
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
2
3
|
import { createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
3
4
|
import { SessionLogOffset } from '@deepseek-ai/dsh-session';
|
|
4
5
|
import { memberMemoryDirectoryPath } from "./member-runtime.js";
|
|
@@ -33,7 +34,8 @@ export function apply(ctx) {
|
|
|
33
34
|
? 'memory.md is absent; the private memory index is empty.'
|
|
34
35
|
: 'memory.md is currently unreadable; do not use any earlier private memory context.');
|
|
35
36
|
}
|
|
36
|
-
const
|
|
37
|
+
const workspaces = renderMemberWorkspaces(host.workspacesForAgent(agent));
|
|
38
|
+
const text = `${renderMemberIdentity(member)}\n\n${memory}${workspaces === '' ? '' : `\n\n${workspaces}`}`;
|
|
37
39
|
const latestText = agent.session.surface.nodes.toReversed().flatMap(sequence => {
|
|
38
40
|
// `nodes` are event identities (SessionSeq); snapshotEvents takes log
|
|
39
41
|
// offsets. Re-entering through the validating constructor keeps the two
|
|
@@ -61,6 +63,21 @@ export function renderMemberIdentity(member) {
|
|
|
61
63
|
? `Team identity: you are @${member.handle}.`
|
|
62
64
|
: `Team identity: you are @${member.handle} — ${member.description}`;
|
|
63
65
|
}
|
|
66
|
+
/** A current address list, not a copy of instructions from another checkout. */
|
|
67
|
+
export function renderMemberWorkspaces(workspaces) {
|
|
68
|
+
if (workspaces.length <= 1)
|
|
69
|
+
return '';
|
|
70
|
+
return [
|
|
71
|
+
'Team Workspace participation — this list replaces all earlier participation context.',
|
|
72
|
+
...workspaces.map(workspace => `${workspace.workspaceId}${workspace.default ? ' (default; Session cwd)' : ''}: ${workspace.path === undefined
|
|
73
|
+
? 'Workspace path unavailable; ask @human before filesystem work.'
|
|
74
|
+
: `${JSON.stringify(workspace.path)}; instructions: ${JSON.stringify(join(workspace.path, 'AGENTS.md'))}`}`),
|
|
75
|
+
'Your single Session and cwd stay in the default Workspace. Joining another Workspace does not move them.',
|
|
76
|
+
'For filesystem work in another Workspace, use absolute paths (or an explicit command cwd). Before working there, read its AGENTS.md and applicable directory instructions yourself; their contents are not injected here.',
|
|
77
|
+
'Pass workspace explicitly to team_view, team_thread, team_message and team_claim. team_inbox is the cross-Workspace triage entry point.',
|
|
78
|
+
'If an instruction is ambiguous about which Workspace it concerns, ask @human before acting. Participation does not itself join Channels.',
|
|
79
|
+
].join('\n');
|
|
80
|
+
}
|
|
64
81
|
/** The four private-memory paths plus the out-of-cwd warning; callers append their own sentence. */
|
|
65
82
|
function memoryPathsBlock(privateMemoryPath) {
|
|
66
83
|
return `Private memory directory: ${privateMemoryPath}\nMemory index: ${privateMemoryPath}/memory.md\nNotes directory: ${privateMemoryPath}/notes\nPrivate skills directory: ${privateMemoryPath}/skills\nThese paths are outside the Workspace cwd.`;
|
|
@@ -311,7 +311,8 @@ export class MemberRuntime {
|
|
|
311
311
|
* moved home) holds a sanitized target in ITS tree while the record still names
|
|
312
312
|
* the real one. Renaming across trees would take a Member's whole private
|
|
313
313
|
* memory out of its home — silently, since only the next session notices an
|
|
314
|
-
* empty index. Such a source is left exactly where it is
|
|
314
|
+
* empty index. Such a source is left exactly where it is, and only that
|
|
315
|
+
* refusal is reported back: every other outcome needs nothing from the caller.
|
|
315
316
|
*/
|
|
316
317
|
async function migrateLegacyMemoryDirectory(legacyPath, path) {
|
|
317
318
|
let legacy;
|
|
@@ -320,7 +321,7 @@ async function migrateLegacyMemoryDirectory(legacyPath, path) {
|
|
|
320
321
|
}
|
|
321
322
|
catch (error) {
|
|
322
323
|
if (error.code === 'ENOENT')
|
|
323
|
-
return
|
|
324
|
+
return undefined;
|
|
324
325
|
throw error;
|
|
325
326
|
}
|
|
326
327
|
if (!legacy.isDirectory())
|
|
@@ -329,14 +330,14 @@ async function migrateLegacyMemoryDirectory(legacyPath, path) {
|
|
|
329
330
|
return 'refused-foreign-home';
|
|
330
331
|
try {
|
|
331
332
|
await stat(path);
|
|
332
|
-
return
|
|
333
|
+
return undefined; // Sanitized directory already present: migration already done or a new install.
|
|
333
334
|
}
|
|
334
335
|
catch (error) {
|
|
335
336
|
if (error.code !== 'ENOENT')
|
|
336
337
|
throw error;
|
|
337
338
|
}
|
|
338
339
|
await rename(legacyPath, path);
|
|
339
|
-
return
|
|
340
|
+
return undefined;
|
|
340
341
|
}
|
|
341
342
|
/**
|
|
342
343
|
* Whether one absolute path names a Member directory inside THIS process's
|