@runfusion/fusion 0.21.0 → 0.22.0

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 (58) hide show
  1. package/dist/bin.js +1991 -993
  2. package/dist/client/assets/AgentDetailView-BKKpbp1S.js +18 -0
  3. package/dist/client/assets/AgentDetailView-CeO_1MK7.css +1 -0
  4. package/dist/client/assets/AgentsView-BRXFmrcJ.js +527 -0
  5. package/dist/client/assets/AgentsView-Bs03ptrd.css +1 -0
  6. package/dist/client/assets/ChatView-D7L2e_qu.js +1 -0
  7. package/dist/client/assets/DevServerView-l8RCyL2k.js +1 -0
  8. package/dist/client/assets/DirectoryPicker-CS1dwqcC.js +1 -0
  9. package/dist/client/assets/DocumentsView-DmthQWDZ.js +1 -0
  10. package/dist/client/assets/{InsightsView-CqDethVs.js → InsightsView-DvXpMKmH.js} +2 -2
  11. package/dist/client/assets/{MemoryView-BLIm9Vr7.js → MemoryView-CPwlKnUI.js} +2 -2
  12. package/dist/client/assets/{NodesView-DEXvp3WT.js → NodesView-BLlfUfsy.js} +3 -3
  13. package/dist/client/assets/{PiExtensionsManager-C2YjI9o2.js → PiExtensionsManager-j8rPXqmB.js} +2 -2
  14. package/dist/client/assets/PluginManager-pW6RMz5z.js +1 -0
  15. package/dist/client/assets/ResearchView-D9DNJYDq.js +1 -0
  16. package/dist/client/assets/{RoadmapsView-DPcfX5MS.js → RoadmapsView-Djc_X35v.js} +2 -2
  17. package/dist/client/assets/SettingsModal-WGCF_pk8.js +31 -0
  18. package/dist/client/assets/{SettingsModal-BRNAPR1u.js → SettingsModal-fxvTFLtR.js} +1 -1
  19. package/dist/client/assets/SetupWizardModal-tG_MF_nA.js +1 -0
  20. package/dist/client/assets/SkillsView-Ddf0YL8z.js +1 -0
  21. package/dist/client/assets/agentSkills-DDHJnrkn.css +1 -0
  22. package/dist/client/assets/agentSkills-EwIwBlG8.js +1 -0
  23. package/dist/client/assets/folder-open-BiJpmnaT.js +6 -0
  24. package/dist/client/assets/index-D6ebxTPF.css +1 -0
  25. package/dist/client/assets/index-DYDLmOcK.js +694 -0
  26. package/dist/client/assets/{star-B314SwLA.js → star-BwRZmiuZ.js} +2 -2
  27. package/dist/client/assets/upload-D4NwZhPp.js +6 -0
  28. package/dist/client/assets/{users-Bu_ltePs.js → users-DNISDtI1.js} +2 -2
  29. package/dist/client/index.html +2 -2
  30. package/dist/client/version.json +1 -1
  31. package/dist/droid-cli/package.json +1 -1
  32. package/dist/extension.js +1154 -401
  33. package/dist/pi-claude-cli/package.json +1 -1
  34. package/dist/plugins/fusion-plugin-dependency-graph/package.json +1 -1
  35. package/dist/plugins/fusion-plugin-hermes-runtime/bundled.js +480 -0
  36. package/dist/plugins/fusion-plugin-hermes-runtime/manifest.json +14 -0
  37. package/dist/plugins/fusion-plugin-hermes-runtime/package.json +11 -0
  38. package/dist/plugins/fusion-plugin-openclaw-runtime/bundled.js +369 -0
  39. package/dist/plugins/fusion-plugin-openclaw-runtime/manifest.json +14 -0
  40. package/dist/plugins/fusion-plugin-openclaw-runtime/package.json +11 -0
  41. package/dist/plugins/fusion-plugin-paperclip-runtime/bundled.js +966 -0
  42. package/dist/plugins/fusion-plugin-paperclip-runtime/manifest.json +15 -0
  43. package/dist/plugins/fusion-plugin-paperclip-runtime/package.json +11 -0
  44. package/package.json +2 -1
  45. package/skill/fusion/references/engine-tools.md +1 -1
  46. package/dist/client/assets/AgentDetailView-CUtWvXBn.css +0 -1
  47. package/dist/client/assets/AgentDetailView-Dg7Qa1rG.js +0 -18
  48. package/dist/client/assets/ChatView-ODq-kBk6.js +0 -1
  49. package/dist/client/assets/DevServerView-6PS9Lvl7.js +0 -1
  50. package/dist/client/assets/DirectoryPicker-B3dza2Dq.js +0 -1
  51. package/dist/client/assets/DocumentsView-Bu9YYlki.js +0 -1
  52. package/dist/client/assets/PluginManager-Dnf-LhYw.js +0 -1
  53. package/dist/client/assets/ResearchView-Z0TZ7WGo.js +0 -1
  54. package/dist/client/assets/SettingsModal-B6RN9VYe.js +0 -31
  55. package/dist/client/assets/SetupWizardModal-BFc3xID2.js +0 -1
  56. package/dist/client/assets/SkillsView-CipGahOR.js +0 -1
  57. package/dist/client/assets/index-Df1bHDY4.css +0 -1
  58. package/dist/client/assets/index-NFptaeUQ.js +0 -1222
@@ -0,0 +1,966 @@
1
+ // ../plugin-sdk/dist/index.js
2
+ function definePlugin(plugin2) {
3
+ return plugin2;
4
+ }
5
+
6
+ // ../../plugins/fusion-plugin-paperclip-runtime/dist/paperclip-client.js
7
+ var ConflictError = class extends Error {
8
+ status = 409;
9
+ constructor(message) {
10
+ super(message);
11
+ this.name = "ConflictError";
12
+ }
13
+ };
14
+ function normalizeApiUrl(url) {
15
+ return url.replace(/\/+$/, "");
16
+ }
17
+ function buildUrl(apiUrl, path, query) {
18
+ const base = normalizeApiUrl(apiUrl);
19
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
20
+ const qs = query && query.size > 0 ? `?${query.toString()}` : "";
21
+ return `${base}/api${normalizedPath}${qs}`;
22
+ }
23
+ function buildHeaders(apiKey, extra) {
24
+ const headers = {
25
+ Accept: "application/json",
26
+ ...extra
27
+ };
28
+ if (apiKey) {
29
+ headers.Authorization = `Bearer ${apiKey}`;
30
+ }
31
+ return headers;
32
+ }
33
+ async function parseJsonBody(response) {
34
+ const raw = await response.text();
35
+ if (raw.trim() === "") {
36
+ return { value: void 0, raw };
37
+ }
38
+ try {
39
+ return { value: JSON.parse(raw), raw };
40
+ } catch {
41
+ throw new Error(`Paperclip API ${response.status} ${response.statusText}: invalid JSON response body`);
42
+ }
43
+ }
44
+ function toErrorMessage(status, statusText, body, raw) {
45
+ if (body && typeof body === "object") {
46
+ const b = body;
47
+ if (typeof b.error === "string")
48
+ return b.error;
49
+ if (typeof b.message === "string")
50
+ return b.message;
51
+ }
52
+ if (raw.trim() !== "")
53
+ return raw.slice(0, 200).trim();
54
+ return `${status} ${statusText}`.trim();
55
+ }
56
+ async function request(apiUrl, path, options) {
57
+ const method = options?.method ?? "GET";
58
+ const url = buildUrl(apiUrl, path, options?.query);
59
+ const headers = buildHeaders(options?.apiKey);
60
+ let bodyStr;
61
+ if (options && "body" in options && options.body !== void 0) {
62
+ headers["Content-Type"] = "application/json";
63
+ bodyStr = JSON.stringify(options.body);
64
+ }
65
+ let response;
66
+ try {
67
+ response = await fetch(url, { method, headers, body: bodyStr });
68
+ } catch (err) {
69
+ const reason = err instanceof Error ? err.message : String(err);
70
+ throw new Error(`Paperclip API network error (${method} ${url}): ${reason}`);
71
+ }
72
+ const { value, raw } = await parseJsonBody(response);
73
+ if (!response.ok) {
74
+ const msg = toErrorMessage(response.status, response.statusText, value, raw);
75
+ const full = `Paperclip API ${response.status} (${method} ${path}): ${msg}`;
76
+ if (response.status === 409)
77
+ throw new ConflictError(full);
78
+ throw new Error(full);
79
+ }
80
+ return value;
81
+ }
82
+ function getSettingString(settings, key) {
83
+ const v = settings?.[key];
84
+ return typeof v === "string" && v.trim() !== "" ? v.trim() : void 0;
85
+ }
86
+ function resolvePaperclipConfig(settings) {
87
+ const apiUrl = normalizeApiUrl(getSettingString(settings, "apiUrl") ?? process.env.PAPERCLIP_API_URL?.trim() ?? "http://localhost:3100");
88
+ const apiKey = getSettingString(settings, "apiKey") ?? (process.env.PAPERCLIP_API_KEY?.trim() || void 0);
89
+ const agentId = getSettingString(settings, "agentId") ?? process.env.PAPERCLIP_AGENT_ID?.trim() ?? void 0;
90
+ const companyId = getSettingString(settings, "companyId") ?? process.env.PAPERCLIP_COMPANY_ID?.trim() ?? void 0;
91
+ const mode = getSettingString(settings, "mode") ?? process.env.PAPERCLIP_RUNTIME_MODE?.trim() ?? "rolling-issue";
92
+ const transportRaw = getSettingString(settings, "transport") ?? process.env.PAPERCLIP_TRANSPORT?.trim() ?? "api";
93
+ const transport = transportRaw === "cli" ? "cli" : "api";
94
+ const cliBinaryPath = getSettingString(settings, "cliBinaryPath") ?? process.env.PAPERCLIPAI_BIN?.trim() ?? "paperclipai";
95
+ const cliConfigPath = getSettingString(settings, "cliConfigPath") ?? process.env.PAPERCLIP_CLI_CONFIG?.trim() ?? void 0;
96
+ const parentIssueId = getSettingString(settings, "parentIssueId") ?? process.env.PAPERCLIP_PARENT_ISSUE_ID?.trim() ?? void 0;
97
+ const projectId = getSettingString(settings, "projectId") ?? process.env.PAPERCLIP_PROJECT_ID?.trim() ?? void 0;
98
+ const goalId = getSettingString(settings, "goalId") ?? process.env.PAPERCLIP_GOAL_ID?.trim() ?? void 0;
99
+ const runTimeoutMs = parseInt(getSettingString(settings, "runTimeoutMs") ?? process.env.PAPERCLIP_RUN_TIMEOUT_MS ?? "600000", 10);
100
+ const pollIntervalMs = parseInt(getSettingString(settings, "pollIntervalMs") ?? process.env.PAPERCLIP_POLL_INTERVAL_MS ?? "500", 10);
101
+ const pollIntervalMaxMs = parseInt(getSettingString(settings, "pollIntervalMaxMs") ?? process.env.PAPERCLIP_POLL_INTERVAL_MAX_MS ?? "2000", 10);
102
+ return {
103
+ apiUrl,
104
+ apiKey,
105
+ agentId,
106
+ companyId,
107
+ mode,
108
+ transport,
109
+ cliBinaryPath,
110
+ cliConfigPath,
111
+ parentIssueId,
112
+ projectId,
113
+ goalId,
114
+ runTimeoutMs: isFinite(runTimeoutMs) ? runTimeoutMs : 6e5,
115
+ pollIntervalMs: isFinite(pollIntervalMs) ? pollIntervalMs : 500,
116
+ pollIntervalMaxMs: isFinite(pollIntervalMaxMs) ? pollIntervalMaxMs : 2e3
117
+ };
118
+ }
119
+ async function discoverPaperclipCliConfig(opts = {}) {
120
+ const fs = await import("node:fs/promises");
121
+ const path = await import("node:path");
122
+ const os = await import("node:os");
123
+ const configPath = opts.configPath ?? path.join(os.homedir(), ".paperclip", "instances", "default", "config.json");
124
+ let raw;
125
+ try {
126
+ raw = await fs.readFile(configPath, "utf-8");
127
+ } catch (error) {
128
+ const code = error.code;
129
+ return {
130
+ ok: false,
131
+ configPath,
132
+ reason: code === "ENOENT" ? `Paperclip CLI config not found at ${configPath}. Install paperclipai and run \`paperclipai onboard\`, or use API mode.` : `Could not read ${configPath}: ${error.message}`
133
+ };
134
+ }
135
+ let parsed;
136
+ try {
137
+ parsed = JSON.parse(raw);
138
+ } catch {
139
+ return { ok: false, configPath, reason: `Invalid JSON in ${configPath}` };
140
+ }
141
+ const server = parsed.server ?? {};
142
+ const host = typeof server.host === "string" ? server.host : "127.0.0.1";
143
+ const port = typeof server.port === "number" ? server.port : Number(server.port ?? 3100) || 3100;
144
+ const deploymentMode = typeof server.deploymentMode === "string" ? server.deploymentMode : void 0;
145
+ return {
146
+ ok: true,
147
+ apiUrl: `http://${host}:${port}`,
148
+ apiKey: void 0,
149
+ configPath,
150
+ deploymentMode
151
+ };
152
+ }
153
+ async function agentsMe(apiUrl, apiKey) {
154
+ const raw = await request(apiUrl, "/agents/me", { apiKey });
155
+ const id = typeof raw.id === "string" ? raw.id : void 0;
156
+ const name = typeof raw.name === "string" ? raw.name : void 0;
157
+ const role = typeof raw.role === "string" ? raw.role : void 0;
158
+ const cId = typeof raw.companyId === "string" ? raw.companyId : void 0;
159
+ const cName = typeof raw.companyName === "string" ? raw.companyName : void 0;
160
+ if (!id || !cId) {
161
+ throw new Error("Paperclip /api/agents/me returned a response missing `id` or `companyId`");
162
+ }
163
+ return { agentId: id, agentName: name ?? id, role, companyId: cId, companyName: cName };
164
+ }
165
+ async function createIssue(apiUrl, apiKey, companyId, body) {
166
+ return request(apiUrl, `/companies/${companyId}/issues`, {
167
+ method: "POST",
168
+ apiKey,
169
+ body
170
+ });
171
+ }
172
+ async function getIssue(apiUrl, apiKey, issueId) {
173
+ return request(apiUrl, `/issues/${issueId}`, { apiKey });
174
+ }
175
+ async function getIssueComments(apiUrl, apiKey, issueId) {
176
+ return request(apiUrl, `/issues/${issueId}/comments`, { apiKey });
177
+ }
178
+ async function wakeAgent(apiUrl, apiKey, agentId, body) {
179
+ return request(apiUrl, `/agents/${agentId}/wakeup`, {
180
+ method: "POST",
181
+ apiKey,
182
+ body
183
+ });
184
+ }
185
+ async function getRunEvents(apiUrl, apiKey, runId, afterSeq, limit = 100) {
186
+ const query = new URLSearchParams({
187
+ afterSeq: String(afterSeq),
188
+ limit: String(limit)
189
+ });
190
+ const result = await request(apiUrl, `/heartbeat-runs/${runId}/events`, {
191
+ apiKey,
192
+ query
193
+ });
194
+ if (Array.isArray(result))
195
+ return result;
196
+ const r = result;
197
+ if (Array.isArray(r.events))
198
+ return r.events;
199
+ return [];
200
+ }
201
+ async function listCompanies(apiUrl, apiKey) {
202
+ const raw = await request(apiUrl, "/companies", { apiKey });
203
+ if (!Array.isArray(raw))
204
+ return [];
205
+ const out = [];
206
+ for (const entry of raw) {
207
+ if (!entry || typeof entry !== "object")
208
+ continue;
209
+ const r = entry;
210
+ const id = typeof r.id === "string" ? r.id : void 0;
211
+ if (!id)
212
+ continue;
213
+ const name = typeof r.name === "string" ? r.name : id;
214
+ const urlKey = typeof r.urlKey === "string" ? r.urlKey : typeof r.slug === "string" ? r.slug : void 0;
215
+ out.push({ id, name, urlKey });
216
+ }
217
+ return out;
218
+ }
219
+ async function listCompanyAgents(apiUrl, apiKey, companyId) {
220
+ const raw = await request(apiUrl, `/companies/${companyId}/agents`, { apiKey });
221
+ if (!Array.isArray(raw))
222
+ return [];
223
+ const out = [];
224
+ for (const entry of raw) {
225
+ if (!entry || typeof entry !== "object")
226
+ continue;
227
+ const r = entry;
228
+ const id = typeof r.id === "string" ? r.id : void 0;
229
+ if (!id)
230
+ continue;
231
+ const name = typeof r.name === "string" ? r.name : id;
232
+ const role = typeof r.role === "string" ? r.role : void 0;
233
+ const cId = typeof r.companyId === "string" ? r.companyId : companyId;
234
+ const status = typeof r.status === "string" ? r.status : void 0;
235
+ out.push({ id, name, role, companyId: cId, status });
236
+ }
237
+ return out;
238
+ }
239
+ async function probePaperclipConnection(opts) {
240
+ const { apiKey, timeoutMs = 5e3 } = opts;
241
+ const apiUrl = normalizeApiUrl(opts.apiUrl);
242
+ const url = buildUrl(apiUrl, "/agents/me");
243
+ const started = Date.now();
244
+ let response;
245
+ try {
246
+ response = await fetch(url, {
247
+ method: "GET",
248
+ headers: buildHeaders(apiKey),
249
+ signal: AbortSignal.timeout(timeoutMs)
250
+ });
251
+ } catch (err) {
252
+ const reason = err instanceof Error ? err.message : String(err);
253
+ return {
254
+ available: false,
255
+ apiUrl,
256
+ reason: `Paperclip server not reachable at ${apiUrl}: ${reason}`,
257
+ probeDurationMs: Date.now() - started
258
+ };
259
+ }
260
+ const probeDurationMs = Date.now() - started;
261
+ if (response.status === 401 || response.status === 403) {
262
+ return { available: false, apiUrl, reason: "API key rejected", probeDurationMs };
263
+ }
264
+ if (response.status === 404) {
265
+ return {
266
+ available: false,
267
+ apiUrl,
268
+ reason: `Paperclip server not reachable at ${apiUrl}: 404 Not Found`,
269
+ probeDurationMs
270
+ };
271
+ }
272
+ if (!response.ok) {
273
+ let body = "";
274
+ try {
275
+ body = (await response.text()).slice(0, 200);
276
+ } catch {
277
+ }
278
+ return {
279
+ available: false,
280
+ apiUrl,
281
+ reason: `${response.status} ${response.statusText}: ${body}`.trim(),
282
+ probeDurationMs
283
+ };
284
+ }
285
+ let identity;
286
+ try {
287
+ const { value: raw } = await parseJsonBody(response);
288
+ const r = raw;
289
+ const agentId = typeof r.id === "string" ? r.id : "";
290
+ const companyId = typeof r.companyId === "string" ? r.companyId : "";
291
+ identity = {
292
+ agentId,
293
+ agentName: typeof r.name === "string" ? r.name : agentId,
294
+ role: typeof r.role === "string" ? r.role : void 0,
295
+ companyId,
296
+ companyName: typeof r.companyName === "string" ? r.companyName : void 0
297
+ };
298
+ } catch {
299
+ return {
300
+ available: false,
301
+ apiUrl,
302
+ reason: "Paperclip server responded 200 but returned invalid JSON",
303
+ probeDurationMs
304
+ };
305
+ }
306
+ return { available: true, apiUrl, identity, probeDurationMs };
307
+ }
308
+ function stripAnsi(str) {
309
+ return str.replace(/\x1B\[[0-9;]*[mGKJHFABCDSTsu]/g, "");
310
+ }
311
+ async function mintAgentApiKeyViaCli(opts) {
312
+ const { spawn } = await import("node:child_process");
313
+ const bin = opts.cliBinaryPath ?? "paperclipai";
314
+ const args = [
315
+ "agent",
316
+ "local-cli",
317
+ opts.agentRef,
318
+ "--json",
319
+ "--no-install-skills",
320
+ "--key-name",
321
+ opts.keyName ?? "fusion-runtime"
322
+ ];
323
+ if (opts.companyId) {
324
+ args.push("--company-id", opts.companyId);
325
+ }
326
+ if (opts.configPath) {
327
+ args.push("--config", opts.configPath);
328
+ }
329
+ if (opts.dataDir) {
330
+ args.push("--data-dir", opts.dataDir);
331
+ }
332
+ const timeoutMs = opts.cliTimeoutMs ?? 3e4;
333
+ return new Promise((resolve, reject) => {
334
+ let child;
335
+ try {
336
+ child = spawn(bin, args, { stdio: ["ignore", "pipe", "pipe"] });
337
+ } catch (err) {
338
+ const code = err.code;
339
+ if (code === "ENOENT") {
340
+ reject(new Error(`paperclipai binary not found at ${bin}; install via \`npm i -g paperclipai\``));
341
+ } else {
342
+ reject(err instanceof Error ? err : new Error(String(err)));
343
+ }
344
+ return;
345
+ }
346
+ const stdoutChunks = [];
347
+ const stderrLines = [];
348
+ let killed = false;
349
+ const timer = setTimeout(() => {
350
+ killed = true;
351
+ child.kill("SIGKILL");
352
+ reject(new Error(`paperclipai agent local-cli timed out after ${timeoutMs}ms`));
353
+ }, timeoutMs);
354
+ child.stdout?.on("data", (chunk) => {
355
+ stdoutChunks.push(chunk);
356
+ });
357
+ child.stderr?.on("data", (chunk) => {
358
+ const lines = chunk.toString("utf-8").split("\n");
359
+ for (const line of lines) {
360
+ const stripped = stripAnsi(line).trim();
361
+ if (stripped)
362
+ stderrLines.push(stripped);
363
+ }
364
+ });
365
+ child.on("error", (err) => {
366
+ clearTimeout(timer);
367
+ if (err.code === "ENOENT") {
368
+ reject(new Error(`paperclipai binary not found at ${bin}; install via \`npm i -g paperclipai\``));
369
+ } else {
370
+ reject(err);
371
+ }
372
+ });
373
+ child.on("close", (code) => {
374
+ clearTimeout(timer);
375
+ if (killed)
376
+ return;
377
+ const rawStdout = Buffer.concat(stdoutChunks).toString("utf-8");
378
+ const cleanedStdout = stripAnsi(rawStdout).trim();
379
+ if (code !== 0) {
380
+ const lastStderrLine = stderrLines.filter(Boolean).pop() ?? "";
381
+ const hint = "run `paperclipai onboard` to authenticate the CLI";
382
+ const msg = lastStderrLine ? `paperclipai agent local-cli exited ${code}: ${lastStderrLine} \u2014 ${hint}` : `paperclipai agent local-cli exited ${code} \u2014 ${hint}`;
383
+ reject(new Error(msg));
384
+ return;
385
+ }
386
+ if (!cleanedStdout) {
387
+ const hint = "run `paperclipai onboard` to authenticate the CLI";
388
+ reject(new Error(`paperclipai agent local-cli produced no output \u2014 ${hint}`));
389
+ return;
390
+ }
391
+ let parsed;
392
+ try {
393
+ parsed = JSON.parse(cleanedStdout);
394
+ } catch {
395
+ reject(new Error(`paperclipai agent local-cli returned non-JSON output: ${cleanedStdout.slice(0, 200)}`));
396
+ return;
397
+ }
398
+ const r = parsed;
399
+ const apiKey = (typeof r.apiKey === "string" ? r.apiKey : void 0) ?? (typeof r.api_key === "string" ? r.api_key : void 0) ?? (typeof r.token === "string" ? r.token : void 0);
400
+ if (!apiKey) {
401
+ reject(new Error(`paperclipai agent local-cli JSON missing apiKey/api_key/token field: ${cleanedStdout.slice(0, 200)}`));
402
+ return;
403
+ }
404
+ const apiBase = (typeof r.apiBase === "string" ? r.apiBase : void 0) ?? (typeof r.api_base === "string" ? r.api_base : void 0);
405
+ const agentId = (typeof r.agentId === "string" ? r.agentId : void 0) ?? (typeof r.id === "string" ? r.id : void 0);
406
+ const companyId = typeof r.companyId === "string" ? r.companyId : void 0;
407
+ resolve({ apiKey, apiBase, agentId, companyId, raw: parsed });
408
+ });
409
+ });
410
+ }
411
+ function remapSpawnError(err, bin) {
412
+ const code = err?.code;
413
+ if (code === "ENOENT") {
414
+ return new Error(`paperclipai binary not found at ${bin}; install via \`npm i -g paperclipai\``);
415
+ }
416
+ return err instanceof Error ? err : new Error(String(err));
417
+ }
418
+ async function spawnPaperclipCliJson(args, opts) {
419
+ const { spawn } = await import("node:child_process");
420
+ const bin = opts.cliBinaryPath ?? "paperclipai";
421
+ const fullArgs = [...args, "--json"];
422
+ if (opts.cliConfigPath) {
423
+ fullArgs.push("--config", opts.cliConfigPath);
424
+ }
425
+ const timeoutMs = opts.cliTimeoutMs ?? 15e3;
426
+ const label = ["paperclipai", ...args].join(" ");
427
+ return new Promise((resolve, reject) => {
428
+ let child;
429
+ try {
430
+ child = spawn(bin, fullArgs, { stdio: ["ignore", "pipe", "pipe"] });
431
+ } catch (err) {
432
+ reject(remapSpawnError(err, bin));
433
+ return;
434
+ }
435
+ const stdoutChunks = [];
436
+ const stderrLines = [];
437
+ let killed = false;
438
+ const timer = setTimeout(() => {
439
+ killed = true;
440
+ child.kill("SIGKILL");
441
+ reject(new Error(`${label} timed out after ${timeoutMs}ms`));
442
+ }, timeoutMs);
443
+ child.stdout?.on("data", (chunk) => {
444
+ stdoutChunks.push(chunk);
445
+ });
446
+ child.stderr?.on("data", (chunk) => {
447
+ const lines = chunk.toString("utf-8").split("\n");
448
+ for (const line of lines) {
449
+ const stripped = stripAnsi(line).trim();
450
+ if (stripped)
451
+ stderrLines.push(stripped);
452
+ }
453
+ });
454
+ child.on("error", (err) => {
455
+ clearTimeout(timer);
456
+ reject(remapSpawnError(err, bin));
457
+ });
458
+ child.on("close", (code) => {
459
+ clearTimeout(timer);
460
+ if (killed)
461
+ return;
462
+ const cleaned = stripAnsi(Buffer.concat(stdoutChunks).toString("utf-8")).trim();
463
+ if (code !== 0) {
464
+ const lastErr = stderrLines.filter(Boolean).pop() ?? "";
465
+ reject(new Error(lastErr ? `${label} exited ${code}: ${lastErr}` : `${label} exited ${code}`));
466
+ return;
467
+ }
468
+ if (!cleaned) {
469
+ reject(new Error(`${label} produced no output`));
470
+ return;
471
+ }
472
+ try {
473
+ resolve(JSON.parse(cleaned));
474
+ } catch {
475
+ reject(new Error(`${label} returned non-JSON output: ${cleaned.slice(0, 200)}`));
476
+ }
477
+ });
478
+ });
479
+ }
480
+ async function listCompaniesViaCli(opts) {
481
+ const raw = await spawnPaperclipCliJson(["company", "list"], opts);
482
+ if (!Array.isArray(raw))
483
+ return [];
484
+ const out = [];
485
+ for (const entry of raw) {
486
+ if (!entry || typeof entry !== "object")
487
+ continue;
488
+ const r = entry;
489
+ const id = typeof r.id === "string" ? r.id : void 0;
490
+ if (!id)
491
+ continue;
492
+ const name = typeof r.name === "string" ? r.name : id;
493
+ const urlKey = typeof r.urlKey === "string" ? r.urlKey : typeof r.slug === "string" ? r.slug : void 0;
494
+ out.push({ id, name, urlKey });
495
+ }
496
+ return out;
497
+ }
498
+ async function listCompanyAgentsViaCli(opts) {
499
+ const raw = await spawnPaperclipCliJson(["agent", "list", "--company-id", opts.companyId], opts);
500
+ if (!Array.isArray(raw))
501
+ return [];
502
+ const out = [];
503
+ for (const entry of raw) {
504
+ if (!entry || typeof entry !== "object")
505
+ continue;
506
+ const r = entry;
507
+ const id = typeof r.id === "string" ? r.id : void 0;
508
+ if (!id)
509
+ continue;
510
+ const name = typeof r.name === "string" ? r.name : id;
511
+ const role = typeof r.role === "string" ? r.role : void 0;
512
+ const cId = typeof r.companyId === "string" ? r.companyId : opts.companyId;
513
+ const status = typeof r.status === "string" ? r.status : void 0;
514
+ out.push({ id, name, role, companyId: cId, status });
515
+ }
516
+ return out;
517
+ }
518
+ async function createIssueViaCli(opts) {
519
+ const args = ["issue", "create", "--company-id", opts.companyId];
520
+ args.push("--title", opts.body.title);
521
+ if (opts.body.description)
522
+ args.push("--description", opts.body.description);
523
+ if (opts.body.status)
524
+ args.push("--status", opts.body.status);
525
+ if (opts.body.assigneeAgentId)
526
+ args.push("--assignee-agent-id", opts.body.assigneeAgentId);
527
+ if (opts.body.parentId)
528
+ args.push("--parent-id", opts.body.parentId);
529
+ if (opts.body.projectId)
530
+ args.push("--project-id", opts.body.projectId);
531
+ if (opts.body.goalId)
532
+ args.push("--goal-id", opts.body.goalId);
533
+ const raw = await spawnPaperclipCliJson(args, opts);
534
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
535
+ throw new Error(`paperclipai issue create returned unexpected payload: ${JSON.stringify(raw).slice(0, 200)}`);
536
+ }
537
+ return raw;
538
+ }
539
+ async function getIssueViaCli(opts) {
540
+ const raw = await spawnPaperclipCliJson(["issue", "get", opts.issueId], opts);
541
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
542
+ throw new Error(`paperclipai issue get returned unexpected payload: ${JSON.stringify(raw).slice(0, 200)}`);
543
+ }
544
+ return raw;
545
+ }
546
+ async function agentsMeViaCli(opts) {
547
+ const raw = await spawnPaperclipCliJson(["agent", "get", opts.agentId], opts);
548
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
549
+ throw new Error(`paperclipai agent get returned unexpected payload: ${JSON.stringify(raw).slice(0, 200)}`);
550
+ }
551
+ const id = typeof raw.id === "string" ? raw.id : void 0;
552
+ const cId = typeof raw.companyId === "string" ? raw.companyId : void 0;
553
+ if (!id || !cId) {
554
+ throw new Error("paperclipai agent get returned a response missing `id` or `companyId`");
555
+ }
556
+ return {
557
+ agentId: id,
558
+ agentName: typeof raw.name === "string" ? raw.name : id,
559
+ role: typeof raw.role === "string" ? raw.role : void 0,
560
+ companyId: cId,
561
+ companyName: typeof raw.companyName === "string" ? raw.companyName : void 0
562
+ };
563
+ }
564
+ async function probePaperclipViaCli(opts) {
565
+ const started = Date.now();
566
+ let apiUrl = "(via paperclipai CLI)";
567
+ try {
568
+ const disc = await discoverPaperclipCliConfig({ configPath: opts.cliConfigPath });
569
+ if (disc.ok)
570
+ apiUrl = disc.apiUrl;
571
+ } catch {
572
+ }
573
+ try {
574
+ await spawnPaperclipCliJson(["company", "list"], opts);
575
+ return { available: true, apiUrl, probeDurationMs: Date.now() - started };
576
+ } catch (err) {
577
+ return {
578
+ available: false,
579
+ apiUrl,
580
+ reason: err instanceof Error ? err.message : String(err),
581
+ probeDurationMs: Date.now() - started
582
+ };
583
+ }
584
+ }
585
+
586
+ // ../../plugins/fusion-plugin-paperclip-runtime/dist/runtime-adapter.js
587
+ import { randomUUID } from "node:crypto";
588
+ var TERMINAL_RUN_STATUSES = /* @__PURE__ */ new Set([
589
+ "succeeded",
590
+ "failed",
591
+ "cancelled",
592
+ "timed_out"
593
+ ]);
594
+ var VALID_MODES = /* @__PURE__ */ new Set([
595
+ "issue-per-prompt",
596
+ "rolling-issue",
597
+ "wakeup-only"
598
+ ]);
599
+ function sleep(ms) {
600
+ return new Promise((resolve) => setTimeout(resolve, ms));
601
+ }
602
+ function asString(value) {
603
+ return typeof value === "string" ? value : void 0;
604
+ }
605
+ function deriveIssueTitle(prompt) {
606
+ const firstLine = prompt.split("\n").find((line) => line.trim() !== "") ?? "Fusion runtime prompt";
607
+ return firstLine.slice(0, 200);
608
+ }
609
+ function buildIssueDescription(session, prompt) {
610
+ return [
611
+ `System Prompt:
612
+ ${session.systemPrompt}`,
613
+ `Working Directory: ${session.cwd}`,
614
+ `Prompt:
615
+ ${prompt}`
616
+ ].join("\n\n");
617
+ }
618
+ function pickIssueId(issue) {
619
+ const issueId = asString(issue.id);
620
+ if (!issueId) {
621
+ throw new Error("Paperclip createIssue response missing issue id");
622
+ }
623
+ return issueId;
624
+ }
625
+ function normalizeMode(mode) {
626
+ if (mode && VALID_MODES.has(mode))
627
+ return mode;
628
+ return "rolling-issue";
629
+ }
630
+ var PaperclipRuntimeAdapter = class {
631
+ id = "paperclip";
632
+ name = "Paperclip Runtime";
633
+ config;
634
+ logger;
635
+ constructor(config, logger) {
636
+ const resolved = resolvePaperclipConfig(config);
637
+ this.config = {
638
+ ...resolved,
639
+ mode: normalizeMode(resolved.mode),
640
+ ...config
641
+ };
642
+ this.logger = logger ?? console;
643
+ }
644
+ async createSession(options) {
645
+ let effectiveApiUrl = this.config.apiUrl;
646
+ let effectiveApiKey = this.config.apiKey;
647
+ if (this.config.transport === "cli") {
648
+ const discovery = await discoverPaperclipCliConfig({
649
+ configPath: this.config.cliConfigPath
650
+ });
651
+ if (!discovery.ok) {
652
+ throw new Error(`Paperclip CLI mode failed: ${discovery.reason} (Switch to API mode in settings if paperclipai isn't installed.)`);
653
+ }
654
+ effectiveApiUrl = discovery.apiUrl;
655
+ if (!effectiveApiKey) {
656
+ effectiveApiKey = discovery.apiKey;
657
+ }
658
+ this.logger.info(`Paperclip CLI mode resolved apiUrl=${effectiveApiUrl} (deploymentMode=${discovery.deploymentMode ?? "unknown"})`);
659
+ }
660
+ let agentId = this.config.agentId;
661
+ let companyId = this.config.companyId;
662
+ if (!agentId || !companyId) {
663
+ try {
664
+ if (this.config.transport === "cli") {
665
+ if (!agentId) {
666
+ throw new Error("agentId is required in Local CLI mode (paperclipai has no `agents/me` equivalent \u2014 pick an agent in settings)");
667
+ }
668
+ const me = await agentsMeViaCli({
669
+ agentId,
670
+ cliBinaryPath: this.config.cliBinaryPath,
671
+ cliConfigPath: this.config.cliConfigPath
672
+ });
673
+ companyId = companyId ?? me.companyId;
674
+ } else {
675
+ const me = await agentsMe(effectiveApiUrl, effectiveApiKey);
676
+ agentId = agentId ?? me.agentId;
677
+ companyId = companyId ?? me.companyId;
678
+ }
679
+ } catch (error) {
680
+ const reason = error instanceof Error ? error.message : String(error);
681
+ throw new Error(`Paperclip runtime could not derive agentId/companyId. Configure them explicitly or check the API key / CLI auth. Underlying error: ${reason}`);
682
+ }
683
+ }
684
+ if (!agentId || !companyId) {
685
+ throw new Error("Paperclip runtime is missing required config: agentId or companyId. Configure plugin settings (apiUrl, apiKey, agentId, companyId) or PAPERCLIP_* env vars.");
686
+ }
687
+ const session = {
688
+ apiUrl: effectiveApiUrl,
689
+ apiKey: effectiveApiKey,
690
+ agentId,
691
+ companyId,
692
+ sessionId: randomUUID(),
693
+ systemPrompt: options.systemPrompt,
694
+ cwd: options.cwd,
695
+ mode: normalizeMode(this.config.mode),
696
+ parentIssueId: this.config.parentIssueId,
697
+ projectId: this.config.projectId,
698
+ goalId: this.config.goalId,
699
+ issueId: void 0,
700
+ transport: this.config.transport ?? "api",
701
+ cliBinaryPath: this.config.cliBinaryPath,
702
+ cliConfigPath: this.config.cliConfigPath,
703
+ turnIndex: 0,
704
+ runTimeoutMs: this.config.runTimeoutMs ?? 6e5,
705
+ pollIntervalMs: this.config.pollIntervalMs ?? 500,
706
+ pollIntervalMaxMs: this.config.pollIntervalMaxMs ?? 2e3,
707
+ onText: options.onText,
708
+ onThinking: options.onThinking,
709
+ onToolStart: options.onToolStart,
710
+ onToolEnd: options.onToolEnd,
711
+ dispose: () => void 0
712
+ };
713
+ return { session, sessionFile: void 0 };
714
+ }
715
+ async promptWithFallback(session, prompt, _options) {
716
+ session.turnIndex += 1;
717
+ const turn = session.turnIndex;
718
+ session.onToolStart?.("paperclip.run", {
719
+ sessionId: session.sessionId,
720
+ mode: session.mode,
721
+ turn
722
+ });
723
+ let issueId;
724
+ if (session.mode === "issue-per-prompt") {
725
+ issueId = await this.createIssueForPrompt(session, prompt);
726
+ } else if (session.mode === "rolling-issue") {
727
+ if (!session.issueId) {
728
+ session.issueId = await this.createIssueForPrompt(session, prompt);
729
+ }
730
+ issueId = session.issueId;
731
+ }
732
+ const idempotencyKey = `${session.sessionId}:${turn}`;
733
+ let runId;
734
+ try {
735
+ const wakeResponse = await wakeAgent(session.apiUrl, session.apiKey, session.agentId, {
736
+ source: "on_demand",
737
+ triggerDetail: "manual",
738
+ reason: "Fusion runtime prompt",
739
+ idempotencyKey,
740
+ payload: {
741
+ fusionSessionId: session.sessionId,
742
+ prompt,
743
+ issueId
744
+ }
745
+ });
746
+ if (wakeResponse.status === "skipped") {
747
+ session.onToolEnd?.("paperclip.run", true, {
748
+ issueId,
749
+ runStatus: "skipped",
750
+ reason: "Paperclip coalesced this wakeup with a recent one (status=skipped)."
751
+ });
752
+ return;
753
+ }
754
+ runId = wakeResponse.id;
755
+ if (!runId) {
756
+ session.onToolEnd?.("paperclip.run", true, {
757
+ issueId,
758
+ reason: "Paperclip wakeup response missing run id"
759
+ });
760
+ return;
761
+ }
762
+ } catch (error) {
763
+ const reason = error instanceof Error ? error.message : String(error);
764
+ this.logger.warn(`Paperclip wakeup failed: ${reason}`);
765
+ session.onToolEnd?.("paperclip.run", true, { issueId, reason });
766
+ return;
767
+ }
768
+ const stream = await this.streamRunEvents(session, runId);
769
+ let issueStatus;
770
+ let finalText = stream.text;
771
+ if (issueId) {
772
+ try {
773
+ const issue = session.transport === "cli" ? await getIssueViaCli({
774
+ issueId,
775
+ cliBinaryPath: session.cliBinaryPath,
776
+ cliConfigPath: session.cliConfigPath
777
+ }) : await getIssue(session.apiUrl, session.apiKey, issueId);
778
+ issueStatus = asString(issue.status) ?? void 0;
779
+ if (!finalText) {
780
+ const comments = await getIssueComments(session.apiUrl, session.apiKey, issueId);
781
+ const latest = pickLatestVisibleComment(comments);
782
+ if (latest)
783
+ finalText = latest;
784
+ }
785
+ } catch (error) {
786
+ const reason = error instanceof Error ? error.message : String(error);
787
+ this.logger.warn(`Paperclip post-run fetch failed: ${reason}`);
788
+ }
789
+ }
790
+ if (finalText)
791
+ session.onText?.(finalText);
792
+ if (stream.thinking)
793
+ session.onThinking?.(stream.thinking);
794
+ const isError = stream.runStatus === "failed" || stream.runStatus === "timed_out";
795
+ session.onToolEnd?.("paperclip.run", isError || stream.timedOutLocally, {
796
+ runId,
797
+ runStatus: stream.runStatus,
798
+ issueId,
799
+ issueStatus,
800
+ timedOutLocally: stream.timedOutLocally,
801
+ deepLink: issueId ? `${session.apiUrl.replace(/\/$/, "")}/issues/${issueId}` : void 0
802
+ });
803
+ }
804
+ describeModel(session) {
805
+ return `paperclip/${session.agentId}`;
806
+ }
807
+ async dispose(_session) {
808
+ }
809
+ // ---------------------------------------------------------------------
810
+ // Internals
811
+ // ---------------------------------------------------------------------
812
+ async createIssueForPrompt(session, prompt) {
813
+ const body = {
814
+ title: deriveIssueTitle(prompt),
815
+ description: buildIssueDescription(session, prompt),
816
+ status: "todo",
817
+ assigneeAgentId: session.agentId,
818
+ ...session.parentIssueId ? { parentId: session.parentIssueId } : {},
819
+ ...session.projectId ? { projectId: session.projectId } : {},
820
+ ...session.goalId ? { goalId: session.goalId } : {}
821
+ };
822
+ const created = session.transport === "cli" ? await createIssueViaCli({
823
+ companyId: session.companyId,
824
+ body,
825
+ cliBinaryPath: session.cliBinaryPath,
826
+ cliConfigPath: session.cliConfigPath
827
+ }) : await createIssue(session.apiUrl, session.apiKey, session.companyId, body);
828
+ return pickIssueId(created);
829
+ }
830
+ async streamRunEvents(session, runId) {
831
+ const startedAt = Date.now();
832
+ let afterSeq = 0;
833
+ let interval = session.pollIntervalMs;
834
+ let runStatus = "running";
835
+ let timedOutLocally = false;
836
+ let textBuf = "";
837
+ let thinkBuf = "";
838
+ while (true) {
839
+ let events = [];
840
+ try {
841
+ events = await getRunEvents(session.apiUrl, session.apiKey, runId, afterSeq, 200);
842
+ } catch (error) {
843
+ const reason = error instanceof Error ? error.message : String(error);
844
+ this.logger.warn(`Paperclip getRunEvents failed: ${reason}`);
845
+ }
846
+ for (const ev of events) {
847
+ if (typeof ev.seq === "number" && ev.seq > afterSeq)
848
+ afterSeq = ev.seq;
849
+ const type = ev.type ?? "";
850
+ const payload = ev.payload ?? {};
851
+ if (type === "heartbeat.run.status") {
852
+ const next = asString(payload.status);
853
+ if (next)
854
+ runStatus = next;
855
+ } else if (type === "heartbeat.run.log") {
856
+ const chunk = asString(payload.chunk) ?? "";
857
+ if (!chunk)
858
+ continue;
859
+ if (payload.stream === "stdout") {
860
+ textBuf += chunk;
861
+ session.onText?.(chunk);
862
+ } else if (payload.stream === "stderr") {
863
+ this.logger.warn(`[paperclip:run:${runId}] ${chunk.trimEnd()}`);
864
+ } else if (payload.stream === "system") {
865
+ const message = asString(payload.message) ?? chunk;
866
+ thinkBuf += (thinkBuf ? "\n" : "") + message;
867
+ }
868
+ }
869
+ }
870
+ if (TERMINAL_RUN_STATUSES.has(runStatus))
871
+ break;
872
+ if (Date.now() - startedAt > session.runTimeoutMs) {
873
+ timedOutLocally = true;
874
+ this.logger.warn(`Paperclip run ${runId} exceeded local runTimeoutMs=${session.runTimeoutMs}; abandoning poll. Run continues server-side.`);
875
+ break;
876
+ }
877
+ await sleep(interval);
878
+ interval = Math.min(interval * 2, session.pollIntervalMaxMs);
879
+ }
880
+ return { text: textBuf, thinking: thinkBuf, runStatus, timedOutLocally };
881
+ }
882
+ };
883
+ function pickLatestVisibleComment(comments) {
884
+ for (let i = comments.length - 1; i >= 0; i--) {
885
+ const c = comments[i];
886
+ const body = asString(c.body)?.trim();
887
+ if (body)
888
+ return body;
889
+ }
890
+ return void 0;
891
+ }
892
+
893
+ // ../../plugins/fusion-plugin-paperclip-runtime/dist/index.js
894
+ function getSettingsConfig(settings) {
895
+ return resolvePaperclipConfig(settings ?? {});
896
+ }
897
+ async function paperclipRuntimeFactory(ctx) {
898
+ const config = getSettingsConfig(ctx.settings);
899
+ return new PaperclipRuntimeAdapter(config, ctx.logger);
900
+ }
901
+ var paperclipRuntime = {
902
+ metadata: {
903
+ runtimeId: "paperclip",
904
+ name: "Paperclip Runtime",
905
+ description: "Drives a Paperclip agent via the wakeup + heartbeat-run REST API",
906
+ version: "1.0.0"
907
+ },
908
+ factory: paperclipRuntimeFactory
909
+ };
910
+ var plugin = definePlugin({
911
+ manifest: {
912
+ id: "fusion-plugin-paperclip-runtime",
913
+ name: "Paperclip Runtime Plugin",
914
+ version: "1.0.0",
915
+ description: "Drives a Paperclip agent via the wakeup + heartbeat-run REST API",
916
+ author: "Fusion Team",
917
+ homepage: "https://paperclip.ing/",
918
+ fusionVersion: ">=0.1.0",
919
+ runtime: {
920
+ runtimeId: "paperclip",
921
+ name: "Paperclip Runtime",
922
+ description: "Drives a Paperclip agent via the wakeup + heartbeat-run REST API",
923
+ version: "1.0.0"
924
+ }
925
+ },
926
+ state: "installed",
927
+ runtime: paperclipRuntime,
928
+ hooks: {
929
+ onLoad: async (ctx) => {
930
+ const config = getSettingsConfig(ctx.settings);
931
+ ctx.logger.info(`Paperclip Runtime Plugin loaded (apiUrl=${config.apiUrl})`);
932
+ try {
933
+ const status = await probePaperclipConnection({
934
+ apiUrl: config.apiUrl,
935
+ apiKey: config.apiKey
936
+ });
937
+ if (status.available) {
938
+ const ident = status.identity;
939
+ ctx.logger.info(ident ? `Paperclip reachable as ${ident.agentName} (${ident.role ?? "agent"}) at ${ident.companyName ?? ident.companyId}` : `Paperclip reachable at ${config.apiUrl}`);
940
+ } else {
941
+ ctx.logger.warn(`Paperclip probe failed: ${status.reason ?? "unknown"}`);
942
+ }
943
+ } catch (error) {
944
+ const reason = error instanceof Error ? error.message : String(error);
945
+ ctx.logger.warn(`Paperclip probe threw: ${reason}`);
946
+ }
947
+ }
948
+ }
949
+ });
950
+ var index_default = plugin;
951
+ export {
952
+ PaperclipRuntimeAdapter,
953
+ agentsMe,
954
+ agentsMeViaCli,
955
+ createIssueViaCli,
956
+ index_default as default,
957
+ discoverPaperclipCliConfig,
958
+ getIssueViaCli,
959
+ listCompanies,
960
+ listCompaniesViaCli,
961
+ listCompanyAgents,
962
+ listCompanyAgentsViaCli,
963
+ mintAgentApiKeyViaCli,
964
+ probePaperclipConnection,
965
+ probePaperclipViaCli
966
+ };