@mastra/code-sdk 1.11.0-alpha.1 → 1.11.0-alpha.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 (67) hide show
  1. package/dist/acp/agent.d.ts +3 -3
  2. package/dist/acp/agent.d.ts.map +1 -1
  3. package/dist/acp/agent.js +95 -22
  4. package/dist/acp/agent.js.map +1 -1
  5. package/dist/acp/runtime.d.ts.map +1 -1
  6. package/dist/acp/runtime.js +18 -10
  7. package/dist/acp/runtime.js.map +1 -1
  8. package/dist/agents/memory.d.ts +3 -1
  9. package/dist/agents/memory.d.ts.map +1 -1
  10. package/dist/agents/memory.js +34 -23
  11. package/dist/agents/memory.js.map +1 -1
  12. package/dist/agents/model.d.ts +4 -39
  13. package/dist/agents/model.d.ts.map +1 -1
  14. package/dist/agents/model.js +31 -93
  15. package/dist/agents/model.js.map +1 -1
  16. package/dist/agents/prompts/agent-instructions.js +1 -1
  17. package/dist/auth/account-rotation-processor.d.ts +26 -54
  18. package/dist/auth/account-rotation-processor.d.ts.map +1 -1
  19. package/dist/auth/account-rotation-processor.js +105 -154
  20. package/dist/auth/account-rotation-processor.js.map +1 -1
  21. package/dist/auth/index.js +2 -2
  22. package/dist/auth/providers/kimi-coding.js +2 -1
  23. package/dist/auth/providers/kimi-coding.js.map +1 -1
  24. package/dist/auth/providers/xai.js +2 -1
  25. package/dist/auth/providers/xai.js.map +1 -1
  26. package/dist/auth/storage.js +1 -1
  27. package/dist/auth/types.d.ts +1 -0
  28. package/dist/auth/types.d.ts.map +1 -1
  29. package/dist/clipboard/index.js +1 -1
  30. package/dist/constants.d.ts +2 -0
  31. package/dist/constants.d.ts.map +1 -1
  32. package/dist/constants.js +3 -1
  33. package/dist/constants.js.map +1 -1
  34. package/dist/headless/cli.d.ts.map +1 -1
  35. package/dist/headless/cli.js +9 -4
  36. package/dist/headless/cli.js.map +1 -1
  37. package/dist/headless/run-mc.d.ts.map +1 -1
  38. package/dist/headless/run-mc.js +46 -42
  39. package/dist/headless/run-mc.js.map +1 -1
  40. package/dist/index.d.ts +3 -5
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +31 -27
  43. package/dist/index.js.map +1 -1
  44. package/dist/lsp/server.js +1 -1
  45. package/dist/onboarding/om-settings.d.ts +1 -0
  46. package/dist/onboarding/om-settings.d.ts.map +1 -1
  47. package/dist/onboarding/om-settings.js +10 -1
  48. package/dist/onboarding/om-settings.js.map +1 -1
  49. package/dist/onboarding/settings.js +2 -2
  50. package/dist/providers/openai-codex.js +1 -1
  51. package/dist/schedules/run-script.js +1 -1
  52. package/dist/schema.d.ts +34 -19
  53. package/dist/schema.d.ts.map +1 -1
  54. package/dist/schema.js +11 -5
  55. package/dist/schema.js.map +1 -1
  56. package/dist/utils/open-url.d.ts +10 -0
  57. package/dist/utils/open-url.d.ts.map +1 -0
  58. package/dist/utils/open-url.js +30 -0
  59. package/dist/utils/open-url.js.map +1 -0
  60. package/dist/utils/project.js +2 -2
  61. package/dist/utils/signals-pubsub.d.ts +32 -4
  62. package/dist/utils/signals-pubsub.d.ts.map +1 -1
  63. package/dist/utils/signals-pubsub.js +207 -28
  64. package/dist/utils/signals-pubsub.js.map +1 -1
  65. package/dist/utils/slash-command-processor.js +1 -1
  66. package/dist/utils/update-check.js +1 -1
  67. package/package.json +12 -12
@@ -1,4 +1,4 @@
1
- import { join } from "path";
1
+ import { isAbsolute, join } from "path";
2
2
  import { mkdir } from "fs/promises";
3
3
  import { PubSub, UnixSocketPubSub } from "@mastra/core/events";
4
4
  //#region src/utils/signals-pubsub.ts
@@ -8,8 +8,25 @@ const THREAD_STREAM_PREFIX = "agent.thread-stream.";
8
8
  const THREAD_KEY_SEPARATOR = "\0";
9
9
  const THREAD_CLAIM_LEASE_PREFIX = "thread-claim:";
10
10
  const NOTIFICATION_DISPATCH_LEASE_PREFIX = "notification-dispatch:";
11
+ const LEASE_SOCKET_NAME = ".leases.sock";
11
12
  const OWNER_DISCOVERY_TOPIC = "agent.thread-owner-discovery";
13
+ const PEER_DISCOVERY_TOPIC = "agent.thread-peer-discovery";
14
+ const RESERVED_SOCKET_NAME = /^agent_thread-(?:peer|owner)-discovery(?:_|$)/i;
12
15
  const MAX_PATH_SEGMENT_LENGTH = 128;
16
+ const UUID_PATTERN = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
17
+ const DISCOVERY_REPLY_TOPIC = new RegExp(`^agent\\.thread-(?:peer|owner)-discovery\\.${UUID_PATTERN}$`, "i");
18
+ const IDLE_ACCEPTANCE_REPLY_TOPIC = new RegExp(`^agent\\.thread-stream\\..+\\.idle-acceptance\\.${UUID_PATTERN}$`, "i");
19
+ /**
20
+ * One-shot reply topics are minted per request (`<topic>.<uuid>`), used for a
21
+ * single round trip, and never published to again once the request settles.
22
+ */
23
+ function isEphemeralTopic(topic) {
24
+ return DISCOVERY_REPLY_TOPIC.test(topic) || IDLE_ACCEPTANCE_REPLY_TOPIC.test(topic);
25
+ }
26
+ function socketRootFromEnv() {
27
+ const root = process.env.MASTRACODE_SIGNALS_SOCKET_ROOT;
28
+ return root && isAbsolute(root) ? root : DEFAULT_SOCKET_ROOT;
29
+ }
13
30
  /**
14
31
  * Whether an id can name a single directory: no path separators or control
15
32
  * characters, not `.`/`..`, and bounded in length.
@@ -60,6 +77,25 @@ function resourceOfLeaseKey(key) {
60
77
  function isOwnerDiscoveryTopic(topic) {
61
78
  return topic === OWNER_DISCOVERY_TOPIC || topic.startsWith(`${OWNER_DISCOVERY_TOPIC}.`);
62
79
  }
80
+ function isPeerDiscoveryTopic(topic) {
81
+ return topic === PEER_DISCOVERY_TOPIC || topic.startsWith(`${PEER_DISCOVERY_TOPIC}.`);
82
+ }
83
+ /**
84
+ * Whether a threadId can be the base name of a thread's socket file: no
85
+ * separators or control characters, not `.`/`..`, and not a name another
86
+ * socket in the same directory already uses (the lease socket, and the
87
+ * sanitized peer/owner discovery topics and their reply topics), compared
88
+ * case-insensitively because default macOS and Windows filesystems are.
89
+ */
90
+ function isSafeFileName(value) {
91
+ if (!value || value === "." || value === ".." || `${value}.sock`.toLowerCase() === LEASE_SOCKET_NAME) return false;
92
+ if (RESERVED_SOCKET_NAME.test(value)) return false;
93
+ for (const char of value) {
94
+ const code = char.charCodeAt(0);
95
+ if (char === "/" || char === "\\" || code < 32 || code === 127) return false;
96
+ }
97
+ return true;
98
+ }
63
99
  /**
64
100
  * Derive a filesystem-safe key for the topic. Thread-stream topics embed
65
101
  * a threadId; all other topics use a sanitized version of the topic name.
@@ -72,6 +108,9 @@ function topicKey(topic) {
72
108
  }
73
109
  return topic.replace(/[^a-zA-Z0-9_-]/g, "_");
74
110
  }
111
+ function socketKey(dir, topic) {
112
+ return `${dir}/${topicKey(topic)}`;
113
+ }
75
114
  /**
76
115
  * A PubSub that manages one Unix socket per topic for cross-process signal
77
116
  * coordination between mastracode processes on this machine.
@@ -91,14 +130,25 @@ function topicKey(topic) {
91
130
  * - Thread-owner discovery lives in `<root>/_shared/`, so a process that finds
92
131
  * a thread claimed can reach the owner and hand it the signal. It only
93
132
  * answers for a thread the asker names, so it discloses nothing new.
94
- * - Everything else, including peer discovery (which lists threads), stays in
95
- * this process's own resource directory.
133
+ * - Peer discovery (which lists threads) runs in `<root>/_shared/` when
134
+ * `sharedAgentDiscovery` is set, so processes in every project can list each
135
+ * other's threads. Without it, it stays in this resource's directory.
136
+ * - Everything else stays in this process's own resource directory.
96
137
  *
97
138
  * Stale sockets from crashed processes are handled by
98
139
  * {@link UnixSocketPubSub}'s built-in election logic: it detects
99
140
  * ECONNREFUSED on a dead broker socket, unlinks it, and re-elects.
100
141
  * No blanket cleanup is needed here — that would break concurrent
101
142
  * mc instances sharing a directory.
143
+ *
144
+ * One-shot reply topics (discovery and idle-acceptance replies) are
145
+ * reference-counted: every in-flight publish/subscribe and every live
146
+ * subscription holds a reference, and the topic's socket is closed as soon as
147
+ * the count drops to zero. Without this each request would keep a socket, a
148
+ * file descriptor and (for the broker) a socket file for the process lifetime.
149
+ * With `sharedAgentDiscovery`, another resource's thread topics are counted the
150
+ * same way, so a process that signals many threads elsewhere does not keep a
151
+ * socket open for each.
102
152
  */
103
153
  var SignalsPubSub = class extends PubSub {
104
154
  #resourceId;
@@ -106,20 +156,29 @@ var SignalsPubSub = class extends PubSub {
106
156
  #leaseSockets = /* @__PURE__ */ new Map();
107
157
  #sockets = /* @__PURE__ */ new Map();
108
158
  #pending = /* @__PURE__ */ new Map();
159
+ #refs = /* @__PURE__ */ new Map();
160
+ #liveSubscriptions = /* @__PURE__ */ new Map();
161
+ #clearGenerations = /* @__PURE__ */ new Map();
162
+ #closing = /* @__PURE__ */ new Map();
109
163
  #leaseProvider;
164
+ #sharedPeerDiscovery;
110
165
  #closed = false;
111
166
  constructor(resourceId, options = {}) {
112
167
  super();
113
168
  this.#resourceId = resourceId;
114
- this.#rootDir = options.rootDir ?? DEFAULT_SOCKET_ROOT;
169
+ this.#rootDir = options.rootDir ?? socketRootFromEnv();
170
+ this.#sharedPeerDiscovery = Boolean(options.sharedAgentDiscovery) && this.#canShareDiscovery();
115
171
  this.#leasesFor(resourceId);
116
- const route = (key) => this.#leasesFor(resourceOfLeaseKey(key) ?? this.#resourceId);
172
+ const route = (key) => {
173
+ if (this.#closed) throw new Error("SignalsPubSub is closed");
174
+ return this.#leasesFor(resourceOfLeaseKey(key) ?? this.#resourceId);
175
+ };
117
176
  this.#leaseProvider = {
118
- acquireLease: (key, owner, ttlMs) => route(key).acquireLease(key, owner, ttlMs),
119
- getLeaseOwner: (key) => route(key).getLeaseOwner(key),
120
- releaseLease: (key, owner) => route(key).releaseLease(key, owner),
121
- renewLease: (key, owner, ttlMs) => route(key).renewLease(key, owner, ttlMs),
122
- transferLease: (key, fromOwner, toOwner, ttlMs) => route(key).transferLease(key, fromOwner, toOwner, ttlMs)
177
+ acquireLease: async (key, owner, ttlMs) => route(key).acquireLease(key, owner, ttlMs),
178
+ getLeaseOwner: async (key) => route(key).getLeaseOwner(key),
179
+ releaseLease: async (key, owner) => route(key).releaseLease(key, owner),
180
+ renewLease: async (key, owner, ttlMs) => route(key).renewLease(key, owner, ttlMs),
181
+ transferLease: async (key, fromOwner, toOwner, ttlMs) => route(key).transferLease(key, fromOwner, toOwner, ttlMs)
123
182
  };
124
183
  }
125
184
  get supportedModes() {
@@ -134,33 +193,128 @@ var SignalsPubSub = class extends PubSub {
134
193
  return this.#leaseProvider;
135
194
  }
136
195
  async publish(topic, event, options) {
137
- await (await this.#getOrCreate(topic)).publish(topic, event, options);
196
+ await this.#publishTo(topic, this.#dirFor(topic), event, options);
138
197
  }
139
198
  async subscribe(topic, cb, options) {
140
- await (await this.#getOrCreate(topic)).subscribe(topic, cb, options);
199
+ await this.#subscribeTo(topic, this.#dirFor(topic), cb, options);
141
200
  }
142
201
  async unsubscribe(topic, cb) {
143
- const socket = this.#sockets.get(this.#socketKey(topic));
144
- if (!socket) return;
145
- await socket.unsubscribe(topic, cb);
202
+ await this.#unsubscribeFrom(topic, this.#dirFor(topic), cb);
203
+ }
204
+ /**
205
+ * Drops a one-shot reply topic: its remaining subscriptions release their
206
+ * references and the socket closes once no operation still uses it. Other
207
+ * topics (workflow events, thread streams) have subscribers in other
208
+ * processes that need the broker, so clearing them is a no-op.
209
+ */
210
+ async clearTopic(topic) {
211
+ if (!isEphemeralTopic(topic)) return;
212
+ const key = socketKey(this.#dirFor(topic), topic);
213
+ if (!this.#refs.has(key)) return;
214
+ this.#clearGenerations.set(key, (this.#clearGenerations.get(key) ?? 0) + 1);
215
+ const subscriptions = this.#liveSubscriptions.get(key);
216
+ if (!subscriptions) return;
217
+ this.#liveSubscriptions.delete(key);
218
+ for (let i = 0; i < subscriptions.size; i++) this.#release(key);
146
219
  }
147
220
  async flush() {
148
221
  await Promise.all([...this.#sockets.values()].map((s) => s.flush()));
149
222
  }
150
223
  async close() {
151
224
  this.#closed = true;
152
- await Promise.allSettled([...[...this.#leaseSockets.values()].map((s) => s.close()), ...[...this.#sockets.values()].map((s) => s.close())]);
225
+ await Promise.allSettled([
226
+ ...[...this.#leaseSockets.values()].map((s) => s.close()),
227
+ ...[...this.#sockets.values()].map((s) => s.close()),
228
+ ...this.#closing.values()
229
+ ]);
153
230
  this.#sockets.clear();
154
231
  this.#leaseSockets.clear();
232
+ this.#refs.clear();
233
+ this.#liveSubscriptions.clear();
234
+ this.#clearGenerations.clear();
155
235
  }
156
236
  /** Get the underlying socket for a topic (for testing/inspection). */
157
237
  getSocket(topic) {
158
- return this.#sockets.get(this.#socketKey(topic));
238
+ return this.#sockets.get(socketKey(this.#dirFor(topic), topic));
239
+ }
240
+ #canShareDiscovery() {
241
+ if (isSafePathSegment(this.#resourceId) && this.#resourceId !== SHARED_SCOPE_DIR) return true;
242
+ console.warn(`Cross-project agent discovery is disabled: resource id ${JSON.stringify(this.#resourceId)} cannot be used as a directory name, so other projects could not reach this instance.`);
243
+ return false;
244
+ }
245
+ /**
246
+ * One-shot reply topics close once unused. With shared discovery, so do thread
247
+ * topics routed to another resource; without it, they behave as before.
248
+ */
249
+ #isRefCounted(topic) {
250
+ if (isEphemeralTopic(topic)) return true;
251
+ if (!this.#sharedPeerDiscovery) return false;
252
+ const decoded = decodeThreadTopic(topic);
253
+ if (decoded === void 0) return false;
254
+ const resourceId = resourceOfThreadKey(decoded);
255
+ return resourceId !== void 0 && resourceId !== this.#resourceId;
256
+ }
257
+ async #publishTo(topic, dir, event, options) {
258
+ if (!this.#isRefCounted(topic)) {
259
+ await (await this.#getOrCreate(topic, dir)).publish(topic, event, options);
260
+ return;
261
+ }
262
+ const key = socketKey(dir, topic);
263
+ this.#retain(key);
264
+ try {
265
+ await (await this.#getOrCreate(topic, dir)).publish(topic, event, options);
266
+ } finally {
267
+ this.#release(key);
268
+ }
269
+ }
270
+ async #subscribeTo(topic, dir, cb, options) {
271
+ if (!this.#isRefCounted(topic)) {
272
+ await (await this.#getOrCreate(topic, dir)).subscribe(topic, cb, options);
273
+ return;
274
+ }
275
+ const key = socketKey(dir, topic);
276
+ this.#retain(key);
277
+ const generation = this.#clearGenerations.get(key) ?? 0;
278
+ try {
279
+ const socket = await this.#getOrCreate(topic, dir);
280
+ await socket.subscribe(topic, cb, options);
281
+ if ((this.#clearGenerations.get(key) ?? 0) !== generation) {
282
+ await socket.unsubscribe(topic, cb);
283
+ return;
284
+ }
285
+ let subscriptions = this.#liveSubscriptions.get(key);
286
+ if (!subscriptions) {
287
+ subscriptions = /* @__PURE__ */ new Set();
288
+ this.#liveSubscriptions.set(key, subscriptions);
289
+ }
290
+ if (!subscriptions.has(cb)) {
291
+ subscriptions.add(cb);
292
+ this.#retain(key);
293
+ }
294
+ } finally {
295
+ this.#release(key);
296
+ }
297
+ }
298
+ async #unsubscribeFrom(topic, dir, cb) {
299
+ const key = socketKey(dir, topic);
300
+ const socket = this.#sockets.get(key);
301
+ const subscriptions = this.#isRefCounted(topic) ? this.#liveSubscriptions.get(key) : void 0;
302
+ if (!subscriptions?.delete(cb)) {
303
+ if (!socket) return;
304
+ await socket.unsubscribe(topic, cb);
305
+ return;
306
+ }
307
+ if (subscriptions.size === 0) this.#liveSubscriptions.delete(key);
308
+ try {
309
+ await socket?.unsubscribe(topic, cb);
310
+ } finally {
311
+ this.#release(key);
312
+ }
159
313
  }
160
314
  #leasesFor(resourceId) {
161
315
  let socket = this.#leaseSockets.get(resourceId);
162
316
  if (!socket) {
163
- socket = new UnixSocketPubSub(join(this.#rootDir, resourceId, ".leases.sock"));
317
+ socket = new UnixSocketPubSub(join(this.#rootDir, resourceId, LEASE_SOCKET_NAME));
164
318
  this.#leaseSockets.set(resourceId, socket);
165
319
  }
166
320
  return socket;
@@ -168,30 +322,53 @@ var SignalsPubSub = class extends PubSub {
168
322
  /** The directory, under the root, that holds a topic's socket. */
169
323
  #dirFor(topic) {
170
324
  if (isOwnerDiscoveryTopic(topic)) return SHARED_SCOPE_DIR;
325
+ if (this.#sharedPeerDiscovery && isPeerDiscoveryTopic(topic)) return SHARED_SCOPE_DIR;
171
326
  const decoded = decodeThreadTopic(topic);
172
327
  if (decoded !== void 0) return resourceOfThreadKey(decoded) ?? this.#resourceId;
173
328
  return this.#resourceId;
174
329
  }
175
- #socketKey(topic) {
176
- return `${this.#dirFor(topic)}/${topicKey(topic)}`;
330
+ #retain(key) {
331
+ this.#refs.set(key, (this.#refs.get(key) ?? 0) + 1);
177
332
  }
178
- async #getOrCreate(topic) {
333
+ #release(key) {
334
+ const refs = (this.#refs.get(key) ?? 0) - 1;
335
+ if (refs > 0) {
336
+ this.#refs.set(key, refs);
337
+ return;
338
+ }
339
+ this.#refs.delete(key);
340
+ this.#clearGenerations.delete(key);
341
+ this.#liveSubscriptions.delete(key);
342
+ const socket = this.#sockets.get(key);
343
+ if (!socket) return;
344
+ this.#sockets.delete(key);
345
+ const closing = socket.close().catch(() => {}).finally(() => {
346
+ if (this.#closing.get(key) === closing) this.#closing.delete(key);
347
+ });
348
+ this.#closing.set(key, closing);
349
+ }
350
+ async #getOrCreate(topic, dir) {
179
351
  if (this.#closed) throw new Error("SignalsPubSub is closed");
180
- const key = this.#socketKey(topic);
352
+ const key = socketKey(dir, topic);
353
+ const closing = this.#closing.get(key);
354
+ if (closing) {
355
+ await closing;
356
+ if (this.#closed) throw new Error("SignalsPubSub is closed");
357
+ }
181
358
  const existing = this.#sockets.get(key);
182
359
  if (existing) return existing;
183
360
  let inflight = this.#pending.get(key);
184
361
  if (!inflight) {
185
- inflight = this.#initSocket(topic, key);
362
+ inflight = this.#initSocket(topic, dir, key);
186
363
  this.#pending.set(key, inflight);
187
364
  }
188
365
  const socket = await inflight;
189
366
  if (this.#closed) throw new Error("SignalsPubSub is closed");
190
367
  return socket;
191
368
  }
192
- async #initSocket(topic, key) {
369
+ async #initSocket(topic, dir, key) {
193
370
  try {
194
- const socketPath = await this.#socketPath(topic);
371
+ const socketPath = await this.#socketPath(topic, dir);
195
372
  if (this.#closed) throw new Error("SignalsPubSub is closed");
196
373
  const socket = new UnixSocketPubSub(socketPath);
197
374
  this.#sockets.set(key, socket);
@@ -200,9 +377,10 @@ var SignalsPubSub = class extends PubSub {
200
377
  this.#pending.delete(key);
201
378
  }
202
379
  }
203
- async #socketPath(topic) {
380
+ async #socketPath(topic, scope) {
204
381
  let key = topicKey(topic);
205
- const dir = join(this.#rootDir, this.#dirFor(topic));
382
+ if (decodeThreadTopic(topic) !== void 0 && !isSafeFileName(key)) throw new Error("Cannot route an agent thread topic whose threadId is not a safe file name");
383
+ const dir = join(this.#rootDir, scope);
206
384
  await mkdir(dir, { recursive: true });
207
385
  const candidate = join(dir, `${key}.sock`);
208
386
  if (Buffer.byteLength(candidate) > 104) {
@@ -218,7 +396,8 @@ var SignalsPubSub = class extends PubSub {
218
396
  *
219
397
  * Topics live under `/tmp/mc/<resourceId>/`, except that a thread's stream and
220
398
  * leases live under its own resource's directory and thread-owner discovery
221
- * under `/tmp/mc/_shared/` (see {@link SignalsPubSub}). Stale sockets from
399
+ * under `/tmp/mc/_shared/` (see {@link SignalsPubSub}). With
400
+ * `sharedAgentDiscovery`, peer discovery runs there too. Stale sockets from
222
401
  * crashed processes are handled by the underlying {@link UnixSocketPubSub}'s
223
402
  * broker election logic.
224
403
  */
@@ -1 +1 @@
1
- {"version":3,"file":"signals-pubsub.js","names":["#resourceId","#rootDir","#leaseSockets","#sockets","#pending","#leaseProvider","#leasesFor","#getOrCreate","#socketKey","#closed","#dirFor","#initSocket","#socketPath"],"sources":["../../src/utils/signals-pubsub.ts"],"sourcesContent":["import { mkdir } from 'node:fs/promises';\nimport { join } from 'node:path';\n\nimport { PubSub, UnixSocketPubSub } from '@mastra/core/events';\nimport type { Event, EventCallback, LeaseProvider, PubSubDeliveryMode, SubscribeOptions } from '@mastra/core/events';\n\nconst DEFAULT_SOCKET_ROOT = '/tmp/mc';\nconst SHARED_SCOPE_DIR = '_shared';\nconst THREAD_STREAM_PREFIX = 'agent.thread-stream.';\nconst THREAD_KEY_SEPARATOR = '\\0';\nconst THREAD_CLAIM_LEASE_PREFIX = 'thread-claim:';\nconst NOTIFICATION_DISPATCH_LEASE_PREFIX = 'notification-dispatch:';\nconst OWNER_DISCOVERY_TOPIC = 'agent.thread-owner-discovery';\nconst MAX_PATH_SEGMENT_LENGTH = 128;\n\nexport type SignalsPubSubOptions = {\n /** Directory that holds every resource's socket directory. Defaults to `/tmp/mc`. */\n rootDir?: string;\n};\n\n/**\n * Whether an id can name a single directory: no path separators or control\n * characters, not `.`/`..`, and bounded in length.\n */\nfunction isSafePathSegment(value: string): boolean {\n if (!value || value.length > MAX_PATH_SEGMENT_LENGTH || value === '.' || value === '..') return false;\n for (const char of value) {\n const code = char.charCodeAt(0);\n if (char === '/' || char === '\\\\' || code < 0x20 || code === 0x7f) return false;\n }\n return true;\n}\n\n/** The resource a thread key (`<resourceId>\\0<threadId>`) belongs to. */\nfunction resourceOfThreadKey(key: string): string | undefined {\n const separatorIdx = key.indexOf(THREAD_KEY_SEPARATOR);\n if (separatorIdx <= 0) return undefined;\n const resourceId = key.slice(0, separatorIdx);\n return isSafePathSegment(resourceId) ? resourceId : undefined;\n}\n\n/** Decodes `agent.thread-stream.<encoded resourceId\\0threadId>[...]`. */\nfunction decodeThreadTopic(topic: string): string | undefined {\n if (!topic.startsWith(THREAD_STREAM_PREFIX)) return undefined;\n try {\n return decodeURIComponent(topic.slice(THREAD_STREAM_PREFIX.length));\n } catch {\n return undefined;\n }\n}\n\n/**\n * The lease that lets exactly one process dispatch a resource's due\n * notifications. Held in that resource's directory, so every process serving\n * the resource contends for it whichever project it was started in.\n */\nexport function notificationDispatchLeaseKey(resourceId: string): string {\n return `${NOTIFICATION_DISPATCH_LEASE_PREFIX}${resourceId}`;\n}\n\n/**\n * The resource a lease key belongs to: a thread's run lease and claim lease,\n * and a resource's notification dispatch lease.\n */\nfunction resourceOfLeaseKey(key: string): string | undefined {\n if (key.startsWith(NOTIFICATION_DISPATCH_LEASE_PREFIX)) {\n const resourceId = key.slice(NOTIFICATION_DISPATCH_LEASE_PREFIX.length);\n return isSafePathSegment(resourceId) ? resourceId : undefined;\n }\n return resourceOfThreadKey(\n key.startsWith(THREAD_CLAIM_LEASE_PREFIX) ? key.slice(THREAD_CLAIM_LEASE_PREFIX.length) : key,\n );\n}\n\nfunction isOwnerDiscoveryTopic(topic: string): boolean {\n return topic === OWNER_DISCOVERY_TOPIC || topic.startsWith(`${OWNER_DISCOVERY_TOPIC}.`);\n}\n\n/**\n * Derive a filesystem-safe key for the topic. Thread-stream topics embed\n * a threadId; all other topics use a sanitized version of the topic name.\n */\nfunction topicKey(topic: string): string {\n const decoded = decodeThreadTopic(topic);\n if (decoded !== undefined) {\n const separatorIdx = decoded.indexOf(THREAD_KEY_SEPARATOR);\n if (separatorIdx !== -1) return decoded.slice(separatorIdx + 1);\n }\n return topic.replace(/[^a-zA-Z0-9_-]/g, '_');\n}\n\n/**\n * A PubSub that manages one Unix socket per topic for cross-process signal\n * coordination between mastracode processes on this machine.\n *\n * Socket paths use `<root>/<dir>/<sanitized-topic>.sock` (root `/tmp/mc`)\n * for inspectability and automatic OS cleanup. Each topic gets its own\n * socket, so broker election and message routing are per-topic.\n *\n * Every process shares one database, so any process can act on any project's\n * thread — the notification dispatcher, for one, delivers whatever is due.\n * Whatever coordinates a thread therefore lives with the thread, not with the\n * process touching it:\n *\n * - A thread's stream topic and its leases (the run lease and the claim\n * lease) live in the directory of the resource that owns the thread, so a\n * process from another project sees that thread's active run and claim.\n * - Thread-owner discovery lives in `<root>/_shared/`, so a process that finds\n * a thread claimed can reach the owner and hand it the signal. It only\n * answers for a thread the asker names, so it discloses nothing new.\n * - Everything else, including peer discovery (which lists threads), stays in\n * this process's own resource directory.\n *\n * Stale sockets from crashed processes are handled by\n * {@link UnixSocketPubSub}'s built-in election logic: it detects\n * ECONNREFUSED on a dead broker socket, unlinks it, and re-elects.\n * No blanket cleanup is needed here — that would break concurrent\n * mc instances sharing a directory.\n */\nclass SignalsPubSub extends PubSub {\n readonly #resourceId: string;\n readonly #rootDir: string;\n readonly #leaseSockets = new Map<string, UnixSocketPubSub>();\n readonly #sockets = new Map<string, UnixSocketPubSub>();\n readonly #pending = new Map<string, Promise<UnixSocketPubSub>>();\n readonly #leaseProvider: LeaseProvider;\n #closed = false;\n\n constructor(resourceId: string, options: SignalsPubSubOptions = {}) {\n super();\n this.#resourceId = resourceId;\n this.#rootDir = options.rootDir ?? DEFAULT_SOCKET_ROOT;\n // Created eagerly, as before, so this resource's lease socket exists from the start.\n this.#leasesFor(resourceId);\n const route = (key: string) => this.#leasesFor(resourceOfLeaseKey(key) ?? this.#resourceId);\n this.#leaseProvider = {\n acquireLease: (key, owner, ttlMs) => route(key).acquireLease(key, owner, ttlMs),\n getLeaseOwner: key => route(key).getLeaseOwner(key),\n releaseLease: (key, owner) => route(key).releaseLease(key, owner),\n renewLease: (key, owner, ttlMs) => route(key).renewLease(key, owner, ttlMs),\n transferLease: (key, fromOwner, toOwner, ttlMs) => route(key).transferLease(key, fromOwner, toOwner, ttlMs),\n };\n }\n\n override get supportedModes(): ReadonlyArray<PubSubDeliveryMode> {\n return ['push'];\n }\n\n /**\n * Filesystem leases. A thread's run and claim leases are held in the\n * directory of the thread's resource, so every process contends for the\n * same lease; any other key stays in this resource's directory.\n */\n getLeaseProvider(): LeaseProvider {\n return this.#leaseProvider;\n }\n\n async publish(\n topic: string,\n event: Omit<Event, 'id' | 'createdAt'>,\n options?: { localOnly?: boolean },\n ): Promise<void> {\n const socket = await this.#getOrCreate(topic);\n await socket.publish(topic, event, options);\n }\n\n async subscribe(topic: string, cb: EventCallback, options?: SubscribeOptions): Promise<void> {\n const socket = await this.#getOrCreate(topic);\n await socket.subscribe(topic, cb, options);\n }\n\n async unsubscribe(topic: string, cb: EventCallback): Promise<void> {\n const socket = this.#sockets.get(this.#socketKey(topic));\n if (!socket) return;\n await socket.unsubscribe(topic, cb);\n }\n\n async flush(): Promise<void> {\n await Promise.all([...this.#sockets.values()].map(s => s.flush()));\n }\n\n async close(): Promise<void> {\n this.#closed = true;\n await Promise.allSettled([\n ...[...this.#leaseSockets.values()].map(s => s.close()),\n ...[...this.#sockets.values()].map(s => s.close()),\n ]);\n this.#sockets.clear();\n this.#leaseSockets.clear();\n }\n\n /** Get the underlying socket for a topic (for testing/inspection). */\n getSocket(topic: string): UnixSocketPubSub | undefined {\n return this.#sockets.get(this.#socketKey(topic));\n }\n\n #leasesFor(resourceId: string): UnixSocketPubSub {\n let socket = this.#leaseSockets.get(resourceId);\n if (!socket) {\n socket = new UnixSocketPubSub(join(this.#rootDir, resourceId, '.leases.sock'));\n this.#leaseSockets.set(resourceId, socket);\n }\n return socket;\n }\n\n /** The directory, under the root, that holds a topic's socket. */\n #dirFor(topic: string): string {\n if (isOwnerDiscoveryTopic(topic)) return SHARED_SCOPE_DIR;\n const decoded = decodeThreadTopic(topic);\n if (decoded !== undefined) return resourceOfThreadKey(decoded) ?? this.#resourceId;\n return this.#resourceId;\n }\n\n #socketKey(topic: string): string {\n return `${this.#dirFor(topic)}/${topicKey(topic)}`;\n }\n\n async #getOrCreate(topic: string): Promise<UnixSocketPubSub> {\n if (this.#closed) throw new Error('SignalsPubSub is closed');\n const key = this.#socketKey(topic);\n const existing = this.#sockets.get(key);\n if (existing) return existing;\n // Deduplicate concurrent callers so only one socket is created per topic.\n let inflight = this.#pending.get(key);\n if (!inflight) {\n inflight = this.#initSocket(topic, key);\n this.#pending.set(key, inflight);\n }\n const socket = await inflight;\n if (this.#closed) throw new Error('SignalsPubSub is closed');\n return socket;\n }\n\n async #initSocket(topic: string, key: string): Promise<UnixSocketPubSub> {\n try {\n const socketPath = await this.#socketPath(topic);\n if (this.#closed) throw new Error('SignalsPubSub is closed');\n const socket = new UnixSocketPubSub(socketPath);\n this.#sockets.set(key, socket);\n return socket;\n } finally {\n this.#pending.delete(key);\n }\n }\n\n async #socketPath(topic: string): Promise<string> {\n let key = topicKey(topic);\n const dir = join(this.#rootDir, this.#dirFor(topic));\n await mkdir(dir, { recursive: true });\n const candidate = join(dir, `${key}.sock`);\n // macOS sun_path limit is 104 bytes; Linux is 108. Use 104 as the\n // conservative bound. When the path is too long, replace the key with\n // a short hash so the socket can still be created.\n if (Buffer.byteLength(candidate) > 104) {\n key = Buffer.from(await globalThis.crypto.subtle.digest('SHA-256', new TextEncoder().encode(key)))\n .toString('hex')\n .slice(0, 16);\n return join(dir, `${key}.sock`);\n }\n return candidate;\n }\n}\n\n/**\n * Creates a per-topic PubSub backed by Unix sockets for cross-process signal\n * and workflow event coordination between mastracode processes.\n *\n * Topics live under `/tmp/mc/<resourceId>/`, except that a thread's stream and\n * leases live under its own resource's directory and thread-owner discovery\n * under `/tmp/mc/_shared/` (see {@link SignalsPubSub}). Stale sockets from\n * crashed processes are handled by the underlying {@link UnixSocketPubSub}'s\n * broker election logic.\n */\nexport function createSignalsPubSub(resourceId: string, options?: SignalsPubSubOptions): SignalsPubSub {\n return new SignalsPubSub(resourceId, options);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"signals-pubsub.js","names":["#resourceId","#rootDir","#leaseSockets","#sockets","#pending","#refs","#liveSubscriptions","#clearGenerations","#closing","#leaseProvider","#sharedPeerDiscovery","#canShareDiscovery","#leasesFor","#closed","#publishTo","#dirFor","#subscribeTo","#unsubscribeFrom","#release","#isRefCounted","#getOrCreate","#retain","#initSocket","#socketPath"],"sources":["../../src/utils/signals-pubsub.ts"],"sourcesContent":["import { mkdir } from 'node:fs/promises';\nimport { isAbsolute, join } from 'node:path';\n\nimport { PubSub, UnixSocketPubSub } from '@mastra/core/events';\nimport type { Event, EventCallback, LeaseProvider, PubSubDeliveryMode, SubscribeOptions } from '@mastra/core/events';\n\nconst DEFAULT_SOCKET_ROOT = '/tmp/mc';\nconst SHARED_SCOPE_DIR = '_shared';\nconst THREAD_STREAM_PREFIX = 'agent.thread-stream.';\nconst THREAD_KEY_SEPARATOR = '\\0';\nconst THREAD_CLAIM_LEASE_PREFIX = 'thread-claim:';\nconst NOTIFICATION_DISPATCH_LEASE_PREFIX = 'notification-dispatch:';\nconst LEASE_SOCKET_NAME = '.leases.sock';\nconst OWNER_DISCOVERY_TOPIC = 'agent.thread-owner-discovery';\nconst PEER_DISCOVERY_TOPIC = 'agent.thread-peer-discovery';\nconst RESERVED_SOCKET_NAME = /^agent_thread-(?:peer|owner)-discovery(?:_|$)/i;\nconst MAX_PATH_SEGMENT_LENGTH = 128;\nconst UUID_PATTERN = '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}';\nconst DISCOVERY_REPLY_TOPIC = new RegExp(`^agent\\\\.thread-(?:peer|owner)-discovery\\\\.${UUID_PATTERN}$`, 'i');\nconst IDLE_ACCEPTANCE_REPLY_TOPIC = new RegExp(`^agent\\\\.thread-stream\\\\..+\\\\.idle-acceptance\\\\.${UUID_PATTERN}$`, 'i');\n\n/**\n * One-shot reply topics are minted per request (`<topic>.<uuid>`), used for a\n * single round trip, and never published to again once the request settles.\n */\nfunction isEphemeralTopic(topic: string): boolean {\n return DISCOVERY_REPLY_TOPIC.test(topic) || IDLE_ACCEPTANCE_REPLY_TOPIC.test(topic);\n}\n\nexport type SignalsPubSubOptions = {\n /**\n * Also list this process's threads to, and find threads in, other projects on\n * this machine: peer discovery runs in `<root>/_shared/` instead of this\n * resource's directory.\n */\n sharedAgentDiscovery?: boolean;\n /**\n * Directory that holds every resource's socket directory. Defaults to the\n * absolute path in `MASTRACODE_SIGNALS_SOCKET_ROOT`, else `/tmp/mc`.\n */\n rootDir?: string;\n};\n\nfunction socketRootFromEnv(): string {\n const root = process.env.MASTRACODE_SIGNALS_SOCKET_ROOT;\n return root && isAbsolute(root) ? root : DEFAULT_SOCKET_ROOT;\n}\n\n/**\n * Whether an id can name a single directory: no path separators or control\n * characters, not `.`/`..`, and bounded in length.\n */\nfunction isSafePathSegment(value: string): boolean {\n if (!value || value.length > MAX_PATH_SEGMENT_LENGTH || value === '.' || value === '..') return false;\n for (const char of value) {\n const code = char.charCodeAt(0);\n if (char === '/' || char === '\\\\' || code < 0x20 || code === 0x7f) return false;\n }\n return true;\n}\n\n/** The resource a thread key (`<resourceId>\\0<threadId>`) belongs to. */\nfunction resourceOfThreadKey(key: string): string | undefined {\n const separatorIdx = key.indexOf(THREAD_KEY_SEPARATOR);\n if (separatorIdx <= 0) return undefined;\n const resourceId = key.slice(0, separatorIdx);\n return isSafePathSegment(resourceId) ? resourceId : undefined;\n}\n\n/** Decodes `agent.thread-stream.<encoded resourceId\\0threadId>[...]`. */\nfunction decodeThreadTopic(topic: string): string | undefined {\n if (!topic.startsWith(THREAD_STREAM_PREFIX)) return undefined;\n try {\n return decodeURIComponent(topic.slice(THREAD_STREAM_PREFIX.length));\n } catch {\n return undefined;\n }\n}\n\n/**\n * The lease that lets exactly one process dispatch a resource's due\n * notifications. Held in that resource's directory, so every process serving\n * the resource contends for it whichever project it was started in.\n */\nexport function notificationDispatchLeaseKey(resourceId: string): string {\n return `${NOTIFICATION_DISPATCH_LEASE_PREFIX}${resourceId}`;\n}\n\n/**\n * The resource a lease key belongs to: a thread's run lease and claim lease,\n * and a resource's notification dispatch lease.\n */\nfunction resourceOfLeaseKey(key: string): string | undefined {\n if (key.startsWith(NOTIFICATION_DISPATCH_LEASE_PREFIX)) {\n const resourceId = key.slice(NOTIFICATION_DISPATCH_LEASE_PREFIX.length);\n return isSafePathSegment(resourceId) ? resourceId : undefined;\n }\n return resourceOfThreadKey(\n key.startsWith(THREAD_CLAIM_LEASE_PREFIX) ? key.slice(THREAD_CLAIM_LEASE_PREFIX.length) : key,\n );\n}\n\nfunction isOwnerDiscoveryTopic(topic: string): boolean {\n return topic === OWNER_DISCOVERY_TOPIC || topic.startsWith(`${OWNER_DISCOVERY_TOPIC}.`);\n}\n\nfunction isPeerDiscoveryTopic(topic: string): boolean {\n return topic === PEER_DISCOVERY_TOPIC || topic.startsWith(`${PEER_DISCOVERY_TOPIC}.`);\n}\n\n/**\n * Whether a threadId can be the base name of a thread's socket file: no\n * separators or control characters, not `.`/`..`, and not a name another\n * socket in the same directory already uses (the lease socket, and the\n * sanitized peer/owner discovery topics and their reply topics), compared\n * case-insensitively because default macOS and Windows filesystems are.\n */\nfunction isSafeFileName(value: string): boolean {\n if (!value || value === '.' || value === '..' || `${value}.sock`.toLowerCase() === LEASE_SOCKET_NAME) return false;\n if (RESERVED_SOCKET_NAME.test(value)) return false;\n for (const char of value) {\n const code = char.charCodeAt(0);\n if (char === '/' || char === '\\\\' || code < 0x20 || code === 0x7f) return false;\n }\n return true;\n}\n\n/**\n * Derive a filesystem-safe key for the topic. Thread-stream topics embed\n * a threadId; all other topics use a sanitized version of the topic name.\n */\nfunction topicKey(topic: string): string {\n const decoded = decodeThreadTopic(topic);\n if (decoded !== undefined) {\n const separatorIdx = decoded.indexOf(THREAD_KEY_SEPARATOR);\n if (separatorIdx !== -1) return decoded.slice(separatorIdx + 1);\n }\n return topic.replace(/[^a-zA-Z0-9_-]/g, '_');\n}\n\nfunction socketKey(dir: string, topic: string): string {\n return `${dir}/${topicKey(topic)}`;\n}\n\n/**\n * A PubSub that manages one Unix socket per topic for cross-process signal\n * coordination between mastracode processes on this machine.\n *\n * Socket paths use `<root>/<dir>/<sanitized-topic>.sock` (root `/tmp/mc`)\n * for inspectability and automatic OS cleanup. Each topic gets its own\n * socket, so broker election and message routing are per-topic.\n *\n * Every process shares one database, so any process can act on any project's\n * thread — the notification dispatcher, for one, delivers whatever is due.\n * Whatever coordinates a thread therefore lives with the thread, not with the\n * process touching it:\n *\n * - A thread's stream topic and its leases (the run lease and the claim\n * lease) live in the directory of the resource that owns the thread, so a\n * process from another project sees that thread's active run and claim.\n * - Thread-owner discovery lives in `<root>/_shared/`, so a process that finds\n * a thread claimed can reach the owner and hand it the signal. It only\n * answers for a thread the asker names, so it discloses nothing new.\n * - Peer discovery (which lists threads) runs in `<root>/_shared/` when\n * `sharedAgentDiscovery` is set, so processes in every project can list each\n * other's threads. Without it, it stays in this resource's directory.\n * - Everything else stays in this process's own resource directory.\n *\n * Stale sockets from crashed processes are handled by\n * {@link UnixSocketPubSub}'s built-in election logic: it detects\n * ECONNREFUSED on a dead broker socket, unlinks it, and re-elects.\n * No blanket cleanup is needed here — that would break concurrent\n * mc instances sharing a directory.\n *\n * One-shot reply topics (discovery and idle-acceptance replies) are\n * reference-counted: every in-flight publish/subscribe and every live\n * subscription holds a reference, and the topic's socket is closed as soon as\n * the count drops to zero. Without this each request would keep a socket, a\n * file descriptor and (for the broker) a socket file for the process lifetime.\n * With `sharedAgentDiscovery`, another resource's thread topics are counted the\n * same way, so a process that signals many threads elsewhere does not keep a\n * socket open for each.\n */\nclass SignalsPubSub extends PubSub {\n readonly #resourceId: string;\n readonly #rootDir: string;\n readonly #leaseSockets = new Map<string, UnixSocketPubSub>();\n readonly #sockets = new Map<string, UnixSocketPubSub>();\n readonly #pending = new Map<string, Promise<UnixSocketPubSub>>();\n readonly #refs = new Map<string, number>();\n readonly #liveSubscriptions = new Map<string, Set<EventCallback>>();\n readonly #clearGenerations = new Map<string, number>();\n readonly #closing = new Map<string, Promise<void>>();\n readonly #leaseProvider: LeaseProvider;\n readonly #sharedPeerDiscovery: boolean;\n #closed = false;\n\n constructor(resourceId: string, options: SignalsPubSubOptions = {}) {\n super();\n this.#resourceId = resourceId;\n this.#rootDir = options.rootDir ?? socketRootFromEnv();\n this.#sharedPeerDiscovery = Boolean(options.sharedAgentDiscovery) && this.#canShareDiscovery();\n // Created eagerly, as before, so this resource's lease socket exists from the start.\n this.#leasesFor(resourceId);\n const route = (key: string) => {\n // Lease sockets are created per resource on demand; once closed, never\n // reopen one (a lease renewal timer may outlive close()).\n if (this.#closed) throw new Error('SignalsPubSub is closed');\n return this.#leasesFor(resourceOfLeaseKey(key) ?? this.#resourceId);\n };\n this.#leaseProvider = {\n acquireLease: async (key, owner, ttlMs) => route(key).acquireLease(key, owner, ttlMs),\n getLeaseOwner: async key => route(key).getLeaseOwner(key),\n releaseLease: async (key, owner) => route(key).releaseLease(key, owner),\n renewLease: async (key, owner, ttlMs) => route(key).renewLease(key, owner, ttlMs),\n transferLease: async (key, fromOwner, toOwner, ttlMs) => route(key).transferLease(key, fromOwner, toOwner, ttlMs),\n };\n }\n\n override get supportedModes(): ReadonlyArray<PubSubDeliveryMode> {\n return ['push'];\n }\n\n /**\n * Filesystem leases. A thread's run and claim leases are held in the\n * directory of the thread's resource, so every process contends for the\n * same lease; any other key stays in this resource's directory.\n */\n getLeaseProvider(): LeaseProvider {\n return this.#leaseProvider;\n }\n\n async publish(\n topic: string,\n event: Omit<Event, 'id' | 'createdAt'>,\n options?: { localOnly?: boolean },\n ): Promise<void> {\n await this.#publishTo(topic, this.#dirFor(topic), event, options);\n }\n\n async subscribe(topic: string, cb: EventCallback, options?: SubscribeOptions): Promise<void> {\n await this.#subscribeTo(topic, this.#dirFor(topic), cb, options);\n }\n\n async unsubscribe(topic: string, cb: EventCallback): Promise<void> {\n await this.#unsubscribeFrom(topic, this.#dirFor(topic), cb);\n }\n\n /**\n * Drops a one-shot reply topic: its remaining subscriptions release their\n * references and the socket closes once no operation still uses it. Other\n * topics (workflow events, thread streams) have subscribers in other\n * processes that need the broker, so clearing them is a no-op.\n */\n override async clearTopic(topic: string): Promise<void> {\n if (!isEphemeralTopic(topic)) return;\n const key = socketKey(this.#dirFor(topic), topic);\n if (!this.#refs.has(key)) return;\n this.#clearGenerations.set(key, (this.#clearGenerations.get(key) ?? 0) + 1);\n const subscriptions = this.#liveSubscriptions.get(key);\n if (!subscriptions) return;\n this.#liveSubscriptions.delete(key);\n for (let i = 0; i < subscriptions.size; i++) this.#release(key);\n }\n\n async flush(): Promise<void> {\n await Promise.all([...this.#sockets.values()].map(s => s.flush()));\n }\n\n async close(): Promise<void> {\n this.#closed = true;\n await Promise.allSettled([\n ...[...this.#leaseSockets.values()].map(s => s.close()),\n ...[...this.#sockets.values()].map(s => s.close()),\n ...this.#closing.values(),\n ]);\n this.#sockets.clear();\n this.#leaseSockets.clear();\n this.#refs.clear();\n this.#liveSubscriptions.clear();\n this.#clearGenerations.clear();\n }\n\n /** Get the underlying socket for a topic (for testing/inspection). */\n getSocket(topic: string): UnixSocketPubSub | undefined {\n return this.#sockets.get(socketKey(this.#dirFor(topic), topic));\n }\n\n #canShareDiscovery(): boolean {\n if (isSafePathSegment(this.#resourceId) && this.#resourceId !== SHARED_SCOPE_DIR) return true;\n console.warn(\n `Cross-project agent discovery is disabled: resource id ${JSON.stringify(this.#resourceId)} cannot be used as a directory name, so other projects could not reach this instance.`,\n );\n return false;\n }\n\n /**\n * One-shot reply topics close once unused. With shared discovery, so do thread\n * topics routed to another resource; without it, they behave as before.\n */\n #isRefCounted(topic: string): boolean {\n if (isEphemeralTopic(topic)) return true;\n if (!this.#sharedPeerDiscovery) return false;\n const decoded = decodeThreadTopic(topic);\n if (decoded === undefined) return false;\n const resourceId = resourceOfThreadKey(decoded);\n return resourceId !== undefined && resourceId !== this.#resourceId;\n }\n\n async #publishTo(\n topic: string,\n dir: string,\n event: Omit<Event, 'id' | 'createdAt'>,\n options?: { localOnly?: boolean },\n ): Promise<void> {\n if (!this.#isRefCounted(topic)) {\n const socket = await this.#getOrCreate(topic, dir);\n await socket.publish(topic, event, options);\n return;\n }\n const key = socketKey(dir, topic);\n // Retain synchronously so a concurrent release cannot close the socket\n // this publish is about to use.\n this.#retain(key);\n try {\n const socket = await this.#getOrCreate(topic, dir);\n await socket.publish(topic, event, options);\n } finally {\n this.#release(key);\n }\n }\n\n async #subscribeTo(topic: string, dir: string, cb: EventCallback, options?: SubscribeOptions): Promise<void> {\n if (!this.#isRefCounted(topic)) {\n const socket = await this.#getOrCreate(topic, dir);\n await socket.subscribe(topic, cb, options);\n return;\n }\n const key = socketKey(dir, topic);\n this.#retain(key);\n const generation = this.#clearGenerations.get(key) ?? 0;\n try {\n const socket = await this.#getOrCreate(topic, dir);\n await socket.subscribe(topic, cb, options);\n if ((this.#clearGenerations.get(key) ?? 0) !== generation) {\n // The topic was cleared while this subscribe was in flight.\n await socket.unsubscribe(topic, cb);\n return;\n }\n let subscriptions = this.#liveSubscriptions.get(key);\n if (!subscriptions) {\n subscriptions = new Set();\n this.#liveSubscriptions.set(key, subscriptions);\n }\n if (!subscriptions.has(cb)) {\n subscriptions.add(cb);\n this.#retain(key);\n }\n } finally {\n this.#release(key);\n }\n }\n\n async #unsubscribeFrom(topic: string, dir: string, cb: EventCallback): Promise<void> {\n const key = socketKey(dir, topic);\n const socket = this.#sockets.get(key);\n const subscriptions = this.#isRefCounted(topic) ? this.#liveSubscriptions.get(key) : undefined;\n if (!subscriptions?.delete(cb)) {\n if (!socket) return;\n await socket.unsubscribe(topic, cb);\n return;\n }\n if (subscriptions.size === 0) this.#liveSubscriptions.delete(key);\n try {\n await socket?.unsubscribe(topic, cb);\n } finally {\n this.#release(key);\n }\n }\n\n #leasesFor(resourceId: string): UnixSocketPubSub {\n let socket = this.#leaseSockets.get(resourceId);\n if (!socket) {\n socket = new UnixSocketPubSub(join(this.#rootDir, resourceId, LEASE_SOCKET_NAME));\n this.#leaseSockets.set(resourceId, socket);\n }\n return socket;\n }\n\n /** The directory, under the root, that holds a topic's socket. */\n #dirFor(topic: string): string {\n if (isOwnerDiscoveryTopic(topic)) return SHARED_SCOPE_DIR;\n if (this.#sharedPeerDiscovery && isPeerDiscoveryTopic(topic)) return SHARED_SCOPE_DIR;\n const decoded = decodeThreadTopic(topic);\n if (decoded !== undefined) return resourceOfThreadKey(decoded) ?? this.#resourceId;\n return this.#resourceId;\n }\n\n #retain(key: string): void {\n this.#refs.set(key, (this.#refs.get(key) ?? 0) + 1);\n }\n\n #release(key: string): void {\n const refs = (this.#refs.get(key) ?? 0) - 1;\n if (refs > 0) {\n this.#refs.set(key, refs);\n return;\n }\n this.#refs.delete(key);\n this.#clearGenerations.delete(key);\n this.#liveSubscriptions.delete(key);\n const socket = this.#sockets.get(key);\n if (!socket) return;\n this.#sockets.delete(key);\n const closing: Promise<void> = socket\n .close()\n .catch(() => {})\n .finally(() => {\n if (this.#closing.get(key) === closing) this.#closing.delete(key);\n });\n this.#closing.set(key, closing);\n }\n\n async #getOrCreate(topic: string, dir: string): Promise<UnixSocketPubSub> {\n if (this.#closed) throw new Error('SignalsPubSub is closed');\n const key = socketKey(dir, topic);\n // A socket for this key may still be closing after its last reference\n // was released; never hand out the closing instance.\n const closing = this.#closing.get(key);\n if (closing) {\n await closing;\n if (this.#closed) throw new Error('SignalsPubSub is closed');\n }\n const existing = this.#sockets.get(key);\n if (existing) return existing;\n // Deduplicate concurrent callers so only one socket is created per topic.\n let inflight = this.#pending.get(key);\n if (!inflight) {\n inflight = this.#initSocket(topic, dir, key);\n this.#pending.set(key, inflight);\n }\n const socket = await inflight;\n if (this.#closed) throw new Error('SignalsPubSub is closed');\n return socket;\n }\n\n async #initSocket(topic: string, dir: string, key: string): Promise<UnixSocketPubSub> {\n try {\n const socketPath = await this.#socketPath(topic, dir);\n if (this.#closed) throw new Error('SignalsPubSub is closed');\n const socket = new UnixSocketPubSub(socketPath);\n this.#sockets.set(key, socket);\n return socket;\n } finally {\n this.#pending.delete(key);\n }\n }\n\n async #socketPath(topic: string, scope: string): Promise<string> {\n let key = topicKey(topic);\n // A threadId names a file in its resource's directory, so never let it\n // reach outside that directory, regardless of who owns the thread or\n // whether cross-project discovery is enabled.\n if (decodeThreadTopic(topic) !== undefined && !isSafeFileName(key)) {\n throw new Error('Cannot route an agent thread topic whose threadId is not a safe file name');\n }\n const dir = join(this.#rootDir, scope);\n await mkdir(dir, { recursive: true });\n const candidate = join(dir, `${key}.sock`);\n // macOS sun_path limit is 104 bytes; Linux is 108. Use 104 as the\n // conservative bound. When the path is too long, replace the key with\n // a short hash so the socket can still be created.\n if (Buffer.byteLength(candidate) > 104) {\n key = Buffer.from(await globalThis.crypto.subtle.digest('SHA-256', new TextEncoder().encode(key)))\n .toString('hex')\n .slice(0, 16);\n return join(dir, `${key}.sock`);\n }\n return candidate;\n }\n}\n\n/**\n * Creates a per-topic PubSub backed by Unix sockets for cross-process signal\n * and workflow event coordination between mastracode processes.\n *\n * Topics live under `/tmp/mc/<resourceId>/`, except that a thread's stream and\n * leases live under its own resource's directory and thread-owner discovery\n * under `/tmp/mc/_shared/` (see {@link SignalsPubSub}). With\n * `sharedAgentDiscovery`, peer discovery runs there too. Stale sockets from\n * crashed processes are handled by the underlying {@link UnixSocketPubSub}'s\n * broker election logic.\n */\nexport function createSignalsPubSub(resourceId: string, options?: SignalsPubSubOptions): SignalsPubSub {\n return new SignalsPubSub(resourceId, options);\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import * as path$1 from "path";
2
- import { execSync } from "child_process";
3
2
  import { promises } from "fs";
3
+ import { execSync } from "child_process";
4
4
  //#region src/utils/slash-command-processor.ts
5
5
  /**
6
6
  * Process a slash command by replacing variables and executing shell commands
@@ -1,6 +1,6 @@
1
1
  import { dirname, join, resolve, sep } from "path";
2
- import { execFile } from "child_process";
3
2
  import { readFileSync, realpathSync } from "fs";
3
+ import { execFile } from "child_process";
4
4
  import { homedir } from "os";
5
5
  import { fileURLToPath } from "url";
6
6
  //#region src/utils/update-check.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mastra/code-sdk",
3
- "version": "1.11.0-alpha.1",
3
+ "version": "1.11.0-alpha.10",
4
4
  "description": "Mastra Code SDK: the agent core behind Mastra Code (everything except the TUI) — build your own UIs and surfaces on top of it",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -56,26 +56,26 @@
56
56
  "execa": "^9.6.1",
57
57
  "libsql": "^0.5.29",
58
58
  "posthog-node": "^5.46.1",
59
- "smol-toml": "1.8.0",
59
+ "smol-toml": "1.9.0",
60
60
  "tokenx": "^1.3.0",
61
61
  "vscode-jsonrpc": "^8.2.1",
62
62
  "vscode-languageserver-protocol": "^3.17.5",
63
63
  "yaml": "^2.7.1",
64
64
  "zod": "^4.6.4",
65
- "@mastra/duckdb": "1.12.1",
66
65
  "@mastra/agent-browser": "0.5.3",
67
- "@mastra/github-signals": "0.5.1",
66
+ "@mastra/duckdb": "1.13.0-alpha.3",
68
67
  "@mastra/fastembed": "1.3.2",
69
- "@mastra/libsql": "1.25.0",
70
- "@mastra/mcp": "2.1.2",
71
- "@mastra/core": "1.75.0-alpha.1",
72
- "@mastra/memory": "1.36.0-alpha.1",
73
- "@mastra/observability": "1.18.3",
68
+ "@mastra/github-signals": "0.5.1",
69
+ "@mastra/libsql": "1.25.1-alpha.0",
70
+ "@mastra/core": "1.75.0-alpha.8",
71
+ "@mastra/mcp": "2.2.0-alpha.2",
72
+ "@mastra/memory": "1.36.0-alpha.6",
74
73
  "@mastra/parallel": "0.1.2",
75
- "@mastra/pg": "1.30.0-alpha.0",
76
- "@mastra/tavily": "1.1.2",
74
+ "@mastra/observability": "1.18.4-alpha.1",
75
+ "@mastra/stagehand": "0.3.5",
77
76
  "@mastra/schema-compat": "1.3.13",
78
- "@mastra/stagehand": "0.3.5"
77
+ "@mastra/tavily": "1.1.2",
78
+ "@mastra/pg": "1.30.0-alpha.6"
79
79
  },
80
80
  "devDependencies": {
81
81
  "@libsql/client": "^0.18.0",