@wowyuarm/dsh-agent-team 0.1.14-alpha.2 → 0.1.14
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 +5 -3
- package/README.zh.md +5 -3
- package/cordis.patch.yml +2 -185
- package/package.json +52 -46
- package/packages/agent-team/README.md +1 -1
- package/packages/agent-team/README.zh.md +1 -1
- package/packages/agent-team/core-skills/member-memory-manager/SKILL.md +30 -0
- package/packages/agent-team/lib/context-continuity-host.js +91 -0
- package/packages/agent-team/lib/context-projection.js +266 -489
- package/packages/agent-team/lib/context-source.js +17 -106
- package/packages/agent-team/lib/human-avatar.js +61 -0
- package/packages/agent-team/lib/human-profile.js +46 -0
- package/packages/agent-team/lib/human-update-check.js +119 -0
- package/packages/agent-team/lib/index.js +408 -264
- package/packages/agent-team/lib/ledger.js +120 -15
- package/packages/agent-team/lib/member-context.js +9 -8
- package/packages/agent-team/lib/member-runtime.js +1 -1
- package/packages/agent-team/lib/member-time-context.js +2 -5
- package/packages/agent-team/lib/preset-roster.js +15 -0
- package/packages/agent-team/lib/pressure-policy.js +9 -6
- package/packages/agent-team/lib/session-remediation.js +493 -0
- package/packages/agent-team/lib/stored-session-reader.js +2 -1
- package/packages/agent-team/lib/typert.host.js +605 -291
- package/packages/agent-team/lib/typert.remote-client.d.ts +15 -6
- package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
- package/packages/agent-team/lib/typert.remote-client.js +367 -244
- package/packages/agent-team/lib/types/context-continuity-host.d.ts +84 -0
- package/packages/agent-team/lib/types/context-continuity-host.d.ts.map +1 -0
- package/packages/agent-team/lib/types/context-projection.d.ts +130 -185
- package/packages/agent-team/lib/types/context-projection.d.ts.map +1 -1
- package/packages/agent-team/lib/types/context-source.d.ts +17 -81
- package/packages/agent-team/lib/types/context-source.d.ts.map +1 -1
- package/packages/agent-team/lib/types/human-avatar.d.ts +29 -0
- package/packages/agent-team/lib/types/human-avatar.d.ts.map +1 -0
- package/packages/agent-team/lib/types/human-profile.d.ts +44 -0
- package/packages/agent-team/lib/types/human-profile.d.ts.map +1 -0
- package/packages/agent-team/lib/types/human-update-check.d.ts +73 -0
- package/packages/agent-team/lib/types/human-update-check.d.ts.map +1 -0
- package/packages/agent-team/lib/types/index.d.ts +112 -38
- package/packages/agent-team/lib/types/index.d.ts.map +1 -1
- package/packages/agent-team/lib/types/ledger.d.ts +31 -2
- package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
- package/packages/agent-team/lib/types/member-context.d.ts +0 -14
- package/packages/agent-team/lib/types/member-context.d.ts.map +1 -1
- package/packages/agent-team/lib/types/member-time-context.d.ts +0 -15
- package/packages/agent-team/lib/types/member-time-context.d.ts.map +1 -1
- package/packages/agent-team/lib/types/preset-roster.d.ts +5 -0
- package/packages/agent-team/lib/types/preset-roster.d.ts.map +1 -0
- package/packages/agent-team/lib/types/pressure-policy.d.ts +6 -0
- package/packages/agent-team/lib/types/pressure-policy.d.ts.map +1 -1
- package/packages/agent-team/lib/types/session-remediation.d.ts +172 -0
- package/packages/agent-team/lib/types/session-remediation.d.ts.map +1 -0
- package/packages/agent-team/lib/types/stored-session-reader.d.ts +2 -1
- package/packages/agent-team/lib/types/stored-session-reader.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types/entities.d.ts +20 -3
- package/packages/agent-team/lib/types/types/entities.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types/requests-results.d.ts +63 -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 +166 -0
- package/packages/client-agent-team/lib/client.js +1667 -516
- package/packages/client-agent-team/lib/client.js.map +1 -1
- package/packages/client-agent-team/lib/types/client/HumanSettingsSection.d.ts +15 -0
- package/packages/client-agent-team/lib/types/client/HumanSettingsSection.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/HumanSettingsSection.js +115 -0
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +12 -8
- package/packages/client-agent-team/lib/types/client/TeamAvatarStack.d.ts +22 -1
- package/packages/client-agent-team/lib/types/client/TeamAvatarStack.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAvatarStack.js +25 -2
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts +6 -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 +69 -31
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +4 -4
- package/packages/client-agent-team/lib/types/client/TeamComposer.js +3 -3
- 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 +12 -5
- package/packages/client-agent-team/lib/types/client/TeamFooterAction.js +2 -2
- package/packages/client-agent-team/lib/types/client/TeamInboxPage.d.ts +4 -1
- package/packages/client-agent-team/lib/types/client/TeamInboxPage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamInboxPage.js +22 -7
- package/packages/client-agent-team/lib/types/client/TeamMemberEditor.d.ts +11 -1
- package/packages/client-agent-team/lib/types/client/TeamMemberEditor.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamMemberEditor.js +69 -8
- package/packages/client-agent-team/lib/types/client/TeamMembersAction.js +2 -2
- package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts +18 -3
- package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamMessage.js +213 -67
- package/packages/client-agent-team/lib/types/client/TeamRowMenu.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamRowMenu.js +2 -2
- package/packages/client-agent-team/lib/types/client/TeamSidebarSection.js +2 -2
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts +5 -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 +55 -29
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.js +3 -3
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceSelector.js +2 -2
- package/packages/client-agent-team/lib/types/client/avatar-image.d.ts +21 -0
- package/packages/client-agent-team/lib/types/client/avatar-image.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/avatar-image.js +9 -0
- package/packages/client-agent-team/lib/types/client/human-identity.d.ts +94 -0
- package/packages/client-agent-team/lib/types/client/human-identity.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/human-identity.js +117 -0
- package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/index.js +112 -41
- package/packages/client-agent-team/lib/types/client/locales.d.ts +72 -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 +78 -2
- package/packages/client-agent-team/lib/types/client/multi-menu-field.js +2 -2
- package/packages/client-agent-team/lib/types/client/refs.d.ts +71 -0
- package/packages/client-agent-team/lib/types/client/refs.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/refs.js +181 -0
- package/packages/client-agent-team/lib/types/client/slots.d.ts +8 -2
- package/packages/client-agent-team/lib/types/client/slots.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/team-formatters.d.ts +57 -9
- 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 +85 -17
- 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/context-tools.js +20 -6
- package/packages/tool-agent-team/lib/index.js +2 -2
- package/packages/tool-agent-team/lib/types/context-tools.d.ts.map +1 -1
- package/packages/agent-team/lib/context-management.js +0 -371
- package/packages/agent-team/lib/types/context-management.d.ts +0 -154
- package/packages/agent-team/lib/types/context-management.d.ts.map +0 -1
- package/packages/client-agent-team/lib/types/client/task-refs.d.ts +0 -31
- package/packages/client-agent-team/lib/types/client/task-refs.d.ts.map +0 -1
- package/packages/client-agent-team/lib/types/client/task-refs.js +0 -99
|
@@ -50,19 +50,25 @@ import { Session, SessionId, SessionLogOffset } from '@deepseek-ai/dsh-session';
|
|
|
50
50
|
import { setSandboxMode } from '@deepseek-ai/dsh-sandbox-policy';
|
|
51
51
|
import { Remote, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
52
52
|
import { ATTACHMENT_MAX_BYTES, attachmentPayloadPath, attachmentsRoot, copyPathAttachment, newAttachmentId, readAttachment, sanitizeMediaType, sweepAttachmentCache, validatePathAttachment, writeAttachment } from "./attachments.js";
|
|
53
|
+
import { HUMAN_PROFILE_DEFAULT_NAME, HUMAN_PROFILE_REPO_URL, HUMAN_PROFILE_SETTINGS_NAMESPACE, HUMAN_PROFILE_SETTINGS_SCHEMA, HUMAN_PROFILE_VERSION, assertValidHumanName, normalizeHumanName } from "./human-profile.js";
|
|
54
|
+
import { humanAvatarsRoot, readHumanAvatar, removeHumanAvatar, writeHumanAvatar } from "./human-avatar.js";
|
|
55
|
+
import { createHumanUpdateChecker } from "./human-update-check.js";
|
|
53
56
|
import { PressurePolicyCoordinator } from "./pressure-policy.js";
|
|
54
|
-
import {
|
|
55
|
-
import {
|
|
56
|
-
import {
|
|
57
|
-
import {
|
|
57
|
+
import { CONTEXT_CONTINUITY_PROJECTION_KEY, readContextTimeline } from '@wowyuarm/dsh-context-continuity';
|
|
58
|
+
import { createTeamContextManagement, TEAM_CONTEXT_CODEC } from "./context-continuity-host.js";
|
|
59
|
+
import { AGENT_TEAM_PLUGIN_ID } from "./context-source.js";
|
|
60
|
+
import { boundaryByRef, carriedInputOf, checkpointByRef, checkpointRefFor, createTeamContextProjectionConfig, createTeamContextProjectionDefinition, foldTeamContextProjection, retainedTopicsThrough, TeamContextProjectionHost } from "./context-projection.js";
|
|
58
61
|
import { AGENT_TEAM_HUMAN_MEMBER_ID, AgentTeamLedger, agentTeamHumanActor } from "./ledger.js";
|
|
59
62
|
import { AGENT_TEAM_TOOL_NAMES, deepCopyCapabilities, memberMemoryDirectoryName, MemberRuntime } from "./member-runtime.js";
|
|
60
63
|
import { classifyRecoverableError, RecoveryCoordinator, RECOVERY_MAX_CONSECUTIVE_ERRORS } from "./recovery.js";
|
|
64
|
+
import { SessionRemediation, handoffAlreadyInLog } from "./session-remediation.js";
|
|
61
65
|
import { StoredSessionReadError, StoredSessionReader, sessionFailureOf } from "./stored-session-reader.js";
|
|
62
66
|
import { agentTeamDomainSpec } from "./spec.js";
|
|
63
67
|
import { formatTeamTimestamp } from "./time-format.js";
|
|
64
68
|
export { agentTeamDomainSpec, agentTeamOperationSchema } from "./spec.js";
|
|
65
69
|
export { AGENT_TEAM_HUMAN_HANDLE, AGENT_TEAM_HUMAN_MEMBER_ID, AGENT_TEAM_INITIALIZE_REQUEST_ID } from "./ledger.js";
|
|
70
|
+
export { HUMAN_PROFILE_DEFAULT_NAME, HUMAN_PROFILE_REPO_URL, HUMAN_PROFILE_SETTINGS_NAMESPACE, HUMAN_PROFILE_SETTINGS_SCHEMA, HUMAN_PROFILE_VERSION, assertValidHumanName, normalizeHumanName } from "./human-profile.js";
|
|
71
|
+
export { humanAvatarsRoot } from "./human-avatar.js";
|
|
66
72
|
export { AGENT_TEAM_TOOL_NAMES } from "./member-runtime.js";
|
|
67
73
|
/** Process-stable marker carried by the final Team message tool definition. */
|
|
68
74
|
export const AGENT_TEAM_PRESET_MARKER = Symbol.for('@wowyuarm/dsh-agent-team.preset');
|
|
@@ -85,7 +91,12 @@ const MAX_CHECKPOINT_NAME_CHARS = 120;
|
|
|
85
91
|
/** Default and maximum number of timeline items one query returns. */
|
|
86
92
|
const DEFAULT_TIMELINE_LIMIT = 12;
|
|
87
93
|
const MAX_TIMELINE_LIMIT = 24;
|
|
88
|
-
/**
|
|
94
|
+
/**
|
|
95
|
+
* Archived generations the timeline and seed resolution walk: the timeline
|
|
96
|
+
* reads this many ancestors behind the current generation, and the seed guard
|
|
97
|
+
* resolves a cited ref through the same depth, so the two surfaces cannot
|
|
98
|
+
* disagree about where history ends.
|
|
99
|
+
*/
|
|
89
100
|
const MAX_TIMELINE_ANCESTORS = 8;
|
|
90
101
|
/** Product pressure budget constants (see docs/team-collaboration.md). */
|
|
91
102
|
const CONTEXT_HARD_LIMIT_CAP = 256_000;
|
|
@@ -163,6 +174,7 @@ let AgentTeam = (() => {
|
|
|
163
174
|
let _classSuper = TypertRemoteService;
|
|
164
175
|
let _instanceExtraInitializers = [];
|
|
165
176
|
let _resolveTaskRefs_decorators;
|
|
177
|
+
let _resolveThreadRefs_decorators;
|
|
166
178
|
let _membersForClient_decorators;
|
|
167
179
|
let _changes_decorators;
|
|
168
180
|
let _createChannel_decorators;
|
|
@@ -182,6 +194,10 @@ let AgentTeam = (() => {
|
|
|
182
194
|
let _sendMessage_decorators;
|
|
183
195
|
let _putAttachment_decorators;
|
|
184
196
|
let _getAttachment_decorators;
|
|
197
|
+
let _humanProfileForClient_decorators;
|
|
198
|
+
let _putHumanAvatar_decorators;
|
|
199
|
+
let _getHumanAvatar_decorators;
|
|
200
|
+
let _removeHumanAvatar_decorators;
|
|
185
201
|
let _reply_decorators;
|
|
186
202
|
let _changeAttention_decorators;
|
|
187
203
|
let _inbox_decorators;
|
|
@@ -193,6 +209,7 @@ let AgentTeam = (() => {
|
|
|
193
209
|
static {
|
|
194
210
|
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
195
211
|
_resolveTaskRefs_decorators = [Remote('resolveTaskRefs')];
|
|
212
|
+
_resolveThreadRefs_decorators = [Remote('resolveThreadRefs')];
|
|
196
213
|
_membersForClient_decorators = [Remote('members')];
|
|
197
214
|
_changes_decorators = [Remote({ mode: 'stream' })];
|
|
198
215
|
_createChannel_decorators = [Remote('createChannel')];
|
|
@@ -212,6 +229,10 @@ let AgentTeam = (() => {
|
|
|
212
229
|
_sendMessage_decorators = [Remote('sendMessage')];
|
|
213
230
|
_putAttachment_decorators = [Remote('putAttachment')];
|
|
214
231
|
_getAttachment_decorators = [Remote('getAttachment')];
|
|
232
|
+
_humanProfileForClient_decorators = [Remote('humanProfile')];
|
|
233
|
+
_putHumanAvatar_decorators = [Remote('putHumanAvatar')];
|
|
234
|
+
_getHumanAvatar_decorators = [Remote('getHumanAvatar')];
|
|
235
|
+
_removeHumanAvatar_decorators = [Remote('removeHumanAvatar')];
|
|
215
236
|
_reply_decorators = [Remote('reply')];
|
|
216
237
|
_changeAttention_decorators = [Remote('changeAttention')];
|
|
217
238
|
_inbox_decorators = [Remote('inbox')];
|
|
@@ -220,6 +241,7 @@ let AgentTeam = (() => {
|
|
|
220
241
|
_threadHistory_decorators = [Remote('threadHistory')];
|
|
221
242
|
_view_decorators = [Remote('view')];
|
|
222
243
|
__esDecorate(this, null, _resolveTaskRefs_decorators, { kind: "method", name: "resolveTaskRefs", static: false, private: false, access: { has: obj => "resolveTaskRefs" in obj, get: obj => obj.resolveTaskRefs }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
244
|
+
__esDecorate(this, null, _resolveThreadRefs_decorators, { kind: "method", name: "resolveThreadRefs", static: false, private: false, access: { has: obj => "resolveThreadRefs" in obj, get: obj => obj.resolveThreadRefs }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
223
245
|
__esDecorate(this, null, _membersForClient_decorators, { kind: "method", name: "membersForClient", static: false, private: false, access: { has: obj => "membersForClient" in obj, get: obj => obj.membersForClient }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
224
246
|
__esDecorate(this, null, _changes_decorators, { kind: "method", name: "changes", static: false, private: false, access: { has: obj => "changes" in obj, get: obj => obj.changes }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
225
247
|
__esDecorate(this, null, _createChannel_decorators, { kind: "method", name: "createChannel", static: false, private: false, access: { has: obj => "createChannel" in obj, get: obj => obj.createChannel }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
@@ -239,6 +261,10 @@ let AgentTeam = (() => {
|
|
|
239
261
|
__esDecorate(this, null, _sendMessage_decorators, { kind: "method", name: "sendMessage", static: false, private: false, access: { has: obj => "sendMessage" in obj, get: obj => obj.sendMessage }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
240
262
|
__esDecorate(this, null, _putAttachment_decorators, { kind: "method", name: "putAttachment", static: false, private: false, access: { has: obj => "putAttachment" in obj, get: obj => obj.putAttachment }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
241
263
|
__esDecorate(this, null, _getAttachment_decorators, { kind: "method", name: "getAttachment", static: false, private: false, access: { has: obj => "getAttachment" in obj, get: obj => obj.getAttachment }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
264
|
+
__esDecorate(this, null, _humanProfileForClient_decorators, { kind: "method", name: "humanProfileForClient", static: false, private: false, access: { has: obj => "humanProfileForClient" in obj, get: obj => obj.humanProfileForClient }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
265
|
+
__esDecorate(this, null, _putHumanAvatar_decorators, { kind: "method", name: "putHumanAvatar", static: false, private: false, access: { has: obj => "putHumanAvatar" in obj, get: obj => obj.putHumanAvatar }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
266
|
+
__esDecorate(this, null, _getHumanAvatar_decorators, { kind: "method", name: "getHumanAvatar", static: false, private: false, access: { has: obj => "getHumanAvatar" in obj, get: obj => obj.getHumanAvatar }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
267
|
+
__esDecorate(this, null, _removeHumanAvatar_decorators, { kind: "method", name: "removeHumanAvatar", static: false, private: false, access: { has: obj => "removeHumanAvatar" in obj, get: obj => obj.removeHumanAvatar }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
242
268
|
__esDecorate(this, null, _reply_decorators, { kind: "method", name: "reply", static: false, private: false, access: { has: obj => "reply" in obj, get: obj => obj.reply }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
243
269
|
__esDecorate(this, null, _changeAttention_decorators, { kind: "method", name: "changeAttention", static: false, private: false, access: { has: obj => "changeAttention" in obj, get: obj => obj.changeAttention }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
244
270
|
__esDecorate(this, null, _inbox_decorators, { kind: "method", name: "inbox", static: false, private: false, access: { has: obj => "inbox" in obj, get: obj => obj.inbox }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
@@ -256,6 +282,7 @@ let AgentTeam = (() => {
|
|
|
256
282
|
'agentPresets',
|
|
257
283
|
'tools',
|
|
258
284
|
'sessionPersistence',
|
|
285
|
+
'sessionProjections',
|
|
259
286
|
];
|
|
260
287
|
domain = __runInitializers(this, _instanceExtraInitializers);
|
|
261
288
|
ledger;
|
|
@@ -297,6 +324,18 @@ let AgentTeam = (() => {
|
|
|
297
324
|
pressurePolicy;
|
|
298
325
|
notifiedInbox = new Map();
|
|
299
326
|
attachmentGcTimer;
|
|
327
|
+
/**
|
|
328
|
+
* Current human profile source, wired to the `agent-team-human` settings
|
|
329
|
+
* namespace when a settings service is present. Falls back to the historic
|
|
330
|
+
* default so team_view and @ matching keep working without settings.
|
|
331
|
+
*/
|
|
332
|
+
humanProfileSource = () => ({ name: HUMAN_PROFILE_DEFAULT_NAME });
|
|
333
|
+
/**
|
|
334
|
+
* New-release check behind the settings footnote. Memory-only and
|
|
335
|
+
* background-refreshed, so the profile read path never waits on the
|
|
336
|
+
* network and every failure settles as "no update known".
|
|
337
|
+
*/
|
|
338
|
+
humanUpdateCheck = createHumanUpdateChecker({ currentVersion: HUMAN_PROFILE_VERSION });
|
|
300
339
|
recovery = new RecoveryCoordinator({
|
|
301
340
|
wake: memberId => {
|
|
302
341
|
this.ctx.logger.info(`agent-team: automatic recovery wakeup for member '${this.memberLabel(memberId)}' after consecutive recoverable failures`);
|
|
@@ -307,36 +346,35 @@ let AgentTeam = (() => {
|
|
|
307
346
|
},
|
|
308
347
|
});
|
|
309
348
|
/**
|
|
310
|
-
*
|
|
311
|
-
*
|
|
312
|
-
*
|
|
313
|
-
*
|
|
314
|
-
*
|
|
349
|
+
* Team's domain half of the continuity projection: the durable ref naming,
|
|
350
|
+
* the Team-notice rule, and the boundary judgement (committed messages,
|
|
351
|
+
* claim changes, first Thread arrivals). Its claim attribution resolves the
|
|
352
|
+
* Task's Thread through the ledger, which is why the resolver reads
|
|
353
|
+
* `this.ledger` lazily — the fold may run before the domain is open, and an
|
|
354
|
+
* unattributed boundary is still a valid anchor.
|
|
315
355
|
*/
|
|
316
|
-
|
|
356
|
+
contextProjectionHost = new TeamContextProjectionHost({
|
|
357
|
+
threadForTask: taskRef => this.ledger?.threadForTask(taskRef),
|
|
358
|
+
});
|
|
317
359
|
/**
|
|
318
360
|
* Context self-management: the one deep module that turns a Member's
|
|
319
361
|
* successful `context_rollover` tool result into its next private context
|
|
320
|
-
* generation. The ledger owns the binding audit, the
|
|
321
|
-
* owns intent, and this coordinator owns only reconstructible process
|
|
362
|
+
* generation. The ledger owns the binding audit, the engine's projection
|
|
363
|
+
* unit owns intent, and this coordinator owns only reconstructible process
|
|
322
364
|
* state. See docs/architecture.md and docs/team-collaboration.md.
|
|
323
365
|
*/
|
|
324
|
-
contextManagement =
|
|
366
|
+
contextManagement = createTeamContextManagement({
|
|
325
367
|
agentForMember: memberId => this.handles.get(memberId)?.agent,
|
|
326
368
|
memberForAgent: agent => this.memberForAgent(agent),
|
|
327
369
|
projectionForMember: (memberId, sessionId) => {
|
|
328
370
|
const handle = this.handles.get(memberId);
|
|
329
371
|
if (handle === undefined || handle.agent.session.id !== sessionId)
|
|
330
372
|
return undefined;
|
|
331
|
-
|
|
332
|
-
//
|
|
333
|
-
//
|
|
334
|
-
//
|
|
335
|
-
|
|
336
|
-
// and a rewritten log falls back to a cold fold inside the cursor.
|
|
337
|
-
const owned = advanceOwnedSessionEventCursor(this.contextCursors.get(memberId), session.id, contextProjectionFold(session.id), session.ownEvents(), session.inheritedEventCount);
|
|
338
|
-
this.contextCursors.set(memberId, owned);
|
|
339
|
-
return owned.cursor.value;
|
|
373
|
+
// The engine's registered unit folds this Session's durable log (replay
|
|
374
|
+
// on attach, then incrementally per committed event) and keys every ref
|
|
375
|
+
// to the Session that recorded it; the registry materializes the cell
|
|
376
|
+
// lazily, so a Member that has not folded yet is built on this read.
|
|
377
|
+
return this.ctx.sessionProjections.stateOf(handle.agent.session, CONTEXT_CONTINUITY_PROJECTION_KEY);
|
|
340
378
|
},
|
|
341
379
|
executeTransition: (memberId, plan) => this.executeMemberTransition(memberId, plan),
|
|
342
380
|
log: message => { this.ctx.logger.warn(`agent-team: ${message}`); },
|
|
@@ -350,6 +388,12 @@ let AgentTeam = (() => {
|
|
|
350
388
|
*/
|
|
351
389
|
presenceEpoch = 0;
|
|
352
390
|
changeWaiters = new Set();
|
|
391
|
+
/**
|
|
392
|
+
* The startup-opened remediation instance, held for the restart heal: the
|
|
393
|
+
* completion-cache domain may only be opened once per plugin lifecycle, so
|
|
394
|
+
* the restart path reuses this instance instead of opening its own.
|
|
395
|
+
*/
|
|
396
|
+
remediation;
|
|
353
397
|
constructor(ctx) {
|
|
354
398
|
super(ctx, 'agentTeam');
|
|
355
399
|
this.pressurePolicy = new PressurePolicyCoordinator({
|
|
@@ -368,9 +412,72 @@ let AgentTeam = (() => {
|
|
|
368
412
|
},
|
|
369
413
|
log: message => { this.ctx.logger.warn(`agent-team: ${message}`); },
|
|
370
414
|
});
|
|
415
|
+
// Human profile settings: the composition entry is the historic default;
|
|
416
|
+
// the settings provider overlays the user document section when present.
|
|
417
|
+
// Reads stay live through the source closure, so team_view and @ matching
|
|
418
|
+
// follow a rename without a Host restart.
|
|
419
|
+
this.ctx.inject(['settings'], settingsCtx => {
|
|
420
|
+
settingsCtx.settings.installSection(this.ctx, HUMAN_PROFILE_SETTINGS_NAMESPACE, HUMAN_PROFILE_SETTINGS_SCHEMA, { name: HUMAN_PROFILE_DEFAULT_NAME }, {
|
|
421
|
+
setSource: (current) => {
|
|
422
|
+
this.humanProfileSource = current;
|
|
423
|
+
this.syncHumanHandle();
|
|
424
|
+
},
|
|
425
|
+
validate: (value) => {
|
|
426
|
+
this.validateHumanProfile(value);
|
|
427
|
+
},
|
|
428
|
+
onChange: () => {
|
|
429
|
+
this.syncHumanHandle();
|
|
430
|
+
},
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
/** Current human display name; the single source for team_view and @ matching. */
|
|
435
|
+
humanHandle() {
|
|
436
|
+
const name = normalizeHumanName(this.humanProfileSource().name);
|
|
437
|
+
return name === '' ? HUMAN_PROFILE_DEFAULT_NAME : name;
|
|
438
|
+
}
|
|
439
|
+
/** Current human profile reference held in settings (name + avatarRef). */
|
|
440
|
+
humanProfile() {
|
|
441
|
+
const current = this.humanProfileSource();
|
|
442
|
+
const name = this.humanHandle();
|
|
443
|
+
return Object.freeze({ name, ...(current.avatarRef === undefined ? {} : { avatarRef: current.avatarRef }) });
|
|
444
|
+
}
|
|
445
|
+
/** Push the current settings name into the ledger's runtime @ handle. */
|
|
446
|
+
syncHumanHandle() {
|
|
447
|
+
try {
|
|
448
|
+
this.ledger?.setHumanDisplayHandle(this.humanHandle());
|
|
449
|
+
}
|
|
450
|
+
catch {
|
|
451
|
+
// The ledger is absent before Service.init; the post-open sync covers it.
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Settings-level validation for the human profile: same non-empty floor as
|
|
456
|
+
* Member handles plus global uniqueness against live Members. Runs inside
|
|
457
|
+
* the settings write path, so a colliding rename rejects before persisting.
|
|
458
|
+
*/
|
|
459
|
+
validateHumanProfile(value) {
|
|
460
|
+
const name = assertValidHumanName(value.name);
|
|
461
|
+
const ledger = this.ledger;
|
|
462
|
+
if (ledger === undefined)
|
|
463
|
+
return;
|
|
464
|
+
const normalized = name.normalize('NFKC').trim().toLowerCase();
|
|
465
|
+
for (const member of ledger.listMembers()) {
|
|
466
|
+
if (member.state === 'inactive' || member.state === 'archived')
|
|
467
|
+
continue;
|
|
468
|
+
if (member.handle.normalize('NFKC').trim().toLowerCase() === normalized) {
|
|
469
|
+
throw new Error(`human name '${name}' collides with an existing Member handle`);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
371
472
|
}
|
|
372
473
|
/** Open the durable ledger and restore every enabled Member independently. */
|
|
373
474
|
async [Service.init]() {
|
|
475
|
+
// The continuity projection registers once per Host: the framework keeps
|
|
476
|
+
// one unit per projection key and drives it for every Session, so Team's
|
|
477
|
+
// fold is session-agnostic and the state carries the identity it folds.
|
|
478
|
+
// The registration rides this plugin's fiber — unloading Team removes the
|
|
479
|
+
// key (and its cached cells) from later drives and snapshots.
|
|
480
|
+
this.ctx.effect(() => this.ctx.root.sessionProjections.register(createTeamContextProjectionDefinition(this.contextProjectionHost)), 'agentTeam.contextProjection');
|
|
374
481
|
this.ctx.on('agent/error', ({ agent, error }) => {
|
|
375
482
|
const member = this.memberForAgent(agent);
|
|
376
483
|
if (member === undefined)
|
|
@@ -438,6 +545,7 @@ let AgentTeam = (() => {
|
|
|
438
545
|
const domain = await this.ctx.storageDomain.open(agentTeamDomainSpec);
|
|
439
546
|
this.ctx.effect(() => async () => {
|
|
440
547
|
this.accepting = false;
|
|
548
|
+
this.remediation = undefined;
|
|
441
549
|
this.recovery.dispose();
|
|
442
550
|
this.contextManagement.dispose();
|
|
443
551
|
this.pressurePolicy.dispose();
|
|
@@ -456,10 +564,25 @@ let AgentTeam = (() => {
|
|
|
456
564
|
this.domain = domain;
|
|
457
565
|
const ledger = new AgentTeamLedger(domain.table('operations'));
|
|
458
566
|
this.ledger = ledger;
|
|
567
|
+
// The settings wiring in the constructor may have fired before the ledger
|
|
568
|
+
// existed; sync once here so @ matching starts from the stored name.
|
|
569
|
+
this.syncHumanHandle();
|
|
459
570
|
const initialization = await ledger.initialize();
|
|
460
571
|
if (initialization.committed)
|
|
461
572
|
this.emitCommitted(initialization.value);
|
|
462
573
|
this.startAttachmentGc(ledger);
|
|
574
|
+
// Legacy-artifact remediation runs before any Member activation: no write
|
|
575
|
+
// lease exists yet, so publishing sibling generations for refused Session
|
|
576
|
+
// logs cannot race a live writer. Remediation failure never blocks
|
|
577
|
+
// startup — the next start retries exactly what the cache does not cover.
|
|
578
|
+
try {
|
|
579
|
+
const remediation = new SessionRemediation(this.ctx, this.ctx.sessionPersistence, await SessionRemediation.open(this.ctx));
|
|
580
|
+
this.remediation = remediation;
|
|
581
|
+
await remediation.remediateEnabledMembers(ledger.listMembers());
|
|
582
|
+
}
|
|
583
|
+
catch (error) {
|
|
584
|
+
this.ctx.logger.warn(`agent-team: legacy Session remediation did not run to completion (it will retry on the next start): ${error instanceof Error ? error.message : String(error)}`);
|
|
585
|
+
}
|
|
463
586
|
// One metadata listing serves every Member restore; per-member list calls
|
|
464
587
|
// would repeat the same I/O linearly during startup.
|
|
465
588
|
const persistedSessions = new Set((await this.persistedSessionHeaders()).map(snapshot => snapshot.header.id));
|
|
@@ -531,6 +654,18 @@ let AgentTeam = (() => {
|
|
|
531
654
|
});
|
|
532
655
|
return Object.freeze({ resolved: Object.freeze(this.requireLedger().resolveTaskRefs(request.workspaceId, taskRefs)) });
|
|
533
656
|
}
|
|
657
|
+
/** Read-only navigation lookup for branded Thread refs found in message bodies. */
|
|
658
|
+
resolveThreadRefs(request) {
|
|
659
|
+
this.requireWorkspace(request.workspaceId);
|
|
660
|
+
const seen = new Set();
|
|
661
|
+
const threadRefs = request.threadRefs.filter(threadRef => {
|
|
662
|
+
if (seen.has(threadRef))
|
|
663
|
+
return false;
|
|
664
|
+
seen.add(threadRef);
|
|
665
|
+
return true;
|
|
666
|
+
});
|
|
667
|
+
return Object.freeze({ resolved: Object.freeze(this.requireLedger().resolveThreadRefs(request.workspaceId, threadRefs)) });
|
|
668
|
+
}
|
|
534
669
|
/** Browser-safe Human roster, optionally filtered to one participation. */
|
|
535
670
|
membersForClient(request) {
|
|
536
671
|
if (request.workspaceId !== undefined)
|
|
@@ -634,13 +769,16 @@ let AgentTeam = (() => {
|
|
|
634
769
|
privateMemoryPath: dshHomePath('agent-team', 'members', memberMemoryDirectoryName(memberId)),
|
|
635
770
|
state: 'enabled',
|
|
636
771
|
});
|
|
637
|
-
const
|
|
772
|
+
const ledger = this.requireLedger();
|
|
773
|
+
const result = await ledger.addMember({ ...request, actor: agentTeamHumanActor(), member });
|
|
638
774
|
if (result.committed)
|
|
639
775
|
this.emitCommitted(result.value.receipt);
|
|
640
776
|
const stored = result.value.member;
|
|
641
777
|
if (!this.handles.has(stored.memberId))
|
|
642
778
|
await this.activateMember(stored, workspace.path);
|
|
643
|
-
|
|
779
|
+
// Creation seeds exactly the creation Workspace participation; attach it
|
|
780
|
+
// here (like membersForClient) so the Client never synthesizes it.
|
|
781
|
+
return Object.freeze({ receipt: result.value.receipt, status: this.memberStatus(stored), workspaceIds: ledger.workspacesOf(stored.memberId) });
|
|
644
782
|
});
|
|
645
783
|
}
|
|
646
784
|
/** Commit suspended intent, then wait for the owned AgentHandle to become quiescent. */
|
|
@@ -698,10 +836,23 @@ let AgentTeam = (() => {
|
|
|
698
836
|
if (member.state !== 'enabled')
|
|
699
837
|
throw new Error(`Agent Member '${member.handle}' is ${member.state}; only enabled Members can be restarted`);
|
|
700
838
|
this.ctx.logger.info(`agent-team: restarting member '${member.handle}' after a failed activation`);
|
|
701
|
-
//
|
|
702
|
-
//
|
|
703
|
-
//
|
|
704
|
-
|
|
839
|
+
// A deterministic session refusal may be repairable in place: run the
|
|
840
|
+
// same bounded startup remediation for this one Member before retrying
|
|
841
|
+
// activation, so the restart heals instead of replaying the failure.
|
|
842
|
+
const activation = this.memberFailures.get(request.memberId)?.activation;
|
|
843
|
+
if (activation !== undefined && activation.class === 'session-refused' && this.remediation !== undefined) {
|
|
844
|
+
const outcome = await this.remediation.remediateMember(member);
|
|
845
|
+
if (outcome.repaired > 0) {
|
|
846
|
+
this.ctx.logger.info(`agent-team: repaired ${outcome.repaired} refused Session artifact(s) for member '${member.handle}'; retrying activation`);
|
|
847
|
+
}
|
|
848
|
+
else if (outcome.completed) {
|
|
849
|
+
// The walk finished and nothing was provably this plugin's to fix:
|
|
850
|
+
// a retry would fail identically. Mark the refusal non-remediable
|
|
851
|
+
// so the surface stops offering restart and says why.
|
|
852
|
+
this.markRefusalNonRemediable(request.memberId);
|
|
853
|
+
return Object.freeze({ status: this.memberStatus(member) });
|
|
854
|
+
}
|
|
855
|
+
}
|
|
705
856
|
await this.reactivateMember(request.memberId);
|
|
706
857
|
return Object.freeze({ status: this.memberStatus(member) });
|
|
707
858
|
}
|
|
@@ -820,6 +971,9 @@ let AgentTeam = (() => {
|
|
|
820
971
|
throw new Error(`the context rollover is refused: this Member now owns jobs that would not survive the switch (${blockingJobs.join(', ')}); collect or stop them, then retry`);
|
|
821
972
|
}
|
|
822
973
|
const rolled = await this.rolloverSessionForAgent(active.agent, {
|
|
974
|
+
// The engine plan carries the host-derived request id as a plain
|
|
975
|
+
// string; Team's rollover operation vocabulary brands it, and the
|
|
976
|
+
// context-continuity host adapter is its only producer.
|
|
823
977
|
requestId: plan.requestId,
|
|
824
978
|
workspaceId: stored.workspaceId,
|
|
825
979
|
memberId,
|
|
@@ -836,8 +990,9 @@ let AgentTeam = (() => {
|
|
|
836
990
|
// to override the fold.
|
|
837
991
|
const preRetireCapture = this.contextManagement.drainCapturedInput(memberId);
|
|
838
992
|
await this.retireMemberGeneration(memberId, active, plan.previousSessionId);
|
|
839
|
-
const
|
|
840
|
-
const
|
|
993
|
+
const finalEvents = active.agent.session.ownEvents();
|
|
994
|
+
const finalState = foldTeamContextProjection(finalEvents, { sessionId: active.agent.session.id, inheritedEventCount: active.agent.session.inheritedEventCount }, this.contextProjectionHost);
|
|
995
|
+
const foldedCarried = carriedInputOf(finalState, finalEvents);
|
|
841
996
|
const carriedById = new Map(plan.carriedInput.map(message => [message.id, message]));
|
|
842
997
|
for (const message of foldedCarried)
|
|
843
998
|
carriedById.set(message.id, message);
|
|
@@ -913,7 +1068,7 @@ let AgentTeam = (() => {
|
|
|
913
1068
|
const body = notifications.length === 0 ? text : `${text}\n\n${this.notificationText(notifications, member.memberId)}`;
|
|
914
1069
|
const hint = createUserMessage({
|
|
915
1070
|
content: [{ type: 'text', text: body }],
|
|
916
|
-
source: { kind: AGENT_TEAM_PLUGIN_ID, form: 'notice', summary: RECOVERY_NOTICE_SUMMARY },
|
|
1071
|
+
source: { kind: 'plugin', plugin: AGENT_TEAM_PLUGIN_ID, form: 'notice', summary: RECOVERY_NOTICE_SUMMARY },
|
|
917
1072
|
});
|
|
918
1073
|
for (const pending of [...handle.agent.inbox.nextStep, ...handle.agent.inbox.nextTurn]) {
|
|
919
1074
|
if (this.isInboxNotice(pending))
|
|
@@ -1119,6 +1274,50 @@ let AgentTeam = (() => {
|
|
|
1119
1274
|
throw new Error(`attachment '${request.attachmentId}' is no longer cached`);
|
|
1120
1275
|
return Object.freeze({ name: stored.name, mediaType: stored.mediaType, byteSize: stored.byteSize, bytesBase64: stored.bytes.toString('base64') });
|
|
1121
1276
|
}
|
|
1277
|
+
/**
|
|
1278
|
+
* Human profile read for the settings page and footnote: name + avatar
|
|
1279
|
+
* reference from settings plus version facts. Human-scoped (the Web
|
|
1280
|
+
* Client calls it); agent tools never receive avatar bytes, only the name
|
|
1281
|
+
* through team_view. The new-release check stays best-effort and cached —
|
|
1282
|
+
* `updateAvailable` is false until a background refresh actually observes a
|
|
1283
|
+
* newer published release.
|
|
1284
|
+
*/
|
|
1285
|
+
humanProfileForClient(_request) {
|
|
1286
|
+
const profile = this.humanProfile();
|
|
1287
|
+
const update = this.humanUpdateCheck.snapshot();
|
|
1288
|
+
return Object.freeze({
|
|
1289
|
+
name: profile.name,
|
|
1290
|
+
...(profile.avatarRef === undefined ? {} : { avatarRef: profile.avatarRef }),
|
|
1291
|
+
version: HUMAN_PROFILE_VERSION,
|
|
1292
|
+
repoUrl: HUMAN_PROFILE_REPO_URL,
|
|
1293
|
+
updateAvailable: update.updateAvailable,
|
|
1294
|
+
...(update.latestVersion === undefined ? {} : { latestVersion: update.latestVersion }),
|
|
1295
|
+
});
|
|
1296
|
+
}
|
|
1297
|
+
/**
|
|
1298
|
+
* Upload one human avatar image into the persistent store. Human-only by
|
|
1299
|
+
* construction: only the Web Client calls this Remote, never agent tools.
|
|
1300
|
+
* The caller stores the returned ref in settings; bytes never enter the
|
|
1301
|
+
* TTL-bound attachment cache.
|
|
1302
|
+
*/
|
|
1303
|
+
async putHumanAvatar(request) {
|
|
1304
|
+
this.requireAccepting();
|
|
1305
|
+
const bytes = Buffer.from(request.bytesBase64, 'base64');
|
|
1306
|
+
return Object.freeze(await writeHumanAvatar(humanAvatarsRoot(), request.name, request.mediaType ?? 'application/octet-stream', bytes));
|
|
1307
|
+
}
|
|
1308
|
+
/** Read one human avatar back; removed entries throw and the UI falls back to hue/initial. */
|
|
1309
|
+
async getHumanAvatar(request) {
|
|
1310
|
+
const stored = await readHumanAvatar(humanAvatarsRoot(), request.avatarRef);
|
|
1311
|
+
if (stored === undefined)
|
|
1312
|
+
throw new Error(`human avatar '${request.avatarRef}' is no longer stored`);
|
|
1313
|
+
return Object.freeze({ name: stored.name, mediaType: stored.mediaType, byteSize: stored.byteSize, bytesBase64: stored.bytes.toString('base64') });
|
|
1314
|
+
}
|
|
1315
|
+
/** Remove one human avatar entry; the UI falls back to hue/initial afterwards. */
|
|
1316
|
+
async removeHumanAvatar(request) {
|
|
1317
|
+
this.requireAccepting();
|
|
1318
|
+
await removeHumanAvatar(humanAvatarsRoot(), request.avatarRef);
|
|
1319
|
+
return Object.freeze({ removed: true });
|
|
1320
|
+
}
|
|
1122
1321
|
/** Human existing-Thread reply; unread and revision conflicts are business outcomes. */
|
|
1123
1322
|
async reply(request) {
|
|
1124
1323
|
return this.replyAs(this.humanCall(request.workspaceId), request);
|
|
@@ -1284,7 +1483,7 @@ let AgentTeam = (() => {
|
|
|
1284
1483
|
try {
|
|
1285
1484
|
const message = createUserMessage({
|
|
1286
1485
|
content: [{ type: 'text', text: this.dmRelayText(agent, recipient, request.body.trim(), result.value.receipt.occurredAt, result.value.receipt.operationId, request.workspaceId) }],
|
|
1287
|
-
source: { kind: AGENT_TEAM_PLUGIN_ID, form: 'relay' },
|
|
1486
|
+
source: { kind: 'plugin', plugin: AGENT_TEAM_PLUGIN_ID, form: 'relay' },
|
|
1288
1487
|
});
|
|
1289
1488
|
// An idle recipient gets one ordinary turn; a busy one is steered into
|
|
1290
1489
|
// its current turn — the same wake split subagent continuations use.
|
|
@@ -1429,7 +1628,7 @@ let AgentTeam = (() => {
|
|
|
1429
1628
|
let sessionId = agent.session.id;
|
|
1430
1629
|
let live = true;
|
|
1431
1630
|
let guard = 0;
|
|
1432
|
-
while (sessionId !== undefined && guard++
|
|
1631
|
+
while (sessionId !== undefined && guard++ <= MAX_TIMELINE_ANCESTORS) {
|
|
1433
1632
|
let events;
|
|
1434
1633
|
let inheritedEventCount;
|
|
1435
1634
|
let parentSession;
|
|
@@ -1454,19 +1653,19 @@ let AgentTeam = (() => {
|
|
|
1454
1653
|
// Fold the source with its inherited cut respected: inherited events
|
|
1455
1654
|
// are resolved history in that source, never fresh intent; checkpoints
|
|
1456
1655
|
// recorded in this source's own span are the selectable targets.
|
|
1457
|
-
const state =
|
|
1656
|
+
const state = foldTeamContextProjection(events, { sessionId, inheritedEventCount }, this.contextProjectionHost);
|
|
1458
1657
|
// A Team-boundary default checkpoint: the boundary's completed-turn
|
|
1459
1658
|
// anchor is the seed cut, and it is selectable exactly when one
|
|
1460
1659
|
// Thread's facts entered the context through it — the same proof the
|
|
1461
1660
|
// timeline requires, revalidated here because the model may cite a
|
|
1462
1661
|
// boundary the timeline never surfaced.
|
|
1463
1662
|
const boundary = checkpointRef.startsWith('team-boundary-')
|
|
1464
|
-
? state
|
|
1663
|
+
? boundaryByRef(state, checkpointRef)
|
|
1465
1664
|
: undefined;
|
|
1466
1665
|
const entry = checkpointByRef(state, checkpointRef);
|
|
1467
1666
|
const anchorTurnEndSeq = boundary !== undefined ? boundary.turnEndSeq : entry?.turnEndSeq;
|
|
1468
1667
|
if (anchorTurnEndSeq !== undefined && anchorTurnEndSeq !== -1) {
|
|
1469
|
-
if (boundary !== undefined && boundary.
|
|
1668
|
+
if (boundary !== undefined && boundary.kind !== 'team-boundary') {
|
|
1470
1669
|
throw new Error(`checkpoint '${checkpointRef}' is not a restorable boundary`);
|
|
1471
1670
|
}
|
|
1472
1671
|
// The seed is the exact contiguous prefix through the anchor's
|
|
@@ -1474,7 +1673,12 @@ let AgentTeam = (() => {
|
|
|
1474
1673
|
const throughSeq = anchorTurnEndSeq + 1;
|
|
1475
1674
|
const prefix = events.slice(0, throughSeq);
|
|
1476
1675
|
if (boundary !== undefined) {
|
|
1477
|
-
|
|
1676
|
+
// The seed must stay inside one Thread's context: the retained
|
|
1677
|
+
// prefix through the anchor holds exactly one Thread's facts, read
|
|
1678
|
+
// from the fold's own boundary attributions — the same accumulated
|
|
1679
|
+
// set the timeline shows, so a ref the timeline offered is never
|
|
1680
|
+
// refused here for a reason it did not state.
|
|
1681
|
+
const threads = retainedTopicsThrough(state, anchorTurnEndSeq);
|
|
1478
1682
|
if (threads.length !== 1) {
|
|
1479
1683
|
throw new Error(threads.length === 0
|
|
1480
1684
|
? `boundary '${checkpointRef}' has no single attributable Thread; write a fresh handoff instead`
|
|
@@ -1535,11 +1739,11 @@ let AgentTeam = (() => {
|
|
|
1535
1739
|
return;
|
|
1536
1740
|
}
|
|
1537
1741
|
const inspection = previousRead.inspection;
|
|
1538
|
-
const state =
|
|
1742
|
+
const state = foldTeamContextProjection(inspection.events, { sessionId: previousSessionId, inheritedEventCount: inspection.inheritedEventCount }, this.contextProjectionHost);
|
|
1539
1743
|
if (state.pending === null)
|
|
1540
1744
|
return;
|
|
1541
1745
|
const pending = state.pending;
|
|
1542
|
-
const message = createHandoffMessage({
|
|
1746
|
+
const message = TEAM_CONTEXT_CODEC.createHandoffMessage({
|
|
1543
1747
|
handoff: pending.handoff,
|
|
1544
1748
|
previousSessionId,
|
|
1545
1749
|
newSessionId: agent.session.id,
|
|
@@ -1573,9 +1777,9 @@ let AgentTeam = (() => {
|
|
|
1573
1777
|
if (!Number.isSafeInteger(through) || through < 0 || through > inspection.events.length) {
|
|
1574
1778
|
throw new Error(`the recorded checkpoint-return seed cut ${through} is not a valid prefix of Session '${seed.sourceSessionId}'`);
|
|
1575
1779
|
}
|
|
1576
|
-
const state =
|
|
1780
|
+
const state = foldTeamContextProjection(inspection.events, { sessionId: seed.sourceSessionId, inheritedEventCount: inspection.inheritedEventCount }, this.contextProjectionHost);
|
|
1577
1781
|
const anchorProven = checkpointByRef(state, seed.checkpointRef) !== undefined
|
|
1578
|
-
|| state
|
|
1782
|
+
|| boundaryByRef(state, seed.checkpointRef) !== undefined;
|
|
1579
1783
|
if (!anchorProven) {
|
|
1580
1784
|
throw new Error(`the recorded checkpoint-return anchor '${seed.checkpointRef}' no longer resolves in Session '${seed.sourceSessionId}'`);
|
|
1581
1785
|
}
|
|
@@ -1641,8 +1845,8 @@ let AgentTeam = (() => {
|
|
|
1641
1845
|
}
|
|
1642
1846
|
inspectionEvents = previousRead.inspection.events;
|
|
1643
1847
|
inspectionInherited = previousRead.inspection.inheritedEventCount;
|
|
1644
|
-
const state =
|
|
1645
|
-
const carried = carriedInputOf(state);
|
|
1848
|
+
const state = foldTeamContextProjection(inspectionEvents, { sessionId: transition.previousSessionId, inheritedEventCount: inspectionInherited }, this.contextProjectionHost);
|
|
1849
|
+
const carried = carriedInputOf(state, inspectionEvents);
|
|
1646
1850
|
if (carried.length === 0)
|
|
1647
1851
|
return 0;
|
|
1648
1852
|
const known = new Set();
|
|
@@ -1693,9 +1897,11 @@ let AgentTeam = (() => {
|
|
|
1693
1897
|
* Agent-only bounded structural timeline: resolved checkpoints plus Team
|
|
1694
1898
|
* delivery, handoff, and compaction boundaries across the current
|
|
1695
1899
|
* generation and its archived ancestor lineage. Structural only — no
|
|
1696
|
-
* transcript content. The
|
|
1697
|
-
*
|
|
1698
|
-
*
|
|
1900
|
+
* transcript content. The walk, the per-source folds, the pricing, and the
|
|
1901
|
+
* anchor rules are the engine's `readContextTimeline`; Team contributes the
|
|
1902
|
+
* fold configuration, the measurement, and the one judgement the engine
|
|
1903
|
+
* leaves to its host — which Threads a boundary's retained prefix holds — so
|
|
1904
|
+
* a ref this list offers is a ref `context_rollover` accepts.
|
|
1699
1905
|
*/
|
|
1700
1906
|
async contextTimelineForAgent(agent, request) {
|
|
1701
1907
|
const member = this.memberForAgent(agent);
|
|
@@ -1710,70 +1916,111 @@ let AgentTeam = (() => {
|
|
|
1710
1916
|
const limits = await this.routeLimitsForAgent(agent);
|
|
1711
1917
|
const hardLimit = limits?.hardLimit ?? CONTEXT_HARD_LIMIT_CAP;
|
|
1712
1918
|
const handoffAt = limits?.handoffAt ?? CONTEXT_HANDOFF_AT_CAP;
|
|
1713
|
-
//
|
|
1714
|
-
//
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1919
|
+
// One measurement per lineage source, memoized by Session: the engine
|
|
1920
|
+
// measures a source once before pricing its anchors, and the boundary
|
|
1921
|
+
// overlay below reads the same map to tell an unprovable budget (never
|
|
1922
|
+
// priced as free) from a real budget rejection.
|
|
1923
|
+
const measurements = new Map();
|
|
1924
|
+
const timeline = await readContextTimeline({
|
|
1925
|
+
current: {
|
|
1926
|
+
sessionId: agent.session.id,
|
|
1927
|
+
header: agent.session.header,
|
|
1928
|
+
inheritedEventCount: agent.session.inheritedEventCount,
|
|
1929
|
+
events: agent.session.snapshotEvents(),
|
|
1930
|
+
},
|
|
1931
|
+
config: this.contextFoldConfig(),
|
|
1932
|
+
readAncestor: sessionId => this.sessionReader.read(sessionId),
|
|
1933
|
+
measureSource: source => {
|
|
1934
|
+
const key = String(source.sessionId);
|
|
1935
|
+
if (!measurements.has(key))
|
|
1936
|
+
measurements.set(key, this.measureContextSourceForAgent(agent, source));
|
|
1937
|
+
return measurements.get(key);
|
|
1938
|
+
},
|
|
1939
|
+
currentUsageTokens: usageTokens,
|
|
1940
|
+
handoffAt,
|
|
1941
|
+
limit,
|
|
1942
|
+
// Archived ancestors the engine walks; the seed guard resolves a cited
|
|
1943
|
+
// ref through the same depth, so the timeline never offers a ref the
|
|
1944
|
+
// guard cannot reach.
|
|
1945
|
+
maxAncestors: MAX_TIMELINE_ANCESTORS,
|
|
1946
|
+
});
|
|
1947
|
+
return {
|
|
1948
|
+
usageTokens,
|
|
1949
|
+
hardLimit,
|
|
1950
|
+
handoffAt,
|
|
1951
|
+
items: timeline.items.map(item => this.teamTimelineItemFor(item, agent.session.id, handoffAt, measurements)),
|
|
1952
|
+
...(timeline.incompleteFrom === undefined ? {} : { incompleteFrom: timeline.incompleteFrom }),
|
|
1953
|
+
};
|
|
1954
|
+
}
|
|
1955
|
+
/**
|
|
1956
|
+
* Map one engine timeline item onto the model-facing Team item. The engine
|
|
1957
|
+
* decided the walk, the fold, the pricing, and the head, checkpoint, and
|
|
1958
|
+
* measurable-source verdicts; Team restates exactly one policy of its own: a
|
|
1959
|
+
* Team boundary is selectable only when the RETAINED PREFIX through it stays
|
|
1960
|
+
* inside one Thread — the same proof the seed guard revalidates before it
|
|
1961
|
+
* swaps a generation. The engine judges a boundary by its OWN attribution
|
|
1962
|
+
* instead, so a boundary that arrived after a second Thread's facts would be
|
|
1963
|
+
* offered here and refused by `context_rollover`; Team's stricter rule is
|
|
1964
|
+
* what keeps the two surfaces answering one question.
|
|
1965
|
+
*/
|
|
1966
|
+
teamTimelineItemFor(item, currentSessionId, handoffAt, measurements) {
|
|
1967
|
+
const source = item.source === 'boundary'
|
|
1968
|
+
? item.kind === 'handoff' || item.kind === 'compaction' ? item.kind : 'team-boundary'
|
|
1969
|
+
: item.source === 'checkpoint' ? 'agent' : 'head';
|
|
1970
|
+
const affectedThreads = item.affectedTopics;
|
|
1971
|
+
let restorable = item.restorable;
|
|
1972
|
+
let reason = item.reason;
|
|
1973
|
+
// A boundary's verdict is Team's to make, but only once its source is
|
|
1974
|
+
// measurable: "the budget cannot be proven" is the engine's first
|
|
1975
|
+
// rejection and stays first — an unmeasurable boundary is not selectable
|
|
1976
|
+
// even when its prefix holds exactly one Thread.
|
|
1977
|
+
if (item.source === 'boundary' && measurements.get(String(item.sourceSessionId ?? currentSessionId)) !== undefined) {
|
|
1978
|
+
if (source === 'handoff' || source === 'compaction') {
|
|
1979
|
+
// A handoff opens a generation and a compaction rewrites the visible
|
|
1980
|
+
// surface: rewinding into either is not a proven-safe target. The
|
|
1981
|
+
// engine has no vocabulary for that and would answer "no single topic
|
|
1982
|
+
// is attributable", which misdescribes why.
|
|
1983
|
+
restorable = false;
|
|
1984
|
+
reason = `source '${source}' is not a restorable checkpoint`;
|
|
1733
1985
|
}
|
|
1734
|
-
else {
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
incompleteFrom = { sessionId, reason: `${read.failure.kind}: ${read.failure.detail}` };
|
|
1740
|
-
sessionId = undefined;
|
|
1741
|
-
}
|
|
1742
|
-
else {
|
|
1743
|
-
state = foldContextProjection(read.inspection.events, read.inspection.inheritedEventCount, sessionId);
|
|
1744
|
-
sourceEvents = read.inspection.events;
|
|
1745
|
-
sourceSessionId = sessionId;
|
|
1746
|
-
sessionId = read.inspection.header.parentSession;
|
|
1747
|
-
}
|
|
1986
|
+
else if (affectedThreads.length !== 1) {
|
|
1987
|
+
restorable = false;
|
|
1988
|
+
reason = affectedThreads.length === 0
|
|
1989
|
+
? 'no single Thread is attributable to this boundary'
|
|
1990
|
+
: 'multiple Threads entered the context through this boundary; write a fresh handoff instead';
|
|
1748
1991
|
}
|
|
1749
|
-
if (
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
const sourceUsage = await this.sourceUsageTokens(sourceSessionId, sourceIsCurrent, agent);
|
|
1757
|
-
for (const candidate of timelineCandidates(state, limit)) {
|
|
1758
|
-
if (seen.has(candidate.ref))
|
|
1759
|
-
continue;
|
|
1760
|
-
seen.add(candidate.ref);
|
|
1761
|
-
items.push(this.timelineItemFor(candidate, sourceUsage, usageTokens, hardLimit, handoffAt, sourceSessionId, sourceIsCurrent, sourceEvents));
|
|
1762
|
-
if (items.length >= limit)
|
|
1763
|
-
break;
|
|
1992
|
+
else if (item.retainedTokens >= handoffAt) {
|
|
1993
|
+
restorable = false;
|
|
1994
|
+
reason = 'retained context would not materially shrink the working set';
|
|
1995
|
+
}
|
|
1996
|
+
else {
|
|
1997
|
+
restorable = true;
|
|
1998
|
+
reason = undefined;
|
|
1764
1999
|
}
|
|
1765
|
-
if (items.length >= limit)
|
|
1766
|
-
break;
|
|
1767
2000
|
}
|
|
1768
|
-
return {
|
|
2001
|
+
return {
|
|
2002
|
+
checkpointRef: item.ref,
|
|
2003
|
+
name: item.label,
|
|
2004
|
+
source,
|
|
2005
|
+
retainedTokens: item.retainedTokens,
|
|
2006
|
+
discardedTokens: item.discardedTokens,
|
|
2007
|
+
affectedThreads,
|
|
2008
|
+
restorable,
|
|
2009
|
+
...(reason === undefined ? {} : { reason }),
|
|
2010
|
+
// Team's field semantics, unchanged: every item that is not a
|
|
2011
|
+
// checkpoint names the Session it anchors in — an ancestor generation's
|
|
2012
|
+
// boundary is how a lineage reads — and a checkpoint is keyed to its own
|
|
2013
|
+
// Session by its ref already.
|
|
2014
|
+
...(source === 'agent' ? {} : { sourceSessionId: item.sourceSessionId ?? currentSessionId }),
|
|
2015
|
+
};
|
|
1769
2016
|
}
|
|
1770
2017
|
/**
|
|
1771
2018
|
* Replayed measurement of one lineage source: the live current Session
|
|
1772
|
-
* measures directly; an archived ancestor measures through a
|
|
1773
|
-
*
|
|
1774
|
-
* own tokens — never the current generation's.
|
|
1775
|
-
* meter is available or the source cannot be
|
|
1776
|
-
* closed on the unknown.
|
|
2019
|
+
* measures directly; an archived ancestor measures through a detached
|
|
2020
|
+
* Session rebuilt from the source's own log, so a seed's retained cost is
|
|
2021
|
+
* priced in the SOURCE's own tokens — never the current generation's.
|
|
2022
|
+
* Returns undefined when no meter is available or the source cannot be
|
|
2023
|
+
* replayed; callers fail closed on the unknown.
|
|
1777
2024
|
*/
|
|
1778
2025
|
async sourceUsageTokens(sessionId, live, agent) {
|
|
1779
2026
|
const meter = agent.ctx.get('tokenMeter');
|
|
@@ -1788,14 +2035,50 @@ let AgentTeam = (() => {
|
|
|
1788
2035
|
const read = await this.sessionReader.read(sessionId);
|
|
1789
2036
|
if (!read.ok)
|
|
1790
2037
|
return undefined;
|
|
2038
|
+
return this.measureDetachedSource(agent, {
|
|
2039
|
+
sessionId,
|
|
2040
|
+
header: read.inspection.header,
|
|
2041
|
+
inheritedEventCount: read.inspection.inheritedEventCount,
|
|
2042
|
+
events: read.inspection.events,
|
|
2043
|
+
});
|
|
2044
|
+
}
|
|
2045
|
+
/**
|
|
2046
|
+
* One already-read source's replayed measurement: an archived generation is
|
|
2047
|
+
* rebuilt as a detached Session and measured in its own tokens. Undefined
|
|
2048
|
+
* means unmeasurable, never free.
|
|
2049
|
+
*/
|
|
2050
|
+
measureDetachedSource(agent, source) {
|
|
2051
|
+
const meter = agent.ctx.get('tokenMeter');
|
|
2052
|
+
if (meter === undefined)
|
|
2053
|
+
return undefined;
|
|
1791
2054
|
try {
|
|
1792
|
-
const session = Session.create(sessionId,
|
|
2055
|
+
const session = Session.create(source.sessionId, source.events, source.header, source.inheritedEventCount);
|
|
1793
2056
|
return meter.measure(session)?.totalTokens;
|
|
1794
2057
|
}
|
|
1795
2058
|
catch {
|
|
1796
2059
|
return undefined;
|
|
1797
2060
|
}
|
|
1798
2061
|
}
|
|
2062
|
+
/**
|
|
2063
|
+
* One source's replayed measurement, in that source's own tokens, for a
|
|
2064
|
+
* caller that already holds the source: the live generation measures
|
|
2065
|
+
* directly, an archived one is rebuilt from the log it came with. Undefined
|
|
2066
|
+
* means unmeasurable — a caller must never price an unknown source as free.
|
|
2067
|
+
*/
|
|
2068
|
+
measureContextSourceForAgent(agent, source) {
|
|
2069
|
+
if (String(source.sessionId) === String(agent.session.id)) {
|
|
2070
|
+
return agent.ctx.get('tokenMeter')?.measure(agent.session)?.totalTokens;
|
|
2071
|
+
}
|
|
2072
|
+
return this.measureDetachedSource(agent, source);
|
|
2073
|
+
}
|
|
2074
|
+
/**
|
|
2075
|
+
* The one fold configuration the registered projection unit and every cold
|
|
2076
|
+
* fold use, so a generation read back for the timeline reads exactly as the
|
|
2077
|
+
* projection folded it — same codec, same boundary attribution.
|
|
2078
|
+
*/
|
|
2079
|
+
contextFoldConfig() {
|
|
2080
|
+
return createTeamContextProjectionConfig(this.contextProjectionHost);
|
|
2081
|
+
}
|
|
1799
2082
|
/**
|
|
1800
2083
|
* Monotonic anchor-share estimate of a seed's retained cost, priced in the
|
|
1801
2084
|
* SOURCE Session's own measurement: the fraction of the source log the
|
|
@@ -1811,155 +2094,6 @@ let AgentTeam = (() => {
|
|
|
1811
2094
|
const share = Math.min(1, Math.max(0, (anchorTurnEndSeq + 1) / sourceLength));
|
|
1812
2095
|
return Math.round(sourceUsageTokens * share);
|
|
1813
2096
|
}
|
|
1814
|
-
/**
|
|
1815
|
-
* Threads whose facts entered this Session's model context by the given
|
|
1816
|
-
* seq: delivered Team notices (their bodies quote `Thread: <ref>`
|
|
1817
|
-
* structurally) and successful Team-claim mutations (their task overlays
|
|
1818
|
-
* resolve to Threads through the ledger). Never from unread ledger
|
|
1819
|
-
* activity — a Thread the Member never saw did not enter its context.
|
|
1820
|
-
* Order-stable, deduplicated.
|
|
1821
|
-
*/
|
|
1822
|
-
threadsEnteringContext(events, throughSeq) {
|
|
1823
|
-
const refs = [];
|
|
1824
|
-
const openAttributions = new Map();
|
|
1825
|
-
const push = (ref) => {
|
|
1826
|
-
if (ref !== undefined && !refs.includes(ref))
|
|
1827
|
-
refs.push(ref);
|
|
1828
|
-
};
|
|
1829
|
-
for (const event of events) {
|
|
1830
|
-
if (event.seq > throughSeq)
|
|
1831
|
-
break;
|
|
1832
|
-
if (event.type === 'tool/call' && (event.data.name === 'team_claim' || event.data.name === 'team_message')) {
|
|
1833
|
-
openAttributions.set(event.data.callId, { name: event.data.name, arguments: event.data.arguments });
|
|
1834
|
-
}
|
|
1835
|
-
else if (event.type === 'tool/result') {
|
|
1836
|
-
const result = event.data.message;
|
|
1837
|
-
if (result.isError !== true) {
|
|
1838
|
-
const recorded = openAttributions.get(result.toolCallId);
|
|
1839
|
-
if (recorded !== undefined) {
|
|
1840
|
-
openAttributions.delete(result.toolCallId);
|
|
1841
|
-
try {
|
|
1842
|
-
const args = JSON.parse(recorded.arguments);
|
|
1843
|
-
// A claim mutation resolves its Task overlay through the ledger;
|
|
1844
|
-
// a team_message committed reply names its Thread in its call
|
|
1845
|
-
// arguments; a committed start's Thread is born in the result
|
|
1846
|
-
// and is read from the durable presentation meta.
|
|
1847
|
-
if (recorded.name === 'team_claim' && typeof args.taskRef === 'string' && args.taskRef !== '') {
|
|
1848
|
-
push(this.requireLedger().threadForTask(args.taskRef));
|
|
1849
|
-
}
|
|
1850
|
-
else if (recorded.name === 'team_message') {
|
|
1851
|
-
const meta = event.data.meta;
|
|
1852
|
-
if (meta !== undefined && typeof meta === 'object' && meta.kind === 'committed') {
|
|
1853
|
-
const metaThreadRef = meta.threadRef;
|
|
1854
|
-
if (typeof metaThreadRef === 'string' && metaThreadRef !== '')
|
|
1855
|
-
push(metaThreadRef);
|
|
1856
|
-
}
|
|
1857
|
-
else if (typeof args.threadRef === 'string' && args.threadRef !== '') {
|
|
1858
|
-
push(args.threadRef);
|
|
1859
|
-
}
|
|
1860
|
-
}
|
|
1861
|
-
}
|
|
1862
|
-
catch {
|
|
1863
|
-
// Malformed call arguments contribute no attribution.
|
|
1864
|
-
}
|
|
1865
|
-
}
|
|
1866
|
-
}
|
|
1867
|
-
}
|
|
1868
|
-
else if (event.type === 'user/message') {
|
|
1869
|
-
const source = event.data.source;
|
|
1870
|
-
// Only this plugin's own delivered notices attribute Threads, by exact
|
|
1871
|
-
// kind identity — the shape written now and the read-time conversion's
|
|
1872
|
-
// rename of the released V3 history. A `plugin:` prefix test would
|
|
1873
|
-
// claim third-party producers' rows as Team facts.
|
|
1874
|
-
if (source === undefined || !isAgentTeamSourceKind(source.kind))
|
|
1875
|
-
continue;
|
|
1876
|
-
// Reminder notices never enter attribution — a recovery instruction
|
|
1877
|
-
// (or a historical progress-nudge notice, kept decodable in session
|
|
1878
|
-
// logs recorded before that system was removed) is not a Team fact.
|
|
1879
|
-
if (source.form === 'notice' && source.summary !== undefined && isReminderNoticeSummary(source.summary))
|
|
1880
|
-
continue;
|
|
1881
|
-
const text = event.data.content.filter(block => block.type === 'text').map(block => block.text ?? '').join('\n');
|
|
1882
|
-
for (const match of text.matchAll(/Thread: (thread:[0-9a-f-]{6,})/g)) {
|
|
1883
|
-
push(match[1]);
|
|
1884
|
-
}
|
|
1885
|
-
}
|
|
1886
|
-
}
|
|
1887
|
-
return refs;
|
|
1888
|
-
}
|
|
1889
|
-
/**
|
|
1890
|
-
* Price and annotate one timeline candidate without mutating anything.
|
|
1891
|
-
* Retained prices in the SOURCE Session's own replayed measurement (the
|
|
1892
|
-
* monotonic anchor-share of the source log); discarded is what a return
|
|
1893
|
-
* replaces — for a current-generation anchor, the measured usage beyond
|
|
1894
|
-
* the anchor; for an ancestor anchor, the current generation's whole
|
|
1895
|
-
* usage (an approximation: the ancestor's own suffix is not part of this
|
|
1896
|
-
* generation). A small current child therefore discards little but may
|
|
1897
|
-
* still retain a large ancestor seed, and both numbers say so honestly.
|
|
1898
|
-
*/
|
|
1899
|
-
timelineItemFor(candidate, sourceUsage, currentUsage, _hardLimit, handoffAt, sourceSessionId, sourceIsCurrent, sourceEvents) {
|
|
1900
|
-
const retainedTokens = candidate.source === 'head'
|
|
1901
|
-
? currentUsage
|
|
1902
|
-
: this.retainedEstimate(sourceUsage ?? 0, sourceEvents.length, candidate.turnEndSeq);
|
|
1903
|
-
const discardedTokens = candidate.source === 'head'
|
|
1904
|
-
? 0
|
|
1905
|
-
: sourceIsCurrent
|
|
1906
|
-
? Math.max(0, currentUsage - retainedTokens)
|
|
1907
|
-
: currentUsage;
|
|
1908
|
-
const affectedThreads = candidate.turnEndSeq === -1 ? [] : this.threadsEnteringContext(sourceEvents, candidate.turnEndSeq);
|
|
1909
|
-
let restorable = candidate.turnEndSeq !== -1;
|
|
1910
|
-
let reason;
|
|
1911
|
-
if (candidate.source === 'head') {
|
|
1912
|
-
// The head is the current working set: returning to it discards
|
|
1913
|
-
// nothing and is never a meaningful return target.
|
|
1914
|
-
restorable = false;
|
|
1915
|
-
reason = 'the head is the current working set; returning to it discards nothing';
|
|
1916
|
-
}
|
|
1917
|
-
else if (sourceUsage === undefined) {
|
|
1918
|
-
// The source's cost cannot be measured (no meter, or the archived
|
|
1919
|
-
// ancestor cannot be borrowed): the budget cannot be proven, so the
|
|
1920
|
-
// candidate is not selectable. Never price an unknown as zero.
|
|
1921
|
-
restorable = false;
|
|
1922
|
-
reason = 'the source Session\'s context cost cannot be measured, so the return budget cannot be proven';
|
|
1923
|
-
}
|
|
1924
|
-
else if (candidate.source === 'handoff' || candidate.source === 'compaction') {
|
|
1925
|
-
// A handoff starts a generation and a compaction rewrites the visible
|
|
1926
|
-
// surface: rewinding into them is not a proven-safe V1 target.
|
|
1927
|
-
restorable = false;
|
|
1928
|
-
reason = `source '${candidate.source}' is not a restorable checkpoint`;
|
|
1929
|
-
}
|
|
1930
|
-
else if (candidate.source === 'team-boundary') {
|
|
1931
|
-
// A Team delivery is a selectable default checkpoint exactly when the
|
|
1932
|
-
// Host can prove it stays inside one Thread's context: the boundary
|
|
1933
|
-
// resolved at a completed turn AND exactly one Thread's facts entered
|
|
1934
|
-
// the Session context through it. Multi-Thread or unattributable
|
|
1935
|
-
// boundaries document why they are not selectable.
|
|
1936
|
-
if (affectedThreads.length !== 1) {
|
|
1937
|
-
restorable = false;
|
|
1938
|
-
reason = affectedThreads.length === 0
|
|
1939
|
-
? 'no single Thread is attributable to this boundary'
|
|
1940
|
-
: 'multiple Threads entered the context through this boundary; write a fresh handoff instead';
|
|
1941
|
-
}
|
|
1942
|
-
else if (retainedTokens >= handoffAt) {
|
|
1943
|
-
restorable = false;
|
|
1944
|
-
reason = 'retained context would not materially shrink the working set';
|
|
1945
|
-
}
|
|
1946
|
-
}
|
|
1947
|
-
else if (candidate.source === 'agent' && retainedTokens >= handoffAt) {
|
|
1948
|
-
restorable = false;
|
|
1949
|
-
reason = 'retained context would be at or above the handoff budget';
|
|
1950
|
-
}
|
|
1951
|
-
return {
|
|
1952
|
-
checkpointRef: candidate.ref,
|
|
1953
|
-
name: candidate.label,
|
|
1954
|
-
source: candidate.source,
|
|
1955
|
-
retainedTokens,
|
|
1956
|
-
discardedTokens,
|
|
1957
|
-
affectedThreads,
|
|
1958
|
-
restorable,
|
|
1959
|
-
...(reason === undefined ? {} : { reason }),
|
|
1960
|
-
...(candidate.source === 'agent' ? {} : { sourceSessionId }),
|
|
1961
|
-
};
|
|
1962
|
-
}
|
|
1963
2097
|
/**
|
|
1964
2098
|
* Agent-only rollover request validation: the tool calls this to check its
|
|
1965
2099
|
* Member binding, exclusivity, and checkpoint ownership. It performs no
|
|
@@ -2284,7 +2418,8 @@ let AgentTeam = (() => {
|
|
|
2284
2418
|
// old Session; the projection still carries the intent, so finish the
|
|
2285
2419
|
// transition (or keep waiting for the containing turn) from here.
|
|
2286
2420
|
if (persisted) {
|
|
2287
|
-
const
|
|
2421
|
+
const inheritedEventCount = created.agent.session.inheritedEventCount;
|
|
2422
|
+
const state = foldTeamContextProjection(created.agent.session.ownEvents(), { sessionId: created.agent.session.id, inheritedEventCount }, this.contextProjectionHost);
|
|
2288
2423
|
if (state.pending !== null)
|
|
2289
2424
|
this.contextManagement.recoverPendingTransition(member.memberId, created.agent, member.sessionId);
|
|
2290
2425
|
// A restart between one checkpoint's durable result and its quiet
|
|
@@ -2295,11 +2430,12 @@ let AgentTeam = (() => {
|
|
|
2295
2430
|
// delivered activates the new Session with no handoff in its own
|
|
2296
2431
|
// log — never treat that as an ordinary blank Member Session. The
|
|
2297
2432
|
// operation's recorded previous Session (the lineage parent) still
|
|
2298
|
-
// holds the intent; rebuild the handoff from it.
|
|
2299
|
-
//
|
|
2300
|
-
//
|
|
2301
|
-
//
|
|
2302
|
-
|
|
2433
|
+
// holds the intent; rebuild the handoff from it. "No handoff in its
|
|
2434
|
+
// own log" is judged over both shapes: a generation rescued from the
|
|
2435
|
+
// retired custom kinds carries its handoff as a source the projection
|
|
2436
|
+
// does not classify, and rebuilding on top of it would inject the same
|
|
2437
|
+
// handoff twice.
|
|
2438
|
+
if (!handoffAlreadyInLog(state.boundaries, created.agent.session.ownEvents())) {
|
|
2303
2439
|
await this.reconstructMissingHandoff(member, created.agent);
|
|
2304
2440
|
}
|
|
2305
2441
|
// Carried input redelivery binds to the committed transition target —
|
|
@@ -2473,6 +2609,14 @@ let AgentTeam = (() => {
|
|
|
2473
2609
|
}
|
|
2474
2610
|
return { class: 'activation', detail: error instanceof Error ? error.message : String(error) };
|
|
2475
2611
|
}
|
|
2612
|
+
/** Mark a session-refused activation diagnostic as proven non-remediable. */
|
|
2613
|
+
markRefusalNonRemediable(memberId) {
|
|
2614
|
+
const failures = this.memberFailures.get(memberId);
|
|
2615
|
+
const activation = failures?.activation;
|
|
2616
|
+
if (activation === undefined || activation.class !== 'session-refused')
|
|
2617
|
+
return;
|
|
2618
|
+
failures.activation = Object.freeze({ ...activation, remediable: false });
|
|
2619
|
+
}
|
|
2476
2620
|
clearMemberFailure(memberId, slot) {
|
|
2477
2621
|
const failures = this.memberFailures.get(memberId);
|
|
2478
2622
|
if (failures === undefined || failures[slot] === undefined)
|
|
@@ -2513,7 +2657,7 @@ let AgentTeam = (() => {
|
|
|
2513
2657
|
const path = this.ctx.workspaceRegistry.get(operation.data.workspaceId)?.path;
|
|
2514
2658
|
const text = `Team participation changed: you have ${operation.kind === 'team/member-workspace-joined' ? 'joined' : 'left'} Workspace ${operation.data.workspaceId}${path === undefined ? ' (path unavailable)' : ` (${JSON.stringify(path)})`}.\nCurrent Workspace ids: ${ledger.workspacesOf(operation.data.memberId).join(', ')}. This replaces earlier participation information. Your Session and cwd have not moved.`;
|
|
2515
2659
|
const notice = createUserMessage({ content: [{ type: 'text', text }],
|
|
2516
|
-
source: { kind: AGENT_TEAM_PLUGIN_ID, form: 'notice', summary: 'Team Workspace participation changed' } });
|
|
2660
|
+
source: { kind: 'plugin', plugin: AGENT_TEAM_PLUGIN_ID, form: 'notice', summary: 'Team Workspace participation changed' } });
|
|
2517
2661
|
try {
|
|
2518
2662
|
if (agent.status === 'idle' || agent.inbox.nextTurn.some(message => message.source.kind === 'user'))
|
|
2519
2663
|
agent.followup(notice);
|
|
@@ -2610,7 +2754,7 @@ let AgentTeam = (() => {
|
|
|
2610
2754
|
agent.inbox.remove(existingInboxHint.id);
|
|
2611
2755
|
const hint = createUserMessage({
|
|
2612
2756
|
content: [{ type: 'text', text: this.notificationText(notifications, member.memberId) }],
|
|
2613
|
-
source: { kind: AGENT_TEAM_PLUGIN_ID, form: 'notice', summary: INBOX_NOTICE_SUMMARY },
|
|
2757
|
+
source: { kind: 'plugin', plugin: AGENT_TEAM_PLUGIN_ID, form: 'notice', summary: INBOX_NOTICE_SUMMARY },
|
|
2614
2758
|
});
|
|
2615
2759
|
this.notifiedInbox.set(member.memberId, signature);
|
|
2616
2760
|
try {
|
|
@@ -2631,12 +2775,12 @@ let AgentTeam = (() => {
|
|
|
2631
2775
|
}
|
|
2632
2776
|
isInboxNotice(message) {
|
|
2633
2777
|
const source = message.source;
|
|
2634
|
-
return
|
|
2778
|
+
return source.kind === 'plugin' && source.plugin === AGENT_TEAM_PLUGIN_ID
|
|
2635
2779
|
&& source.form === 'notice' && source.summary === INBOX_NOTICE_SUMMARY;
|
|
2636
2780
|
}
|
|
2637
2781
|
isRecoveryNotice(message) {
|
|
2638
2782
|
const source = message.source;
|
|
2639
|
-
return
|
|
2783
|
+
return source.kind === 'plugin' && source.plugin === AGENT_TEAM_PLUGIN_ID
|
|
2640
2784
|
&& source.form === 'notice' && source.summary === RECOVERY_NOTICE_SUMMARY;
|
|
2641
2785
|
}
|
|
2642
2786
|
notificationText(notifications, readerId) {
|