@xrkseek/harness 0.6.7 → 0.6.9

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 (98) hide show
  1. package/node_modules/@xrkseek/preset-harness/dist/preset.d.ts.map +1 -1
  2. package/node_modules/@xrkseek/preset-harness/dist/preset.js +10 -2
  3. package/node_modules/@xrkseek/preset-harness/dist/preset.js.map +1 -1
  4. package/node_modules/@xrkseek/preset-harness/dist/tsconfig.tsbuildinfo +1 -1
  5. package/node_modules/@xrkseek/preset-minimal/dist/tsconfig.tsbuildinfo +1 -1
  6. package/node_modules/@xrkseek/protocol/dist/content.d.ts.map +1 -1
  7. package/node_modules/@xrkseek/protocol/dist/content.js +5 -1
  8. package/node_modules/@xrkseek/protocol/dist/content.js.map +1 -1
  9. package/node_modules/@xrkseek/server-face/dist/agent-member-card.d.ts.map +1 -1
  10. package/node_modules/@xrkseek/server-face/dist/agent-member-card.js +14 -1
  11. package/node_modules/@xrkseek/server-face/dist/agent-member-card.js.map +1 -1
  12. package/node_modules/@xrkseek/server-face/dist/agent-member-pack.d.ts +160 -0
  13. package/node_modules/@xrkseek/server-face/dist/agent-member-pack.d.ts.map +1 -0
  14. package/node_modules/@xrkseek/server-face/dist/agent-member-pack.js +684 -0
  15. package/node_modules/@xrkseek/server-face/dist/agent-member-pack.js.map +1 -0
  16. package/node_modules/@xrkseek/server-face/dist/agent-roster-store.d.ts +44 -2
  17. package/node_modules/@xrkseek/server-face/dist/agent-roster-store.d.ts.map +1 -1
  18. package/node_modules/@xrkseek/server-face/dist/agent-roster-store.js +122 -2
  19. package/node_modules/@xrkseek/server-face/dist/agent-roster-store.js.map +1 -1
  20. package/node_modules/@xrkseek/server-face/dist/agent-roster-tools.d.ts +7 -5
  21. package/node_modules/@xrkseek/server-face/dist/agent-roster-tools.d.ts.map +1 -1
  22. package/node_modules/@xrkseek/server-face/dist/agent-roster-tools.js +831 -64
  23. package/node_modules/@xrkseek/server-face/dist/agent-roster-tools.js.map +1 -1
  24. package/node_modules/@xrkseek/server-face/dist/agent-team-roles.d.ts +9 -0
  25. package/node_modules/@xrkseek/server-face/dist/agent-team-roles.d.ts.map +1 -1
  26. package/node_modules/@xrkseek/server-face/dist/agent-team-roles.js +25 -2
  27. package/node_modules/@xrkseek/server-face/dist/agent-team-roles.js.map +1 -1
  28. package/node_modules/@xrkseek/server-face/dist/capture-member-playbook.d.ts +4 -0
  29. package/node_modules/@xrkseek/server-face/dist/capture-member-playbook.d.ts.map +1 -1
  30. package/node_modules/@xrkseek/server-face/dist/capture-member-playbook.js +28 -7
  31. package/node_modules/@xrkseek/server-face/dist/capture-member-playbook.js.map +1 -1
  32. package/node_modules/@xrkseek/server-face/dist/dispatch.d.ts.map +1 -1
  33. package/node_modules/@xrkseek/server-face/dist/dispatch.js +5 -1
  34. package/node_modules/@xrkseek/server-face/dist/dispatch.js.map +1 -1
  35. package/node_modules/@xrkseek/server-face/dist/handlers/team.d.ts +4 -0
  36. package/node_modules/@xrkseek/server-face/dist/handlers/team.d.ts.map +1 -1
  37. package/node_modules/@xrkseek/server-face/dist/handlers/team.js +223 -3
  38. package/node_modules/@xrkseek/server-face/dist/handlers/team.js.map +1 -1
  39. package/node_modules/@xrkseek/server-face/dist/index.d.ts +2 -2
  40. package/node_modules/@xrkseek/server-face/dist/index.d.ts.map +1 -1
  41. package/node_modules/@xrkseek/server-face/dist/index.js +2 -2
  42. package/node_modules/@xrkseek/server-face/dist/index.js.map +1 -1
  43. package/node_modules/@xrkseek/server-face/dist/runtime.d.ts.map +1 -1
  44. package/node_modules/@xrkseek/server-face/dist/runtime.js +7 -2
  45. package/node_modules/@xrkseek/server-face/dist/runtime.js.map +1 -1
  46. package/node_modules/@xrkseek/server-face/dist/session-capability.d.ts.map +1 -1
  47. package/node_modules/@xrkseek/server-face/dist/session-capability.js +30 -2
  48. package/node_modules/@xrkseek/server-face/dist/session-capability.js.map +1 -1
  49. package/node_modules/@xrkseek/server-face/dist/session-posture.d.ts.map +1 -1
  50. package/node_modules/@xrkseek/server-face/dist/session-posture.js +3 -1
  51. package/node_modules/@xrkseek/server-face/dist/session-posture.js.map +1 -1
  52. package/node_modules/@xrkseek/server-face/dist/session-tool-copy.d.ts +9 -0
  53. package/node_modules/@xrkseek/server-face/dist/session-tool-copy.d.ts.map +1 -1
  54. package/node_modules/@xrkseek/server-face/dist/session-tool-copy.js +18 -2
  55. package/node_modules/@xrkseek/server-face/dist/session-tool-copy.js.map +1 -1
  56. package/node_modules/@xrkseek/server-face/dist/session-tools.d.ts.map +1 -1
  57. package/node_modules/@xrkseek/server-face/dist/session-tools.js +5 -0
  58. package/node_modules/@xrkseek/server-face/dist/session-tools.js.map +1 -1
  59. package/node_modules/@xrkseek/server-face/dist/subagent-tools.d.ts.map +1 -1
  60. package/node_modules/@xrkseek/server-face/dist/subagent-tools.js +46 -3
  61. package/node_modules/@xrkseek/server-face/dist/subagent-tools.js.map +1 -1
  62. package/node_modules/@xrkseek/server-face/dist/xiangyang-skill-bodies/demand-translate.d.ts +1 -1
  63. package/node_modules/@xrkseek/server-face/dist/xiangyang-skill-bodies/demand-translate.d.ts.map +1 -1
  64. package/node_modules/@xrkseek/server-face/dist/xiangyang-skill-bodies/demand-translate.js +25 -7
  65. package/node_modules/@xrkseek/server-face/dist/xiangyang-skill-bodies/demand-translate.js.map +1 -1
  66. package/node_modules/@xrkseek/server-face/dist/xiangyang-skill-bodies/home-config.d.ts +1 -1
  67. package/node_modules/@xrkseek/server-face/dist/xiangyang-skill-bodies/home-config.d.ts.map +1 -1
  68. package/node_modules/@xrkseek/server-face/dist/xiangyang-skill-bodies/home-config.js +18 -5
  69. package/node_modules/@xrkseek/server-face/dist/xiangyang-skill-bodies/home-config.js.map +1 -1
  70. package/node_modules/@xrkseek/server-face/dist/xiangyang-skill-bodies/index.d.ts +1 -1
  71. package/node_modules/@xrkseek/server-face/dist/xiangyang-skill-bodies/index.js +1 -1
  72. package/node_modules/@xrkseek/server-face/dist/xiangyang-skill-bodies/skill-merge.d.ts +1 -1
  73. package/node_modules/@xrkseek/server-face/dist/xiangyang-skill-bodies/skill-merge.d.ts.map +1 -1
  74. package/node_modules/@xrkseek/server-face/dist/xiangyang-skill-bodies/skill-merge.js +12 -7
  75. package/node_modules/@xrkseek/server-face/dist/xiangyang-skill-bodies/skill-merge.js.map +1 -1
  76. package/node_modules/@xrkseek/server-face/dist/zip-store.d.ts +5 -3
  77. package/node_modules/@xrkseek/server-face/dist/zip-store.d.ts.map +1 -1
  78. package/node_modules/@xrkseek/server-face/dist/zip-store.js +60 -1
  79. package/node_modules/@xrkseek/server-face/dist/zip-store.js.map +1 -1
  80. package/node_modules/@xrkseek/server-host/dist/index.d.ts.map +1 -1
  81. package/node_modules/@xrkseek/server-host/dist/index.js +31 -24
  82. package/node_modules/@xrkseek/server-host/dist/index.js.map +1 -1
  83. package/node_modules/@xrkseek/workspace/dist/index.d.ts +8 -0
  84. package/node_modules/@xrkseek/workspace/dist/index.d.ts.map +1 -1
  85. package/node_modules/@xrkseek/workspace/dist/index.js +17 -5
  86. package/node_modules/@xrkseek/workspace/dist/index.js.map +1 -1
  87. package/node_modules/@xrkseek/workspace/dist/inject-fingerprint.d.ts +1 -0
  88. package/node_modules/@xrkseek/workspace/dist/inject-fingerprint.d.ts.map +1 -1
  89. package/node_modules/@xrkseek/workspace/dist/inject-fingerprint.js +4 -1
  90. package/node_modules/@xrkseek/workspace/dist/inject-fingerprint.js.map +1 -1
  91. package/node_modules/@xrkseek/workspace/dist/skill-dirs.d.ts +2 -0
  92. package/node_modules/@xrkseek/workspace/dist/skill-dirs.d.ts.map +1 -1
  93. package/node_modules/@xrkseek/workspace/dist/skill-dirs.js +24 -21
  94. package/node_modules/@xrkseek/workspace/dist/skill-dirs.js.map +1 -1
  95. package/node_modules/@xrkseek/workspace/dist/skills.d.ts +2 -0
  96. package/node_modules/@xrkseek/workspace/dist/skills.d.ts.map +1 -1
  97. package/node_modules/@xrkseek/workspace/dist/skills.js.map +1 -1
  98. package/package.json +1 -1
@@ -1,9 +1,35 @@
1
+ /**
2
+ * Model-facing team_list / save / import / ready / export / remove /
3
+ * team_self (child own surfaces). Packs under agent-rosters/packs/.
4
+ */
5
+ import { writeFileSync } from "node:fs";
6
+ import path from "node:path";
7
+ import { readSessionEvents } from "@xrkseek/core-session";
8
+ import { isAttachmentError } from "@xrkseek/attachment";
1
9
  import { canvasWorkspaceIdForSession } from "./canvas-tools.js";
2
10
  import { isAgentTeamSpawnRole } from "./agent-team-roles.js";
3
- import { memberIdProblem, catalogMemberWriteProblem, parseMemberToolPolicy, parseRosterScope, rosterPublicFields, } from "./agent-roster-store.js";
4
- import { parseAgentMemberCard } from "./agent-member-card.js";
5
- import { captureSessionPlaybook } from "./capture-member-playbook.js";
6
- import { liveRosterCatalog, teamListDescription, } from "./session-tool-copy.js";
11
+ import { GLOBAL_ROSTER_ID, memberIdProblem, catalogMemberWriteProblem, parseMemberInject, parseMemberToolPolicy, parseRosterScope, rosterPublicFields, } from "./agent-roster-store.js";
12
+ import { parseAgentMemberCard, } from "./agent-member-card.js";
13
+ import { AGENT_MEMBER_PACK_LAYOUT, MEMBER_PACK_BRIEFCASE, buildAgentMemberPackZip, collectPackSkillFiles, collectPackWorkspaceFiles, listMemberPackInventory, loadAgentMemberPack, loadAgentMemberPackBytes, materializeAgentMemberPack, mergeIntoMemberPack, notReadyPackHints, syncMemberPackPlaybook, } from "./agent-member-pack.js";
14
+ import { withAdvancedPackPathGuidance } from "./capture-member-playbook.js";
15
+ import { resolveSessionCwd } from "./session-cwd.js";
16
+ import { referencedAttachmentIds } from "./session-attachment.js";
17
+ import { dispatchFaceMethod } from "./dispatch.js";
18
+ import { liveRosterCatalog, rosterScopeVisibility, teamListDescription, } from "./session-tool-copy.js";
19
+ function teamPresent(title, kind, args = {}) {
20
+ return { card: "generic", title, kind, rawInput: args };
21
+ }
22
+ function missingMemberHint(runtime, canvasId, id, tool) {
23
+ const home = runtime.agentRoster.findHome(id);
24
+ if (!home) {
25
+ return `${tool}: unknown or catalog member "${id}" (team_list first)`;
26
+ }
27
+ if (home.scope === "global" || home.rosterWorkspaceId === canvasId) {
28
+ return `${tool}: unknown or catalog member "${id}"`;
29
+ }
30
+ return (`${tool}: member "${id}" is workspace-scoped on canvas ${home.rosterWorkspaceId} ` +
31
+ `(not visible here). Open a session in that workspace, or re-import with scope=global.`);
32
+ }
7
33
  function registerTool(tools, tool) {
8
34
  if (tools.get(tool.name))
9
35
  return;
@@ -14,24 +40,255 @@ function readArgs(args) {
14
40
  return {};
15
41
  return args;
16
42
  }
17
- /** Register team_list / team_save (or list-only). Spawn stays `subagent` + member_id. */
43
+ function resolvePackPath(runtime, sessionId, raw) {
44
+ const trimmed = raw.trim();
45
+ if (path.isAbsolute(trimmed))
46
+ return path.resolve(trimmed);
47
+ return path.resolve(resolveSessionCwd(runtime, sessionId), trimmed);
48
+ }
49
+ /** `sha256:…` or `attachment:sha256:…` → durable id. */
50
+ function parseAttachmentPackId(raw) {
51
+ const trimmed = raw.trim();
52
+ if (/^sha256:[a-f0-9]+$/i.test(trimmed))
53
+ return trimmed;
54
+ if (/^attachment:/i.test(trimmed)) {
55
+ const id = trimmed.slice("attachment:".length).trim();
56
+ if (/^sha256:[a-f0-9]+$/i.test(id))
57
+ return id;
58
+ }
59
+ return undefined;
60
+ }
61
+ async function loadPackFromArg(runtime, sessionId, packRaw) {
62
+ const attachmentId = parseAttachmentPackId(packRaw);
63
+ if (attachmentId) {
64
+ const attachments = runtime.attachments;
65
+ if (!attachments) {
66
+ return { ok: false, error: "attachments unavailable on this Host" };
67
+ }
68
+ const events = readSessionEvents(runtime.store, sessionId);
69
+ if (!referencedAttachmentIds(events).has(attachmentId)) {
70
+ return {
71
+ ok: false,
72
+ error: `attachment ${attachmentId} is not referenced by this session`,
73
+ };
74
+ }
75
+ try {
76
+ const stored = await attachments.readFile(attachmentId);
77
+ return loadAgentMemberPackBytes(Buffer.from(stored.data));
78
+ }
79
+ catch (err) {
80
+ if (isAttachmentError(err) && err.code === "NOT_FOUND") {
81
+ return { ok: false, error: `attachment ${attachmentId} bytes missing` };
82
+ }
83
+ throw err;
84
+ }
85
+ }
86
+ return loadAgentMemberPack(resolvePackPath(runtime, sessionId, packRaw));
87
+ }
88
+ function enrichListedMember(runtime, canvasId, row) {
89
+ const rosterWs = row.scope === "global" ? GLOBAL_ROSTER_ID : canvasId;
90
+ const pub = {
91
+ ...rosterPublicFields(row),
92
+ visibility: rosterScopeVisibility(row.scope === "global" ? "global" : "workspace", rosterWs),
93
+ };
94
+ if (row.kind !== "advanced")
95
+ return pub;
96
+ const packOpts = {
97
+ rosterWorkspaceId: rosterWs,
98
+ memberId: row.id,
99
+ productHome: runtime.agentRoster.home,
100
+ };
101
+ const inventory = listMemberPackInventory(packOpts);
102
+ const base = {
103
+ ...pub,
104
+ ...(inventory
105
+ ? {
106
+ packHome: inventory.packHome,
107
+ playbookPath: inventory.playbookPath,
108
+ ...(inventory.envPrepPath
109
+ ? { envPrepPath: inventory.envPrepPath }
110
+ : {}),
111
+ ...(inventory.materialsPath
112
+ ? {
113
+ materialsPath: inventory.materialsPath,
114
+ materialsTop: inventory.materialsTop,
115
+ }
116
+ : {}),
117
+ skills: inventory.skills,
118
+ briefcase: MEMBER_PACK_BRIEFCASE,
119
+ }
120
+ : {}),
121
+ };
122
+ if (row.ready)
123
+ return base;
124
+ return {
125
+ ...base,
126
+ ...notReadyPackHints(packOpts),
127
+ };
128
+ }
129
+ function resolveCwdPaths(runtime, sessionId, rows) {
130
+ const cwd = resolveSessionCwd(runtime, sessionId);
131
+ if (!Array.isArray(rows))
132
+ return [];
133
+ return rows
134
+ .filter((s) => typeof s === "string" && s.trim().length > 0)
135
+ .map((s) => path.isAbsolute(s.trim()) ? s.trim() : path.resolve(cwd, s.trim()));
136
+ }
137
+ /**
138
+ * Mint an advanced pack member (playbook + optional skills/files/env).
139
+ * Shared by team_save — team_import only installs card=/pack=.
140
+ */
141
+ function mintAdvancedMember(input) {
142
+ const { runtime, sessionId, canvasId, scope, name, playbook, } = input;
143
+ if (!name.trim() || !playbook.trim()) {
144
+ return {
145
+ ok: false,
146
+ content: "team_save: advanced needs name and playbook (from the real scenario)",
147
+ };
148
+ }
149
+ const toolsPolicy = parseMemberToolPolicy(input.tools);
150
+ if (input.tools !== undefined && input.tools !== null && !toolsPolicy) {
151
+ return {
152
+ ok: false,
153
+ content: 'team_save: tools needs { mode: "allow"|"deny", names: [...] }',
154
+ };
155
+ }
156
+ const card = {
157
+ name: name.trim(),
158
+ playbook: withAdvancedPackPathGuidance(playbook),
159
+ role: isAgentTeamSpawnRole(input.roleRaw) ? input.roleRaw : "worker",
160
+ ...(input.brief?.trim() ? { brief: input.brief.trim() } : {}),
161
+ ...(input.inject === "subagent" || input.inject === "minimal"
162
+ ? { inject: input.inject }
163
+ : {}),
164
+ ...(toolsPolicy ? { tools: toolsPolicy } : {}),
165
+ };
166
+ const skillPaths = resolveCwdPaths(runtime, sessionId, input.skills);
167
+ let skillsFiles;
168
+ if (skillPaths.length > 0) {
169
+ const bundled = collectPackSkillFiles(skillPaths);
170
+ if (!bundled.ok) {
171
+ return { ok: false, content: `team_save: ${bundled.error}` };
172
+ }
173
+ skillsFiles = bundled.files;
174
+ }
175
+ const filePaths = resolveCwdPaths(runtime, sessionId, input.files);
176
+ let workspaceFiles;
177
+ if (filePaths.length > 0) {
178
+ const bundled = collectPackWorkspaceFiles(filePaths);
179
+ if (!bundled.ok) {
180
+ return { ok: false, content: `team_save: ${bundled.error}` };
181
+ }
182
+ workspaceFiles = bundled.files;
183
+ }
184
+ const envPrep = input.envPrep?.trim() || undefined;
185
+ const zip = buildAgentMemberPackZip(card, {
186
+ ...(skillsFiles && skillsFiles.size > 0 ? { skillsFiles } : {}),
187
+ ...(workspaceFiles && workspaceFiles.size > 0 ? { workspaceFiles } : {}),
188
+ ...(envPrep ? { envPrep } : {}),
189
+ });
190
+ const loaded = loadAgentMemberPackBytes(zip);
191
+ if (!loaded.ok) {
192
+ return { ok: false, content: `team_save: ${loaded.error}` };
193
+ }
194
+ const rosterWs = scope === "global" ? GLOBAL_ROSTER_ID : canvasId;
195
+ const member = runtime.agentRoster.upsertAtScope(canvasId, scope, {
196
+ ...loaded.pack.card,
197
+ kind: "advanced",
198
+ ready: false,
199
+ ...(input.id?.trim() ? { id: input.id.trim() } : {}),
200
+ });
201
+ if (!member) {
202
+ return { ok: false, content: "team_save: name and playbook required" };
203
+ }
204
+ const placed = materializeAgentMemberPack(loaded.pack, {
205
+ rosterWorkspaceId: rosterWs,
206
+ memberId: member.id,
207
+ productHome: runtime.agentRoster.home,
208
+ });
209
+ return {
210
+ ok: true,
211
+ content: JSON.stringify({
212
+ workspaceId: rosterWs,
213
+ scope,
214
+ visibility: rosterScopeVisibility(scope, rosterWs),
215
+ member,
216
+ source: "team_save",
217
+ kind: "advanced",
218
+ packDir: placed.packDir,
219
+ skillNames: placed.skillNames,
220
+ ready: false,
221
+ ...(placed.envPrep
222
+ ? {
223
+ envPrep: placed.envPrep,
224
+ envHint: "Review envPrep here (or hand zip to a friend). Then team_ready. Do not auto-run scripts.",
225
+ }
226
+ : {
227
+ envHint: "No env/SETUP.md — team_ready when configured; friend gets playbook (± skills).",
228
+ }),
229
+ next: "team_ready → subagent member_id",
230
+ layout: AGENT_MEMBER_PACK_LAYOUT,
231
+ pathHint: `Spawn keeps parent workspace cwd. ${MEMBER_PACK_BRIEFCASE} = pack_home (injected) — child may read/edit/copy into workspace. Share zip with relative paths under the pack tree.`,
232
+ shareHint: "Friend: team_export zip / UI import, or they team_import pack=… → follow envPrep with main session → team_ready. " +
233
+ "scope=global keeps it on every canvas team_list.",
234
+ }),
235
+ };
236
+ }
237
+ function patchExistingMember(runtime, canvasId, existing, patch) {
238
+ const name = (patch.name ?? existing.name).trim();
239
+ let playbook = (patch.playbook ?? existing.playbook).trim();
240
+ if (existing.kind === "advanced" && patch.playbook !== undefined) {
241
+ playbook = withAdvancedPackPathGuidance(playbook);
242
+ }
243
+ if (!name || !playbook)
244
+ return undefined;
245
+ const scope = existing.scope === "global" ? "global" : "workspace";
246
+ const inject = patch.inject ?? existing.inject;
247
+ const tools = patch.tools === undefined
248
+ ? existing.tools
249
+ : patch.tools === null
250
+ ? undefined
251
+ : patch.tools;
252
+ const member = runtime.agentRoster.upsertAtScope(canvasId, scope, {
253
+ id: existing.id,
254
+ name,
255
+ playbook,
256
+ role: isAgentTeamSpawnRole(existing.role) ? existing.role : "worker",
257
+ kind: existing.kind,
258
+ ready: existing.ready,
259
+ brief: patch.brief !== undefined ? patch.brief : existing.brief,
260
+ inject,
261
+ ...(tools ? { tools } : patch.tools === null ? { tools: null } : {}),
262
+ appearance: existing.appearance,
263
+ });
264
+ if (!member)
265
+ return undefined;
266
+ if (existing.kind === "advanced") {
267
+ const rosterWs = scope === "global" ? GLOBAL_ROSTER_ID : canvasId;
268
+ syncMemberPackPlaybook({
269
+ rosterWorkspaceId: rosterWs,
270
+ memberId: member.id,
271
+ playbook: member.playbook,
272
+ productHome: runtime.agentRoster.home,
273
+ });
274
+ }
275
+ return runtime.agentRoster.get(canvasId, member.id);
276
+ }
277
+ /** Register team_list / team_save / team_import / team_ready / team_remove (or list + team_self). */
18
278
  export function bindAgentRosterTools(tools, options) {
19
279
  const { runtime, sessionId, listOnly = false } = options;
280
+ const selfMemberId = options.selfMemberId?.trim() || undefined;
20
281
  const ws = () => canvasWorkspaceIdForSession(runtime, sessionId);
21
282
  registerTool(tools, {
22
283
  name: "team_list",
23
284
  description: "List Agent Team members (干员) for this workspace + global.",
24
285
  parameters: { type: "object", properties: {}, additionalProperties: false },
25
286
  isConcurrencySafe: () => true,
26
- presentCall: () => ({
27
- card: "generic",
28
- title: "team_list",
29
- kind: "search",
30
- rawInput: {},
31
- }),
287
+ presentCall: () => teamPresent("team_list", "search"),
32
288
  dynamicSchema: () => ({
33
289
  description: teamListDescription({
34
290
  listOnly,
291
+ ...(selfMemberId ? { selfRevise: true } : {}),
35
292
  catalog: liveRosterCatalog(runtime, sessionId),
36
293
  }),
37
294
  }),
@@ -39,67 +296,171 @@ export function bindAgentRosterTools(tools, options) {
39
296
  const workspaceId = ws();
40
297
  const members = runtime.agentRoster
41
298
  .listVisible(workspaceId)
42
- .map(rosterPublicFields);
299
+ .map((row) => enrichListedMember(runtime, workspaceId, row));
43
300
  return { content: JSON.stringify({ workspaceId, members }) };
44
301
  },
45
302
  });
46
- if (listOnly)
303
+ if (listOnly) {
304
+ if (selfMemberId) {
305
+ registerTool(tools, {
306
+ name: "team_self",
307
+ description: "Revise YOUR 干员 surfaces (this member_id): playbook, inject, tools, brief. " +
308
+ "Child tool/inject surface ≠ parent. Durable for next spawn. " +
309
+ "Cannot mint, import, export, or edit other members.",
310
+ parameters: {
311
+ type: "object",
312
+ properties: {
313
+ playbook: {
314
+ type: "string",
315
+ description: "Replacement playbook (max 20000). Omit to keep.",
316
+ },
317
+ brief: {
318
+ type: "string",
319
+ description: "Replacement duty line. Omit to keep.",
320
+ },
321
+ name: {
322
+ type: "string",
323
+ description: "Optional display-name tweak. Omit to keep.",
324
+ },
325
+ inject: {
326
+ type: "string",
327
+ description: "minimal (thin context) | subagent (keep project docs). Omit to keep.",
328
+ },
329
+ tools: {
330
+ type: "object",
331
+ properties: {
332
+ mode: { type: "string" },
333
+ names: { type: "array", items: { type: "string" } },
334
+ },
335
+ additionalProperties: false,
336
+ description: "allow|deny list on top of role. Pass null to clear.",
337
+ },
338
+ },
339
+ additionalProperties: false,
340
+ },
341
+ presentCall: (args) => teamPresent("team_self", "execute", args),
342
+ execute: async (args) => {
343
+ const a = readArgs(args);
344
+ const canvasId = ws();
345
+ const existing = runtime.agentRoster.get(canvasId, selfMemberId);
346
+ if (!existing || existing.catalog) {
347
+ return {
348
+ content: `team_self: member "${selfMemberId}" not editable here`,
349
+ isError: true,
350
+ };
351
+ }
352
+ const hasPlaybook = typeof a.playbook === "string";
353
+ const hasBrief = typeof a.brief === "string";
354
+ const hasName = typeof a.name === "string" && a.name.trim().length > 0;
355
+ const hasInject = a.inject === "minimal" || a.inject === "subagent";
356
+ const hasTools = a.tools !== undefined;
357
+ if (!hasPlaybook && !hasBrief && !hasName && !hasInject && !hasTools) {
358
+ return {
359
+ content: "team_self: pass playbook, brief, inject, and/or tools (optional name)",
360
+ isError: true,
361
+ };
362
+ }
363
+ const toolsPatch = a.tools === null
364
+ ? null
365
+ : hasTools
366
+ ? parseMemberToolPolicy(a.tools)
367
+ : undefined;
368
+ if (hasTools && a.tools !== null && !toolsPatch) {
369
+ return {
370
+ content: 'team_self: tools needs { mode: "allow"|"deny", names: [...] }',
371
+ isError: true,
372
+ };
373
+ }
374
+ const patched = patchExistingMember(runtime, canvasId, existing, {
375
+ ...(hasName ? { name: String(a.name).trim() } : {}),
376
+ ...(hasPlaybook ? { playbook: String(a.playbook) } : {}),
377
+ ...(hasBrief ? { brief: String(a.brief) } : {}),
378
+ ...(hasInject
379
+ ? { inject: parseMemberInject(a.inject, existing.inject) }
380
+ : {}),
381
+ ...(hasTools ? { tools: toolsPatch ?? null } : {}),
382
+ });
383
+ if (!patched) {
384
+ return { content: "team_self: name and playbook required", isError: true };
385
+ }
386
+ return {
387
+ content: JSON.stringify({
388
+ member: {
389
+ ...enrichListedMember(runtime, canvasId, patched),
390
+ playbook: patched.playbook,
391
+ },
392
+ next: "next subagent member_id uses these surfaces; this turn already started",
393
+ }),
394
+ };
395
+ },
396
+ });
397
+ }
47
398
  return;
399
+ }
48
400
  registerTool(tools, {
49
401
  name: "team_save",
50
- description: "Create or update an Agent Team member (干员 / 成员 / 子代理团队的一员): tools + inject + playbook. " +
51
- "Use this when the user says 干员, 成员, teammate, or wants someone on the subagent team — not propose_skill, which only writes a SKILL.md. " +
52
- "Built-in catalog ids (mem_seed_*) cannot be updated — omit id to mint a new role, copying fields from a catalog member if you want that template. " +
53
- "Use after a repeatable process (release, review, deploy) so later `subagent member_id` " +
54
- "does not rediscover it. from_session=true fills name/playbook from this chat. " +
55
- "scope global|workspace. inject minimal (skip home persona + skill catalog) | subagent. " +
56
- "tools {mode:allow|deny, names:[...]} weakens the parent preset only. " +
57
- "role worker|researcher|reviewer|lead|default is the deny template under the playbook.",
402
+ description: "Mint or revise a 干员 (template → skill xrk-delegate). " +
403
+ "Thin: name+playbook (+inject/tools). " +
404
+ `Advanced ${MEMBER_PACK_BRIEFCASE}: kind=advanced + skills/files/env_prep (playbook.md · skills/ · workspace/ · env/SETUP.md) → team_ready → team_export. ` +
405
+ "Revise any custom member with id= (omit unchanged fields); advanced may also merge skills/files/env_prep into 公文包. " +
406
+ "Install friend zip/json → team_import (not this).",
58
407
  parameters: {
59
408
  type: "object",
60
409
  properties: {
61
410
  id: {
62
411
  type: "string",
63
- description: "mem_ then [A-Za-z0-9][A-Za-z0-9._-]*, max 64 chars. " +
64
- "Omit to mint one. Reuse a previous user-owned id to update that member. Catalog mem_seed_* ids are read-only.",
412
+ description: "Existing mem_… to revise (thin or advanced). Omit to mint. Pattern: mem_[A-Za-z0-9][A-Za-z0-9._-]*",
413
+ },
414
+ name: {
415
+ type: "string",
416
+ description: "Short label (e.g. PPT排版员). On revise, omit to keep.",
65
417
  },
66
- name: { type: "string" },
67
418
  playbook: {
68
419
  type: "string",
69
- description: "Standalone instructions a later child can run without this chat.",
420
+ description: "Durable how-to. Prefer: Role; Steps; Constraints; Acceptance; for advanced also list what is in 公文包 (briefcase). " +
421
+ "On revise, omit to keep. Max 20000. Template: skill xrk-delegate.",
70
422
  },
71
423
  role: { type: "string" },
72
- from_session: {
73
- type: "boolean",
74
- description: "If true, mint name/playbook from this session's user+assistant log.",
424
+ kind: {
425
+ type: "string",
426
+ description: "thin (default) | advanced. Also advanced when skills/files/env_prep set.",
75
427
  },
76
- brief: { type: "string" },
428
+ brief: { type: "string", description: "One-line duty for team_list." },
77
429
  scope: {
78
430
  type: "string",
79
- description: "global | workspace (default workspace)",
431
+ description: "global | workspace. Thin default workspace; advanced default global.",
80
432
  },
81
433
  inject: {
82
434
  type: "string",
83
- description: "minimal (default, skip home persona + skill catalog) | subagent",
435
+ description: "minimal (default) | subagent (keep project docs)",
84
436
  },
85
437
  tools: {
86
438
  type: "object",
87
- description: "Weaken-only tool policy. {mode:'allow'|'deny', names:['bash','read']}.",
88
439
  properties: {
89
440
  mode: { type: "string" },
90
441
  names: { type: "array", items: { type: "string" } },
91
442
  },
92
443
  additionalProperties: false,
444
+ description: "Child tool allow|deny on top of role.",
445
+ },
446
+ env_prep: {
447
+ type: "string",
448
+ description: `Advanced ${MEMBER_PACK_BRIEFCASE}: env/SETUP.md (links, installs). Never auto-run. Merge on id= revise.`,
449
+ },
450
+ skills: {
451
+ type: "array",
452
+ items: { type: "string" },
453
+ description: `Advanced: skill dirs → ${MEMBER_PACK_BRIEFCASE} skills/<id>/ (full tree incl. references/). Merge on id=.`,
454
+ },
455
+ files: {
456
+ type: "array",
457
+ items: { type: "string" },
458
+ description: `Advanced: paths → ${MEMBER_PACK_BRIEFCASE} workspace/ (materials; not session cwd). Merge on id=.`,
93
459
  },
94
460
  },
95
461
  additionalProperties: false,
96
462
  },
97
- presentCall: (args) => ({
98
- card: "generic",
99
- title: "team_save",
100
- kind: "execute",
101
- rawInput: args,
102
- }),
463
+ presentCall: (args) => teamPresent("team_save", "execute", args),
103
464
  execute: async (args) => {
104
465
  const a = readArgs(args);
105
466
  const idProblem = memberIdProblem(a.id);
@@ -108,30 +469,199 @@ export function bindAgentRosterTools(tools, options) {
108
469
  const catalogProblem = catalogMemberWriteProblem(a.id);
109
470
  if (catalogProblem)
110
471
  return { content: `team_save: ${catalogProblem}`, isError: true };
111
- const captured = a.from_session === true
112
- ? captureSessionPlaybook(runtime, sessionId)
113
- : undefined;
114
- const name = String(a.name ?? "").trim() || captured?.name || "";
115
- const playbook = String(a.playbook ?? "").trim() || captured?.playbook || "";
472
+ const name = String(a.name ?? "").trim();
473
+ const playbook = String(a.playbook ?? "").trim();
116
474
  const roleRaw = typeof a.role === "string" ? a.role : "worker";
117
475
  const canvasId = ws();
118
- const scope = parseRosterScope(a.scope);
476
+ const wantsAdvanced = a.kind === "advanced" ||
477
+ (typeof a.env_prep === "string" && a.env_prep.trim().length > 0) ||
478
+ (Array.isArray(a.skills) && a.skills.length > 0) ||
479
+ (Array.isArray(a.files) && a.files.length > 0);
480
+ const scope = parseRosterScope(a.scope ?? (wantsAdvanced ? "global" : "workspace"));
119
481
  if (scope === undefined) {
120
482
  return {
121
483
  content: 'team_save: scope must be "workspace" or "global"',
122
484
  isError: true,
123
485
  };
124
486
  }
125
- const tools = parseMemberToolPolicy(a.tools);
487
+ if (typeof a.id === "string" && a.id.trim()) {
488
+ const existing = runtime.agentRoster.get(canvasId, a.id.trim());
489
+ if (!existing) {
490
+ return {
491
+ content: missingMemberHint(runtime, canvasId, a.id.trim(), "team_save"),
492
+ isError: true,
493
+ };
494
+ }
495
+ const toolsPatch = a.tools === null
496
+ ? null
497
+ : a.tools !== undefined
498
+ ? parseMemberToolPolicy(a.tools)
499
+ : undefined;
500
+ if (a.tools !== undefined && a.tools !== null && !toolsPatch) {
501
+ return {
502
+ content: 'team_save: tools needs { mode: "allow"|"deny", names: [...] }',
503
+ isError: true,
504
+ };
505
+ }
506
+ const hasName = name.length > 0;
507
+ const hasPlaybook = playbook.length > 0;
508
+ const hasSurface = hasName ||
509
+ hasPlaybook ||
510
+ typeof a.brief === "string" ||
511
+ a.inject === "minimal" ||
512
+ a.inject === "subagent" ||
513
+ a.tools !== undefined;
514
+ if (existing.kind === "advanced") {
515
+ const rosterWs = existing.scope === "global" ? GLOBAL_ROSTER_ID : canvasId;
516
+ const packOpts = {
517
+ rosterWorkspaceId: rosterWs,
518
+ memberId: existing.id,
519
+ productHome: runtime.agentRoster.home,
520
+ };
521
+ let inventory = listMemberPackInventory(packOpts);
522
+ const skillPaths = resolveCwdPaths(runtime, sessionId, a.skills);
523
+ const filePaths = resolveCwdPaths(runtime, sessionId, a.files);
524
+ const envPrep = typeof a.env_prep === "string" && a.env_prep.trim()
525
+ ? a.env_prep.trim()
526
+ : undefined;
527
+ if (skillPaths.length > 0 || filePaths.length > 0 || envPrep) {
528
+ let skillsFiles;
529
+ if (skillPaths.length > 0) {
530
+ const bundled = collectPackSkillFiles(skillPaths);
531
+ if (!bundled.ok) {
532
+ return { content: `team_save: ${bundled.error}`, isError: true };
533
+ }
534
+ skillsFiles = bundled.files;
535
+ }
536
+ let workspaceFiles;
537
+ if (filePaths.length > 0) {
538
+ const bundled = collectPackWorkspaceFiles(filePaths);
539
+ if (!bundled.ok) {
540
+ return { content: `team_save: ${bundled.error}`, isError: true };
541
+ }
542
+ workspaceFiles = bundled.files;
543
+ }
544
+ const merged = mergeIntoMemberPack({
545
+ ...packOpts,
546
+ ...(skillsFiles ? { skillsFiles } : {}),
547
+ ...(workspaceFiles ? { workspaceFiles } : {}),
548
+ ...(envPrep ? { envPrep } : {}),
549
+ });
550
+ if (!merged.ok) {
551
+ return { content: `team_save: ${merged.error}`, isError: true };
552
+ }
553
+ inventory = merged.inventory;
554
+ }
555
+ let patched = existing;
556
+ if (hasSurface) {
557
+ const next = patchExistingMember(runtime, canvasId, existing, {
558
+ ...(hasName ? { name } : {}),
559
+ ...(hasPlaybook ? { playbook } : {}),
560
+ ...(typeof a.brief === "string" ? { brief: a.brief } : {}),
561
+ ...(a.inject === "minimal" || a.inject === "subagent"
562
+ ? { inject: a.inject }
563
+ : {}),
564
+ ...(a.tools !== undefined ? { tools: toolsPatch ?? null } : {}),
565
+ });
566
+ if (!next) {
567
+ return {
568
+ content: "team_save: name and playbook required",
569
+ isError: true,
570
+ };
571
+ }
572
+ patched = next;
573
+ }
574
+ else if (skillPaths.length === 0 &&
575
+ filePaths.length === 0 &&
576
+ !envPrep) {
577
+ return {
578
+ content: "team_save: pass playbook/brief/inject/tools and/or skills/files/env_prep to revise",
579
+ isError: true,
580
+ };
581
+ }
582
+ return {
583
+ content: JSON.stringify({
584
+ workspaceId: patched.scope === "global" ? "global" : canvasId,
585
+ member: {
586
+ ...enrichListedMember(runtime, canvasId, patched),
587
+ playbook: patched.playbook,
588
+ },
589
+ kind: "advanced",
590
+ briefcase: MEMBER_PACK_BRIEFCASE,
591
+ ...(inventory ? { inventory } : {}),
592
+ next: patched.ready
593
+ ? "subagent member_id · team_export to share"
594
+ : "follow envPrep → team_ready → subagent member_id",
595
+ }),
596
+ };
597
+ }
598
+ // Thin revise (or upgrade path falls through to mint when wantsAdvanced).
599
+ if (!wantsAdvanced) {
600
+ if (!hasSurface) {
601
+ return {
602
+ content: "team_save: pass playbook/brief/inject/tools to revise thin",
603
+ isError: true,
604
+ };
605
+ }
606
+ const patched = patchExistingMember(runtime, canvasId, existing, {
607
+ ...(hasName ? { name } : {}),
608
+ ...(hasPlaybook ? { playbook } : {}),
609
+ ...(typeof a.brief === "string" ? { brief: a.brief } : {}),
610
+ ...(a.inject === "minimal" || a.inject === "subagent"
611
+ ? { inject: a.inject }
612
+ : {}),
613
+ ...(a.tools !== undefined ? { tools: toolsPatch ?? null } : {}),
614
+ });
615
+ if (!patched) {
616
+ return {
617
+ content: "team_save: name and playbook required",
618
+ isError: true,
619
+ };
620
+ }
621
+ return {
622
+ content: JSON.stringify({
623
+ workspaceId: patched.scope === "global" ? "global" : canvasId,
624
+ member: enrichListedMember(runtime, canvasId, patched),
625
+ kind: "thin",
626
+ next: "subagent member_id",
627
+ }),
628
+ };
629
+ }
630
+ }
631
+ if (wantsAdvanced) {
632
+ const minted = mintAdvancedMember({
633
+ runtime,
634
+ sessionId,
635
+ canvasId,
636
+ scope,
637
+ name,
638
+ playbook,
639
+ roleRaw,
640
+ ...(typeof a.brief === "string" ? { brief: a.brief } : {}),
641
+ inject: a.inject,
642
+ tools: a.tools,
643
+ ...(typeof a.env_prep === "string" ? { envPrep: a.env_prep } : {}),
644
+ skills: a.skills,
645
+ files: a.files,
646
+ ...(typeof a.id === "string" ? { id: a.id } : {}),
647
+ });
648
+ return {
649
+ content: minted.content,
650
+ ...(minted.ok ? {} : { isError: true }),
651
+ };
652
+ }
653
+ const toolsPolicy = parseMemberToolPolicy(a.tools);
126
654
  const member = runtime.agentRoster.upsertAtScope(canvasId, scope, {
127
655
  name,
128
656
  playbook,
129
657
  role: isAgentTeamSpawnRole(roleRaw) ? roleRaw : "worker",
658
+ kind: "thin",
659
+ ready: true,
130
660
  ...(typeof a.brief === "string" ? { brief: a.brief } : {}),
131
661
  ...(a.inject === "subagent" || a.inject === "minimal"
132
662
  ? { inject: a.inject }
133
663
  : {}),
134
- ...(tools ? { tools } : {}),
664
+ ...(toolsPolicy ? { tools: toolsPolicy } : {}),
135
665
  ...(typeof a.id === "string" ? { id: a.id } : {}),
136
666
  });
137
667
  if (!member) {
@@ -141,53 +671,290 @@ export function bindAgentRosterTools(tools, options) {
141
671
  content: JSON.stringify({
142
672
  workspaceId: scope === "global" ? "global" : canvasId,
143
673
  member,
674
+ kind: "thin",
675
+ next: "subagent member_id",
144
676
  }),
145
677
  };
146
678
  },
147
679
  });
148
680
  registerTool(tools, {
149
681
  name: "team_import",
150
- description: "Import a 干员 from an xrk-agent-member card (JSON). " +
151
- "Appearance, dressing, and ids are ignored — a new workspace member is minted. " +
152
- "Use when the user pastes or attaches someone else's exported member.",
682
+ description: "Install a friend-shared 干员: card= (thin JSON) or pack= (advanced zip/dir/attachment → 公文包/briefcase). " +
683
+ "Then follow envPrep with this main session → team_ready. Illegal pack: inspect/fix → re-import. " +
684
+ "After import you may team_save id= to edit playbook or merge skills/files. Mint new → team_save (not this).",
153
685
  parameters: {
154
686
  type: "object",
155
687
  properties: {
156
688
  card: {
157
689
  type: "string",
158
- description: "JSON object with kind xrk-agent-member, name, playbook, optional brief/role/inject/tools.",
690
+ description: "Thin JSON (kind xrk-agent-member). Omit when pack.",
691
+ },
692
+ pack: {
693
+ type: "string",
694
+ description: "Marked pack zip/dir path (cwd-relative OK) or upload id " +
695
+ "(attachment:sha256:… / sha256:…). Omit when card.",
159
696
  },
160
697
  scope: {
161
698
  type: "string",
162
- description: "global | workspace (default workspace)",
699
+ description: "global | workspace. Defaults: card→workspace; pack→global (shareable).",
163
700
  },
164
701
  },
165
- required: ["card"],
166
702
  additionalProperties: false,
167
703
  },
168
- presentCall: (args) => ({
169
- card: "generic",
170
- title: "team_import",
171
- kind: "execute",
172
- rawInput: args,
173
- }),
704
+ presentCall: (args) => teamPresent("team_import", "execute", args),
174
705
  execute: async (args) => {
175
706
  const a = readArgs(args);
176
- const parsed = parseAgentMemberCard(a.card);
177
- if (!parsed.ok)
178
- return { content: `team_import: ${parsed.error}`, isError: true };
179
- const scope = parseRosterScope(a.scope ?? "workspace");
707
+ if (a.from_session === true) {
708
+ return {
709
+ content: "team_import: minting moved to team_save (kind=advanced + name/playbook/skills/files). " +
710
+ "team_import is only card= or pack= install.",
711
+ isError: true,
712
+ };
713
+ }
714
+ const hasCard = typeof a.card === "string" && a.card.trim().length > 0;
715
+ const hasPack = typeof a.pack === "string" && a.pack.trim().length > 0;
716
+ if (hasCard === hasPack) {
717
+ return {
718
+ content: "team_import: set exactly one of card (JSON) or pack (path|attachment). " +
719
+ "Mint new → team_save.",
720
+ isError: true,
721
+ };
722
+ }
723
+ const scope = parseRosterScope(a.scope ?? (hasCard ? "workspace" : "global"));
180
724
  if (scope === undefined) {
181
725
  return { content: 'team_import: scope must be "workspace" or "global"', isError: true };
182
726
  }
183
727
  const canvasId = ws();
184
- const member = runtime.agentRoster.upsertAtScope(canvasId, scope, parsed.card);
728
+ const rosterWs = scope === "global" ? GLOBAL_ROSTER_ID : canvasId;
729
+ const visibility = rosterScopeVisibility(scope, rosterWs);
730
+ if (hasCard) {
731
+ const parsed = parseAgentMemberCard(a.card);
732
+ if (!parsed.ok)
733
+ return { content: `team_import: ${parsed.error}`, isError: true };
734
+ const member = runtime.agentRoster.upsertAtScope(canvasId, scope, {
735
+ ...parsed.card,
736
+ kind: "thin",
737
+ ready: true,
738
+ });
739
+ if (!member)
740
+ return { content: "team_import: name and playbook required", isError: true };
741
+ return {
742
+ content: JSON.stringify({
743
+ workspaceId: rosterWs,
744
+ scope,
745
+ visibility,
746
+ member,
747
+ source: "card",
748
+ next: "subagent member_id",
749
+ }),
750
+ };
751
+ }
752
+ const packLabel = String(a.pack);
753
+ const loaded = await loadPackFromArg(runtime, sessionId, packLabel);
754
+ if (!loaded.ok)
755
+ return { content: `team_import: ${loaded.error}`, isError: true };
756
+ const member = runtime.agentRoster.upsertAtScope(canvasId, scope, {
757
+ ...loaded.pack.card,
758
+ kind: "advanced",
759
+ ready: false,
760
+ });
185
761
  if (!member)
186
762
  return { content: "team_import: name and playbook required", isError: true };
763
+ const placed = materializeAgentMemberPack(loaded.pack, {
764
+ rosterWorkspaceId: rosterWs,
765
+ memberId: member.id,
766
+ productHome: runtime.agentRoster.home,
767
+ });
187
768
  return {
188
769
  content: JSON.stringify({
189
- workspaceId: scope === "global" ? "global" : canvasId,
770
+ workspaceId: rosterWs,
771
+ scope,
772
+ visibility,
190
773
  member,
774
+ source: "pack",
775
+ pack: packLabel,
776
+ packDir: placed.packDir,
777
+ skillNames: placed.skillNames,
778
+ ready: false,
779
+ ...(placed.envPrep
780
+ ? {
781
+ envPrep: placed.envPrep,
782
+ envHint: "Review envPrep here (or hand zip to a friend). Then team_ready. Do not auto-run scripts.",
783
+ }
784
+ : {
785
+ envHint: "No env/SETUP.md — team_ready when configured; friend gets playbook (± skills).",
786
+ }),
787
+ next: "team_ready → subagent member_id",
788
+ layout: AGENT_MEMBER_PACK_LAYOUT,
789
+ pathHint: `Spawn keeps parent workspace cwd. ${MEMBER_PACK_BRIEFCASE} = pack_home (injected) — child may read/edit/copy into workspace. Share zip with relative paths under the pack tree.`,
790
+ shareHint: "Friend: team_export zip / UI import, or they team_import pack=… → follow envPrep with main session → team_ready. " +
791
+ "scope=global keeps it on every canvas team_list.",
792
+ }),
793
+ };
794
+ },
795
+ });
796
+ registerTool(tools, {
797
+ name: "team_ready",
798
+ description: "Mark an advanced 干员 ready after env prep. " +
799
+ "Triggers: team_list shows not-ready; UI zip import; user says 帮我就绪 / mark ready / prep done. " +
800
+ "Flow: team_list → read envPrep → help install if needed → team_ready id=. " +
801
+ "Thin members stay ready. Required before subagent member_id on advanced.",
802
+ parameters: {
803
+ type: "object",
804
+ properties: {
805
+ id: { type: "string", description: "mem_… from team_list (not-ready advanced)" },
806
+ ready: {
807
+ type: "boolean",
808
+ description: "Default true. Pass false to mark not-ready again.",
809
+ },
810
+ },
811
+ required: ["id"],
812
+ additionalProperties: false,
813
+ },
814
+ presentCall: (args) => teamPresent("team_ready", "execute", args),
815
+ execute: async (args) => {
816
+ const a = readArgs(args);
817
+ const id = String(a.id ?? "").trim();
818
+ if (!id)
819
+ return { content: "team_ready: id required", isError: true };
820
+ const canvasId = ws();
821
+ const updated = runtime.agentRoster.setReady(canvasId, id, a.ready === false ? false : true);
822
+ if (!updated) {
823
+ return { content: missingMemberHint(runtime, canvasId, id, "team_ready"), isError: true };
824
+ }
825
+ const listed = runtime.agentRoster.get(canvasId, updated.id);
826
+ return {
827
+ content: JSON.stringify({
828
+ member: listed ? enrichListedMember(runtime, canvasId, listed) : updated,
829
+ next: updated.ready ? "subagent member_id" : "configure then team_ready again",
830
+ }),
831
+ };
832
+ },
833
+ });
834
+ registerTool(tools, {
835
+ name: "team_export",
836
+ description: "Export a custom 干员 for the user: thin→JSON, advanced→zip (公文包/briefcase: playbook+skills+references+workspace+env). " +
837
+ "Writes to dir= (default session cwd). Catalog mem_seed_* cannot export — mint a copy first. " +
838
+ "User said 导出/分享这个干员 / export member. UI Export is equivalent.",
839
+ parameters: {
840
+ type: "object",
841
+ properties: {
842
+ id: { type: "string", description: "mem_… from team_list (not catalog)" },
843
+ dir: {
844
+ type: "string",
845
+ description: "Output directory (cwd-relative or absolute). Default: session cwd.",
846
+ },
847
+ },
848
+ required: ["id"],
849
+ additionalProperties: false,
850
+ },
851
+ presentCall: (args) => teamPresent("team_export", "execute", args),
852
+ execute: async (args) => {
853
+ const a = readArgs(args);
854
+ const id = String(a.id ?? "").trim();
855
+ if (!id)
856
+ return { content: "team_export: id required", isError: true };
857
+ const exported = await dispatchFaceMethod(runtime, "team.export", `te-${Date.now()}`, {
858
+ sessionId,
859
+ id,
860
+ });
861
+ if (!exported.result.ok) {
862
+ const msg = typeof exported.result.error === "object" &&
863
+ exported.result.error &&
864
+ "message" in exported.result.error
865
+ ? String(exported.result.error.message)
866
+ : "export failed";
867
+ return { content: `team_export: ${msg}`, isError: true };
868
+ }
869
+ const value = exported.result.value;
870
+ const cwd = resolveSessionCwd(runtime, sessionId);
871
+ const dirRaw = typeof a.dir === "string" ? a.dir.trim() : "";
872
+ const outDir = dirRaw
873
+ ? path.isAbsolute(dirRaw)
874
+ ? dirRaw
875
+ : path.resolve(cwd, dirRaw)
876
+ : cwd;
877
+ const outPath = path.join(outDir, value.fileName);
878
+ try {
879
+ if (value.format === "json" && typeof value.text === "string") {
880
+ writeFileSync(outPath, value.text, "utf8");
881
+ }
882
+ else if (value.format === "zip" && typeof value.base64 === "string") {
883
+ writeFileSync(outPath, Buffer.from(value.base64, "base64"));
884
+ }
885
+ else {
886
+ return { content: "team_export: empty payload", isError: true };
887
+ }
888
+ }
889
+ catch (err) {
890
+ return {
891
+ content: `team_export: write failed — ${err instanceof Error ? err.message : String(err)}`,
892
+ isError: true,
893
+ };
894
+ }
895
+ return {
896
+ content: JSON.stringify({
897
+ format: value.format,
898
+ path: outPath,
899
+ fileName: value.fileName,
900
+ ...(value.packIncomplete
901
+ ? { packIncomplete: true, hint: value.hint }
902
+ : {}),
903
+ next: value.format === "zip"
904
+ ? "Hand zip → team_import pack= → follow envPrep → team_ready"
905
+ : "Hand JSON → team_import card= or UI import",
906
+ }),
907
+ };
908
+ },
909
+ });
910
+ registerTool(tools, {
911
+ name: "team_remove",
912
+ description: "Uninstall a custom 干员 (not catalog mem_seed_*). Like deleting a skill. " +
913
+ "Pass id from team_list; scope global|workspace must match where it lives.",
914
+ parameters: {
915
+ type: "object",
916
+ properties: {
917
+ id: { type: "string", description: "mem_… to delete" },
918
+ scope: {
919
+ type: "string",
920
+ description: "global | workspace — must match team_list scope for that id",
921
+ },
922
+ },
923
+ required: ["id", "scope"],
924
+ additionalProperties: false,
925
+ },
926
+ presentCall: (args) => teamPresent("team_remove", "execute", args),
927
+ execute: async (args) => {
928
+ const a = readArgs(args);
929
+ const id = String(a.id ?? "").trim();
930
+ if (!id)
931
+ return { content: "team_remove: id required", isError: true };
932
+ const catalogProblem = catalogMemberWriteProblem(id);
933
+ if (catalogProblem) {
934
+ return { content: `team_remove: ${catalogProblem}`, isError: true };
935
+ }
936
+ const scope = parseRosterScope(a.scope);
937
+ if (scope === undefined) {
938
+ return {
939
+ content: 'team_remove: scope must be "workspace" or "global"',
940
+ isError: true,
941
+ };
942
+ }
943
+ const canvasId = ws();
944
+ const rosterWs = scope === "global" ? GLOBAL_ROSTER_ID : canvasId;
945
+ const ok = runtime.agentRoster.remove(rosterWs, id);
946
+ if (!ok) {
947
+ return {
948
+ content: missingMemberHint(runtime, canvasId, id, "team_remove"),
949
+ isError: true,
950
+ };
951
+ }
952
+ return {
953
+ content: JSON.stringify({
954
+ workspaceId: rosterWs,
955
+ deleted: true,
956
+ id,
957
+ next: "gone from team_list; zip/JSON copies elsewhere are untouched",
191
958
  }),
192
959
  };
193
960
  },