@xrkseek/harness 0.6.8 → 0.6.10

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 (60) hide show
  1. package/node_modules/@xrkseek/server-face/dist/agent-member-pack.d.ts +96 -0
  2. package/node_modules/@xrkseek/server-face/dist/agent-member-pack.d.ts.map +1 -1
  3. package/node_modules/@xrkseek/server-face/dist/agent-member-pack.js +365 -9
  4. package/node_modules/@xrkseek/server-face/dist/agent-member-pack.js.map +1 -1
  5. package/node_modules/@xrkseek/server-face/dist/agent-roster-store.d.ts +8 -1
  6. package/node_modules/@xrkseek/server-face/dist/agent-roster-store.d.ts.map +1 -1
  7. package/node_modules/@xrkseek/server-face/dist/agent-roster-store.js +21 -1
  8. package/node_modules/@xrkseek/server-face/dist/agent-roster-store.js.map +1 -1
  9. package/node_modules/@xrkseek/server-face/dist/agent-roster-tools.d.ts +7 -2
  10. package/node_modules/@xrkseek/server-face/dist/agent-roster-tools.d.ts.map +1 -1
  11. package/node_modules/@xrkseek/server-face/dist/agent-roster-tools.js +655 -76
  12. package/node_modules/@xrkseek/server-face/dist/agent-roster-tools.js.map +1 -1
  13. package/node_modules/@xrkseek/server-face/dist/agent-team-roles.d.ts +9 -0
  14. package/node_modules/@xrkseek/server-face/dist/agent-team-roles.d.ts.map +1 -1
  15. package/node_modules/@xrkseek/server-face/dist/agent-team-roles.js +25 -2
  16. package/node_modules/@xrkseek/server-face/dist/agent-team-roles.js.map +1 -1
  17. package/node_modules/@xrkseek/server-face/dist/capture-member-playbook.d.ts +4 -0
  18. package/node_modules/@xrkseek/server-face/dist/capture-member-playbook.d.ts.map +1 -1
  19. package/node_modules/@xrkseek/server-face/dist/capture-member-playbook.js +28 -7
  20. package/node_modules/@xrkseek/server-face/dist/capture-member-playbook.js.map +1 -1
  21. package/node_modules/@xrkseek/server-face/dist/handlers/team.d.ts.map +1 -1
  22. package/node_modules/@xrkseek/server-face/dist/handlers/team.js +37 -3
  23. package/node_modules/@xrkseek/server-face/dist/handlers/team.js.map +1 -1
  24. package/node_modules/@xrkseek/server-face/dist/presets-catalog.d.ts.map +1 -1
  25. package/node_modules/@xrkseek/server-face/dist/presets-catalog.js +6 -3
  26. package/node_modules/@xrkseek/server-face/dist/presets-catalog.js.map +1 -1
  27. package/node_modules/@xrkseek/server-face/dist/runtime.d.ts.map +1 -1
  28. package/node_modules/@xrkseek/server-face/dist/runtime.js +7 -2
  29. package/node_modules/@xrkseek/server-face/dist/runtime.js.map +1 -1
  30. package/node_modules/@xrkseek/server-face/dist/session-capability.d.ts.map +1 -1
  31. package/node_modules/@xrkseek/server-face/dist/session-capability.js +30 -2
  32. package/node_modules/@xrkseek/server-face/dist/session-capability.js.map +1 -1
  33. package/node_modules/@xrkseek/server-face/dist/session-posture.d.ts.map +1 -1
  34. package/node_modules/@xrkseek/server-face/dist/session-posture.js +3 -1
  35. package/node_modules/@xrkseek/server-face/dist/session-posture.js.map +1 -1
  36. package/node_modules/@xrkseek/server-face/dist/session-tool-copy.d.ts +6 -1
  37. package/node_modules/@xrkseek/server-face/dist/session-tool-copy.d.ts.map +1 -1
  38. package/node_modules/@xrkseek/server-face/dist/session-tool-copy.js +10 -10
  39. package/node_modules/@xrkseek/server-face/dist/session-tool-copy.js.map +1 -1
  40. package/node_modules/@xrkseek/server-face/dist/session-tools.d.ts.map +1 -1
  41. package/node_modules/@xrkseek/server-face/dist/session-tools.js +5 -0
  42. package/node_modules/@xrkseek/server-face/dist/session-tools.js.map +1 -1
  43. package/node_modules/@xrkseek/server-face/dist/subagent-tools.d.ts.map +1 -1
  44. package/node_modules/@xrkseek/server-face/dist/subagent-tools.js +32 -3
  45. package/node_modules/@xrkseek/server-face/dist/subagent-tools.js.map +1 -1
  46. package/node_modules/@xrkseek/server-face/dist/xiangyang-skill-bodies/demand-translate.d.ts +1 -1
  47. package/node_modules/@xrkseek/server-face/dist/xiangyang-skill-bodies/demand-translate.d.ts.map +1 -1
  48. package/node_modules/@xrkseek/server-face/dist/xiangyang-skill-bodies/demand-translate.js +25 -7
  49. package/node_modules/@xrkseek/server-face/dist/xiangyang-skill-bodies/demand-translate.js.map +1 -1
  50. package/node_modules/@xrkseek/server-face/dist/xiangyang-skill-bodies/home-config.d.ts +1 -1
  51. package/node_modules/@xrkseek/server-face/dist/xiangyang-skill-bodies/home-config.d.ts.map +1 -1
  52. package/node_modules/@xrkseek/server-face/dist/xiangyang-skill-bodies/home-config.js +18 -5
  53. package/node_modules/@xrkseek/server-face/dist/xiangyang-skill-bodies/home-config.js.map +1 -1
  54. package/node_modules/@xrkseek/server-face/dist/xiangyang-skill-bodies/index.d.ts +1 -1
  55. package/node_modules/@xrkseek/server-face/dist/xiangyang-skill-bodies/index.js +1 -1
  56. package/node_modules/@xrkseek/server-face/dist/xiangyang-skill-bodies/skill-merge.d.ts +1 -1
  57. package/node_modules/@xrkseek/server-face/dist/xiangyang-skill-bodies/skill-merge.d.ts.map +1 -1
  58. package/node_modules/@xrkseek/server-face/dist/xiangyang-skill-bodies/skill-merge.js +12 -7
  59. package/node_modules/@xrkseek/server-face/dist/xiangyang-skill-bodies/skill-merge.js.map +1 -1
  60. package/package.json +1 -1
@@ -1,19 +1,24 @@
1
1
  /**
2
- * Model-facing team_list / team_save / team_import / team_ready.
3
- * Pack import materializes under agent-rosters/packs/ (child-only skills).
2
+ * Model-facing team_list / save / import / ready / export / remove /
3
+ * team_self (child own surfaces). Packs under agent-rosters/packs/.
4
4
  */
5
+ import { writeFileSync } from "node:fs";
5
6
  import path from "node:path";
6
7
  import { readSessionEvents } from "@xrkseek/core-session";
7
8
  import { isAttachmentError } from "@xrkseek/attachment";
8
9
  import { canvasWorkspaceIdForSession } from "./canvas-tools.js";
9
10
  import { isAgentTeamSpawnRole } from "./agent-team-roles.js";
10
- import { GLOBAL_ROSTER_ID, memberIdProblem, catalogMemberWriteProblem, parseMemberToolPolicy, parseRosterScope, rosterPublicFields, } from "./agent-roster-store.js";
11
- import { parseAgentMemberCard } from "./agent-member-card.js";
12
- import { loadAgentMemberPack, loadAgentMemberPackBytes, materializeAgentMemberPack, notReadyPackHints, } from "./agent-member-pack.js";
13
- import { captureSessionPlaybook } from "./capture-member-playbook.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";
14
15
  import { resolveSessionCwd } from "./session-cwd.js";
15
16
  import { referencedAttachmentIds } from "./session-attachment.js";
16
- import { liveRosterCatalog, packShareFlowHint, rosterScopeVisibility, teamListDescription, } from "./session-tool-copy.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
+ }
17
22
  function missingMemberHint(runtime, canvasId, id, tool) {
18
23
  const home = runtime.agentRoster.findHome(id);
19
24
  if (!home) {
@@ -86,35 +91,204 @@ function enrichListedMember(runtime, canvasId, row) {
86
91
  ...rosterPublicFields(row),
87
92
  visibility: rosterScopeVisibility(row.scope === "global" ? "global" : "workspace", rosterWs),
88
93
  };
89
- if (row.kind !== "advanced" || row.ready)
94
+ if (row.kind !== "advanced")
90
95
  return pub;
91
- return {
96
+ const packOpts = {
97
+ rosterWorkspaceId: rosterWs,
98
+ memberId: row.id,
99
+ productHome: runtime.agentRoster.home,
100
+ };
101
+ const inventory = listMemberPackInventory(packOpts);
102
+ const base = {
92
103
  ...pub,
93
- ...notReadyPackHints({
94
- rosterWorkspaceId: rosterWs,
95
- memberId: row.id,
96
- productHome: runtime.agentRoster.home,
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.",
97
234
  }),
98
235
  };
99
236
  }
100
- /** Register team_list / team_save / team_import / team_ready (or list-only). */
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). */
101
278
  export function bindAgentRosterTools(tools, options) {
102
279
  const { runtime, sessionId, listOnly = false } = options;
280
+ const selfMemberId = options.selfMemberId?.trim() || undefined;
103
281
  const ws = () => canvasWorkspaceIdForSession(runtime, sessionId);
104
282
  registerTool(tools, {
105
283
  name: "team_list",
106
284
  description: "List Agent Team members (干员) for this workspace + global.",
107
285
  parameters: { type: "object", properties: {}, additionalProperties: false },
108
286
  isConcurrencySafe: () => true,
109
- presentCall: () => ({
110
- card: "generic",
111
- title: "team_list",
112
- kind: "search",
113
- rawInput: {},
114
- }),
287
+ presentCall: () => teamPresent("team_list", "search"),
115
288
  dynamicSchema: () => ({
116
289
  description: teamListDescription({
117
290
  listOnly,
291
+ ...(selfMemberId ? { selfRevise: true } : {}),
118
292
  catalog: liveRosterCatalog(runtime, sessionId),
119
293
  }),
120
294
  }),
@@ -126,33 +300,139 @@ export function bindAgentRosterTools(tools, options) {
126
300
  return { content: JSON.stringify({ workspaceId, members }) };
127
301
  },
128
302
  });
129
- 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
+ }
130
398
  return;
399
+ }
131
400
  registerTool(tools, {
132
401
  name: "team_save",
133
- description: "Mint/update a thin 干员 (always ready). Not propose_skill. Catalog mem_seed_* are read-only — omit id. " +
134
- "from_session=true fills name/playbook from this chat. scope global|workspace. " +
135
- "Advanced pack members: use team_import pack= (not team_save). " +
136
- packShareFlowHint(),
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).",
137
407
  parameters: {
138
408
  type: "object",
139
409
  properties: {
140
410
  id: {
141
411
  type: "string",
142
- description: "mem_ then [A-Za-z0-9][A-Za-z0-9._-]*, max 64 chars. Omit to mint.",
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.",
143
417
  },
144
- name: { type: "string" },
145
418
  playbook: {
146
419
  type: "string",
147
- description: "Standalone child instructions (max 20000 chars).",
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.",
148
422
  },
149
423
  role: { type: "string" },
150
- from_session: { type: "boolean" },
151
- brief: { type: "string" },
152
- scope: { type: "string", description: "global | workspace (default workspace)" },
424
+ kind: {
425
+ type: "string",
426
+ description: "thin (default) | advanced. Also advanced when skills/files/env_prep set.",
427
+ },
428
+ brief: { type: "string", description: "One-line duty for team_list." },
429
+ scope: {
430
+ type: "string",
431
+ description: "global | workspace. Thin default workspace; advanced default global.",
432
+ },
153
433
  inject: {
154
434
  type: "string",
155
- description: "minimal (default) | subagent",
435
+ description: "minimal (default) | subagent (keep project docs)",
156
436
  },
157
437
  tools: {
158
438
  type: "object",
@@ -161,16 +441,26 @@ export function bindAgentRosterTools(tools, options) {
161
441
  names: { type: "array", items: { type: "string" } },
162
442
  },
163
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=.`,
164
459
  },
165
460
  },
166
461
  additionalProperties: false,
167
462
  },
168
- presentCall: (args) => ({
169
- card: "generic",
170
- title: "team_save",
171
- kind: "execute",
172
- rawInput: args,
173
- }),
463
+ presentCall: (args) => teamPresent("team_save", "execute", args),
174
464
  execute: async (args) => {
175
465
  const a = readArgs(args);
176
466
  const idProblem = memberIdProblem(a.id);
@@ -179,14 +469,15 @@ export function bindAgentRosterTools(tools, options) {
179
469
  const catalogProblem = catalogMemberWriteProblem(a.id);
180
470
  if (catalogProblem)
181
471
  return { content: `team_save: ${catalogProblem}`, isError: true };
182
- const captured = a.from_session === true
183
- ? captureSessionPlaybook(runtime, sessionId)
184
- : undefined;
185
- const name = String(a.name ?? "").trim() || captured?.name || "";
186
- const playbook = String(a.playbook ?? "").trim() || captured?.playbook || "";
472
+ const name = String(a.name ?? "").trim();
473
+ const playbook = String(a.playbook ?? "").trim();
187
474
  const roleRaw = typeof a.role === "string" ? a.role : "worker";
188
475
  const canvasId = ws();
189
- 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"));
190
481
  if (scope === undefined) {
191
482
  return {
192
483
  content: 'team_save: scope must be "workspace" or "global"',
@@ -195,13 +486,169 @@ export function bindAgentRosterTools(tools, options) {
195
486
  }
196
487
  if (typeof a.id === "string" && a.id.trim()) {
197
488
  const existing = runtime.agentRoster.get(canvasId, a.id.trim());
198
- if (existing?.kind === "advanced") {
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) {
199
501
  return {
200
- content: "team_save: refuse — this id is an advanced pack member. " +
201
- "Prep with team_list envPrep, then team_ready (or import a new pack). Do not downgrade via team_save.",
502
+ content: 'team_save: tools needs { mode: "allow"|"deny", names: [...] }',
202
503
  isError: true,
203
504
  };
204
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
+ };
205
652
  }
206
653
  const toolsPolicy = parseMemberToolPolicy(a.tools);
207
654
  const member = runtime.agentRoster.upsertAtScope(canvasId, scope, {
@@ -232,45 +679,48 @@ export function bindAgentRosterTools(tools, options) {
232
679
  });
233
680
  registerTool(tools, {
234
681
  name: "team_import",
235
- description: packShareFlowHint(),
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).",
236
685
  parameters: {
237
686
  type: "object",
238
687
  properties: {
239
688
  card: {
240
689
  type: "string",
241
- description: "Thin JSON (kind xrk-agent-member). Omit when pack is set.",
690
+ description: "Thin JSON (kind xrk-agent-member). Omit when pack.",
242
691
  },
243
692
  pack: {
244
693
  type: "string",
245
694
  description: "Marked pack zip/dir path (cwd-relative OK) or upload id " +
246
- "(attachment:sha256:… / sha256:… from the chat file handle). Omit when card is set.",
695
+ "(attachment:sha256:… / sha256:…). Omit when card.",
247
696
  },
248
697
  scope: {
249
698
  type: "string",
250
- description: "global | workspace. global = every workspace; workspace = this canvas only. " +
251
- "Defaults: card→workspace, pack→global (pass workspace for project-only packs).",
699
+ description: "global | workspace. Defaults: card→workspace; pack→global (shareable).",
252
700
  },
253
701
  },
254
702
  additionalProperties: false,
255
703
  },
256
- presentCall: (args) => ({
257
- card: "generic",
258
- title: "team_import",
259
- kind: "execute",
260
- rawInput: args,
261
- }),
704
+ presentCall: (args) => teamPresent("team_import", "execute", args),
262
705
  execute: async (args) => {
263
706
  const a = readArgs(args);
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
+ }
264
714
  const hasCard = typeof a.card === "string" && a.card.trim().length > 0;
265
715
  const hasPack = typeof a.pack === "string" && a.pack.trim().length > 0;
266
716
  if (hasCard === hasPack) {
267
717
  return {
268
- content: "team_import: set exactly one of card (JSON) or pack (path|attachment id)",
718
+ content: "team_import: set exactly one of card (JSON) or pack (path|attachment). " +
719
+ "Mint new → team_save.",
269
720
  isError: true,
270
721
  };
271
722
  }
272
- // Pack defaults global (shareable); card defaults workspace. Either may override.
273
- const scope = parseRosterScope(a.scope ?? (hasPack ? "global" : "workspace"));
723
+ const scope = parseRosterScope(a.scope ?? (hasCard ? "workspace" : "global"));
274
724
  if (scope === undefined) {
275
725
  return { content: 'team_import: scope must be "workspace" or "global"', isError: true };
276
726
  }
@@ -299,8 +749,8 @@ export function bindAgentRosterTools(tools, options) {
299
749
  }),
300
750
  };
301
751
  }
302
- const packRaw = String(a.pack);
303
- const loaded = await loadPackFromArg(runtime, sessionId, packRaw);
752
+ const packLabel = String(a.pack);
753
+ const loaded = await loadPackFromArg(runtime, sessionId, packLabel);
304
754
  if (!loaded.ok)
305
755
  return { content: `team_import: ${loaded.error}`, isError: true };
306
756
  const member = runtime.agentRoster.upsertAtScope(canvasId, scope, {
@@ -322,31 +772,37 @@ export function bindAgentRosterTools(tools, options) {
322
772
  visibility,
323
773
  member,
324
774
  source: "pack",
325
- pack: packRaw,
775
+ pack: packLabel,
326
776
  packDir: placed.packDir,
327
777
  skillNames: placed.skillNames,
328
778
  ready: false,
329
779
  ...(placed.envPrep
330
780
  ? {
331
781
  envPrep: placed.envPrep,
332
- envHint: "Review envPrep in this parent session, then call team_ready. Do not auto-run untrusted scripts.",
782
+ envHint: "Review envPrep here (or hand zip to a friend). Then team_ready. Do not auto-run scripts.",
333
783
  }
334
784
  : {
335
- envHint: "No env/SETUP.md — call team_ready when the member is configured.",
785
+ envHint: "No env/SETUP.md — team_ready when configured; friend gets playbook (± skills).",
336
786
  }),
337
- next: "team_ready → subagent member_id (pack skills inject into the child catalog only)",
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.",
338
792
  }),
339
793
  };
340
794
  },
341
795
  });
342
796
  registerTool(tools, {
343
797
  name: "team_ready",
344
- description: "Mark an advanced pack 干员 ready after env prep / playbook review. " +
345
- "Required before subagent member_id when team_list shows not-ready. Thin members stay 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.",
346
802
  parameters: {
347
803
  type: "object",
348
804
  properties: {
349
- id: { type: "string", description: "mem_… member id" },
805
+ id: { type: "string", description: "mem_… from team_list (not-ready advanced)" },
350
806
  ready: {
351
807
  type: "boolean",
352
808
  description: "Default true. Pass false to mark not-ready again.",
@@ -355,12 +811,7 @@ export function bindAgentRosterTools(tools, options) {
355
811
  required: ["id"],
356
812
  additionalProperties: false,
357
813
  },
358
- presentCall: (args) => ({
359
- card: "generic",
360
- title: "team_ready",
361
- kind: "execute",
362
- rawInput: args,
363
- }),
814
+ presentCall: (args) => teamPresent("team_ready", "execute", args),
364
815
  execute: async (args) => {
365
816
  const a = readArgs(args);
366
817
  const id = String(a.id ?? "").trim();
@@ -380,5 +831,133 @@ export function bindAgentRosterTools(tools, options) {
380
831
  };
381
832
  },
382
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",
958
+ }),
959
+ };
960
+ },
961
+ });
383
962
  }
384
963
  //# sourceMappingURL=agent-roster-tools.js.map