@wowyuarm/dsh-agent-team 0.1.11 → 0.1.13

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 (114) hide show
  1. package/README.md +6 -4
  2. package/README.zh.md +6 -4
  3. package/cordis.patch.yml +7 -3
  4. package/package.json +16 -3
  5. package/packages/agent-team/README.md +4 -4
  6. package/packages/agent-team/README.zh.md +4 -4
  7. package/packages/agent-team/lib/context-projection.js +5 -1
  8. package/packages/agent-team/lib/index.js +162 -113
  9. package/packages/agent-team/lib/ledger.js +496 -169
  10. package/packages/agent-team/lib/member-context.js +18 -1
  11. package/packages/agent-team/lib/member-runtime.js +48 -9
  12. package/packages/agent-team/lib/mentions.js +80 -0
  13. package/packages/agent-team/lib/spec.js +20 -0
  14. package/packages/agent-team/lib/typert.host.js +241 -46
  15. package/packages/agent-team/lib/typert.remote-client.d.ts +7 -3
  16. package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
  17. package/packages/agent-team/lib/typert.remote-client.js +178 -28
  18. package/packages/agent-team/lib/types/context-projection.d.ts.map +1 -1
  19. package/packages/agent-team/lib/types/index.d.ts +33 -14
  20. package/packages/agent-team/lib/types/index.d.ts.map +1 -1
  21. package/packages/agent-team/lib/types/ledger.d.ts +120 -18
  22. package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
  23. package/packages/agent-team/lib/types/member-context.d.ts +6 -0
  24. package/packages/agent-team/lib/types/member-context.d.ts.map +1 -1
  25. package/packages/agent-team/lib/types/member-runtime.d.ts +2 -1
  26. package/packages/agent-team/lib/types/member-runtime.d.ts.map +1 -1
  27. package/packages/agent-team/lib/types/mentions.d.ts +47 -0
  28. package/packages/agent-team/lib/types/mentions.d.ts.map +1 -0
  29. package/packages/agent-team/lib/types/spec.d.ts.map +1 -1
  30. package/packages/agent-team/lib/types/types/entities.d.ts +3 -20
  31. package/packages/agent-team/lib/types/types/entities.d.ts.map +1 -1
  32. package/packages/agent-team/lib/types/types/operations.d.ts +37 -1
  33. package/packages/agent-team/lib/types/types/operations.d.ts.map +1 -1
  34. package/packages/agent-team/lib/types/types/requests-results.d.ts +107 -25
  35. package/packages/agent-team/lib/types/types/requests-results.d.ts.map +1 -1
  36. package/packages/agent-team/lib/types/vendor/storage-sqlite/index.d.ts +87 -0
  37. package/packages/agent-team/lib/types/vendor/storage-sqlite/index.d.ts.map +1 -0
  38. package/packages/agent-team/lib/types/vendor/storage-sqlite/schema.d.ts +59 -0
  39. package/packages/agent-team/lib/types/vendor/storage-sqlite/schema.d.ts.map +1 -0
  40. package/packages/agent-team/lib/types/vendor/storage-sqlite/unit.d.ts +57 -0
  41. package/packages/agent-team/lib/types/vendor/storage-sqlite/unit.d.ts.map +1 -0
  42. package/packages/agent-team/lib/vendor/storage-sqlite/index.js +139 -0
  43. package/packages/agent-team/lib/vendor/storage-sqlite/schema.js +120 -0
  44. package/packages/agent-team/lib/vendor/storage-sqlite/unit.js +132 -0
  45. package/packages/agent-team/preset/team-member/agent.cordis.yml +48 -8
  46. package/packages/client-agent-team/README.md +1 -1
  47. package/packages/client-agent-team/README.zh.md +1 -1
  48. package/packages/client-agent-team/lib/client.js +2123 -1406
  49. package/packages/client-agent-team/lib/client.js.map +1 -1
  50. package/packages/client-agent-team/lib/types/client/TeamAgentImport.d.ts +12 -0
  51. package/packages/client-agent-team/lib/types/client/TeamAgentImport.d.ts.map +1 -0
  52. package/packages/client-agent-team/lib/types/client/TeamAgentImport.js +69 -0
  53. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts +3 -1
  54. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
  55. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +31 -19
  56. package/packages/client-agent-team/lib/types/client/TeamAvatarStack.d.ts +18 -0
  57. package/packages/client-agent-team/lib/types/client/TeamAvatarStack.d.ts.map +1 -0
  58. package/packages/client-agent-team/lib/types/client/TeamAvatarStack.js +22 -0
  59. package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts +3 -1
  60. package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts.map +1 -1
  61. package/packages/client-agent-team/lib/types/client/TeamChannelPage.js +111 -24
  62. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.d.ts.map +1 -1
  63. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +10 -3
  64. package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts.map +1 -1
  65. package/packages/client-agent-team/lib/types/client/TeamComposer.js +11 -17
  66. package/packages/client-agent-team/lib/types/client/TeamConversation.js +1 -1
  67. package/packages/client-agent-team/lib/types/client/TeamCountBadge.d.ts +20 -0
  68. package/packages/client-agent-team/lib/types/client/TeamCountBadge.d.ts.map +1 -0
  69. package/packages/client-agent-team/lib/types/client/TeamCountBadge.js +25 -0
  70. package/packages/client-agent-team/lib/types/client/TeamInboxPage.d.ts +9 -5
  71. package/packages/client-agent-team/lib/types/client/TeamInboxPage.d.ts.map +1 -1
  72. package/packages/client-agent-team/lib/types/client/TeamInboxPage.js +92 -23
  73. package/packages/client-agent-team/lib/types/client/TeamMemberRow.d.ts +43 -0
  74. package/packages/client-agent-team/lib/types/client/TeamMemberRow.d.ts.map +1 -0
  75. package/packages/client-agent-team/lib/types/client/TeamMemberRow.js +27 -0
  76. package/packages/client-agent-team/lib/types/client/TeamMembersAction.d.ts +2 -2
  77. package/packages/client-agent-team/lib/types/client/TeamMembersAction.d.ts.map +1 -1
  78. package/packages/client-agent-team/lib/types/client/TeamMembersAction.js +2 -2
  79. package/packages/client-agent-team/lib/types/client/TeamPresenceDot.d.ts +1 -1
  80. package/packages/client-agent-team/lib/types/client/TeamPresenceDot.d.ts.map +1 -1
  81. package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -1
  82. package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +21 -31
  83. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts +1 -1
  84. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts.map +1 -1
  85. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.js +29 -11
  86. package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
  87. package/packages/client-agent-team/lib/types/client/index.js +11 -5
  88. package/packages/client-agent-team/lib/types/client/locales.d.ts +54 -16
  89. package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
  90. package/packages/client-agent-team/lib/types/client/locales.js +54 -16
  91. package/packages/client-agent-team/lib/types/client/slots.d.ts +3 -2
  92. package/packages/client-agent-team/lib/types/client/slots.d.ts.map +1 -1
  93. package/packages/client-agent-team/lib/types/client/team-changes.d.ts +15 -25
  94. package/packages/client-agent-team/lib/types/client/team-changes.d.ts.map +1 -1
  95. package/packages/client-agent-team/lib/types/client/team-changes.js +82 -80
  96. package/packages/client-agent-team/lib/types/client/team-formatters.d.ts +45 -10
  97. package/packages/client-agent-team/lib/types/client/team-formatters.d.ts.map +1 -1
  98. package/packages/client-agent-team/lib/types/client/team-formatters.js +71 -30
  99. package/packages/tool-agent-team/README.md +3 -3
  100. package/packages/tool-agent-team/README.zh.md +3 -3
  101. package/packages/tool-agent-team/lib/host-access.js +19 -0
  102. package/packages/tool-agent-team/lib/index.js +71 -49
  103. package/packages/tool-agent-team/lib/types/host-access.d.ts +10 -0
  104. package/packages/tool-agent-team/lib/types/host-access.d.ts.map +1 -1
  105. package/packages/tool-agent-team/lib/types/index.d.ts.map +1 -1
  106. package/packages/agent-team/lib/progress-nudge.js +0 -328
  107. package/packages/agent-team/lib/types/progress-nudge.d.ts +0 -136
  108. package/packages/agent-team/lib/types/progress-nudge.d.ts.map +0 -1
  109. package/packages/client-agent-team/lib/types/client/TeamSettings.d.ts +0 -3
  110. package/packages/client-agent-team/lib/types/client/TeamSettings.d.ts.map +0 -1
  111. package/packages/client-agent-team/lib/types/client/TeamSettings.js +0 -5
  112. package/packages/client-agent-team/lib/types/client/scope-coverage.d.ts +0 -41
  113. package/packages/client-agent-team/lib/types/client/scope-coverage.d.ts.map +0 -1
  114. package/packages/client-agent-team/lib/types/client/scope-coverage.js +0 -64
@@ -1,4 +1,5 @@
1
1
  import { readFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
2
3
  import { createUserMessage } from '@deepseek-ai/dsh-llm';
3
4
  import { SessionLogOffset } from '@deepseek-ai/dsh-session';
4
5
  import { memberMemoryDirectoryPath } from "./member-runtime.js";
@@ -33,7 +34,8 @@ export function apply(ctx) {
33
34
  ? 'memory.md is absent; the private memory index is empty.'
34
35
  : 'memory.md is currently unreadable; do not use any earlier private memory context.');
35
36
  }
36
- const text = `${renderMemberIdentity(member)}\n\n${memory}`;
37
+ const workspaces = renderMemberWorkspaces(host.workspacesForAgent(agent));
38
+ const text = `${renderMemberIdentity(member)}\n\n${memory}${workspaces === '' ? '' : `\n\n${workspaces}`}`;
37
39
  const latestText = agent.session.surface.nodes.toReversed().flatMap(sequence => {
38
40
  // `nodes` are event identities (SessionSeq); snapshotEvents takes log
39
41
  // offsets. Re-entering through the validating constructor keeps the two
@@ -61,6 +63,21 @@ export function renderMemberIdentity(member) {
61
63
  ? `Team identity: you are @${member.handle}.`
62
64
  : `Team identity: you are @${member.handle} — ${member.description}`;
63
65
  }
66
+ /** A current address list, not a copy of instructions from another checkout. */
67
+ export function renderMemberWorkspaces(workspaces) {
68
+ if (workspaces.length <= 1)
69
+ return '';
70
+ return [
71
+ 'Team Workspace participation — this list replaces all earlier participation context.',
72
+ ...workspaces.map(workspace => `${workspace.workspaceId}${workspace.default ? ' (default; Session cwd)' : ''}: ${workspace.path === undefined
73
+ ? 'Workspace path unavailable; ask @human before filesystem work.'
74
+ : `${JSON.stringify(workspace.path)}; instructions: ${JSON.stringify(join(workspace.path, 'AGENTS.md'))}`}`),
75
+ 'Your single Session and cwd stay in the default Workspace. Joining another Workspace does not move them.',
76
+ 'For filesystem work in another Workspace, use absolute paths (or an explicit command cwd). Before working there, read its AGENTS.md and applicable directory instructions yourself; their contents are not injected here.',
77
+ 'Pass workspace explicitly to team_view, team_thread, team_message and team_claim. team_inbox is the cross-Workspace triage entry point.',
78
+ 'If an instruction is ambiguous about which Workspace it concerns, ask @human before acting. Participation does not itself join Channels.',
79
+ ].join('\n');
80
+ }
64
81
  /** The four private-memory paths plus the out-of-cwd warning; callers append their own sentence. */
65
82
  function memoryPathsBlock(privateMemoryPath) {
66
83
  return `Private memory directory: ${privateMemoryPath}\nMemory index: ${privateMemoryPath}/memory.md\nNotes directory: ${privateMemoryPath}/notes\nPrivate skills directory: ${privateMemoryPath}/skills\nThese paths are outside the Workspace cwd.`;
@@ -14,9 +14,10 @@
14
14
  * @module @wowyuarm/dsh-agent-team/member-runtime
15
15
  */
16
16
  import { mkdir, readdir, rename, rm, stat, writeFile } from 'node:fs/promises';
17
- import { dirname, join, resolve } from 'node:path';
17
+ import { dirname, isAbsolute, join, relative, resolve } from 'node:path';
18
18
  import { fileURLToPath } from 'node:url';
19
19
  import { scopeOf } from '@deepseek-ai/dsh-scope';
20
+ import { dshHomePath } from '@deepseek-ai/dsh-home-paths';
20
21
  import * as memberSkills from "./member-skills.js";
21
22
  /**
22
23
  * Read-only core skills shipped beside the preset (the meta skill first:
@@ -218,7 +219,8 @@ export class MemberRuntime {
218
219
  * `privateMemoryPath` is a durable Member fact the renewal path cannot
219
220
  * rewrite: when the legacy directory exists it is renamed onto the sanitized
220
221
  * path once (same-parent rename, atomic), so existing private memory
221
- * survives instead of being silently orphaned.
222
+ * survives instead of being silently orphaned. A recorded path that names a
223
+ * different DSH home is never acted on — see `migrateLegacyMemoryDirectory`.
222
224
  *
223
225
  * A colon-form twin directory is also merged when it exists: a Member may
224
226
  * have written files under the ledger identity spelling (the branded
@@ -231,8 +233,14 @@ export class MemberRuntime {
231
233
  * and twin-only notes/skills are moved in without overwriting.
232
234
  */
233
235
  async initializePrivateMemory(path, legacyPath) {
234
- if (legacyPath !== undefined && legacyPath !== path)
235
- await migrateLegacyMemoryDirectory(legacyPath, path);
236
+ if (legacyPath !== undefined && legacyPath !== path) {
237
+ const migration = await migrateLegacyMemoryDirectory(legacyPath, path);
238
+ // A refusal means the record names a directory in ANOTHER DSH home: this
239
+ // process provisions the Member fresh here instead of taking that
240
+ // directory out of its own home.
241
+ if (migration === 'refused-foreign-home')
242
+ this.deps.ctx.logger.warn(`agent-team: refused to migrate the recorded Member private memory '${legacyPath}' into '${path}': they are not in the same directory, so the recorded directory names a different DSH home and is left untouched`);
243
+ }
236
244
  const twinPath = twinMemoryDirectoryPath(path);
237
245
  if (twinPath !== undefined)
238
246
  await mergeTwinMemoryDirectory(twinPath, path);
@@ -250,12 +258,20 @@ export class MemberRuntime {
250
258
  async cleanupRemovedMember(member) {
251
259
  // The ledger path may still name the legacy colon directory (never
252
260
  // activated after the fix): remove both spellings; rm is force-tolerant
253
- // of the one that does not exist.
261
+ // of the one that does not exist. Every target must sit inside THIS
262
+ // process's members root: the recorded path is a durable absolute fact,
263
+ // and a process resolving another DSH home (an isolated test home, a
264
+ // second instance) would otherwise delete the directory that fact names —
265
+ // the real Member's private memory.
254
266
  const sanitized = memberMemoryDirectoryPath(member);
267
+ const targets = sanitized === member.privateMemoryPath ? [sanitized] : [sanitized, member.privateMemoryPath];
268
+ const owned = targets.filter(target => isInsideMembersRoot(target));
269
+ const refused = targets.filter(target => !isInsideMembersRoot(target));
270
+ if (refused.length > 0)
271
+ this.deps.ctx.logger.warn(`agent-team: refused to delete recorded Member private memory outside this DSH home: ${refused.map(target => `'${target}'`).join(', ')}`);
255
272
  const results = await Promise.allSettled([
256
273
  this.deps.ctx.workspaceRegistry.archiveSession(member.sessionId),
257
- rm(sanitized, { recursive: true, force: true }),
258
- ...(sanitized === member.privateMemoryPath ? [] : [rm(member.privateMemoryPath, { recursive: true, force: true })]),
274
+ ...owned.map(target => rm(target, { recursive: true, force: true })),
259
275
  ]);
260
276
  const failures = results.flatMap(result => result.status === 'rejected' ? [result.reason] : []);
261
277
  if (failures.length > 0)
@@ -288,6 +304,15 @@ export class MemberRuntime {
288
304
  * directory onto its sanitized path. A sanitized target that already exists
289
305
  * wins (idempotent across restarts and partially migrated installs); a legacy
290
306
  * source that never existed is simply the fresh-install case.
307
+ *
308
+ * The rename is confined to one parent directory, which is what the migration
309
+ * has always meant: the recorded path is a durable absolute fact, so a process
310
+ * resolving a different DSH home (an isolated test home, a second instance, a
311
+ * moved home) holds a sanitized target in ITS tree while the record still names
312
+ * the real one. Renaming across trees would take a Member's whole private
313
+ * memory out of its home — silently, since only the next session notices an
314
+ * empty index. Such a source is left exactly where it is, and only that
315
+ * refusal is reported back: every other outcome needs nothing from the caller.
291
316
  */
292
317
  async function migrateLegacyMemoryDirectory(legacyPath, path) {
293
318
  let legacy;
@@ -296,20 +321,34 @@ async function migrateLegacyMemoryDirectory(legacyPath, path) {
296
321
  }
297
322
  catch (error) {
298
323
  if (error.code === 'ENOENT')
299
- return;
324
+ return undefined;
300
325
  throw error;
301
326
  }
302
327
  if (!legacy.isDirectory())
303
328
  throw new Error(`legacy Member memory path '${legacyPath}' exists but is not a directory`);
329
+ if (dirname(legacyPath) !== dirname(path))
330
+ return 'refused-foreign-home';
304
331
  try {
305
332
  await stat(path);
306
- return; // Sanitized directory already present: migration already done or a new install.
333
+ return undefined; // Sanitized directory already present: migration already done or a new install.
307
334
  }
308
335
  catch (error) {
309
336
  if (error.code !== 'ENOENT')
310
337
  throw error;
311
338
  }
312
339
  await rename(legacyPath, path);
340
+ return undefined;
341
+ }
342
+ /**
343
+ * Whether one absolute path names a Member directory inside THIS process's
344
+ * members root. Only such a path is this process's to delete: a recorded path
345
+ * may name the same Member in another DSH home, where the removal happening
346
+ * here has no authority.
347
+ */
348
+ function isInsideMembersRoot(candidate) {
349
+ const fromRoot = relative(dshHomePath('agent-team', 'members'), resolve(candidate));
350
+ // '' is the members root itself and '..' escapes it: only a real descendant is authorized.
351
+ return fromRoot.length > 0 && !fromRoot.startsWith('..') && !isAbsolute(fromRoot);
313
352
  }
314
353
  /** The colon-form twin path of one sanitized Member memory directory, when the segment form admits one. */
315
354
  function twinMemoryDirectoryPath(sanitizedPath) {
@@ -0,0 +1,80 @@
1
+ function escapeRegExp(value) {
2
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
3
+ }
4
+ /**
5
+ * Character ranges the scanner must not read as mentions: fenced code blocks
6
+ * and inline code spans. Authors quote handles there to talk *about* a name
7
+ * rather than call it, and resolving those would notify the wrong Member.
8
+ * The alternation tries the fenced form first so a block is never mistaken for
9
+ * a run of inline spans.
10
+ */
11
+ function codeRanges(body) {
12
+ const ranges = [];
13
+ for (const match of body.matchAll(/```[\s\S]*?```|`[^`\n]*`/g)) {
14
+ const start = match.index ?? 0;
15
+ ranges.push([start, start + match[0].length]);
16
+ }
17
+ return ranges;
18
+ }
19
+ const ALL_MARKER = /(?<![\p{L}\p{N}_@])@all(?=$|[^\p{L}\p{N}_])/iu;
20
+ /** Whether one body carries the `@all` marker outside code. */
21
+ export function hasAllMarker(body) {
22
+ const excluded = codeRanges(body);
23
+ const marker = ALL_MARKER.exec(body);
24
+ return marker !== null && !excluded.some(([start, end]) => marker.index >= start && marker.index < end);
25
+ }
26
+ /**
27
+ * Every `@Handle` occurrence in one body that names one of `handles`:
28
+ * case-insensitive, on Unicode word boundaries, longest handle first, outside
29
+ * code. Writing `@` is required: bare handles are ordinary words. This is the
30
+ * single definition of an authored mention — the Host delivery resolution and
31
+ * the Client's chip rendering and draft preview all read through it, so a name
32
+ * that renders as a chip is the same name that delivers a notification.
33
+ */
34
+ export function scanBodyHandles(body, handles) {
35
+ const seen = new Set();
36
+ const usable = [];
37
+ for (const handle of handles) {
38
+ const key = handle.toLowerCase();
39
+ if (handle.trim() === '' || seen.has(key))
40
+ continue;
41
+ seen.add(key);
42
+ usable.push(handle);
43
+ }
44
+ if (usable.length === 0)
45
+ return Object.freeze([]);
46
+ const excluded = codeRanges(body);
47
+ // Longest first so `@Reeves` never resolves as `@Reeve` plus a stray letter.
48
+ const ordered = [...usable].sort((left, right) => right.length - left.length);
49
+ const pattern = new RegExp(`(?<![\\p{L}\\p{N}_@])@(?:${ordered.map(handle => escapeRegExp(handle)).join('|')})(?=$|[^\\p{L}\\p{N}_])`, 'giu');
50
+ const matches = [];
51
+ for (const match of body.matchAll(pattern)) {
52
+ const start = match.index ?? 0;
53
+ if (excluded.some(([rangeStart, rangeEnd]) => start >= rangeStart && start < rangeEnd))
54
+ continue;
55
+ const written = match[0].slice(1).toLowerCase();
56
+ const hit = ordered.find(handle => handle.toLowerCase() === written);
57
+ if (hit === undefined)
58
+ continue;
59
+ matches.push(Object.freeze({ handle: hit, start, end: start + match[0].length }));
60
+ }
61
+ return Object.freeze(matches);
62
+ }
63
+ /**
64
+ * Resolve the `@Handle` mentions authored in one Message body.
65
+ *
66
+ * Callers pass the candidates reachable in the Message's Channel, so a name
67
+ * that resolves here is already an addressable target; handles the Channel
68
+ * cannot reach simply stay prose.
69
+ *
70
+ * `sender` is excluded because a Message never mentions its own author, which
71
+ * also keeps the result usable as a recipient set without further filtering.
72
+ */
73
+ export function resolveBodyMentions(body, candidates, sender) {
74
+ const usable = candidates.filter(candidate => candidate.memberId !== sender && candidate.handle.trim() !== '');
75
+ const all = hasAllMarker(body);
76
+ if (usable.length === 0)
77
+ return { memberIds: Object.freeze([]), all };
78
+ const matched = new Set(scanBodyHandles(body, usable.map(candidate => candidate.handle)).map(match => match.handle.toLowerCase()));
79
+ return { memberIds: Object.freeze(usable.filter(candidate => matched.has(candidate.handle.toLowerCase())).map(candidate => candidate.memberId)), all };
80
+ }
@@ -254,6 +254,7 @@ const threadReadSnapshotDataSchema = z.object({
254
254
  facts: z.array(readFactSchema),
255
255
  readThroughSequence: z.number().int().nonnegative(),
256
256
  remainingUnreadCount: z.number().int().nonnegative(),
257
+ earlierFactCount: z.number().int().nonnegative().optional(),
257
258
  attention: attentionSchema.optional(),
258
259
  inbox: inboxDeltaSchema,
259
260
  }).strict().transform(omitUndefined);
@@ -364,6 +365,25 @@ const storedAgentTeamOperationSchema = z.discriminatedUnion('kind', [
364
365
  ...releaseSnapshotFields,
365
366
  }).strict(),
366
367
  }).strict(),
368
+ z.object({
369
+ ...operationBase,
370
+ previousOperationId: operationIdSchema.nullable(),
371
+ kind: z.literal('team/member-workspace-joined'),
372
+ data: z.object({
373
+ workspaceId: workspaceIdSchema,
374
+ memberId: memberIdSchema,
375
+ }).strict(),
376
+ }).strict(),
377
+ z.object({
378
+ ...operationBase,
379
+ previousOperationId: operationIdSchema.nullable(),
380
+ kind: z.literal('team/member-workspace-left'),
381
+ data: z.object({
382
+ workspaceId: workspaceIdSchema,
383
+ memberId: memberIdSchema,
384
+ ...releaseSnapshotFields,
385
+ }).strict(),
386
+ }).strict(),
367
387
  z.object({
368
388
  ...operationBase,
369
389
  previousOperationId: operationIdSchema.nullable(),