memeloop 0.0.1

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 (137) hide show
  1. package/dist/anthropic.cjs +72 -0
  2. package/dist/anthropic.cjs.map +1 -0
  3. package/dist/anthropic.d.cts +21 -0
  4. package/dist/anthropic.d.ts +21 -0
  5. package/dist/anthropic.js +21 -0
  6. package/dist/anthropic.js.map +1 -0
  7. package/dist/azure.cjs +72 -0
  8. package/dist/azure.cjs.map +1 -0
  9. package/dist/azure.d.cts +21 -0
  10. package/dist/azure.d.ts +21 -0
  11. package/dist/azure.js +21 -0
  12. package/dist/azure.js.map +1 -0
  13. package/dist/chunk-3CN7DFBL.js +4894 -0
  14. package/dist/chunk-3CN7DFBL.js.map +1 -0
  15. package/dist/chunk-4AZ4D7U5.js +32 -0
  16. package/dist/chunk-4AZ4D7U5.js.map +1 -0
  17. package/dist/chunk-6ON4XSOV.js +63 -0
  18. package/dist/chunk-6ON4XSOV.js.map +1 -0
  19. package/dist/chunk-DJQNGIXX.js +4793 -0
  20. package/dist/chunk-DJQNGIXX.js.map +1 -0
  21. package/dist/chunk-FKQP6AOQ.js +32 -0
  22. package/dist/chunk-FKQP6AOQ.js.map +1 -0
  23. package/dist/chunk-OVJ7JJBA.js +35 -0
  24. package/dist/chunk-OVJ7JJBA.js.map +1 -0
  25. package/dist/chunk-P4UZ7IXC.js +42 -0
  26. package/dist/chunk-P4UZ7IXC.js.map +1 -0
  27. package/dist/chunk-YHJ2AEFR.js +4933 -0
  28. package/dist/chunk-YHJ2AEFR.js.map +1 -0
  29. package/dist/chunk-YVK7RYRO.js +4973 -0
  30. package/dist/chunk-YVK7RYRO.js.map +1 -0
  31. package/dist/code-assistant.json +114 -0
  32. package/dist/cohere.cjs +72 -0
  33. package/dist/cohere.cjs.map +1 -0
  34. package/dist/cohere.d.cts +21 -0
  35. package/dist/cohere.d.ts +21 -0
  36. package/dist/cohere.js +21 -0
  37. package/dist/cohere.js.map +1 -0
  38. package/dist/deepseek.cjs +72 -0
  39. package/dist/deepseek.cjs.map +1 -0
  40. package/dist/deepseek.d.cts +21 -0
  41. package/dist/deepseek.d.ts +21 -0
  42. package/dist/deepseek.js +21 -0
  43. package/dist/deepseek.js.map +1 -0
  44. package/dist/dist-65U7OYRG.js +1257 -0
  45. package/dist/dist-65U7OYRG.js.map +1 -0
  46. package/dist/fetchProvider-Cu4WkU5O.d.cts +819 -0
  47. package/dist/fetchProvider-Cu4WkU5O.d.ts +819 -0
  48. package/dist/frontend-ui-ux.json +105 -0
  49. package/dist/general-assistant.json +95 -0
  50. package/dist/git-master.json +51 -0
  51. package/dist/google-vertex.cjs +72 -0
  52. package/dist/google-vertex.cjs.map +1 -0
  53. package/dist/google-vertex.d.cts +21 -0
  54. package/dist/google-vertex.d.ts +21 -0
  55. package/dist/google-vertex.js +21 -0
  56. package/dist/google-vertex.js.map +1 -0
  57. package/dist/google.cjs +72 -0
  58. package/dist/google.cjs.map +1 -0
  59. package/dist/google.d.cts +21 -0
  60. package/dist/google.d.ts +21 -0
  61. package/dist/google.js +21 -0
  62. package/dist/google.js.map +1 -0
  63. package/dist/groq.cjs +72 -0
  64. package/dist/groq.cjs.map +1 -0
  65. package/dist/groq.d.cts +21 -0
  66. package/dist/groq.d.ts +21 -0
  67. package/dist/groq.js +21 -0
  68. package/dist/groq.js.map +1 -0
  69. package/dist/index.cjs +8916 -0
  70. package/dist/index.cjs.map +1 -0
  71. package/dist/index.d.cts +1816 -0
  72. package/dist/index.d.mts +3203 -0
  73. package/dist/index.d.ts +1816 -0
  74. package/dist/index.js +4040 -0
  75. package/dist/index.js.map +1 -0
  76. package/dist/index.mjs +7064 -0
  77. package/dist/index.mjs.map +1 -0
  78. package/dist/llm-providers.cjs +311 -0
  79. package/dist/llm-providers.cjs.map +1 -0
  80. package/dist/llm-providers.d.cts +111 -0
  81. package/dist/llm-providers.d.ts +111 -0
  82. package/dist/llm-providers.js +232 -0
  83. package/dist/llm-providers.js.map +1 -0
  84. package/dist/loop-api.cjs +4924 -0
  85. package/dist/loop-api.cjs.map +1 -0
  86. package/dist/loop-api.d.cts +11 -0
  87. package/dist/loop-api.d.ts +11 -0
  88. package/dist/loop-api.js +189 -0
  89. package/dist/loop-api.js.map +1 -0
  90. package/dist/mistral.cjs +72 -0
  91. package/dist/mistral.cjs.map +1 -0
  92. package/dist/mistral.d.cts +21 -0
  93. package/dist/mistral.d.ts +21 -0
  94. package/dist/mistral.js +21 -0
  95. package/dist/mistral.js.map +1 -0
  96. package/dist/multipart-parser-EJCXIUHC.js +372 -0
  97. package/dist/multipart-parser-EJCXIUHC.js.map +1 -0
  98. package/dist/openai.cjs +72 -0
  99. package/dist/openai.cjs.map +1 -0
  100. package/dist/openai.d.cts +21 -0
  101. package/dist/openai.d.ts +21 -0
  102. package/dist/openai.js +21 -0
  103. package/dist/openai.js.map +1 -0
  104. package/dist/perplexity.cjs +72 -0
  105. package/dist/perplexity.cjs.map +1 -0
  106. package/dist/perplexity.d.cts +21 -0
  107. package/dist/perplexity.d.ts +21 -0
  108. package/dist/perplexity.js +21 -0
  109. package/dist/perplexity.js.map +1 -0
  110. package/dist/playwright.json +52 -0
  111. package/dist/promptConcat-BJdazTg5.d.cts +1832 -0
  112. package/dist/promptConcat-BJdazTg5.d.ts +1832 -0
  113. package/dist/promptConcat-CAkpni_8.d.cts +1834 -0
  114. package/dist/promptConcat-CAkpni_8.d.ts +1834 -0
  115. package/dist/promptConcat-CfH5-GAE.d.cts +1036 -0
  116. package/dist/promptConcat-ChcAZOOL.d.ts +1036 -0
  117. package/dist/promptConcat-G5TnJVRy.d.cts +1800 -0
  118. package/dist/promptConcat-G5TnJVRy.d.ts +1800 -0
  119. package/dist/promptConcat-L72eowzp.d.ts +1090 -0
  120. package/dist/promptConcat-eLHdN7K8.d.cts +1090 -0
  121. package/dist/src-VTSXKG72.js +1409 -0
  122. package/dist/src-VTSXKG72.js.map +1 -0
  123. package/dist/togetherai.cjs +72 -0
  124. package/dist/togetherai.cjs.map +1 -0
  125. package/dist/togetherai.d.cts +21 -0
  126. package/dist/togetherai.d.ts +21 -0
  127. package/dist/togetherai.js +21 -0
  128. package/dist/togetherai.js.map +1 -0
  129. package/dist/types-B0Jm2l38.d.cts +767 -0
  130. package/dist/types-B0Jm2l38.d.ts +767 -0
  131. package/dist/xai.cjs +72 -0
  132. package/dist/xai.cjs.map +1 -0
  133. package/dist/xai.d.cts +21 -0
  134. package/dist/xai.d.ts +21 -0
  135. package/dist/xai.js +21 -0
  136. package/dist/xai.js.map +1 -0
  137. package/package.json +87 -0
package/dist/index.js ADDED
@@ -0,0 +1,4040 @@
1
+ import {
2
+ AGENT_AGENT_LOOP_ID,
3
+ AGENT_TOOL_LOOP_ID,
4
+ ALL_CATEGORIES,
5
+ ASK_QUESTION_TOOL_ID,
6
+ ASK_USER_QUESTION_TOOL_ID,
7
+ AgentProfileRegistry,
8
+ BUILTIN_AGENT_AGENT_LOOP_QUALITY_GATE_SCRIPT_ID,
9
+ BUILTIN_AGENT_PROFILES,
10
+ BUILTIN_AGENT_TOOL_LOOP_DEFAULT_SCRIPT_ID,
11
+ BUILTIN_TOOL_PLUGIN_IDS,
12
+ HookRegistry,
13
+ MEMELOOP_STRUCTURED_TOOL_KEY,
14
+ PERMISSIONS_TABLE_DDL,
15
+ PLUGIN_ASK_QUESTION,
16
+ PLUGIN_ASK_USER_QUESTION,
17
+ PLUGIN_MCP_CLIENT,
18
+ PLUGIN_MCP_FORWARD,
19
+ PLUGIN_REMOTE_AGENT,
20
+ PLUGIN_SPAWN_AGENT,
21
+ PLUGIN_TASK,
22
+ PLUGIN_TODO_WRITE,
23
+ ProviderRegistry,
24
+ TODO_WRITE_TOOL_ID,
25
+ TokenTracker,
26
+ __clearTodoStore,
27
+ askQuestionConfigSchema,
28
+ askQuestionImpl,
29
+ askUserQuestionConfigSchema,
30
+ askUserQuestionImpl,
31
+ autoCompact,
32
+ buildAgent,
33
+ builtinAgentAgentLoopSources,
34
+ builtinAgentToolLoopSources,
35
+ cancelPendingApprovals,
36
+ checkPermission,
37
+ clearHooks,
38
+ clearPluginRegistrations,
39
+ compactMessages,
40
+ createAgentAgentLoopDefinition,
41
+ createAgentFrameworkHooks,
42
+ createAgentInstanceFromDefinition,
43
+ createAgentLoopRunner,
44
+ createAgentToolLoopDefinition,
45
+ createAgentToolLoopRunner,
46
+ createChatMessage,
47
+ createHooksWithPlugins,
48
+ createMemeLoopRuntime,
49
+ createPluginAPI,
50
+ evaluateApproval,
51
+ executeHooks,
52
+ exploreAgent,
53
+ extractMemeloopStructuredToolPayload,
54
+ findPromptById,
55
+ flattenPrompts,
56
+ getActivePluginRegistry,
57
+ getAgentAgentLoopId,
58
+ getAgentProfileRegistry,
59
+ getBuiltinAgentAgentLoopSource,
60
+ getBuiltinAgentToolLoopSource,
61
+ getBuiltinLoopProfile,
62
+ getBuiltinLoopProfiles,
63
+ getBuiltinToolPlugins,
64
+ getCategoryConfig,
65
+ getDefaultHookRegistry,
66
+ getHookCount,
67
+ getLoadedPlugin,
68
+ getLoopRegistry,
69
+ getMcpClientToolId,
70
+ getMcpForwardToolId,
71
+ getPendingApprovals,
72
+ getPluginRegistrations,
73
+ getRemoteAgentToolId,
74
+ getSpawnAgentToolId,
75
+ getTaskToolId,
76
+ getToolMetadata,
77
+ getToolParameterSchema,
78
+ hasHooks,
79
+ isPluginLoaded,
80
+ isTaskCategory,
81
+ librarianAgent,
82
+ listPlugins,
83
+ listRegisteredHookTypes,
84
+ loadAgentAgentLoopScript,
85
+ loadAgentToolLoopScript,
86
+ loadPluginModule,
87
+ loadPluginModules,
88
+ loadUserPermissions,
89
+ matchAllToolCallings,
90
+ matchPattern,
91
+ matchToolCalling,
92
+ mcpClientConfigSchema,
93
+ mcpClientImpl,
94
+ mcpForwardConfigSchema,
95
+ mcpForwardImpl,
96
+ mergePermissionSets,
97
+ onApprovalRequest,
98
+ oracleAgent,
99
+ planAgent,
100
+ pluginRegistry,
101
+ promptConcatStream,
102
+ registerBuiltinLoops,
103
+ registerBuiltinPromptPlugins,
104
+ registerBuiltinToolPlugins,
105
+ registerHook,
106
+ registerPluginHooks,
107
+ registerPluginTools,
108
+ registerToolParameterSchema,
109
+ remoteAgentConfigSchema,
110
+ remoteAgentImpl,
111
+ remoteAgentListImpl,
112
+ requestApproval,
113
+ resetAgentProfileRegistry,
114
+ resetLoopRegistry,
115
+ resolveAgentToolLoopTerminalState,
116
+ resolveApproval,
117
+ resolveCategory,
118
+ resolvePromptPluginMap,
119
+ resolveQuestionAnswer,
120
+ runAgentToolLoopTurn,
121
+ runPostProcessHooks,
122
+ runProcessPromptsHooks,
123
+ runResponseCompleteHooks,
124
+ runToolExecutedHooks,
125
+ runWithPluginRegistry,
126
+ saveUserPermissions,
127
+ shouldCompact,
128
+ spawnAgentConfigSchema,
129
+ spawnAgentImpl,
130
+ taskToolConfigSchema,
131
+ taskToolImpl,
132
+ todoWriteConfigSchema,
133
+ todoWriteImpl,
134
+ truncateToolSummary,
135
+ unloadAllPlugins,
136
+ unloadPlugin,
137
+ unregisterHook,
138
+ validatePluginManifest
139
+ } from "./chunk-3CN7DFBL.js";
140
+ import {
141
+ createFetchLLMProvider
142
+ } from "./chunk-OVJ7JJBA.js";
143
+
144
+ // src/sync/attachmentRpcCodec.ts
145
+ function decodeAttachmentBlobRpc(result) {
146
+ if (result == null || typeof result !== "object") return null;
147
+ const r = result;
148
+ if (r.error || !r.found || typeof r.dataBase64 !== "string") return null;
149
+ const data = Buffer.from(r.dataBase64, "base64");
150
+ if (!data.length) return null;
151
+ return {
152
+ data: new Uint8Array(data),
153
+ filename: typeof r.filename === "string" ? r.filename : "attachment",
154
+ mimeType: typeof r.mimeType === "string" ? r.mimeType : "application/octet-stream",
155
+ size: typeof r.size === "number" && r.size > 0 ? r.size : data.length
156
+ };
157
+ }
158
+
159
+ // src/sync/chatSyncEngine.ts
160
+ var ChatSyncEngine = class {
161
+ nodeId;
162
+ storage;
163
+ getPeers;
164
+ versionVector = {};
165
+ constructor(options) {
166
+ this.nodeId = options.nodeId;
167
+ this.storage = options.storage;
168
+ this.getPeers = options.peers;
169
+ this.versionVector[this.nodeId] = 0;
170
+ }
171
+ /**
172
+ * 在本节点新产生一条消息时调用,增加本节点的 lamportClock。
173
+ */
174
+ bumpLocalVersion() {
175
+ this.versionVector[this.nodeId] = (this.versionVector[this.nodeId] ?? 0) + 1;
176
+ }
177
+ /**
178
+ * 执行一次与所有 peer 的增量同步(metadata 级别),并尽力拉取缺失消息。
179
+ */
180
+ async syncOnce() {
181
+ const peers = this.getPeers();
182
+ if (peers.length === 0) return;
183
+ const localVersion = { ...this.versionVector };
184
+ const pulledConversations = /* @__PURE__ */ new Set();
185
+ for (const peer of peers) {
186
+ const { remoteVersion, missingForRemote } = await peer.exchangeVersionVector(localVersion);
187
+ const missingForLocal = await peer.pullMissingMetadata(this.versionVector);
188
+ for (const meta of missingForLocal) {
189
+ await this.storage.upsertConversationMetadata(meta);
190
+ pulledConversations.add(meta.conversationId);
191
+ }
192
+ for (const [nodeId, clock] of Object.entries(remoteVersion)) {
193
+ const current = this.versionVector[nodeId] ?? 0;
194
+ if (clock > current) {
195
+ this.versionVector[nodeId] = clock;
196
+ }
197
+ }
198
+ if (missingForRemote.length > 0) {
199
+ this.bumpLocalVersion();
200
+ }
201
+ }
202
+ for (const conversationId of pulledConversations) {
203
+ await this.pullMessagesForConversationFromPeers(conversationId, peers);
204
+ }
205
+ }
206
+ /**
207
+ * Anti-entropy:先 syncOnce,再对已知会话逐个向各 peer 拉取缺失消息(参考审查单「定期对账」)。
208
+ */
209
+ async antiEntropyOnce() {
210
+ await this.syncOnce();
211
+ const list = await this.storage.listConversations({ limit: 500 });
212
+ const peers = this.getPeers();
213
+ for (const meta of list) {
214
+ await this.pullMessagesForConversationFromPeers(meta.conversationId, peers);
215
+ }
216
+ }
217
+ async pullMessagesForConversationFromPeers(conversationId, peers) {
218
+ const localMsgs = await this.storage.getMessages(conversationId, { mode: "full-content" });
219
+ const knownIds = localMsgs.map((m) => m.messageId);
220
+ for (const peer of peers) {
221
+ if (!peer.pullMissingMessages) continue;
222
+ try {
223
+ const incoming = await peer.pullMissingMessages(conversationId, knownIds);
224
+ if (incoming.length > 0) {
225
+ await this.storage.insertMessagesIfAbsent(incoming);
226
+ await this.ensureAttachmentsFromMessages(incoming, peers);
227
+ }
228
+ } catch {
229
+ }
230
+ }
231
+ }
232
+ /** 从消息元数据收集 `attachments[].contentHash`,对本地缺失 BLOB 的哈希向各 peer 拉取并 `saveAttachment`。 */
233
+ async ensureAttachmentsFromMessages(messages, peers) {
234
+ const hashes = /* @__PURE__ */ new Set();
235
+ for (const m of messages) {
236
+ for (const a of m.attachments ?? []) {
237
+ if (a.contentHash) hashes.add(a.contentHash);
238
+ }
239
+ }
240
+ for (const h of hashes) {
241
+ const reference = await this.storage.getAttachment(h);
242
+ if (reference) {
243
+ const reader = this.storage.readAttachmentData?.bind(this.storage);
244
+ if (!reader) continue;
245
+ const bytes = await reader(h);
246
+ if (bytes && bytes.length > 0) continue;
247
+ }
248
+ for (const peer of peers) {
249
+ const pull = peer.pullAttachmentBlob?.bind(peer);
250
+ if (!pull) continue;
251
+ try {
252
+ const blob = await pull(h);
253
+ if (blob?.data?.length) {
254
+ const ar = {
255
+ contentHash: h,
256
+ filename: blob.filename,
257
+ mimeType: blob.mimeType,
258
+ size: blob.size > 0 ? blob.size : blob.data.length
259
+ };
260
+ await this.storage.saveAttachment(ar, blob.data);
261
+ break;
262
+ }
263
+ } catch {
264
+ }
265
+ }
266
+ }
267
+ }
268
+ getVersionVector() {
269
+ return { ...this.versionVector };
270
+ }
271
+ getStorage() {
272
+ return this.storage;
273
+ }
274
+ };
275
+
276
+ // src/sync/peerNodeAdapter.ts
277
+ var PeerNodeSyncAdapter = class {
278
+ nodeId;
279
+ transport;
280
+ constructor(nodeId, transport) {
281
+ this.nodeId = nodeId;
282
+ this.transport = transport;
283
+ }
284
+ exchangeVersionVector(localVersion) {
285
+ return this.transport.exchangeVersionVector(this.nodeId, localVersion);
286
+ }
287
+ pullMissingMetadata(sinceVersion) {
288
+ return this.transport.pullMissingMetadata(this.nodeId, sinceVersion);
289
+ }
290
+ pullMissingMessages(conversationId, knownMessageIds) {
291
+ const function_ = this.transport.pullMissingMessages?.bind(this.transport);
292
+ if (!function_) {
293
+ return Promise.resolve([]);
294
+ }
295
+ return function_(this.nodeId, conversationId, knownMessageIds);
296
+ }
297
+ pullAttachmentBlob(contentHash) {
298
+ const function_ = this.transport.pullAttachmentBlob?.bind(this.transport);
299
+ if (!function_) {
300
+ return Promise.resolve(null);
301
+ }
302
+ return function_(this.nodeId, contentHash);
303
+ }
304
+ };
305
+
306
+ // src/types.ts
307
+ function isUserInitiatedConversation(meta) {
308
+ return meta.isUserInitiated;
309
+ }
310
+ function createInstanceDeltaFromDefinition(definition, overrides) {
311
+ const delta = {};
312
+ for (const key of Object.keys(overrides)) {
313
+ const value = overrides[key];
314
+ if (value !== void 0 && value !== definition[key]) {
315
+ delta[key] = value;
316
+ }
317
+ }
318
+ return delta;
319
+ }
320
+
321
+ // src/storage/sessionStorage.ts
322
+ function createCheckpointRecord(conversationId, messages, savedAt = (/* @__PURE__ */ new Date()).toISOString()) {
323
+ const lastMessage = messages[messages.length - 1];
324
+ const preview = lastMessage && typeof lastMessage.content === "string" ? lastMessage.content.slice(0, 200) : "";
325
+ return {
326
+ conversationId,
327
+ messages: [...messages],
328
+ savedAt,
329
+ messageCount: messages.length,
330
+ lastMessagePreview: preview
331
+ };
332
+ }
333
+ function parseCheckpointRecord(raw) {
334
+ try {
335
+ const record = JSON.parse(raw);
336
+ return isCheckpointRecord(record) ? record : null;
337
+ } catch {
338
+ return null;
339
+ }
340
+ }
341
+ function serializeCheckpointRecord(record) {
342
+ return JSON.stringify(record, null, 2);
343
+ }
344
+ var InMemoryCheckpointStore = class {
345
+ records = /* @__PURE__ */ new Map();
346
+ constructor(options = {}) {
347
+ for (const record of options.records ?? []) {
348
+ this.records.set(record.conversationId, cloneCheckpointRecord(record));
349
+ }
350
+ }
351
+ async saveCheckpoint(conversationId, messages) {
352
+ const record = createCheckpointRecord(conversationId, messages);
353
+ this.records.set(conversationId, cloneCheckpointRecord(record));
354
+ return cloneCheckpointRecord(record);
355
+ }
356
+ async loadCheckpoint(conversationId) {
357
+ const record = this.records.get(conversationId);
358
+ return record ? cloneCheckpointRecord(record) : null;
359
+ }
360
+ async listCheckpoints() {
361
+ return [...this.records.values()].map(toCheckpointSummary).sort((a, b) => b.savedAt.localeCompare(a.savedAt));
362
+ }
363
+ async deleteCheckpoint(conversationId) {
364
+ return this.records.delete(conversationId);
365
+ }
366
+ };
367
+ var SessionStorage = class extends InMemoryCheckpointStore {
368
+ };
369
+ function isCheckpointRecord(record) {
370
+ if (!record || typeof record !== "object") return false;
371
+ const candidate = record;
372
+ return typeof candidate.conversationId === "string" && Array.isArray(candidate.messages) && typeof candidate.savedAt === "string" && typeof candidate.messageCount === "number" && typeof candidate.lastMessagePreview === "string";
373
+ }
374
+ function cloneCheckpointRecord(record) {
375
+ return {
376
+ ...record,
377
+ messages: [...record.messages]
378
+ };
379
+ }
380
+ function toCheckpointSummary(record) {
381
+ return {
382
+ conversationId: record.conversationId,
383
+ savedAt: record.savedAt,
384
+ messageCount: record.messageCount,
385
+ lastMessagePreview: record.lastMessagePreview
386
+ };
387
+ }
388
+
389
+ // src/agent/tiddlerTemplateConverter.ts
390
+ function tiddlerToAgentDefinition(tiddler, workspaceName) {
391
+ if (!tiddler || !tiddler.title || typeof tiddler.text !== "string") return null;
392
+ let agentFrameworkConfig;
393
+ try {
394
+ const parsed = JSON.parse(tiddler.text);
395
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
396
+ return null;
397
+ }
398
+ agentFrameworkConfig = parsed;
399
+ } catch {
400
+ return null;
401
+ }
402
+ const getString = (field, fallback = "") => typeof field === "string" ? field : field && typeof field === "object" ? JSON.stringify(field) : fallback;
403
+ const parseJSON = (field) => {
404
+ if (typeof field === "string") {
405
+ try {
406
+ const parsed = JSON.parse(field);
407
+ if (typeof parsed === "object" && parsed !== null) {
408
+ return parsed;
409
+ }
410
+ } catch {
411
+ }
412
+ }
413
+ return void 0;
414
+ };
415
+ const id = `wiki-template-${getString(tiddler.title).replace(/[^a-zA-Z0-9-_]/g, "-")}`;
416
+ const name = getString(tiddler.caption) || getString(tiddler.title);
417
+ const description = getString(tiddler.description) || `Agent template from ${workspaceName || "wiki"}`;
418
+ const modelConfigRaw = parseJSON(tiddler.ai_api_config);
419
+ const rawAsRecord = modelConfigRaw && typeof modelConfigRaw === "object" && !Array.isArray(modelConfigRaw) ? modelConfigRaw : void 0;
420
+ const modelConfig = rawAsRecord ? {
421
+ provider: typeof rawAsRecord.provider === "string" ? rawAsRecord.provider : "",
422
+ model: typeof rawAsRecord.model === "string" ? rawAsRecord.model : ""
423
+ } : void 0;
424
+ const toolsRaw = parseJSON(tiddler.agent_tools);
425
+ const toolNames = Array.isArray(toolsRaw) ? toolsRaw.filter((t) => typeof t?.toolId === "string").map((t) => String(t.toolId)) : [];
426
+ return {
427
+ id,
428
+ name,
429
+ description,
430
+ systemPrompt: "",
431
+ tools: toolNames,
432
+ modelConfig,
433
+ // Keep the original config as promptSchema for the UI editor
434
+ promptSchema: agentFrameworkConfig,
435
+ agentFrameworkConfig: {
436
+ prompts: Array.isArray(agentFrameworkConfig.prompts) ? agentFrameworkConfig.prompts : [],
437
+ plugins: Array.isArray(agentFrameworkConfig.plugins) ? agentFrameworkConfig.plugins : [],
438
+ response: Array.isArray(agentFrameworkConfig.response) ? agentFrameworkConfig.response : void 0
439
+ },
440
+ version: "1"
441
+ };
442
+ }
443
+
444
+ // src/agent-management/AgentSessionController.ts
445
+ var AgentSessionController = class {
446
+ options;
447
+ listener = null;
448
+ snapshot = {
449
+ agent: null,
450
+ loading: false,
451
+ error: null,
452
+ messages: [],
453
+ orderedMessageIds: [],
454
+ streamingMessageIds: /* @__PURE__ */ new Set()
455
+ };
456
+ unsubAgentUpdates = null;
457
+ unsubMessages = null;
458
+ pollingTimer = null;
459
+ agentId = null;
460
+ constructor(options) {
461
+ this.options = {
462
+ pollInterval: 500,
463
+ ...options
464
+ };
465
+ }
466
+ // ── Lifecycle ─────────────────────────────────────────────────
467
+ /**
468
+ * Start a session for the given agent ID.
469
+ * Fetches the agent, subscribes to live updates, and loads messages.
470
+ */
471
+ async start(agentId) {
472
+ this.agentId = agentId;
473
+ this.emitPartial({ loading: true, error: null });
474
+ try {
475
+ const [agent, rawMessages] = await Promise.all([
476
+ this.options.agentInstanceClient.fetchAgent(agentId),
477
+ this.options.conversationClient.getMessages(agentId)
478
+ ]);
479
+ const orderedMessageIds = rawMessages.map((m) => m.messageId);
480
+ const messagesMap = /* @__PURE__ */ new Map();
481
+ for (const m of rawMessages) messagesMap.set(m.messageId, m);
482
+ this.emitPartial({
483
+ agent,
484
+ loading: false,
485
+ messages: rawMessages,
486
+ orderedMessageIds,
487
+ streamingMessageIds: /* @__PURE__ */ new Set()
488
+ });
489
+ this.unsubAgentUpdates = this.options.agentInstanceClient.subscribeToUpdates(
490
+ agentId,
491
+ this.handleAgentUpdate
492
+ );
493
+ this.unsubMessages = this.options.conversationClient.subscribeToMessages(
494
+ agentId,
495
+ this.handleNewMessage
496
+ );
497
+ this.startPolling(agentId);
498
+ } catch (error_) {
499
+ this.emitPartial({
500
+ loading: false,
501
+ error: error_ instanceof Error ? error_ : new Error(String(error_))
502
+ });
503
+ }
504
+ }
505
+ /** Stop the session and clean up subscriptions. */
506
+ stop() {
507
+ this.stopPolling();
508
+ this.unsubAgentUpdates?.();
509
+ this.unsubAgentUpdates = null;
510
+ this.unsubMessages?.();
511
+ this.unsubMessages = null;
512
+ this.agentId = null;
513
+ }
514
+ // ── Actions ───────────────────────────────────────────────────
515
+ /** Send a user message. */
516
+ async sendMessage(content, file, wikiTiddlers) {
517
+ if (!this.agentId) return;
518
+ this.emitPartial({ error: null });
519
+ try {
520
+ await this.options.conversationClient.sendMessage(this.agentId, content, file, wikiTiddlers);
521
+ } catch (error_) {
522
+ this.emitPartial({
523
+ error: error_ instanceof Error ? error_ : new Error(String(error_))
524
+ });
525
+ }
526
+ }
527
+ /** Cancel the current agent operation. */
528
+ async cancel() {
529
+ if (!this.agentId) return;
530
+ try {
531
+ await this.options.agentInstanceClient.cancelAgent(this.agentId);
532
+ } catch (error_) {
533
+ this.emitPartial({
534
+ error: error_ instanceof Error ? error_ : new Error(String(error_))
535
+ });
536
+ }
537
+ }
538
+ /** Delete a turn starting at the given user message ID. */
539
+ async deleteTurn(userMessageId) {
540
+ return this.options.conversationClient.deleteTurn(userMessageId);
541
+ }
542
+ /** Retry a turn starting at the given user message ID. */
543
+ async retryTurn(userMessageId) {
544
+ return this.options.conversationClient.retryTurn(userMessageId);
545
+ }
546
+ // ── Subscription ──────────────────────────────────────────────
547
+ /** Subscribe to snapshot changes. Returns unsubscribe function. */
548
+ subscribe(listener) {
549
+ this.listener = listener;
550
+ listener({ ...this.snapshot, streamingMessageIds: new Set(this.snapshot.streamingMessageIds) });
551
+ return () => {
552
+ if (this.listener === listener) this.listener = null;
553
+ };
554
+ }
555
+ /** Get current snapshot without subscribing. */
556
+ getSnapshot() {
557
+ return this.snapshot;
558
+ }
559
+ // ── Private helpers ───────────────────────────────────────────
560
+ emitPartial(partial) {
561
+ this.snapshot = { ...this.snapshot, ...partial };
562
+ if (this.listener) {
563
+ this.listener({
564
+ ...this.snapshot,
565
+ streamingMessageIds: new Set(this.snapshot.streamingMessageIds)
566
+ });
567
+ }
568
+ }
569
+ handleAgentUpdate = (update) => {
570
+ if (this.snapshot.agent) {
571
+ this.emitPartial({ agent: { ...this.snapshot.agent, ...update } });
572
+ }
573
+ };
574
+ handleNewMessage = (message) => {
575
+ const messages = [...this.snapshot.messages.filter((m) => m.messageId !== message.messageId), message];
576
+ const orderedMessageIds = [.../* @__PURE__ */ new Set([...this.snapshot.orderedMessageIds, message.messageId])];
577
+ this.emitPartial({ messages, orderedMessageIds });
578
+ };
579
+ startPolling(agentId) {
580
+ this.stopPolling();
581
+ this.pollingTimer = setInterval(async () => {
582
+ try {
583
+ const agent = await this.options.agentInstanceClient.fetchAgent(agentId);
584
+ this.emitPartial({ agent });
585
+ } catch {
586
+ }
587
+ }, this.options.pollInterval);
588
+ }
589
+ stopPolling() {
590
+ if (this.pollingTimer !== null) {
591
+ clearInterval(this.pollingTimer);
592
+ this.pollingTimer = null;
593
+ }
594
+ }
595
+ };
596
+
597
+ // src/agent-management/AgentDefinitionEditorController.ts
598
+ var AgentDefinitionEditorController = class {
599
+ options;
600
+ listener = null;
601
+ state = {
602
+ agentDefinition: null,
603
+ agentName: "",
604
+ previewAgentId: null,
605
+ isLoading: false,
606
+ isSaving: false,
607
+ promptSchema: null
608
+ };
609
+ autosaveTimer = null;
610
+ constructor(options) {
611
+ this.options = {
612
+ autosaveDebounceMs: 1e3,
613
+ ...options
614
+ };
615
+ }
616
+ // ── Lifecycle ─────────────────────────────────────────────────
617
+ /** Load an agent definition and its prompt schema. */
618
+ async loadDefinition(definitionId) {
619
+ this.emit({ isLoading: true });
620
+ try {
621
+ const [definition, previewAgentId] = await Promise.all([
622
+ this.options.definitionRepository.getAgentDef(definitionId),
623
+ this.findOrCreatePreviewAgent(definitionId)
624
+ ]);
625
+ if (!definition) {
626
+ this.emit({ isLoading: false });
627
+ return;
628
+ }
629
+ let promptSchema = null;
630
+ if (definition.agentFrameworkID) {
631
+ try {
632
+ promptSchema = await this.options.agentInstanceClient.getFrameworkConfigSchema(
633
+ definition.agentFrameworkID
634
+ );
635
+ } catch {
636
+ promptSchema = null;
637
+ }
638
+ }
639
+ this.state = {
640
+ agentDefinition: definition,
641
+ agentName: definition.name ?? "",
642
+ previewAgentId,
643
+ isLoading: false,
644
+ isSaving: false,
645
+ promptSchema
646
+ };
647
+ this.emit(this.state);
648
+ } catch (_error) {
649
+ this.emit({ isLoading: false });
650
+ }
651
+ }
652
+ /** Update the agent definition field. Triggers debounced autosave. */
653
+ updateDefinition(change) {
654
+ if (!this.state.agentDefinition) return;
655
+ const updated = { ...this.state.agentDefinition, ...change };
656
+ this.state = { ...this.state, agentDefinition: updated, agentName: updated.name ?? "" };
657
+ this.emit({ agentDefinition: updated, agentName: this.state.agentName });
658
+ this.scheduleAutosave(updated);
659
+ }
660
+ /** Force an immediate save to the repository. */
661
+ async saveNow() {
662
+ if (!this.state.agentDefinition) return;
663
+ this.emit({ isSaving: true });
664
+ try {
665
+ await this.options.definitionRepository.updateAgentDef({
666
+ id: this.state.agentDefinition.id,
667
+ name: this.state.agentName,
668
+ description: this.state.agentDefinition.description,
669
+ agentFrameworkConfig: this.state.agentDefinition.agentFrameworkConfig,
670
+ aiApiConfig: this.state.agentDefinition.aiApiConfig,
671
+ agentTools: this.state.agentDefinition.agentTools,
672
+ heartbeat: this.state.agentDefinition.heartbeat
673
+ });
674
+ } catch (_error) {
675
+ } finally {
676
+ this.emit({ isSaving: false });
677
+ }
678
+ }
679
+ /** Reload the prompt schema for a framework ID. */
680
+ async reloadSchema(frameworkId) {
681
+ try {
682
+ const schema = await this.options.agentInstanceClient.getFrameworkConfigSchema(frameworkId);
683
+ this.emit({ promptSchema: schema });
684
+ } catch (_error) {
685
+ this.emit({ promptSchema: null });
686
+ }
687
+ }
688
+ // ── Preview agent lifecycle ───────────────────────────────────
689
+ /** Get or create a preview agent instance tied to this definition. */
690
+ async findOrCreatePreviewAgent(definitionId) {
691
+ try {
692
+ const result = await this.options.agentInstanceClient.createAgent(definitionId, {
693
+ preview: true
694
+ });
695
+ return result.id;
696
+ } catch (_error) {
697
+ return null;
698
+ }
699
+ }
700
+ // ── Scheduled tasks ───────────────────────────────────────────
701
+ async loadScheduledTasks() {
702
+ if (!this.state.previewAgentId) return [];
703
+ return this.options.scheduledTaskClient.listScheduledTasksForAgent(
704
+ this.state.previewAgentId
705
+ );
706
+ }
707
+ async saveScheduledTask(input) {
708
+ return this.options.scheduledTaskClient.createScheduledTask(input);
709
+ }
710
+ async deleteScheduledTask(taskId) {
711
+ return this.options.scheduledTaskClient.deleteScheduledTask(taskId);
712
+ }
713
+ // ── Subscription ──────────────────────────────────────────────
714
+ subscribe(listener) {
715
+ this.listener = listener;
716
+ listener(this.state);
717
+ return () => {
718
+ if (this.listener === listener) this.listener = null;
719
+ };
720
+ }
721
+ getState() {
722
+ return this.state;
723
+ }
724
+ // ── Private ───────────────────────────────────────────────────
725
+ emit(change) {
726
+ this.listener?.(change);
727
+ }
728
+ scheduleAutosave(definition) {
729
+ if (this.autosaveTimer !== null) clearTimeout(this.autosaveTimer);
730
+ this.autosaveTimer = setTimeout(() => {
731
+ this.options.definitionRepository.updateAgentDef({
732
+ id: definition.id,
733
+ name: definition.name,
734
+ description: definition.description,
735
+ agentFrameworkConfig: definition.agentFrameworkConfig,
736
+ aiApiConfig: definition.aiApiConfig,
737
+ agentTools: definition.agentTools,
738
+ heartbeat: definition.heartbeat
739
+ }).catch(() => {
740
+ });
741
+ }, this.options.autosaveDebounceMs);
742
+ }
743
+ };
744
+
745
+ // src/agent-management/AgentCreationController.ts
746
+ var AgentCreationController = class {
747
+ options;
748
+ listener = null;
749
+ state = {
750
+ currentStep: 0,
751
+ agentName: "",
752
+ selectedTemplate: null,
753
+ temporaryAgentDefinition: null,
754
+ previewAgentId: null,
755
+ isLoading: false,
756
+ promptSchema: null
757
+ };
758
+ constructor(options) {
759
+ this.options = {
760
+ defaultFrameworkId: "memeloopAgentToolLoop",
761
+ ...options
762
+ };
763
+ }
764
+ /** Initialize the creation wizard, optionally from a saved tab state. */
765
+ async start(definitionId, templateDefinitionId) {
766
+ this.emit({ isLoading: true });
767
+ try {
768
+ if (definitionId) {
769
+ const temporaryDefinition = await this.options.definitionRepository.getAgentDef(definitionId);
770
+ if (temporaryDefinition) {
771
+ this.state = {
772
+ ...this.state,
773
+ temporaryAgentDefinition: temporaryDefinition,
774
+ agentName: temporaryDefinition.name ?? "",
775
+ isLoading: false
776
+ };
777
+ this.emit(this.state);
778
+ if (templateDefinitionId) {
779
+ const template = await this.options.definitionRepository.getAgentDef(templateDefinitionId);
780
+ if (template) {
781
+ this.state = { ...this.state, selectedTemplate: template };
782
+ this.emit(this.state);
783
+ }
784
+ }
785
+ return;
786
+ }
787
+ }
788
+ this.emit({ isLoading: false });
789
+ } catch {
790
+ this.emit({ isLoading: false });
791
+ }
792
+ }
793
+ /** Select a template for the new agent. */
794
+ selectTemplate(template) {
795
+ this.state = { ...this.state, selectedTemplate: template };
796
+ this.emit(this.state);
797
+ }
798
+ /** Update the agent name. */
799
+ setAgentName(name) {
800
+ this.state = { ...this.state, agentName: name };
801
+ this.emit(this.state);
802
+ }
803
+ /** Advance to the next wizard step. */
804
+ nextStep() {
805
+ this.state = { ...this.state, currentStep: Math.min(this.state.currentStep + 1, 2) };
806
+ this.emit(this.state);
807
+ }
808
+ /** Go back to the previous wizard step. */
809
+ previousStep() {
810
+ this.state = { ...this.state, currentStep: Math.max(this.state.currentStep - 1, 0) };
811
+ this.emit(this.state);
812
+ }
813
+ /** Set the current wizard step directly. */
814
+ setStep(step) {
815
+ this.state = { ...this.state, currentStep: Math.max(0, Math.min(step, 2)) };
816
+ this.emit(this.state);
817
+ }
818
+ /** Update the temporary agent definition. */
819
+ updateTemporaryDefinition(change) {
820
+ const updated = this.state.temporaryAgentDefinition ? { ...this.state.temporaryAgentDefinition, ...change } : null;
821
+ this.state = { ...this.state, temporaryAgentDefinition: updated };
822
+ this.emit(this.state);
823
+ }
824
+ /** Set the prompt schema for the selected framework. */
825
+ setPromptSchema(schema) {
826
+ this.state = { ...this.state, promptSchema: schema };
827
+ this.emit(this.state);
828
+ }
829
+ /**
830
+ * Create a preview agent instance for the chat-preview step.
831
+ * Should be called when entering step 2 (or the chat preview step).
832
+ */
833
+ async createPreviewAgent(definitionId) {
834
+ this.emit({ isLoading: true });
835
+ try {
836
+ const result = await this.options.agentInstanceClient.createAgent(definitionId, {
837
+ preview: true
838
+ });
839
+ this.state = { ...this.state, previewAgentId: result.id, isLoading: false };
840
+ this.emit(this.state);
841
+ } catch {
842
+ this.emit({ isLoading: false });
843
+ }
844
+ }
845
+ /**
846
+ * Finalize creation: persist the temporary definition and create the actual
847
+ * agent instance. Returns the final agent instance ID, or null on failure.
848
+ */
849
+ async finalize() {
850
+ if (!this.state.temporaryAgentDefinition) return null;
851
+ try {
852
+ const definition = await this.options.definitionRepository.createAgentDef(
853
+ this.state.temporaryAgentDefinition
854
+ );
855
+ const instance = await this.options.agentInstanceClient.createAgent(definition.id);
856
+ return instance.id;
857
+ } catch {
858
+ return null;
859
+ }
860
+ }
861
+ /** Clean up temporary resources. */
862
+ async cleanup() {
863
+ if (this.state.previewAgentId) {
864
+ try {
865
+ await this.options.agentInstanceClient.deleteAgent(this.state.previewAgentId);
866
+ } catch {
867
+ }
868
+ }
869
+ }
870
+ // ── Subscription ──────────────────────────────────────────────
871
+ subscribe(listener) {
872
+ this.listener = listener;
873
+ listener({ ...this.state });
874
+ return () => {
875
+ if (this.listener === listener) this.listener = null;
876
+ };
877
+ }
878
+ getState() {
879
+ return this.state;
880
+ }
881
+ // ── Private ───────────────────────────────────────────────────
882
+ emit(change) {
883
+ this.state = { ...this.state, ...change };
884
+ this.listener?.({ ...this.state });
885
+ }
886
+ };
887
+
888
+ // src/agent-management/PromptPreviewController.ts
889
+ var PromptPreviewController = class {
890
+ options;
891
+ listener = null;
892
+ state = {
893
+ open: false,
894
+ baseMode: "preview",
895
+ activeTab: "tree",
896
+ loading: false,
897
+ progress: 0,
898
+ currentStep: "",
899
+ currentPlugin: null,
900
+ result: null,
901
+ lastUpdated: null,
902
+ formFieldsToScrollTo: []
903
+ };
904
+ constructor(options) {
905
+ this.options = options;
906
+ }
907
+ /** Open the dialog with an optional base mode. */
908
+ open(baseMode) {
909
+ this.state = {
910
+ ...this.state,
911
+ open: true,
912
+ baseMode: baseMode ?? "preview",
913
+ loading: false,
914
+ progress: 0,
915
+ currentStep: "Starting...",
916
+ currentPlugin: null,
917
+ result: null,
918
+ lastUpdated: null,
919
+ formFieldsToScrollTo: []
920
+ };
921
+ this.emit();
922
+ }
923
+ /** Close the dialog. */
924
+ close() {
925
+ this.state = {
926
+ ...this.state,
927
+ open: false,
928
+ baseMode: "preview",
929
+ lastUpdated: null,
930
+ formFieldsToScrollTo: []
931
+ };
932
+ this.emit();
933
+ }
934
+ /** Set the active tab in the dialog. */
935
+ setActiveTab(tab) {
936
+ this.state = { ...this.state, activeTab: tab };
937
+ this.emit();
938
+ }
939
+ /** Set form fields to scroll to (for edit mode navigation). */
940
+ setFormFieldsToScrollTo(fieldPaths) {
941
+ this.state = { ...this.state, formFieldsToScrollTo: fieldPaths };
942
+ this.emit();
943
+ }
944
+ /**
945
+ * Generate a preview of the prompt tree.
946
+ * @param agentFrameworkConfig The agent's framework configuration
947
+ * @param messages Current conversation messages
948
+ * @param inputText Optional input text to include in the preview
949
+ */
950
+ async generate(agentFrameworkConfig, messages, inputText) {
951
+ this.state = {
952
+ ...this.state,
953
+ loading: true,
954
+ progress: 0,
955
+ currentStep: "Preparing..."
956
+ };
957
+ this.emit();
958
+ try {
959
+ const result = await this.options.previewClient.generatePreview(
960
+ agentFrameworkConfig,
961
+ messages,
962
+ inputText,
963
+ (progress) => {
964
+ this.state = {
965
+ ...this.state,
966
+ progress: progress.progress,
967
+ currentStep: progress.step,
968
+ currentPlugin: progress.currentPlugin ?? null
969
+ };
970
+ this.emit();
971
+ }
972
+ );
973
+ this.state = {
974
+ ...this.state,
975
+ loading: false,
976
+ progress: 1,
977
+ currentStep: "Complete",
978
+ currentPlugin: null,
979
+ result,
980
+ lastUpdated: /* @__PURE__ */ new Date()
981
+ };
982
+ this.emit();
983
+ return result;
984
+ } catch (_error) {
985
+ this.state = {
986
+ ...this.state,
987
+ loading: false,
988
+ progress: 0,
989
+ currentStep: "Error occurred",
990
+ currentPlugin: null,
991
+ result: null
992
+ };
993
+ this.emit();
994
+ return null;
995
+ }
996
+ }
997
+ // ── Subscription ──────────────────────────────────────────────
998
+ subscribe(listener) {
999
+ this.listener = listener;
1000
+ listener({ ...this.state });
1001
+ return () => {
1002
+ if (this.listener === listener) this.listener = null;
1003
+ };
1004
+ }
1005
+ getState() {
1006
+ return this.state;
1007
+ }
1008
+ // ── Private ───────────────────────────────────────────────────
1009
+ emit() {
1010
+ this.listener?.({ ...this.state });
1011
+ }
1012
+ };
1013
+
1014
+ // src/network/gitProxyUrlPolicy.ts
1015
+ function ipv4Parts(host) {
1016
+ const m = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(host);
1017
+ if (!m) return null;
1018
+ return m.slice(1, 5).map((x) => Number(x));
1019
+ }
1020
+ function isPrivateOrLoopbackIpv4(host) {
1021
+ const p = ipv4Parts(host);
1022
+ if (!p || p.some((n) => n > 255)) return false;
1023
+ const [a, b] = p;
1024
+ if (a === 127 || a === 0) return true;
1025
+ if (a === 10) return true;
1026
+ if (a === 172 && b >= 16 && b <= 31) return true;
1027
+ if (a === 192 && b === 168) return true;
1028
+ if (a === 169 && b === 254) return true;
1029
+ return false;
1030
+ }
1031
+ function gitProxyTargetBlockReason(url) {
1032
+ if (url.username || url.password) {
1033
+ return "credentials_in_url";
1034
+ }
1035
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
1036
+ return "unsupported_protocol";
1037
+ }
1038
+ const host = url.hostname.toLowerCase();
1039
+ if (host === "localhost" || host.endsWith(".localhost") || host === "0.0.0.0" || host === "[::]" || host === "::") {
1040
+ return "loopback_or_wildcard";
1041
+ }
1042
+ if (host.startsWith("127.")) {
1043
+ return "loopback_ipv4";
1044
+ }
1045
+ if (isPrivateOrLoopbackIpv4(host)) {
1046
+ return "private_ipv4";
1047
+ }
1048
+ if (host.includes(":") && !host.startsWith("[")) {
1049
+ const lower = host.toLowerCase();
1050
+ if (lower.startsWith("fe80:") || lower.startsWith("fc") || lower.startsWith("fd")) {
1051
+ return "private_ipv6";
1052
+ }
1053
+ if (lower === "::1") {
1054
+ return "loopback_ipv6";
1055
+ }
1056
+ }
1057
+ if (host.startsWith("[") && host.endsWith("]")) {
1058
+ const inner = host.slice(1, -1).toLowerCase();
1059
+ if (inner === "::1" || inner.startsWith("fe80:") || inner.startsWith("fc") || inner.startsWith("fd")) {
1060
+ return "blocked_ipv6_literal";
1061
+ }
1062
+ }
1063
+ const h = host.replace(/^\[|\]$/g, "").toLowerCase();
1064
+ if (h === "metadata.google.internal" || h.endsWith(".internal")) {
1065
+ return "internal_hostname";
1066
+ }
1067
+ return null;
1068
+ }
1069
+
1070
+ // src/network/uri.ts
1071
+ var PREFIX = "memeloop://node/";
1072
+ function buildMemeloopFileUri(nodeId, filePath) {
1073
+ const normalized = filePath.replace(/^\/+/, "");
1074
+ const segments = normalized.split("/").filter(Boolean).map(encodeURIComponent);
1075
+ return `memeloop://node/${encodeURIComponent(nodeId)}/file/${segments.join("/")}`;
1076
+ }
1077
+ var buildMemeloopUri = buildMemeloopFileUri;
1078
+ function parseMemeloopUri(uri) {
1079
+ const u = uri.trim();
1080
+ if (!u.startsWith(PREFIX)) return null;
1081
+ const rest = u.slice(PREFIX.length);
1082
+ const slash = rest.indexOf("/");
1083
+ if (slash <= 0) return null;
1084
+ let nodeId;
1085
+ try {
1086
+ nodeId = decodeURIComponent(rest.slice(0, slash));
1087
+ } catch {
1088
+ return null;
1089
+ }
1090
+ const afterNode = rest.slice(slash + 1);
1091
+ if (!afterNode.startsWith("file/")) return null;
1092
+ const pathPart = afterNode.slice("file/".length);
1093
+ if (pathPart.length === 0) {
1094
+ return { scheme: "memeloop", kind: "file", nodeId, filePath: "" };
1095
+ }
1096
+ try {
1097
+ const filePath = pathPart.split("/").map(decodeURIComponent).join("/");
1098
+ return { scheme: "memeloop", kind: "file", nodeId, filePath };
1099
+ } catch {
1100
+ return null;
1101
+ }
1102
+ }
1103
+
1104
+ // src/device-network/agentRuntimeRpcHandler.ts
1105
+ function createAgentRuntimeDeviceRpcHandler(options) {
1106
+ return async ({ method, parameters }) => {
1107
+ switch (method) {
1108
+ case "memeloop.agent.getDefinitions":
1109
+ return { definitions: await options.getAgentDefinitions?.() ?? [] };
1110
+ case "memeloop.agent.create": {
1111
+ const rpcParameters = objectParameter(parameters);
1112
+ return options.runtime.createAgent({
1113
+ definitionId: stringParameter(rpcParameters, "definitionId"),
1114
+ initialMessage: optionalStringParameter(rpcParameters, "initialMessage")
1115
+ });
1116
+ }
1117
+ case "memeloop.agent.send": {
1118
+ const rpcParameters = objectParameter(parameters);
1119
+ await options.runtime.sendMessage({
1120
+ conversationId: stringParameter(rpcParameters, "conversationId"),
1121
+ message: stringParameter(rpcParameters, "message"),
1122
+ definitionId: optionalStringParameter(rpcParameters, "definitionId"),
1123
+ userMessage: chatMessageParameter(rpcParameters, "userMessage"),
1124
+ resumeSession: chatMessageArrayParameter(rpcParameters, "resumeSession")
1125
+ });
1126
+ return { ok: true };
1127
+ }
1128
+ case "memeloop.agent.runTurn": {
1129
+ const rpcParameters = objectParameter(parameters);
1130
+ const conversationId = stringParameter(rpcParameters, "conversationId");
1131
+ const definitionId = stringParameter(rpcParameters, "definitionId");
1132
+ await upsertConversationForRemoteTurn({
1133
+ storage: options.storage,
1134
+ conversationId,
1135
+ definitionId,
1136
+ conversation: conversationMetaParameter(rpcParameters, "conversation"),
1137
+ localNodeId: options.localNodeId
1138
+ });
1139
+ await options.runtime.sendMessage({
1140
+ conversationId,
1141
+ definitionId,
1142
+ message: stringParameter(rpcParameters, "message"),
1143
+ userMessage: chatMessageParameter(rpcParameters, "userMessage"),
1144
+ resumeSession: chatMessageArrayParameter(rpcParameters, "resumeSession")
1145
+ });
1146
+ return { ok: true, conversationId };
1147
+ }
1148
+ case "memeloop.agent.cancel": {
1149
+ const rpcParameters = objectParameter(parameters);
1150
+ await options.runtime.cancelAgent(stringParameter(rpcParameters, "conversationId"));
1151
+ return { ok: true };
1152
+ }
1153
+ case "memeloop.chat.pullAgentRunLog": {
1154
+ const rpcParameters = objectParameter(parameters);
1155
+ const conversationId = stringParameter(rpcParameters, "conversationId");
1156
+ const known = new Set(stringArrayParameter(rpcParameters, "knownMessageIds"));
1157
+ const messages = await options.storage.getMessages(conversationId, { mode: "full-content" });
1158
+ return { messages: messages.filter((message) => !known.has(message.messageId)) };
1159
+ }
1160
+ default:
1161
+ throw new Error(`rpc_method_not_found:${method}`);
1162
+ }
1163
+ };
1164
+ }
1165
+ async function upsertConversationForRemoteTurn(input) {
1166
+ if (input.conversation) {
1167
+ await input.storage.upsertConversationMetadata(input.conversation);
1168
+ return;
1169
+ }
1170
+ const existing = await input.storage.getConversationMeta(input.conversationId).catch(() => null);
1171
+ if (existing) return;
1172
+ const now = Date.now();
1173
+ await input.storage.upsertConversationMetadata({
1174
+ conversationId: input.conversationId,
1175
+ title: input.definitionId,
1176
+ lastMessagePreview: "",
1177
+ lastMessageTimestamp: now,
1178
+ messageCount: 0,
1179
+ originNodeId: input.localNodeId ?? "remote",
1180
+ definitionId: input.definitionId,
1181
+ isUserInitiated: true
1182
+ });
1183
+ }
1184
+ function objectParameter(value) {
1185
+ if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error("invalid_rpc_params");
1186
+ return value;
1187
+ }
1188
+ function stringParameter(parameters, key) {
1189
+ const value = parameters[key];
1190
+ if (typeof value !== "string" || value.length === 0) throw new Error("invalid_rpc_params");
1191
+ return value;
1192
+ }
1193
+ function optionalStringParameter(parameters, key) {
1194
+ const value = parameters[key];
1195
+ if (value === void 0) return void 0;
1196
+ if (typeof value !== "string") throw new Error("invalid_rpc_params");
1197
+ return value;
1198
+ }
1199
+ function stringArrayParameter(parameters, key) {
1200
+ const value = parameters[key];
1201
+ if (value === void 0) return [];
1202
+ if (!Array.isArray(value)) throw new Error("invalid_rpc_params");
1203
+ return value.filter((item) => typeof item === "string");
1204
+ }
1205
+ function chatMessageParameter(parameters, key) {
1206
+ const value = parameters[key];
1207
+ if (value === void 0) return void 0;
1208
+ if (!isChatMessage(value)) throw new Error("invalid_rpc_params");
1209
+ return value;
1210
+ }
1211
+ function chatMessageArrayParameter(parameters, key) {
1212
+ const value = parameters[key];
1213
+ if (value === void 0) return void 0;
1214
+ if (!Array.isArray(value)) throw new Error("invalid_rpc_params");
1215
+ return value.filter(isChatMessage);
1216
+ }
1217
+ function conversationMetaParameter(parameters, key) {
1218
+ const value = parameters[key];
1219
+ if (value === void 0) return void 0;
1220
+ if (!isConversationMetaLike(value)) throw new Error("invalid_rpc_params");
1221
+ return value;
1222
+ }
1223
+ function isChatMessage(value) {
1224
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
1225
+ const record = value;
1226
+ return typeof record.messageId === "string" && typeof record.conversationId === "string" && typeof record.originNodeId === "string" && typeof record.timestamp === "number" && typeof record.lamportClock === "number" && typeof record.role === "string" && typeof record.content === "string";
1227
+ }
1228
+ function isConversationMetaLike(value) {
1229
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
1230
+ const record = value;
1231
+ return typeof record.conversationId === "string" && typeof record.title === "string" && typeof record.lastMessageTimestamp === "number" && typeof record.messageCount === "number" && typeof record.originNodeId === "string" && typeof record.definitionId === "string" && typeof record.isUserInitiated === "boolean";
1232
+ }
1233
+
1234
+ // src/device-network/libp2pDeviceNetworkService.ts
1235
+ import { noise } from "@chainsafe/libp2p-noise";
1236
+ import { yamux } from "@chainsafe/libp2p-yamux";
1237
+ import { bootstrap } from "@libp2p/bootstrap";
1238
+ import { circuitRelayTransport } from "@libp2p/circuit-relay-v2";
1239
+ import { identify } from "@libp2p/identify";
1240
+ import { mdns } from "@libp2p/mdns";
1241
+ import { peerIdFromPrivateKey, peerIdFromPublicKey, peerIdFromString } from "@libp2p/peer-id";
1242
+ import { ping } from "@libp2p/ping";
1243
+ import { tcp } from "@libp2p/tcp";
1244
+ import { webSockets } from "@libp2p/websockets";
1245
+ import { multiaddr } from "@multiformats/multiaddr";
1246
+ import { createLibp2p } from "libp2p";
1247
+
1248
+ // src/device-network/libp2pSyncProtocol.ts
1249
+ var LIBP2P_SYNC_REQUEST_TYPE = "memeloop-sync-request-v1";
1250
+ var LIBP2P_SYNC_RESPONSE_TYPE = "memeloop-sync-response-v1";
1251
+ var syncMethods = /* @__PURE__ */ new Set([
1252
+ "exchangeVersionVector",
1253
+ "pullMissingMetadata",
1254
+ "pullMissingMessages",
1255
+ "pullAttachmentBlob"
1256
+ ]);
1257
+ function isLibp2pSyncRequest(value) {
1258
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
1259
+ const record = value;
1260
+ return record.type === LIBP2P_SYNC_REQUEST_TYPE && typeof record.id === "string" && typeof record.method === "string" && syncMethods.has(record.method);
1261
+ }
1262
+ function isLibp2pSyncResponse(value) {
1263
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
1264
+ const record = value;
1265
+ if (record.type !== LIBP2P_SYNC_RESPONSE_TYPE || typeof record.id !== "string" || typeof record.ok !== "boolean") return false;
1266
+ if (record.ok) return "result" in record;
1267
+ return typeof record.error === "string";
1268
+ }
1269
+ function isAttachmentBlobWire(value) {
1270
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
1271
+ const record = value;
1272
+ return typeof record.dataBase64Url === "string" && typeof record.filename === "string" && typeof record.mimeType === "string" && typeof record.size === "number";
1273
+ }
1274
+ async function attachmentBlobFromWire(value) {
1275
+ if (value === null) return null;
1276
+ if (!isAttachmentBlobWire(value)) throw new Error("invalid_sync_response");
1277
+ const { fromString } = await import("uint8arrays");
1278
+ return {
1279
+ data: fromString(value.dataBase64Url, "base64url"),
1280
+ filename: value.filename,
1281
+ mimeType: value.mimeType,
1282
+ size: value.size
1283
+ };
1284
+ }
1285
+
1286
+ // src/device-network/libp2pDeviceSyncTransport.ts
1287
+ var Libp2pDeviceSyncTransport = class {
1288
+ nodeId;
1289
+ deviceNetwork;
1290
+ grantProvider;
1291
+ constructor(options) {
1292
+ this.nodeId = options.nodeId;
1293
+ this.deviceNetwork = options.deviceNetwork;
1294
+ this.grantProvider = options.grantProvider;
1295
+ }
1296
+ listPeers() {
1297
+ return this.deviceNetwork.listDevices();
1298
+ }
1299
+ exchangeVersionVector(peerId, localVersion) {
1300
+ return this.request(peerId, "exchangeVersionVector", { localVersion });
1301
+ }
1302
+ pullMissingMetadata(peerId, sinceVersion) {
1303
+ return this.request(peerId, "pullMissingMetadata", { sinceVersion });
1304
+ }
1305
+ pullMissingMessages(peerId, conversationId, knownMessageIds) {
1306
+ return this.request(peerId, "pullMissingMessages", { conversationId, knownMessageIds });
1307
+ }
1308
+ async pullAttachmentBlob(peerId, contentHash) {
1309
+ const result = await this.request(peerId, "pullAttachmentBlob", { contentHash });
1310
+ return attachmentBlobFromWire(result);
1311
+ }
1312
+ async request(peerId, method, parameters) {
1313
+ const grant = await this.grantProvider?.(peerId);
1314
+ const stream = await this.deviceNetwork.openStream(peerId, "/memeloop/sync/1.0.0", grant);
1315
+ const request = {
1316
+ type: LIBP2P_SYNC_REQUEST_TYPE,
1317
+ id: crypto.randomUUID(),
1318
+ method,
1319
+ params: parameters,
1320
+ grant
1321
+ };
1322
+ await writeJsonToStream(stream, request);
1323
+ const response = await readJsonFromStream(stream);
1324
+ await stream.close();
1325
+ if (!isLibp2pSyncResponse(response)) throw new Error("invalid_sync_response");
1326
+ if (response.id !== request.id) throw new Error("sync_response_id_mismatch");
1327
+ if (!response.ok) throw new Error(response.error);
1328
+ return response.result;
1329
+ }
1330
+ };
1331
+ async function writeJsonToStream(stream, message) {
1332
+ const payload = new TextEncoder().encode(JSON.stringify(message));
1333
+ await stream.sink((async function* () {
1334
+ yield payload;
1335
+ })());
1336
+ }
1337
+ async function readJsonFromStream(stream) {
1338
+ const reader = stream.source[Symbol.asyncIterator]();
1339
+ const result = await reader.next();
1340
+ if (result.done || !result.value) throw new Error("sync_response_missing");
1341
+ return JSON.parse(new TextDecoder().decode(result.value));
1342
+ }
1343
+
1344
+ // src/device-network/libp2pRpcProtocol.ts
1345
+ var LIBP2P_RPC_REQUEST_TYPE = "memeloop-rpc-request-v1";
1346
+ var LIBP2P_RPC_RESPONSE_TYPE = "memeloop-rpc-response-v1";
1347
+ function isLibp2pRpcRequest(value) {
1348
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
1349
+ const record = value;
1350
+ return record.type === LIBP2P_RPC_REQUEST_TYPE && typeof record.id === "string" && typeof record.method === "string";
1351
+ }
1352
+ function isLibp2pRpcResponse(value) {
1353
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
1354
+ const record = value;
1355
+ if (record.type !== LIBP2P_RPC_RESPONSE_TYPE || typeof record.id !== "string" || typeof record.ok !== "boolean") return false;
1356
+ if (record.ok) return "result" in record;
1357
+ return typeof record.error === "string";
1358
+ }
1359
+
1360
+ // src/device-network/localTrustDeviceAuthorizer.ts
1361
+ var PAIRING_PROTOCOL = "/memeloop/pairing/1.0.0";
1362
+ var LocalTrustDeviceAuthorizer = class {
1363
+ constructor(options = {}) {
1364
+ this.options = options;
1365
+ this.allowPairingProtocol = options.allowPairingProtocol ?? true;
1366
+ for (const record of options.trustedDevices ?? []) {
1367
+ this.trustedDevices.set(record.peerId, record);
1368
+ }
1369
+ }
1370
+ trustedDevices = /* @__PURE__ */ new Map();
1371
+ allowPairingProtocol;
1372
+ setTrustedDevice(record) {
1373
+ this.trustedDevices.set(record.peerId, record);
1374
+ }
1375
+ removeTrustedDevice(peerId) {
1376
+ this.trustedDevices.delete(peerId);
1377
+ }
1378
+ async canOpenProtocol(input) {
1379
+ const record = this.options.getTrustedDevice?.(input.remotePeerId) ?? this.trustedDevices.get(input.remotePeerId);
1380
+ if (record?.revokedAt) return false;
1381
+ if (input.protocol === PAIRING_PROTOCOL) return this.allowPairingProtocol;
1382
+ return record !== void 0;
1383
+ }
1384
+ };
1385
+
1386
+ // src/device-network/libp2pDeviceNetworkService.ts
1387
+ var emptyCapabilities = {
1388
+ tools: [],
1389
+ mcpServers: [],
1390
+ hasWiki: false,
1391
+ agentLoop: false,
1392
+ imChannels: [],
1393
+ wikis: []
1394
+ };
1395
+ var defaultListen = {
1396
+ addresses: ["/ip4/0.0.0.0/tcp/0", "/ip4/0.0.0.0/tcp/0/ws"]
1397
+ };
1398
+ var PAIRING_PROTOCOL2 = "/memeloop/pairing/1.0.0";
1399
+ var RPC_PROTOCOL = "/memeloop/rpc/1.0.0";
1400
+ var SYNC_PROTOCOL = "/memeloop/sync/1.0.0";
1401
+ var RELAY_ADMISSION_PROTOCOL = "/memeloop/relay-admission/1.0.0";
1402
+ var PAIRING_SESSION_TTL_MS = 5 * 6e4;
1403
+ var PAIRING_MESSAGE_MAX_BYTES = 64 * 1024;
1404
+ var RPC_MESSAGE_MAX_BYTES = 16 * 1024 * 1024;
1405
+ var SYNC_MESSAGE_MAX_BYTES = 16 * 1024 * 1024;
1406
+ var RELAY_ADMISSION_MESSAGE_MAX_BYTES = 64 * 1024;
1407
+ var RELAY_ADMISSION_REQUEST_TYPE = "memeloop-relay-admission-request-v1";
1408
+ var RELAY_ADMISSION_RESPONSE_TYPE = "memeloop-relay-admission-response-v1";
1409
+ var Libp2pDeviceNetworkService = class {
1410
+ constructor(options) {
1411
+ this.options = options;
1412
+ this.capabilities = options.capabilities ?? emptyCapabilities;
1413
+ for (const record of options.trustedDevices ?? []) {
1414
+ this.trustedDevices.set(record.peerId, record);
1415
+ }
1416
+ for (const address of options.bootstrapMultiaddrs ?? []) {
1417
+ const trimmed = address.trim();
1418
+ if (trimmed) this.bootstrapMultiaddrs.add(trimmed);
1419
+ }
1420
+ this.authorizer = options.authorizer ?? new LocalTrustDeviceAuthorizer({
1421
+ getTrustedDevice: (peerId) => this.trustedDevices.get(peerId)
1422
+ });
1423
+ }
1424
+ libp2p;
1425
+ capabilities;
1426
+ discoveredDevices = /* @__PURE__ */ new Map();
1427
+ trustedDevices = /* @__PURE__ */ new Map();
1428
+ bootstrapMultiaddrs = /* @__PURE__ */ new Set();
1429
+ authorizer;
1430
+ pairingSessions = /* @__PURE__ */ new Map();
1431
+ listeners = /* @__PURE__ */ new Set();
1432
+ pairingListeners = /* @__PURE__ */ new Set();
1433
+ async start() {
1434
+ if (this.libp2p) return;
1435
+ await this.loadTrustedDevicesFromStore();
1436
+ this.libp2p = await this.createNode();
1437
+ this.registerDiscoveryListeners(this.libp2p);
1438
+ await this.registerProtocolHandlers(this.libp2p);
1439
+ await this.libp2p.start();
1440
+ this.emitDevices();
1441
+ }
1442
+ async stop() {
1443
+ if (!this.libp2p) return;
1444
+ await this.libp2p.stop();
1445
+ this.libp2p = void 0;
1446
+ this.discoveredDevices.clear();
1447
+ this.emitDevices();
1448
+ }
1449
+ async getLocalDevice() {
1450
+ const node = this.requireNode();
1451
+ return {
1452
+ peerId: node.peerId.toString(),
1453
+ displayName: this.options.identity.deviceName,
1454
+ platform: this.options.identity.platform,
1455
+ trustMode: "local-pairing",
1456
+ reachability: {
1457
+ state: node.status === "started" ? "online" : "offline",
1458
+ paths: node.status === "started" ? ["lan"] : []
1459
+ },
1460
+ capabilities: this.capabilities,
1461
+ multiaddrs: this.getMultiaddrs(),
1462
+ lastSeen: Date.now()
1463
+ };
1464
+ }
1465
+ async listDevices() {
1466
+ return this.toVisibleDevices();
1467
+ }
1468
+ observeDevices(listener) {
1469
+ this.listeners.add(listener);
1470
+ void this.listDevices().then(listener);
1471
+ return () => {
1472
+ this.listeners.delete(listener);
1473
+ };
1474
+ }
1475
+ async listPairingSessions() {
1476
+ this.refreshPairingSessionExpiry();
1477
+ return [...this.pairingSessions.values()];
1478
+ }
1479
+ observePairingSessions(listener) {
1480
+ this.pairingListeners.add(listener);
1481
+ void this.listPairingSessions().then(listener);
1482
+ return () => {
1483
+ this.pairingListeners.delete(listener);
1484
+ };
1485
+ }
1486
+ async requestLocalPairing(peerId, options = {}) {
1487
+ const requestNonce = randomPairingNonce();
1488
+ const createdAt = Date.now();
1489
+ const expiresAt = createdAt + PAIRING_SESSION_TTL_MS;
1490
+ const request = {
1491
+ type: "memeloop-local-pairing-request-v1",
1492
+ sessionId: `pairing-${this.options.identity.peerId}-${peerId}-${createdAt}-${requestNonce}`,
1493
+ requestNonce,
1494
+ createdAt,
1495
+ expiresAt,
1496
+ device: this.localPairingDevice()
1497
+ };
1498
+ const stream = await this.requireNode().dialProtocol(this.pairingDialTarget(peerId, options), PAIRING_PROTOCOL2);
1499
+ try {
1500
+ await writeJsonMessage(stream, request);
1501
+ const response = await readJsonMessage(stream);
1502
+ await stream.close();
1503
+ const session = await this.sessionFromPairingResponse(peerId, request, response);
1504
+ this.pairingSessions.set(session.sessionId, session);
1505
+ this.upsertDiscoveredDeviceFromPairing(session.remotePeerId, response.device);
1506
+ this.emitPairingSessions();
1507
+ this.emitDevices();
1508
+ return session;
1509
+ } catch (error2) {
1510
+ stream.abort(error2 instanceof Error ? error2 : new Error("pairing_request_failed"));
1511
+ throw error2;
1512
+ }
1513
+ }
1514
+ async acceptPairing(sessionId) {
1515
+ const session = this.requirePairingSession(sessionId);
1516
+ if (session.expiresAt < Date.now()) {
1517
+ session.status = "expired";
1518
+ this.emitPairingSessions();
1519
+ throw new Error("pairing_session_expired");
1520
+ }
1521
+ if (session.status !== "pending") throw new Error("pairing_session_not_pending");
1522
+ const trustedDevice = {
1523
+ peerId: session.remotePeerId,
1524
+ publicKeyMultibase: session.remotePublicKeyMultibase,
1525
+ deviceName: session.remoteDeviceName,
1526
+ platform: session.remotePlatform,
1527
+ trustMode: "local-pairing",
1528
+ createdAt: Date.now(),
1529
+ lastSeen: Date.now()
1530
+ };
1531
+ this.trustedDevices.set(session.remotePeerId, trustedDevice);
1532
+ await this.options.trustStore?.saveTrustedDevice(trustedDevice);
1533
+ session.status = "accepted";
1534
+ this.updateDeviceTrust(session.remotePeerId, "local-pairing", true);
1535
+ this.emitPairingSessions();
1536
+ this.emitDevices();
1537
+ }
1538
+ async rejectPairing(sessionId) {
1539
+ const session = this.pairingSessions.get(sessionId);
1540
+ if (!session) return;
1541
+ session.status = "rejected";
1542
+ this.emitPairingSessions();
1543
+ }
1544
+ async removeTrustedDevice(peerId) {
1545
+ this.trustedDevices.delete(peerId);
1546
+ await this.options.trustStore?.removeTrustedDevice(peerId);
1547
+ this.updateDeviceTrust(peerId, "local-pairing", false);
1548
+ this.emitDevices();
1549
+ }
1550
+ async openStream(peerId, protocol, presentedGrant) {
1551
+ const authorized = await this.authorizer.canOpenProtocol({
1552
+ remotePeerId: peerId,
1553
+ protocol,
1554
+ direction: "outbound",
1555
+ presentedGrant
1556
+ });
1557
+ if (!authorized) throw new Error("device_not_trusted");
1558
+ const addresses = this.discoveredDevices.get(peerId)?.multiaddrs ?? [];
1559
+ const dialTarget = addresses.length > 0 ? addresses.map((address) => multiaddr(address)) : peerIdFromString(peerId);
1560
+ const stream = await this.requireNode().dialProtocol(dialTarget, protocol, {
1561
+ runOnLimitedConnection: true
1562
+ });
1563
+ return this.wrapStream(stream);
1564
+ }
1565
+ async sendRpc(peerId, method, parameters, presentedGrant) {
1566
+ const stream = await this.openStream(peerId, RPC_PROTOCOL, presentedGrant);
1567
+ const request = {
1568
+ type: LIBP2P_RPC_REQUEST_TYPE,
1569
+ id: crypto.randomUUID(),
1570
+ method,
1571
+ params: parameters,
1572
+ grant: presentedGrant
1573
+ };
1574
+ await writeStreamJson(stream, request);
1575
+ const response = await readStreamJson(stream);
1576
+ await stream.close();
1577
+ if (!isLibp2pRpcResponse(response)) throw new Error("invalid_rpc_response");
1578
+ if (response.id !== request.id) throw new Error("rpc_response_id_mismatch");
1579
+ if (!response.ok) throw new Error(response.error);
1580
+ return response.result;
1581
+ }
1582
+ async syncWithDevice(peerId, presentedGrant) {
1583
+ const authorized = await this.authorizer.canOpenProtocol({
1584
+ remotePeerId: peerId,
1585
+ protocol: "/memeloop/sync/1.0.0",
1586
+ direction: "outbound",
1587
+ presentedGrant
1588
+ });
1589
+ if (!authorized) throw new Error("device_not_trusted");
1590
+ if (this.options.syncStorage) {
1591
+ const transport = new Libp2pDeviceSyncTransport({
1592
+ nodeId: this.options.identity.peerId,
1593
+ deviceNetwork: this,
1594
+ grantProvider: async (remotePeerId) => remotePeerId === peerId ? presentedGrant : void 0
1595
+ });
1596
+ const peer = new PeerNodeSyncAdapter(peerId, transport);
1597
+ const engine = new ChatSyncEngine({
1598
+ nodeId: this.options.identity.peerId,
1599
+ storage: this.options.syncStorage,
1600
+ peers: () => [peer]
1601
+ });
1602
+ await engine.syncOnce();
1603
+ }
1604
+ return { ok: true, peerId, syncedAt: Date.now() };
1605
+ }
1606
+ getTrustedDevice(peerId) {
1607
+ return this.trustedDevices.get(peerId);
1608
+ }
1609
+ upsertTrustedDevice(record) {
1610
+ this.trustedDevices.set(record.peerId, record);
1611
+ this.updateDeviceTrust(record.peerId, record.trustMode, true);
1612
+ this.emitDevices();
1613
+ }
1614
+ upsertDiscoveredDevice(device) {
1615
+ const trustedRecord = this.trustedDevices.get(device.peerId);
1616
+ this.discoveredDevices.set(device.peerId, {
1617
+ ...device,
1618
+ displayName: trustedRecord?.deviceName ?? device.displayName,
1619
+ platform: trustedRecord?.platform ?? device.platform,
1620
+ trustMode: trustedRecord?.trustMode ?? device.trustMode,
1621
+ trusted: device.trusted ?? trustedRecord !== void 0,
1622
+ lastSeen: device.lastSeen ?? trustedRecord?.lastSeen
1623
+ });
1624
+ this.emitDevices();
1625
+ }
1626
+ async configureRelayReservation(token) {
1627
+ for (const address of [...token.bootstrapMultiaddrs, ...token.relayMultiaddrs]) {
1628
+ const trimmed = address.trim();
1629
+ if (trimmed) this.bootstrapMultiaddrs.add(trimmed);
1630
+ }
1631
+ if (!this.libp2p) return;
1632
+ await this.admitRelayReservation(token);
1633
+ await this.reserveRelayListeners(token.relayMultiaddrs);
1634
+ await this.dialBootstrapPeers([...this.bootstrapMultiaddrs]);
1635
+ }
1636
+ getMultiaddrs() {
1637
+ return this.libp2p?.getMultiaddrs().map((address) => address.toString()) ?? [];
1638
+ }
1639
+ async createNode() {
1640
+ const privateKey = await privateKeyFromIdentity(this.options.identity);
1641
+ const listen = this.options.listen ?? defaultListen;
1642
+ const peerDiscovery = [
1643
+ ...this.createBootstrapDiscovery(),
1644
+ ...this.options.enableMdns === false ? [] : [mdns({ serviceTag: "memeloop" })]
1645
+ ];
1646
+ const transports = this.options.enableCircuitRelay === false ? [tcp(), webSockets()] : [tcp(), webSockets(), circuitRelayTransport()];
1647
+ return createLibp2p({
1648
+ privateKey,
1649
+ addresses: {
1650
+ listen: this.options.enableCircuitRelay === false ? listen.addresses : addRelayListenAddress(listen.addresses),
1651
+ announce: listen.announce
1652
+ },
1653
+ transports,
1654
+ connectionEncrypters: [noise()],
1655
+ streamMuxers: [yamux()],
1656
+ peerDiscovery,
1657
+ services: {
1658
+ identify: identify(),
1659
+ ping: ping()
1660
+ },
1661
+ start: false
1662
+ });
1663
+ }
1664
+ createBootstrapDiscovery() {
1665
+ const list = [...this.bootstrapMultiaddrs];
1666
+ if (list.length === 0) return [];
1667
+ return [bootstrap({ list, tagName: "memeloop-bootstrap", tagTTL: Infinity })];
1668
+ }
1669
+ async dialBootstrapPeers(addresses) {
1670
+ const node = this.requireNode();
1671
+ for (const address of addresses) {
1672
+ await node.dial(multiaddr(address)).catch(() => void 0);
1673
+ }
1674
+ }
1675
+ async admitRelayReservation(token) {
1676
+ const relayAddresses = token.relayMultiaddrs.map((address) => address.trim()).filter((address) => address.length > 0);
1677
+ if (relayAddresses.length === 0) return;
1678
+ const node = this.requireNode();
1679
+ const errors = [];
1680
+ for (const address of relayAddresses) {
1681
+ let stream;
1682
+ try {
1683
+ stream = await node.dialProtocol(multiaddr(address), RELAY_ADMISSION_PROTOCOL);
1684
+ const request = {
1685
+ type: RELAY_ADMISSION_REQUEST_TYPE,
1686
+ token
1687
+ };
1688
+ await writeJsonMessage(stream, request, RELAY_ADMISSION_MESSAGE_MAX_BYTES);
1689
+ const response = await readJsonMessage(stream, RELAY_ADMISSION_MESSAGE_MAX_BYTES);
1690
+ await stream.close();
1691
+ if (isRelayAdmissionResponse(response) && response.ok) return;
1692
+ const reason = isRelayAdmissionResponse(response) ? response.reason : "invalid_relay_admission_response";
1693
+ errors.push(`${address}: ${reason}`);
1694
+ } catch (error2) {
1695
+ stream?.abort(error2 instanceof Error ? error2 : new Error("relay_admission_failed"));
1696
+ errors.push(`${address}: ${error2 instanceof Error ? error2.message : "relay_admission_failed"}`);
1697
+ }
1698
+ }
1699
+ throw new Error(`relay_admission_failed${errors.length > 0 ? `: ${errors.join("; ")}` : ""}`);
1700
+ }
1701
+ async reserveRelayListeners(relayMultiaddrs) {
1702
+ const relayAddresses = relayMultiaddrs.map((address) => address.trim()).filter((address) => address.length > 0);
1703
+ if (relayAddresses.length === 0) return;
1704
+ const transportManager = this.requireNode().components?.transportManager;
1705
+ if (!transportManager) throw new Error("relay_transport_manager_unavailable");
1706
+ const errors = [];
1707
+ for (const address of relayAddresses) {
1708
+ try {
1709
+ await transportManager.listen([relayCircuitMultiaddr(address)]);
1710
+ return;
1711
+ } catch (error2) {
1712
+ errors.push(`${address}: ${error2 instanceof Error ? error2.message : "relay_reservation_failed"}`);
1713
+ }
1714
+ }
1715
+ throw new Error(`relay_reservation_failed${errors.length > 0 ? `: ${errors.join("; ")}` : ""}`);
1716
+ }
1717
+ async loadTrustedDevicesFromStore() {
1718
+ const records = await this.options.trustStore?.loadTrustedDevices();
1719
+ for (const record of records ?? []) {
1720
+ this.trustedDevices.set(record.peerId, record);
1721
+ }
1722
+ }
1723
+ registerDiscoveryListeners(node) {
1724
+ node.addEventListener("peer:discovery", (event) => {
1725
+ const detail = event.detail;
1726
+ const peerId = detail.id.toString();
1727
+ const trustedRecord = this.trustedDevices.get(peerId);
1728
+ this.discoveredDevices.set(peerId, {
1729
+ peerId,
1730
+ displayName: trustedRecord?.deviceName ?? peerId,
1731
+ platform: trustedRecord?.platform ?? "cli",
1732
+ trustMode: trustedRecord?.trustMode ?? "local-pairing",
1733
+ trusted: trustedRecord !== void 0,
1734
+ reachability: {
1735
+ state: "nearby",
1736
+ paths: ["lan"]
1737
+ },
1738
+ capabilities: emptyCapabilities,
1739
+ multiaddrs: detail.multiaddrs.map((address) => address.toString()),
1740
+ lastSeen: Date.now()
1741
+ });
1742
+ this.emitDevices();
1743
+ if (this.options.autoDialDiscoveredPeers === true && detail.multiaddrs.length > 0) {
1744
+ void node.dial(detail.multiaddrs).catch(() => void 0);
1745
+ }
1746
+ });
1747
+ node.addEventListener("peer:connect", (event) => {
1748
+ this.markPeerOnline(event.detail);
1749
+ });
1750
+ node.addEventListener("peer:disconnect", (event) => {
1751
+ this.markPeerOffline(event.detail);
1752
+ });
1753
+ }
1754
+ async registerProtocolHandlers(node) {
1755
+ const protocols = [
1756
+ "/memeloop/pairing/1.0.0",
1757
+ "/memeloop/rpc/1.0.0",
1758
+ "/memeloop/sync/1.0.0",
1759
+ "/memeloop/agent/1.0.0"
1760
+ ];
1761
+ await node.handle(protocols, async (stream, connection) => {
1762
+ const remotePeerId = connection.remotePeer.toString();
1763
+ if (stream.protocol === PAIRING_PROTOCOL2) {
1764
+ if (!await this.authorizer.canOpenProtocol({ remotePeerId, protocol: PAIRING_PROTOCOL2, direction: "inbound" })) {
1765
+ stream.abort(new Error("device_not_trusted"));
1766
+ return;
1767
+ }
1768
+ await this.handlePairingStream(stream, remotePeerId).catch((error2) => {
1769
+ stream.abort(error2 instanceof Error ? error2 : new Error("pairing_handler_failed"));
1770
+ });
1771
+ return;
1772
+ }
1773
+ if (stream.protocol === SYNC_PROTOCOL) {
1774
+ await this.handleSyncStream(stream, remotePeerId);
1775
+ return;
1776
+ }
1777
+ if (stream.protocol === RPC_PROTOCOL) {
1778
+ await this.handleRpcStream(stream, remotePeerId);
1779
+ return;
1780
+ }
1781
+ if (!await this.authorizer.canOpenProtocol({ remotePeerId, protocol: stream.protocol, direction: "inbound" })) {
1782
+ stream.abort(new Error("device_not_trusted"));
1783
+ return;
1784
+ }
1785
+ stream.abort(new Error("protocol_handler_not_registered"));
1786
+ }, {
1787
+ runOnLimitedConnection: true
1788
+ });
1789
+ }
1790
+ async handleRpcStream(stream, remotePeerId) {
1791
+ let requestId = "unknown";
1792
+ try {
1793
+ const request = await readJsonMessage(stream, RPC_MESSAGE_MAX_BYTES);
1794
+ if (!isLibp2pRpcRequest(request)) throw new Error("invalid_rpc_request");
1795
+ requestId = request.id;
1796
+ const authorized = await this.authorizer.canOpenProtocol({
1797
+ remotePeerId,
1798
+ protocol: RPC_PROTOCOL,
1799
+ direction: "inbound",
1800
+ presentedGrant: request.grant
1801
+ });
1802
+ if (!authorized) throw new Error("device_not_trusted");
1803
+ if (!this.options.rpcHandler) throw new Error("rpc_handler_not_configured");
1804
+ const result = await this.options.rpcHandler({
1805
+ remotePeerId,
1806
+ method: request.method,
1807
+ parameters: request.params,
1808
+ presentedGrant: request.grant
1809
+ });
1810
+ await writeJsonMessage(stream, {
1811
+ type: LIBP2P_RPC_RESPONSE_TYPE,
1812
+ id: request.id,
1813
+ ok: true,
1814
+ result
1815
+ }, RPC_MESSAGE_MAX_BYTES);
1816
+ } catch (error2) {
1817
+ await writeJsonMessage(stream, {
1818
+ type: LIBP2P_RPC_RESPONSE_TYPE,
1819
+ id: requestId,
1820
+ ok: false,
1821
+ error: error2 instanceof Error ? error2.message : "rpc_handler_failed"
1822
+ }, RPC_MESSAGE_MAX_BYTES).catch(() => void 0);
1823
+ } finally {
1824
+ await stream.close().catch(() => void 0);
1825
+ }
1826
+ }
1827
+ async handleSyncStream(stream, remotePeerId) {
1828
+ let requestId = "unknown";
1829
+ try {
1830
+ const request = await readJsonMessage(stream, SYNC_MESSAGE_MAX_BYTES);
1831
+ if (!isLibp2pSyncRequest(request)) throw new Error("invalid_sync_request");
1832
+ requestId = request.id;
1833
+ const authorized = await this.authorizer.canOpenProtocol({
1834
+ remotePeerId,
1835
+ protocol: SYNC_PROTOCOL,
1836
+ direction: "inbound",
1837
+ presentedGrant: request.grant
1838
+ });
1839
+ if (!authorized) throw new Error("device_not_trusted");
1840
+ const result = await this.handleSyncRequest(request);
1841
+ await writeJsonMessage(stream, {
1842
+ type: LIBP2P_SYNC_RESPONSE_TYPE,
1843
+ id: request.id,
1844
+ ok: true,
1845
+ result
1846
+ }, SYNC_MESSAGE_MAX_BYTES);
1847
+ } catch (error2) {
1848
+ await writeJsonMessage(stream, {
1849
+ type: LIBP2P_SYNC_RESPONSE_TYPE,
1850
+ id: requestId,
1851
+ ok: false,
1852
+ error: error2 instanceof Error ? error2.message : "sync_handler_failed"
1853
+ }, SYNC_MESSAGE_MAX_BYTES).catch(() => void 0);
1854
+ } finally {
1855
+ await stream.close().catch(() => void 0);
1856
+ }
1857
+ }
1858
+ async handleSyncRequest(request) {
1859
+ const storage = this.options.syncStorage;
1860
+ if (!storage) throw new Error("sync_storage_not_configured");
1861
+ switch (request.method) {
1862
+ case "exchangeVersionVector":
1863
+ return {
1864
+ remoteVersion: this.options.syncVersionVector?.() ?? {},
1865
+ missingForRemote: []
1866
+ };
1867
+ case "pullMissingMetadata": {
1868
+ const sinceVersion = versionVectorParameter(request.params, "sinceVersion");
1869
+ const conversations = await storage.listConversations({ limit: 500 });
1870
+ return conversations.filter((conversation) => shouldSendConversation(conversation, sinceVersion));
1871
+ }
1872
+ case "pullMissingMessages": {
1873
+ const parameters = objectParameter2(request.params);
1874
+ const conversationId = stringParameter2(parameters, "conversationId");
1875
+ const knownMessageIds = stringArrayParameter2(parameters, "knownMessageIds");
1876
+ const messages = await storage.getMessages(conversationId, { mode: "full-content" });
1877
+ const known = new Set(knownMessageIds);
1878
+ return messages.filter((message) => !known.has(message.messageId));
1879
+ }
1880
+ case "pullAttachmentBlob": {
1881
+ const parameters = objectParameter2(request.params);
1882
+ const contentHash = stringParameter2(parameters, "contentHash");
1883
+ return this.readAttachmentBlob(contentHash);
1884
+ }
1885
+ }
1886
+ }
1887
+ async readAttachmentBlob(contentHash) {
1888
+ const storage = this.options.syncStorage;
1889
+ if (!storage?.readAttachmentData) return null;
1890
+ const reference = await storage.getAttachment(contentHash);
1891
+ if (!reference) return null;
1892
+ const data = await storage.readAttachmentData(contentHash);
1893
+ if (!data) return null;
1894
+ const { toString } = await loadUint8arrays();
1895
+ return {
1896
+ dataBase64Url: toString(data, "base64url"),
1897
+ filename: reference.filename,
1898
+ mimeType: reference.mimeType,
1899
+ size: reference.size
1900
+ };
1901
+ }
1902
+ pairingDialTarget(peerId, options) {
1903
+ const addresses = options.multiaddrs ?? this.discoveredDevices.get(peerId)?.multiaddrs ?? [];
1904
+ if (addresses.length === 0) return peerIdFromString(peerId);
1905
+ return addresses.map((address) => multiaddr(address));
1906
+ }
1907
+ localPairingDevice() {
1908
+ return {
1909
+ peerId: this.options.identity.peerId,
1910
+ publicKeyMultibase: this.options.identity.publicKeyMultibase,
1911
+ deviceName: this.options.identity.deviceName,
1912
+ platform: this.options.identity.platform,
1913
+ capabilities: this.capabilities,
1914
+ multiaddrs: this.getMultiaddrs()
1915
+ };
1916
+ }
1917
+ async handlePairingStream(stream, remotePeerId) {
1918
+ const request = await readJsonMessage(stream);
1919
+ if (!isPairingRequest(request)) throw new Error("invalid_pairing_request");
1920
+ if (request.device.peerId !== remotePeerId) throw new Error("pairing_peer_id_mismatch");
1921
+ if (request.expiresAt <= Date.now()) throw new Error("pairing_request_expired");
1922
+ await assertPairingDeviceIdentity(request.device);
1923
+ const responseNonce = randomPairingNonce();
1924
+ const localDevice = this.localPairingDevice();
1925
+ const expiresAt = Math.min(request.expiresAt, Date.now() + PAIRING_SESSION_TTL_MS);
1926
+ const session = {
1927
+ sessionId: request.sessionId,
1928
+ localPeerId: this.options.identity.peerId,
1929
+ remotePeerId: request.device.peerId,
1930
+ remotePublicKeyMultibase: request.device.publicKeyMultibase,
1931
+ remoteDeviceName: request.device.deviceName,
1932
+ remotePlatform: request.device.platform,
1933
+ remoteCapabilities: request.device.capabilities,
1934
+ remoteMultiaddrs: request.device.multiaddrs,
1935
+ direction: "inbound",
1936
+ status: "pending",
1937
+ confirmCode: await buildPairingConfirmCode({
1938
+ initiator: request.device,
1939
+ responder: localDevice,
1940
+ requestNonce: request.requestNonce,
1941
+ responseNonce
1942
+ }),
1943
+ createdAt: Date.now(),
1944
+ expiresAt
1945
+ };
1946
+ this.pairingSessions.set(session.sessionId, session);
1947
+ this.upsertDiscoveredDeviceFromPairing(session.remotePeerId, request.device);
1948
+ await writeJsonMessage(stream, {
1949
+ type: "memeloop-local-pairing-response-v1",
1950
+ sessionId: request.sessionId,
1951
+ requestNonce: request.requestNonce,
1952
+ responseNonce,
1953
+ accepted: true,
1954
+ expiresAt,
1955
+ device: localDevice
1956
+ });
1957
+ await stream.close();
1958
+ this.emitPairingSessions();
1959
+ this.emitDevices();
1960
+ }
1961
+ async sessionFromPairingResponse(peerId, request, response) {
1962
+ if (!isPairingResponse(response)) throw new Error("invalid_pairing_response");
1963
+ if (response.sessionId !== request.sessionId) throw new Error("pairing_session_mismatch");
1964
+ if (response.requestNonce !== request.requestNonce) throw new Error("pairing_nonce_mismatch");
1965
+ if (response.device.peerId !== peerId) throw new Error("pairing_peer_id_mismatch");
1966
+ if (response.expiresAt <= Date.now()) throw new Error("pairing_response_expired");
1967
+ await assertPairingDeviceIdentity(response.device);
1968
+ return {
1969
+ sessionId: request.sessionId,
1970
+ localPeerId: this.options.identity.peerId,
1971
+ remotePeerId: response.device.peerId,
1972
+ remotePublicKeyMultibase: response.device.publicKeyMultibase,
1973
+ remoteDeviceName: response.device.deviceName,
1974
+ remotePlatform: response.device.platform,
1975
+ remoteCapabilities: response.device.capabilities,
1976
+ remoteMultiaddrs: response.device.multiaddrs,
1977
+ direction: "outbound",
1978
+ status: "pending",
1979
+ confirmCode: await buildPairingConfirmCode({
1980
+ initiator: request.device,
1981
+ responder: response.device,
1982
+ requestNonce: request.requestNonce,
1983
+ responseNonce: response.responseNonce
1984
+ }),
1985
+ createdAt: request.createdAt,
1986
+ expiresAt: Math.min(request.expiresAt, response.expiresAt)
1987
+ };
1988
+ }
1989
+ requirePairingSession(sessionId) {
1990
+ const session = this.pairingSessions.get(sessionId);
1991
+ if (!session) throw new Error("pairing_session_not_found");
1992
+ return session;
1993
+ }
1994
+ refreshPairingSessionExpiry() {
1995
+ const now = Date.now();
1996
+ let changed = false;
1997
+ for (const session of this.pairingSessions.values()) {
1998
+ if (session.status === "pending" && session.expiresAt <= now) {
1999
+ session.status = "expired";
2000
+ changed = true;
2001
+ }
2002
+ }
2003
+ if (changed) this.emitPairingSessions();
2004
+ }
2005
+ upsertDiscoveredDeviceFromPairing(peerId, device) {
2006
+ const current = this.discoveredDevices.get(peerId);
2007
+ this.discoveredDevices.set(peerId, {
2008
+ peerId,
2009
+ displayName: device.deviceName,
2010
+ platform: device.platform,
2011
+ trustMode: current?.trustMode ?? "local-pairing",
2012
+ trusted: current?.trusted ?? this.trustedDevices.has(peerId),
2013
+ reachability: current?.reachability ?? {
2014
+ state: "nearby",
2015
+ paths: device.multiaddrs.length > 0 ? ["lan"] : []
2016
+ },
2017
+ capabilities: device.capabilities,
2018
+ multiaddrs: device.multiaddrs,
2019
+ lastSeen: Date.now()
2020
+ });
2021
+ }
2022
+ wrapStream(stream) {
2023
+ return {
2024
+ source: this.streamSource(stream),
2025
+ async sink(source) {
2026
+ for await (const chunk of source) {
2027
+ stream.send(chunk);
2028
+ }
2029
+ await stream.close();
2030
+ },
2031
+ close: async () => {
2032
+ await stream.close();
2033
+ }
2034
+ };
2035
+ }
2036
+ async *streamSource(stream) {
2037
+ for await (const chunk of stream) {
2038
+ if (chunk instanceof Uint8Array) {
2039
+ yield chunk;
2040
+ } else {
2041
+ yield chunk.subarray();
2042
+ }
2043
+ }
2044
+ }
2045
+ markPeerOnline(peerId) {
2046
+ const id = peerId.toString();
2047
+ const current = this.discoveredDevices.get(id);
2048
+ if (current) {
2049
+ current.reachability = { state: "online", paths: current.reachability.paths };
2050
+ current.lastSeen = Date.now();
2051
+ this.emitDevices();
2052
+ }
2053
+ }
2054
+ markPeerOffline(peerId) {
2055
+ const id = peerId.toString();
2056
+ const current = this.discoveredDevices.get(id);
2057
+ if (current) {
2058
+ current.reachability = { state: "offline", paths: [] };
2059
+ current.lastSeen = Date.now();
2060
+ this.emitDevices();
2061
+ }
2062
+ }
2063
+ updateDeviceTrust(peerId, trustMode, trusted) {
2064
+ const current = this.discoveredDevices.get(peerId);
2065
+ if (current) {
2066
+ this.discoveredDevices.set(peerId, {
2067
+ ...current,
2068
+ trustMode,
2069
+ trusted
2070
+ });
2071
+ }
2072
+ }
2073
+ toVisibleDevices() {
2074
+ const visible = new Map(this.discoveredDevices);
2075
+ for (const record of this.trustedDevices.values()) {
2076
+ const current = visible.get(record.peerId);
2077
+ if (current) {
2078
+ visible.set(record.peerId, {
2079
+ ...current,
2080
+ displayName: record.deviceName,
2081
+ platform: record.platform,
2082
+ trustMode: record.trustMode,
2083
+ trusted: !record.revokedAt,
2084
+ lastSeen: current.lastSeen ?? record.lastSeen
2085
+ });
2086
+ continue;
2087
+ }
2088
+ if (record.revokedAt) continue;
2089
+ visible.set(record.peerId, {
2090
+ peerId: record.peerId,
2091
+ displayName: record.deviceName,
2092
+ platform: record.platform,
2093
+ trustMode: record.trustMode,
2094
+ trusted: true,
2095
+ reachability: {
2096
+ state: "offline",
2097
+ paths: []
2098
+ },
2099
+ capabilities: emptyCapabilities,
2100
+ lastSeen: record.lastSeen
2101
+ });
2102
+ }
2103
+ return [...visible.values()];
2104
+ }
2105
+ emitDevices() {
2106
+ const devices = this.toVisibleDevices();
2107
+ for (const listener of this.listeners) listener(devices);
2108
+ }
2109
+ emitPairingSessions() {
2110
+ const sessions = [...this.pairingSessions.values()];
2111
+ for (const listener of this.pairingListeners) listener(sessions);
2112
+ }
2113
+ requireNode() {
2114
+ if (!this.libp2p) throw new Error("device_network_not_started");
2115
+ return this.libp2p;
2116
+ }
2117
+ };
2118
+ var PUBLIC_KEY_MULTIBASE_PREFIX = "libp2p-pub:";
2119
+ async function loadCryptoKeys() {
2120
+ return import("@libp2p/crypto/keys");
2121
+ }
2122
+ async function loadUint8arrays() {
2123
+ return import("uint8arrays");
2124
+ }
2125
+ function randomPairingNonce() {
2126
+ const bytes = new Uint8Array(16);
2127
+ crypto.getRandomValues(bytes);
2128
+ return [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
2129
+ }
2130
+ async function writeJsonMessage(stream, message, maxBytes = PAIRING_MESSAGE_MAX_BYTES) {
2131
+ const payload = new TextEncoder().encode(JSON.stringify(message));
2132
+ if (payload.byteLength > maxBytes) throw new Error("json_message_too_large");
2133
+ stream.send(payload);
2134
+ }
2135
+ async function readJsonMessage(stream, maxBytes = PAIRING_MESSAGE_MAX_BYTES) {
2136
+ const reader = stream[Symbol.asyncIterator]();
2137
+ const result = await reader.next();
2138
+ if (result.done || !result.value) throw new Error("pairing_message_missing");
2139
+ const chunk = result.value instanceof Uint8Array ? result.value : result.value.subarray();
2140
+ if (chunk.byteLength > maxBytes) throw new Error("json_message_too_large");
2141
+ return JSON.parse(new TextDecoder().decode(chunk));
2142
+ }
2143
+ async function writeStreamJson(stream, message) {
2144
+ const payload = new TextEncoder().encode(JSON.stringify(message));
2145
+ await stream.sink((async function* () {
2146
+ yield payload;
2147
+ })());
2148
+ }
2149
+ async function readStreamJson(stream) {
2150
+ const reader = stream.source[Symbol.asyncIterator]();
2151
+ const result = await reader.next();
2152
+ if (result.done || !result.value) throw new Error("rpc_response_missing");
2153
+ return JSON.parse(new TextDecoder().decode(result.value));
2154
+ }
2155
+ function isRelayAdmissionResponse(value) {
2156
+ if (value === null || typeof value !== "object") return false;
2157
+ const record = value;
2158
+ return record.type === RELAY_ADMISSION_RESPONSE_TYPE && typeof record.ok === "boolean";
2159
+ }
2160
+ function relayCircuitMultiaddr(address) {
2161
+ const parsed = multiaddr(address);
2162
+ return address.includes("/p2p-circuit") ? parsed : parsed.encapsulate("/p2p-circuit");
2163
+ }
2164
+ function objectParameter2(value) {
2165
+ if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error("invalid_sync_params");
2166
+ return value;
2167
+ }
2168
+ function stringParameter2(parameters, key) {
2169
+ const value = parameters[key];
2170
+ if (typeof value !== "string" || value.length === 0) throw new Error("invalid_sync_params");
2171
+ return value;
2172
+ }
2173
+ function stringArrayParameter2(parameters, key) {
2174
+ const value = parameters[key];
2175
+ if (!Array.isArray(value)) throw new Error("invalid_sync_params");
2176
+ return value.filter((item) => typeof item === "string");
2177
+ }
2178
+ function versionVectorParameter(parameters, key) {
2179
+ const raw = objectParameter2(parameters)[key];
2180
+ if (raw === void 0) return {};
2181
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) throw new Error("invalid_sync_params");
2182
+ const version = {};
2183
+ for (const [nodeId, clock] of Object.entries(raw)) {
2184
+ if (typeof clock === "number" && Number.isFinite(clock) && clock >= 0) version[nodeId] = clock;
2185
+ }
2186
+ return version;
2187
+ }
2188
+ function shouldSendConversation(conversation, sinceVersion) {
2189
+ const clock = Math.max(1, conversation.messageCount);
2190
+ return (sinceVersion[conversation.originNodeId] ?? 0) < clock;
2191
+ }
2192
+ function isDevicePlatform(value) {
2193
+ return value === "desktop" || value === "mobile" || value === "cli";
2194
+ }
2195
+ function normalizePairingCapabilities(value) {
2196
+ const raw = value && typeof value === "object" && !Array.isArray(value) ? value : {};
2197
+ return {
2198
+ tools: Array.isArray(raw.tools) ? raw.tools.filter((item) => typeof item === "string") : [],
2199
+ mcpServers: Array.isArray(raw.mcpServers) ? raw.mcpServers.filter((item) => typeof item === "string") : [],
2200
+ hasWiki: raw.hasWiki === true,
2201
+ agentLoop: raw.agentLoop === true,
2202
+ imChannels: Array.isArray(raw.imChannels) ? raw.imChannels.filter((item) => typeof item === "string") : [],
2203
+ wikis: Array.isArray(raw.wikis) ? raw.wikis.filter((item) => item !== null && typeof item === "object") : []
2204
+ };
2205
+ }
2206
+ function normalizePairingDevice(value) {
2207
+ const raw = value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
2208
+ if (!raw) return void 0;
2209
+ const peerId = typeof raw.peerId === "string" ? raw.peerId.trim() : "";
2210
+ const publicKeyMultibase = typeof raw.publicKeyMultibase === "string" ? raw.publicKeyMultibase.trim() : "";
2211
+ const deviceName = typeof raw.deviceName === "string" ? raw.deviceName.trim() : "";
2212
+ if (!peerId || !publicKeyMultibase || !deviceName || !isDevicePlatform(raw.platform)) return void 0;
2213
+ return {
2214
+ peerId,
2215
+ publicKeyMultibase,
2216
+ deviceName,
2217
+ platform: raw.platform,
2218
+ capabilities: normalizePairingCapabilities(raw.capabilities),
2219
+ multiaddrs: Array.isArray(raw.multiaddrs) ? raw.multiaddrs.filter((item) => typeof item === "string") : []
2220
+ };
2221
+ }
2222
+ function isPairingRequest(value) {
2223
+ const raw = value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
2224
+ if (!raw || raw.type !== "memeloop-local-pairing-request-v1") return false;
2225
+ return typeof raw.sessionId === "string" && typeof raw.requestNonce === "string" && typeof raw.createdAt === "number" && typeof raw.expiresAt === "number" && normalizePairingDevice(raw.device) !== void 0;
2226
+ }
2227
+ function isPairingResponse(value) {
2228
+ const raw = value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
2229
+ if (!raw || raw.type !== "memeloop-local-pairing-response-v1") return false;
2230
+ return typeof raw.sessionId === "string" && typeof raw.requestNonce === "string" && typeof raw.responseNonce === "string" && raw.accepted === true && typeof raw.expiresAt === "number" && normalizePairingDevice(raw.device) !== void 0;
2231
+ }
2232
+ async function assertPairingDeviceIdentity(device) {
2233
+ const publicKey = await decodePublicKeyMultibase(device.publicKeyMultibase);
2234
+ if (peerIdFromPublicKey(publicKey).toString() !== device.peerId) throw new Error("pairing_public_key_peer_id_mismatch");
2235
+ }
2236
+ async function buildPairingConfirmCode(input) {
2237
+ const text = [
2238
+ "memeloop-local-pairing-confirm-v1",
2239
+ `initiatorPeerId=${input.initiator.peerId}`,
2240
+ `initiatorPublicKey=${input.initiator.publicKeyMultibase}`,
2241
+ `responderPeerId=${input.responder.peerId}`,
2242
+ `responderPublicKey=${input.responder.publicKeyMultibase}`,
2243
+ `requestNonce=${input.requestNonce}`,
2244
+ `responseNonce=${input.responseNonce}`
2245
+ ].join("\n");
2246
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text));
2247
+ const bytes = new Uint8Array(digest);
2248
+ const value = (bytes[0] << 24 | bytes[1] << 16 | bytes[2] << 8 | bytes[3]) >>> 0;
2249
+ return (value % 1e6).toString().padStart(6, "0");
2250
+ }
2251
+ async function encodePublicKeyMultibase(publicKey) {
2252
+ const { publicKeyToProtobuf } = await loadCryptoKeys();
2253
+ const { toString } = await loadUint8arrays();
2254
+ const protobuf = publicKeyToProtobuf(publicKey);
2255
+ return `${PUBLIC_KEY_MULTIBASE_PREFIX}${toString(protobuf, "base64url")}`;
2256
+ }
2257
+ async function decodePublicKeyMultibase(multibase) {
2258
+ if (!multibase.startsWith(PUBLIC_KEY_MULTIBASE_PREFIX)) {
2259
+ throw new Error("unsupported_public_key_multibase_prefix");
2260
+ }
2261
+ const { publicKeyFromProtobuf } = await loadCryptoKeys();
2262
+ const { fromString } = await loadUint8arrays();
2263
+ const encoded = multibase.slice(PUBLIC_KEY_MULTIBASE_PREFIX.length);
2264
+ const protobuf = fromString(encoded, "base64url");
2265
+ return publicKeyFromProtobuf(protobuf);
2266
+ }
2267
+ async function createDeviceIdentity(platform, deviceName) {
2268
+ const { generateKeyPair } = await loadCryptoKeys();
2269
+ const { toString } = await loadUint8arrays();
2270
+ const privateKey = await generateKeyPair("Ed25519");
2271
+ const publicKey = privateKey.publicKey;
2272
+ return {
2273
+ peerId: peerIdFromPrivateKey(privateKey).toString(),
2274
+ publicKeyMultibase: await encodePublicKeyMultibase(publicKey),
2275
+ privateKeyRef: "libp2p-raw-seed",
2276
+ privateKeyRawSeedBase64Url: toString(privateKey.raw, "base64url"),
2277
+ createdAt: Date.now(),
2278
+ deviceName,
2279
+ platform
2280
+ };
2281
+ }
2282
+ async function privateKeyFromIdentity(identity) {
2283
+ if (identity.privateKeyRawSeedBase64Url) {
2284
+ const { privateKeyFromRaw } = await loadCryptoKeys();
2285
+ const { fromString } = await loadUint8arrays();
2286
+ const raw = fromString(identity.privateKeyRawSeedBase64Url, "base64url");
2287
+ return privateKeyFromRaw(raw);
2288
+ }
2289
+ throw new Error("unsupported_private_key_format");
2290
+ }
2291
+ function buildDeviceBindingMessage(input) {
2292
+ const message = [
2293
+ "memeloop-device-binding-v1",
2294
+ `accountId=${input.accountId}`,
2295
+ `peerId=${input.peerId}`,
2296
+ `publicKey=${input.publicKeyMultibase}`,
2297
+ `nonce=${input.nonce}`
2298
+ ].join("\n");
2299
+ return new TextEncoder().encode(message);
2300
+ }
2301
+ function buildDeviceConnectionGrantMessage(grant) {
2302
+ const message = [
2303
+ "memeloop-device-connection-grant-v1",
2304
+ `issuer=${grant.issuer}`,
2305
+ `accountId=${grant.accountId}`,
2306
+ `subjectPeerId=${grant.subjectPeerId}`,
2307
+ `allowedPeerIds=${grant.allowedPeerIds.join(",")}`,
2308
+ `issuedAt=${grant.issuedAt}`,
2309
+ `expiresAt=${grant.expiresAt}`
2310
+ ].join("\n");
2311
+ return new TextEncoder().encode(message);
2312
+ }
2313
+ function buildDeviceRelayReservationTokenMessage(token) {
2314
+ const message = [
2315
+ "memeloop-device-relay-admission-v1",
2316
+ `issuer=${token.issuer}`,
2317
+ `accountId=${token.accountId}`,
2318
+ `peerId=${token.peerId}`,
2319
+ `relayMultiaddrs=${token.relayMultiaddrs.join(",")}`,
2320
+ `bootstrapMultiaddrs=${token.bootstrapMultiaddrs.join(",")}`,
2321
+ `issuedAt=${token.issuedAt}`,
2322
+ `expiresAt=${token.expiresAt}`
2323
+ ].join("\n");
2324
+ return new TextEncoder().encode(message);
2325
+ }
2326
+ async function signDeviceBinding(input) {
2327
+ const { toString } = await loadUint8arrays();
2328
+ const privateKey = await privateKeyFromIdentity(input.identity);
2329
+ const publicKey = privateKey.publicKey;
2330
+ if (await encodePublicKeyMultibase(publicKey) !== input.identity.publicKeyMultibase) {
2331
+ throw new Error("device_identity_public_key_mismatch");
2332
+ }
2333
+ if (peerIdFromPrivateKey(privateKey).toString() !== input.identity.peerId) {
2334
+ throw new Error("device_identity_peer_id_mismatch");
2335
+ }
2336
+ const message = buildDeviceBindingMessage({
2337
+ accountId: input.accountId,
2338
+ peerId: input.identity.peerId,
2339
+ publicKeyMultibase: input.identity.publicKeyMultibase,
2340
+ nonce: input.nonce
2341
+ });
2342
+ const signature = await privateKey.sign(message);
2343
+ return toString(signature, "base64url");
2344
+ }
2345
+ async function verifyDeviceBinding(input) {
2346
+ try {
2347
+ const { fromString } = await loadUint8arrays();
2348
+ const publicKey = await decodePublicKeyMultibase(input.publicKeyMultibase);
2349
+ if (peerIdFromPublicKey(publicKey).toString() !== input.peerId) {
2350
+ return false;
2351
+ }
2352
+ const message = buildDeviceBindingMessage({
2353
+ accountId: input.accountId,
2354
+ peerId: input.peerId,
2355
+ publicKeyMultibase: input.publicKeyMultibase,
2356
+ nonce: input.cloudNonce
2357
+ });
2358
+ const signature = fromString(input.signature, "base64url");
2359
+ return await publicKey.verify(message, signature);
2360
+ } catch {
2361
+ return false;
2362
+ }
2363
+ }
2364
+ async function verifyDeviceConnectionGrant(input) {
2365
+ try {
2366
+ const now = input.now ?? Date.now();
2367
+ const { grant } = input;
2368
+ if (grant.issuer !== "memeloop-cloud") return false;
2369
+ if (grant.issuedAt > grant.expiresAt) return false;
2370
+ if (grant.expiresAt <= now) return false;
2371
+ if (input.subjectPeerId && grant.subjectPeerId !== input.subjectPeerId) return false;
2372
+ if (input.allowedPeerId && !grant.allowedPeerIds.includes(input.allowedPeerId)) return false;
2373
+ const { fromString } = await loadUint8arrays();
2374
+ const publicKey = await decodePublicKeyMultibase(input.verificationPublicKeyMultibase);
2375
+ const message = buildDeviceConnectionGrantMessage({
2376
+ issuer: grant.issuer,
2377
+ accountId: grant.accountId,
2378
+ subjectPeerId: grant.subjectPeerId,
2379
+ allowedPeerIds: grant.allowedPeerIds,
2380
+ issuedAt: grant.issuedAt,
2381
+ expiresAt: grant.expiresAt
2382
+ });
2383
+ const signature = fromString(grant.signature, "base64url");
2384
+ return await publicKey.verify(message, signature);
2385
+ } catch {
2386
+ return false;
2387
+ }
2388
+ }
2389
+ async function verifyDeviceRelayReservationToken(input) {
2390
+ try {
2391
+ const now = input.now ?? Date.now();
2392
+ const { token } = input;
2393
+ if (token.issuer !== "memeloop-cloud") return false;
2394
+ if (token.issuedAt > token.expiresAt) return false;
2395
+ if (token.expiresAt <= now) return false;
2396
+ if (input.peerId && token.peerId !== input.peerId) return false;
2397
+ const { fromString } = await loadUint8arrays();
2398
+ const publicKey = await decodePublicKeyMultibase(input.verificationPublicKeyMultibase);
2399
+ const message = buildDeviceRelayReservationTokenMessage({
2400
+ issuer: token.issuer,
2401
+ accountId: token.accountId,
2402
+ peerId: token.peerId,
2403
+ relayMultiaddrs: token.relayMultiaddrs,
2404
+ bootstrapMultiaddrs: token.bootstrapMultiaddrs,
2405
+ issuedAt: token.issuedAt,
2406
+ expiresAt: token.expiresAt
2407
+ });
2408
+ const signature = fromString(token.signature, "base64url");
2409
+ return await publicKey.verify(message, signature);
2410
+ } catch {
2411
+ return false;
2412
+ }
2413
+ }
2414
+ function addRelayListenAddress(addresses) {
2415
+ return addresses.includes("/p2p-circuit") ? addresses : [...addresses, "/p2p-circuit"];
2416
+ }
2417
+
2418
+ // src/device-network/cloudDeviceAuthorizer.ts
2419
+ var PAIRING_PROTOCOL3 = "/memeloop/pairing/1.0.0";
2420
+ var CloudDeviceAuthorizer = class {
2421
+ constructor(options) {
2422
+ this.options = options;
2423
+ this.allowPairingProtocol = options.allowPairingProtocol ?? true;
2424
+ }
2425
+ allowPairingProtocol;
2426
+ async canOpenProtocol(input) {
2427
+ const record = this.options.getTrustedDevice?.(input.remotePeerId);
2428
+ if (record?.revokedAt) return false;
2429
+ if (input.protocol === PAIRING_PROTOCOL3) return this.allowPairingProtocol;
2430
+ if (record) return true;
2431
+ if (!input.presentedGrant) return false;
2432
+ const direction = input.direction ?? "inbound";
2433
+ return verifyDeviceConnectionGrant({
2434
+ grant: input.presentedGrant,
2435
+ verificationPublicKeyMultibase: this.options.grantVerificationPublicKeyMultibase,
2436
+ subjectPeerId: direction === "outbound" ? this.options.localPeerId : input.remotePeerId,
2437
+ allowedPeerId: direction === "outbound" ? input.remotePeerId : this.options.localPeerId,
2438
+ now: this.options.now?.()
2439
+ });
2440
+ }
2441
+ };
2442
+
2443
+ // src/device-network/memoryDeviceNetworkService.ts
2444
+ var emptyCapabilities2 = {
2445
+ tools: [],
2446
+ mcpServers: [],
2447
+ hasWiki: false,
2448
+ agentLoop: false,
2449
+ imChannels: [],
2450
+ wikis: []
2451
+ };
2452
+ function toDevice(record, capabilities) {
2453
+ return {
2454
+ peerId: record.peerId,
2455
+ displayName: record.deviceName,
2456
+ platform: record.platform,
2457
+ trustMode: record.trustMode,
2458
+ trusted: !record.revokedAt,
2459
+ reachability: {
2460
+ state: record.revokedAt ? "offline" : "nearby",
2461
+ paths: record.revokedAt ? [] : ["lan"]
2462
+ },
2463
+ capabilities,
2464
+ lastSeen: record.lastSeen
2465
+ };
2466
+ }
2467
+ var MemoryDeviceNetworkService = class {
2468
+ constructor(options) {
2469
+ this.options = options;
2470
+ this.capabilities = options.capabilities ?? emptyCapabilities2;
2471
+ for (const record of options.trustedDevices ?? []) {
2472
+ this.trustedDevices.set(record.peerId, record);
2473
+ }
2474
+ }
2475
+ started = false;
2476
+ capabilities;
2477
+ trustedDevices = /* @__PURE__ */ new Map();
2478
+ pairingSessions = /* @__PURE__ */ new Map();
2479
+ listeners = /* @__PURE__ */ new Set();
2480
+ pairingListeners = /* @__PURE__ */ new Set();
2481
+ async start() {
2482
+ this.started = true;
2483
+ this.emitDevices();
2484
+ }
2485
+ async stop() {
2486
+ this.started = false;
2487
+ this.emitDevices();
2488
+ }
2489
+ async getLocalDevice() {
2490
+ return {
2491
+ peerId: this.options.identity.peerId,
2492
+ displayName: this.options.identity.deviceName,
2493
+ platform: this.options.identity.platform,
2494
+ trustMode: "local-pairing",
2495
+ reachability: {
2496
+ state: this.started ? "online" : "offline",
2497
+ paths: this.started ? ["lan"] : []
2498
+ },
2499
+ capabilities: this.capabilities,
2500
+ lastSeen: Date.now()
2501
+ };
2502
+ }
2503
+ async listDevices() {
2504
+ return [...this.trustedDevices.values()].map((record) => toDevice(record, emptyCapabilities2));
2505
+ }
2506
+ observeDevices(listener) {
2507
+ this.listeners.add(listener);
2508
+ void this.listDevices().then(listener);
2509
+ return () => {
2510
+ this.listeners.delete(listener);
2511
+ };
2512
+ }
2513
+ async listPairingSessions() {
2514
+ return [...this.pairingSessions.values()];
2515
+ }
2516
+ observePairingSessions(listener) {
2517
+ this.pairingListeners.add(listener);
2518
+ void this.listPairingSessions().then(listener);
2519
+ return () => {
2520
+ this.pairingListeners.delete(listener);
2521
+ };
2522
+ }
2523
+ async requestLocalPairing(peerId, _options = {}) {
2524
+ const createdAt = Date.now();
2525
+ const session = {
2526
+ sessionId: `pairing-${this.options.identity.peerId}-${peerId}-${createdAt}`,
2527
+ localPeerId: this.options.identity.peerId,
2528
+ remotePeerId: peerId,
2529
+ remotePublicKeyMultibase: "",
2530
+ remoteDeviceName: peerId,
2531
+ remotePlatform: "cli",
2532
+ remoteCapabilities: emptyCapabilities2,
2533
+ remoteMultiaddrs: [],
2534
+ direction: "outbound",
2535
+ status: "pending",
2536
+ confirmCode: this.confirmCode(peerId),
2537
+ createdAt,
2538
+ expiresAt: createdAt + 5 * 6e4
2539
+ };
2540
+ this.pairingSessions.set(session.sessionId, session);
2541
+ this.emitPairingSessions();
2542
+ return session;
2543
+ }
2544
+ async acceptPairing(sessionId) {
2545
+ const session = this.pairingSessions.get(sessionId);
2546
+ if (!session) throw new Error("pairing_session_not_found");
2547
+ if (session.expiresAt < Date.now()) throw new Error("pairing_session_expired");
2548
+ if (session.status !== "pending") throw new Error("pairing_session_not_pending");
2549
+ this.trustedDevices.set(session.remotePeerId, {
2550
+ peerId: session.remotePeerId,
2551
+ publicKeyMultibase: session.remotePublicKeyMultibase,
2552
+ deviceName: session.remoteDeviceName,
2553
+ platform: session.remotePlatform,
2554
+ trustMode: "local-pairing",
2555
+ createdAt: Date.now(),
2556
+ lastSeen: Date.now()
2557
+ });
2558
+ session.status = "accepted";
2559
+ this.emitPairingSessions();
2560
+ this.emitDevices();
2561
+ }
2562
+ async rejectPairing(sessionId) {
2563
+ const session = this.pairingSessions.get(sessionId);
2564
+ if (!session) return;
2565
+ session.status = "rejected";
2566
+ this.emitPairingSessions();
2567
+ }
2568
+ async removeTrustedDevice(peerId) {
2569
+ this.trustedDevices.delete(peerId);
2570
+ this.emitDevices();
2571
+ }
2572
+ async openStream(peerId, _protocol, _presentedGrant) {
2573
+ if (!this.trustedDevices.has(peerId)) throw new Error("device_not_trusted");
2574
+ return {
2575
+ source: (async function* emptySource() {
2576
+ })(),
2577
+ async sink() {
2578
+ },
2579
+ async close() {
2580
+ }
2581
+ };
2582
+ }
2583
+ async sendRpc(peerId, _method, _parameters, _presentedGrant) {
2584
+ if (!this.trustedDevices.has(peerId)) throw new Error("device_not_trusted");
2585
+ throw new Error("rpc_handler_not_registered");
2586
+ }
2587
+ async syncWithDevice(peerId, _presentedGrant) {
2588
+ if (!this.trustedDevices.has(peerId)) throw new Error("device_not_trusted");
2589
+ return { ok: true, peerId, syncedAt: Date.now() };
2590
+ }
2591
+ confirmCode(peerId) {
2592
+ const input = `${this.options.identity.peerId}:${peerId}`;
2593
+ let hash = 0;
2594
+ for (let index = 0; index < input.length; index += 1) {
2595
+ hash = hash * 31 + input.charCodeAt(index) >>> 0;
2596
+ }
2597
+ return (hash % 1e6).toString().padStart(6, "0");
2598
+ }
2599
+ emitDevices() {
2600
+ void this.listDevices().then((devices) => {
2601
+ for (const listener of this.listeners) listener(devices);
2602
+ });
2603
+ }
2604
+ emitPairingSessions() {
2605
+ void this.listPairingSessions().then((sessions) => {
2606
+ for (const listener of this.pairingListeners) listener(sessions);
2607
+ });
2608
+ }
2609
+ };
2610
+
2611
+ // src/device-network/syncCloudDevices.ts
2612
+ async function syncCloudDevices(input) {
2613
+ const cloudDevices = await input.cloudClient.listDevices();
2614
+ if (cloudDevices.length === 0) return [];
2615
+ const existingRecords = await input.trustStore.loadTrustedDevices();
2616
+ const existingByPeerId = new Map(existingRecords.map((r) => [r.peerId, r]));
2617
+ for (const cloudDevice of cloudDevices) {
2618
+ const existing = existingByPeerId.get(cloudDevice.peerId);
2619
+ const record = {
2620
+ peerId: cloudDevice.peerId,
2621
+ publicKeyMultibase: cloudDevice.publicKeyMultibase,
2622
+ deviceName: cloudDevice.deviceName,
2623
+ platform: cloudDevice.platform,
2624
+ trustMode: "cloud-account",
2625
+ accountId: cloudDevice.accountId,
2626
+ createdAt: existing?.createdAt ?? Date.now(),
2627
+ lastSeen: cloudDevice.lastSeen,
2628
+ revokedAt: cloudDevice.revokedAt
2629
+ };
2630
+ if (existing) {
2631
+ existing.lastSeen = cloudDevice.lastSeen;
2632
+ existing.revokedAt = cloudDevice.revokedAt;
2633
+ if (existing.publicKeyMultibase !== record.publicKeyMultibase || existing.deviceName !== record.deviceName || existing.platform !== record.platform) {
2634
+ await input.trustStore.saveTrustedDevice(record);
2635
+ }
2636
+ } else {
2637
+ await input.trustStore.saveTrustedDevice(record);
2638
+ }
2639
+ }
2640
+ return cloudDevices;
2641
+ }
2642
+
2643
+ // src/im/channelManager.ts
2644
+ var IMChannelManager = class {
2645
+ constructor(storage) {
2646
+ this.storage = storage;
2647
+ }
2648
+ bindings = /* @__PURE__ */ new Map();
2649
+ key(channelId, imUserId) {
2650
+ return `${channelId}::${imUserId}`;
2651
+ }
2652
+ async getBinding(channelId, imUserId) {
2653
+ const k = this.key(channelId, imUserId);
2654
+ if (this.storage?.getImBinding) {
2655
+ const row = await this.storage.getImBinding(channelId, imUserId);
2656
+ if (row) {
2657
+ this.bindings.set(k, row);
2658
+ return row;
2659
+ }
2660
+ }
2661
+ return this.bindings.get(k);
2662
+ }
2663
+ async setBinding(record) {
2664
+ this.bindings.set(this.key(record.channelId, record.imUserId), record);
2665
+ if (this.storage?.setImBinding) {
2666
+ await this.storage.setImBinding(record);
2667
+ }
2668
+ }
2669
+ /**
2670
+ * 处理入站文本:无 binding 时创建会话并发送首条用户消息。
2671
+ */
2672
+ async dispatchInbound(message, driver, options) {
2673
+ const existing = await this.getBinding(message.channelId, message.imUserId);
2674
+ if (existing) {
2675
+ await driver.sendMessage({
2676
+ conversationId: existing.activeConversationId,
2677
+ message: message.text
2678
+ });
2679
+ return { conversationId: existing.activeConversationId };
2680
+ }
2681
+ const definitionId = options.defaultDefinitionId;
2682
+ const { conversationId } = await driver.createAgent({
2683
+ definitionId,
2684
+ initialMessage: message.text
2685
+ });
2686
+ await this.setBinding({
2687
+ channelId: message.channelId,
2688
+ imUserId: message.imUserId,
2689
+ activeConversationId: conversationId,
2690
+ createdAt: Date.now(),
2691
+ defaultDefinitionId: definitionId
2692
+ });
2693
+ return { conversationId };
2694
+ }
2695
+ async switchConversation(channelId, imUserId, conversationId) {
2696
+ const current = await this.getBinding(channelId, imUserId);
2697
+ if (current) {
2698
+ await this.setBinding({ ...current, activeConversationId: conversationId });
2699
+ } else {
2700
+ await this.setBinding({
2701
+ channelId,
2702
+ imUserId,
2703
+ activeConversationId: conversationId,
2704
+ createdAt: Date.now()
2705
+ });
2706
+ }
2707
+ }
2708
+ };
2709
+ function pickAdapter(adapters, platform) {
2710
+ return adapters.find((a) => a.platform === platform);
2711
+ }
2712
+
2713
+ // src/im/imPlatformLimits.ts
2714
+ function imPlatformMaxMessageChars(platform) {
2715
+ switch (platform) {
2716
+ case "telegram":
2717
+ return 4096;
2718
+ case "discord":
2719
+ return 2e3;
2720
+ case "lark":
2721
+ return 4096;
2722
+ case "wecom":
2723
+ return 2048;
2724
+ case "slack":
2725
+ return 4e3;
2726
+ case "qq":
2727
+ return 4500;
2728
+ default:
2729
+ return 3500;
2730
+ }
2731
+ }
2732
+
2733
+ // src/im/slashCommands.ts
2734
+ async function tryHandleImSlashCommand(context) {
2735
+ const t = context.rawText.trim();
2736
+ if (!t.startsWith("/") || t.startsWith("//") || t.length < 2) {
2737
+ return { handled: false, messages: [] };
2738
+ }
2739
+ const parts = t.slice(1).split(/\s+/).filter(Boolean);
2740
+ const cmd = (parts[0] ?? "").toLowerCase();
2741
+ const rest = parts.slice(1).join(" ").trim();
2742
+ const binding = await context.manager.getBinding(context.channelId, context.imUserId);
2743
+ const ok = (lines) => ({ handled: true, messages: lines });
2744
+ if (cmd === "list") {
2745
+ const list = await context.storage.listConversations({ limit: 50 });
2746
+ if (list.length === 0) return ok(["\uFF08\u6682\u65E0\u4F1A\u8BDD\uFF09"]);
2747
+ const body = list.map(
2748
+ (c, index) => `${index + 1}. ${c.title || c.conversationId} \u2014 ${c.conversationId.slice(0, 14)}\u2026`
2749
+ );
2750
+ return ok(["\u53EF\u5207\u6362\u4F1A\u8BDD\uFF1A", ...body.map((l) => `\u2022 ${l}`)]);
2751
+ }
2752
+ if (cmd === "switch") {
2753
+ if (!binding) return ok(["\u5F53\u524D\u65E0\u7ED1\u5B9A\u4F1A\u8BDD\uFF0C\u5148\u7528 /new \u521B\u5EFA\u3002"]);
2754
+ const list = await context.storage.listConversations({ limit: 50 });
2755
+ const argument = rest.trim();
2756
+ if (!argument) return ok(["\u7528\u6CD5\uFF1A/switch <\u7F16\u53F7\u6216 conversationId>"]);
2757
+ const n = parseInt(argument, 10);
2758
+ let targetId;
2759
+ if (!Number.isNaN(n) && n >= 1 && n <= list.length) {
2760
+ targetId = list[n - 1]?.conversationId;
2761
+ } else {
2762
+ targetId = list.find(
2763
+ (c) => c.conversationId === argument || c.conversationId.includes(argument)
2764
+ )?.conversationId;
2765
+ }
2766
+ if (!targetId) return ok([`\u672A\u627E\u5230\u4F1A\u8BDD\uFF1A${argument}`]);
2767
+ await context.manager.switchConversation(context.channelId, context.imUserId, targetId);
2768
+ return ok([`\u5DF2\u5207\u6362\u5230\uFF1A${targetId}`]);
2769
+ }
2770
+ if (cmd === "new" || cmd === "agent") {
2771
+ const definitionId = rest || context.defaultDefinitionId;
2772
+ const { conversationId } = await context.driver.createAgent({ definitionId });
2773
+ await context.manager.setBinding({
2774
+ channelId: context.channelId,
2775
+ imUserId: context.imUserId,
2776
+ activeConversationId: conversationId,
2777
+ createdAt: Date.now(),
2778
+ defaultDefinitionId: definitionId
2779
+ });
2780
+ return ok([`\u5DF2\u65B0\u5EFA\u5E76\u7ED1\u5B9A\u4F1A\u8BDD\u3002
2781
+ ID\uFF1A\`${conversationId}\`
2782
+ \u53D1\u9001\u6D88\u606F\u5373\u53EF\u5F00\u59CB\u3002`]);
2783
+ }
2784
+ if (cmd === "status") {
2785
+ if (!binding) return ok(["\u65E0\u6D3B\u8DC3 IM \u7ED1\u5B9A\u3002"]);
2786
+ const meta = await context.storage.getConversationMeta(binding.activeConversationId);
2787
+ return ok([
2788
+ `\u4F1A\u8BDD\uFF1A${binding.activeConversationId}`,
2789
+ `\u5B9A\u4E49\uFF1A${meta?.definitionId ?? "?"}`,
2790
+ `\u6D88\u606F\u6570\uFF1A${meta?.messageCount ?? "?"}`
2791
+ ]);
2792
+ }
2793
+ if (cmd === "cancel") {
2794
+ if (!binding) return ok(["\u65E0\u4F1A\u8BDD\u53EF\u53D6\u6D88\u3002"]);
2795
+ await context.runtime.cancelAgent(binding.activeConversationId);
2796
+ return ok(["\u5DF2\u8BF7\u6C42\u53D6\u6D88\u5F53\u524D\u4EFB\u52A1\u3002"]);
2797
+ }
2798
+ return { handled: false, messages: [] };
2799
+ }
2800
+
2801
+ // src/im/streamAgentReplyToIm.ts
2802
+ function stringifyImValue(value) {
2803
+ if (value == null) return "";
2804
+ if (typeof value === "string") return value;
2805
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
2806
+ return String(value);
2807
+ }
2808
+ try {
2809
+ return JSON.stringify(value);
2810
+ } catch {
2811
+ return "[unserializable value]";
2812
+ }
2813
+ }
2814
+ async function streamRuntimeAgentReplyToIm(options) {
2815
+ const max = imPlatformMaxMessageChars(options.platform);
2816
+ let buf = "";
2817
+ const sendSlice = async (force) => {
2818
+ if (buf.length === 0) return;
2819
+ if (force) {
2820
+ while (buf.length > 0) {
2821
+ const take = Math.min(max, buf.length);
2822
+ const chunk = buf.slice(0, take);
2823
+ buf = buf.slice(take);
2824
+ if (chunk.trim().length > 0) await options.flush(chunk);
2825
+ }
2826
+ return;
2827
+ }
2828
+ while (buf.length >= max) {
2829
+ const chunk = buf.slice(0, max);
2830
+ buf = buf.slice(max);
2831
+ await options.flush(chunk);
2832
+ }
2833
+ };
2834
+ await new Promise((resolve, reject) => {
2835
+ const off = options.runtime.subscribeToUpdates(options.conversationId, (u) => {
2836
+ const o = u;
2837
+ if (o.type === "agent-done") {
2838
+ void sendSlice(true).then(() => {
2839
+ off();
2840
+ resolve();
2841
+ }).catch(reject);
2842
+ return;
2843
+ }
2844
+ if (o.type === "agent-error") {
2845
+ off();
2846
+ reject(new Error(o.error ?? "agent-error"));
2847
+ return;
2848
+ }
2849
+ if (o.type === "agent-step" && o.step?.type === "message") {
2850
+ const stepData = o.step.data;
2851
+ let piece = "";
2852
+ if (typeof stepData === "string") piece = stepData;
2853
+ else if (stepData != null && typeof stepData === "object" && "content" in stepData) {
2854
+ const content = stepData.content;
2855
+ piece = stringifyImValue(content);
2856
+ } else piece = stringifyImValue(stepData);
2857
+ buf += piece;
2858
+ void sendSlice(false);
2859
+ }
2860
+ if (o.type === "agent-step" && o.step?.type === "tool") {
2861
+ const td = o.step.data;
2862
+ const id = td.toolId;
2863
+ if (!id) return;
2864
+ if (id === "ask-question") {
2865
+ const p = td.parameters;
2866
+ const q = typeof p?.question === "string" ? p.question : "";
2867
+ const optLabels = p?.options?.map((o2) => o2.label).filter((x) => typeof x === "string");
2868
+ const askText = options.renderer.renderAskQuestion(q, optLabels);
2869
+ buf += (buf && !buf.endsWith("\n") ? "\n" : "") + askText + "\n";
2870
+ void sendSlice(true);
2871
+ return;
2872
+ }
2873
+ if (td.isError) {
2874
+ buf += (buf && !buf.endsWith("\n") ? "\n" : "") + options.renderer.renderError(stringifyImValue(td.result) || "tool error") + "\n";
2875
+ void sendSlice(false);
2876
+ return;
2877
+ }
2878
+ const callLine = options.renderer.renderToolCallSummary(id, td.parameters ?? {});
2879
+ const resultLine = options.renderer.renderToolResultSummary(id, td.result ?? null);
2880
+ buf += (buf && !buf.endsWith("\n") ? "\n" : "") + callLine + (resultLine ? `
2881
+ ${resultLine}` : "") + "\n";
2882
+ void sendSlice(false);
2883
+ }
2884
+ });
2885
+ });
2886
+ }
2887
+
2888
+ // src/im/textRenderer.ts
2889
+ var TextMessageRenderer = class {
2890
+ renderPlainText(content) {
2891
+ return content.trim();
2892
+ }
2893
+ renderToolCallSummary(toolName, arguments_) {
2894
+ const s = (() => {
2895
+ try {
2896
+ const index = JSON.stringify(arguments_);
2897
+ if (!index || index === "{}") return "";
2898
+ return index.length > 120 ? `${index.slice(0, 117)}\u2026` : index;
2899
+ } catch {
2900
+ return "";
2901
+ }
2902
+ })();
2903
+ return s ? `\u{1F527} ${toolName}: ${s}` : `\u{1F527} ${toolName}`;
2904
+ }
2905
+ renderToolResultSummary(toolName, result) {
2906
+ if (result == null) return `\u2705 ${toolName}: done`;
2907
+ if (typeof result === "string") {
2908
+ const t = result.trim();
2909
+ if (!t) return `\u2705 ${toolName}: done`;
2910
+ return `\u2705 ${toolName}: ${t.length > 120 ? `${t.slice(0, 117)}\u2026` : t}`;
2911
+ }
2912
+ if (typeof result === "object") {
2913
+ const keys = Object.keys(result);
2914
+ if (keys.length === 0) return `\u2705 ${toolName}: done`;
2915
+ return `\u2705 ${toolName}: {${keys.slice(0, 6).join(", ")}${keys.length > 6 ? ", \u2026" : ""}}`;
2916
+ }
2917
+ if (typeof result === "symbol") return `\u2705 ${toolName}: ${result.toString()}`;
2918
+ if (typeof result === "function") return `\u2705 ${toolName}: [function]`;
2919
+ if (typeof result === "number" || typeof result === "boolean" || typeof result === "bigint") {
2920
+ return `\u2705 ${toolName}: ${String(result)}`;
2921
+ }
2922
+ return `\u2705 ${toolName}: done`;
2923
+ }
2924
+ renderToolApproval(toolName, arguments_) {
2925
+ return `\u23F8 \u9700\u8981\u5BA1\u6279\u6267\u884C\u5DE5\u5177\uFF1A${toolName}
2926
+ \u53C2\u6570\uFF1A
2927
+ \`\`\`json
2928
+ ${JSON.stringify(arguments_, null, 2)}
2929
+ \`\`\`
2930
+ \u56DE\u590D Y \u540C\u610F / N \u62D2\u7EDD`;
2931
+ }
2932
+ renderAskQuestion(question, options) {
2933
+ const lines = options?.length ? [`\u2753 ${question}`, ...options.map((o, index) => `${index + 1}. ${o}`)] : [`\u2753 ${question}`];
2934
+ return lines.join("\n");
2935
+ }
2936
+ renderThinking(_content) {
2937
+ return null;
2938
+ }
2939
+ renderError(error2) {
2940
+ return `\u26A0\uFE0F \u9519\u8BEF\uFF1A${error2}`;
2941
+ }
2942
+ renderTodoList(todos) {
2943
+ return ["\u{1F4CB} Todo", ...todos.map((t) => `${t.done ? "[x]" : "[ ]"} ${t.text}`)].join("\n");
2944
+ }
2945
+ };
2946
+
2947
+ // src/im/tools.ts
2948
+ var imToolDefinitions = [
2949
+ {
2950
+ name: "im.listConversations",
2951
+ description: "\u5217\u51FA\u53EF\u5207\u6362\u7684\u4F1A\u8BDD\uFF08\u7531 memeloop-cli registerImSessionTools \u6CE8\u518C\uFF0C\u4EC5 IM \u6765\u6E90\u4F1A\u8BDD\u53EF\u7528\uFF09\u3002"
2952
+ },
2953
+ {
2954
+ name: "im.switchConversation",
2955
+ description: "\u5207\u6362\u5F53\u524D IM \u7528\u6237\u7ED1\u5B9A\u7684\u6D3B\u8DC3\u4F1A\u8BDD\u3002\u53C2\u6570\uFF1A{ conversationId: string }"
2956
+ },
2957
+ {
2958
+ name: "im.newConversation",
2959
+ description: "\u521B\u5EFA\u65B0\u4F1A\u8BDD\u5E76\u5207\u6362\u7ED1\u5B9A\u3002\u53C2\u6570\uFF1A{ definitionId?: string }"
2960
+ },
2961
+ {
2962
+ name: "im.summarizeHistory",
2963
+ description: "\u8FD4\u56DE\u5F53\u524D\u4F1A\u8BDD\u6700\u8FD1\u6D88\u606F\u7684\u7EAF\u6587\u672C\u6458\u8981\uFF08\u89C4\u5219\u62FC\u63A5\uFF0C\u975E LLM\uFF09\u3002\u53C2\u6570\uFF1A{ maxMessages?: number }"
2964
+ }
2965
+ ];
2966
+ async function imSwitchConversation(rt, conversationId) {
2967
+ await rt.manager.switchConversation(rt.channelId, rt.imUserId, conversationId);
2968
+ }
2969
+
2970
+ // src/sync/protocol.ts
2971
+ function isConversationMeta(value) {
2972
+ if (value === null || typeof value !== "object") return false;
2973
+ const o = value;
2974
+ return typeof o.conversationId === "string" && typeof o.title === "string" && typeof o.originNodeId === "string" && typeof o.definitionId === "string" && typeof o.messageCount === "number" && typeof o.lastMessageTimestamp === "number" && typeof o.isUserInitiated === "boolean";
2975
+ }
2976
+
2977
+ // src/tools/builtins/imBuiltinTools.ts
2978
+ import { z } from "zod";
2979
+ var IM_SESSION_TOOL_IDS = [
2980
+ "im.listConversations",
2981
+ "im.switchConversation",
2982
+ "im.newConversation",
2983
+ "im.summarizeHistory"
2984
+ ];
2985
+ var listSchema = z.object({});
2986
+ var switchSchema = z.object({
2987
+ conversationId: z.string().min(1)
2988
+ });
2989
+ var newSchema = z.object({
2990
+ definitionId: z.string().min(1).optional()
2991
+ });
2992
+ var summarizeSchema = z.object({
2993
+ maxMessages: z.number().int().positive().max(200).optional()
2994
+ });
2995
+ function error(message) {
2996
+ return { error: message };
2997
+ }
2998
+ async function requireImSource(context, conversationId) {
2999
+ const meta = await context.storage.getConversationMeta(conversationId);
3000
+ const sc = meta?.sourceChannel;
3001
+ if (!sc) {
3002
+ return { error: "im_tools_only_in_im_session" };
3003
+ }
3004
+ return { channelId: sc.channelId, imUserId: sc.imUserId, platform: sc.platform };
3005
+ }
3006
+ async function imListConversationsImpl(arguments_, context, _reg) {
3007
+ const parsed = listSchema.safeParse(arguments_);
3008
+ if (!parsed.success) return error("invalid_args");
3009
+ const cid = context.activeToolConversationId;
3010
+ if (!cid) return error("no_active_conversation");
3011
+ const source = await requireImSource(context, cid);
3012
+ if ("error" in source) return source;
3013
+ const all = await context.storage.listConversations({});
3014
+ if (all.length === 0) return { result: "\uFF08\u6682\u65E0\u4F1A\u8BDD\uFF09" };
3015
+ const sorted = [...all].sort((a, b) => b.lastMessageTimestamp - a.lastMessageTimestamp);
3016
+ const lines = sorted.map((m, index) => {
3017
+ const title = m.title || m.conversationId;
3018
+ const t = new Date(m.lastMessageTimestamp).toISOString();
3019
+ return `${index + 1}. ${title}
3020
+ id: ${m.conversationId}
3021
+ def: ${m.definitionId} | ${t}`;
3022
+ });
3023
+ return { result: `\u53EF\u5207\u6362\u7684\u4F1A\u8BDD\uFF1A
3024
+ ${lines.join("\n")}` };
3025
+ }
3026
+ async function imSwitchConversationImpl(arguments_, context, reg) {
3027
+ const parsed = switchSchema.safeParse(arguments_);
3028
+ if (!parsed.success) return error("invalid_args");
3029
+ const cid = context.activeToolConversationId;
3030
+ if (!cid) return error("no_active_conversation");
3031
+ const source = await requireImSource(context, cid);
3032
+ if ("error" in source) return source;
3033
+ await reg.imChannelManager.switchConversation(
3034
+ source.channelId,
3035
+ source.imUserId,
3036
+ parsed.data.conversationId
3037
+ );
3038
+ return { result: `\u5DF2\u5207\u6362\u5230\u4F1A\u8BDD\uFF1A${parsed.data.conversationId}` };
3039
+ }
3040
+ async function imNewConversationImpl(arguments_, context, reg) {
3041
+ const parsed = newSchema.safeParse(arguments_);
3042
+ if (!parsed.success) return error("invalid_args");
3043
+ const cid = context.activeToolConversationId;
3044
+ if (!cid) return error("no_active_conversation");
3045
+ const source = await requireImSource(context, cid);
3046
+ if ("error" in source) return source;
3047
+ const current = await reg.imChannelManager.getBinding(source.channelId, source.imUserId);
3048
+ const definitionId = parsed.data.definitionId?.trim() || current?.defaultDefinitionId || "memeloop:general-assistant";
3049
+ const rt = reg.getMemeLoopRuntime();
3050
+ const { conversationId } = await rt.createAgent({ definitionId, initialMessage: "" });
3051
+ await reg.imChannelManager.setBinding({
3052
+ channelId: source.channelId,
3053
+ imUserId: source.imUserId,
3054
+ activeConversationId: conversationId,
3055
+ createdAt: Date.now(),
3056
+ defaultDefinitionId: definitionId
3057
+ });
3058
+ const meta = await context.storage.getConversationMeta(conversationId);
3059
+ if (meta) {
3060
+ await context.storage.upsertConversationMetadata({
3061
+ ...meta,
3062
+ sourceChannel: {
3063
+ channelId: source.channelId,
3064
+ platform: source.platform,
3065
+ imUserId: source.imUserId
3066
+ }
3067
+ });
3068
+ }
3069
+ return {
3070
+ result: `\u5DF2\u65B0\u5EFA\u5E76\u5207\u6362\u5230\u4F1A\u8BDD\uFF1A${conversationId}
3071
+ definition: ${definitionId}
3072
+ \u8BF7\u8BA9\u7528\u6237\u53D1\u9001\u4E0B\u4E00\u6761\u6D88\u606F\u4EE5\u5F00\u59CB\u5BF9\u8BDD\u3002`
3073
+ };
3074
+ }
3075
+ async function imSummarizeHistoryImpl(arguments_, context, _reg) {
3076
+ const parsed = summarizeSchema.safeParse(arguments_);
3077
+ if (!parsed.success) return error("invalid_args");
3078
+ const cid = context.activeToolConversationId;
3079
+ if (!cid) return error("no_active_conversation");
3080
+ const source = await requireImSource(context, cid);
3081
+ if ("error" in source) return source;
3082
+ void source;
3083
+ const max = parsed.data.maxMessages ?? 40;
3084
+ const msgs = await context.storage.getMessages(cid, { mode: "full-content" });
3085
+ const tail = msgs.slice(-max);
3086
+ if (tail.length === 0) return { result: "\uFF08\u5F53\u524D\u4F1A\u8BDD\u5C1A\u65E0\u6D88\u606F\uFF09" };
3087
+ const lines = tail.map(
3088
+ (m) => `[${m.role}] ${m.content.slice(0, 2e3)}${m.content.length > 2e3 ? "\u2026" : ""}`
3089
+ );
3090
+ return {
3091
+ result: `\u6700\u8FD1 ${tail.length} \u6761\u6D88\u606F\u6458\u8981\uFF08\u4F9B IM \u4E0A\u4E0B\u6587\uFF09\uFF1A
3092
+ ${lines.join("\n---\n")}`
3093
+ };
3094
+ }
3095
+ function registerImSessionBuiltinTools(registry, context, reg) {
3096
+ const bound = (function_) => (arguments_) => function_(arguments_, context, reg);
3097
+ registry.registerTool("im.listConversations", bound(imListConversationsImpl));
3098
+ registry.registerTool("im.switchConversation", bound(imSwitchConversationImpl));
3099
+ registry.registerTool("im.newConversation", bound(imNewConversationImpl));
3100
+ registry.registerTool("im.summarizeHistory", bound(imSummarizeHistoryImpl));
3101
+ registerToolParameterSchema("im.listConversations", listSchema, {
3102
+ displayName: "IM: list conversations",
3103
+ description: "List conversations the user can switch to (IM sessions only)."
3104
+ });
3105
+ registerToolParameterSchema("im.switchConversation", switchSchema, {
3106
+ displayName: "IM: switch conversation",
3107
+ description: "Switch this IM user binding to another conversationId."
3108
+ });
3109
+ registerToolParameterSchema("im.newConversation", newSchema, {
3110
+ displayName: "IM: new conversation",
3111
+ description: "Create a new agent conversation and bind this IM user to it."
3112
+ });
3113
+ registerToolParameterSchema("im.summarizeHistory", summarizeSchema, {
3114
+ displayName: "IM: summarize history",
3115
+ description: "Return a plain-text digest of recent messages in the current conversation."
3116
+ });
3117
+ }
3118
+
3119
+ // src/tools/builtins/index.ts
3120
+ function registerBuiltinTools(registry, context) {
3121
+ const promptDestination = registry.getPromptPlugins?.();
3122
+ if (promptDestination) {
3123
+ for (const [id, tool] of pluginRegistry) {
3124
+ promptDestination.set(id, tool);
3125
+ }
3126
+ registerBuiltinPromptPlugins(promptDestination);
3127
+ } else {
3128
+ registerBuiltinPromptPlugins();
3129
+ }
3130
+ registerBuiltinToolPlugins();
3131
+ getLoopRegistry().installPluginsForLoop("*", { ...context, toolRegistry: registry }, [
3132
+ ...BUILTIN_TOOL_PLUGIN_IDS
3133
+ ]);
3134
+ }
3135
+
3136
+ // src/tools/parallelExecution.ts
3137
+ var DEFAULT_TOOL_TIMEOUT_MS = 3e4;
3138
+ var DEFAULT_BATCH_TIMEOUT_MS = 12e4;
3139
+ async function executeWithTimeout(entry) {
3140
+ const timeoutMs = entry.timeoutMs ?? DEFAULT_TOOL_TIMEOUT_MS;
3141
+ return new Promise((resolve) => {
3142
+ let settled = false;
3143
+ const timer = timeoutMs > 0 ? setTimeout(() => {
3144
+ if (!settled) {
3145
+ settled = true;
3146
+ resolve({
3147
+ call: entry.call,
3148
+ status: "timeout",
3149
+ error: `Tool "${entry.call.toolId}" timed out after ${timeoutMs}ms`
3150
+ });
3151
+ }
3152
+ }, timeoutMs) : void 0;
3153
+ entry.executor(entry.call.parameters ?? {}).then((result) => {
3154
+ if (!settled) {
3155
+ settled = true;
3156
+ if (timer) clearTimeout(timer);
3157
+ resolve({ call: entry.call, status: "fulfilled", result });
3158
+ }
3159
+ }).catch((error2) => {
3160
+ if (!settled) {
3161
+ settled = true;
3162
+ if (timer) clearTimeout(timer);
3163
+ resolve({
3164
+ call: entry.call,
3165
+ status: "rejected",
3166
+ result: {
3167
+ success: false,
3168
+ error: error2 instanceof Error ? error2.message : String(error2)
3169
+ },
3170
+ error: error2 instanceof Error ? error2.message : String(error2)
3171
+ });
3172
+ }
3173
+ });
3174
+ });
3175
+ }
3176
+ async function executeToolCallsParallel(entries, batchTimeoutMs = DEFAULT_BATCH_TIMEOUT_MS) {
3177
+ if (entries.length === 0) return [];
3178
+ if (entries.length === 1) {
3179
+ return [await executeWithTimeout(entries[0])];
3180
+ }
3181
+ const promises = entries.map((entry) => executeWithTimeout(entry));
3182
+ if (batchTimeoutMs > 0) {
3183
+ const batchTimer = new Promise((resolve) => {
3184
+ setTimeout(() => {
3185
+ resolve(
3186
+ entries.map((entry) => ({
3187
+ call: entry.call,
3188
+ status: "timeout",
3189
+ error: `Batch timeout: ${batchTimeoutMs}ms exceeded`
3190
+ }))
3191
+ );
3192
+ }, batchTimeoutMs);
3193
+ });
3194
+ return Promise.race([Promise.all(promises), batchTimer]);
3195
+ }
3196
+ return Promise.all(promises);
3197
+ }
3198
+ async function executeToolCallsSequential(entries) {
3199
+ const results = [];
3200
+ for (const entry of entries) {
3201
+ results.push(await executeWithTimeout(entry));
3202
+ }
3203
+ return results;
3204
+ }
3205
+
3206
+ // src/tools/schemaToToolContent.ts
3207
+ function schemaToToolContent(schema) {
3208
+ const jsonSchema = schema.toJSONSchema?.() ?? {};
3209
+ const meta = Array.isArray(schema.meta) ? schema.meta?.[0] : void 0;
3210
+ const title = meta?.title || jsonSchema.title || "tool";
3211
+ const description = meta?.description || jsonSchema.description || "";
3212
+ const examples = meta?.examples || jsonSchema.examples || [];
3213
+ const props = jsonSchema.properties;
3214
+ const requiredArray = Array.isArray(jsonSchema.required) ? jsonSchema.required : [];
3215
+ let parameterLines = "";
3216
+ if (props) {
3217
+ parameterLines = Object.entries(props).map(([key, value]) => {
3218
+ const p = value;
3219
+ const type = p?.type || "string";
3220
+ const desc = p?.description || p?.title || "";
3221
+ const required = requiredArray.includes(key) ? "required" : "optional";
3222
+ return `- ${key} (${type}, ${required}): ${desc}`;
3223
+ }).join("\n");
3224
+ }
3225
+ const exampleSection = examples.map((example) => `- <tool_use name="${title}">${JSON.stringify(example)}</tool_use>`).join("\n");
3226
+ return `
3227
+ ## ${title}
3228
+ **Description**: ${description}
3229
+ **Parameters**:
3230
+ ${parameterLines}
3231
+
3232
+ **Examples**:
3233
+ ${exampleSection}
3234
+ `;
3235
+ }
3236
+
3237
+ // src/tools/defineTool.ts
3238
+ var MAX_TOOL_RESULT_CHARS = 32e3;
3239
+ var logger = {
3240
+ debug: (..._a) => {
3241
+ },
3242
+ warn: (...a) => {
3243
+ console.warn("[memeloop.defineTool]", ...a);
3244
+ },
3245
+ error: (...a) => {
3246
+ console.error("[memeloop.defineTool]", ...a);
3247
+ }
3248
+ };
3249
+ function defineTool(definition) {
3250
+ const {
3251
+ toolId,
3252
+ configSchema,
3253
+ llmToolSchemas,
3254
+ onProcessPrompts,
3255
+ onResponseComplete,
3256
+ onPostProcess
3257
+ } = definition;
3258
+ const parameterKey = `${toolId}Param`;
3259
+ const tool = (hooks) => {
3260
+ if (onProcessPrompts) {
3261
+ hooks.processPrompts.tapAsync(`${toolId}-processPrompts`, async (context, callback) => {
3262
+ try {
3263
+ const { toolConfig, prompts, messages, agentFrameworkContext } = context;
3264
+ if (toolConfig.toolId !== toolId) {
3265
+ callback();
3266
+ return;
3267
+ }
3268
+ if (toolConfig.enabled === false) {
3269
+ callback();
3270
+ return;
3271
+ }
3272
+ const rawConfig = toolConfig[parameterKey];
3273
+ if (!rawConfig) {
3274
+ callback();
3275
+ return;
3276
+ }
3277
+ const config = configSchema.parse(rawConfig);
3278
+ const handlerContext = {
3279
+ config,
3280
+ toolConfig,
3281
+ prompts,
3282
+ messages,
3283
+ agentFrameworkContext,
3284
+ findPrompt: (id) => findPromptById(prompts, id),
3285
+ injectToolList: (options) => {
3286
+ const target = findPromptById(prompts, options.targetId);
3287
+ if (!target) {
3288
+ logger.warn(`Target prompt not found for tool list injection`, {
3289
+ targetId: options.targetId,
3290
+ toolId
3291
+ });
3292
+ return;
3293
+ }
3294
+ const schemas = options.toolSchemas ?? (llmToolSchemas ? Object.values(llmToolSchemas) : []);
3295
+ const toolContent = schemas.map((schema) => schemaToToolContent(schema)).join("\n\n");
3296
+ const pluginIndex = context.pluginIndex;
3297
+ const source = pluginIndex !== void 0 ? ["plugins", toolConfig.id] : void 0;
3298
+ const toolPrompt = {
3299
+ id: `${toolId}-tool-list-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
3300
+ text: toolContent,
3301
+ caption: options.caption ?? `${definition.displayName} Tools`,
3302
+ enabled: true,
3303
+ source
3304
+ };
3305
+ if (options.position === "child") {
3306
+ if (!target.prompt.children) {
3307
+ target.prompt.children = [];
3308
+ }
3309
+ target.prompt.children.push(toolPrompt);
3310
+ } else if (options.position === "before") {
3311
+ target.parent.splice(target.index, 0, toolPrompt);
3312
+ } else {
3313
+ target.parent.splice(target.index + 1, 0, toolPrompt);
3314
+ }
3315
+ },
3316
+ injectContent: (options) => {
3317
+ const target = findPromptById(prompts, options.targetId);
3318
+ if (!target) {
3319
+ logger.warn(`Target prompt not found for content injection`, {
3320
+ targetId: options.targetId,
3321
+ toolId
3322
+ });
3323
+ return;
3324
+ }
3325
+ const pluginIndex = context.pluginIndex;
3326
+ const source = pluginIndex !== void 0 ? ["plugins", toolConfig.id] : void 0;
3327
+ const contentPrompt = {
3328
+ id: options.id ?? `${toolId}-content-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
3329
+ text: options.content,
3330
+ caption: options.caption ?? "Injected Content",
3331
+ enabled: true,
3332
+ source
3333
+ };
3334
+ if (options.position === "child") {
3335
+ if (!target.prompt.children) {
3336
+ target.prompt.children = [];
3337
+ }
3338
+ target.prompt.children.push(contentPrompt);
3339
+ } else if (options.position === "before") {
3340
+ target.parent.splice(target.index, 0, contentPrompt);
3341
+ } else {
3342
+ target.parent.splice(target.index + 1, 0, contentPrompt);
3343
+ }
3344
+ }
3345
+ };
3346
+ await onProcessPrompts(handlerContext);
3347
+ callback();
3348
+ } catch (error2) {
3349
+ logger.error(`Error in ${toolId} processPrompts handler`, error2);
3350
+ callback();
3351
+ }
3352
+ });
3353
+ }
3354
+ if (onResponseComplete) {
3355
+ hooks.responseComplete.tapAsync(`${toolId}-responseComplete`, async (context, callback) => {
3356
+ try {
3357
+ const {
3358
+ agentFrameworkContext,
3359
+ response,
3360
+ agentFrameworkConfig,
3361
+ requestId,
3362
+ toolConfig: directToolConfig
3363
+ } = context;
3364
+ const configuredToolConfig = agentFrameworkConfig?.plugins?.find(
3365
+ (p) => p.toolId === toolId
3366
+ );
3367
+ const ourToolConfig = configuredToolConfig ?? (directToolConfig?.toolId === toolId ? directToolConfig : void 0);
3368
+ if (!ourToolConfig) {
3369
+ callback();
3370
+ return;
3371
+ }
3372
+ if (ourToolConfig.enabled === false) {
3373
+ callback();
3374
+ return;
3375
+ }
3376
+ if (response.status !== "done" || !response.content) {
3377
+ callback();
3378
+ return;
3379
+ }
3380
+ const { calls: allCalls, parallel: isParallel } = matchAllToolCallings(response.content);
3381
+ const toolCall = allCalls.length > 0 ? allCalls[0] : null;
3382
+ const rawConfig = ourToolConfig[parameterKey];
3383
+ let config;
3384
+ if (rawConfig) {
3385
+ try {
3386
+ config = configSchema.parse(rawConfig);
3387
+ } catch (parseError) {
3388
+ logger.warn(`Failed to parse config for ${toolId}`, parseError);
3389
+ }
3390
+ }
3391
+ const persist = agentFrameworkContext.persistAgentMessage;
3392
+ const handlerContext = {
3393
+ config,
3394
+ toolConfig: ourToolConfig,
3395
+ messages: agentFrameworkContext.agent.messages,
3396
+ agentFrameworkContext,
3397
+ response,
3398
+ toolCall,
3399
+ allToolCalls: allCalls,
3400
+ isParallel,
3401
+ agentFrameworkConfig,
3402
+ hooks,
3403
+ requestId,
3404
+ findPrompt: () => void 0,
3405
+ injectToolList: () => {
3406
+ logger.warn("injectToolList is not available in response phase");
3407
+ },
3408
+ injectContent: () => {
3409
+ logger.warn("injectContent is not available in response phase");
3410
+ },
3411
+ executeToolCall: async (toolName, executor) => {
3412
+ const toolNameString = String(toolName);
3413
+ if (!toolCall || toolCall.toolId !== toolNameString) {
3414
+ return false;
3415
+ }
3416
+ const toolSchema = llmToolSchemas?.[toolName];
3417
+ if (!toolSchema) {
3418
+ logger.error(`No schema found for tool: ${toolNameString}`);
3419
+ return false;
3420
+ }
3421
+ try {
3422
+ const validatedParameters = toolSchema.parse(toolCall.parameters);
3423
+ const approvalConfig = ourToolConfig.approval;
3424
+ const decision = evaluateApproval(
3425
+ approvalConfig,
3426
+ toolNameString,
3427
+ validatedParameters
3428
+ );
3429
+ if (decision === "deny") {
3430
+ handlerContext.addToolResult({
3431
+ toolName: toolNameString,
3432
+ parameters: validatedParameters,
3433
+ result: "Tool execution denied by approval policy.",
3434
+ isError: true,
3435
+ duration: 2
3436
+ });
3437
+ handlerContext.yieldToSelf();
3438
+ return true;
3439
+ }
3440
+ if (decision === "pending") {
3441
+ const approvalId = `approval-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
3442
+ const userDecision = await requestApproval({
3443
+ approvalId,
3444
+ agentId: agentFrameworkContext.agent.id,
3445
+ toolName: toolNameString,
3446
+ parameters: validatedParameters,
3447
+ originalText: toolCall.originalText,
3448
+ created: /* @__PURE__ */ new Date()
3449
+ });
3450
+ if (userDecision === "deny") {
3451
+ handlerContext.addToolResult({
3452
+ toolName: toolNameString,
3453
+ parameters: validatedParameters,
3454
+ result: "Tool execution denied by user.",
3455
+ isError: true,
3456
+ duration: 2
3457
+ });
3458
+ handlerContext.yieldToSelf();
3459
+ return true;
3460
+ }
3461
+ }
3462
+ const result = await executor(validatedParameters);
3463
+ const toolResultDuration = config?.toolResultDuration ?? 1;
3464
+ handlerContext.addToolResult({
3465
+ toolName: toolNameString,
3466
+ parameters: validatedParameters,
3467
+ result: result.success ? result.data ?? "Success" : result.error ?? "Unknown error",
3468
+ isError: !result.success,
3469
+ duration: toolResultDuration
3470
+ });
3471
+ handlerContext.yieldToSelf();
3472
+ await hooks.toolExecuted.promise({
3473
+ agentFrameworkContext,
3474
+ toolResult: result,
3475
+ toolInfo: {
3476
+ toolId: toolNameString,
3477
+ parameters: validatedParameters,
3478
+ originalText: toolCall.originalText
3479
+ },
3480
+ requestId
3481
+ });
3482
+ return true;
3483
+ } catch (error2) {
3484
+ logger.error(`Tool execution failed: ${toolNameString}`, error2);
3485
+ handlerContext.addToolResult({
3486
+ toolName: toolNameString,
3487
+ parameters: toolCall.parameters,
3488
+ result: error2 instanceof Error ? error2.message : String(error2),
3489
+ isError: true,
3490
+ duration: 2
3491
+ });
3492
+ handlerContext.yieldToSelf();
3493
+ await hooks.toolExecuted.promise({
3494
+ agentFrameworkContext,
3495
+ toolResult: {
3496
+ success: false,
3497
+ error: error2 instanceof Error ? error2.message : String(error2)
3498
+ },
3499
+ toolInfo: {
3500
+ toolId: toolNameString,
3501
+ parameters: toolCall.parameters || {}
3502
+ }
3503
+ });
3504
+ return true;
3505
+ }
3506
+ },
3507
+ addToolResult: (options) => {
3508
+ const now = /* @__PURE__ */ new Date();
3509
+ let resultContent = options.result;
3510
+ if (resultContent.length > MAX_TOOL_RESULT_CHARS) {
3511
+ const truncated = resultContent.slice(0, MAX_TOOL_RESULT_CHARS);
3512
+ resultContent = `${truncated}
3513
+
3514
+ [... truncated \u2014 result was ${options.result.length} chars, showing first ${MAX_TOOL_RESULT_CHARS}]`;
3515
+ }
3516
+ const toolResultText = `<functions_result>
3517
+ Tool: ${options.toolName}
3518
+ Parameters: ${JSON.stringify(options.parameters)}
3519
+ ${options.isError ? "Error" : "Result"}: ${resultContent}
3520
+ </functions_result>`;
3521
+ const toolResultMessage = {
3522
+ messageId: `tool-result-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
3523
+ conversationId: agentFrameworkContext.agent.id,
3524
+ originNodeId: "local",
3525
+ timestamp: now.getTime(),
3526
+ lamportClock: 0,
3527
+ role: "tool",
3528
+ content: toolResultText,
3529
+ duration: options.duration ?? 1,
3530
+ metadata: {
3531
+ isToolResult: true,
3532
+ isError: options.isError ?? false,
3533
+ toolId: options.toolName,
3534
+ toolParameters: options.parameters,
3535
+ isPersisted: false,
3536
+ isComplete: true
3537
+ }
3538
+ };
3539
+ agentFrameworkContext.agent.messages.push(toolResultMessage);
3540
+ const aiMessages = agentFrameworkContext.agent.messages.filter(
3541
+ (m) => m.role === "assistant"
3542
+ );
3543
+ if (aiMessages.length > 0) {
3544
+ const latestAiMessage = aiMessages[aiMessages.length - 1];
3545
+ if (latestAiMessage.content === response.content && !latestAiMessage.metadata?.containsToolCall) {
3546
+ latestAiMessage.duration = 1;
3547
+ latestAiMessage.metadata = {
3548
+ ...latestAiMessage.metadata,
3549
+ containsToolCall: true,
3550
+ toolId: options.toolName,
3551
+ isPersisted: true
3552
+ };
3553
+ if (persist) {
3554
+ void (async () => {
3555
+ try {
3556
+ await persist(latestAiMessage);
3557
+ } catch (error2) {
3558
+ logger.warn("Failed to persist AI message with tool call", {
3559
+ error: error2,
3560
+ messageId: latestAiMessage.messageId
3561
+ });
3562
+ }
3563
+ })();
3564
+ }
3565
+ }
3566
+ }
3567
+ if (persist) {
3568
+ void (async () => {
3569
+ try {
3570
+ await persist(toolResultMessage);
3571
+ toolResultMessage.metadata = {
3572
+ ...toolResultMessage.metadata,
3573
+ isPersisted: true
3574
+ };
3575
+ } catch (error2) {
3576
+ logger.warn("Failed to persist tool result", {
3577
+ error: error2,
3578
+ messageId: toolResultMessage.messageId
3579
+ });
3580
+ }
3581
+ })();
3582
+ }
3583
+ },
3584
+ yieldToSelf: () => {
3585
+ const context_ = context;
3586
+ if (!context_.actions) {
3587
+ context_.actions = {};
3588
+ }
3589
+ context_.actions.yieldNextRoundTo = "self";
3590
+ },
3591
+ yieldToHuman: () => {
3592
+ const context_ = context;
3593
+ if (!context_.actions) {
3594
+ context_.actions = {};
3595
+ }
3596
+ context_.actions.yieldNextRoundTo = "human";
3597
+ },
3598
+ executeAllMatchingToolCalls: async (toolName, executor, options) => {
3599
+ const toolNameString = String(toolName);
3600
+ const matchingCalls = allCalls.filter((call) => call.toolId === toolNameString);
3601
+ if (matchingCalls.length === 0) return 0;
3602
+ const toolSchema = llmToolSchemas?.[toolName];
3603
+ if (!toolSchema) {
3604
+ logger.error(`No schema found for tool: ${toolNameString}`);
3605
+ return 0;
3606
+ }
3607
+ const toolResultDuration = config?.toolResultDuration ?? 1;
3608
+ const entries = [];
3609
+ const approvalConfig = ourToolConfig.approval;
3610
+ const batchDecision = evaluateApproval(
3611
+ approvalConfig,
3612
+ toolNameString,
3613
+ matchingCalls[0]?.parameters ?? {}
3614
+ );
3615
+ if (batchDecision === "deny") {
3616
+ for (const call of matchingCalls) {
3617
+ handlerContext.addToolResult({
3618
+ toolName: toolNameString,
3619
+ parameters: call.parameters,
3620
+ result: "Tool execution denied by approval policy.",
3621
+ isError: true,
3622
+ duration: toolResultDuration
3623
+ });
3624
+ }
3625
+ handlerContext.yieldToSelf();
3626
+ return matchingCalls.length;
3627
+ }
3628
+ if (batchDecision === "pending") {
3629
+ const approvalId = `approval-batch-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
3630
+ const userDecision = await requestApproval({
3631
+ approvalId,
3632
+ agentId: agentFrameworkContext.agent.id,
3633
+ toolName: toolNameString,
3634
+ parameters: {
3635
+ _batchSize: matchingCalls.length,
3636
+ _firstCallParams: matchingCalls[0]?.parameters
3637
+ },
3638
+ created: /* @__PURE__ */ new Date()
3639
+ });
3640
+ if (userDecision === "deny") {
3641
+ for (const call of matchingCalls) {
3642
+ handlerContext.addToolResult({
3643
+ toolName: toolNameString,
3644
+ parameters: call.parameters,
3645
+ result: "Tool execution denied by user.",
3646
+ isError: true,
3647
+ duration: toolResultDuration
3648
+ });
3649
+ }
3650
+ handlerContext.yieldToSelf();
3651
+ return matchingCalls.length;
3652
+ }
3653
+ }
3654
+ for (const call of matchingCalls) {
3655
+ try {
3656
+ const validatedParameters = toolSchema.parse(call.parameters);
3657
+ entries.push({
3658
+ call,
3659
+ executor: async () => executor(validatedParameters),
3660
+ timeoutMs: options?.timeoutMs
3661
+ });
3662
+ } catch (validationError) {
3663
+ handlerContext.addToolResult({
3664
+ toolName: toolNameString,
3665
+ parameters: call.parameters,
3666
+ result: `Parameter validation failed: ${validationError instanceof Error ? validationError.message : String(validationError)}`,
3667
+ isError: true,
3668
+ duration: toolResultDuration
3669
+ });
3670
+ }
3671
+ }
3672
+ if (entries.length === 0) return matchingCalls.length;
3673
+ let results;
3674
+ if (isParallel) {
3675
+ results = await executeToolCallsParallel(entries);
3676
+ } else {
3677
+ results = await executeToolCallsSequential(entries);
3678
+ }
3679
+ for (const result of results) {
3680
+ const isError = result.status !== "fulfilled" || result.result !== void 0 && !result.result.success;
3681
+ const resultText = result.status === "timeout" ? result.error ?? "Tool execution timed out" : result.status === "rejected" ? result.error ?? "Tool execution failed" : result.result?.success ? result.result.data ?? "Success" : result.result?.error ?? "Unknown error";
3682
+ handlerContext.addToolResult({
3683
+ toolName: toolNameString,
3684
+ parameters: result.call.parameters,
3685
+ result: resultText,
3686
+ isError,
3687
+ duration: toolResultDuration
3688
+ });
3689
+ await hooks.toolExecuted.promise({
3690
+ agentFrameworkContext,
3691
+ toolResult: result.result ?? { success: false, error: resultText },
3692
+ toolInfo: {
3693
+ toolId: toolNameString,
3694
+ parameters: result.call.parameters ?? {},
3695
+ originalText: result.call.originalText
3696
+ },
3697
+ requestId
3698
+ });
3699
+ }
3700
+ handlerContext.yieldToSelf();
3701
+ return matchingCalls.length;
3702
+ }
3703
+ };
3704
+ await onResponseComplete(handlerContext);
3705
+ callback();
3706
+ } catch (error2) {
3707
+ logger.error(`Error in ${toolId} responseComplete handler`, error2);
3708
+ callback();
3709
+ }
3710
+ });
3711
+ }
3712
+ if (onPostProcess) {
3713
+ hooks.postProcess.tapAsync(
3714
+ `${toolId}-postProcess`,
3715
+ async (context, callback) => {
3716
+ try {
3717
+ const { toolConfig, prompts, messages, agentFrameworkContext, llmResponse, responses } = context;
3718
+ if (toolConfig.toolId !== toolId) {
3719
+ callback();
3720
+ return;
3721
+ }
3722
+ if (toolConfig.enabled === false) {
3723
+ callback();
3724
+ return;
3725
+ }
3726
+ const rawConfig = toolConfig[parameterKey];
3727
+ if (!rawConfig) {
3728
+ callback();
3729
+ return;
3730
+ }
3731
+ const config = configSchema.parse(rawConfig);
3732
+ const handlerContext = {
3733
+ config,
3734
+ toolConfig,
3735
+ prompts,
3736
+ messages,
3737
+ agentFrameworkContext,
3738
+ llmResponse,
3739
+ responses,
3740
+ findPrompt: (id) => findPromptById(prompts, id),
3741
+ injectToolList: () => {
3742
+ logger.warn("injectToolList is not recommended in postProcess phase");
3743
+ },
3744
+ injectContent: () => {
3745
+ logger.warn("injectContent is not recommended in postProcess phase");
3746
+ }
3747
+ };
3748
+ await onPostProcess(handlerContext);
3749
+ callback();
3750
+ } catch (error2) {
3751
+ logger.error(`Error in ${toolId} postProcess handler`, error2);
3752
+ callback();
3753
+ }
3754
+ }
3755
+ );
3756
+ }
3757
+ };
3758
+ getActivePluginRegistry().set(toolId, tool);
3759
+ return {
3760
+ tool,
3761
+ toolId,
3762
+ configSchema,
3763
+ llmToolSchemas,
3764
+ displayName: definition.displayName,
3765
+ description: definition.description
3766
+ };
3767
+ }
3768
+
3769
+ // src/tools/hostAgentTools.ts
3770
+ function isPluginConfig(value) {
3771
+ return typeof value === "object" && value !== null;
3772
+ }
3773
+ function mergeAgentToolsIntoFrameworkConfig(frameworkConfig, agentTools) {
3774
+ const baseConfig = { ...frameworkConfig ?? {} };
3775
+ const rawPlugins = Array.isArray(baseConfig.plugins) ? baseConfig.plugins : [];
3776
+ const pluginByToolId = /* @__PURE__ */ new Map();
3777
+ const pluginWithoutToolId = [];
3778
+ for (const plugin of rawPlugins) {
3779
+ if (isPluginConfig(plugin) && typeof plugin.toolId === "string" && plugin.toolId.length > 0) {
3780
+ pluginByToolId.set(plugin.toolId, plugin);
3781
+ } else {
3782
+ pluginWithoutToolId.push(plugin);
3783
+ }
3784
+ }
3785
+ for (const tool of agentTools ?? []) {
3786
+ if (!tool.toolId) continue;
3787
+ pluginByToolId.set(tool.toolId, {
3788
+ id: `${tool.toolId}-agent-tool`,
3789
+ toolId: tool.toolId,
3790
+ enabled: tool.enabled ?? true,
3791
+ ...tool.parameters ?? {}
3792
+ });
3793
+ }
3794
+ return {
3795
+ ...baseConfig,
3796
+ prompts: Array.isArray(baseConfig.prompts) ? baseConfig.prompts : [],
3797
+ response: Array.isArray(baseConfig.response) ? baseConfig.response : void 0,
3798
+ plugins: [...pluginWithoutToolId, ...pluginByToolId.values()]
3799
+ };
3800
+ }
3801
+
3802
+ // src/tools/toolRegistry.ts
3803
+ var ToolDefinitionRegistry = class {
3804
+ registry = /* @__PURE__ */ new Map();
3805
+ registerToolDefinition(definition) {
3806
+ const toolDefinition = defineTool(definition);
3807
+ registerToolParameterSchema(toolDefinition.toolId, toolDefinition.configSchema, {
3808
+ displayName: toolDefinition.displayName,
3809
+ description: toolDefinition.description
3810
+ });
3811
+ this.registry.set(toolDefinition.toolId, toolDefinition);
3812
+ return toolDefinition;
3813
+ }
3814
+ getAllToolDefinitions() {
3815
+ return this.registry;
3816
+ }
3817
+ getToolDefinition(toolId) {
3818
+ return this.registry.get(toolId);
3819
+ }
3820
+ };
3821
+ var defaultToolDefinitionRegistry = new ToolDefinitionRegistry();
3822
+ function getDefaultToolDefinitionRegistry() {
3823
+ return defaultToolDefinitionRegistry;
3824
+ }
3825
+ function registerToolDefinition(definition) {
3826
+ return defaultToolDefinitionRegistry.registerToolDefinition(definition);
3827
+ }
3828
+ function getAllToolDefinitions() {
3829
+ return defaultToolDefinitionRegistry.getAllToolDefinitions();
3830
+ }
3831
+ function getToolDefinition(toolId) {
3832
+ return defaultToolDefinitionRegistry.getToolDefinition(toolId);
3833
+ }
3834
+ export {
3835
+ AGENT_AGENT_LOOP_ID,
3836
+ AGENT_TOOL_LOOP_ID,
3837
+ ALL_CATEGORIES,
3838
+ ASK_QUESTION_TOOL_ID,
3839
+ ASK_USER_QUESTION_TOOL_ID,
3840
+ AgentCreationController,
3841
+ AgentDefinitionEditorController,
3842
+ AgentProfileRegistry,
3843
+ AgentSessionController,
3844
+ BUILTIN_AGENT_AGENT_LOOP_QUALITY_GATE_SCRIPT_ID,
3845
+ BUILTIN_AGENT_PROFILES,
3846
+ BUILTIN_AGENT_TOOL_LOOP_DEFAULT_SCRIPT_ID,
3847
+ BUILTIN_TOOL_PLUGIN_IDS,
3848
+ ChatSyncEngine,
3849
+ CloudDeviceAuthorizer,
3850
+ HookRegistry,
3851
+ IMChannelManager,
3852
+ IM_SESSION_TOOL_IDS,
3853
+ LIBP2P_RPC_REQUEST_TYPE,
3854
+ LIBP2P_RPC_RESPONSE_TYPE,
3855
+ LIBP2P_SYNC_REQUEST_TYPE,
3856
+ LIBP2P_SYNC_RESPONSE_TYPE,
3857
+ Libp2pDeviceNetworkService,
3858
+ Libp2pDeviceSyncTransport,
3859
+ LocalTrustDeviceAuthorizer,
3860
+ MEMELOOP_STRUCTURED_TOOL_KEY,
3861
+ MemoryDeviceNetworkService,
3862
+ PERMISSIONS_TABLE_DDL,
3863
+ PLUGIN_ASK_QUESTION,
3864
+ PLUGIN_ASK_USER_QUESTION,
3865
+ PLUGIN_MCP_CLIENT,
3866
+ PLUGIN_MCP_FORWARD,
3867
+ PLUGIN_REMOTE_AGENT,
3868
+ PLUGIN_SPAWN_AGENT,
3869
+ PLUGIN_TASK,
3870
+ PLUGIN_TODO_WRITE,
3871
+ PeerNodeSyncAdapter,
3872
+ PromptPreviewController,
3873
+ ProviderRegistry,
3874
+ SessionStorage,
3875
+ TODO_WRITE_TOOL_ID,
3876
+ TextMessageRenderer,
3877
+ TokenTracker,
3878
+ ToolDefinitionRegistry,
3879
+ __clearTodoStore,
3880
+ askQuestionConfigSchema,
3881
+ askQuestionImpl,
3882
+ askUserQuestionConfigSchema,
3883
+ askUserQuestionImpl,
3884
+ attachmentBlobFromWire,
3885
+ autoCompact,
3886
+ buildAgent,
3887
+ buildDeviceBindingMessage,
3888
+ buildDeviceConnectionGrantMessage,
3889
+ buildDeviceRelayReservationTokenMessage,
3890
+ buildMemeloopFileUri,
3891
+ buildMemeloopUri,
3892
+ builtinAgentAgentLoopSources,
3893
+ builtinAgentToolLoopSources,
3894
+ cancelPendingApprovals,
3895
+ checkPermission,
3896
+ clearHooks,
3897
+ clearPluginRegistrations,
3898
+ compactMessages,
3899
+ createAgentAgentLoopDefinition,
3900
+ createAgentFrameworkHooks,
3901
+ createAgentInstanceFromDefinition,
3902
+ createAgentLoopRunner,
3903
+ createAgentRuntimeDeviceRpcHandler,
3904
+ createAgentToolLoopDefinition,
3905
+ createAgentToolLoopRunner,
3906
+ createChatMessage,
3907
+ createCheckpointRecord,
3908
+ createDeviceIdentity,
3909
+ createFetchLLMProvider,
3910
+ createHooksWithPlugins,
3911
+ createInstanceDeltaFromDefinition,
3912
+ createMemeLoopRuntime,
3913
+ createPluginAPI,
3914
+ decodeAttachmentBlobRpc,
3915
+ decodePublicKeyMultibase,
3916
+ defineTool,
3917
+ encodePublicKeyMultibase,
3918
+ evaluateApproval,
3919
+ executeHooks,
3920
+ executeToolCallsParallel,
3921
+ executeToolCallsSequential,
3922
+ exploreAgent,
3923
+ extractMemeloopStructuredToolPayload,
3924
+ findPromptById,
3925
+ flattenPrompts,
3926
+ getActivePluginRegistry,
3927
+ getAgentAgentLoopId,
3928
+ getAgentProfileRegistry,
3929
+ getAllToolDefinitions,
3930
+ getBuiltinAgentAgentLoopSource,
3931
+ getBuiltinAgentToolLoopSource,
3932
+ getBuiltinLoopProfile,
3933
+ getBuiltinLoopProfiles,
3934
+ getBuiltinToolPlugins,
3935
+ getCategoryConfig,
3936
+ getDefaultHookRegistry,
3937
+ getDefaultToolDefinitionRegistry,
3938
+ getHookCount,
3939
+ getLoadedPlugin,
3940
+ getLoopRegistry,
3941
+ getMcpClientToolId,
3942
+ getMcpForwardToolId,
3943
+ getPendingApprovals,
3944
+ getPluginRegistrations,
3945
+ getRemoteAgentToolId,
3946
+ getSpawnAgentToolId,
3947
+ getTaskToolId,
3948
+ getToolDefinition,
3949
+ getToolMetadata,
3950
+ getToolParameterSchema,
3951
+ gitProxyTargetBlockReason,
3952
+ hasHooks,
3953
+ imPlatformMaxMessageChars,
3954
+ imSwitchConversation,
3955
+ imToolDefinitions,
3956
+ isConversationMeta,
3957
+ isLibp2pRpcRequest,
3958
+ isLibp2pRpcResponse,
3959
+ isLibp2pSyncRequest,
3960
+ isLibp2pSyncResponse,
3961
+ isPluginLoaded,
3962
+ isTaskCategory,
3963
+ isUserInitiatedConversation,
3964
+ librarianAgent,
3965
+ listPlugins,
3966
+ listRegisteredHookTypes,
3967
+ loadAgentAgentLoopScript,
3968
+ loadAgentToolLoopScript,
3969
+ loadPluginModule,
3970
+ loadPluginModules,
3971
+ loadUserPermissions,
3972
+ matchAllToolCallings,
3973
+ matchPattern,
3974
+ matchToolCalling,
3975
+ mcpClientConfigSchema,
3976
+ mcpClientImpl,
3977
+ mcpForwardConfigSchema,
3978
+ mcpForwardImpl,
3979
+ mergeAgentToolsIntoFrameworkConfig,
3980
+ mergePermissionSets,
3981
+ onApprovalRequest,
3982
+ oracleAgent,
3983
+ parseCheckpointRecord,
3984
+ parseMemeloopUri,
3985
+ pickAdapter,
3986
+ planAgent,
3987
+ pluginRegistry,
3988
+ promptConcatStream,
3989
+ registerBuiltinLoops,
3990
+ registerBuiltinPromptPlugins,
3991
+ registerBuiltinToolPlugins,
3992
+ registerBuiltinTools,
3993
+ registerHook,
3994
+ registerImSessionBuiltinTools,
3995
+ registerPluginHooks,
3996
+ registerPluginTools,
3997
+ registerToolDefinition,
3998
+ registerToolParameterSchema,
3999
+ remoteAgentConfigSchema,
4000
+ remoteAgentImpl,
4001
+ remoteAgentListImpl,
4002
+ requestApproval,
4003
+ resetAgentProfileRegistry,
4004
+ resetLoopRegistry,
4005
+ resolveAgentToolLoopTerminalState,
4006
+ resolveApproval,
4007
+ resolveCategory,
4008
+ resolvePromptPluginMap,
4009
+ resolveQuestionAnswer,
4010
+ runAgentToolLoopTurn,
4011
+ runPostProcessHooks,
4012
+ runProcessPromptsHooks,
4013
+ runResponseCompleteHooks,
4014
+ runToolExecutedHooks,
4015
+ runWithPluginRegistry,
4016
+ saveUserPermissions,
4017
+ schemaToToolContent,
4018
+ serializeCheckpointRecord,
4019
+ shouldCompact,
4020
+ signDeviceBinding,
4021
+ spawnAgentConfigSchema,
4022
+ spawnAgentImpl,
4023
+ streamRuntimeAgentReplyToIm,
4024
+ syncCloudDevices,
4025
+ taskToolConfigSchema,
4026
+ taskToolImpl,
4027
+ tiddlerToAgentDefinition,
4028
+ todoWriteConfigSchema,
4029
+ todoWriteImpl,
4030
+ truncateToolSummary,
4031
+ tryHandleImSlashCommand,
4032
+ unloadAllPlugins,
4033
+ unloadPlugin,
4034
+ unregisterHook,
4035
+ validatePluginManifest,
4036
+ verifyDeviceBinding,
4037
+ verifyDeviceConnectionGrant,
4038
+ verifyDeviceRelayReservationToken
4039
+ };
4040
+ //# sourceMappingURL=index.js.map