@wowyuarm/dsh-agent-team 0.1.10 → 0.1.12
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 +17 -4
- package/README.zh.md +17 -4
- package/package.json +53 -47
- package/packages/agent-team/README.md +6 -4
- package/packages/agent-team/README.zh.md +6 -4
- package/packages/agent-team/lib/context-management.js +40 -50
- package/packages/agent-team/lib/context-projection.js +15 -1
- package/packages/agent-team/lib/index.js +244 -154
- package/packages/agent-team/lib/invariant.js +38 -4
- package/packages/agent-team/lib/ledger.js +775 -383
- package/packages/agent-team/lib/member-context.js +6 -2
- package/packages/agent-team/lib/member-runtime.js +47 -9
- package/packages/agent-team/lib/member-time-context.js +20 -5
- package/packages/agent-team/lib/mentions.js +56 -0
- package/packages/agent-team/lib/pressure-policy.js +33 -9
- package/packages/agent-team/lib/session-event-cursor.js +116 -0
- package/packages/agent-team/lib/session-remediation.js +38 -26
- package/packages/agent-team/lib/spec.js +47 -34
- package/packages/agent-team/lib/stored-session-reader.js +138 -0
- package/packages/agent-team/lib/typert.host.js +157 -41
- package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
- package/packages/agent-team/lib/typert.remote-client.js +129 -32
- package/packages/agent-team/lib/types/context-management.d.ts +7 -0
- package/packages/agent-team/lib/types/context-management.d.ts.map +1 -1
- package/packages/agent-team/lib/types/context-projection.d.ts +8 -0
- package/packages/agent-team/lib/types/context-projection.d.ts.map +1 -1
- package/packages/agent-team/lib/types/index.d.ts +77 -7
- package/packages/agent-team/lib/types/index.d.ts.map +1 -1
- package/packages/agent-team/lib/types/invariant.d.ts.map +1 -1
- package/packages/agent-team/lib/types/ledger.d.ts +251 -20
- 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 +2 -1
- package/packages/agent-team/lib/types/member-runtime.d.ts.map +1 -1
- package/packages/agent-team/lib/types/member-time-context.d.ts.map +1 -1
- package/packages/agent-team/lib/types/mentions.d.ts +34 -0
- package/packages/agent-team/lib/types/mentions.d.ts.map +1 -0
- package/packages/agent-team/lib/types/pressure-policy.d.ts +18 -0
- package/packages/agent-team/lib/types/pressure-policy.d.ts.map +1 -1
- package/packages/agent-team/lib/types/session-event-cursor.d.ts +119 -0
- package/packages/agent-team/lib/types/session-event-cursor.d.ts.map +1 -0
- package/packages/agent-team/lib/types/session-remediation.d.ts +23 -0
- package/packages/agent-team/lib/types/session-remediation.d.ts.map +1 -1
- package/packages/agent-team/lib/types/spec.d.ts.map +1 -1
- package/packages/agent-team/lib/types/stored-session-reader.d.ts +92 -0
- package/packages/agent-team/lib/types/stored-session-reader.d.ts.map +1 -0
- package/packages/agent-team/lib/types/types/entities.d.ts +28 -20
- package/packages/agent-team/lib/types/types/entities.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types/operations.d.ts +45 -19
- package/packages/agent-team/lib/types/types/operations.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types/requests-results.d.ts +87 -4
- package/packages/agent-team/lib/types/types/requests-results.d.ts.map +1 -1
- package/packages/agent-team/preset/team-member/agent.cordis.yml +49 -7
- 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 +2357 -1207
- package/packages/client-agent-team/lib/client.js.map +1 -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 +27 -7
- package/packages/client-agent-team/lib/types/client/TeamAvatarStack.d.ts +18 -0
- package/packages/client-agent-team/lib/types/client/TeamAvatarStack.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamAvatarStack.js +22 -0
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts +3 -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 +167 -35
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +23 -27
- package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts +11 -5
- package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamComposer.js +26 -26
- 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 +9 -2
- package/packages/client-agent-team/lib/types/client/TeamCountBadge.d.ts +20 -0
- package/packages/client-agent-team/lib/types/client/TeamCountBadge.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamCountBadge.js +25 -0
- package/packages/client-agent-team/lib/types/client/TeamInboxPage.d.ts +25 -0
- package/packages/client-agent-team/lib/types/client/TeamInboxPage.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamInboxPage.js +139 -0
- package/packages/client-agent-team/lib/types/client/TeamMemberEditor.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamMemberEditor.js +8 -24
- package/packages/client-agent-team/lib/types/client/TeamMemberRow.d.ts +43 -0
- package/packages/client-agent-team/lib/types/client/TeamMemberRow.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamMemberRow.js +27 -0
- package/packages/client-agent-team/lib/types/client/TeamMembersAction.d.ts +2 -2
- package/packages/client-agent-team/lib/types/client/TeamMembersAction.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamMembersAction.js +2 -2
- package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts +9 -2
- package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamMessage.js +11 -4
- package/packages/client-agent-team/lib/types/client/TeamPresenceDot.d.ts +8 -0
- package/packages/client-agent-team/lib/types/client/TeamPresenceDot.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamPresenceDot.js +21 -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 +108 -30
- 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 +68 -7
- package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/index.js +19 -9
- package/packages/client-agent-team/lib/types/client/locales.d.ts +40 -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 +40 -2
- package/packages/client-agent-team/lib/types/client/navigation.d.ts +10 -0
- package/packages/client-agent-team/lib/types/client/navigation.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/navigation.js +18 -7
- package/packages/client-agent-team/lib/types/client/scope-coverage.d.ts +41 -0
- package/packages/client-agent-team/lib/types/client/scope-coverage.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/scope-coverage.js +64 -0
- package/packages/client-agent-team/lib/types/client/slots.d.ts +7 -2
- 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 +33 -2
- 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 +107 -25
- package/packages/client-agent-team/lib/types/client/team-dialog-save.d.ts +20 -0
- package/packages/client-agent-team/lib/types/client/team-dialog-save.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/team-dialog-save.js +36 -0
- package/packages/client-agent-team/lib/types/client/team-formatters.d.ts +43 -1
- 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 +81 -6
- package/packages/tool-agent-team/README.md +2 -2
- package/packages/tool-agent-team/README.zh.md +2 -2
- package/packages/tool-agent-team/lib/context-tools.js +18 -14
- package/packages/tool-agent-team/lib/host-access.js +20 -0
- package/packages/tool-agent-team/lib/index.js +44 -32
- package/packages/tool-agent-team/lib/types/context-tools.d.ts.map +1 -1
- package/packages/tool-agent-team/lib/types/host-access.d.ts +14 -0
- package/packages/tool-agent-team/lib/types/host-access.d.ts.map +1 -0
- package/packages/tool-agent-team/lib/types/index.d.ts.map +1 -1
- package/packages/agent-team/lib/progress-nudge.js +0 -328
- package/packages/agent-team/lib/types/progress-nudge.d.ts +0 -136
- package/packages/agent-team/lib/types/progress-nudge.d.ts.map +0 -1
- package/packages/client-agent-team/lib/types/client/TeamSettings.d.ts +0 -3
- package/packages/client-agent-team/lib/types/client/TeamSettings.d.ts.map +0 -1
- package/packages/client-agent-team/lib/types/client/TeamSettings.js +0 -5
|
@@ -47,29 +47,40 @@ import { createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
|
47
47
|
import { dshHomePath } from '@deepseek-ai/dsh-home-paths';
|
|
48
48
|
import { scopeOf } from '@deepseek-ai/dsh-scope';
|
|
49
49
|
import { Session, SessionId, SessionLogOffset } from '@deepseek-ai/dsh-session';
|
|
50
|
-
import { SessionFormatUnsupportedError } from '@deepseek-ai/dsh-session-persistence';
|
|
51
50
|
import { setSandboxMode } from '@deepseek-ai/dsh-sandbox-policy';
|
|
52
51
|
import { Remote, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
53
52
|
import { ATTACHMENT_MAX_BYTES, attachmentPayloadPath, attachmentsRoot, copyPathAttachment, newAttachmentId, readAttachment, sanitizeMediaType, sweepAttachmentCache, validatePathAttachment, writeAttachment } from "./attachments.js";
|
|
54
53
|
import { PressurePolicyCoordinator } from "./pressure-policy.js";
|
|
55
54
|
import { ContextManagementCoordinator } from "./context-management.js";
|
|
56
55
|
import { AGENT_TEAM_PLUGIN_ID, createHandoffMessage } from "./context-source.js";
|
|
57
|
-
import { carriedInputOf, checkpointByRef, checkpointRefFor, foldContextProjection, isReminderNoticeSummary, timelineCandidates } from "./context-projection.js";
|
|
56
|
+
import { carriedInputOf, checkpointByRef, checkpointRefFor, contextProjectionFold, foldContextProjection, isReminderNoticeSummary, timelineCandidates } from "./context-projection.js";
|
|
57
|
+
import { advanceOwnedSessionEventCursor } from "./session-event-cursor.js";
|
|
58
58
|
import { AGENT_TEAM_HUMAN_MEMBER_ID, AgentTeamLedger, agentTeamHumanActor } from "./ledger.js";
|
|
59
59
|
import { AGENT_TEAM_TOOL_NAMES, deepCopyCapabilities, memberMemoryDirectoryName, MemberRuntime } from "./member-runtime.js";
|
|
60
|
-
import { ProgressNudgeCoordinator } from "./progress-nudge.js";
|
|
61
60
|
import { classifyRecoverableError, RecoveryCoordinator, RECOVERY_MAX_CONSECUTIVE_ERRORS } from "./recovery.js";
|
|
62
61
|
import { SessionRemediation, handoffAlreadyInLog } from "./session-remediation.js";
|
|
62
|
+
import { StoredSessionReadError, StoredSessionReader, sessionFailureOf } from "./stored-session-reader.js";
|
|
63
63
|
import { agentTeamDomainSpec } from "./spec.js";
|
|
64
64
|
import { formatTeamTimestamp } from "./time-format.js";
|
|
65
65
|
export { agentTeamDomainSpec, agentTeamOperationSchema } from "./spec.js";
|
|
66
|
-
export { AGENT_TEAM_HUMAN_MEMBER_ID, AGENT_TEAM_INITIALIZE_REQUEST_ID } from "./ledger.js";
|
|
66
|
+
export { AGENT_TEAM_HUMAN_HANDLE, AGENT_TEAM_HUMAN_MEMBER_ID, AGENT_TEAM_INITIALIZE_REQUEST_ID } from "./ledger.js";
|
|
67
67
|
export { AGENT_TEAM_TOOL_NAMES } from "./member-runtime.js";
|
|
68
68
|
/** Process-stable marker carried by the final Team message tool definition. */
|
|
69
69
|
export const AGENT_TEAM_PRESET_MARKER = Symbol.for('@wowyuarm/dsh-agent-team.preset');
|
|
70
70
|
const INBOX_NOTICE_SUMMARY = 'Team Inbox has unread work.';
|
|
71
71
|
const RECOVERY_NOTICE_SUMMARY = 'Recovery: continue your interrupted work.';
|
|
72
72
|
const ORPHANED_MEMBER_DIAGNOSTIC = 'Member preset composition was lost after a reload; its tools are unavailable. Resume rebuilds the member in place.';
|
|
73
|
+
/**
|
|
74
|
+
* A preset mount/validation failure during activation, carrying its own class
|
|
75
|
+
* so the activation diagnostic can route preset-composition failures (the
|
|
76
|
+
* install/runtime split failure mode) without matching message text.
|
|
77
|
+
*/
|
|
78
|
+
class PresetCompositionError extends Error {
|
|
79
|
+
constructor(message, options) {
|
|
80
|
+
super(message, options);
|
|
81
|
+
this.name = 'PresetCompositionError';
|
|
82
|
+
}
|
|
83
|
+
}
|
|
73
84
|
/** Longest accepted model-supplied checkpoint display name. */
|
|
74
85
|
const MAX_CHECKPOINT_NAME_CHARS = 120;
|
|
75
86
|
/** Default and maximum number of timeline items one query returns. */
|
|
@@ -95,6 +106,8 @@ function sameChangeScope(left, right) {
|
|
|
95
106
|
return left.channelRef === right.channelRef;
|
|
96
107
|
if (left.kind === 'thread' && right.kind === 'thread')
|
|
97
108
|
return left.threadRef === right.threadRef;
|
|
109
|
+
if (left.kind === 'presence' && right.kind === 'presence')
|
|
110
|
+
return left.workspaceId === right.workspaceId;
|
|
98
111
|
return false;
|
|
99
112
|
}
|
|
100
113
|
/** Mark the preset's `team_message` definition as an Agent Team consumer. */
|
|
@@ -261,6 +274,13 @@ let AgentTeam = (() => {
|
|
|
261
274
|
},
|
|
262
275
|
runningAgents: this.runningAgents,
|
|
263
276
|
});
|
|
277
|
+
/**
|
|
278
|
+
* The single seam for every per-Session stored read: handle lifecycle and
|
|
279
|
+
* failure normalization live here, so a DSH persistence-interface change is
|
|
280
|
+
* adapted once, and consumers choose policy by failure category instead of
|
|
281
|
+
* matching error text.
|
|
282
|
+
*/
|
|
283
|
+
sessionReader = new StoredSessionReader(this.ctx);
|
|
264
284
|
/**
|
|
265
285
|
* Why one Member shows error presence, per failure source. Reads prefer
|
|
266
286
|
* activation, then runtime, then compaction; slots clear independently, so
|
|
@@ -282,21 +302,13 @@ let AgentTeam = (() => {
|
|
|
282
302
|
},
|
|
283
303
|
});
|
|
284
304
|
/**
|
|
285
|
-
*
|
|
286
|
-
*
|
|
287
|
-
*
|
|
305
|
+
* Incremental fold state for {@link contextManagement}'s projection lookup:
|
|
306
|
+
* one entry per Member, replaced when that Member's Session changes. Keying
|
|
307
|
+
* by Member rather than by Session id is what bounds the map — a rollover
|
|
308
|
+
* that leaves the previous generation's projection behind retains it for the
|
|
309
|
+
* life of the process, and only the current Session can ever read it.
|
|
288
310
|
*/
|
|
289
|
-
|
|
290
|
-
agentForMember: memberId => this.handles.get(memberId)?.agent,
|
|
291
|
-
targetsForMember: memberId => this.requireLedger().progressNudgeTargets(memberId),
|
|
292
|
-
sessionLogForMember: (memberId, sessionId) => {
|
|
293
|
-
const handle = this.handles.get(memberId);
|
|
294
|
-
if (handle === undefined || handle.agent.session.id !== sessionId)
|
|
295
|
-
return undefined;
|
|
296
|
-
return { events: handle.agent.session.ownEvents() };
|
|
297
|
-
},
|
|
298
|
-
log: message => { this.ctx.logger.warn(message); },
|
|
299
|
-
});
|
|
311
|
+
contextCursors = new Map();
|
|
300
312
|
/**
|
|
301
313
|
* Context self-management: the one deep module that turns a Member's
|
|
302
314
|
* successful `context_rollover` tool result into its next private context
|
|
@@ -311,15 +323,34 @@ let AgentTeam = (() => {
|
|
|
311
323
|
const handle = this.handles.get(memberId);
|
|
312
324
|
if (handle === undefined || handle.agent.session.id !== sessionId)
|
|
313
325
|
return undefined;
|
|
314
|
-
|
|
326
|
+
const session = handle.agent.session;
|
|
327
|
+
// The projection is read on every successful tool result and turn end, so
|
|
328
|
+
// it folds incrementally. The entry is keyed by Member and carries the
|
|
329
|
+
// Session it was built from: this Session's log keeps growing, a rollover
|
|
330
|
+
// replaces the entry instead of resuming from its predecessor's cursor,
|
|
331
|
+
// and a rewritten log falls back to a cold fold inside the cursor.
|
|
332
|
+
const owned = advanceOwnedSessionEventCursor(this.contextCursors.get(memberId), session.id, contextProjectionFold(session.id), session.ownEvents(), session.inheritedEventCount);
|
|
333
|
+
this.contextCursors.set(memberId, owned);
|
|
334
|
+
return owned.cursor.value;
|
|
315
335
|
},
|
|
316
336
|
executeTransition: (memberId, plan) => this.executeMemberTransition(memberId, plan),
|
|
317
337
|
log: message => { this.ctx.logger.warn(`agent-team: ${message}`); },
|
|
318
338
|
});
|
|
319
339
|
lifecycleTail = Promise.resolve();
|
|
320
340
|
accepting = true;
|
|
321
|
-
|
|
341
|
+
/**
|
|
342
|
+
* Presence wake epoch: Agent running/idle/failure is runtime state with no
|
|
343
|
+
* durable fact behind it, so the presence scope counts those edge wakes in
|
|
344
|
+
* process. It never takes part in a projection waiter's comparison.
|
|
345
|
+
*/
|
|
346
|
+
presenceEpoch = 0;
|
|
322
347
|
changeWaiters = new Set();
|
|
348
|
+
/**
|
|
349
|
+
* The startup-opened remediation instance, held for the restart heal: the
|
|
350
|
+
* completion-cache domain may only be opened once per plugin lifecycle, so
|
|
351
|
+
* the restart path reuses this instance instead of opening its own.
|
|
352
|
+
*/
|
|
353
|
+
remediation;
|
|
323
354
|
constructor(ctx) {
|
|
324
355
|
super(ctx, 'agentTeam');
|
|
325
356
|
this.pressurePolicy = new PressurePolicyCoordinator({
|
|
@@ -351,7 +382,7 @@ let AgentTeam = (() => {
|
|
|
351
382
|
if (kind !== undefined)
|
|
352
383
|
this.ctx.logger.warn(`agent-team: member '${member.handle}' hit a recoverable ${kind} error; recording a consecutive error occurrence`);
|
|
353
384
|
this.recovery.onError(member.memberId, message);
|
|
354
|
-
this.
|
|
385
|
+
this.emitPresenceChanged(member.workspaceId);
|
|
355
386
|
});
|
|
356
387
|
this.ctx.on('agent/status', ({ agent, status }) => {
|
|
357
388
|
const member = this.memberForAgent(agent);
|
|
@@ -363,7 +394,7 @@ let AgentTeam = (() => {
|
|
|
363
394
|
const recovered = this.clearMemberFailure(member.memberId, 'runtime');
|
|
364
395
|
if (recovered)
|
|
365
396
|
this.notifiedInbox.delete(member.memberId);
|
|
366
|
-
this.
|
|
397
|
+
this.emitPresenceChanged(member.workspaceId);
|
|
367
398
|
// A rollover/recovery in flight delivers its own sequenced
|
|
368
399
|
// rederived Inbox after the handoff and carried input; a status-driven
|
|
369
400
|
// steer here would claim the handoff turn's next step and leapfrog
|
|
@@ -373,24 +404,23 @@ let AgentTeam = (() => {
|
|
|
373
404
|
}
|
|
374
405
|
// A turn that ends without an error closes any automatic recovery episode.
|
|
375
406
|
// The idle transition is itself presence-affecting (working → available),
|
|
376
|
-
// so it must wake
|
|
407
|
+
// so it must wake presence watchers exactly like the running transition
|
|
377
408
|
// above; without this wake, cached Client member rows keep showing the
|
|
378
409
|
// Member as working after every turn until an unrelated change arrives.
|
|
379
410
|
if (status === 'idle' && member !== undefined) {
|
|
380
411
|
if (this.memberFailures.get(member.memberId)?.runtime === undefined) {
|
|
381
412
|
this.recovery.onCleanTurnEnd(member.memberId);
|
|
382
413
|
}
|
|
383
|
-
this.
|
|
414
|
+
this.emitPresenceChanged(member.workspaceId);
|
|
384
415
|
}
|
|
385
416
|
});
|
|
386
|
-
//
|
|
387
|
-
//
|
|
388
|
-
// the
|
|
389
|
-
//
|
|
390
|
-
//
|
|
391
|
-
//
|
|
392
|
-
//
|
|
393
|
-
// streams, the map lookup succeeds.
|
|
417
|
+
// The store's dispatch carrier is untagged, so a scope-tagged listener
|
|
418
|
+
// inside the Agent setup would receive nothing; one root listener that
|
|
419
|
+
// maps the session id back to its Member is the seam that works (same
|
|
420
|
+
// shape as the `agent/status` listener above). The first turn of a
|
|
421
|
+
// freshly published Member cannot race this: `handles.set()` precedes the
|
|
422
|
+
// same activation continuation that publishes the Agent, so by the time
|
|
423
|
+
// any tool call streams, the map lookup succeeds.
|
|
394
424
|
this.ctx.on('session/event', (session, event) => {
|
|
395
425
|
const memberId = this.memberBySessionId.get(session.id);
|
|
396
426
|
if (memberId === undefined)
|
|
@@ -398,7 +428,6 @@ let AgentTeam = (() => {
|
|
|
398
428
|
const handle = this.handles.get(memberId);
|
|
399
429
|
if (handle === undefined || handle.agent.session.id !== session.id)
|
|
400
430
|
return;
|
|
401
|
-
this.progressNudge.onSessionEvent(memberId, session.id, handle.agent, event);
|
|
402
431
|
// A successful assistant response ends any open provider-overflow
|
|
403
432
|
// recovery sequence for this Member.
|
|
404
433
|
if (event.type === 'assistant/message')
|
|
@@ -410,8 +439,8 @@ let AgentTeam = (() => {
|
|
|
410
439
|
const domain = await this.ctx.storageDomain.open(agentTeamDomainSpec);
|
|
411
440
|
this.ctx.effect(() => async () => {
|
|
412
441
|
this.accepting = false;
|
|
442
|
+
this.remediation = undefined;
|
|
413
443
|
this.recovery.dispose();
|
|
414
|
-
this.progressNudge.dispose();
|
|
415
444
|
this.contextManagement.dispose();
|
|
416
445
|
this.pressurePolicy.dispose();
|
|
417
446
|
if (this.attachmentGcTimer !== undefined)
|
|
@@ -439,6 +468,7 @@ let AgentTeam = (() => {
|
|
|
439
468
|
// startup — the next start retries exactly what the cache does not cover.
|
|
440
469
|
try {
|
|
441
470
|
const remediation = new SessionRemediation(this.ctx, this.ctx.sessionPersistence, await SessionRemediation.open(this.ctx));
|
|
471
|
+
this.remediation = remediation;
|
|
442
472
|
await remediation.remediateEnabledMembers(ledger.listMembers());
|
|
443
473
|
}
|
|
444
474
|
catch (error) {
|
|
@@ -463,7 +493,7 @@ let AgentTeam = (() => {
|
|
|
463
493
|
* generation over it.
|
|
464
494
|
*/
|
|
465
495
|
async sessionPersisted(sessionId) {
|
|
466
|
-
return
|
|
496
|
+
return this.sessionReader.exists(sessionId);
|
|
467
497
|
}
|
|
468
498
|
/** Resolve one exact live Agent to its durable Team Member; forks do not inherit identity. */
|
|
469
499
|
memberForAgent(agent) {
|
|
@@ -501,8 +531,9 @@ let AgentTeam = (() => {
|
|
|
501
531
|
if (!Number.isInteger(request.afterVersion) || request.afterVersion < 0)
|
|
502
532
|
throw new Error('afterVersion must be a non-negative integer');
|
|
503
533
|
const scope = this.validateChangeScope(request.scope);
|
|
504
|
-
|
|
505
|
-
|
|
534
|
+
const version = this.changeVersionOf(scope);
|
|
535
|
+
if (version > request.afterVersion || !this.accepting)
|
|
536
|
+
return Object.freeze({ version });
|
|
506
537
|
return new Promise((resolve, reject) => {
|
|
507
538
|
let settled = false;
|
|
508
539
|
const waiter = {
|
|
@@ -521,7 +552,7 @@ let AgentTeam = (() => {
|
|
|
521
552
|
return;
|
|
522
553
|
settled = true;
|
|
523
554
|
this.changeWaiters.delete(waiter);
|
|
524
|
-
resolve(Object.freeze({ version: this.
|
|
555
|
+
resolve(Object.freeze({ version: this.changeVersionOf(scope) }));
|
|
525
556
|
}, 25_000);
|
|
526
557
|
const onAbort = () => {
|
|
527
558
|
if (settled)
|
|
@@ -659,6 +690,23 @@ let AgentTeam = (() => {
|
|
|
659
690
|
if (member.state !== 'enabled')
|
|
660
691
|
throw new Error(`Agent Member '${member.handle}' is ${member.state}; only enabled Members can be restarted`);
|
|
661
692
|
this.ctx.logger.info(`agent-team: restarting member '${member.handle}' after a failed activation`);
|
|
693
|
+
// A deterministic session refusal may be repairable in place: run the
|
|
694
|
+
// same bounded startup remediation for this one Member before retrying
|
|
695
|
+
// activation, so the restart heals instead of replaying the failure.
|
|
696
|
+
const activation = this.memberFailures.get(request.memberId)?.activation;
|
|
697
|
+
if (activation !== undefined && activation.class === 'session-refused' && this.remediation !== undefined) {
|
|
698
|
+
const outcome = await this.remediation.remediateMember(member);
|
|
699
|
+
if (outcome.repaired > 0) {
|
|
700
|
+
this.ctx.logger.info(`agent-team: repaired ${outcome.repaired} refused Session artifact(s) for member '${member.handle}'; retrying activation`);
|
|
701
|
+
}
|
|
702
|
+
else if (outcome.completed) {
|
|
703
|
+
// The walk finished and nothing was provably this plugin's to fix:
|
|
704
|
+
// a retry would fail identically. Mark the refusal non-remediable
|
|
705
|
+
// so the surface stops offering restart and says why.
|
|
706
|
+
this.markRefusalNonRemediable(request.memberId);
|
|
707
|
+
return Object.freeze({ status: this.memberStatus(member) });
|
|
708
|
+
}
|
|
709
|
+
}
|
|
662
710
|
await this.reactivateMember(request.memberId);
|
|
663
711
|
return Object.freeze({ status: this.memberStatus(member) });
|
|
664
712
|
}
|
|
@@ -726,9 +774,6 @@ let AgentTeam = (() => {
|
|
|
726
774
|
// Drop the old handle's transient state: pending recovery episodes and
|
|
727
775
|
// error markers belong to the disposed agent, not to the Member.
|
|
728
776
|
this.recovery.stopTracking(memberId);
|
|
729
|
-
// The fresh Session may re-earn one Claim suggestion per Thread; the
|
|
730
|
-
// old Session's one-shot records must not leak into it.
|
|
731
|
-
this.progressNudge.stopTracking(memberId);
|
|
732
777
|
// The context admission gate stays armed through disposal: input racing
|
|
733
778
|
// the retire window must still be captured for the new generation, and
|
|
734
779
|
// the coordinator drops its own bookkeeping only after the swap settles.
|
|
@@ -879,9 +924,6 @@ let AgentTeam = (() => {
|
|
|
879
924
|
if (this.isInboxNotice(pending))
|
|
880
925
|
handle.agent.inbox.remove(pending.id);
|
|
881
926
|
}
|
|
882
|
-
// Recovery carries the concrete interrupted work; a queued progress nudge
|
|
883
|
-
// is stale next to it and must not survive as a second reminder.
|
|
884
|
-
this.progressNudge.revokePendingNotice(member.memberId);
|
|
885
927
|
handle.agent.steer(hint);
|
|
886
928
|
}
|
|
887
929
|
/** Automatic-recovery wakeup; throwing tells the coordinator the target is gone. */
|
|
@@ -1078,7 +1120,7 @@ let AgentTeam = (() => {
|
|
|
1078
1120
|
this.emitCommitted(result.value.receipt);
|
|
1079
1121
|
return result.value;
|
|
1080
1122
|
}
|
|
1081
|
-
/**
|
|
1123
|
+
/** Human Inbox projection; the Web Client consumes the direct-only slice as its mention queue. */
|
|
1082
1124
|
inbox(request) {
|
|
1083
1125
|
this.requireWorkspace(request.workspaceId);
|
|
1084
1126
|
return this.requireLedger().inbox(agentTeamHumanActor(), request);
|
|
@@ -1087,8 +1129,10 @@ let AgentTeam = (() => {
|
|
|
1087
1129
|
async readThread(request) {
|
|
1088
1130
|
const actor = this.humanCall(request.workspaceId);
|
|
1089
1131
|
const result = await this.requireLedger().readThread({ ...request, actor });
|
|
1090
|
-
|
|
1091
|
-
|
|
1132
|
+
// A read that made no progress commits no operation and carries no receipt.
|
|
1133
|
+
const receipt = result.committed ? result.value.receipt : undefined;
|
|
1134
|
+
if (receipt !== undefined)
|
|
1135
|
+
this.emitCommitted(receipt);
|
|
1092
1136
|
return result.value;
|
|
1093
1137
|
}
|
|
1094
1138
|
/** Human-only durable Attention observations for one Thread. */
|
|
@@ -1147,8 +1191,10 @@ let AgentTeam = (() => {
|
|
|
1147
1191
|
async readThreadForAgent(agent, request) {
|
|
1148
1192
|
const actor = this.memberCall(agent, request.workspaceId);
|
|
1149
1193
|
const result = await this.requireLedger().readThread({ ...request, actor });
|
|
1150
|
-
|
|
1151
|
-
|
|
1194
|
+
// A read that made no progress commits no operation and carries no receipt.
|
|
1195
|
+
const receipt = result.committed ? result.value.receipt : undefined;
|
|
1196
|
+
if (receipt !== undefined)
|
|
1197
|
+
this.emitCommitted(receipt);
|
|
1152
1198
|
const value = result.value;
|
|
1153
1199
|
// Private read-time enrich: an acceptance the reader just acknowledged is
|
|
1154
1200
|
// a natural Task boundary, so the Host prices the reader's context once,
|
|
@@ -1265,6 +1311,16 @@ let AgentTeam = (() => {
|
|
|
1265
1311
|
validateLedger() {
|
|
1266
1312
|
this.requireLedger().validate();
|
|
1267
1313
|
}
|
|
1314
|
+
/**
|
|
1315
|
+
* Validate the durable ledger for the invariant's mount check. The
|
|
1316
|
+
* constructor already re-derived every durable record against its own
|
|
1317
|
+
* scratch projection, so this adopts that conclusion once while nothing has
|
|
1318
|
+
* committed since; every other call, and every commit-driven validation,
|
|
1319
|
+
* replays the whole table again.
|
|
1320
|
+
*/
|
|
1321
|
+
validateLedgerAtMount() {
|
|
1322
|
+
this.requireLedger().validateAtMount();
|
|
1323
|
+
}
|
|
1268
1324
|
/**
|
|
1269
1325
|
* Effective context-pressure budget for one Member's current route:
|
|
1270
1326
|
* `hardLimit = min(256K, routeWindow - outputReserve)` and
|
|
@@ -1362,20 +1418,13 @@ let AgentTeam = (() => {
|
|
|
1362
1418
|
live = false;
|
|
1363
1419
|
}
|
|
1364
1420
|
else {
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
events = (await handle.read()).events;
|
|
1369
|
-
inheritedEventCount = handle.inheritedEventCount;
|
|
1370
|
-
parentSession = handle.header.parentSession;
|
|
1371
|
-
}
|
|
1372
|
-
finally {
|
|
1373
|
-
await handle.close();
|
|
1374
|
-
}
|
|
1375
|
-
}
|
|
1376
|
-
catch (error) {
|
|
1377
|
-
throw new Error(`checkpoint '${checkpointRef}' could not be resolved: its source Session is unreadable (${error instanceof Error ? error.message : String(error)})`);
|
|
1421
|
+
const read = await this.sessionReader.read(sessionId);
|
|
1422
|
+
if (!read.ok) {
|
|
1423
|
+
throw new StoredSessionReadError(`checkpoint '${checkpointRef}' could not be resolved: its source Session is unreadable (${read.failure.kind}: ${read.failure.detail})`, read.failure);
|
|
1378
1424
|
}
|
|
1425
|
+
events = read.inspection.events;
|
|
1426
|
+
inheritedEventCount = read.inspection.inheritedEventCount;
|
|
1427
|
+
parentSession = read.inspection.header.parentSession;
|
|
1379
1428
|
}
|
|
1380
1429
|
// Fold the source with its inherited cut respected: inherited events
|
|
1381
1430
|
// are resolved history in that source, never fresh intent; checkpoints
|
|
@@ -1452,20 +1501,15 @@ let AgentTeam = (() => {
|
|
|
1452
1501
|
?? this.requireLedger().previousSessionForMember(member.memberId);
|
|
1453
1502
|
if (previousSessionId === undefined)
|
|
1454
1503
|
return;
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
}
|
|
1461
|
-
finally {
|
|
1462
|
-
await handle.close();
|
|
1463
|
-
}
|
|
1464
|
-
}
|
|
1465
|
-
catch (error) {
|
|
1466
|
-
this.ctx.logger.warn(`agent-team: rollover handoff reconstruction could not read the previous Session '${previousSessionId}': ${error instanceof Error ? error.message : String(error)}`);
|
|
1504
|
+
const previousRead = await this.sessionReader.read(previousSessionId);
|
|
1505
|
+
if (!previousRead.ok) {
|
|
1506
|
+
// Best-effort recovery: the handoff intent stays lost with the
|
|
1507
|
+
// unreadable previous Session, but the Member still activates.
|
|
1508
|
+
const failure = previousRead.failure;
|
|
1509
|
+
this.ctx.logger.warn(`agent-team: rollover handoff reconstruction could not read the previous Session '${previousSessionId}' (${failure.kind}): ${failure.detail}`);
|
|
1467
1510
|
return;
|
|
1468
1511
|
}
|
|
1512
|
+
const inspection = previousRead.inspection;
|
|
1469
1513
|
const state = foldContextProjection(inspection.events, inspection.inheritedEventCount, previousSessionId);
|
|
1470
1514
|
if (state.pending === null)
|
|
1471
1515
|
return;
|
|
@@ -1494,19 +1538,12 @@ let AgentTeam = (() => {
|
|
|
1494
1538
|
* downgrade to a blank child.
|
|
1495
1539
|
*/
|
|
1496
1540
|
async recordedCheckpointPrefix(seed) {
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
const
|
|
1500
|
-
|
|
1501
|
-
inspection = { events: (await handle.read()).events, inheritedEventCount: handle.inheritedEventCount };
|
|
1502
|
-
}
|
|
1503
|
-
finally {
|
|
1504
|
-
await handle.close();
|
|
1505
|
-
}
|
|
1506
|
-
}
|
|
1507
|
-
catch (error) {
|
|
1508
|
-
throw new Error(`the recorded checkpoint-return seed source Session '${seed.sourceSessionId}' is unreadable: ${error instanceof Error ? error.message : String(error)}`);
|
|
1541
|
+
const seedRead = await this.sessionReader.read(seed.sourceSessionId);
|
|
1542
|
+
if (!seedRead.ok) {
|
|
1543
|
+
const failure = seedRead.failure;
|
|
1544
|
+
throw new StoredSessionReadError(`the recorded checkpoint-return seed source Session '${seed.sourceSessionId}' is unreadable (${failure.kind}): ${failure.detail}`, failure);
|
|
1509
1545
|
}
|
|
1546
|
+
const inspection = seedRead.inspection;
|
|
1510
1547
|
const through = Number(seed.sourceThroughSeq);
|
|
1511
1548
|
if (!Number.isSafeInteger(through) || through < 0 || through > inspection.events.length) {
|
|
1512
1549
|
throw new Error(`the recorded checkpoint-return seed cut ${through} is not a valid prefix of Session '${seed.sourceSessionId}'`);
|
|
@@ -1555,23 +1592,14 @@ let AgentTeam = (() => {
|
|
|
1555
1592
|
}
|
|
1556
1593
|
let inspectionEvents;
|
|
1557
1594
|
let inspectionInherited;
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
inspectionEvents = (await handle.read()).events;
|
|
1562
|
-
inspectionInherited = handle.inheritedEventCount;
|
|
1563
|
-
}
|
|
1564
|
-
finally {
|
|
1565
|
-
await handle.close();
|
|
1566
|
-
}
|
|
1567
|
-
}
|
|
1568
|
-
catch (error) {
|
|
1569
|
-
const detail = error instanceof Error ? error.message : String(error);
|
|
1595
|
+
const previousRead = await this.sessionReader.read(transition.previousSessionId);
|
|
1596
|
+
if (!previousRead.ok) {
|
|
1597
|
+
const failure = previousRead.failure;
|
|
1570
1598
|
// Log-corruption class: bounded fail-open. The current Session's own
|
|
1571
1599
|
// fold is intact and the Host repairs the retired log's torn tail; a
|
|
1572
1600
|
// corrupted retired generation must not permanently block activation.
|
|
1573
|
-
if (
|
|
1574
|
-
this.ctx.logger.warn(`agent-team: previous Session '${transition.previousSessionId}' holding carried input for member '${this.memberLabel(member.memberId)}' is corrupt: ${detail}; skipping the replay`);
|
|
1601
|
+
if (failure.kind === 'corrupt') {
|
|
1602
|
+
this.ctx.logger.warn(`agent-team: previous Session '${transition.previousSessionId}' holding carried input for member '${this.memberLabel(member.memberId)}' is corrupt: ${failure.detail}; skipping the replay`);
|
|
1575
1603
|
return 0;
|
|
1576
1604
|
}
|
|
1577
1605
|
// Deterministic released-format refusal: the retired artifact is refused
|
|
@@ -1580,12 +1608,14 @@ let AgentTeam = (() => {
|
|
|
1580
1608
|
// Member no longer runs in into permanent unavailability — the exact
|
|
1581
1609
|
// failure class this hardening removes. Genuine IO and unknown failures
|
|
1582
1610
|
// stay fail-closed so carried input is never dropped silently.
|
|
1583
|
-
if (
|
|
1584
|
-
this.ctx.logger.warn(`agent-team: previous Session '${transition.previousSessionId}' holding carried input for member '${this.memberLabel(member.memberId)}' is refused by the session-format migration: ${detail}; skipping the replay`);
|
|
1611
|
+
if (failure.kind === 'refused') {
|
|
1612
|
+
this.ctx.logger.warn(`agent-team: previous Session '${transition.previousSessionId}' holding carried input for member '${this.memberLabel(member.memberId)}' is refused by the session-format migration: ${failure.detail}; skipping the replay`);
|
|
1585
1613
|
return 0;
|
|
1586
1614
|
}
|
|
1587
|
-
throw new
|
|
1615
|
+
throw new StoredSessionReadError(`the previous Session '${transition.previousSessionId}' holding the Member's carried input is unreadable (${failure.kind}): ${failure.detail}`, failure);
|
|
1588
1616
|
}
|
|
1617
|
+
inspectionEvents = previousRead.inspection.events;
|
|
1618
|
+
inspectionInherited = previousRead.inspection.inheritedEventCount;
|
|
1589
1619
|
const state = foldContextProjection(inspectionEvents, inspectionInherited, transition.previousSessionId);
|
|
1590
1620
|
const carried = carriedInputOf(state);
|
|
1591
1621
|
if (carried.length === 0)
|
|
@@ -1659,6 +1689,7 @@ let AgentTeam = (() => {
|
|
|
1659
1689
|
// persisted logs; the same projection definition folds every source.
|
|
1660
1690
|
const items = [];
|
|
1661
1691
|
const seen = new Set();
|
|
1692
|
+
let incompleteFrom;
|
|
1662
1693
|
let sessionId = member.sessionId;
|
|
1663
1694
|
let live = true;
|
|
1664
1695
|
let guard = 0;
|
|
@@ -1676,23 +1707,19 @@ let AgentTeam = (() => {
|
|
|
1676
1707
|
live = false;
|
|
1677
1708
|
}
|
|
1678
1709
|
else {
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
sourceEvents = events;
|
|
1685
|
-
sourceSessionId = sessionId;
|
|
1686
|
-
sessionId = handle.header.parentSession;
|
|
1687
|
-
}
|
|
1688
|
-
finally {
|
|
1689
|
-
await handle.close();
|
|
1690
|
-
}
|
|
1691
|
-
}
|
|
1692
|
-
catch {
|
|
1693
|
-
// An unreadable ancestor ends the lineage walk here.
|
|
1710
|
+
const read = await this.sessionReader.read(sessionId);
|
|
1711
|
+
if (!read.ok) {
|
|
1712
|
+
// An unreadable ancestor ends the lineage walk here — recorded in
|
|
1713
|
+
// the result, never silent, and never a Member-availability fact.
|
|
1714
|
+
incompleteFrom = { sessionId, reason: `${read.failure.kind}: ${read.failure.detail}` };
|
|
1694
1715
|
sessionId = undefined;
|
|
1695
1716
|
}
|
|
1717
|
+
else {
|
|
1718
|
+
state = foldContextProjection(read.inspection.events, read.inspection.inheritedEventCount, sessionId);
|
|
1719
|
+
sourceEvents = read.inspection.events;
|
|
1720
|
+
sourceSessionId = sessionId;
|
|
1721
|
+
sessionId = read.inspection.header.parentSession;
|
|
1722
|
+
}
|
|
1696
1723
|
}
|
|
1697
1724
|
if (state === undefined)
|
|
1698
1725
|
break;
|
|
@@ -1713,7 +1740,7 @@ let AgentTeam = (() => {
|
|
|
1713
1740
|
if (items.length >= limit)
|
|
1714
1741
|
break;
|
|
1715
1742
|
}
|
|
1716
|
-
return { usageTokens, hardLimit, handoffAt, items };
|
|
1743
|
+
return { usageTokens, hardLimit, handoffAt, items, ...(incompleteFrom === undefined ? {} : { incompleteFrom }) };
|
|
1717
1744
|
}
|
|
1718
1745
|
/**
|
|
1719
1746
|
* Replayed measurement of one lineage source: the live current Session
|
|
@@ -1729,19 +1756,16 @@ let AgentTeam = (() => {
|
|
|
1729
1756
|
return undefined;
|
|
1730
1757
|
if (live)
|
|
1731
1758
|
return meter.measure(agent.session)?.totalTokens;
|
|
1759
|
+
// 0.1.5 removed borrowSession: rebuild a detached Session from the
|
|
1760
|
+
// stored log so the seed's retained cost is priced by the SOURCE's own
|
|
1761
|
+
// replay, never the current generation's. An unreadable source or a
|
|
1762
|
+
// failing meter is unmeasurable and prices as UNKNOWN.
|
|
1763
|
+
const read = await this.sessionReader.read(sessionId);
|
|
1764
|
+
if (!read.ok)
|
|
1765
|
+
return undefined;
|
|
1732
1766
|
try {
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
// replay, never the current generation's.
|
|
1736
|
-
const handle = await this.ctx.sessionPersistence.open(sessionId, 'read');
|
|
1737
|
-
try {
|
|
1738
|
-
const { events } = await handle.read();
|
|
1739
|
-
const session = Session.create(sessionId, events, handle.header, handle.inheritedEventCount);
|
|
1740
|
-
return meter.measure(session)?.totalTokens;
|
|
1741
|
-
}
|
|
1742
|
-
finally {
|
|
1743
|
-
await handle.close();
|
|
1744
|
-
}
|
|
1767
|
+
const session = Session.create(sessionId, read.inspection.events, read.inspection.header, read.inspection.inheritedEventCount);
|
|
1768
|
+
return meter.measure(session)?.totalTokens;
|
|
1745
1769
|
}
|
|
1746
1770
|
catch {
|
|
1747
1771
|
return undefined;
|
|
@@ -1820,8 +1844,9 @@ let AgentTeam = (() => {
|
|
|
1820
1844
|
const source = event.data.source;
|
|
1821
1845
|
if (source?.kind !== 'plugin')
|
|
1822
1846
|
continue;
|
|
1823
|
-
// Reminder notices never enter attribution — a
|
|
1824
|
-
//
|
|
1847
|
+
// Reminder notices never enter attribution — a recovery instruction
|
|
1848
|
+
// (or a historical progress-nudge notice, kept decodable in session
|
|
1849
|
+
// logs recorded before that system was removed) is not a Team fact.
|
|
1825
1850
|
if (source.form === 'notice' && source.summary !== undefined && isReminderNoticeSummary(source.summary))
|
|
1826
1851
|
continue;
|
|
1827
1852
|
const text = event.data.content.filter(block => block.type === 'text').map(block => block.text ?? '').join('\n');
|
|
@@ -2097,9 +2122,17 @@ let AgentTeam = (() => {
|
|
|
2097
2122
|
// `swap` is bound by the provider at activation (no-op until then).
|
|
2098
2123
|
const skillSelection = { current: member.capabilities?.skills?.allow, swap: () => { } };
|
|
2099
2124
|
const setup = async (agentCtx, agent) => {
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2125
|
+
try {
|
|
2126
|
+
await this.ctx.agentPresets.mount(agentCtx, member.presetId);
|
|
2127
|
+
this.memberRuntime.applyMemberToolPolicy(agentCtx, member);
|
|
2128
|
+
this.validateMemberPreset(agentCtx);
|
|
2129
|
+
}
|
|
2130
|
+
catch (error) {
|
|
2131
|
+
// Tag composition failures with their own class: the activation
|
|
2132
|
+
// diagnostic routes preset-composition (install/runtime split)
|
|
2133
|
+
// failures by type, not by matching message text.
|
|
2134
|
+
throw new PresetCompositionError(error instanceof Error ? error.message : String(error), { cause: error });
|
|
2135
|
+
}
|
|
2103
2136
|
installModelSelection(agentCtx, selected);
|
|
2104
2137
|
// Admission gate for pending context rollovers: once a successful
|
|
2105
2138
|
// context_rollover result is durable, queued input must not open another
|
|
@@ -2276,11 +2309,11 @@ let AgentTeam = (() => {
|
|
|
2276
2309
|
this.memberRuntime.forgetMember(member.memberId);
|
|
2277
2310
|
const message = error instanceof Error ? error.message : String(error);
|
|
2278
2311
|
this.ctx.logger.warn(`agent-team: activation failed for member '${this.memberLabel(member.memberId)}': ${message}`);
|
|
2279
|
-
this.
|
|
2312
|
+
this.setActivationDiagnostic(member.memberId, this.activationDiagnosticOf(error, member.sessionId));
|
|
2280
2313
|
}
|
|
2281
2314
|
finally {
|
|
2282
2315
|
// Activation only changes this Workspace's presence projection.
|
|
2283
|
-
this.
|
|
2316
|
+
this.emitPresenceChanged(member.workspaceId);
|
|
2284
2317
|
}
|
|
2285
2318
|
}
|
|
2286
2319
|
/**
|
|
@@ -2368,13 +2401,13 @@ let AgentTeam = (() => {
|
|
|
2368
2401
|
// new binding as active — a Client following the row would otherwise open
|
|
2369
2402
|
// a Session that does not exist yet.
|
|
2370
2403
|
if (handle.agent.id !== member.sessionId)
|
|
2371
|
-
return Object.freeze({ member, availability: 'unavailable', presence: 'unavailable', diagnostic: 'context rollover in progress' });
|
|
2404
|
+
return Object.freeze({ member, availability: 'unavailable', presence: 'unavailable', diagnostic: { class: 'rollover', detail: 'context rollover in progress' } });
|
|
2372
2405
|
if (this.ctx.agentPresets.composedPreset(handle.agent.ctx) === undefined) {
|
|
2373
|
-
return Object.freeze({ member, availability: 'active', presence: 'error', diagnostic: ORPHANED_MEMBER_DIAGNOSTIC });
|
|
2406
|
+
return Object.freeze({ member, availability: 'active', presence: 'error', diagnostic: { class: 'preset-composition', detail: ORPHANED_MEMBER_DIAGNOSTIC } });
|
|
2374
2407
|
}
|
|
2375
2408
|
const runtimeError = failures?.runtime ?? failures?.compaction;
|
|
2376
2409
|
if (runtimeError !== undefined)
|
|
2377
|
-
return Object.freeze({ member, availability: 'active', presence: 'error', diagnostic: runtimeError });
|
|
2410
|
+
return Object.freeze({ member, availability: 'active', presence: 'error', diagnostic: { class: 'runtime', detail: runtimeError } });
|
|
2378
2411
|
// Capability warnings are runtime-derived at activation (handles-scoped,
|
|
2379
2412
|
// like failures): absent while capabilities resolve cleanly.
|
|
2380
2413
|
const capabilityWarnings = this.memberRuntime.capabilityWarningsFor(member.memberId);
|
|
@@ -2388,6 +2421,37 @@ let AgentTeam = (() => {
|
|
|
2388
2421
|
failures[slot] = message;
|
|
2389
2422
|
this.memberFailures.set(memberId, failures);
|
|
2390
2423
|
}
|
|
2424
|
+
/** Store one structured activation diagnostic; runtime/compaction slots stay plain messages. */
|
|
2425
|
+
setActivationDiagnostic(memberId, diagnostic) {
|
|
2426
|
+
const failures = this.memberFailures.get(memberId) ?? {};
|
|
2427
|
+
failures.activation = Object.freeze(diagnostic);
|
|
2428
|
+
this.memberFailures.set(memberId, failures);
|
|
2429
|
+
}
|
|
2430
|
+
/**
|
|
2431
|
+
* Route one activation failure to its diagnostic class: preset composition
|
|
2432
|
+
* failures by their own error class, session failures by the seam's typed
|
|
2433
|
+
* classification (our call sites carry it directly; a Harness resume
|
|
2434
|
+
* failure carries it through the cause chain), everything else as an
|
|
2435
|
+
* unclassified activation failure.
|
|
2436
|
+
*/
|
|
2437
|
+
activationDiagnosticOf(error, sessionId) {
|
|
2438
|
+
if (error instanceof PresetCompositionError)
|
|
2439
|
+
return { class: 'preset-composition', detail: error.message };
|
|
2440
|
+
const failure = error instanceof StoredSessionReadError ? error.failure : sessionFailureOf(error, sessionId);
|
|
2441
|
+
if (failure !== undefined) {
|
|
2442
|
+
const shared = { detail: failure.detail, ...(failure.location === undefined ? {} : { location: failure.location }), sessionId: failure.sessionId };
|
|
2443
|
+
return failure.kind === 'refused' ? { class: 'session-refused', ...shared } : { class: 'session-unreadable', ...shared };
|
|
2444
|
+
}
|
|
2445
|
+
return { class: 'activation', detail: error instanceof Error ? error.message : String(error) };
|
|
2446
|
+
}
|
|
2447
|
+
/** Mark a session-refused activation diagnostic as proven non-remediable. */
|
|
2448
|
+
markRefusalNonRemediable(memberId) {
|
|
2449
|
+
const failures = this.memberFailures.get(memberId);
|
|
2450
|
+
const activation = failures?.activation;
|
|
2451
|
+
if (activation === undefined || activation.class !== 'session-refused')
|
|
2452
|
+
return;
|
|
2453
|
+
failures.activation = Object.freeze({ ...activation, remediable: false });
|
|
2454
|
+
}
|
|
2391
2455
|
clearMemberFailure(memberId, slot) {
|
|
2392
2456
|
const failures = this.memberFailures.get(memberId);
|
|
2393
2457
|
if (failures === undefined || failures[slot] === undefined)
|
|
@@ -2422,10 +2486,6 @@ let AgentTeam = (() => {
|
|
|
2422
2486
|
}
|
|
2423
2487
|
const ledger = this.requireLedger();
|
|
2424
2488
|
this.emitChanged(ledger.changeScopesOf(operation));
|
|
2425
|
-
// Nudge reconciliation precedes notifyMember: a committed reply both
|
|
2426
|
-
// resets the author's silence and cancels any of its queued nudge before
|
|
2427
|
-
// the Inbox notice (higher priority) is considered.
|
|
2428
|
-
this.progressNudge.onCommitted(operation);
|
|
2429
2489
|
for (const memberId of ledger.affectedMembersOf(operation)) {
|
|
2430
2490
|
const handle = this.handles.get(memberId);
|
|
2431
2491
|
if (handle !== undefined)
|
|
@@ -2462,7 +2522,17 @@ let AgentTeam = (() => {
|
|
|
2462
2522
|
}
|
|
2463
2523
|
emitAutoCompactionChanged(memberId) {
|
|
2464
2524
|
const workspaceId = this.ledger?.getMember(memberId)?.workspaceId;
|
|
2465
|
-
|
|
2525
|
+
if (workspaceId === undefined)
|
|
2526
|
+
return;
|
|
2527
|
+
this.emitPresenceChanged(workspaceId);
|
|
2528
|
+
}
|
|
2529
|
+
/**
|
|
2530
|
+
* Presence-only invalidation: Agent running/idle/activation/failure changes
|
|
2531
|
+
* alter no durable projection, so only members/presence subscribers wake —
|
|
2532
|
+
* the workspace catalog and the scope-less Inbox subscriptions stay parked.
|
|
2533
|
+
*/
|
|
2534
|
+
emitPresenceChanged(workspaceId) {
|
|
2535
|
+
this.emitChanged([{ kind: 'presence', workspaceId }]);
|
|
2466
2536
|
}
|
|
2467
2537
|
/** Wake from durable unread state with bounded facts for direct and state-changing work. */
|
|
2468
2538
|
notifyMember(agent, sequenced = false) {
|
|
@@ -2495,9 +2565,6 @@ let AgentTeam = (() => {
|
|
|
2495
2565
|
const existingInboxHint = pending.find(message => this.isInboxNotice(message));
|
|
2496
2566
|
if (existingInboxHint !== undefined)
|
|
2497
2567
|
agent.inbox.remove(existingInboxHint.id);
|
|
2498
|
-
// A durable Inbox notice outranks a queued progress nudge; revoke it so
|
|
2499
|
-
// the model reads concrete unread work instead of a generic reminder.
|
|
2500
|
-
this.progressNudge.revokePendingNotice(member.memberId);
|
|
2501
2568
|
const hint = createUserMessage({
|
|
2502
2569
|
content: [{ type: 'text', text: this.notificationText(notifications, member.memberId) }],
|
|
2503
2570
|
source: { kind: 'plugin', plugin: AGENT_TEAM_PLUGIN_ID, form: 'notice', summary: INBOX_NOTICE_SUMMARY },
|
|
@@ -2635,7 +2702,6 @@ let AgentTeam = (() => {
|
|
|
2635
2702
|
}
|
|
2636
2703
|
clearMemberRecoveryState(member) {
|
|
2637
2704
|
this.recovery.stopTracking(member.memberId);
|
|
2638
|
-
this.progressNudge.stopTracking(member.memberId);
|
|
2639
2705
|
this.clearMemberFailure(member.memberId, 'runtime');
|
|
2640
2706
|
this.clearMemberNotificationState(member.memberId);
|
|
2641
2707
|
}
|
|
@@ -2646,21 +2712,45 @@ let AgentTeam = (() => {
|
|
|
2646
2712
|
* Wake waiters for one committed or lifecycle change. Undefined broadcasts
|
|
2647
2713
|
* to everyone; an empty scope list invalidates nobody because no shared
|
|
2648
2714
|
* projection changed; otherwise global and matching scoped waiters wake.
|
|
2715
|
+
* Presence-only scopes sit outside that: they change no durable projection,
|
|
2716
|
+
* so only matching presence waiters wake and the scope-less Inbox
|
|
2717
|
+
* subscriptions stay parked.
|
|
2718
|
+
*
|
|
2719
|
+
* Each woken waiter receives the version of its own scope's domain, so a
|
|
2720
|
+
* wake can never hand a projection waiter a presence number or the other way
|
|
2721
|
+
* around. Nothing here advances the projection version.
|
|
2649
2722
|
*/
|
|
2650
2723
|
emitChanged(scopes) {
|
|
2651
|
-
|
|
2724
|
+
const touchesProjection = scopes === undefined || scopes.some(scope => scope.kind !== 'presence');
|
|
2725
|
+
const touchesPresence = scopes !== undefined && scopes.some(scope => scope.kind === 'presence');
|
|
2726
|
+
if (!touchesProjection && !touchesPresence)
|
|
2727
|
+
return;
|
|
2728
|
+
if (touchesPresence)
|
|
2729
|
+
this.presenceEpoch += 1;
|
|
2652
2730
|
for (const waiter of this.changeWaiters) {
|
|
2653
2731
|
const waiterScope = waiter.scope;
|
|
2654
|
-
if (scopes !== undefined && (waiterScope === undefined ?
|
|
2732
|
+
if (scopes !== undefined && (waiterScope === undefined ? !touchesProjection : !scopes.some(scope => sameChangeScope(scope, waiterScope))))
|
|
2655
2733
|
continue;
|
|
2656
2734
|
this.changeWaiters.delete(waiter);
|
|
2657
|
-
waiter.wake(this.
|
|
2735
|
+
waiter.wake(this.changeVersionOf(waiterScope));
|
|
2658
2736
|
}
|
|
2659
2737
|
}
|
|
2738
|
+
/**
|
|
2739
|
+
* The cursor domain of one scope: presence scopes count process-local edge
|
|
2740
|
+
* wakes, every other scope compares against the durable ledger position of
|
|
2741
|
+
* the newest shared-projection commit. The two domains are deliberately
|
|
2742
|
+
* separate, so a presence edge cannot invalidate a projection subscriber and
|
|
2743
|
+
* a private read invalidates no one.
|
|
2744
|
+
*/
|
|
2745
|
+
changeVersionOf(scope) {
|
|
2746
|
+
if (scope?.kind === 'presence')
|
|
2747
|
+
return this.presenceEpoch;
|
|
2748
|
+
return this.ledger?.projectionSequence() ?? 0;
|
|
2749
|
+
}
|
|
2660
2750
|
validateChangeScope(scope) {
|
|
2661
2751
|
if (scope === undefined)
|
|
2662
2752
|
return undefined;
|
|
2663
|
-
const ref = scope.kind === 'workspace' ? scope.workspaceId : scope.kind === 'channel' ? scope.channelRef : scope.threadRef;
|
|
2753
|
+
const ref = scope.kind === 'workspace' || scope.kind === 'presence' ? scope.workspaceId : scope.kind === 'channel' ? scope.channelRef : scope.threadRef;
|
|
2664
2754
|
if (typeof ref !== 'string' || ref.length === 0)
|
|
2665
2755
|
throw new Error(`change scope of kind '${scope.kind}' requires a non-empty ref`);
|
|
2666
2756
|
return scope;
|