@wowyuarm/dsh-agent-team 0.1.7 → 0.1.8
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 +1 -1
- package/packages/agent-team/core-skills/member-skill-manager/SKILL.md +2 -0
- package/packages/agent-team/lib/index.js +58 -0
- package/packages/agent-team/lib/ledger.js +135 -12
- package/packages/agent-team/lib/member-context.js +1 -1
- package/packages/agent-team/lib/progress-nudge.js +320 -0
- package/packages/agent-team/lib/typert.host.js +27 -26
- package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
- package/packages/agent-team/lib/typert.remote-client.js +26 -25
- package/packages/agent-team/lib/types/index.d.ts +8 -0
- package/packages/agent-team/lib/types/index.d.ts.map +1 -1
- package/packages/agent-team/lib/types/ledger.d.ts +27 -2
- package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
- 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/types/entities.d.ts +18 -0
- package/packages/agent-team/lib/types/types/entities.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types/requests-results.d.ts +2 -0
- package/packages/agent-team/lib/types/types/requests-results.d.ts.map +1 -1
- package/packages/agent-team/preset/team-member/agent.cordis.yml +2 -2
- package/packages/client-agent-team/lib/client.js +406 -512
- package/packages/client-agent-team/lib/client.js.map +1 -1
- 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/index.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/index.js +2 -67
- package/packages/client-agent-team/lib/types/client/locales.d.ts +0 -2
- 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 -2
- 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 +3 -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/lib/index.js +9 -9
- 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
package/package.json
CHANGED
|
@@ -7,6 +7,8 @@ description: Create, install, revise, or retire this Member's private skills, in
|
|
|
7
7
|
|
|
8
8
|
A skill preserves a reusable way of working. It is not a memory, a personal preference, or a record of one event.
|
|
9
9
|
|
|
10
|
+
Skills are private to this Member — no other human or agent can read this directory. When a Team message or your session output needs the method, restate the relevant content there; citing a skill by name does not transfer it to anyone.
|
|
11
|
+
|
|
10
12
|
## Decide
|
|
11
13
|
|
|
12
14
|
Create or revise a skill when the method has been demonstrated in real work and will make later work clearer or more reliable. Keep one-off work, changing facts, and personal continuity in this Member's notes and memory index instead.
|
|
@@ -53,6 +53,7 @@ import { ATTACHMENT_MAX_BYTES, attachmentPayloadPath, attachmentsRoot, copyPathA
|
|
|
53
53
|
import { acceptedTaskCompactionMembers, AutoCompactionCoordinator, PRE_COMPACTION_NOTICE_SUMMARY, preCompactionNoticeText } from "./auto-compaction.js";
|
|
54
54
|
import { AGENT_TEAM_HUMAN_MEMBER_ID, AgentTeamLedger, agentTeamHumanActor } from "./ledger.js";
|
|
55
55
|
import { AGENT_TEAM_TOOL_NAMES, deepCopyCapabilities, MemberRuntime } from "./member-runtime.js";
|
|
56
|
+
import { ProgressNudgeCoordinator } from "./progress-nudge.js";
|
|
56
57
|
import { classifyRecoverableError, RecoveryCoordinator, RECOVERY_MAX_CONSECUTIVE_ERRORS } from "./recovery.js";
|
|
57
58
|
import { agentTeamDomainSpec } from "./spec.js";
|
|
58
59
|
export { agentTeamDomainSpec, agentTeamOperationSchema } from "./spec.js";
|
|
@@ -189,6 +190,8 @@ let AgentTeam = (() => {
|
|
|
189
190
|
domain = __runInitializers(this, _instanceExtraInitializers);
|
|
190
191
|
ledger;
|
|
191
192
|
handles = new Map();
|
|
193
|
+
/** Live Member per session id; drives the root session/event listener. */
|
|
194
|
+
memberBySessionId = new Map();
|
|
192
195
|
/** Live selection refs let model edits take effect without disposing the Session. */
|
|
193
196
|
modelSelections = new Map();
|
|
194
197
|
/** Agent ids with a turn in flight; restarts must wait for the boundary. */
|
|
@@ -226,6 +229,22 @@ let AgentTeam = (() => {
|
|
|
226
229
|
this.ctx.logger.warn(`agent-team: member '${this.memberLabel(memberId)}' reached ${consecutiveFailures}/${RECOVERY_MAX_CONSECUTIVE_ERRORS} consecutive recoverable failures; leaving it in error for the operator`);
|
|
227
230
|
},
|
|
228
231
|
});
|
|
232
|
+
/**
|
|
233
|
+
* Advisory progress-visibility nudges. Purely in-process: the ledger owns
|
|
234
|
+
* eligibility, this coordinator owns counting and notices, and neither ever
|
|
235
|
+
* writes Team facts. See docs/team-collaboration.md for the regimes.
|
|
236
|
+
*/
|
|
237
|
+
progressNudge = new ProgressNudgeCoordinator({
|
|
238
|
+
agentForMember: memberId => this.handles.get(memberId)?.agent,
|
|
239
|
+
targetsForMember: memberId => this.requireLedger().progressNudgeTargets(memberId),
|
|
240
|
+
sessionLogForMember: (memberId, sessionId) => {
|
|
241
|
+
const handle = this.handles.get(memberId);
|
|
242
|
+
if (handle === undefined || handle.agent.session.id !== sessionId)
|
|
243
|
+
return undefined;
|
|
244
|
+
return { events: handle.agent.session.ownEvents() };
|
|
245
|
+
},
|
|
246
|
+
log: message => { this.ctx.logger.warn(message); },
|
|
247
|
+
});
|
|
229
248
|
lifecycleTail = Promise.resolve();
|
|
230
249
|
accepting = true;
|
|
231
250
|
changeVersion = 0;
|
|
@@ -252,6 +271,10 @@ let AgentTeam = (() => {
|
|
|
252
271
|
content: [{ type: 'text', text: preCompactionNoticeText() }],
|
|
253
272
|
source: { kind: 'plugin', plugin: AGENT_TEAM_PLUGIN_ID, form: 'notice', summary: PRE_COMPACTION_NOTICE_SUMMARY },
|
|
254
273
|
});
|
|
274
|
+
// Persist-your-conclusions outranks a queued progress nudge.
|
|
275
|
+
const member = this.memberForAgent(agent);
|
|
276
|
+
if (member !== undefined)
|
|
277
|
+
this.progressNudge.revokePendingNotice(member.memberId);
|
|
255
278
|
agent.steer(hint);
|
|
256
279
|
},
|
|
257
280
|
});
|
|
@@ -295,10 +318,28 @@ let AgentTeam = (() => {
|
|
|
295
318
|
this.emitChanged([{ kind: 'workspace', workspaceId: member.workspaceId }]);
|
|
296
319
|
}
|
|
297
320
|
});
|
|
321
|
+
// Progress nudges count every `tool/call` of each Member Session. The
|
|
322
|
+
// store's dispatch carrier is untagged, so a scope-tagged listener inside
|
|
323
|
+
// the Agent setup would receive nothing; one root listener that maps the
|
|
324
|
+
// session id back to its Member is the seam that works (same shape as the
|
|
325
|
+
// `agent/status` listener above). The first turn of a freshly published
|
|
326
|
+
// Member cannot race this: `handles.set()` precedes the same activation
|
|
327
|
+
// continuation that publishes the Agent, so by the time any tool call
|
|
328
|
+
// streams, the map lookup succeeds.
|
|
329
|
+
this.ctx.on('session/event', (session, event) => {
|
|
330
|
+
const memberId = this.memberBySessionId.get(session.id);
|
|
331
|
+
if (memberId === undefined)
|
|
332
|
+
return;
|
|
333
|
+
const handle = this.handles.get(memberId);
|
|
334
|
+
if (handle === undefined || handle.agent.session.id !== session.id)
|
|
335
|
+
return;
|
|
336
|
+
this.progressNudge.onSessionEvent(memberId, session.id, handle.agent, event);
|
|
337
|
+
});
|
|
298
338
|
const domain = await this.ctx.storageDomain.open(agentTeamDomainSpec);
|
|
299
339
|
this.ctx.effect(() => async () => {
|
|
300
340
|
this.accepting = false;
|
|
301
341
|
this.recovery.dispose();
|
|
342
|
+
this.progressNudge.dispose();
|
|
302
343
|
await this.autoCompaction.dispose();
|
|
303
344
|
if (this.attachmentGcTimer !== undefined)
|
|
304
345
|
clearInterval(this.attachmentGcTimer);
|
|
@@ -566,6 +607,10 @@ let AgentTeam = (() => {
|
|
|
566
607
|
// Drop the old handle's transient state: pending recovery episodes and
|
|
567
608
|
// error markers belong to the disposed agent, not to the Member.
|
|
568
609
|
this.recovery.stopTracking(request.memberId);
|
|
610
|
+
// The fresh Session may re-earn one Claim suggestion per Thread; the
|
|
611
|
+
// old Session's one-shot records must not leak into it.
|
|
612
|
+
this.progressNudge.stopTracking(request.memberId);
|
|
613
|
+
this.memberBySessionId.delete(previousSessionId);
|
|
569
614
|
await active.dispose();
|
|
570
615
|
this.handles.delete(request.memberId);
|
|
571
616
|
this.modelSelections.delete(request.memberId);
|
|
@@ -628,6 +673,9 @@ let AgentTeam = (() => {
|
|
|
628
673
|
if (this.isInboxNotice(pending))
|
|
629
674
|
handle.agent.inbox.remove(pending.id);
|
|
630
675
|
}
|
|
676
|
+
// Recovery carries the concrete interrupted work; a queued progress nudge
|
|
677
|
+
// is stale next to it and must not survive as a second reminder.
|
|
678
|
+
this.progressNudge.revokePendingNotice(member.memberId);
|
|
631
679
|
handle.agent.steer(hint);
|
|
632
680
|
}
|
|
633
681
|
/** Automatic-recovery wakeup; throwing tells the coordinator the target is gone. */
|
|
@@ -1090,6 +1138,7 @@ let AgentTeam = (() => {
|
|
|
1090
1138
|
this.memberRuntime.mountMemberSkillProvider(member, created.agent.ctx, skillSelection);
|
|
1091
1139
|
await workspace.attachSession(member.sessionId);
|
|
1092
1140
|
this.handles.set(member.memberId, created);
|
|
1141
|
+
this.memberBySessionId.set(member.sessionId, member.memberId);
|
|
1093
1142
|
this.modelSelections.set(member.memberId, selected);
|
|
1094
1143
|
this.clearMemberFailure(member.memberId, 'activation');
|
|
1095
1144
|
this.nameMemberSession(member, created.agent);
|
|
@@ -1233,6 +1282,10 @@ let AgentTeam = (() => {
|
|
|
1233
1282
|
}
|
|
1234
1283
|
const ledger = this.requireLedger();
|
|
1235
1284
|
this.emitChanged(ledger.changeScopesOf(operation));
|
|
1285
|
+
// Nudge reconciliation precedes notifyMember: a committed reply both
|
|
1286
|
+
// resets the author's silence and cancels any of its queued nudge before
|
|
1287
|
+
// the Inbox notice (higher priority) is considered.
|
|
1288
|
+
this.progressNudge.onCommitted(operation);
|
|
1236
1289
|
for (const memberId of ledger.affectedMembersOf(operation)) {
|
|
1237
1290
|
const handle = this.handles.get(memberId);
|
|
1238
1291
|
if (handle !== undefined)
|
|
@@ -1271,6 +1324,9 @@ let AgentTeam = (() => {
|
|
|
1271
1324
|
const existingInboxHint = pending.find(message => this.isInboxNotice(message));
|
|
1272
1325
|
if (existingInboxHint !== undefined)
|
|
1273
1326
|
agent.inbox.remove(existingInboxHint.id);
|
|
1327
|
+
// A durable Inbox notice outranks a queued progress nudge; revoke it so
|
|
1328
|
+
// the model reads concrete unread work instead of a generic reminder.
|
|
1329
|
+
this.progressNudge.revokePendingNotice(member.memberId);
|
|
1274
1330
|
const hint = createUserMessage({
|
|
1275
1331
|
content: [{ type: 'text', text: this.notificationText(notifications, member.memberId) }],
|
|
1276
1332
|
source: { kind: 'plugin', plugin: AGENT_TEAM_PLUGIN_ID, form: 'notice', summary: INBOX_NOTICE_SUMMARY },
|
|
@@ -1376,12 +1432,14 @@ let AgentTeam = (() => {
|
|
|
1376
1432
|
await handle.dispose();
|
|
1377
1433
|
this.handles.delete(memberId);
|
|
1378
1434
|
}
|
|
1435
|
+
this.memberBySessionId.delete(member.sessionId);
|
|
1379
1436
|
this.modelSelections.delete(memberId);
|
|
1380
1437
|
this.memberRuntime.forgetMember(memberId);
|
|
1381
1438
|
this.clearMemberFailure(memberId, 'activation');
|
|
1382
1439
|
}
|
|
1383
1440
|
clearMemberRecoveryState(member) {
|
|
1384
1441
|
this.recovery.stopTracking(member.memberId);
|
|
1442
|
+
this.progressNudge.stopTracking(member.memberId);
|
|
1385
1443
|
this.clearMemberFailure(member.memberId, 'runtime');
|
|
1386
1444
|
this.clearMemberNotificationState(member.memberId);
|
|
1387
1445
|
}
|
|
@@ -19,6 +19,10 @@ function emptyProjection() {
|
|
|
19
19
|
orderedFacts: [], factsByThread: new Map(), channelRefByThread: new Map(), mentionsByMessage: new Map(), messageCountByThread: new Map(),
|
|
20
20
|
attentionByThread: 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}'`);
|
|
@@ -246,6 +250,80 @@ export class AgentTeamLedger {
|
|
|
246
250
|
getMember(memberId) {
|
|
247
251
|
return this.state.members.get(memberId);
|
|
248
252
|
}
|
|
253
|
+
/**
|
|
254
|
+
* Current progress-nudge candidacy for one Member: threads whose Thread
|
|
255
|
+
* progress reminder applies (active Claim, or following a taskless Thread)
|
|
256
|
+
* and tasks where the Claim reminder applies (following a still-`todo` Task
|
|
257
|
+
* with no Claim history for this Member). A deep read-only projection — the
|
|
258
|
+
* caller never re-derives attention, claim, task-resolution, or channel
|
|
259
|
+
* archive state. Archived channels and resolved tasks produce no candidacy;
|
|
260
|
+
* results are ordered by Thread revision descending, then threadRef.
|
|
261
|
+
*/
|
|
262
|
+
progressNudgeTargets(memberId) {
|
|
263
|
+
const member = this.state.members.get(memberId);
|
|
264
|
+
if (member === undefined || member.state !== 'enabled')
|
|
265
|
+
return EMPTY_PROGRESS_NUDGE_TARGETS;
|
|
266
|
+
const progress = [];
|
|
267
|
+
const claim = [];
|
|
268
|
+
const seenProgress = new Set();
|
|
269
|
+
for (const claimRecord of this.state.claims.values()) {
|
|
270
|
+
if (claimRecord.owner !== memberId || claimRecord.state !== 'active')
|
|
271
|
+
continue;
|
|
272
|
+
const task = this.state.tasks.get(claimRecord.taskRef);
|
|
273
|
+
const thread = this.state.threads.get(claimRecord.threadRef);
|
|
274
|
+
if (task === undefined || thread === undefined || task.resolution !== 'open')
|
|
275
|
+
continue;
|
|
276
|
+
if (this.state.channels.get(task.channelRef)?.state === 'archived')
|
|
277
|
+
continue;
|
|
278
|
+
if (seenProgress.has(claimRecord.threadRef))
|
|
279
|
+
continue;
|
|
280
|
+
seenProgress.add(claimRecord.threadRef);
|
|
281
|
+
progress.push({ reason: 'active-claim', threadRef: claimRecord.threadRef, taskRef: claimRecord.taskRef });
|
|
282
|
+
}
|
|
283
|
+
for (const [threadRef, followers] of this.state.attentionByThread) {
|
|
284
|
+
if (!followers.has(memberId))
|
|
285
|
+
continue;
|
|
286
|
+
const thread = this.state.threads.get(threadRef);
|
|
287
|
+
if (thread === undefined)
|
|
288
|
+
continue;
|
|
289
|
+
const channelRef = this.state.channelRefByThread.get(threadRef);
|
|
290
|
+
if (channelRef === undefined || this.state.channels.get(channelRef)?.state === 'archived')
|
|
291
|
+
continue;
|
|
292
|
+
if (thread.taskRef === undefined) {
|
|
293
|
+
if (!seenProgress.has(threadRef)) {
|
|
294
|
+
seenProgress.add(threadRef);
|
|
295
|
+
progress.push({ reason: 'taskless-follower', threadRef });
|
|
296
|
+
}
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
// Claim reminder: only still-`todo` tasks with zero lifetime claims by
|
|
300
|
+
// this Member; once claimed (any state) the Member moved into the
|
|
301
|
+
// progress-reminder regime.
|
|
302
|
+
if (this.hasLifetimeClaimOnTask(memberId, thread.taskRef))
|
|
303
|
+
continue;
|
|
304
|
+
const task = this.state.tasks.get(thread.taskRef);
|
|
305
|
+
if (task === undefined || task.resolution !== 'open' || this.deriveTaskStatus(task.taskRef, this.state.claims.values()) !== 'todo')
|
|
306
|
+
continue;
|
|
307
|
+
claim.push({ threadRef, taskRef: thread.taskRef });
|
|
308
|
+
}
|
|
309
|
+
const byThreadRevision = (left, right) => {
|
|
310
|
+
const leftRevision = this.state.threads.get(left.threadRef)?.revision ?? 0;
|
|
311
|
+
const rightRevision = this.state.threads.get(right.threadRef)?.revision ?? 0;
|
|
312
|
+
return rightRevision - leftRevision || (left.threadRef < right.threadRef ? -1 : left.threadRef > right.threadRef ? 1 : 0);
|
|
313
|
+
};
|
|
314
|
+
return Object.freeze({
|
|
315
|
+
progress: Object.freeze(progress.sort(byThreadRevision)),
|
|
316
|
+
claim: Object.freeze(claim.sort(byThreadRevision)),
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
/** Whether this Member has ever held a Claim on the Task, in any state. */
|
|
320
|
+
hasLifetimeClaimOnTask(memberId, taskRef) {
|
|
321
|
+
for (const claim of this.state.claims.values()) {
|
|
322
|
+
if (claim.owner === memberId && claim.taskRef === taskRef)
|
|
323
|
+
return true;
|
|
324
|
+
}
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
249
327
|
listMembers() {
|
|
250
328
|
return Object.freeze([...this.state.members.values()]);
|
|
251
329
|
}
|
|
@@ -518,7 +596,7 @@ export class AgentTeamLedger {
|
|
|
518
596
|
else {
|
|
519
597
|
if (request.direction !== undefined)
|
|
520
598
|
throw new Error(`${request.action} action does not accept direction`);
|
|
521
|
-
const previous = request.claimRef === undefined ? undefined : this.state.claims.get(request.claimRef);
|
|
599
|
+
const previous = request.claimRef === undefined ? undefined : this.state.claims.get(this.requireRefKey(this.state.claims, request.claimRef, 'claim', 'Claim'));
|
|
522
600
|
if (previous === undefined) {
|
|
523
601
|
throw new Error(`unknown Claim '${request.claimRef ?? ''}'${request.claimRef === undefined ? '' : this.unknownRefHint(request.claimRef, 'claim', 'Claim')}`);
|
|
524
602
|
}
|
|
@@ -897,7 +975,7 @@ export class AgentTeamLedger {
|
|
|
897
975
|
memberId: next.memberId, action: 'follow' }));
|
|
898
976
|
}
|
|
899
977
|
}
|
|
900
|
-
return Object.freeze({ items: Object.freeze(observations.slice(-limit)) });
|
|
978
|
+
return Object.freeze({ items: Object.freeze(observations.slice(-limit)), followers: Object.freeze([...current.keys()]) });
|
|
901
979
|
}
|
|
902
980
|
/** Every operation carrying an inbox delta drives the Inbox projection; the payload shape decides, not a per-kind list. */
|
|
903
981
|
attentionDelta(operation) {
|
|
@@ -946,7 +1024,8 @@ export class AgentTeamLedger {
|
|
|
946
1024
|
const numbers = this.taskNumbers(workspaceId);
|
|
947
1025
|
const resolved = [];
|
|
948
1026
|
for (const taskRef of taskRefs) {
|
|
949
|
-
const
|
|
1027
|
+
const key = this.uniqueRefKey(this.state.tasks, taskRef, 'task');
|
|
1028
|
+
const task = key === undefined ? undefined : this.state.tasks.get(key);
|
|
950
1029
|
const channel = task === undefined ? undefined : this.state.channels.get(task.channelRef);
|
|
951
1030
|
// Archived Channels do not exist on Team API surfaces: their Tasks stop
|
|
952
1031
|
// resolving, so live message bodies render the refs as plain
|
|
@@ -2433,13 +2512,55 @@ export class AgentTeamLedger {
|
|
|
2433
2512
|
throw new Error('agent-team operation lacks enabled Member authority');
|
|
2434
2513
|
return member;
|
|
2435
2514
|
}
|
|
2436
|
-
/**
|
|
2515
|
+
/** Shortest accepted UUID abbreviation after the branded prefix. */
|
|
2516
|
+
static MIN_REF_UUID_PREFIX = 6;
|
|
2517
|
+
isRefAbbreviationTail(tail) {
|
|
2518
|
+
return tail.length >= AgentTeamLedger.MIN_REF_UUID_PREFIX && /^[0-9a-f]+$/i.test(tail);
|
|
2519
|
+
}
|
|
2520
|
+
/**
|
|
2521
|
+
* Full map key for one branded ref, tolerating a unique UUID abbreviation.
|
|
2522
|
+
* Exact refs win; an abbreviated ref (prefix plus at least 6 hex chars,
|
|
2523
|
+
* hyphens ignored) resolves only when it matches exactly one key. Returns
|
|
2524
|
+
* undefined for unknown or ambiguous refs so lenient callers can degrade
|
|
2525
|
+
* without throwing.
|
|
2526
|
+
*/
|
|
2527
|
+
uniqueRefKey(map, ref, prefix) {
|
|
2528
|
+
if (map.has(ref))
|
|
2529
|
+
return ref;
|
|
2530
|
+
const tail = ref.startsWith(`${prefix}:`) ? ref.slice(prefix.length + 1).replaceAll('-', '') : undefined;
|
|
2531
|
+
if (tail === undefined || !this.isRefAbbreviationTail(tail))
|
|
2532
|
+
return undefined;
|
|
2533
|
+
const lower = tail.toLowerCase();
|
|
2534
|
+
const candidates = [...map.keys()].filter(key => key.slice(prefix.length + 1).replaceAll('-', '').startsWith(lower));
|
|
2535
|
+
return candidates.length === 1 ? candidates[0] : undefined;
|
|
2536
|
+
}
|
|
2537
|
+
/** uniqueRefKey with the established unknown/ambiguous error contract and hints. */
|
|
2538
|
+
requireRefKey(map, ref, prefix, label) {
|
|
2539
|
+
const key = this.uniqueRefKey(map, ref, prefix);
|
|
2540
|
+
if (key !== undefined)
|
|
2541
|
+
return key;
|
|
2542
|
+
const tail = ref.startsWith(`${prefix}:`) ? ref.slice(prefix.length + 1).replaceAll('-', '') : undefined;
|
|
2543
|
+
if (tail !== undefined && this.isRefAbbreviationTail(tail)) {
|
|
2544
|
+
const lower = tail.toLowerCase();
|
|
2545
|
+
const candidates = [...map.keys()].filter(candidate => candidate.slice(prefix.length + 1).replaceAll('-', '').startsWith(lower));
|
|
2546
|
+
if (candidates.length > 1) {
|
|
2547
|
+
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`);
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
throw new Error(`unknown ${label} ref '${ref}'${this.unknownRefHint(ref, prefix, label)}`);
|
|
2551
|
+
}
|
|
2552
|
+
/** Agents strip the branded prefix or abbreviate UUIDs when echoing refs; point at the fix instead of a bare lookup failure. */
|
|
2437
2553
|
unknownRefHint(ref, prefix, label) {
|
|
2438
|
-
|
|
2439
|
-
|
|
2554
|
+
if (!ref.startsWith(`${prefix}:`)) {
|
|
2555
|
+
return ` A ${label} ref must start with '${prefix}:'; reuse the full ref exactly as returned by Team tools ('${prefix}:${ref}').`;
|
|
2556
|
+
}
|
|
2557
|
+
const tail = ref.slice(prefix.length + 1).replaceAll('-', '');
|
|
2558
|
+
return this.isRefAbbreviationTail(tail)
|
|
2559
|
+
? ` No ${label} matches this UUID prefix; reuse the full ref exactly as returned by Team tools.`
|
|
2560
|
+
: ` A ${label} ref needs at least 6 hex characters after '${prefix}:', or the full ref exactly as returned by Team tools.`;
|
|
2440
2561
|
}
|
|
2441
2562
|
requireTask(workspaceId, taskRef) {
|
|
2442
|
-
const task = this.state.tasks.get(taskRef);
|
|
2563
|
+
const task = this.state.tasks.get(this.requireRefKey(this.state.tasks, taskRef, 'task', 'Task'));
|
|
2443
2564
|
if (task === undefined)
|
|
2444
2565
|
throw new Error(`unknown Task ref '${taskRef}'${this.unknownRefHint(taskRef, 'task', 'Task')}`);
|
|
2445
2566
|
if (this.state.channels.get(task.channelRef)?.workspaceId !== workspaceId)
|
|
@@ -2447,7 +2568,7 @@ export class AgentTeamLedger {
|
|
|
2447
2568
|
return task;
|
|
2448
2569
|
}
|
|
2449
2570
|
requireThread(threadRef) {
|
|
2450
|
-
const thread = this.state.threads.get(threadRef);
|
|
2571
|
+
const thread = this.state.threads.get(this.requireRefKey(this.state.threads, threadRef, 'thread', 'Thread'));
|
|
2451
2572
|
if (thread === undefined)
|
|
2452
2573
|
throw new Error(`unknown Thread ref '${threadRef}'${this.unknownRefHint(threadRef, 'thread', 'Thread')}`);
|
|
2453
2574
|
return thread;
|
|
@@ -2470,13 +2591,15 @@ export class AgentTeamLedger {
|
|
|
2470
2591
|
let thread;
|
|
2471
2592
|
let task;
|
|
2472
2593
|
if (request.threadRef !== undefined) {
|
|
2473
|
-
|
|
2594
|
+
const threadKey = this.requireRefKey(projection.threads, request.threadRef, 'thread', 'Thread');
|
|
2595
|
+
thread = projection.threads.get(threadKey);
|
|
2474
2596
|
if (thread === undefined)
|
|
2475
2597
|
throw new Error(`unknown Thread ref '${request.threadRef}'${this.unknownRefHint(request.threadRef, 'thread', 'Thread')}`);
|
|
2476
2598
|
task = thread.taskRef === undefined ? undefined : projection.tasks.get(thread.taskRef);
|
|
2477
2599
|
}
|
|
2478
2600
|
if (request.taskRef !== undefined) {
|
|
2479
|
-
const
|
|
2601
|
+
const taskKey = this.requireRefKey(projection.tasks, request.taskRef, 'task', 'Task');
|
|
2602
|
+
const byTask = projection.tasks.get(taskKey);
|
|
2480
2603
|
if (byTask === undefined)
|
|
2481
2604
|
throw new Error(`unknown Task ref '${request.taskRef}'${this.unknownRefHint(request.taskRef, 'task', 'Task')}`);
|
|
2482
2605
|
if (task !== undefined && task.taskRef !== byTask.taskRef)
|
|
@@ -2507,7 +2630,7 @@ export class AgentTeamLedger {
|
|
|
2507
2630
|
return { ...(task === undefined ? {} : { task }), thread, channelRef };
|
|
2508
2631
|
}
|
|
2509
2632
|
requireChannel(workspaceId, channelRef) {
|
|
2510
|
-
const channel = this.state.channels.get(channelRef);
|
|
2633
|
+
const channel = this.state.channels.get(this.requireRefKey(this.state.channels, channelRef, 'channel', 'Channel'));
|
|
2511
2634
|
if (channel === undefined)
|
|
2512
2635
|
throw new Error(`unknown Channel ref '${channelRef}'${this.unknownRefHint(channelRef, 'channel', 'Channel')}`);
|
|
2513
2636
|
if (channel.workspaceId !== workspaceId)
|
|
@@ -2528,7 +2651,7 @@ export class AgentTeamLedger {
|
|
|
2528
2651
|
}
|
|
2529
2652
|
}
|
|
2530
2653
|
requireMember(memberId) {
|
|
2531
|
-
const member = this.state.members.get(memberId);
|
|
2654
|
+
const member = this.state.members.get(this.requireRefKey(this.state.members, memberId, 'member', 'Agent Member'));
|
|
2532
2655
|
if (member === undefined)
|
|
2533
2656
|
throw new Error(`unknown Agent Member '${memberId}'`);
|
|
2534
2657
|
return member;
|
|
@@ -63,7 +63,7 @@ export function renderMemberMemory(raw, privateMemoryPath = '<private-memory-pat
|
|
|
63
63
|
const warning = overBudget
|
|
64
64
|
? '\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
65
|
: '';
|
|
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}`;
|
|
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. 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
67
|
}
|
|
68
68
|
function renderUnavailableMemory(privateMemoryPath, reason) {
|
|
69
69
|
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}`;
|