svamp-cli 0.2.342 → 0.2.346

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/bin/skills/artifact/SKILL.md +1 -1
  2. package/bin/skills/crew/SKILL.md +21 -3
  3. package/dist/DefaultTransport-vfp78fug.mjs +47 -0
  4. package/dist/GeminiTransport-3OkhtFRO.mjs +130 -0
  5. package/dist/KimiTransport-D8n009Tr.mjs +141 -0
  6. package/dist/acpAgentConfig-CObOPlA8.mjs +48 -0
  7. package/dist/acpBackend-BZ1MJhQV.mjs +922 -0
  8. package/dist/{adminCommands-CRTBDZan.mjs → adminCommands-BvsYnRDA.mjs} +2 -18
  9. package/dist/{agentCommands-BuO988tJ.mjs → agentCommands-B2Nl7Fof.mjs} +6 -23
  10. package/dist/api-BHjyC0Ph.mjs +157 -0
  11. package/dist/{auth--WS0wezs.mjs → auth-CRz8TsEl.mjs} +1 -22
  12. package/dist/bootStatus-DqtNgJch.mjs +143 -0
  13. package/dist/claudeAuth-iAerdaMr.mjs +247 -0
  14. package/dist/cli.mjs +2939 -5
  15. package/dist/codexAppServerBackend-VGsYtbqg.mjs +786 -0
  16. package/dist/codexProvider-BtxYc5Rj.mjs +68 -0
  17. package/dist/{commands-DXM17M6n.mjs → commands-BB1Kcyt-.mjs} +13 -19
  18. package/dist/{commands-DU4B-4Gh.mjs → commands-BOpoVBOM.mjs} +55 -25
  19. package/dist/{commands-C0HQiu-S.mjs → commands-C2aODeVc.mjs} +58 -33
  20. package/dist/{commands-Cwxy-C5N.mjs → commands-CiRCwjkJ.mjs} +10 -32
  21. package/dist/{commands-DDuGBSsz.mjs → commands-D1pZq3d4.mjs} +103 -41
  22. package/dist/{commands-BKb1g8Gq.mjs → commands-D69mnkYu.mjs} +20 -30
  23. package/dist/{commands-BsKgZ5sQ.mjs → commands-DW5RnHZw.mjs} +2 -19
  24. package/dist/{commands-DXLOKJOj.mjs → commands-H4PsTGn8.mjs} +58 -26
  25. package/dist/cron-hHGdb5kU.mjs +132 -0
  26. package/dist/detectIsolation-CzoDCEnh.mjs +319 -0
  27. package/dist/envFlag-COOuJ3AH.mjs +11 -0
  28. package/dist/fileLock-BMPwCrB6.mjs +80 -0
  29. package/dist/{fleet-BHzlAvnu.mjs → fleet-DM_Q7w69.mjs} +85 -24
  30. package/dist/flushExit-C-i6SGjj.mjs +37 -0
  31. package/dist/friendlyName-1UZcyDhu.mjs +586 -0
  32. package/dist/{headlessCli-C2gvOpOt.mjs → headlessCli-C8HzQvis.mjs} +3 -21
  33. package/dist/hookSettings-DYMTUgVA.mjs +10399 -0
  34. package/dist/{httpServer-Be1Hcddr.mjs → httpServer-BzEDbOGn.mjs} +14 -5
  35. package/dist/hyphaClient-ybivOjZo.mjs +49 -0
  36. package/dist/index.mjs +40 -8
  37. package/dist/instance-CeHz5C8v.mjs +203 -0
  38. package/dist/kimiProvider-DbxODebR.mjs +69 -0
  39. package/dist/kimiSetup-NGsoYB5h.mjs +48 -0
  40. package/dist/loopVerify-B47sSjxe.mjs +444 -0
  41. package/dist/machineTools-DygM1jXW.mjs +580 -0
  42. package/dist/model-CUHojegn.mjs +66 -0
  43. package/dist/{notifyCommands-0aZUwKQK.mjs → notifyCommands-D-IKIxiq.mjs} +3 -18
  44. package/dist/package-DtuINqf5.mjs +64 -0
  45. package/dist/pinnedCodex-XW4qvOAe.mjs +11 -0
  46. package/dist/pinnedNono-5ZHb6328.mjs +15 -0
  47. package/dist/{rpc-rAQQ1q8t.mjs → rpc-BB6RF4-F.mjs} +38 -6
  48. package/dist/{rpc-B2Lc2BAB.mjs → rpc-Cf0grcru.mjs} +28 -20
  49. package/dist/{run-C7QVfyw4.mjs → run-ChdsgaSD.mjs} +17 -9
  50. package/dist/run-DkjwF-zA.mjs +9695 -0
  51. package/dist/sandboxDetect-Cdzsj5TC.mjs +19 -0
  52. package/dist/{scheduler-CzF4lOe_.mjs → scheduler-BY30ZrB9.mjs} +38 -6
  53. package/dist/securityContext-Cm5UTCuD.mjs +218 -0
  54. package/dist/serveAuth-DbVLRCRR.mjs +418 -0
  55. package/dist/{serveCommands-EIEqdaVu.mjs → serveCommands-CUHUiYnZ.mjs} +13 -34
  56. package/dist/sharingNotificationSync-qGIQ-3yf.mjs +359 -0
  57. package/dist/{sideband-BoutiRJi.mjs → sideband-68HhGT0i.mjs} +1 -21
  58. package/dist/store-Bn0ByYev.mjs +347 -0
  59. package/dist/store-fi0f-ff1.mjs +184 -0
  60. package/dist/types-57J3Re0n.mjs +17 -0
  61. package/dist/wantsHelp-6bMOeNUz.mjs +5 -0
  62. package/dist/wrapCommand-CdNawSaa.mjs +117 -0
  63. package/package.json +3 -3
  64. package/dist/cli-B24CmIli.mjs +0 -2780
  65. package/dist/package-CTbGYaJG.mjs +0 -64
  66. package/dist/run-CF4kAVPW.mjs +0 -26508
  67. package/dist/sandboxDetect-DNTcbgWD.mjs +0 -12
@@ -0,0 +1,359 @@
1
+ import { randomUUID, createHash } from 'node:crypto';
2
+
3
+ const SHARE_COLLECTION_ID = "public/svamp-shared-sessions";
4
+ const EVENT_COLLECTION_ID = "public/svamp-user-events";
5
+ const SYSTEM_COLLECTION_CONFIG = {
6
+ permissions: { "@": "lf+" },
7
+ private_children: true,
8
+ recipient_field: "recipientEmail",
9
+ recipient_can_delete: true
10
+ };
11
+ function computeCollectionConfigUpdate(existingConfig, extra) {
12
+ const cfg = existingConfig || {};
13
+ const changed = Object.entries(extra).some(([k, v]) => JSON.stringify(cfg[k]) !== JSON.stringify(v));
14
+ return { changed, merged: { ...cfg, ...extra } };
15
+ }
16
+ function emailHash(email) {
17
+ return createHash("sha256").update(email.toLowerCase()).digest("hex").slice(0, 12);
18
+ }
19
+ function sessionIdHash(sessionId) {
20
+ return createHash("sha256").update(sessionId).digest("hex").slice(0, 12);
21
+ }
22
+ function roleLabel(role) {
23
+ switch (role) {
24
+ case "admin":
25
+ return "full access";
26
+ case "interact":
27
+ return "can interact";
28
+ case "view":
29
+ return "view only";
30
+ default:
31
+ return String(role);
32
+ }
33
+ }
34
+ function shareAlias(sessionId, recipientEmail) {
35
+ return `share-${sessionIdHash(sessionId)}-${emailHash(recipientEmail)}`;
36
+ }
37
+ function eventAlias(eventId, recipientEmail) {
38
+ return `evt-${emailHash(recipientEmail)}-${eventId.slice(0, 8)}`;
39
+ }
40
+ class SharingNotificationSync {
41
+ server;
42
+ artifactManager = null;
43
+ amResolvedAt = 0;
44
+ shareCollectionId = null;
45
+ eventCollectionId = null;
46
+ initialized = false;
47
+ log;
48
+ // A resolved `public/artifact-manager` proxy is bound to a SPECIFIC backend client_id. When that
49
+ // backend pod is replaced (deploy/restart/idle-evict), the new instance registers under the same
50
+ // public alias but a fresh client_id — the cached proxy dangles and every call TIMES OUT (~30s).
51
+ // Before this fix that silently broke ALL zero-click share writes fleet-wide (empty
52
+ // svamp-shared-sessions → recipients never auto-discovered). So we re-resolve on a short TTL and,
53
+ // on any RPC error, force one fresh re-resolve + retry (a dead pod's proxy always fails first).
54
+ static AM_TTL_MS = 3e4;
55
+ constructor(server, log) {
56
+ this.server = server;
57
+ this.log = log;
58
+ }
59
+ /**
60
+ * Resolve `public/artifact-manager` on the daemon's live connection. hypha-rpc re-queries the
61
+ * server registry on each getService() call, so a fresh resolve yields the CURRENTLY-registered
62
+ * live instance (not the dangling cached one). Cached within a short TTL to avoid a lookup per
63
+ * write; `force` bypasses the cache after a failure.
64
+ */
65
+ async getAM(force = false) {
66
+ const now = Date.now();
67
+ if (!force && this.artifactManager && now - this.amResolvedAt < SharingNotificationSync.AM_TTL_MS) {
68
+ return this.artifactManager;
69
+ }
70
+ const am = await this.server.getService("public/artifact-manager");
71
+ this.artifactManager = am;
72
+ this.amResolvedAt = Date.now();
73
+ return am;
74
+ }
75
+ /**
76
+ * Run an artifact-manager operation with automatic recovery from a stale/dead proxy: try with the
77
+ * (possibly cached) instance; on ANY error, force a fresh re-resolve and retry ONCE. This is the
78
+ * heart of the stale-proxy fix — a replaced backend pod makes the first call time out, the retry
79
+ * re-binds to the live instance and succeeds. The caller still catches the final error.
80
+ */
81
+ async withAM(op) {
82
+ try {
83
+ return await op(await this.getAM(false));
84
+ } catch (err) {
85
+ this.log(`[SHARING NOTIFY] artifact-manager call failed (${err?.message}); re-resolving + retrying`);
86
+ return await op(await this.getAM(true));
87
+ }
88
+ }
89
+ async init() {
90
+ try {
91
+ const am = await this.getAM(true);
92
+ if (!am) {
93
+ this.log("[SHARING NOTIFY] Artifact manager not available");
94
+ return;
95
+ }
96
+ this.shareCollectionId = SHARE_COLLECTION_ID;
97
+ this.eventCollectionId = EVENT_COLLECTION_ID;
98
+ this.initialized = true;
99
+ this.log(`[SHARING NOTIFY] Initialized (${EVENT_COLLECTION_ID} + ${SHARE_COLLECTION_ID})`);
100
+ } catch (err) {
101
+ this.log(`[SHARING NOTIFY] Init failed: ${err.message}`);
102
+ }
103
+ }
104
+ // ── Share bookmark artifacts (svamp-shared-sessions) ──────────────
105
+ async notifyShare(params) {
106
+ if (!this.initialized || !this.shareCollectionId) return;
107
+ const alias = shareAlias(params.sessionId, params.recipientEmail);
108
+ const manifest = {
109
+ recipientEmail: params.recipientEmail.toLowerCase(),
110
+ sessionId: params.sessionId,
111
+ machineId: params.machineId,
112
+ machineServiceId: params.machineServiceId,
113
+ ownerWorkspace: params.ownerWorkspace,
114
+ ownerEmail: params.ownerEmail,
115
+ label: params.label || "",
116
+ role: params.role,
117
+ sharedAt: Date.now(),
118
+ shareType: params.shareType || "session"
119
+ };
120
+ try {
121
+ await this.withAM(async (am) => {
122
+ const artifact = await am.create({
123
+ alias,
124
+ parent_id: this.shareCollectionId,
125
+ type: "share-notification",
126
+ stage: true,
127
+ manifest,
128
+ _rkwargs: true
129
+ });
130
+ await am.commit({ artifact_id: artifact.id, _rkwargs: true }).catch(() => {
131
+ });
132
+ });
133
+ } catch (createErr) {
134
+ this.log(`[SHARING NOTIFY] share-notification attach skipped for ${params.recipientEmail}: ${createErr.message}`);
135
+ }
136
+ this.log(`[SHARING NOTIFY] Share notified ${params.recipientEmail} about ${params.shareType || "session"} ${params.sessionId.slice(0, 8)}`);
137
+ const shareType = params.shareType || "session";
138
+ const typeLabel = shareType === "machine" ? "Machine" : "Session";
139
+ await this.publishUserEvent({
140
+ id: randomUUID(),
141
+ recipientEmail: params.recipientEmail.toLowerCase(),
142
+ type: "sharing-invite",
143
+ source: {
144
+ service: "svamp-machine",
145
+ workspace: params.ownerWorkspace,
146
+ userEmail: params.ownerEmail
147
+ },
148
+ title: `${typeLabel} shared with you`,
149
+ // #0689: render the recipient's ACTUAL role (view/interact/admin) — authorize.ts genuinely
150
+ // enforces per-user roles, so the old hardcoded "(full access)" misstated a view/interact
151
+ // invitee's access.
152
+ body: `${params.ownerEmail} shared "${params.label || "Untitled"}" with you (${roleLabel(params.role)})`,
153
+ level: "info",
154
+ action: shareType === "session" ? {
155
+ type: "add-session-bookmark",
156
+ sessionId: params.sessionId,
157
+ workspace: params.ownerWorkspace,
158
+ machineServiceId: params.machineServiceId,
159
+ label: params.label || ""
160
+ } : {
161
+ type: "add-machine-bookmark",
162
+ machineId: params.machineId,
163
+ workspace: params.ownerWorkspace,
164
+ label: params.label || ""
165
+ },
166
+ createdAt: Date.now(),
167
+ expiresAt: Date.now() + 30 * 24 * 60 * 60 * 1e3
168
+ // 30 days
169
+ });
170
+ }
171
+ async removeNotification(sessionId, recipientEmail) {
172
+ if (!this.initialized || !this.shareCollectionId) return;
173
+ const alias = shareAlias(sessionId, recipientEmail);
174
+ try {
175
+ await this.withAM(async (am) => {
176
+ const existing = await am.read({
177
+ artifact_id: alias,
178
+ parent_id: this.shareCollectionId,
179
+ _rkwargs: true
180
+ });
181
+ await am.delete({
182
+ artifact_id: existing.id,
183
+ _rkwargs: true
184
+ });
185
+ });
186
+ this.log(`[SHARING NOTIFY] Removed share notification for ${recipientEmail} on ${sessionId.slice(0, 8)}`);
187
+ } catch {
188
+ }
189
+ await this.publishUserEvent({
190
+ id: randomUUID(),
191
+ recipientEmail: recipientEmail.toLowerCase(),
192
+ type: "sharing-revoked",
193
+ source: {
194
+ service: "svamp-machine",
195
+ workspace: this.server.config?.workspace || "",
196
+ userEmail: ""
197
+ },
198
+ title: "Session access removed",
199
+ body: `Your access to a shared session has been revoked`,
200
+ level: "warning",
201
+ createdAt: Date.now(),
202
+ expiresAt: Date.now() + 7 * 24 * 60 * 60 * 1e3
203
+ // 7 days
204
+ });
205
+ }
206
+ // ── General user event artifacts (svamp-user-events) ─────────────
207
+ /**
208
+ * Publish a general user event to the event inbox collection.
209
+ * Any service can call this to deliver an event to any user's bell icon.
210
+ */
211
+ async publishUserEvent(event) {
212
+ if (!this.initialized || !this.eventCollectionId) return;
213
+ const alias = eventAlias(event.id, event.recipientEmail);
214
+ try {
215
+ await this.withAM(async (am) => {
216
+ const artifact = await am.create({
217
+ alias,
218
+ parent_id: this.eventCollectionId,
219
+ type: "user-event",
220
+ stage: true,
221
+ manifest: event,
222
+ _rkwargs: true
223
+ });
224
+ await am.commit({
225
+ artifact_id: artifact.id,
226
+ _rkwargs: true
227
+ });
228
+ });
229
+ this.log(`[USER EVENT] Published ${event.type} to ${event.recipientEmail}`);
230
+ try {
231
+ this.server.emit?.({
232
+ type: "svamp:user-event",
233
+ data: event,
234
+ to: "*"
235
+ });
236
+ } catch {
237
+ }
238
+ } catch (err) {
239
+ this.log(`[USER EVENT] Failed to publish ${event.type} for ${event.recipientEmail}: ${err.message}`);
240
+ }
241
+ }
242
+ /**
243
+ * List ALL children of a collection, paginating past hypha's default page cap (`limit=100`,
244
+ * server hypha/artifact.py list_children). The svamp-user-events / svamp-shared-sessions
245
+ * collections are GLOBAL across every deployment user, so a single unpaginated list() would
246
+ * return only the newest ~100 rows across ALL recipients — silently hiding a given user's
247
+ * events past that page. We page offset+=100 until a short/empty/duplicate page.
248
+ */
249
+ async listAllChildren(parentId) {
250
+ const PAGE = 100;
251
+ const all = [];
252
+ const seen = /* @__PURE__ */ new Set();
253
+ for (let offset = 0; ; offset += PAGE) {
254
+ const page = await this.withAM((am) => am.list({ parent_id: parentId, offset, limit: PAGE, _rkwargs: true }));
255
+ if (!Array.isArray(page) || page.length === 0) break;
256
+ let fresh = 0;
257
+ for (const c of page) {
258
+ const k = String(c?.id ?? c?.manifest?.id ?? c?.alias ?? `${offset}:${fresh}`);
259
+ if (seen.has(k)) continue;
260
+ seen.add(k);
261
+ all.push(c);
262
+ fresh++;
263
+ }
264
+ if (page.length < PAGE || fresh === 0) break;
265
+ }
266
+ return all;
267
+ }
268
+ /**
269
+ * List user events addressed to `recipientEmail` (the bell inbox), newest first, excluding
270
+ * expired ones. Reuses the global svamp-user-events collection — cross-workspace by design.
271
+ * Mirrors the webapp's fetchUserEvents so the CLI reads the same source of truth. Paginated
272
+ * so a recipient's events past the first 100 global rows are not silently dropped; expired
273
+ * events the caller owns are best-effort pruned (a no-op until the collection's
274
+ * recipient_can_delete is active — swallowed on PermissionError).
275
+ */
276
+ async listUserEvents(recipientEmail) {
277
+ if (!this.initialized || !this.eventCollectionId) return [];
278
+ const normalized = recipientEmail.toLowerCase();
279
+ try {
280
+ const children = await this.listAllChildren(this.eventCollectionId);
281
+ if (!Array.isArray(children)) return [];
282
+ const now = Date.now();
283
+ const live = [];
284
+ const expiredOwnedIds = [];
285
+ for (const c of children) {
286
+ const m = c?.manifest;
287
+ if (!m || typeof m.recipientEmail !== "string" || m.recipientEmail.toLowerCase() !== normalized) continue;
288
+ if (m.expiresAt && m.expiresAt <= now) {
289
+ if (c?.id) expiredOwnedIds.push(c.id);
290
+ continue;
291
+ }
292
+ live.push(m);
293
+ }
294
+ for (const id of expiredOwnedIds) {
295
+ this.withAM((am) => am.delete({ artifact_id: id, _rkwargs: true })).catch(() => {
296
+ });
297
+ }
298
+ return live.sort((a, b) => (b.createdAt || 0) - (a.createdAt || 0));
299
+ } catch (err) {
300
+ this.log(`[USER EVENT] listUserEvents failed for ${recipientEmail}: ${err.message}`);
301
+ return [];
302
+ }
303
+ }
304
+ /**
305
+ * Acknowledge (dismiss) a user event by deleting its artifact. The alias is derived from the
306
+ * event id + recipient email, so only the recipient's own event is removed. Returns true if a
307
+ * matching event was deleted.
308
+ */
309
+ async ackUserEvent(eventId, recipientEmail) {
310
+ if (!this.initialized || !this.eventCollectionId) return false;
311
+ const normalized = recipientEmail.toLowerCase();
312
+ try {
313
+ const children = await this.listAllChildren(this.eventCollectionId);
314
+ if (!Array.isArray(children)) return false;
315
+ const short = eventId.slice(0, 8);
316
+ const match = children.find((c) => {
317
+ const m = c?.manifest;
318
+ return m && typeof m.id === "string" && (m.id === eventId || m.id.slice(0, 8) === short) && typeof m.recipientEmail === "string" && m.recipientEmail.toLowerCase() === normalized;
319
+ });
320
+ if (!match) return false;
321
+ await this.withAM((am) => am.delete({ artifact_id: match.id, _rkwargs: true }));
322
+ this.log(`[USER EVENT] Acked ${eventId.slice(0, 8)} for ${recipientEmail}`);
323
+ return true;
324
+ } catch (err) {
325
+ this.log(`[USER EVENT] ackUserEvent failed for ${recipientEmail}: ${err.message}`);
326
+ return false;
327
+ }
328
+ }
329
+ // ── Sharing config diff + sync ───────────────────────────────────
330
+ async syncSharing(sessionId, oldSharing, newSharing, context) {
331
+ if (!this.initialized) return;
332
+ const oldRoleByEmail = new Map(
333
+ (oldSharing?.allowedUsers || []).map((u) => [u.email.toLowerCase(), u.role])
334
+ );
335
+ const oldEmails = new Set(oldRoleByEmail.keys());
336
+ const newUsers = newSharing.allowedUsers || [];
337
+ const newEmails = new Set(newUsers.map((u) => u.email.toLowerCase()));
338
+ for (const user of newUsers) {
339
+ const prevRole = oldRoleByEmail.get(user.email.toLowerCase());
340
+ if (prevRole === void 0 || prevRole !== user.role) {
341
+ this.notifyShare({
342
+ recipientEmail: user.email,
343
+ sessionId,
344
+ role: user.role,
345
+ ...context
346
+ }).catch(() => {
347
+ });
348
+ }
349
+ }
350
+ for (const email of oldEmails) {
351
+ if (!newEmails.has(email)) {
352
+ this.removeNotification(sessionId, email).catch(() => {
353
+ });
354
+ }
355
+ }
356
+ }
357
+ }
358
+
359
+ export { SharingNotificationSync as S, SYSTEM_COLLECTION_CONFIG as a, computeCollectionConfigUpdate as c };
@@ -1,25 +1,5 @@
1
- import { R as READ_ONLY_TOOLS, Q as loadMachineContext, T as buildMachineInstructions, U as machineToolsForRole, V as buildMachineTools } from './run-CF4kAVPW.mjs';
1
+ import { R as READ_ONLY_TOOLS, l as loadMachineContext, b as buildMachineInstructions, m as machineToolsForRole, a as buildMachineTools } from './machineTools-DygM1jXW.mjs';
2
2
  import 'node:child_process';
3
- import 'os';
4
- import 'fs/promises';
5
- import 'fs';
6
- import 'path';
7
- import 'url';
8
- import 'child_process';
9
- import 'crypto';
10
- import 'node:fs';
11
- import 'node:path';
12
- import 'node:crypto';
13
- import 'util';
14
- import 'http';
15
- import 'net';
16
- import 'node:os';
17
- import 'node:events';
18
- import '@agentclientprotocol/sdk';
19
- import 'node:util';
20
- import 'node:readline';
21
- import 'node:fs/promises';
22
- import 'yaml';
23
3
 
24
4
  function toolsForRole(role) {
25
5
  if (role === "admin") return [...READ_ONLY_TOOLS, "run_bash", "send_to_session"];