@wowyuarm/dsh-agent-team 0.1.4 → 0.1.5
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.zh.md +5 -2
- package/package.json +1 -1
- package/packages/agent-team/lib/auto-compaction.js +39 -1
- package/packages/agent-team/lib/index.js +85 -5
- package/packages/agent-team/lib/ledger.js +80 -2
- package/packages/agent-team/lib/spec.js +12 -0
- package/packages/agent-team/lib/typert.host.js +94 -22
- package/packages/agent-team/lib/typert.remote-client.d.ts +3 -1
- package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
- package/packages/agent-team/lib/typert.remote-client.js +79 -22
- package/packages/agent-team/lib/types/auto-compaction.d.ts +9 -0
- package/packages/agent-team/lib/types/auto-compaction.d.ts.map +1 -1
- package/packages/agent-team/lib/types/index.d.ts +13 -1
- package/packages/agent-team/lib/types/index.d.ts.map +1 -1
- package/packages/agent-team/lib/types/ledger.d.ts +20 -0
- package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
- package/packages/agent-team/lib/types/spec.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types.d.ts +41 -1
- package/packages/agent-team/lib/types/types.d.ts.map +1 -1
- package/packages/client-agent-team/lib/client.js +486 -90
- package/packages/client-agent-team/lib/client.js.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts +2 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +41 -5
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.js +20 -0
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +3 -1
- package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamComposer.js +73 -17
- package/packages/client-agent-team/lib/types/client/TeamMemberSessionComposer.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamMemberSessionComposer.js +16 -1
- package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamMessage.js +48 -18
- package/packages/client-agent-team/lib/types/client/TeamRowMenu.d.ts +3 -2
- package/packages/client-agent-team/lib/types/client/TeamRowMenu.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamSidebarSection.d.ts +5 -2
- package/packages/client-agent-team/lib/types/client/TeamSidebarSection.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamSidebarSection.js +2 -4
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +21 -2
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts +1 -1
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.js +4 -2
- package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/index.js +15 -13
- package/packages/client-agent-team/lib/types/client/locales.d.ts +16 -0
- package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/locales.js +16 -0
- package/packages/client-agent-team/lib/types/client/sidebar-sections.d.ts +16 -0
- package/packages/client-agent-team/lib/types/client/sidebar-sections.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/sidebar-sections.js +102 -0
- package/packages/client-agent-team/lib/types/client/slots.d.ts +2 -1
- 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.map +1 -1
- package/packages/client-agent-team/lib/types/client/team-formatters.js +9 -2
package/README.zh.md
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
[English](README.md) | 简体中文
|
|
4
4
|
|
|
5
|
+
[](https://www.npmjs.com/package/@wowyuarm/dsh-agent-team)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](https://github.com/wowyuarm/dsh-agent-team/releases)
|
|
5
8
|
[](https://awesome-dsh-plugin.com/p/wowyuarm/dsh-agent-team/)
|
|
6
9
|
|
|
7
10
|
**dsh-agent-team** 帮助 human 有序管理任务、把 agents 用成真正的协作者:Agent 是 session 的持久身份;Workspace 按项目组织 agents 与 sessions;Channel 承载职责分派;Task Thread 把多个 session agent 串成一条推进线。
|
|
@@ -98,7 +101,7 @@ dsh web
|
|
|
98
101
|
|
|
99
102
|
## 开发
|
|
100
103
|
|
|
101
|
-
维护中的文档入口是 [`docs/README.md`](docs/README.md)。常用检查命令:
|
|
104
|
+
维护中的文档入口是 [`docs/README.zh.md`](docs/README.zh.md)。常用检查命令:
|
|
102
105
|
|
|
103
106
|
```sh
|
|
104
107
|
corepack pnpm install
|
|
@@ -112,7 +115,7 @@ npm pack --dry-run
|
|
|
112
115
|
|
|
113
116
|
`npm run test:browser` 使用相邻的 `../deepseek-harness` checkout、隔离临时 profile 和 `/usr/bin/google-chrome`(可用 `CHROME_PATH` 覆盖),不需要 provider credentials。手动检查时,`npm run preview:ui` 会加载不调用模型的 Team fixture;`DEEPSEEK_API_KEY=... npm run preview` 会启动真实 provider preview。两个 preview 命令都会在 `Ctrl+C` 后清理临时状态。
|
|
114
117
|
|
|
115
|
-
架构和协作协议见 [`docs/architecture.md`](docs/architecture.md) 与 [`docs/team-collaboration.md`](docs/team-collaboration.md)。
|
|
118
|
+
架构和协作协议见 [`docs/architecture.zh.md`](docs/architecture.zh.md) 与 [`docs/team-collaboration.zh.md`](docs/team-collaboration.zh.md)。
|
|
116
119
|
|
|
117
120
|
## 许可证
|
|
118
121
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import { ManualCompactionError } from '@deepseek-ai/dsh-compaction';
|
|
2
2
|
/** Strict threshold: 200K exactly does not compact. */
|
|
3
3
|
export const AUTO_COMPACTION_TOKEN_LIMIT = 200_000;
|
|
4
|
+
/**
|
|
5
|
+
* Suggested summary marker of the pre-compaction memory hint. Same shape as
|
|
6
|
+
* the inbox and recovery notices so future turns can recognize it.
|
|
7
|
+
*/
|
|
8
|
+
export const PRE_COMPACTION_NOTICE_SUMMARY = 'Compaction is imminent; consider persisting key conclusions.';
|
|
9
|
+
/** Suggested pre-compaction hint body; writing is the Agent's own call. */
|
|
10
|
+
export function preCompactionNoticeText() {
|
|
11
|
+
return 'This session is about to be compacted, which will summarize away the current conversation detail. If there are durable conclusions worth keeping — validated facts, decisions, or reusable details — consider writing them to your memory or notes now. This is only a reminder: decide yourself whether anything is worth persisting, and do nothing if not. Do not start new work because of this message.';
|
|
12
|
+
}
|
|
4
13
|
/** Claim owners of a durable Human acceptance, including early-completed ones. */
|
|
5
14
|
export function acceptedTaskCompactionMembers(operation) {
|
|
6
15
|
if (operation.kind !== 'team/task-changed' || operation.actor.kind !== 'human' || operation.data.activity.kind !== 'accept')
|
|
@@ -69,11 +78,40 @@ export class AutoCompactionCoordinator {
|
|
|
69
78
|
this.fail(memberId, agent, 'automatic compaction failed: tokenMeter is unavailable in the Member scope');
|
|
70
79
|
return;
|
|
71
80
|
}
|
|
72
|
-
|
|
81
|
+
let before = meter.measure(agent.session).totalTokens;
|
|
73
82
|
if (before <= AUTO_COMPACTION_TOKEN_LIMIT) {
|
|
74
83
|
this.complete(memberId, agent);
|
|
75
84
|
return;
|
|
76
85
|
}
|
|
86
|
+
// The session is over the threshold and about to be compacted: steer one
|
|
87
|
+
// advisory hint so the Agent can persist its own key conclusions first.
|
|
88
|
+
// The hint turn itself is ordinary agent work — its failure is contained
|
|
89
|
+
// by the agent loop and never blocks compaction; only the fresh idle
|
|
90
|
+
// boundary is awaited before the final measurement and compactNow.
|
|
91
|
+
if (this.options.steerPreCompaction !== undefined) {
|
|
92
|
+
try {
|
|
93
|
+
this.options.steerPreCompaction(agent);
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
this.options.log(`pre-compaction memory hint failed: ${describe(error)} (member ${memberId}, session ${agent.id})`);
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
if (!(await waitForIdleOrAbort(agent, this.controller.signal)))
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
this.options.log(`pre-compaction hint idle wait failed: ${describe(error)} (member ${memberId}, session ${agent.id})`);
|
|
104
|
+
}
|
|
105
|
+
if (this.controller.signal.aborted)
|
|
106
|
+
return;
|
|
107
|
+
if (this.options.agentForMember(memberId) !== agent)
|
|
108
|
+
continue;
|
|
109
|
+
before = meter.measure(agent.session).totalTokens;
|
|
110
|
+
if (before <= AUTO_COMPACTION_TOKEN_LIMIT) {
|
|
111
|
+
this.complete(memberId, agent);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
77
115
|
const engine = this.options.compactionForAgent(agent);
|
|
78
116
|
if (engine === undefined) {
|
|
79
117
|
// A roster-subtree teardown (bundle-row reload, config hot-apply)
|
|
@@ -52,7 +52,7 @@ import { SessionId } from '@deepseek-ai/dsh-session';
|
|
|
52
52
|
import { effectiveSandboxMode, setSandboxMode } from '@deepseek-ai/dsh-sandbox-policy';
|
|
53
53
|
import { Remote, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
54
54
|
import { ATTACHMENT_MAX_BYTES, attachmentPayloadPath, attachmentsRoot, copyPathAttachment, newAttachmentId, readAttachment, sanitizeMediaType, sweepAttachmentCache, validatePathAttachment, writeAttachment } from "./attachments.js";
|
|
55
|
-
import { acceptedTaskCompactionMembers, AutoCompactionCoordinator } from "./auto-compaction.js";
|
|
55
|
+
import { acceptedTaskCompactionMembers, AutoCompactionCoordinator, PRE_COMPACTION_NOTICE_SUMMARY, preCompactionNoticeText } from "./auto-compaction.js";
|
|
56
56
|
import { AGENT_TEAM_HUMAN_MEMBER_ID, AgentTeamLedger, agentTeamHumanActor } from "./ledger.js";
|
|
57
57
|
import { classifyRecoverableError, RecoveryCoordinator, RECOVERY_MAX_CONSECUTIVE_ERRORS } from "./recovery.js";
|
|
58
58
|
import { agentTeamDomainSpec } from "./spec.js";
|
|
@@ -96,6 +96,7 @@ let AgentTeam = (() => {
|
|
|
96
96
|
let _updateChannel_decorators;
|
|
97
97
|
let _addMember_decorators;
|
|
98
98
|
let _recoverMember_decorators;
|
|
99
|
+
let _clearMemberContext_decorators;
|
|
99
100
|
let _updateMember_decorators;
|
|
100
101
|
let _changeTask_decorators;
|
|
101
102
|
let _promoteThread_decorators;
|
|
@@ -121,6 +122,7 @@ let AgentTeam = (() => {
|
|
|
121
122
|
_updateChannel_decorators = [Remote('updateChannel')];
|
|
122
123
|
_addMember_decorators = [Remote('addMember')];
|
|
123
124
|
_recoverMember_decorators = [Remote('recoverMember')];
|
|
125
|
+
_clearMemberContext_decorators = [Remote('clearMemberContext')];
|
|
124
126
|
_updateMember_decorators = [Remote('updateMember')];
|
|
125
127
|
_changeTask_decorators = [Remote('changeTask')];
|
|
126
128
|
_promoteThread_decorators = [Remote('promoteThread')];
|
|
@@ -143,6 +145,7 @@ let AgentTeam = (() => {
|
|
|
143
145
|
__esDecorate(this, null, _updateChannel_decorators, { kind: "method", name: "updateChannel", static: false, private: false, access: { has: obj => "updateChannel" in obj, get: obj => obj.updateChannel }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
144
146
|
__esDecorate(this, null, _addMember_decorators, { kind: "method", name: "addMember", static: false, private: false, access: { has: obj => "addMember" in obj, get: obj => obj.addMember }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
145
147
|
__esDecorate(this, null, _recoverMember_decorators, { kind: "method", name: "recoverMember", static: false, private: false, access: { has: obj => "recoverMember" in obj, get: obj => obj.recoverMember }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
148
|
+
__esDecorate(this, null, _clearMemberContext_decorators, { kind: "method", name: "clearMemberContext", static: false, private: false, access: { has: obj => "clearMemberContext" in obj, get: obj => obj.clearMemberContext }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
146
149
|
__esDecorate(this, null, _updateMember_decorators, { kind: "method", name: "updateMember", static: false, private: false, access: { has: obj => "updateMember" in obj, get: obj => obj.updateMember }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
147
150
|
__esDecorate(this, null, _changeTask_decorators, { kind: "method", name: "changeTask", static: false, private: false, access: { has: obj => "changeTask" in obj, get: obj => obj.changeTask }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
148
151
|
__esDecorate(this, null, _promoteThread_decorators, { kind: "method", name: "promoteThread", static: false, private: false, access: { has: obj => "promoteThread" in obj, get: obj => obj.promoteThread }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
@@ -216,6 +219,14 @@ let AgentTeam = (() => {
|
|
|
216
219
|
this.emitAutoCompactionChanged(memberId);
|
|
217
220
|
},
|
|
218
221
|
log: message => { this.ctx.logger.warn(`agent-team: ${message}`); },
|
|
222
|
+
// Advisory only: the Agent decides whether anything is worth persisting.
|
|
223
|
+
steerPreCompaction: agent => {
|
|
224
|
+
const hint = createUserMessage({
|
|
225
|
+
content: [{ type: 'text', text: preCompactionNoticeText() }],
|
|
226
|
+
source: { kind: 'plugin', plugin: AGENT_TEAM_PLUGIN_ID, form: 'notice', summary: PRE_COMPACTION_NOTICE_SUMMARY },
|
|
227
|
+
});
|
|
228
|
+
agent.steer(hint);
|
|
229
|
+
},
|
|
219
230
|
});
|
|
220
231
|
}
|
|
221
232
|
/** Open the durable ledger and restore every enabled Member independently. */
|
|
@@ -246,8 +257,15 @@ let AgentTeam = (() => {
|
|
|
246
257
|
this.notifyMember(agent);
|
|
247
258
|
}
|
|
248
259
|
// A turn that ends without an error closes any automatic recovery episode.
|
|
249
|
-
|
|
250
|
-
|
|
260
|
+
// The idle transition is itself presence-affecting (working → available),
|
|
261
|
+
// so it must wake workspace watchers exactly like the running transition
|
|
262
|
+
// above; without this wake, cached Client member rows keep showing the
|
|
263
|
+
// Member as working after every turn until an unrelated change arrives.
|
|
264
|
+
if (status === 'idle' && member !== undefined) {
|
|
265
|
+
if (this.memberFailures.get(member.memberId)?.runtime === undefined) {
|
|
266
|
+
this.recovery.onCleanTurnEnd(member.memberId);
|
|
267
|
+
}
|
|
268
|
+
this.emitChanged([{ kind: 'workspace', workspaceId: member.workspaceId }]);
|
|
251
269
|
}
|
|
252
270
|
});
|
|
253
271
|
const domain = await this.ctx.storageDomain.open(agentTeamDomainSpec);
|
|
@@ -463,6 +481,66 @@ let AgentTeam = (() => {
|
|
|
463
481
|
this.steerResume(member, this.manualResumeText());
|
|
464
482
|
return Object.freeze({ status: this.memberStatus(member) });
|
|
465
483
|
}
|
|
484
|
+
/**
|
|
485
|
+
* Start one enabled Member from a new context: dispose the live handle,
|
|
486
|
+
* archive the previous Session (its log stays on disk for history), and
|
|
487
|
+
* activate a fresh Session under a new sessionId, so preset, tools, private
|
|
488
|
+
* memory, and model selection all reload while the next turn carries no
|
|
489
|
+
* history. The durable operation moves the Member's sessionId; identity,
|
|
490
|
+
* memory path, and binding survive. A new id is what keeps the Web Client
|
|
491
|
+
* seat live: a disposed generation's resident instance keeps its `removed`
|
|
492
|
+
* bit forever, so renewing under the same id would leave a permanently
|
|
493
|
+
* grayed session view.
|
|
494
|
+
*/
|
|
495
|
+
async clearMemberContext(request) {
|
|
496
|
+
return this.enqueueLifecycle(async () => {
|
|
497
|
+
this.requireAccepting();
|
|
498
|
+
this.requireWorkspace(request.workspaceId);
|
|
499
|
+
const stored = this.requireLedger().getMember(request.memberId);
|
|
500
|
+
if (stored === undefined || stored.workspaceId !== request.workspaceId)
|
|
501
|
+
throw new Error(`unknown Member '${request.memberId}' in workspace '${request.workspaceId}'`);
|
|
502
|
+
if (stored.state !== 'enabled')
|
|
503
|
+
throw new Error(`Agent Member '${stored.handle}' is ${stored.state}; only enabled Members can start from a new context`);
|
|
504
|
+
const active = this.handles.get(request.memberId);
|
|
505
|
+
if (active === undefined)
|
|
506
|
+
throw new Error(`Agent Member '${stored.handle}' has no active session to clear`);
|
|
507
|
+
if (this.runningAgents.has(active.agent.id))
|
|
508
|
+
throw new Error(`Agent Member '${stored.handle}' is still running; wait for the current turn to end before starting from a new context`);
|
|
509
|
+
const previousSessionId = stored.sessionId;
|
|
510
|
+
// The fresh id derives from the requestId, so a retried identical
|
|
511
|
+
// request mints the same id and the ledger dedupes it instead of
|
|
512
|
+
// colliding; the format matches addMember's `agent-team-<uuid>`.
|
|
513
|
+
const sessionId = SessionId(`agent-team-${request.requestId}`);
|
|
514
|
+
const result = await this.requireLedger().renewMemberSession({ ...request, sessionId, actor: agentTeamHumanActor() });
|
|
515
|
+
if (result.committed)
|
|
516
|
+
this.emitCommitted(result.value.receipt);
|
|
517
|
+
else {
|
|
518
|
+
// A retried identical request already renewed this Member; report the
|
|
519
|
+
// recorded outcome without another dispose/reactivate cycle.
|
|
520
|
+
return Object.freeze({ receipt: result.value.receipt, status: this.memberStatus(result.value.member) });
|
|
521
|
+
}
|
|
522
|
+
const renewed = result.value.member;
|
|
523
|
+
// Drop the old handle's transient state: pending recovery episodes and
|
|
524
|
+
// error markers belong to the disposed agent, not to the Member.
|
|
525
|
+
this.recovery.stopTracking(request.memberId);
|
|
526
|
+
await active.dispose();
|
|
527
|
+
this.handles.delete(request.memberId);
|
|
528
|
+
this.modelSelections.delete(request.memberId);
|
|
529
|
+
this.clearMemberFailure(request.memberId, 'activation');
|
|
530
|
+
this.clearMemberNotificationState(request.memberId);
|
|
531
|
+
// The previous log survives on disk; archiving hides it from every
|
|
532
|
+
// grouping surface so one Member keeps exactly one visible Session.
|
|
533
|
+
await this.ctx.workspaceRegistry.archiveSession(previousSessionId);
|
|
534
|
+
await this.activateMember(renewed, undefined, undefined, previousSessionId);
|
|
535
|
+
const reactivated = this.handles.get(request.memberId);
|
|
536
|
+
if (reactivated === undefined) {
|
|
537
|
+
// Reactivation failed; the activation diagnostic carries the reason and
|
|
538
|
+
// the durable renewal stays honest about the attempt.
|
|
539
|
+
throw new Error(`Agent Member '${stored.handle}' failed to start a new context: ${this.memberFailures.get(request.memberId)?.activation ?? 'unknown error'}`);
|
|
540
|
+
}
|
|
541
|
+
return Object.freeze({ receipt: result.value.receipt, status: this.memberStatus(renewed) });
|
|
542
|
+
});
|
|
543
|
+
}
|
|
466
544
|
/** Ledger handle for log lines; falls back to the raw id when unknown. */
|
|
467
545
|
memberLabel(memberId) {
|
|
468
546
|
return this.ledger?.getMember(memberId)?.handle ?? memberId;
|
|
@@ -831,7 +909,7 @@ let AgentTeam = (() => {
|
|
|
831
909
|
throw error;
|
|
832
910
|
}
|
|
833
911
|
}
|
|
834
|
-
async activateMember(member, knownWorkspacePath, knownSessions) {
|
|
912
|
+
async activateMember(member, knownWorkspacePath, knownSessions, forkedFrom) {
|
|
835
913
|
if (this.handles.has(member.memberId))
|
|
836
914
|
return;
|
|
837
915
|
let created;
|
|
@@ -865,7 +943,9 @@ let AgentTeam = (() => {
|
|
|
865
943
|
? await this.ctx.agents.resume({ resumeSessionId: member.sessionId, agentOptions, setup })
|
|
866
944
|
: await this.ctx.agents.create({
|
|
867
945
|
sessionId: member.sessionId,
|
|
868
|
-
|
|
946
|
+
// A context renewal records its fork lineage so the archived
|
|
947
|
+
// previous Session stays discoverable from the durable header.
|
|
948
|
+
meta: { cwd: workspacePath, agentPreset: member.presetId, ...(forkedFrom === undefined ? {} : { parentSession: forkedFrom }) },
|
|
869
949
|
agentOptions,
|
|
870
950
|
setup,
|
|
871
951
|
});
|
|
@@ -195,6 +195,35 @@ export class AgentTeamLedger {
|
|
|
195
195
|
resumeMember(request) {
|
|
196
196
|
return this.setMemberState(request, 'enabled');
|
|
197
197
|
}
|
|
198
|
+
/**
|
|
199
|
+
* Move one enabled Member onto a fresh Session: the durable operation
|
|
200
|
+
* records the sessionId transition while identity, memory, and binding
|
|
201
|
+
* survive. The Host disposes the old handle and archives the previous
|
|
202
|
+
* Session log around this write, so the next turn starts empty.
|
|
203
|
+
*/
|
|
204
|
+
renewMemberSession(request) {
|
|
205
|
+
return this.enqueue(async () => {
|
|
206
|
+
const existing = this.state.byRequest.get(request.requestId);
|
|
207
|
+
if (existing !== undefined) {
|
|
208
|
+
this.assertSameMemberSessionRenewed(existing, request);
|
|
209
|
+
return this.resolved(this.memberResult(existing));
|
|
210
|
+
}
|
|
211
|
+
this.assertHumanActor(request.actor);
|
|
212
|
+
const prior = this.requireMember(request.memberId);
|
|
213
|
+
if (prior.state !== 'enabled')
|
|
214
|
+
throw new Error(`Agent Member '${prior.handle}' is ${prior.state}; only enabled Members can start from a new context`);
|
|
215
|
+
if (prior.sessionId === request.sessionId)
|
|
216
|
+
throw new Error(`Agent Member '${prior.handle}' already runs Session '${request.sessionId}'`);
|
|
217
|
+
const member = Object.freeze({ ...prior, sessionId: request.sessionId });
|
|
218
|
+
const operation = Object.freeze({
|
|
219
|
+
...this.operationBase(request, this.nextSequence()), kind: 'team/member-session-renewed',
|
|
220
|
+
data: Object.freeze({ member, previousSessionId: prior.sessionId }),
|
|
221
|
+
});
|
|
222
|
+
await this.table.put(operation.operationId, operation);
|
|
223
|
+
this.apply(operation);
|
|
224
|
+
return this.committed(this.memberResult(operation));
|
|
225
|
+
});
|
|
226
|
+
}
|
|
198
227
|
getMember(memberId) {
|
|
199
228
|
return this.state.members.get(memberId);
|
|
200
229
|
}
|
|
@@ -481,8 +510,10 @@ export class AgentTeamLedger {
|
|
|
481
510
|
// Acceptance normally waits for every Claim to finish (in_review);
|
|
482
511
|
// a Human may also accept early while work is in progress, which then
|
|
483
512
|
// completes the still-active Claims inside the same atomic operation.
|
|
484
|
-
|
|
485
|
-
|
|
513
|
+
// A never-claimed todo Task may be accepted directly as well: work
|
|
514
|
+
// finished outside the ledger is declared complete by the Human.
|
|
515
|
+
if (request.action === 'accept' && task.status !== 'in_review' && task.status !== 'in_progress' && task.status !== 'todo') {
|
|
516
|
+
throw new Error(`Task '${task.taskRef}' must be in_review, in_progress, or todo before acceptance`);
|
|
486
517
|
}
|
|
487
518
|
const priorActiveClaims = [...this.state.claims.values()].filter(claim => claim.taskRef === task.taskRef && claim.state === 'active');
|
|
488
519
|
if (request.action === 'accept' && task.status === 'in_progress' && priorActiveClaims.length === 0) {
|
|
@@ -860,6 +891,8 @@ export class AgentTeamLedger {
|
|
|
860
891
|
case 'team/member-suspended':
|
|
861
892
|
case 'team/member-resumed':
|
|
862
893
|
case 'team/member-session-restarted':
|
|
894
|
+
case 'team/member-context-cleared':
|
|
895
|
+
case 'team/member-session-renewed':
|
|
863
896
|
case 'team/member-updated':
|
|
864
897
|
case 'team/member-removed':
|
|
865
898
|
return [{ kind: 'workspace', workspaceId: operation.data.member.workspaceId }];
|
|
@@ -949,6 +982,8 @@ export class AgentTeamLedger {
|
|
|
949
982
|
case 'team/member-suspended':
|
|
950
983
|
case 'team/member-resumed':
|
|
951
984
|
case 'team/member-session-restarted':
|
|
985
|
+
case 'team/member-context-cleared':
|
|
986
|
+
case 'team/member-session-renewed':
|
|
952
987
|
case 'team/member-updated':
|
|
953
988
|
case 'team/channel-member-added':
|
|
954
989
|
case 'team/thread-read':
|
|
@@ -1058,6 +1093,28 @@ export class AgentTeamLedger {
|
|
|
1058
1093
|
throw new Error('invalid Member restart');
|
|
1059
1094
|
return;
|
|
1060
1095
|
}
|
|
1096
|
+
if (operation.kind === 'team/member-context-cleared') {
|
|
1097
|
+
assertHuman();
|
|
1098
|
+
const prior = projection.members.get(operation.data.member.memberId);
|
|
1099
|
+
// Legacy in-place clear: the recorded Member is unchanged, and the
|
|
1100
|
+
// projection deliberately does not change either.
|
|
1101
|
+
if (prior === undefined || prior.state !== 'enabled' || !this.sameMemberIdentity(prior, operation.data.member))
|
|
1102
|
+
throw new Error('invalid Member context clear');
|
|
1103
|
+
return;
|
|
1104
|
+
}
|
|
1105
|
+
if (operation.kind === 'team/member-session-renewed') {
|
|
1106
|
+
assertHuman();
|
|
1107
|
+
const prior = projection.members.get(operation.data.member.memberId);
|
|
1108
|
+
// The renewal moves exactly the sessionId: the prior Member must be
|
|
1109
|
+
// enabled on the recorded previous Session, and every other fact must
|
|
1110
|
+
// carry over unchanged.
|
|
1111
|
+
if (prior === undefined || prior.state !== 'enabled'
|
|
1112
|
+
|| prior.sessionId !== operation.data.previousSessionId
|
|
1113
|
+
|| operation.data.member.sessionId === operation.data.previousSessionId
|
|
1114
|
+
|| !this.sameMemberFacts(prior, operation.data.member))
|
|
1115
|
+
throw new Error('invalid Member session renewal');
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1061
1118
|
if (operation.kind === 'team/channel-updated') {
|
|
1062
1119
|
assertHuman();
|
|
1063
1120
|
const prior = projection.channels.get(operation.data.channel.channelRef);
|
|
@@ -1486,6 +1543,15 @@ export class AgentTeamLedger {
|
|
|
1486
1543
|
// projection deliberately does not change.
|
|
1487
1544
|
return;
|
|
1488
1545
|
}
|
|
1546
|
+
if (operation.kind === 'team/member-context-cleared') {
|
|
1547
|
+
// Legacy audit-only marker: identity, memory path, and binding were
|
|
1548
|
+
// untouched; the Host recreated the live Session empty under the same id.
|
|
1549
|
+
return;
|
|
1550
|
+
}
|
|
1551
|
+
if (operation.kind === 'team/member-session-renewed') {
|
|
1552
|
+
target.members.set(operation.data.member.memberId, operation.data.member);
|
|
1553
|
+
return;
|
|
1554
|
+
}
|
|
1489
1555
|
if (operation.kind === 'team/channel-updated') {
|
|
1490
1556
|
target.channels.set(operation.data.channel.channelRef, operation.data.channel);
|
|
1491
1557
|
return;
|
|
@@ -2176,6 +2242,11 @@ export class AgentTeamLedger {
|
|
|
2176
2242
|
if (operation.kind !== kind || !this.sameActor(operation.actor, request.actor) || operation.data.member.memberId !== request.memberId)
|
|
2177
2243
|
this.throwRequestCollision(request.requestId);
|
|
2178
2244
|
}
|
|
2245
|
+
assertSameMemberSessionRenewed(operation, request) {
|
|
2246
|
+
if (operation.kind !== 'team/member-session-renewed' || !this.sameActor(operation.actor, request.actor)
|
|
2247
|
+
|| operation.data.member.memberId !== request.memberId || operation.data.member.sessionId !== request.sessionId)
|
|
2248
|
+
this.throwRequestCollision(request.requestId);
|
|
2249
|
+
}
|
|
2179
2250
|
assertSameChannelUpdate(operation, request) {
|
|
2180
2251
|
if (operation.kind !== 'team/channel-updated' || !this.sameActor(operation.actor, request.actor)
|
|
2181
2252
|
|| operation.data.workspaceId !== request.workspaceId || operation.data.channel.channelRef !== request.channelRef
|
|
@@ -2282,6 +2353,13 @@ export class AgentTeamLedger {
|
|
|
2282
2353
|
&& isDeepStrictEqual(left.model ?? undefined, right.model ?? undefined)
|
|
2283
2354
|
&& left.privateMemoryPath === right.privateMemoryPath;
|
|
2284
2355
|
}
|
|
2356
|
+
/** sameMemberIdentity minus the sessionId: every durable Member fact a session renewal must carry over unchanged. */
|
|
2357
|
+
sameMemberFacts(left, right) {
|
|
2358
|
+
return left.memberId === right.memberId && left.workspaceId === right.workspaceId
|
|
2359
|
+
&& left.handle === right.handle && left.description === right.description && left.presetId === right.presetId
|
|
2360
|
+
&& isDeepStrictEqual(left.model ?? undefined, right.model ?? undefined)
|
|
2361
|
+
&& left.privateMemoryPath === right.privateMemoryPath;
|
|
2362
|
+
}
|
|
2285
2363
|
sameActor(left, right) {
|
|
2286
2364
|
return left.kind === right.kind && left.memberId === right.memberId && left.handle === right.handle;
|
|
2287
2365
|
}
|
|
@@ -237,6 +237,18 @@ const storedAgentTeamOperationSchema = z.discriminatedUnion('kind', [
|
|
|
237
237
|
kind: z.literal('team/member-session-restarted'),
|
|
238
238
|
data: z.object({ member: memberSchema }).strict(),
|
|
239
239
|
}).strict(),
|
|
240
|
+
z.object({
|
|
241
|
+
...operationBase,
|
|
242
|
+
previousOperationId: operationIdSchema.nullable(),
|
|
243
|
+
kind: z.literal('team/member-context-cleared'),
|
|
244
|
+
data: z.object({ member: memberSchema }).strict(),
|
|
245
|
+
}).strict(),
|
|
246
|
+
z.object({
|
|
247
|
+
...operationBase,
|
|
248
|
+
previousOperationId: operationIdSchema.nullable(),
|
|
249
|
+
kind: z.literal('team/member-session-renewed'),
|
|
250
|
+
data: z.object({ member: memberSchema, previousSessionId: sessionIdSchema }).strict(),
|
|
251
|
+
}).strict(),
|
|
240
252
|
z.object({
|
|
241
253
|
...operationBase,
|
|
242
254
|
previousOperationId: operationIdSchema.nullable(),
|