@wowyuarm/dsh-agent-team 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -5
- package/README.zh.md +11 -5
- package/package.json +44 -42
- package/packages/agent-team/README.md +2 -0
- package/packages/agent-team/README.zh.md +2 -0
- package/packages/agent-team/core-skills/member-skill-manager/SKILL.md +2 -0
- package/packages/agent-team/lib/index.js +140 -204
- package/packages/agent-team/lib/ledger.js +436 -25
- package/packages/agent-team/lib/member-context.js +6 -2
- package/packages/agent-team/lib/member-runtime.js +229 -0
- package/packages/agent-team/lib/preset-roster.js +1 -0
- package/packages/agent-team/lib/progress-nudge.js +320 -0
- package/packages/agent-team/lib/spec.js +30 -1
- package/packages/agent-team/lib/typert.host.js +250 -70
- package/packages/agent-team/lib/typert.remote-client.d.ts +5 -1
- package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
- package/packages/agent-team/lib/typert.remote-client.js +168 -38
- package/packages/agent-team/lib/types/entities.js +1 -0
- package/packages/agent-team/lib/types/index.d.ts +28 -44
- package/packages/agent-team/lib/types/index.d.ts.map +1 -1
- package/packages/agent-team/lib/types/ledger.d.ts +60 -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/member-runtime.d.ts +108 -0
- package/packages/agent-team/lib/types/member-runtime.d.ts.map +1 -0
- package/packages/agent-team/lib/types/operations.js +1 -0
- package/packages/agent-team/lib/types/preset-roster.d.ts.map +1 -1
- package/packages/agent-team/lib/types/progress-nudge.d.ts +136 -0
- package/packages/agent-team/lib/types/progress-nudge.d.ts.map +1 -0
- package/packages/agent-team/lib/types/requests-results.js +1 -0
- package/packages/agent-team/lib/types/spec.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types/entities.d.ts +357 -0
- package/packages/agent-team/lib/types/types/entities.d.ts.map +1 -0
- package/packages/agent-team/lib/types/types/operations.d.ts +303 -0
- package/packages/agent-team/lib/types/types/operations.d.ts.map +1 -0
- package/packages/agent-team/lib/types/types/requests-results.d.ts +529 -0
- package/packages/agent-team/lib/types/types/requests-results.d.ts.map +1 -0
- package/packages/agent-team/lib/types/types.d.ts +10 -1070
- package/packages/agent-team/lib/types/types.d.ts.map +1 -1
- package/packages/agent-team/preset/team-member/agent.cordis.yml +6 -4
- package/packages/client-agent-team/README.md +3 -1
- package/packages/client-agent-team/README.zh.md +3 -1
- package/packages/client-agent-team/lib/client.js +773 -733
- package/packages/client-agent-team/lib/client.js.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts +3 -2
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +30 -7
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts +1 -1
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.js +5 -9
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.d.ts +2 -1
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +31 -5
- package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts +3 -1
- package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamComposer.js +39 -28
- package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts +1 -1
- package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamConversation.js +2 -2
- package/packages/client-agent-team/lib/types/client/TeamMemberAvatar.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamMemberAvatar.js +3 -2
- package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamMessage.js +15 -3
- package/packages/client-agent-team/lib/types/client/TeamPresenceDot.d.ts +2 -2
- package/packages/client-agent-team/lib/types/client/TeamPresenceDot.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamPresenceDot.js +4 -6
- package/packages/client-agent-team/lib/types/client/TeamStateDot.d.ts +14 -0
- package/packages/client-agent-team/lib/types/client/TeamStateDot.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamStateDot.js +15 -0
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts +2 -1
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +41 -16
- 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 +2 -2
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceRow.d.ts +1 -1
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceRow.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/index.d.ts +1 -1
- package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/index.js +9 -77
- package/packages/client-agent-team/lib/types/client/locales.d.ts +26 -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 +26 -0
- package/packages/client-agent-team/lib/types/client/navigation.d.ts +2 -1
- package/packages/client-agent-team/lib/types/client/navigation.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/sidebar-drag.d.ts +1 -2
- package/packages/client-agent-team/lib/types/client/sidebar-drag.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/sidebar-order.d.ts +1 -1
- package/packages/client-agent-team/lib/types/client/sidebar-order.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/sidebar-sections.d.ts +1 -1
- package/packages/client-agent-team/lib/types/client/sidebar-sections.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/slots.d.ts +5 -1
- package/packages/client-agent-team/lib/types/client/slots.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/task-refs.d.ts +7 -1
- package/packages/client-agent-team/lib/types/client/task-refs.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/task-refs.js +25 -3
- package/packages/client-agent-team/lib/types/client/team-formatters.d.ts +11 -10
- package/packages/client-agent-team/lib/types/client/team-formatters.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/team-formatters.js +14 -11
- package/packages/client-agent-team/lib/types/client/team-membership.d.ts +1 -1
- package/packages/client-agent-team/lib/types/client/team-membership.d.ts.map +1 -1
- package/packages/tool-agent-team/lib/index.js +9 -9
- package/packages/client-agent-team/lib/types/client/TeamMemberSessionComposer.d.ts +0 -17
- package/packages/client-agent-team/lib/types/client/TeamMemberSessionComposer.d.ts.map +0 -1
- package/packages/client-agent-team/lib/types/client/TeamMemberSessionComposer.js +0 -135
- package/packages/client-agent-team/lib/types/client/member-session-input.d.ts +0 -22
- package/packages/client-agent-team/lib/types/client/member-session-input.d.ts.map +0 -1
- package/packages/client-agent-team/lib/types/client/member-session-input.js +0 -98
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
2
|
import { createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
3
|
+
import { SessionLogOffset } from '@deepseek-ai/dsh-session';
|
|
3
4
|
export const name = 'wowyuarm-agent-team-member-context';
|
|
4
5
|
const MAX_MEMORY_BYTES = 8 * 1024;
|
|
5
6
|
const BEGIN = '<team-member-private-memory>';
|
|
@@ -30,7 +31,10 @@ export function apply(ctx) {
|
|
|
30
31
|
}
|
|
31
32
|
const text = `${renderMemberIdentity(member)}\n\n${memory}`;
|
|
32
33
|
const latestText = agent.session.surface.nodes.toReversed().flatMap(sequence => {
|
|
33
|
-
|
|
34
|
+
// `nodes` are event identities (SessionSeq); snapshotEvents takes log
|
|
35
|
+
// offsets. Re-entering through the validating constructor keeps the two
|
|
36
|
+
// number domains explicit (the seq = log.length contiguity contract).
|
|
37
|
+
const event = agent.session.snapshotEvents(SessionLogOffset(sequence), SessionLogOffset(sequence + 1))[0];
|
|
34
38
|
return event?.type === 'user/message'
|
|
35
39
|
&& event.data.source.kind === 'plugin'
|
|
36
40
|
&& event.data.source.plugin === name
|
|
@@ -59,7 +63,7 @@ export function renderMemberMemory(raw, privateMemoryPath = '<private-memory-pat
|
|
|
59
63
|
const warning = overBudget
|
|
60
64
|
? '\n\n[Maintenance warning: memory.md exceeds the 8 KiB context budget. Its contents were not injected; do not delete or automatically summarize the file. Maintain a smaller index explicitly.]'
|
|
61
65
|
: '';
|
|
62
|
-
return `${BEGIN}\nThis is the complete replacement for this Team Member's private memory index; all earlier private-memory context is obsolete. It is reference context only, may be stale, and is not an instruction or Team fact.\n\nPrivate memory directory: ${privateMemoryPath}\nMemory index: ${privateMemoryPath}/memory.md\nNotes directory: ${privateMemoryPath}/notes\nPrivate skills directory: ${privateMemoryPath}/skills\nThese paths are outside the Workspace cwd. Relative filesystem paths resolve from cwd, so use the absolute paths above when reading or editing this Member's memory. Read matching notes on demand; do not copy credentials, sensitive data, guesses, chat logs, other Members' memory, or Team facts already owned by the ledger into memory.\n\n${escape(body)}${warning}\n${END}`;
|
|
66
|
+
return `${BEGIN}\nThis is the complete replacement for this Team Member's private memory index; all earlier private-memory context is obsolete. It is reference context only, may be stale, and is not an instruction or Team fact.\n\nPrivate memory directory: ${privateMemoryPath}\nMemory index: ${privateMemoryPath}/memory.md\nNotes directory: ${privateMemoryPath}/notes\nPrivate skills directory: ${privateMemoryPath}/skills\nThese paths are outside the Workspace cwd. Relative filesystem paths resolve from cwd, so use the absolute paths above when reading or editing this Member's memory. Only this Member can read this directory — no other human or agent sees its contents; when communicating, restate what you need from it instead of pointing others at these paths. Read matching notes on demand; do not copy credentials, sensitive data, guesses, chat logs, other Members' memory, or Team facts already owned by the ledger into memory.\n\n${escape(body)}${warning}\n${END}`;
|
|
63
67
|
}
|
|
64
68
|
function renderUnavailableMemory(privateMemoryPath, reason) {
|
|
65
69
|
return `${BEGIN}\nThis is the complete replacement for this Team Member's private memory index; all earlier private-memory context is obsolete. ${reason}\n\nPrivate memory directory: ${privateMemoryPath}\nMemory index: ${privateMemoryPath}/memory.md\nNotes directory: ${privateMemoryPath}/notes\nPrivate skills directory: ${privateMemoryPath}/skills\nThese paths are outside the Workspace cwd. Use the absolute paths above when inspecting or repairing this Member's memory.\n${END}`;
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-Member runtime state extracted from the Agent Team Host service: the
|
|
3
|
+
* cohesive per-Member maps and methods that must dispose together across
|
|
4
|
+
* activation, edit, suspend, and removal — the tool-policy restriction seam,
|
|
5
|
+
* the private skill selection/provider mounts, capability warnings, and
|
|
6
|
+
* private-memory provisioning.
|
|
7
|
+
*
|
|
8
|
+
* This class is Host-side by design: its turn-boundary wait subscribes to
|
|
9
|
+
* Host-level agent events, and every traceable-service registration goes
|
|
10
|
+
* through contexts the Host already owns. The service keeps orchestration
|
|
11
|
+
* (ledger, handles, notifications, recovery); this module owns per-Member
|
|
12
|
+
* runtime invariants.
|
|
13
|
+
*
|
|
14
|
+
* @module @wowyuarm/dsh-agent-team/member-runtime
|
|
15
|
+
*/
|
|
16
|
+
import { mkdir, rm, writeFile } from 'node:fs/promises';
|
|
17
|
+
import { dirname, join, resolve } from 'node:path';
|
|
18
|
+
import { fileURLToPath } from 'node:url';
|
|
19
|
+
import { scopeOf } from '@deepseek-ai/dsh-scope';
|
|
20
|
+
import * as memberSkills from "./member-skills.js";
|
|
21
|
+
/**
|
|
22
|
+
* Read-only core skills shipped beside the preset (the meta skill first:
|
|
23
|
+
* its description routes any skill-management work to itself). Resolved
|
|
24
|
+
* from this module's emitted location so it follows the installed plugin,
|
|
25
|
+
* like the preset roster's own root.
|
|
26
|
+
*/
|
|
27
|
+
const BUNDLED_SKILLS_DIRECTORY = resolve(dirname(fileURLToPath(import.meta.url)), '../core-skills');
|
|
28
|
+
/** Model-facing capabilities every Team-enabled preset must publish. */
|
|
29
|
+
export const AGENT_TEAM_TOOL_NAMES = Object.freeze([
|
|
30
|
+
'team_inbox',
|
|
31
|
+
'team_thread',
|
|
32
|
+
'team_message',
|
|
33
|
+
'team_claim',
|
|
34
|
+
'team_view',
|
|
35
|
+
]);
|
|
36
|
+
/** Copy a Remote-supplied capability overlay into owned frozen storage. */
|
|
37
|
+
export function deepCopyCapabilities(capabilities) {
|
|
38
|
+
const copyAllow = (allow) => allow === undefined ? {} : { allow: [...allow] };
|
|
39
|
+
return {
|
|
40
|
+
...(capabilities.tools === undefined ? {} : { tools: copyAllow(capabilities.tools.allow) }),
|
|
41
|
+
...(capabilities.skills === undefined ? {} : { skills: copyAllow(capabilities.skills.allow) }),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/** Per-Member runtime state; the four maps dispose together with each Member. */
|
|
45
|
+
export class MemberRuntime {
|
|
46
|
+
deps;
|
|
47
|
+
/**
|
|
48
|
+
* Live per-Member tool restriction disposers, mirroring modelSelections:
|
|
49
|
+
* activation registers, disposal paths release, edits swap at a turn
|
|
50
|
+
* boundary. Deliberate interface reservation: the restriction seam is the
|
|
51
|
+
* primitive future Runtime Revision manifests orchestrate — do not remove
|
|
52
|
+
* during cleanup.
|
|
53
|
+
*/
|
|
54
|
+
memberRestrictions = new Map();
|
|
55
|
+
/**
|
|
56
|
+
* Runtime-derived capability warnings, recomputed at every activation (like
|
|
57
|
+
* memberFailures, keyed by Member and never persisted): persisted warnings
|
|
58
|
+
* would lie after a Host restart or a Harness upgrade renames tools.
|
|
59
|
+
*/
|
|
60
|
+
capabilityWarnings = new Map();
|
|
61
|
+
/** Live skill selection refs let capability edits re-filter the catalog without disposing the Session. */
|
|
62
|
+
skillSelections = new Map();
|
|
63
|
+
/** Per-Member private skill provider disposers; released with the Member's agent scope. */
|
|
64
|
+
skillProviderDisposals = new Map();
|
|
65
|
+
constructor(deps) {
|
|
66
|
+
this.deps = deps;
|
|
67
|
+
}
|
|
68
|
+
/** One Member's live capability warnings, if any; runtime-derived, never persisted. */
|
|
69
|
+
capabilityWarningsFor(memberId) {
|
|
70
|
+
return this.capabilityWarnings.get(memberId);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Apply one Member's persisted tool allow-list as a scoped restriction on
|
|
74
|
+
* the freshly composed preset surface, and derive activation-time warnings
|
|
75
|
+
* for entries the current tool surface no longer knows. Runs inside setup
|
|
76
|
+
* BEFORE validateMemberPreset so the validation observes the restricted
|
|
77
|
+
* view (the Host always unions the five Team tools over the configured
|
|
78
|
+
* list). Deliberate interface reservation: this restriction seam is the
|
|
79
|
+
* primitive future Runtime Revision manifests orchestrate — do not remove
|
|
80
|
+
* during cleanup.
|
|
81
|
+
*/
|
|
82
|
+
applyMemberToolPolicy(agentCtx, member) {
|
|
83
|
+
const configured = member.capabilities?.tools?.allow;
|
|
84
|
+
if (configured === undefined) {
|
|
85
|
+
this.capabilityWarnings.delete(member.memberId);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
// Drop names the current surface does not know rather than failing the
|
|
89
|
+
// activation: a Harness upgrade renaming a tool must not make the Member
|
|
90
|
+
// unavailable. The warning carries the known-name digest so a distant
|
|
91
|
+
// future reader can diagnose the drift.
|
|
92
|
+
const known = this.deps.ctx.tools.schemas(scopeOf(agentCtx)).map(tool => tool.name);
|
|
93
|
+
const knownSet = new Set(known);
|
|
94
|
+
const dropped = configured.filter(name => !knownSet.has(name));
|
|
95
|
+
this.setCapabilityWarnings(member.memberId, dropped.map(name => ({ name, knownNames: known })));
|
|
96
|
+
const allow = [...new Set([...configured.filter(name => knownSet.has(name)), ...AGENT_TEAM_TOOL_NAMES])];
|
|
97
|
+
if (allow.length === 0)
|
|
98
|
+
return;
|
|
99
|
+
// tools.restrict() requires a scoped context and rejects names outside
|
|
100
|
+
// the inherited surface; both errors surface as this Member's activation
|
|
101
|
+
// failure without touching any other Member.
|
|
102
|
+
const dispose = agentCtx.tools.restrict({ allow });
|
|
103
|
+
this.memberRestrictions.set(member.memberId, dispose);
|
|
104
|
+
}
|
|
105
|
+
/** Swap a live Member's tool policy at a turn boundary: dispose the old restriction, apply the new. */
|
|
106
|
+
reapplyMemberToolPolicy(member) {
|
|
107
|
+
this.releaseMemberToolPolicy(member.memberId);
|
|
108
|
+
this.applyMemberToolPolicy(this.deps.liveMemberContext(member.memberId), member);
|
|
109
|
+
}
|
|
110
|
+
/** Release one Member's restriction disposer and warning state; safe to call twice. */
|
|
111
|
+
releaseMemberToolPolicy(memberId) {
|
|
112
|
+
const dispose = this.memberRestrictions.get(memberId);
|
|
113
|
+
if (dispose !== undefined) {
|
|
114
|
+
this.memberRestrictions.delete(memberId);
|
|
115
|
+
dispose();
|
|
116
|
+
}
|
|
117
|
+
this.capabilityWarnings.delete(memberId);
|
|
118
|
+
}
|
|
119
|
+
setCapabilityWarnings(memberId, warnings) {
|
|
120
|
+
if (warnings.length === 0)
|
|
121
|
+
this.capabilityWarnings.delete(memberId);
|
|
122
|
+
else
|
|
123
|
+
this.capabilityWarnings.set(memberId, Object.freeze([...warnings]));
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Wait until one live Member's current turn ends or its Session is
|
|
127
|
+
* disposed. While the Agent runs, the current turn keeps its schemas and
|
|
128
|
+
* catalog; the swap happens once idle, so the next step recomputes schemas
|
|
129
|
+
* from the new restriction and the durable replacement skill catalog from
|
|
130
|
+
* the new selection, with the same Session and history surviving.
|
|
131
|
+
* Suspend/remove during the wait resolves it — the disposed handle released
|
|
132
|
+
* the old restriction already and no disposer leaks. Returns whether any
|
|
133
|
+
* wait happened, so the caller can re-check the handle afterwards.
|
|
134
|
+
*/
|
|
135
|
+
async awaitTurnBoundary(active) {
|
|
136
|
+
if (!this.deps.runningAgents.has(active.agent.id))
|
|
137
|
+
return false;
|
|
138
|
+
await new Promise(resolve => {
|
|
139
|
+
const disposers = [];
|
|
140
|
+
const settle = () => {
|
|
141
|
+
for (const dispose of disposers.splice(0))
|
|
142
|
+
dispose();
|
|
143
|
+
resolve();
|
|
144
|
+
};
|
|
145
|
+
disposers.push(this.deps.ctx.on('agent/status', (payload) => {
|
|
146
|
+
if (payload.agent !== active.agent)
|
|
147
|
+
return;
|
|
148
|
+
// A turn that ends — clean idle or error — is the boundary; the
|
|
149
|
+
// next step recomputes schemas from the new restriction.
|
|
150
|
+
if (payload.status !== 'running')
|
|
151
|
+
settle();
|
|
152
|
+
}),
|
|
153
|
+
// Disposal (suspend/remove/reactivation) resolves the wait: the old
|
|
154
|
+
// restriction went with the disposed scope, so only the ledger
|
|
155
|
+
// intent remains to apply at the next activation.
|
|
156
|
+
this.deps.ctx.on('session/disposed', (session) => {
|
|
157
|
+
if (session.id === active.agent.session.id)
|
|
158
|
+
settle();
|
|
159
|
+
}));
|
|
160
|
+
});
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Register the Member-private skill provider on the created agent's exact
|
|
165
|
+
* scope layer (the traceable-service seam, like the tool restriction):
|
|
166
|
+
* bundled read-only core skills plus this Member's own private directory,
|
|
167
|
+
* with the live selection ref that later capability edits swap in place.
|
|
168
|
+
*/
|
|
169
|
+
mountMemberSkillProvider(member, agentCtx, selection) {
|
|
170
|
+
this.skillProviderDisposals.set(member.memberId, memberSkills.mountMemberSkillProvider(agentCtx, {
|
|
171
|
+
skillsDirectory: join(member.privateMemoryPath, 'skills'),
|
|
172
|
+
bundledSkillsDirectory: BUNDLED_SKILLS_DIRECTORY,
|
|
173
|
+
selection,
|
|
174
|
+
}));
|
|
175
|
+
this.skillSelections.set(member.memberId, selection);
|
|
176
|
+
}
|
|
177
|
+
/** Live-apply a capability edit to a registered selection; a no-op when none exists. */
|
|
178
|
+
swapSkillSelection(memberId, allow) {
|
|
179
|
+
this.skillSelections.get(memberId)?.swap(allow);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Provision one Member's private memory namespace: notes/, skills/, and a
|
|
183
|
+
* first-run memory.md scaffold. The Member-private skills directory starts
|
|
184
|
+
* empty; the per-Member provider scans exactly this root (default roots
|
|
185
|
+
* excluded).
|
|
186
|
+
*/
|
|
187
|
+
async initializePrivateMemory(path) {
|
|
188
|
+
await mkdir(join(path, 'notes'), { recursive: true });
|
|
189
|
+
await mkdir(join(path, 'skills'), { recursive: true });
|
|
190
|
+
try {
|
|
191
|
+
await writeFile(join(path, 'memory.md'), '# Member memory\n\n## Stable facts\n- Add only verified, durable facts that help future work.\n\n## Notes index\n- Add focused `notes/*.md` entries here when a reusable detail needs on-demand reading.\n', { flag: 'wx' });
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
if (error.code !== 'EEXIST')
|
|
195
|
+
throw error;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/** Irreversibly remove one Member: archive its Session and delete its private namespace. */
|
|
199
|
+
async cleanupRemovedMember(member) {
|
|
200
|
+
const results = await Promise.allSettled([
|
|
201
|
+
this.deps.ctx.workspaceRegistry.archiveSession(member.sessionId),
|
|
202
|
+
rm(member.privateMemoryPath, { recursive: true, force: true }),
|
|
203
|
+
]);
|
|
204
|
+
const failures = results.flatMap(result => result.status === 'rejected' ? [result.reason] : []);
|
|
205
|
+
if (failures.length > 0)
|
|
206
|
+
throw new AggregateError(failures, `failed to clean up removed Member '${member.memberId}'`);
|
|
207
|
+
}
|
|
208
|
+
/** Drop one Member's transient selection/disposer state after its handle is gone; safe to call twice. */
|
|
209
|
+
forgetMember(memberId) {
|
|
210
|
+
this.skillSelections.delete(memberId);
|
|
211
|
+
const providerDispose = this.skillProviderDisposals.get(memberId);
|
|
212
|
+
if (providerDispose !== undefined) {
|
|
213
|
+
this.skillProviderDisposals.delete(memberId);
|
|
214
|
+
providerDispose();
|
|
215
|
+
}
|
|
216
|
+
this.releaseMemberToolPolicy(memberId);
|
|
217
|
+
}
|
|
218
|
+
/** Dispose every registered per-Member seam; service shutdown only. */
|
|
219
|
+
disposeAll() {
|
|
220
|
+
this.skillSelections.clear();
|
|
221
|
+
for (const dispose of this.skillProviderDisposals.values())
|
|
222
|
+
dispose();
|
|
223
|
+
this.skillProviderDisposals.clear();
|
|
224
|
+
for (const dispose of this.memberRestrictions.values())
|
|
225
|
+
dispose();
|
|
226
|
+
this.memberRestrictions.clear();
|
|
227
|
+
this.capabilityWarnings.clear();
|
|
228
|
+
}
|
|
229
|
+
}
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Progress-visibility nudges: advisory Session notices that keep Member agents
|
|
3
|
+
* reporting progress into their Threads. Two regimes over one shared
|
|
4
|
+
* per-Member silent-tool-call counter:
|
|
5
|
+
*
|
|
6
|
+
* - Thread progress (A): a Member holding an active Claim, or following a
|
|
7
|
+
* taskless Thread, that has run many tool calls since its last committed
|
|
8
|
+
* public communication is reminded to post a brief update. The reminder
|
|
9
|
+
* ladder advances 20 → 40 → 60… per Member.
|
|
10
|
+
* - Claim suggestion (B): a Member following a still-`todo` Task it has never
|
|
11
|
+
* claimed is reminded — once per (Member, Thread) within the current Member
|
|
12
|
+
* Session — that team_claim exists.
|
|
13
|
+
*
|
|
14
|
+
* The Coordinator owns counting, thresholds, notice formatting, dedupe,
|
|
15
|
+
* in-flight revocation, and the deferred steer. It consumes only facts the
|
|
16
|
+
* Host already holds: live `tool/call`/`user/message` session events in their
|
|
17
|
+
* real `{type, seq, time, data}` envelope, the Session log for one-time
|
|
18
|
+
* recovery, committed ledger operations, and a ledger-owned eligibility
|
|
19
|
+
* projection (`progressNudgeTargets`). It never writes Team facts and never
|
|
20
|
+
* wakes idle agents on its own — a nudge rides the member's next tool call.
|
|
21
|
+
*
|
|
22
|
+
* @module @wowyuarm/dsh-agent-team/progress-nudge
|
|
23
|
+
*/
|
|
24
|
+
import { createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
25
|
+
const PROGRESS_NUDGE_PLUGIN_ID = '@wowyuarm/dsh-agent-team';
|
|
26
|
+
/** Notice summary for every nudge this Coordinator injects. */
|
|
27
|
+
export const PROGRESS_NUDGE_NOTICE_SUMMARY = 'Progress visibility reminder';
|
|
28
|
+
export const PROGRESS_NUDGE_TOOL_CALLS_START = 20;
|
|
29
|
+
export const PROGRESS_NUDGE_TOOL_CALLS_STEP = 20;
|
|
30
|
+
export const CLAIM_SUGGESTION_TOOL_CALLS = 5;
|
|
31
|
+
/** Stable line prefix marking Claim-suggestion targets inside a nudge notice; the only text this module ever parses back. */
|
|
32
|
+
const CLAIM_TARGET_LINE_PREFIX = '- Claim target: Task ';
|
|
33
|
+
const CLAIM_TARGET_LINE = /^- Claim target: Task (task:[0-9a-f-]+) — Thread (thread:[0-9a-f-]+)$/;
|
|
34
|
+
/** Ledger operation kinds that count as committed public communication and reset silence. */
|
|
35
|
+
export function isPublicCommunicationOperationKind(kind) {
|
|
36
|
+
return kind === 'team/message-sent' || kind === 'team/thread-replied'
|
|
37
|
+
|| kind === 'team/claim-created' || kind === 'team/claim-done' || kind === 'team/claim-released';
|
|
38
|
+
}
|
|
39
|
+
/** Whether one message is a nudge notice this module injected. */
|
|
40
|
+
export function isProgressNudgeNotice(message) {
|
|
41
|
+
const source = message.source;
|
|
42
|
+
return source.kind === 'plugin' && source.plugin === PROGRESS_NUDGE_PLUGIN_ID
|
|
43
|
+
&& source.form === 'notice' && source.summary === PROGRESS_NUDGE_NOTICE_SUMMARY;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Event-driven per-Member nudge state machine. All state is in-process and
|
|
47
|
+
* scoped to one Member Session; nothing is persisted. The Host feeds it live
|
|
48
|
+
* session events in their real envelope, committed operations, and lifecycle
|
|
49
|
+
* stop points.
|
|
50
|
+
*/
|
|
51
|
+
export class ProgressNudgeCoordinator {
|
|
52
|
+
members = new Map();
|
|
53
|
+
agentForMember;
|
|
54
|
+
targetsForMember;
|
|
55
|
+
sessionLogForMember;
|
|
56
|
+
log;
|
|
57
|
+
scheduleSteer;
|
|
58
|
+
progressThresholdStart;
|
|
59
|
+
progressThresholdStep;
|
|
60
|
+
claimSuggestionThreshold;
|
|
61
|
+
constructor(options) {
|
|
62
|
+
this.agentForMember = options.agentForMember;
|
|
63
|
+
this.targetsForMember = options.targetsForMember;
|
|
64
|
+
this.sessionLogForMember = options.sessionLogForMember;
|
|
65
|
+
this.log = options.log;
|
|
66
|
+
this.scheduleSteer = options.scheduleSteer ?? (run => { queueMicrotask(run); });
|
|
67
|
+
this.progressThresholdStart = options.progressThresholdStart ?? PROGRESS_NUDGE_TOOL_CALLS_START;
|
|
68
|
+
this.progressThresholdStep = options.progressThresholdStep ?? PROGRESS_NUDGE_TOOL_CALLS_STEP;
|
|
69
|
+
this.claimSuggestionThreshold = options.claimSuggestionThreshold ?? CLAIM_SUGGESTION_TOOL_CALLS;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* One live session event for a Member, in its real `{type, seq, time, data}`
|
|
73
|
+
* envelope. Only `tool/call` counts toward silence; a `user/message` whose
|
|
74
|
+
* source is exactly this module's own notice records that the model consumed
|
|
75
|
+
* the nudge (a Claim suggestion becomes final only then). The live `agent`
|
|
76
|
+
* is passed in by the caller because it exists at event time even before
|
|
77
|
+
* the Host finishes publishing the handle.
|
|
78
|
+
*/
|
|
79
|
+
onSessionEvent(memberId, sessionId, agent, event) {
|
|
80
|
+
if (event.type === 'user/message') {
|
|
81
|
+
if (!isProgressNudgeNotice(event.data))
|
|
82
|
+
return;
|
|
83
|
+
const state = this.members.get(memberId);
|
|
84
|
+
if (state === undefined)
|
|
85
|
+
return;
|
|
86
|
+
const notice = state.pending;
|
|
87
|
+
if (notice !== undefined && notice.messageId === event.data.id)
|
|
88
|
+
state.pending = undefined;
|
|
89
|
+
for (const threadRef of claimTargetThreadRefs(event.data))
|
|
90
|
+
state.claimSuggested.add(threadRef);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (event.type !== 'tool/call')
|
|
94
|
+
return;
|
|
95
|
+
let state = this.members.get(memberId);
|
|
96
|
+
if (state === undefined || state.sessionId !== sessionId) {
|
|
97
|
+
// First event of this Member Session (or a generation the Host did not
|
|
98
|
+
// stop explicitly): fresh tracking replaces whatever was there, and
|
|
99
|
+
// consumed one-time suggestions are recovered from the durable log so a
|
|
100
|
+
// Host restart or suspend/resume within the same Session cannot repeat
|
|
101
|
+
// them. A Human-initiated new Session has a different id and starts
|
|
102
|
+
// empty, exactly as specified.
|
|
103
|
+
this.dropState(memberId, state);
|
|
104
|
+
state = { sessionId, silentToolCalls: 0, nextProgressThreshold: this.progressThresholdStart,
|
|
105
|
+
claimSuggested: this.recoverClaimSuggestions(memberId, sessionId), };
|
|
106
|
+
this.members.set(memberId, state);
|
|
107
|
+
}
|
|
108
|
+
state.silentToolCalls += 1;
|
|
109
|
+
if (state.lastNudgeTurn === event.data.turn)
|
|
110
|
+
return;
|
|
111
|
+
this.maybeNudge(memberId, state, agent, event.data.turn);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* One ledger operation committed. A Member actor's public communication
|
|
115
|
+
* resets its silence; every commit also reconciles pending notices whose
|
|
116
|
+
* targets may have just disappeared (Task accepted, Channel archived, the
|
|
117
|
+
* Claim taken by someone else).
|
|
118
|
+
*/
|
|
119
|
+
onCommitted(operation) {
|
|
120
|
+
if (operation.actor.kind === 'member' && operation.actor.memberId !== undefined
|
|
121
|
+
&& isPublicCommunicationOperationKind(operation.kind)) {
|
|
122
|
+
const state = this.members.get(operation.actor.memberId);
|
|
123
|
+
if (state !== undefined) {
|
|
124
|
+
state.silentToolCalls = 0;
|
|
125
|
+
state.nextProgressThreshold = this.progressThresholdStart;
|
|
126
|
+
this.revokePending(operation.actor.memberId, state);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
this.reconcileAll();
|
|
130
|
+
}
|
|
131
|
+
/** Revoke one Member's queued-but-unconsumed nudge, e.g. when a higher-priority Team notice is injected. */
|
|
132
|
+
revokePendingNotice(memberId) {
|
|
133
|
+
const state = this.members.get(memberId);
|
|
134
|
+
if (state !== undefined)
|
|
135
|
+
this.revokePending(memberId, state);
|
|
136
|
+
}
|
|
137
|
+
/** Drop all tracking for one Member (suspend/archive/remove/renew/context-clear/steer failure). */
|
|
138
|
+
stopTracking(memberId) {
|
|
139
|
+
this.dropState(memberId, this.members.get(memberId));
|
|
140
|
+
this.members.delete(memberId);
|
|
141
|
+
}
|
|
142
|
+
/** Stop tracking every Member. */
|
|
143
|
+
dispose() {
|
|
144
|
+
for (const memberId of this.members.keys())
|
|
145
|
+
this.stopTracking(memberId);
|
|
146
|
+
}
|
|
147
|
+
/** Rebuild the consumed one-time suggestion set from this Session's durable log. */
|
|
148
|
+
recoverClaimSuggestions(memberId, sessionId) {
|
|
149
|
+
const suggested = new Set();
|
|
150
|
+
const log = this.sessionLogForMember(memberId, sessionId);
|
|
151
|
+
if (log === undefined)
|
|
152
|
+
return suggested;
|
|
153
|
+
for (const event of log.events) {
|
|
154
|
+
if (event.type !== 'user/message')
|
|
155
|
+
continue;
|
|
156
|
+
if (!isProgressNudgeNotice(event.data))
|
|
157
|
+
continue;
|
|
158
|
+
for (const threadRef of claimTargetThreadRefs(event.data))
|
|
159
|
+
suggested.add(threadRef);
|
|
160
|
+
}
|
|
161
|
+
return suggested;
|
|
162
|
+
}
|
|
163
|
+
dropState(memberId, state) {
|
|
164
|
+
if (state === undefined)
|
|
165
|
+
return;
|
|
166
|
+
this.revokePending(memberId, state);
|
|
167
|
+
this.members.delete(memberId);
|
|
168
|
+
}
|
|
169
|
+
revokePending(memberId, state) {
|
|
170
|
+
const notice = state.pending;
|
|
171
|
+
if (notice !== undefined) {
|
|
172
|
+
notice.canceled = true;
|
|
173
|
+
if (notice.steered) {
|
|
174
|
+
const agent = this.agentForMember(memberId);
|
|
175
|
+
const pending = agent === undefined ? undefined
|
|
176
|
+
: [...agent.inbox.nextStep, ...agent.inbox.nextTurn].find(message => message.id === notice.messageId);
|
|
177
|
+
// A just-consumed notice is simply gone; remove() returning false is expected.
|
|
178
|
+
if (pending !== undefined)
|
|
179
|
+
agent.inbox.remove(pending.id);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
state.pending = undefined;
|
|
183
|
+
}
|
|
184
|
+
/** Re-check every live Member's pending notice against the targets it actually listed. */
|
|
185
|
+
reconcileAll() {
|
|
186
|
+
for (const [memberId, state] of this.members) {
|
|
187
|
+
const notice = state.pending;
|
|
188
|
+
if (notice === undefined)
|
|
189
|
+
continue;
|
|
190
|
+
const agent = this.agentForMember(memberId);
|
|
191
|
+
if (agent === undefined) {
|
|
192
|
+
this.dropState(memberId, state);
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
const targets = this.targetsForMember(memberId);
|
|
196
|
+
// The notice is stale when ANY target it listed is no longer eligible:
|
|
197
|
+
// the body names threads whose work may be finished, so a partial
|
|
198
|
+
// survival must not keep the whole stale notice alive.
|
|
199
|
+
const progressAlive = notice.progressThreadRefs.length === 0
|
|
200
|
+
|| notice.progressThreadRefs.every(threadRef => targets.progress.some(target => target.threadRef === threadRef));
|
|
201
|
+
const claimAlive = notice.claimThreadRefs.length === 0
|
|
202
|
+
|| notice.claimThreadRefs.every(threadRef => targets.claim.some(target => target.threadRef === threadRef));
|
|
203
|
+
if (!progressAlive || !claimAlive)
|
|
204
|
+
this.revokePending(memberId, state);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
maybeNudge(memberId, state, agent, turn) {
|
|
208
|
+
const targets = this.targetsForMember(memberId);
|
|
209
|
+
const progressDue = state.silentToolCalls >= state.nextProgressThreshold && targets.progress.length > 0;
|
|
210
|
+
const freshClaimTargets = targets.claim
|
|
211
|
+
.filter(target => !state.claimSuggested.has(target.threadRef) && state.pending?.claimThreadRefs.includes(target.threadRef) !== true);
|
|
212
|
+
const claimDue = state.silentToolCalls >= this.claimSuggestionThreshold && freshClaimTargets.length > 0;
|
|
213
|
+
if (!progressDue && !claimDue)
|
|
214
|
+
return;
|
|
215
|
+
if (this.hasBlockingNotice(agent))
|
|
216
|
+
return;
|
|
217
|
+
const progressTargets = progressDue ? targets.progress : [];
|
|
218
|
+
const claimTargets = claimDue ? freshClaimTargets : [];
|
|
219
|
+
const notice = createUserMessage({
|
|
220
|
+
content: [{ type: 'text', text: nudgeNoticeText(state.silentToolCalls, progressTargets, claimTargets) }],
|
|
221
|
+
source: { kind: 'plugin', plugin: PROGRESS_NUDGE_PLUGIN_ID, form: 'notice', summary: PROGRESS_NUDGE_NOTICE_SUMMARY },
|
|
222
|
+
});
|
|
223
|
+
const pending = {
|
|
224
|
+
messageId: notice.id,
|
|
225
|
+
progressThreadRefs: progressTargets.map(target => target.threadRef),
|
|
226
|
+
claimThreadRefs: claimTargets.map(target => target.threadRef),
|
|
227
|
+
steered: false,
|
|
228
|
+
canceled: false,
|
|
229
|
+
};
|
|
230
|
+
// Record intent first: the scheduled steer, its cancellation check, and
|
|
231
|
+
// its failure handling all live on this single PendingNotice object, so a
|
|
232
|
+
// revoke between queueing and the microtask leaves no orphan injection.
|
|
233
|
+
// lastNudgeTurn stays as the same-turn intent guard, but the A threshold
|
|
234
|
+
// only commits after the real steer succeeds — a canceled or failed
|
|
235
|
+
// schedule leaves the rung due so the next tool call retries it.
|
|
236
|
+
state.pending = pending;
|
|
237
|
+
state.lastNudgeTurn = turn;
|
|
238
|
+
this.scheduleSteer(() => {
|
|
239
|
+
if (pending.canceled)
|
|
240
|
+
return;
|
|
241
|
+
try {
|
|
242
|
+
agent.steer(notice);
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
this.log?.(`progress nudge steer failed for member '${memberId}': ${error instanceof Error ? error.message : String(error)}`);
|
|
246
|
+
pending.canceled = true;
|
|
247
|
+
if (state.pending === pending)
|
|
248
|
+
state.pending = undefined;
|
|
249
|
+
this.stopTracking(memberId);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
pending.steered = true;
|
|
253
|
+
if (pending.progressThreadRefs.length > 0) {
|
|
254
|
+
// Advance the ladder past the count at real success in one move: a
|
|
255
|
+
// long silence held back by higher-priority notices must not replay
|
|
256
|
+
// every skipped rung on successive tool calls after the blocker
|
|
257
|
+
// clears.
|
|
258
|
+
while (state.nextProgressThreshold <= state.silentToolCalls)
|
|
259
|
+
state.nextProgressThreshold += this.progressThresholdStep;
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Recovery, Inbox, and pre-compaction notices outrank a progress nudge. If
|
|
265
|
+
* one is already pending, this nudge stays due and retries on the next tool
|
|
266
|
+
* call; a DM relay is not a notice and never blocks.
|
|
267
|
+
*/
|
|
268
|
+
hasBlockingNotice(agent) {
|
|
269
|
+
for (const message of [...agent.inbox.nextStep, ...agent.inbox.nextTurn]) {
|
|
270
|
+
const source = message.source;
|
|
271
|
+
if (source.kind !== 'plugin' || source.plugin !== PROGRESS_NUDGE_PLUGIN_ID || source.form !== 'notice')
|
|
272
|
+
continue;
|
|
273
|
+
if (source.summary !== PROGRESS_NUDGE_NOTICE_SUMMARY)
|
|
274
|
+
return true;
|
|
275
|
+
}
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
/** Extract the threadRefs this module itself wrote as Claim targets in one notice. */
|
|
280
|
+
function claimTargetThreadRefs(message) {
|
|
281
|
+
const refs = [];
|
|
282
|
+
for (const block of message.content) {
|
|
283
|
+
if (block.type !== 'text')
|
|
284
|
+
continue;
|
|
285
|
+
for (const line of block.text.split('\n')) {
|
|
286
|
+
const match = CLAIM_TARGET_LINE.exec(line);
|
|
287
|
+
if (match !== null)
|
|
288
|
+
refs.push(match[2]);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return refs;
|
|
292
|
+
}
|
|
293
|
+
function nudgeNoticeText(silentToolCalls, progress, claim) {
|
|
294
|
+
const sections = [];
|
|
295
|
+
if (progress.length > 0) {
|
|
296
|
+
sections.push([
|
|
297
|
+
'Progress visibility reminder',
|
|
298
|
+
'',
|
|
299
|
+
`You have made ${silentToolCalls} tool calls since your last visible Team update. Please briefly update the relevant Thread(s): what you confirmed, what remains, and any blocker. Read the Thread first if needed, then continue working.`,
|
|
300
|
+
'',
|
|
301
|
+
...progress.map(target => target.reason === 'active-claim'
|
|
302
|
+
? `- Task ${target.taskRef} — Thread ${target.threadRef}`
|
|
303
|
+
: `- Taskless Thread ${target.threadRef} (reply only if your current work relates to it)`),
|
|
304
|
+
].join('\n'));
|
|
305
|
+
}
|
|
306
|
+
if (claim.length > 0) {
|
|
307
|
+
sections.push([
|
|
308
|
+
'Claim visibility reminder',
|
|
309
|
+
'',
|
|
310
|
+
'You have been working while following the Task(s) below, but you have never claimed a direction there.',
|
|
311
|
+
'',
|
|
312
|
+
...claim.map(target => `${CLAIM_TARGET_LINE_PREFIX}${target.taskRef} — Thread ${target.threadRef}`),
|
|
313
|
+
'',
|
|
314
|
+
'If your current work belongs to one of these Tasks, briefly state your direction in its Thread and consider team_claim. If it does not, no action is required. This is advisory, not a claim requirement.',
|
|
315
|
+
].join('\n'));
|
|
316
|
+
}
|
|
317
|
+
if (progress.length > 0)
|
|
318
|
+
sections.push('This is advisory; do not stop useful work merely to produce a long status report.');
|
|
319
|
+
return sections.join('\n\n');
|
|
320
|
+
}
|
|
@@ -57,7 +57,7 @@ const memberSchema = z.object({
|
|
|
57
57
|
// Ledgers written before member capabilities existed omit the field.
|
|
58
58
|
capabilities: memberCapabilitiesSchema.optional(),
|
|
59
59
|
privateMemoryPath: z.string().min(1),
|
|
60
|
-
state: z.union([z.literal('enabled'), z.literal('suspended'), z.literal('inactive')]),
|
|
60
|
+
state: z.union([z.literal('enabled'), z.literal('suspended'), z.literal('inactive'), z.literal('archived')]),
|
|
61
61
|
}).strict();
|
|
62
62
|
const channelSchema = z.object({
|
|
63
63
|
channelRef: channelRefSchema,
|
|
@@ -65,6 +65,8 @@ const channelSchema = z.object({
|
|
|
65
65
|
name: z.string().min(1),
|
|
66
66
|
description: z.string(),
|
|
67
67
|
createdAtSequence: z.number().int().positive(),
|
|
68
|
+
// Ledgers written before Channel archival existed omit the field.
|
|
69
|
+
state: z.enum(['active', 'archived']).default('active'),
|
|
68
70
|
}).strict();
|
|
69
71
|
// Ledgers written before message occurredAt or attachments existed store bare
|
|
70
72
|
// messages; the union schema below stamps occurredAt on load and leaves absent
|
|
@@ -246,6 +248,19 @@ const storedAgentTeamOperationSchema = z.discriminatedUnion('kind', [
|
|
|
246
248
|
kind: z.literal('team/member-resumed'),
|
|
247
249
|
data: z.object({ member: memberSchema }).strict(),
|
|
248
250
|
}).strict(),
|
|
251
|
+
z.object({
|
|
252
|
+
...operationBase,
|
|
253
|
+
previousOperationId: operationIdSchema.nullable(),
|
|
254
|
+
kind: z.literal('team/member-archived'),
|
|
255
|
+
data: z.object({
|
|
256
|
+
member: memberSchema,
|
|
257
|
+
claims: z.array(claimSchema),
|
|
258
|
+
activities: z.array(claimsReleasedActivitySchema),
|
|
259
|
+
tasks: z.array(taskSchema),
|
|
260
|
+
threads: z.array(threadSchema),
|
|
261
|
+
inbox: inboxDeltaSchema,
|
|
262
|
+
}).strict(),
|
|
263
|
+
}).strict(),
|
|
249
264
|
z.object({
|
|
250
265
|
...operationBase,
|
|
251
266
|
previousOperationId: operationIdSchema.nullable(),
|
|
@@ -297,6 +312,20 @@ const storedAgentTeamOperationSchema = z.discriminatedUnion('kind', [
|
|
|
297
312
|
inbox: inboxDeltaSchema,
|
|
298
313
|
}).strict(),
|
|
299
314
|
}).strict(),
|
|
315
|
+
z.object({
|
|
316
|
+
...operationBase,
|
|
317
|
+
previousOperationId: operationIdSchema.nullable(),
|
|
318
|
+
kind: z.literal('team/channel-archived'),
|
|
319
|
+
data: z.object({
|
|
320
|
+
workspaceId: workspaceIdSchema,
|
|
321
|
+
channel: channelSchema,
|
|
322
|
+
claims: z.array(claimSchema),
|
|
323
|
+
activities: z.array(claimsReleasedActivitySchema),
|
|
324
|
+
tasks: z.array(taskSchema),
|
|
325
|
+
threads: z.array(threadSchema),
|
|
326
|
+
inbox: inboxDeltaSchema,
|
|
327
|
+
}).strict(),
|
|
328
|
+
}).strict(),
|
|
300
329
|
z.object({
|
|
301
330
|
...operationBase,
|
|
302
331
|
previousOperationId: operationIdSchema.nullable(),
|