@wowyuarm/dsh-agent-team 0.1.5 → 0.1.7

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.
Files changed (100) hide show
  1. package/README.md +23 -3
  2. package/README.zh.md +23 -3
  3. package/package.json +50 -43
  4. package/packages/agent-team/README.md +8 -0
  5. package/packages/agent-team/README.zh.md +8 -0
  6. package/packages/agent-team/core-skills/member-skill-manager/SKILL.md +41 -0
  7. package/packages/agent-team/core-skills/member-skill-manager/references/auth-and-config.md +30 -0
  8. package/packages/agent-team/core-skills/member-skill-manager/references/writing-great-skills.md +17 -0
  9. package/packages/agent-team/lib/index.js +168 -37
  10. package/packages/agent-team/lib/ledger.js +440 -18
  11. package/packages/agent-team/lib/member-context.js +7 -3
  12. package/packages/agent-team/lib/member-runtime.js +229 -0
  13. package/packages/agent-team/lib/member-skills.js +94 -0
  14. package/packages/agent-team/lib/preset-roster.js +1 -0
  15. package/packages/agent-team/lib/spec.js +56 -1
  16. package/packages/agent-team/lib/typert.host.js +359 -77
  17. package/packages/agent-team/lib/typert.remote-client.d.ts +5 -1
  18. package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
  19. package/packages/agent-team/lib/typert.remote-client.js +248 -43
  20. package/packages/agent-team/lib/types/entities.js +1 -0
  21. package/packages/agent-team/lib/types/index.d.ts +51 -7
  22. package/packages/agent-team/lib/types/index.d.ts.map +1 -1
  23. package/packages/agent-team/lib/types/ledger.d.ts +62 -1
  24. package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
  25. package/packages/agent-team/lib/types/member-context.d.ts.map +1 -1
  26. package/packages/agent-team/lib/types/member-runtime.d.ts +108 -0
  27. package/packages/agent-team/lib/types/member-runtime.d.ts.map +1 -0
  28. package/packages/agent-team/lib/types/member-skills.d.ts +45 -0
  29. package/packages/agent-team/lib/types/member-skills.d.ts.map +1 -0
  30. package/packages/agent-team/lib/types/operations.js +1 -0
  31. package/packages/agent-team/lib/types/preset-roster.d.ts.map +1 -1
  32. package/packages/agent-team/lib/types/requests-results.js +1 -0
  33. package/packages/agent-team/lib/types/spec.d.ts.map +1 -1
  34. package/packages/agent-team/lib/types/types/entities.d.ts +339 -0
  35. package/packages/agent-team/lib/types/types/entities.d.ts.map +1 -0
  36. package/packages/agent-team/lib/types/types/operations.d.ts +303 -0
  37. package/packages/agent-team/lib/types/types/operations.d.ts.map +1 -0
  38. package/packages/agent-team/lib/types/types/requests-results.d.ts +527 -0
  39. package/packages/agent-team/lib/types/types/requests-results.d.ts.map +1 -0
  40. package/packages/agent-team/lib/types/types.d.ts +10 -985
  41. package/packages/agent-team/lib/types/types.d.ts.map +1 -1
  42. package/packages/agent-team/preset/team-member/agent.cordis.yml +11 -6
  43. package/packages/client-agent-team/README.md +4 -2
  44. package/packages/client-agent-team/README.zh.md +4 -2
  45. package/packages/client-agent-team/lib/client.js +636 -572
  46. package/packages/client-agent-team/lib/client.js.map +1 -1
  47. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts +3 -5
  48. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
  49. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +37 -26
  50. package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts +1 -1
  51. package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts.map +1 -1
  52. package/packages/client-agent-team/lib/types/client/TeamChannelPage.js +2 -2
  53. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.d.ts +2 -1
  54. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.d.ts.map +1 -1
  55. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +31 -5
  56. package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts.map +1 -1
  57. package/packages/client-agent-team/lib/types/client/TeamComposer.js +2 -1
  58. package/packages/client-agent-team/lib/types/client/TeamMemberDock.d.ts +12 -0
  59. package/packages/client-agent-team/lib/types/client/TeamMemberDock.d.ts.map +1 -0
  60. package/packages/client-agent-team/lib/types/client/TeamMemberDock.js +13 -0
  61. package/packages/client-agent-team/lib/types/client/TeamMemberEditor.d.ts +4 -8
  62. package/packages/client-agent-team/lib/types/client/TeamMemberEditor.d.ts.map +1 -1
  63. package/packages/client-agent-team/lib/types/client/TeamMemberEditor.js +7 -40
  64. package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts.map +1 -1
  65. package/packages/client-agent-team/lib/types/client/TeamMessage.js +10 -3
  66. package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts +1 -1
  67. package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -1
  68. package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +61 -20
  69. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts +1 -1
  70. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts.map +1 -1
  71. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.js +2 -2
  72. package/packages/client-agent-team/lib/types/client/TeamWorkspaceRow.d.ts +1 -1
  73. package/packages/client-agent-team/lib/types/client/TeamWorkspaceRow.d.ts.map +1 -1
  74. package/packages/client-agent-team/lib/types/client/index.d.ts +1 -1
  75. package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
  76. package/packages/client-agent-team/lib/types/client/index.js +25 -28
  77. package/packages/client-agent-team/lib/types/client/locales.d.ts +32 -16
  78. package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
  79. package/packages/client-agent-team/lib/types/client/locales.js +32 -16
  80. package/packages/client-agent-team/lib/types/client/member-session-input.d.ts +1 -3
  81. package/packages/client-agent-team/lib/types/client/member-session-input.d.ts.map +1 -1
  82. package/packages/client-agent-team/lib/types/client/member-session-input.js +18 -6
  83. package/packages/client-agent-team/lib/types/client/navigation.d.ts +2 -1
  84. package/packages/client-agent-team/lib/types/client/navigation.d.ts.map +1 -1
  85. package/packages/client-agent-team/lib/types/client/sidebar-order.d.ts +1 -1
  86. package/packages/client-agent-team/lib/types/client/sidebar-order.d.ts.map +1 -1
  87. package/packages/client-agent-team/lib/types/client/sidebar-sections.d.ts +1 -1
  88. package/packages/client-agent-team/lib/types/client/sidebar-sections.d.ts.map +1 -1
  89. package/packages/client-agent-team/lib/types/client/slots.d.ts +3 -1
  90. package/packages/client-agent-team/lib/types/client/slots.d.ts.map +1 -1
  91. package/packages/client-agent-team/lib/types/client/team-membership.d.ts +1 -1
  92. package/packages/client-agent-team/lib/types/client/team-membership.d.ts.map +1 -1
  93. package/packages/client-agent-team/lib/types/client/timeline-scroll.d.ts +5 -7
  94. package/packages/client-agent-team/lib/types/client/timeline-scroll.d.ts.map +1 -1
  95. package/packages/client-agent-team/lib/types/client/timeline-scroll.js +9 -22
  96. package/packages/tool-agent-team/lib/index.js +25 -5
  97. package/packages/tool-agent-team/lib/types/index.d.ts.map +1 -1
  98. package/packages/client-agent-team/lib/types/client/TeamMemberSessionComposer.d.ts +0 -17
  99. package/packages/client-agent-team/lib/types/client/TeamMemberSessionComposer.d.ts.map +0 -1
  100. package/packages/client-agent-team/lib/types/client/TeamMemberSessionComposer.js +0 -135
@@ -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
- const event = agent.session.events[sequence];
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,10 +63,10 @@ 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\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. 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
- 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\nThese paths are outside the Workspace cwd. Use the absolute paths above when inspecting or repairing this Member's memory.\n${END}`;
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}`;
66
70
  }
67
71
  function escape(value) {
68
72
  return value.replaceAll(BEGIN, '[escaped begin marker]').replaceAll(END, '[escaped end marker]');
@@ -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,94 @@
1
+ /**
2
+ * Per-Member private skill provider: the Team-owned wrapper around the
3
+ * Harness filesystem provider that scopes one Member's skill discovery to
4
+ * exactly the plugin's bundled read-only core skills plus that Member's
5
+ * private `skills/` directory (default roots excluded), and filters `list()`
6
+ * output by that Member's selection ref.
7
+ *
8
+ * Deliberate interface reservation: this per-Member provider seam is the
9
+ * primitive future Runtime Revision manifests orchestrate — do not remove
10
+ * during cleanup.
11
+ *
12
+ * @module @wowyuarm/dsh-agent-team/member-skills
13
+ */
14
+ import { FileSystemSkillProvider } from '@deepseek-ai/dsh-skill-filesystem';
15
+ /**
16
+ * Register the Member-private skill provider on one Member's agent scope and
17
+ * return its disposer. The provider files into that agent's exact layer, so
18
+ * no sibling Member or ordinary session can observe the catalog; discovery,
19
+ * watching, and invalidation flow through the same Harness provider below.
20
+ *
21
+ * Registration goes through the traceable service resolved FROM the agent
22
+ * context (the same shape as `tools.restrict()`), never through a plugin
23
+ * mount: a plugin mounted from Host activation code lands on the Host's
24
+ * async-trace fiber instead of the agent scope, and a plugin `inject` would
25
+ * hold Member activation open while the Host is still starting. A deployment
26
+ * without the skill registry keeps its Members; they simply carry no private
27
+ * skill catalog.
28
+ */
29
+ export function mountMemberSkillProvider(agentCtx, config) {
30
+ const selection = config.selection;
31
+ // A no-op swap stands in for deployments without the skill registry, so
32
+ // the Host's edit path never sees a half-initialized ref.
33
+ selection.swap = () => { };
34
+ const skills = agentCtx.get('skills');
35
+ if (skills === undefined)
36
+ return () => { };
37
+ return skills.registerProvider(control => {
38
+ const provider = new MemberPrivateSkillProvider(agentCtx, control, config, selection);
39
+ // The ref swap updates the live filter and drops cached catalogs through
40
+ // the provider's own registration, so the next catalog query (and the
41
+ // next step's durable replacement catalog) sees the new selection.
42
+ selection.swap = (allow) => {
43
+ selection.current = allow;
44
+ control.invalidate();
45
+ };
46
+ return provider;
47
+ });
48
+ }
49
+ /** One Member's bundled-plus-private skill roots wrapped as a filtered registry provider. */
50
+ class MemberPrivateSkillProvider {
51
+ selection;
52
+ name;
53
+ wrapped;
54
+ constructor(ctx, control, config, selection) {
55
+ this.selection = selection;
56
+ this.name = `member-private:${config.skillsDirectory}`;
57
+ // Two Team-owned roots, no project/user/global leakage: the bundled
58
+ // read-only core skills first, then the Member's writable private
59
+ // directory. Within one provider both roots share the custom rank, and
60
+ // discovery keeps the first root's same-name candidate, so a bundled
61
+ // skill stays stable across plugin upgrades while a Member installs
62
+ // its own additions under their own names.
63
+ const customSkillDirs = config.bundledSkillsDirectory === undefined
64
+ ? [config.skillsDirectory]
65
+ : [config.bundledSkillsDirectory, config.skillsDirectory];
66
+ this.wrapped = new FileSystemSkillProvider(ctx, control, {
67
+ providerName: this.name,
68
+ includeDefaultRoots: false,
69
+ customSkillDirs,
70
+ });
71
+ }
72
+ /**
73
+ * Filter the wrapped discovery to the Member's selection. Filtering is
74
+ * list()-output only — the directory is still scanned and watched, so this
75
+ * is a performance/visibility semantic, not a security boundary.
76
+ */
77
+ async list(options) {
78
+ const output = await this.wrapped.list(options);
79
+ const allow = this.selection.current;
80
+ if (allow === undefined)
81
+ return output;
82
+ const allowed = new Set(allow);
83
+ return filterObservation(output, candidate => allowed.has(candidate.name));
84
+ }
85
+ async get(candidate, options) {
86
+ return this.wrapped.get(candidate, options);
87
+ }
88
+ }
89
+ /** Apply one name filter to either provider observation shape. */
90
+ function filterObservation(output, keep) {
91
+ if (!('candidates' in output))
92
+ return output.filter(keep);
93
+ return { candidates: output.candidates.filter(keep), complete: output.complete };
94
+ }
@@ -9,6 +9,7 @@ export function apply(ctx) {
9
9
  ctx.plugin(AgentPresets, {
10
10
  default: 'team-member',
11
11
  roots: [{ path: root, trust: 'system' }],
12
+ includeShippedRoot: false,
12
13
  includeUserRoot: false,
13
14
  });
14
15
  }
@@ -32,6 +32,19 @@ const modelSelectionSchema = z.object({
32
32
  model,
33
33
  ...(reasoningEffort === undefined ? {} : { reasoningEffort }),
34
34
  }));
35
+ // Pure intent: allow-list entries are plain strings. They are never
36
+ // validated against a known-name set here — committing must survive Harness
37
+ // upgrades that rename or remove tools, so divergence is derived at
38
+ // activation instead (see AgentTeamCapabilityWarning).
39
+ const memberCapabilitiesSchema = z.object({
40
+ // Deliberate interface reservation, no UI writes it today: Runtime Revision
41
+ // manifests depend on this seam — do not remove during cleanup.
42
+ tools: z.object({ allow: z.array(z.string().min(1)) }).strict().transform(omitUndefined).optional(),
43
+ skills: z.object({ allow: z.array(z.string().min(1)) }).strict().transform(omitUndefined).optional(),
44
+ }).strict().transform(({ tools, skills }) => ({
45
+ ...(tools === undefined ? {} : { tools }),
46
+ ...(skills === undefined ? {} : { skills }),
47
+ }));
35
48
  const memberSchema = z.object({
36
49
  memberId: memberIdSchema,
37
50
  sessionId: sessionIdSchema,
@@ -41,8 +54,10 @@ const memberSchema = z.object({
41
54
  presetId: z.string().min(1),
42
55
  // Ledgers written before per-Member model selection existed omit the field.
43
56
  model: modelSelectionSchema.optional(),
57
+ // Ledgers written before member capabilities existed omit the field.
58
+ capabilities: memberCapabilitiesSchema.optional(),
44
59
  privateMemoryPath: z.string().min(1),
45
- 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')]),
46
61
  }).strict();
47
62
  const channelSchema = z.object({
48
63
  channelRef: channelRefSchema,
@@ -50,6 +65,8 @@ const channelSchema = z.object({
50
65
  name: z.string().min(1),
51
66
  description: z.string(),
52
67
  createdAtSequence: z.number().int().positive(),
68
+ // Ledgers written before Channel archival existed omit the field.
69
+ state: z.enum(['active', 'archived']).default('active'),
53
70
  }).strict();
54
71
  // Ledgers written before message occurredAt or attachments existed store bare
55
72
  // messages; the union schema below stamps occurredAt on load and leaves absent
@@ -231,6 +248,19 @@ const storedAgentTeamOperationSchema = z.discriminatedUnion('kind', [
231
248
  kind: z.literal('team/member-resumed'),
232
249
  data: z.object({ member: memberSchema }).strict(),
233
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(),
234
264
  z.object({
235
265
  ...operationBase,
236
266
  previousOperationId: operationIdSchema.nullable(),
@@ -282,6 +312,20 @@ const storedAgentTeamOperationSchema = z.discriminatedUnion('kind', [
282
312
  inbox: inboxDeltaSchema,
283
313
  }).strict(),
284
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(),
285
329
  z.object({
286
330
  ...operationBase,
287
331
  previousOperationId: operationIdSchema.nullable(),
@@ -384,6 +428,17 @@ const storedAgentTeamOperationSchema = z.discriminatedUnion('kind', [
384
428
  inbox: inboxDeltaSchema,
385
429
  }).strict(),
386
430
  }).strict(),
431
+ z.object({
432
+ ...operationBase,
433
+ previousOperationId: operationIdSchema.nullable(),
434
+ kind: z.literal('team/dm-sent'),
435
+ data: z.object({
436
+ workspaceId: workspaceIdSchema,
437
+ senderMemberId: memberIdSchema,
438
+ recipientMemberId: memberIdSchema,
439
+ body: z.string().min(1),
440
+ }).strict(),
441
+ }).strict(),
387
442
  ]);
388
443
  /** Durable validator for the closed Agent Team operation union; ledgers written before message occurredAt existed normalize on load. */
389
444
  export const agentTeamOperationSchema = storedAgentTeamOperationSchema.transform(stampOperationMessages);