@wowyuarm/dsh-agent-team 0.1.14-alpha.2 → 0.1.15
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 +6 -4
- package/README.zh.md +6 -4
- package/cordis.patch.yml +3 -1
- package/package.json +51 -46
- package/packages/agent-team/README.md +2 -2
- package/packages/agent-team/README.zh.md +2 -2
- package/packages/agent-team/core-skills/member-memory-manager/SKILL.md +30 -0
- package/packages/agent-team/lib/context-continuity-host.js +90 -0
- package/packages/agent-team/lib/context-projection.js +266 -489
- package/packages/agent-team/lib/context-source.js +4 -72
- package/packages/agent-team/lib/human-avatar.js +61 -0
- package/packages/agent-team/lib/human-profile.js +157 -0
- package/packages/agent-team/lib/human-update-check.js +119 -0
- package/packages/agent-team/lib/index.js +464 -254
- package/packages/agent-team/lib/ledger.js +120 -15
- package/packages/agent-team/lib/member-context.js +6 -3
- package/packages/agent-team/lib/member-runtime.js +1 -1
- package/packages/agent-team/lib/pressure-policy.js +6 -1
- package/packages/agent-team/lib/typert.host.js +489 -33
- package/packages/agent-team/lib/typert.remote-client.d.ts +15 -3
- package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
- package/packages/agent-team/lib/typert.remote-client.js +250 -28
- 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 +4 -35
- 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 +116 -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 +153 -41
- 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.map +1 -1
- 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/types/entities.d.ts +17 -0
- package/packages/agent-team/lib/types/types/entities.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types/requests-results.d.ts +81 -0
- package/packages/agent-team/lib/types/types/requests-results.d.ts.map +1 -1
- 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 +1545 -303
- 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 +6 -2
- 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 +67 -29
- package/packages/client-agent-team/lib/types/client/TeamComposer.js +2 -2
- 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/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 +66 -5
- 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/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 +52 -26
- 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 +90 -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 +96 -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 +76 -4
- 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/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 +9 -3
- 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 +2 -2
- package/packages/tool-agent-team/README.zh.md +2 -2
- 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
|
@@ -40,6 +40,8 @@ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn,
|
|
|
40
40
|
done = true;
|
|
41
41
|
};
|
|
42
42
|
import { randomUUID } from 'node:crypto';
|
|
43
|
+
import { readFileSync } from 'node:fs';
|
|
44
|
+
import { join } from 'node:path';
|
|
43
45
|
import { isDeepStrictEqual } from 'node:util';
|
|
44
46
|
import { Service } from '@deepseek-ai/cordis';
|
|
45
47
|
import { installModelSelection } from '@deepseek-ai/dsh-agent';
|
|
@@ -50,11 +52,14 @@ import { Session, SessionId, SessionLogOffset } from '@deepseek-ai/dsh-session';
|
|
|
50
52
|
import { setSandboxMode } from '@deepseek-ai/dsh-sandbox-policy';
|
|
51
53
|
import { Remote, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
52
54
|
import { ATTACHMENT_MAX_BYTES, attachmentPayloadPath, attachmentsRoot, copyPathAttachment, newAttachmentId, readAttachment, sanitizeMediaType, sweepAttachmentCache, validatePathAttachment, writeAttachment } from "./attachments.js";
|
|
55
|
+
import { HUMAN_PROFILE_DEFAULT_NAME, HUMAN_PROFILE_REPO_URL, HUMAN_PROFILE_SETTINGS_NAMESPACE, HUMAN_PROFILE_SETTINGS_SCHEMA, HUMAN_PROFILE_VERSION, assertValidHumanName, normalizeHumanName, parseLegacyHumanProfile, planLegacyAdoption } from "./human-profile.js";
|
|
56
|
+
import { humanAvatarsRoot, readHumanAvatar, removeHumanAvatar, writeHumanAvatar } from "./human-avatar.js";
|
|
57
|
+
import { createHumanUpdateChecker } from "./human-update-check.js";
|
|
53
58
|
import { PressurePolicyCoordinator } from "./pressure-policy.js";
|
|
54
|
-
import {
|
|
55
|
-
import {
|
|
56
|
-
import {
|
|
57
|
-
import {
|
|
59
|
+
import { CONTEXT_CONTINUITY_PROJECTION_KEY, readContextTimeline } from '@wowyuarm/dsh-context-continuity';
|
|
60
|
+
import { createTeamContextManagement, TEAM_CONTEXT_CODEC } from "./context-continuity-host.js";
|
|
61
|
+
import { AGENT_TEAM_PLUGIN_ID, isAgentTeamSource } from "./context-source.js";
|
|
62
|
+
import { boundaryByRef, carriedInputOf, checkpointByRef, checkpointRefFor, createTeamContextProjectionConfig, createTeamContextProjectionDefinition, foldTeamContextProjection, retainedTopicsThrough, TeamContextProjectionHost } from "./context-projection.js";
|
|
58
63
|
import { AGENT_TEAM_HUMAN_MEMBER_ID, AgentTeamLedger, agentTeamHumanActor } from "./ledger.js";
|
|
59
64
|
import { AGENT_TEAM_TOOL_NAMES, deepCopyCapabilities, memberMemoryDirectoryName, MemberRuntime } from "./member-runtime.js";
|
|
60
65
|
import { classifyRecoverableError, RecoveryCoordinator, RECOVERY_MAX_CONSECUTIVE_ERRORS } from "./recovery.js";
|
|
@@ -63,6 +68,8 @@ import { agentTeamDomainSpec } from "./spec.js";
|
|
|
63
68
|
import { formatTeamTimestamp } from "./time-format.js";
|
|
64
69
|
export { agentTeamDomainSpec, agentTeamOperationSchema } from "./spec.js";
|
|
65
70
|
export { AGENT_TEAM_HUMAN_HANDLE, AGENT_TEAM_HUMAN_MEMBER_ID, AGENT_TEAM_INITIALIZE_REQUEST_ID } from "./ledger.js";
|
|
71
|
+
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";
|
|
72
|
+
export { humanAvatarsRoot } from "./human-avatar.js";
|
|
66
73
|
export { AGENT_TEAM_TOOL_NAMES } from "./member-runtime.js";
|
|
67
74
|
/** Process-stable marker carried by the final Team message tool definition. */
|
|
68
75
|
export const AGENT_TEAM_PRESET_MARKER = Symbol.for('@wowyuarm/dsh-agent-team.preset');
|
|
@@ -85,9 +92,14 @@ const MAX_CHECKPOINT_NAME_CHARS = 120;
|
|
|
85
92
|
/** Default and maximum number of timeline items one query returns. */
|
|
86
93
|
const DEFAULT_TIMELINE_LIMIT = 12;
|
|
87
94
|
const MAX_TIMELINE_LIMIT = 24;
|
|
88
|
-
/**
|
|
95
|
+
/**
|
|
96
|
+
* Archived generations the timeline and seed resolution walk: the timeline
|
|
97
|
+
* reads this many ancestors behind the current generation, and the seed guard
|
|
98
|
+
* resolves a cited ref through the same depth, so the two surfaces cannot
|
|
99
|
+
* disagree about where history ends.
|
|
100
|
+
*/
|
|
89
101
|
const MAX_TIMELINE_ANCESTORS = 8;
|
|
90
|
-
/** Product pressure budget constants (see docs/team-collaboration.md). */
|
|
102
|
+
/** Product pressure budget constants (see docs/team-collaboration/README.md). */
|
|
91
103
|
const CONTEXT_HARD_LIMIT_CAP = 256_000;
|
|
92
104
|
const CONTEXT_HANDOFF_AT_CAP = 200_000;
|
|
93
105
|
const CONTEXT_HANDOFF_RESERVE = 8_000;
|
|
@@ -163,6 +175,7 @@ let AgentTeam = (() => {
|
|
|
163
175
|
let _classSuper = TypertRemoteService;
|
|
164
176
|
let _instanceExtraInitializers = [];
|
|
165
177
|
let _resolveTaskRefs_decorators;
|
|
178
|
+
let _resolveThreadRefs_decorators;
|
|
166
179
|
let _membersForClient_decorators;
|
|
167
180
|
let _changes_decorators;
|
|
168
181
|
let _createChannel_decorators;
|
|
@@ -182,6 +195,11 @@ let AgentTeam = (() => {
|
|
|
182
195
|
let _sendMessage_decorators;
|
|
183
196
|
let _putAttachment_decorators;
|
|
184
197
|
let _getAttachment_decorators;
|
|
198
|
+
let _humanProfileForClient_decorators;
|
|
199
|
+
let _putHumanAvatar_decorators;
|
|
200
|
+
let _getHumanAvatar_decorators;
|
|
201
|
+
let _removeHumanAvatar_decorators;
|
|
202
|
+
let _setHumanProfile_decorators;
|
|
185
203
|
let _reply_decorators;
|
|
186
204
|
let _changeAttention_decorators;
|
|
187
205
|
let _inbox_decorators;
|
|
@@ -193,6 +211,7 @@ let AgentTeam = (() => {
|
|
|
193
211
|
static {
|
|
194
212
|
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
195
213
|
_resolveTaskRefs_decorators = [Remote('resolveTaskRefs')];
|
|
214
|
+
_resolveThreadRefs_decorators = [Remote('resolveThreadRefs')];
|
|
196
215
|
_membersForClient_decorators = [Remote('members')];
|
|
197
216
|
_changes_decorators = [Remote({ mode: 'stream' })];
|
|
198
217
|
_createChannel_decorators = [Remote('createChannel')];
|
|
@@ -212,6 +231,11 @@ let AgentTeam = (() => {
|
|
|
212
231
|
_sendMessage_decorators = [Remote('sendMessage')];
|
|
213
232
|
_putAttachment_decorators = [Remote('putAttachment')];
|
|
214
233
|
_getAttachment_decorators = [Remote('getAttachment')];
|
|
234
|
+
_humanProfileForClient_decorators = [Remote('humanProfile')];
|
|
235
|
+
_putHumanAvatar_decorators = [Remote('putHumanAvatar')];
|
|
236
|
+
_getHumanAvatar_decorators = [Remote('getHumanAvatar')];
|
|
237
|
+
_removeHumanAvatar_decorators = [Remote('removeHumanAvatar')];
|
|
238
|
+
_setHumanProfile_decorators = [Remote('setHumanProfile')];
|
|
215
239
|
_reply_decorators = [Remote('reply')];
|
|
216
240
|
_changeAttention_decorators = [Remote('changeAttention')];
|
|
217
241
|
_inbox_decorators = [Remote('inbox')];
|
|
@@ -220,6 +244,7 @@ let AgentTeam = (() => {
|
|
|
220
244
|
_threadHistory_decorators = [Remote('threadHistory')];
|
|
221
245
|
_view_decorators = [Remote('view')];
|
|
222
246
|
__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);
|
|
247
|
+
__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
248
|
__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
249
|
__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
250
|
__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 +264,11 @@ let AgentTeam = (() => {
|
|
|
239
264
|
__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
265
|
__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
266
|
__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);
|
|
267
|
+
__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);
|
|
268
|
+
__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);
|
|
269
|
+
__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);
|
|
270
|
+
__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);
|
|
271
|
+
__esDecorate(this, null, _setHumanProfile_decorators, { kind: "method", name: "setHumanProfile", static: false, private: false, access: { has: obj => "setHumanProfile" in obj, get: obj => obj.setHumanProfile }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
242
272
|
__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
273
|
__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
274
|
__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);
|
|
@@ -248,6 +278,8 @@ let AgentTeam = (() => {
|
|
|
248
278
|
__esDecorate(this, null, _view_decorators, { kind: "method", name: "view", static: false, private: false, access: { has: obj => "view" in obj, get: obj => obj.view }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
249
279
|
if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
250
280
|
}
|
|
281
|
+
config = __runInitializers(this, _instanceExtraInitializers);
|
|
282
|
+
static Config = HUMAN_PROFILE_SETTINGS_SCHEMA;
|
|
251
283
|
static inject = [
|
|
252
284
|
'storageDomain',
|
|
253
285
|
'workspaceRegistry',
|
|
@@ -256,8 +288,9 @@ let AgentTeam = (() => {
|
|
|
256
288
|
'agentPresets',
|
|
257
289
|
'tools',
|
|
258
290
|
'sessionPersistence',
|
|
291
|
+
'sessionProjections',
|
|
259
292
|
];
|
|
260
|
-
domain
|
|
293
|
+
domain;
|
|
261
294
|
ledger;
|
|
262
295
|
handles = new Map();
|
|
263
296
|
/** Live Member per session id; drives the root session/event listener. */
|
|
@@ -297,6 +330,12 @@ let AgentTeam = (() => {
|
|
|
297
330
|
pressurePolicy;
|
|
298
331
|
notifiedInbox = new Map();
|
|
299
332
|
attachmentGcTimer;
|
|
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,42 +346,43 @@ 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
|
|
322
|
-
* state. See docs/architecture.md and docs/team-collaboration.md.
|
|
362
|
+
* generation. The ledger owns the binding audit, the engine's projection
|
|
363
|
+
* unit owns intent, and this coordinator owns only reconstructible process
|
|
364
|
+
* state. See docs/architecture/README.md and docs/team-collaboration/README.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}`); },
|
|
343
381
|
});
|
|
344
382
|
lifecycleTail = Promise.resolve();
|
|
345
383
|
accepting = true;
|
|
384
|
+
/** One adoption attempt per boot: the two readiness edges fire once each, and this keeps their attempt single. */
|
|
385
|
+
legacyAdoptionStarted = false;
|
|
346
386
|
/**
|
|
347
387
|
* Presence wake epoch: Agent running/idle/failure is runtime state with no
|
|
348
388
|
* durable fact behind it, so the presence scope counts those edge wakes in
|
|
@@ -350,8 +390,9 @@ let AgentTeam = (() => {
|
|
|
350
390
|
*/
|
|
351
391
|
presenceEpoch = 0;
|
|
352
392
|
changeWaiters = new Set();
|
|
353
|
-
constructor(ctx) {
|
|
393
|
+
constructor(ctx, config) {
|
|
354
394
|
super(ctx, 'agentTeam');
|
|
395
|
+
this.config = config;
|
|
355
396
|
this.pressurePolicy = new PressurePolicyCoordinator({
|
|
356
397
|
agentForMember: memberId => this.handles.get(memberId)?.agent,
|
|
357
398
|
memberForAgent: agent => {
|
|
@@ -368,9 +409,133 @@ let AgentTeam = (() => {
|
|
|
368
409
|
},
|
|
369
410
|
log: message => { this.ctx.logger.warn(`agent-team: ${message}`); },
|
|
370
411
|
});
|
|
412
|
+
// Human profile: this Host row's own Config (name + avatarRef, both
|
|
413
|
+
// volatile), so the settings service derives its form from the schema and
|
|
414
|
+
// an edit lands in the running plugin without a remount. Team's own page
|
|
415
|
+
// owns that surface, so the row declares itself presentation-owned rather
|
|
416
|
+
// than schema-page owned. Reads go straight to the live Config reference;
|
|
417
|
+
// this subscription is the change signal the retired section hook used to
|
|
418
|
+
// be — the ledger's runtime @ handle follows every edit.
|
|
419
|
+
this.ctx.inject(['settings'], settingsCtx => {
|
|
420
|
+
settingsCtx.effect(() => settingsCtx.settings.configure({ auto: false }, this.ctx.fiber));
|
|
421
|
+
this.adoptLegacyHumanProfile();
|
|
422
|
+
});
|
|
423
|
+
this.ctx.on('loader/volatile-update', () => { this.syncHumanHandle(); });
|
|
424
|
+
}
|
|
425
|
+
/** Current human display name; the single source for team_view and @ matching. */
|
|
426
|
+
humanHandle() {
|
|
427
|
+
const name = normalizeHumanName(this.config.name.get());
|
|
428
|
+
return name === '' ? HUMAN_PROFILE_DEFAULT_NAME : name;
|
|
429
|
+
}
|
|
430
|
+
/** Current human profile reference held in this Host row's Config (name + avatarRef). */
|
|
431
|
+
humanProfile() {
|
|
432
|
+
const avatarRef = this.config.avatarRef.get();
|
|
433
|
+
return Object.freeze({ name: this.humanHandle(), ...(avatarRef === undefined ? {} : { avatarRef }) });
|
|
434
|
+
}
|
|
435
|
+
/** Push the current Config name into the ledger's runtime @ handle. */
|
|
436
|
+
syncHumanHandle() {
|
|
437
|
+
try {
|
|
438
|
+
this.ledger?.setHumanDisplayHandle(this.humanHandle());
|
|
439
|
+
}
|
|
440
|
+
catch {
|
|
441
|
+
// The ledger is absent before Service.init; the post-open sync covers it.
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* The two judgements the Config schema cannot make about a Human name: the
|
|
446
|
+
* same non-empty floor as Member handles, plus global uniqueness against live
|
|
447
|
+
* Members. `setHumanProfile` runs it before the write, so a colliding rename
|
|
448
|
+
* rejects instead of persisting.
|
|
449
|
+
*/
|
|
450
|
+
validateHumanProfile(value) {
|
|
451
|
+
const name = assertValidHumanName(value.name);
|
|
452
|
+
const ledger = this.ledger;
|
|
453
|
+
if (ledger === undefined)
|
|
454
|
+
return;
|
|
455
|
+
const normalized = name.normalize('NFKC').trim().toLowerCase();
|
|
456
|
+
for (const member of ledger.listMembers()) {
|
|
457
|
+
if (member.state === 'inactive' || member.state === 'archived')
|
|
458
|
+
continue;
|
|
459
|
+
if (member.handle.normalize('NFKC').trim().toLowerCase() === normalized) {
|
|
460
|
+
throw new Error(`human name '${name}' collides with an existing Member handle`);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* One-time adoption of the profile facts the retired `agent-team-human`
|
|
466
|
+
* settings section held (see `LEGACY_HUMAN_PROFILE_SECTION` for why the
|
|
467
|
+
* upstream importer cannot carry them over). The values land in this Host
|
|
468
|
+
* row's own Config through the same Remote the profile page writes, so the
|
|
469
|
+
* page, the ledger's @ handle, and every avatar seat follow as after any
|
|
470
|
+
* edit. Readiness has two one-way edges — the settings service arrives, the
|
|
471
|
+
* ledger opens — and whichever fires second starts the attempt; only a
|
|
472
|
+
* pristine profile is adopted, and one attempt per boot is its whole
|
|
473
|
+
* lifetime, so adoption never loops and never re-writes.
|
|
474
|
+
*/
|
|
475
|
+
adoptLegacyHumanProfile() {
|
|
476
|
+
if (this.legacyAdoptionStarted)
|
|
477
|
+
return;
|
|
478
|
+
if (this.ctx.get('settings') === undefined || this.ledger === undefined)
|
|
479
|
+
return;
|
|
480
|
+
this.legacyAdoptionStarted = true;
|
|
481
|
+
void this.adoptLegacyHumanProfileNow().catch((error) => {
|
|
482
|
+
this.ctx.logger.warn('agent-team: legacy human profile adoption failed: %s', String(error));
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
/** Carry the first legacy document that still has the section into a pristine profile. */
|
|
486
|
+
async adoptLegacyHumanProfileNow() {
|
|
487
|
+
// A profile-launched Host always carries the profile context; a Host booted
|
|
488
|
+
// without one still adopts from the DSH home, which is where a pre-rc.1
|
|
489
|
+
// install kept the document. Read through `get`: cordis refuses a direct
|
|
490
|
+
// `ctx.profileContext` for a service this plugin does not declare, and
|
|
491
|
+
// adoption may not gate the Host on app-boot.
|
|
492
|
+
const profileHome = this.ctx.get('profileContext')?.home;
|
|
493
|
+
const homes = profileHome === undefined ? [dshHomePath()] : [dshHomePath(), profileHome];
|
|
494
|
+
const candidates = new Set(homes.flatMap(home => [join(home, 'settings.yaml'), join(home, 'settings.yaml.imported')]));
|
|
495
|
+
let legacy;
|
|
496
|
+
let source;
|
|
497
|
+
for (const candidate of candidates) {
|
|
498
|
+
let text;
|
|
499
|
+
try {
|
|
500
|
+
text = readFileSync(candidate, 'utf8');
|
|
501
|
+
}
|
|
502
|
+
catch {
|
|
503
|
+
// Absent candidates are the steady state once adoption settles; the
|
|
504
|
+
// live document is one only because the first boot after the upgrade
|
|
505
|
+
// can race the importer that renames it.
|
|
506
|
+
continue;
|
|
507
|
+
}
|
|
508
|
+
const parsed = parseLegacyHumanProfile(text);
|
|
509
|
+
if (parsed !== undefined) {
|
|
510
|
+
legacy = parsed;
|
|
511
|
+
source = candidate;
|
|
512
|
+
break;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
if (legacy === undefined || source === undefined)
|
|
516
|
+
return;
|
|
517
|
+
// The reference is carried only while its bytes still draw: the avatar
|
|
518
|
+
// store is the authority for whether this avatar exists at all.
|
|
519
|
+
let avatarRef = legacy.avatarRef;
|
|
520
|
+
if (avatarRef !== undefined && await readHumanAvatar(humanAvatarsRoot(), avatarRef) === undefined)
|
|
521
|
+
avatarRef = undefined;
|
|
522
|
+
const plan = planLegacyAdoption(this.humanProfile(), {
|
|
523
|
+
...(legacy.name === undefined ? {} : { name: legacy.name }),
|
|
524
|
+
...(avatarRef === undefined ? {} : { avatarRef }),
|
|
525
|
+
});
|
|
526
|
+
if (plan === undefined)
|
|
527
|
+
return;
|
|
528
|
+
await this.setHumanProfile(plan);
|
|
529
|
+
this.ctx.logger.info('agent-team: adopted the legacy human profile from %s', source);
|
|
371
530
|
}
|
|
372
531
|
/** Open the durable ledger and restore every enabled Member independently. */
|
|
373
532
|
async [Service.init]() {
|
|
533
|
+
// The continuity projection registers once per Host: the framework keeps
|
|
534
|
+
// one unit per projection key and drives it for every Session, so Team's
|
|
535
|
+
// fold is session-agnostic and the state carries the identity it folds.
|
|
536
|
+
// The registration rides this plugin's fiber — unloading Team removes the
|
|
537
|
+
// key (and its cached cells) from later drives and snapshots.
|
|
538
|
+
this.ctx.effect(() => this.ctx.root.sessionProjections.register(createTeamContextProjectionDefinition(this.contextProjectionHost)), 'agentTeam.contextProjection');
|
|
374
539
|
this.ctx.on('agent/error', ({ agent, error }) => {
|
|
375
540
|
const member = this.memberForAgent(agent);
|
|
376
541
|
if (member === undefined)
|
|
@@ -456,6 +621,11 @@ let AgentTeam = (() => {
|
|
|
456
621
|
this.domain = domain;
|
|
457
622
|
const ledger = new AgentTeamLedger(domain.table('operations'));
|
|
458
623
|
this.ledger = ledger;
|
|
624
|
+
// A live Config edit in the constructor's window may have arrived before
|
|
625
|
+
// the ledger existed; sync once here so @ matching starts from the stored
|
|
626
|
+
// name.
|
|
627
|
+
this.syncHumanHandle();
|
|
628
|
+
this.adoptLegacyHumanProfile();
|
|
459
629
|
const initialization = await ledger.initialize();
|
|
460
630
|
if (initialization.committed)
|
|
461
631
|
this.emitCommitted(initialization.value);
|
|
@@ -531,6 +701,18 @@ let AgentTeam = (() => {
|
|
|
531
701
|
});
|
|
532
702
|
return Object.freeze({ resolved: Object.freeze(this.requireLedger().resolveTaskRefs(request.workspaceId, taskRefs)) });
|
|
533
703
|
}
|
|
704
|
+
/** Read-only navigation lookup for branded Thread refs found in message bodies. */
|
|
705
|
+
resolveThreadRefs(request) {
|
|
706
|
+
this.requireWorkspace(request.workspaceId);
|
|
707
|
+
const seen = new Set();
|
|
708
|
+
const threadRefs = request.threadRefs.filter(threadRef => {
|
|
709
|
+
if (seen.has(threadRef))
|
|
710
|
+
return false;
|
|
711
|
+
seen.add(threadRef);
|
|
712
|
+
return true;
|
|
713
|
+
});
|
|
714
|
+
return Object.freeze({ resolved: Object.freeze(this.requireLedger().resolveThreadRefs(request.workspaceId, threadRefs)) });
|
|
715
|
+
}
|
|
534
716
|
/** Browser-safe Human roster, optionally filtered to one participation. */
|
|
535
717
|
membersForClient(request) {
|
|
536
718
|
if (request.workspaceId !== undefined)
|
|
@@ -634,13 +816,16 @@ let AgentTeam = (() => {
|
|
|
634
816
|
privateMemoryPath: dshHomePath('agent-team', 'members', memberMemoryDirectoryName(memberId)),
|
|
635
817
|
state: 'enabled',
|
|
636
818
|
});
|
|
637
|
-
const
|
|
819
|
+
const ledger = this.requireLedger();
|
|
820
|
+
const result = await ledger.addMember({ ...request, actor: agentTeamHumanActor(), member });
|
|
638
821
|
if (result.committed)
|
|
639
822
|
this.emitCommitted(result.value.receipt);
|
|
640
823
|
const stored = result.value.member;
|
|
641
824
|
if (!this.handles.has(stored.memberId))
|
|
642
825
|
await this.activateMember(stored, workspace.path);
|
|
643
|
-
|
|
826
|
+
// Creation seeds exactly the creation Workspace participation; attach it
|
|
827
|
+
// here (like membersForClient) so the Client never synthesizes it.
|
|
828
|
+
return Object.freeze({ receipt: result.value.receipt, status: this.memberStatus(stored), workspaceIds: ledger.workspacesOf(stored.memberId) });
|
|
644
829
|
});
|
|
645
830
|
}
|
|
646
831
|
/** Commit suspended intent, then wait for the owned AgentHandle to become quiescent. */
|
|
@@ -820,6 +1005,9 @@ let AgentTeam = (() => {
|
|
|
820
1005
|
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
1006
|
}
|
|
822
1007
|
const rolled = await this.rolloverSessionForAgent(active.agent, {
|
|
1008
|
+
// The engine plan carries the host-derived request id as a plain
|
|
1009
|
+
// string; Team's rollover operation vocabulary brands it, and the
|
|
1010
|
+
// context-continuity host adapter is its only producer.
|
|
823
1011
|
requestId: plan.requestId,
|
|
824
1012
|
workspaceId: stored.workspaceId,
|
|
825
1013
|
memberId,
|
|
@@ -836,8 +1024,9 @@ let AgentTeam = (() => {
|
|
|
836
1024
|
// to override the fold.
|
|
837
1025
|
const preRetireCapture = this.contextManagement.drainCapturedInput(memberId);
|
|
838
1026
|
await this.retireMemberGeneration(memberId, active, plan.previousSessionId);
|
|
839
|
-
const
|
|
840
|
-
const
|
|
1027
|
+
const finalEvents = active.agent.session.ownEvents();
|
|
1028
|
+
const finalState = foldTeamContextProjection(finalEvents, { sessionId: active.agent.session.id, inheritedEventCount: active.agent.session.inheritedEventCount }, this.contextProjectionHost);
|
|
1029
|
+
const foldedCarried = carriedInputOf(finalState, finalEvents);
|
|
841
1030
|
const carriedById = new Map(plan.carriedInput.map(message => [message.id, message]));
|
|
842
1031
|
for (const message of foldedCarried)
|
|
843
1032
|
carriedById.set(message.id, message);
|
|
@@ -1119,6 +1308,91 @@ let AgentTeam = (() => {
|
|
|
1119
1308
|
throw new Error(`attachment '${request.attachmentId}' is no longer cached`);
|
|
1120
1309
|
return Object.freeze({ name: stored.name, mediaType: stored.mediaType, byteSize: stored.byteSize, bytesBase64: stored.bytes.toString('base64') });
|
|
1121
1310
|
}
|
|
1311
|
+
/**
|
|
1312
|
+
* Human profile read for the settings page and footnote: name + avatar
|
|
1313
|
+
* reference from this Host row's live Config plus version facts. Human-scoped
|
|
1314
|
+
* (the Web Client calls it); agent tools never receive avatar bytes, only the
|
|
1315
|
+
* name through team_view. The new-release check stays best-effort and cached —
|
|
1316
|
+
* `updateAvailable` is false until a background refresh actually observes a
|
|
1317
|
+
* newer published release.
|
|
1318
|
+
*/
|
|
1319
|
+
humanProfileForClient(_request) {
|
|
1320
|
+
const profile = this.humanProfile();
|
|
1321
|
+
const update = this.humanUpdateCheck.snapshot();
|
|
1322
|
+
return Object.freeze({
|
|
1323
|
+
name: profile.name,
|
|
1324
|
+
...(profile.avatarRef === undefined ? {} : { avatarRef: profile.avatarRef }),
|
|
1325
|
+
version: HUMAN_PROFILE_VERSION,
|
|
1326
|
+
repoUrl: HUMAN_PROFILE_REPO_URL,
|
|
1327
|
+
updateAvailable: update.updateAvailable,
|
|
1328
|
+
...(update.latestVersion === undefined ? {} : { latestVersion: update.latestVersion }),
|
|
1329
|
+
});
|
|
1330
|
+
}
|
|
1331
|
+
/**
|
|
1332
|
+
* Upload one human avatar image into the persistent store. Human-only by
|
|
1333
|
+
* construction: only the Web Client calls this Remote, never agent tools.
|
|
1334
|
+
* The caller stores the returned ref in settings; bytes never enter the
|
|
1335
|
+
* TTL-bound attachment cache.
|
|
1336
|
+
*/
|
|
1337
|
+
async putHumanAvatar(request) {
|
|
1338
|
+
this.requireAccepting();
|
|
1339
|
+
const bytes = Buffer.from(request.bytesBase64, 'base64');
|
|
1340
|
+
return Object.freeze(await writeHumanAvatar(humanAvatarsRoot(), request.name, request.mediaType ?? 'application/octet-stream', bytes));
|
|
1341
|
+
}
|
|
1342
|
+
/** Read one human avatar back; removed entries throw and the UI falls back to hue/initial. */
|
|
1343
|
+
async getHumanAvatar(request) {
|
|
1344
|
+
const stored = await readHumanAvatar(humanAvatarsRoot(), request.avatarRef);
|
|
1345
|
+
if (stored === undefined)
|
|
1346
|
+
throw new Error(`human avatar '${request.avatarRef}' is no longer stored`);
|
|
1347
|
+
return Object.freeze({ name: stored.name, mediaType: stored.mediaType, byteSize: stored.byteSize, bytesBase64: stored.bytes.toString('base64') });
|
|
1348
|
+
}
|
|
1349
|
+
/** Remove one human avatar entry; the UI falls back to hue/initial afterwards. */
|
|
1350
|
+
async removeHumanAvatar(request) {
|
|
1351
|
+
this.requireAccepting();
|
|
1352
|
+
await removeHumanAvatar(humanAvatarsRoot(), request.avatarRef);
|
|
1353
|
+
return Object.freeze({ removed: true });
|
|
1354
|
+
}
|
|
1355
|
+
/**
|
|
1356
|
+
* Overwrite the Human profile fields the caller supplies. The Host owns this
|
|
1357
|
+
* write because the profile is the Host row's own Config: the schema supplies
|
|
1358
|
+
* the shape, this method supplies the two judgements the schema cannot make
|
|
1359
|
+
* (a non-empty name, a name no live Member already answers to), and the
|
|
1360
|
+
* settings service persists the result into the active profile's patch
|
|
1361
|
+
* document and applies it to the running plugin live.
|
|
1362
|
+
*
|
|
1363
|
+
* No expected revision accompanies the write: the profile is two scalar
|
|
1364
|
+
* fields written from the Human's own pages, where the last write wins. This
|
|
1365
|
+
* is not a hard boundary around a user-editable document — the settings
|
|
1366
|
+
* service's own document opener (and a text editor) can change the stored
|
|
1367
|
+
* name without passing here, exactly as the retired section validator could
|
|
1368
|
+
* not stop it.
|
|
1369
|
+
*/
|
|
1370
|
+
async setHumanProfile(request) {
|
|
1371
|
+
this.requireAccepting();
|
|
1372
|
+
const settings = this.ctx.get('settings');
|
|
1373
|
+
if (settings === undefined)
|
|
1374
|
+
throw new Error('the settings service is unavailable');
|
|
1375
|
+
const name = request.name === undefined ? undefined : assertValidHumanName(request.name);
|
|
1376
|
+
if (name !== undefined)
|
|
1377
|
+
this.validateHumanProfile({ name });
|
|
1378
|
+
const ops = [];
|
|
1379
|
+
if (name !== undefined)
|
|
1380
|
+
ops.push({ op: 'set', path: ['name'], value: name });
|
|
1381
|
+
if (request.avatarRef === null)
|
|
1382
|
+
ops.push({ op: 'unset', path: ['avatarRef'] });
|
|
1383
|
+
else if (request.avatarRef !== undefined)
|
|
1384
|
+
ops.push({ op: 'set', path: ['avatarRef'], value: request.avatarRef });
|
|
1385
|
+
if (ops.length !== 0) {
|
|
1386
|
+
// The settings namespace IS this Host row's id in the composition, and it
|
|
1387
|
+
// must be addressed by that name rather than by `ctx.fiber.entry` here: a
|
|
1388
|
+
// Remote call runs under its caller's context, whose fiber entry is the
|
|
1389
|
+
// RPC gateway's row (`typert-gateway`), which the settings service then
|
|
1390
|
+
// looks up as an unrelated plugin. shipping.spec.ts pins the constant to
|
|
1391
|
+
// the row `cordis.patch.yml` declares.
|
|
1392
|
+
await settings.mutate(HUMAN_PROFILE_SETTINGS_NAMESPACE, ops);
|
|
1393
|
+
}
|
|
1394
|
+
return Object.freeze({ ...this.humanProfile() });
|
|
1395
|
+
}
|
|
1122
1396
|
/** Human existing-Thread reply; unread and revision conflicts are business outcomes. */
|
|
1123
1397
|
async reply(request) {
|
|
1124
1398
|
return this.replyAs(this.humanCall(request.workspaceId), request);
|
|
@@ -1429,7 +1703,7 @@ let AgentTeam = (() => {
|
|
|
1429
1703
|
let sessionId = agent.session.id;
|
|
1430
1704
|
let live = true;
|
|
1431
1705
|
let guard = 0;
|
|
1432
|
-
while (sessionId !== undefined && guard++
|
|
1706
|
+
while (sessionId !== undefined && guard++ <= MAX_TIMELINE_ANCESTORS) {
|
|
1433
1707
|
let events;
|
|
1434
1708
|
let inheritedEventCount;
|
|
1435
1709
|
let parentSession;
|
|
@@ -1454,19 +1728,19 @@ let AgentTeam = (() => {
|
|
|
1454
1728
|
// Fold the source with its inherited cut respected: inherited events
|
|
1455
1729
|
// are resolved history in that source, never fresh intent; checkpoints
|
|
1456
1730
|
// recorded in this source's own span are the selectable targets.
|
|
1457
|
-
const state =
|
|
1731
|
+
const state = foldTeamContextProjection(events, { sessionId, inheritedEventCount }, this.contextProjectionHost);
|
|
1458
1732
|
// A Team-boundary default checkpoint: the boundary's completed-turn
|
|
1459
1733
|
// anchor is the seed cut, and it is selectable exactly when one
|
|
1460
1734
|
// Thread's facts entered the context through it — the same proof the
|
|
1461
1735
|
// timeline requires, revalidated here because the model may cite a
|
|
1462
1736
|
// boundary the timeline never surfaced.
|
|
1463
1737
|
const boundary = checkpointRef.startsWith('team-boundary-')
|
|
1464
|
-
? state
|
|
1738
|
+
? boundaryByRef(state, checkpointRef)
|
|
1465
1739
|
: undefined;
|
|
1466
1740
|
const entry = checkpointByRef(state, checkpointRef);
|
|
1467
1741
|
const anchorTurnEndSeq = boundary !== undefined ? boundary.turnEndSeq : entry?.turnEndSeq;
|
|
1468
1742
|
if (anchorTurnEndSeq !== undefined && anchorTurnEndSeq !== -1) {
|
|
1469
|
-
if (boundary !== undefined && boundary.
|
|
1743
|
+
if (boundary !== undefined && boundary.kind !== 'team-boundary') {
|
|
1470
1744
|
throw new Error(`checkpoint '${checkpointRef}' is not a restorable boundary`);
|
|
1471
1745
|
}
|
|
1472
1746
|
// The seed is the exact contiguous prefix through the anchor's
|
|
@@ -1474,7 +1748,12 @@ let AgentTeam = (() => {
|
|
|
1474
1748
|
const throughSeq = anchorTurnEndSeq + 1;
|
|
1475
1749
|
const prefix = events.slice(0, throughSeq);
|
|
1476
1750
|
if (boundary !== undefined) {
|
|
1477
|
-
|
|
1751
|
+
// The seed must stay inside one Thread's context: the retained
|
|
1752
|
+
// prefix through the anchor holds exactly one Thread's facts, read
|
|
1753
|
+
// from the fold's own boundary attributions — the same accumulated
|
|
1754
|
+
// set the timeline shows, so a ref the timeline offered is never
|
|
1755
|
+
// refused here for a reason it did not state.
|
|
1756
|
+
const threads = retainedTopicsThrough(state, anchorTurnEndSeq);
|
|
1478
1757
|
if (threads.length !== 1) {
|
|
1479
1758
|
throw new Error(threads.length === 0
|
|
1480
1759
|
? `boundary '${checkpointRef}' has no single attributable Thread; write a fresh handoff instead`
|
|
@@ -1535,11 +1814,11 @@ let AgentTeam = (() => {
|
|
|
1535
1814
|
return;
|
|
1536
1815
|
}
|
|
1537
1816
|
const inspection = previousRead.inspection;
|
|
1538
|
-
const state =
|
|
1817
|
+
const state = foldTeamContextProjection(inspection.events, { sessionId: previousSessionId, inheritedEventCount: inspection.inheritedEventCount }, this.contextProjectionHost);
|
|
1539
1818
|
if (state.pending === null)
|
|
1540
1819
|
return;
|
|
1541
1820
|
const pending = state.pending;
|
|
1542
|
-
const message = createHandoffMessage({
|
|
1821
|
+
const message = TEAM_CONTEXT_CODEC.createHandoffMessage({
|
|
1543
1822
|
handoff: pending.handoff,
|
|
1544
1823
|
previousSessionId,
|
|
1545
1824
|
newSessionId: agent.session.id,
|
|
@@ -1573,9 +1852,9 @@ let AgentTeam = (() => {
|
|
|
1573
1852
|
if (!Number.isSafeInteger(through) || through < 0 || through > inspection.events.length) {
|
|
1574
1853
|
throw new Error(`the recorded checkpoint-return seed cut ${through} is not a valid prefix of Session '${seed.sourceSessionId}'`);
|
|
1575
1854
|
}
|
|
1576
|
-
const state =
|
|
1855
|
+
const state = foldTeamContextProjection(inspection.events, { sessionId: seed.sourceSessionId, inheritedEventCount: inspection.inheritedEventCount }, this.contextProjectionHost);
|
|
1577
1856
|
const anchorProven = checkpointByRef(state, seed.checkpointRef) !== undefined
|
|
1578
|
-
|| state
|
|
1857
|
+
|| boundaryByRef(state, seed.checkpointRef) !== undefined;
|
|
1579
1858
|
if (!anchorProven) {
|
|
1580
1859
|
throw new Error(`the recorded checkpoint-return anchor '${seed.checkpointRef}' no longer resolves in Session '${seed.sourceSessionId}'`);
|
|
1581
1860
|
}
|
|
@@ -1641,8 +1920,8 @@ let AgentTeam = (() => {
|
|
|
1641
1920
|
}
|
|
1642
1921
|
inspectionEvents = previousRead.inspection.events;
|
|
1643
1922
|
inspectionInherited = previousRead.inspection.inheritedEventCount;
|
|
1644
|
-
const state =
|
|
1645
|
-
const carried = carriedInputOf(state);
|
|
1923
|
+
const state = foldTeamContextProjection(inspectionEvents, { sessionId: transition.previousSessionId, inheritedEventCount: inspectionInherited }, this.contextProjectionHost);
|
|
1924
|
+
const carried = carriedInputOf(state, inspectionEvents);
|
|
1646
1925
|
if (carried.length === 0)
|
|
1647
1926
|
return 0;
|
|
1648
1927
|
const known = new Set();
|
|
@@ -1693,9 +1972,11 @@ let AgentTeam = (() => {
|
|
|
1693
1972
|
* Agent-only bounded structural timeline: resolved checkpoints plus Team
|
|
1694
1973
|
* delivery, handoff, and compaction boundaries across the current
|
|
1695
1974
|
* generation and its archived ancestor lineage. Structural only — no
|
|
1696
|
-
* transcript content. The
|
|
1697
|
-
*
|
|
1698
|
-
*
|
|
1975
|
+
* transcript content. The walk, the per-source folds, the pricing, and the
|
|
1976
|
+
* anchor rules are the engine's `readContextTimeline`; Team contributes the
|
|
1977
|
+
* fold configuration, the measurement, and the one judgement the engine
|
|
1978
|
+
* leaves to its host — which Threads a boundary's retained prefix holds — so
|
|
1979
|
+
* a ref this list offers is a ref `context_rollover` accepts.
|
|
1699
1980
|
*/
|
|
1700
1981
|
async contextTimelineForAgent(agent, request) {
|
|
1701
1982
|
const member = this.memberForAgent(agent);
|
|
@@ -1710,70 +1991,111 @@ let AgentTeam = (() => {
|
|
|
1710
1991
|
const limits = await this.routeLimitsForAgent(agent);
|
|
1711
1992
|
const hardLimit = limits?.hardLimit ?? CONTEXT_HARD_LIMIT_CAP;
|
|
1712
1993
|
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
|
-
|
|
1994
|
+
// One measurement per lineage source, memoized by Session: the engine
|
|
1995
|
+
// measures a source once before pricing its anchors, and the boundary
|
|
1996
|
+
// overlay below reads the same map to tell an unprovable budget (never
|
|
1997
|
+
// priced as free) from a real budget rejection.
|
|
1998
|
+
const measurements = new Map();
|
|
1999
|
+
const timeline = await readContextTimeline({
|
|
2000
|
+
current: {
|
|
2001
|
+
sessionId: agent.session.id,
|
|
2002
|
+
header: agent.session.header,
|
|
2003
|
+
inheritedEventCount: agent.session.inheritedEventCount,
|
|
2004
|
+
events: agent.session.snapshotEvents(),
|
|
2005
|
+
},
|
|
2006
|
+
config: this.contextFoldConfig(),
|
|
2007
|
+
readAncestor: sessionId => this.sessionReader.read(sessionId),
|
|
2008
|
+
measureSource: source => {
|
|
2009
|
+
const key = String(source.sessionId);
|
|
2010
|
+
if (!measurements.has(key))
|
|
2011
|
+
measurements.set(key, this.measureContextSourceForAgent(agent, source));
|
|
2012
|
+
return measurements.get(key);
|
|
2013
|
+
},
|
|
2014
|
+
currentUsageTokens: usageTokens,
|
|
2015
|
+
handoffAt,
|
|
2016
|
+
limit,
|
|
2017
|
+
// Archived ancestors the engine walks; the seed guard resolves a cited
|
|
2018
|
+
// ref through the same depth, so the timeline never offers a ref the
|
|
2019
|
+
// guard cannot reach.
|
|
2020
|
+
maxAncestors: MAX_TIMELINE_ANCESTORS,
|
|
2021
|
+
});
|
|
2022
|
+
return {
|
|
2023
|
+
usageTokens,
|
|
2024
|
+
hardLimit,
|
|
2025
|
+
handoffAt,
|
|
2026
|
+
items: timeline.items.map(item => this.teamTimelineItemFor(item, agent.session.id, handoffAt, measurements)),
|
|
2027
|
+
...(timeline.incompleteFrom === undefined ? {} : { incompleteFrom: timeline.incompleteFrom }),
|
|
2028
|
+
};
|
|
2029
|
+
}
|
|
2030
|
+
/**
|
|
2031
|
+
* Map one engine timeline item onto the model-facing Team item. The engine
|
|
2032
|
+
* decided the walk, the fold, the pricing, and the head, checkpoint, and
|
|
2033
|
+
* measurable-source verdicts; Team restates exactly one policy of its own: a
|
|
2034
|
+
* Team boundary is selectable only when the RETAINED PREFIX through it stays
|
|
2035
|
+
* inside one Thread — the same proof the seed guard revalidates before it
|
|
2036
|
+
* swaps a generation. The engine judges a boundary by its OWN attribution
|
|
2037
|
+
* instead, so a boundary that arrived after a second Thread's facts would be
|
|
2038
|
+
* offered here and refused by `context_rollover`; Team's stricter rule is
|
|
2039
|
+
* what keeps the two surfaces answering one question.
|
|
2040
|
+
*/
|
|
2041
|
+
teamTimelineItemFor(item, currentSessionId, handoffAt, measurements) {
|
|
2042
|
+
const source = item.source === 'boundary'
|
|
2043
|
+
? item.kind === 'handoff' || item.kind === 'compaction' ? item.kind : 'team-boundary'
|
|
2044
|
+
: item.source === 'checkpoint' ? 'agent' : 'head';
|
|
2045
|
+
const affectedThreads = item.affectedTopics;
|
|
2046
|
+
let restorable = item.restorable;
|
|
2047
|
+
let reason = item.reason;
|
|
2048
|
+
// A boundary's verdict is Team's to make, but only once its source is
|
|
2049
|
+
// measurable: "the budget cannot be proven" is the engine's first
|
|
2050
|
+
// rejection and stays first — an unmeasurable boundary is not selectable
|
|
2051
|
+
// even when its prefix holds exactly one Thread.
|
|
2052
|
+
if (item.source === 'boundary' && measurements.get(String(item.sourceSessionId ?? currentSessionId)) !== undefined) {
|
|
2053
|
+
if (source === 'handoff' || source === 'compaction') {
|
|
2054
|
+
// A handoff opens a generation and a compaction rewrites the visible
|
|
2055
|
+
// surface: rewinding into either is not a proven-safe target. The
|
|
2056
|
+
// engine has no vocabulary for that and would answer "no single topic
|
|
2057
|
+
// is attributable", which misdescribes why.
|
|
2058
|
+
restorable = false;
|
|
2059
|
+
reason = `source '${source}' is not a restorable checkpoint`;
|
|
1733
2060
|
}
|
|
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
|
-
}
|
|
2061
|
+
else if (affectedThreads.length !== 1) {
|
|
2062
|
+
restorable = false;
|
|
2063
|
+
reason = affectedThreads.length === 0
|
|
2064
|
+
? 'no single Thread is attributable to this boundary'
|
|
2065
|
+
: 'multiple Threads entered the context through this boundary; write a fresh handoff instead';
|
|
1748
2066
|
}
|
|
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;
|
|
2067
|
+
else if (item.retainedTokens >= handoffAt) {
|
|
2068
|
+
restorable = false;
|
|
2069
|
+
reason = 'retained context would not materially shrink the working set';
|
|
2070
|
+
}
|
|
2071
|
+
else {
|
|
2072
|
+
restorable = true;
|
|
2073
|
+
reason = undefined;
|
|
1764
2074
|
}
|
|
1765
|
-
if (items.length >= limit)
|
|
1766
|
-
break;
|
|
1767
2075
|
}
|
|
1768
|
-
return {
|
|
2076
|
+
return {
|
|
2077
|
+
checkpointRef: item.ref,
|
|
2078
|
+
name: item.label,
|
|
2079
|
+
source,
|
|
2080
|
+
retainedTokens: item.retainedTokens,
|
|
2081
|
+
discardedTokens: item.discardedTokens,
|
|
2082
|
+
affectedThreads,
|
|
2083
|
+
restorable,
|
|
2084
|
+
...(reason === undefined ? {} : { reason }),
|
|
2085
|
+
// Team's field semantics, unchanged: every item that is not a
|
|
2086
|
+
// checkpoint names the Session it anchors in — an ancestor generation's
|
|
2087
|
+
// boundary is how a lineage reads — and a checkpoint is keyed to its own
|
|
2088
|
+
// Session by its ref already.
|
|
2089
|
+
...(source === 'agent' ? {} : { sourceSessionId: item.sourceSessionId ?? currentSessionId }),
|
|
2090
|
+
};
|
|
1769
2091
|
}
|
|
1770
2092
|
/**
|
|
1771
2093
|
* 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.
|
|
2094
|
+
* measures directly; an archived ancestor measures through a detached
|
|
2095
|
+
* Session rebuilt from the source's own log, so a seed's retained cost is
|
|
2096
|
+
* priced in the SOURCE's own tokens — never the current generation's.
|
|
2097
|
+
* Returns undefined when no meter is available or the source cannot be
|
|
2098
|
+
* replayed; callers fail closed on the unknown.
|
|
1777
2099
|
*/
|
|
1778
2100
|
async sourceUsageTokens(sessionId, live, agent) {
|
|
1779
2101
|
const meter = agent.ctx.get('tokenMeter');
|
|
@@ -1788,14 +2110,50 @@ let AgentTeam = (() => {
|
|
|
1788
2110
|
const read = await this.sessionReader.read(sessionId);
|
|
1789
2111
|
if (!read.ok)
|
|
1790
2112
|
return undefined;
|
|
2113
|
+
return this.measureDetachedSource(agent, {
|
|
2114
|
+
sessionId,
|
|
2115
|
+
header: read.inspection.header,
|
|
2116
|
+
inheritedEventCount: read.inspection.inheritedEventCount,
|
|
2117
|
+
events: read.inspection.events,
|
|
2118
|
+
});
|
|
2119
|
+
}
|
|
2120
|
+
/**
|
|
2121
|
+
* One already-read source's replayed measurement: an archived generation is
|
|
2122
|
+
* rebuilt as a detached Session and measured in its own tokens. Undefined
|
|
2123
|
+
* means unmeasurable, never free.
|
|
2124
|
+
*/
|
|
2125
|
+
measureDetachedSource(agent, source) {
|
|
2126
|
+
const meter = agent.ctx.get('tokenMeter');
|
|
2127
|
+
if (meter === undefined)
|
|
2128
|
+
return undefined;
|
|
1791
2129
|
try {
|
|
1792
|
-
const session = Session.create(sessionId,
|
|
2130
|
+
const session = Session.create(source.sessionId, source.events, source.header, source.inheritedEventCount);
|
|
1793
2131
|
return meter.measure(session)?.totalTokens;
|
|
1794
2132
|
}
|
|
1795
2133
|
catch {
|
|
1796
2134
|
return undefined;
|
|
1797
2135
|
}
|
|
1798
2136
|
}
|
|
2137
|
+
/**
|
|
2138
|
+
* One source's replayed measurement, in that source's own tokens, for a
|
|
2139
|
+
* caller that already holds the source: the live generation measures
|
|
2140
|
+
* directly, an archived one is rebuilt from the log it came with. Undefined
|
|
2141
|
+
* means unmeasurable — a caller must never price an unknown source as free.
|
|
2142
|
+
*/
|
|
2143
|
+
measureContextSourceForAgent(agent, source) {
|
|
2144
|
+
if (String(source.sessionId) === String(agent.session.id)) {
|
|
2145
|
+
return agent.ctx.get('tokenMeter')?.measure(agent.session)?.totalTokens;
|
|
2146
|
+
}
|
|
2147
|
+
return this.measureDetachedSource(agent, source);
|
|
2148
|
+
}
|
|
2149
|
+
/**
|
|
2150
|
+
* The one fold configuration the registered projection unit and every cold
|
|
2151
|
+
* fold use, so a generation read back for the timeline reads exactly as the
|
|
2152
|
+
* projection folded it — same codec, same boundary attribution.
|
|
2153
|
+
*/
|
|
2154
|
+
contextFoldConfig() {
|
|
2155
|
+
return createTeamContextProjectionConfig(this.contextProjectionHost);
|
|
2156
|
+
}
|
|
1799
2157
|
/**
|
|
1800
2158
|
* Monotonic anchor-share estimate of a seed's retained cost, priced in the
|
|
1801
2159
|
* SOURCE Session's own measurement: the fraction of the source log the
|
|
@@ -1811,155 +2169,6 @@ let AgentTeam = (() => {
|
|
|
1811
2169
|
const share = Math.min(1, Math.max(0, (anchorTurnEndSeq + 1) / sourceLength));
|
|
1812
2170
|
return Math.round(sourceUsageTokens * share);
|
|
1813
2171
|
}
|
|
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
2172
|
/**
|
|
1964
2173
|
* Agent-only rollover request validation: the tool calls this to check its
|
|
1965
2174
|
* Member binding, exclusivity, and checkpoint ownership. It performs no
|
|
@@ -2284,7 +2493,8 @@ let AgentTeam = (() => {
|
|
|
2284
2493
|
// old Session; the projection still carries the intent, so finish the
|
|
2285
2494
|
// transition (or keep waiting for the containing turn) from here.
|
|
2286
2495
|
if (persisted) {
|
|
2287
|
-
const
|
|
2496
|
+
const inheritedEventCount = created.agent.session.inheritedEventCount;
|
|
2497
|
+
const state = foldTeamContextProjection(created.agent.session.ownEvents(), { sessionId: created.agent.session.id, inheritedEventCount }, this.contextProjectionHost);
|
|
2288
2498
|
if (state.pending !== null)
|
|
2289
2499
|
this.contextManagement.recoverPendingTransition(member.memberId, created.agent, member.sessionId);
|
|
2290
2500
|
// A restart between one checkpoint's durable result and its quiet
|
|
@@ -2299,7 +2509,7 @@ let AgentTeam = (() => {
|
|
|
2299
2509
|
// by the projection boundary alone: every handoff this Host ever
|
|
2300
2510
|
// published classifies under the context source's recognizer,
|
|
2301
2511
|
// including the history the read-time conversion renamed.
|
|
2302
|
-
if (!state.boundaries.some(boundary => boundary.
|
|
2512
|
+
if (!state.boundaries.some(boundary => boundary.kind === 'handoff')) {
|
|
2303
2513
|
await this.reconstructMissingHandoff(member, created.agent);
|
|
2304
2514
|
}
|
|
2305
2515
|
// Carried input redelivery binds to the committed transition target —
|