@hyperdrive.bot/fleet-server 0.3.99 → 0.3.102

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 (73) hide show
  1. package/dist/server/server/ingestion/composio/gateway.js +6 -1
  2. package/dist/server/server/ingestion/pipedream/gateway.js +7 -0
  3. package/dist/server/server/ingestion/service.d.ts +40 -0
  4. package/dist/server/server/ingestion/service.js +119 -8
  5. package/dist/server/server/ingestion/types.d.ts +9 -0
  6. package/dist/server/server/session/ingestion/source-session.d.ts +10 -0
  7. package/dist/server/server/session/ingestion/source-session.js +25 -0
  8. package/dist/server/server/session.js +11 -3
  9. package/dist/server/web-ui/_expo/static/js/web/{desktop-attachment-bridge-67f19cbfce758da6aa23c38c2dbd6a33.js → desktop-attachment-bridge-6beae08a69e097c7f552119696c67413.js} +5 -5
  10. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-6beae08a69e097c7f552119696c67413.js.br +0 -0
  11. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-6beae08a69e097c7f552119696c67413.js.gz +0 -0
  12. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-6beae08a69e097c7f552119696c67413.js.map.br +0 -0
  13. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-6beae08a69e097c7f552119696c67413.js.map.gz +0 -0
  14. package/dist/server/web-ui/_expo/static/js/web/{desktop-attachment-store-6888615a6b700f975c1f78501ad8577c.js → desktop-attachment-store-7a589090b52127457319ba733fcca36f.js} +3 -3
  15. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-7a589090b52127457319ba733fcca36f.js.br +0 -0
  16. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-7a589090b52127457319ba733fcca36f.js.gz +0 -0
  17. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-7a589090b52127457319ba733fcca36f.js.map.br +0 -0
  18. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-7a589090b52127457319ba733fcca36f.js.map.gz +0 -0
  19. package/dist/server/web-ui/_expo/static/js/web/{index-dc1e81e7cbad84d30a01c48dcc9a5a3e.js → index-ea21f43eebd5b96bf3cb1be6496567ed.js} +30 -29
  20. package/dist/server/web-ui/_expo/static/js/web/index-ea21f43eebd5b96bf3cb1be6496567ed.js.br +0 -0
  21. package/dist/server/web-ui/_expo/static/js/web/index-ea21f43eebd5b96bf3cb1be6496567ed.js.gz +0 -0
  22. package/dist/server/web-ui/_expo/static/js/web/index-ea21f43eebd5b96bf3cb1be6496567ed.js.map.br +0 -0
  23. package/dist/server/web-ui/_expo/static/js/web/{index-dc1e81e7cbad84d30a01c48dcc9a5a3e.js.map.gz → index-ea21f43eebd5b96bf3cb1be6496567ed.js.map.gz} +0 -0
  24. package/dist/server/web-ui/_expo/static/js/web/{indexeddb-attachment-store-991ae7fd00f9fed45084fbb39160e8b8.js → indexeddb-attachment-store-425dc000c40ab14c7de479c8a86e55b3.js} +3 -3
  25. package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-425dc000c40ab14c7de479c8a86e55b3.js.br +0 -0
  26. package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-425dc000c40ab14c7de479c8a86e55b3.js.gz +0 -0
  27. package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-425dc000c40ab14c7de479c8a86e55b3.js.map.br +0 -0
  28. package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-425dc000c40ab14c7de479c8a86e55b3.js.map.gz +0 -0
  29. package/dist/server/web-ui/_expo/static/js/web/{native-file-attachment-store-fdb4d787a259ce350f76280ca0e2bfa2.js → native-file-attachment-store-5a21541a0e9eb4097726331dae9f2952.js} +5 -5
  30. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-5a21541a0e9eb4097726331dae9f2952.js.br +0 -0
  31. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-5a21541a0e9eb4097726331dae9f2952.js.gz +0 -0
  32. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-5a21541a0e9eb4097726331dae9f2952.js.map.br +0 -0
  33. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-5a21541a0e9eb4097726331dae9f2952.js.map.gz +0 -0
  34. package/dist/server/web-ui/_expo/static/js/web/{profiler-9502a8e721925a6353ec45f7613f537e.js → profiler-d61e032c3f43b3aa19e02fb97c3f7331.js} +11 -11
  35. package/dist/server/web-ui/_expo/static/js/web/profiler-d61e032c3f43b3aa19e02fb97c3f7331.js.br +0 -0
  36. package/dist/server/web-ui/_expo/static/js/web/profiler-d61e032c3f43b3aa19e02fb97c3f7331.js.gz +0 -0
  37. package/dist/server/web-ui/_expo/static/js/web/profiler-d61e032c3f43b3aa19e02fb97c3f7331.js.map.br +0 -0
  38. package/dist/server/web-ui/_expo/static/js/web/profiler-d61e032c3f43b3aa19e02fb97c3f7331.js.map.gz +0 -0
  39. package/dist/server/web-ui/_expo/static/js/web/{startRecording-4ed5094aa059a0fc724f5ae70dd2f693.js → startRecording-1bb242d6754dee4266ba9a09a252ccea.js} +60 -60
  40. package/dist/server/web-ui/_expo/static/js/web/startRecording-1bb242d6754dee4266ba9a09a252ccea.js.br +0 -0
  41. package/dist/server/web-ui/_expo/static/js/web/startRecording-1bb242d6754dee4266ba9a09a252ccea.js.gz +0 -0
  42. package/dist/server/web-ui/_expo/static/js/web/startRecording-1bb242d6754dee4266ba9a09a252ccea.js.map.br +0 -0
  43. package/dist/server/web-ui/_expo/static/js/web/{startRecording-4ed5094aa059a0fc724f5ae70dd2f693.js.map.gz → startRecording-1bb242d6754dee4266ba9a09a252ccea.js.map.gz} +0 -0
  44. package/dist/server/web-ui/index.html +1 -1
  45. package/dist/server/web-ui/index.html.br +0 -0
  46. package/dist/server/web-ui/index.html.gz +0 -0
  47. package/package.json +6 -6
  48. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-67f19cbfce758da6aa23c38c2dbd6a33.js.br +0 -0
  49. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-67f19cbfce758da6aa23c38c2dbd6a33.js.gz +0 -0
  50. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-67f19cbfce758da6aa23c38c2dbd6a33.js.map.br +0 -0
  51. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-67f19cbfce758da6aa23c38c2dbd6a33.js.map.gz +0 -0
  52. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-6888615a6b700f975c1f78501ad8577c.js.br +0 -0
  53. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-6888615a6b700f975c1f78501ad8577c.js.gz +0 -0
  54. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-6888615a6b700f975c1f78501ad8577c.js.map.br +0 -0
  55. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-6888615a6b700f975c1f78501ad8577c.js.map.gz +0 -0
  56. package/dist/server/web-ui/_expo/static/js/web/index-dc1e81e7cbad84d30a01c48dcc9a5a3e.js.br +0 -0
  57. package/dist/server/web-ui/_expo/static/js/web/index-dc1e81e7cbad84d30a01c48dcc9a5a3e.js.gz +0 -0
  58. package/dist/server/web-ui/_expo/static/js/web/index-dc1e81e7cbad84d30a01c48dcc9a5a3e.js.map.br +0 -0
  59. package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-991ae7fd00f9fed45084fbb39160e8b8.js.br +0 -0
  60. package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-991ae7fd00f9fed45084fbb39160e8b8.js.gz +0 -0
  61. package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-991ae7fd00f9fed45084fbb39160e8b8.js.map.br +0 -0
  62. package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-991ae7fd00f9fed45084fbb39160e8b8.js.map.gz +0 -0
  63. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-fdb4d787a259ce350f76280ca0e2bfa2.js.br +0 -0
  64. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-fdb4d787a259ce350f76280ca0e2bfa2.js.gz +0 -0
  65. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-fdb4d787a259ce350f76280ca0e2bfa2.js.map.br +0 -0
  66. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-fdb4d787a259ce350f76280ca0e2bfa2.js.map.gz +0 -0
  67. package/dist/server/web-ui/_expo/static/js/web/profiler-9502a8e721925a6353ec45f7613f537e.js.br +0 -0
  68. package/dist/server/web-ui/_expo/static/js/web/profiler-9502a8e721925a6353ec45f7613f537e.js.gz +0 -0
  69. package/dist/server/web-ui/_expo/static/js/web/profiler-9502a8e721925a6353ec45f7613f537e.js.map.br +0 -0
  70. package/dist/server/web-ui/_expo/static/js/web/profiler-9502a8e721925a6353ec45f7613f537e.js.map.gz +0 -0
  71. package/dist/server/web-ui/_expo/static/js/web/startRecording-4ed5094aa059a0fc724f5ae70dd2f693.js.br +0 -0
  72. package/dist/server/web-ui/_expo/static/js/web/startRecording-4ed5094aa059a0fc724f5ae70dd2f693.js.gz +0 -0
  73. package/dist/server/web-ui/_expo/static/js/web/startRecording-4ed5094aa059a0fc724f5ae70dd2f693.js.map.br +0 -0
@@ -375,7 +375,12 @@ export function createComposioGateway(config) {
375
375
  if (!item.id) {
376
376
  continue;
377
377
  }
378
- accounts.push({ id: item.id, status: normalizeStatus(item.status) });
378
+ // Composio's connected-account payload carries no email or display
379
+ // name for the authorized account - `user_id` is the caller-supplied
380
+ // external id, which is the daemon's own tenant string here and so
381
+ // identifies nothing about the user. Null rather than a value that
382
+ // would look like an identity and not be one.
383
+ accounts.push({ id: item.id, status: normalizeStatus(item.status), label: null });
379
384
  }
380
385
  return accounts;
381
386
  },
@@ -181,6 +181,13 @@ export function createPipedreamGateway(config) {
181
181
  accounts.push({
182
182
  id: account.id,
183
183
  status: account.healthy === false ? "inactive" : "active",
184
+ // Pipedream's `name` is the account's custom name, which for an OAuth
185
+ // app is normally the address the user authorized with. It is
186
+ // optional in the SDK type, so it is coalesced rather than assumed -
187
+ // an absent label renders as "Not linked to an account", never as the
188
+ // opaque `apn_...` id, which would be an identifier pretending to be
189
+ // an identity.
190
+ label: account.name ?? null,
184
191
  });
185
192
  }
186
193
  return accounts;
@@ -66,6 +66,15 @@ export declare class IngestionService {
66
66
  */
67
67
  connect(kind: SourceKind, appSlug: string): Promise<SourceConnectResult>;
68
68
  list(): Promise<SourceSummary[]>;
69
+ /**
70
+ * sourceId -> number of filters watching it.
71
+ *
72
+ * Best-effort: a filter store that cannot be read must not blank the sources
73
+ * list, because the count is a decoration on a row whose real subject is the
74
+ * connection. Degrades to an empty map, so every row reports zero rather than
75
+ * the whole screen reporting nothing.
76
+ */
77
+ private filterCounts;
69
78
  /** Check one source and persist the verdict. Null when the source is gone. */
70
79
  check(sourceId: string): Promise<SourceSummary | null>;
71
80
  private hasConfiguredGateway;
@@ -137,6 +146,37 @@ export declare class IngestionService {
137
146
  * uniformly cheerful `true`.
138
147
  */
139
148
  deleteFilter(filterId: string): Promise<boolean>;
149
+ /**
150
+ * The catalogue's own name and icon for an app slug.
151
+ *
152
+ * Best-effort by design: a catalogue call that fails or returns no match must
153
+ * not fail a connect whose OAuth handoff already succeeded, so it degrades to
154
+ * exactly what this method replaced (the raw slug, no icon) rather than
155
+ * throwing. `imgSrc` stays `""` in that case, matching what every source
156
+ * written before this change already holds on disk.
157
+ */
158
+ private resolvePresentation;
159
+ /**
160
+ * Forget a source and every filter bound to it.
161
+ *
162
+ * LOCAL ONLY. The aggregator keeps the grant, so this is "paseo stops
163
+ * watching", not "the provider forgot you" - and the confirm copy must say
164
+ * that, because promising a revoke paseo does not perform is the same class
165
+ * of lie as the connect sheet's old "it updates on its own".
166
+ *
167
+ * Filters CASCADE rather than being orphaned. A filter whose `sourceId` no
168
+ * longer resolves is not inert: it resolves to `SOURCE_MISSING` and shows up
169
+ * as a broken row forever, so leaving them would trade one piece of
170
+ * unremovable state for another.
171
+ *
172
+ * The in-flight connect poll is cancelled FIRST. It writes health on a timer,
173
+ * so deleting the record underneath a live poll would let it recreate state
174
+ * for a source the user just removed.
175
+ */
176
+ disconnect(sourceId: string): Promise<{
177
+ removed: boolean;
178
+ removedFilterIds: string[];
179
+ }>;
140
180
  /**
141
181
  * Field by field on purpose. A `...stored` spread would mean a field added to
142
182
  * StoredSource later reaches the wire the day it is added, with no diff on
@@ -104,13 +104,25 @@ export class IngestionService {
104
104
  const gateway = this.resolveGateway(kind);
105
105
  const handoff = await gateway.connect(this.externalUserId, appSlug);
106
106
  const timestamp = this.now().toISOString();
107
+ // The catalogue already knows this app as "Google Calendar" with a real
108
+ // icon, and the client rendered exactly that one tap earlier. Writing the
109
+ // raw slug threw both away at the moment of persisting, so every row read
110
+ // "google_calendar" with no icon forever after.
111
+ //
112
+ // Resolved daemon-side rather than accepted from the client: a client that
113
+ // could supply displayName and imgSrc could write the strings the row
114
+ // renders, and store.test.ts already guards against exactly that shape.
115
+ // Best-effort - a catalogue hiccup must not fail a connect that otherwise
116
+ // succeeded, so it falls back to the previous behaviour.
117
+ const presentation = await this.resolvePresentation(gateway, appSlug);
107
118
  const source = await this.store.create({
108
119
  kind,
109
120
  appSlug,
110
- displayName: appSlug,
111
- imgSrc: "",
121
+ displayName: presentation.displayName,
122
+ imgSrc: presentation.imgSrc,
112
123
  externalUserId: this.externalUserId,
113
124
  externalAccountId: null,
125
+ accountLabel: null,
114
126
  status: "connecting",
115
127
  createdAt: timestamp,
116
128
  updatedAt: timestamp,
@@ -118,11 +130,37 @@ export class IngestionService {
118
130
  lastError: null,
119
131
  });
120
132
  this.startConnectPoll(source);
121
- return { source: this.toSummary(source), redirectUrl: handoff.redirectUrl };
133
+ // 0 is correct and not a placeholder: a source created moments ago cannot
134
+ // have a filter bound to it yet.
135
+ return { source: this.toSummary(source, 0), redirectUrl: handoff.redirectUrl };
122
136
  }
123
137
  async list() {
124
138
  const sources = await this.store.list();
125
- return sources.map((source) => this.toSummary(source));
139
+ // ONE filter read for the whole list, not one per source. `list()` is
140
+ // readdir + parse of every filter file, so calling it inside the map would
141
+ // make a nine-source list do nine full scans of the same directory.
142
+ const counts = await this.filterCounts();
143
+ return sources.map((source) => this.toSummary(source, counts.get(source.id) ?? 0));
144
+ }
145
+ /**
146
+ * sourceId -> number of filters watching it.
147
+ *
148
+ * Best-effort: a filter store that cannot be read must not blank the sources
149
+ * list, because the count is a decoration on a row whose real subject is the
150
+ * connection. Degrades to an empty map, so every row reports zero rather than
151
+ * the whole screen reporting nothing.
152
+ */
153
+ async filterCounts() {
154
+ const counts = new Map();
155
+ try {
156
+ for (const filter of await this.filterStore.list()) {
157
+ counts.set(filter.sourceId, (counts.get(filter.sourceId) ?? 0) + 1);
158
+ }
159
+ }
160
+ catch (error) {
161
+ this.logger.warn({ err: error }, "Filter count unavailable; reporting zero per source");
162
+ }
163
+ return counts;
126
164
  }
127
165
  /** Check one source and persist the verdict. Null when the source is gone. */
128
166
  async check(sourceId) {
@@ -132,7 +170,14 @@ export class IngestionService {
132
170
  }
133
171
  const health = await checkSource(source, this.healthDeps());
134
172
  const updated = await this.persistHealth(sourceId, health);
135
- return updated ? this.toSummary(updated) : null;
173
+ if (!updated) {
174
+ return null;
175
+ }
176
+ // Counted, not defaulted: `check` returns an existing source, and reporting
177
+ // zero filters for a source that has some would let a re-check silently
178
+ // blank the row's work line.
179
+ const counts = await this.filterCounts();
180
+ return this.toSummary(updated, counts.get(updated.id) ?? 0);
136
181
  }
137
182
  hasConfiguredGateway() {
138
183
  return SOURCE_CAPABLE_KINDS.some((kind) => this.resolveGateway(kind).configured);
@@ -205,7 +250,7 @@ export class IngestionService {
205
250
  }
206
251
  const active = accounts.find((account) => account.status === "active");
207
252
  if (active) {
208
- await this.persistHealth(source.id, { status: "active", lastError: null }, active.id);
253
+ await this.persistHealth(source.id, { status: "active", lastError: null }, active.id, active.label);
209
254
  return;
210
255
  }
211
256
  await this.sleep(CONNECT_POLL_INTERVAL_MS, state);
@@ -254,7 +299,7 @@ export class IngestionService {
254
299
  * Re-reads the record first so a source deleted while a poll or a sweep was
255
300
  * in flight is not resurrected by a stale write.
256
301
  */
257
- async persistHealth(sourceId, health, externalAccountId) {
302
+ async persistHealth(sourceId, health, externalAccountId, accountLabel) {
258
303
  const current = await this.store.get(sourceId);
259
304
  if (!current) {
260
305
  return null;
@@ -267,6 +312,7 @@ export class IngestionService {
267
312
  lastCheckedAt: timestamp,
268
313
  updatedAt: timestamp,
269
314
  ...(externalAccountId !== undefined ? { externalAccountId } : {}),
315
+ ...(accountLabel !== undefined ? { accountLabel } : {}),
270
316
  };
271
317
  await this.store.put(next);
272
318
  return next;
@@ -340,12 +386,75 @@ export class IngestionService {
340
386
  await this.filterStore.delete(filterId);
341
387
  return true;
342
388
  }
389
+ /**
390
+ * The catalogue's own name and icon for an app slug.
391
+ *
392
+ * Best-effort by design: a catalogue call that fails or returns no match must
393
+ * not fail a connect whose OAuth handoff already succeeded, so it degrades to
394
+ * exactly what this method replaced (the raw slug, no icon) rather than
395
+ * throwing. `imgSrc` stays `""` in that case, matching what every source
396
+ * written before this change already holds on disk.
397
+ */
398
+ async resolvePresentation(gateway, appSlug) {
399
+ try {
400
+ const page = await gateway.listSources(appSlug);
401
+ const match = page.items.find((entry) => entry.slug === appSlug);
402
+ if (match) {
403
+ return { displayName: match.name, imgSrc: match.imgSrc };
404
+ }
405
+ }
406
+ catch (error) {
407
+ // Logged, never swallowed silently: if this degrades in production the
408
+ // symptom is a row that says "google_calendar", and a bare catch would
409
+ // make that indistinguishable from an aggregator that simply has no
410
+ // catalogue entry.
411
+ this.logger.warn({ err: error, appSlug }, "Catalogue lookup failed; falling back to slug");
412
+ }
413
+ return { displayName: appSlug, imgSrc: "" };
414
+ }
415
+ /**
416
+ * Forget a source and every filter bound to it.
417
+ *
418
+ * LOCAL ONLY. The aggregator keeps the grant, so this is "paseo stops
419
+ * watching", not "the provider forgot you" - and the confirm copy must say
420
+ * that, because promising a revoke paseo does not perform is the same class
421
+ * of lie as the connect sheet's old "it updates on its own".
422
+ *
423
+ * Filters CASCADE rather than being orphaned. A filter whose `sourceId` no
424
+ * longer resolves is not inert: it resolves to `SOURCE_MISSING` and shows up
425
+ * as a broken row forever, so leaving them would trade one piece of
426
+ * unremovable state for another.
427
+ *
428
+ * The in-flight connect poll is cancelled FIRST. It writes health on a timer,
429
+ * so deleting the record underneath a live poll would let it recreate state
430
+ * for a source the user just removed.
431
+ */
432
+ async disconnect(sourceId) {
433
+ const existing = await this.store.get(sourceId);
434
+ if (existing === null) {
435
+ return { removed: false, removedFilterIds: [] };
436
+ }
437
+ // Cancel BEFORE deleting. The poll writes health on a 2s timer, so
438
+ // removing the record underneath a live poll lets it persist state for a
439
+ // source the user just removed. `cancel()` also wakes the sleep, so the
440
+ // loop exits at its next check rather than after the full interval; the
441
+ // poll's own `finally` removes the map entry.
442
+ this.connectPolls.get(sourceId)?.cancel();
443
+ const filters = await this.filterStore.list();
444
+ const bound = filters.filter((filter) => filter.sourceId === sourceId);
445
+ for (const filter of bound) {
446
+ await this.filterStore.delete(filter.id);
447
+ }
448
+ await this.store.delete(sourceId);
449
+ this.logger.info({ sourceId, removedFilters: bound.length }, "Source disconnected and its filters removed");
450
+ return { removed: true, removedFilterIds: bound.map((filter) => filter.id) };
451
+ }
343
452
  /**
344
453
  * Field by field on purpose. A `...stored` spread would mean a field added to
345
454
  * StoredSource later reaches the wire the day it is added, with no diff on
346
455
  * this file to review.
347
456
  */
348
- toSummary(source) {
457
+ toSummary(source, filterCount = 0) {
349
458
  return {
350
459
  id: source.id,
351
460
  kind: source.kind,
@@ -353,11 +462,13 @@ export class IngestionService {
353
462
  displayName: source.displayName,
354
463
  imgSrc: source.imgSrc,
355
464
  externalAccountId: source.externalAccountId,
465
+ accountLabel: source.accountLabel,
356
466
  status: source.status,
357
467
  createdAt: source.createdAt,
358
468
  updatedAt: source.updatedAt,
359
469
  lastCheckedAt: source.lastCheckedAt,
360
470
  lastError: source.lastError,
471
+ filterCount,
361
472
  };
362
473
  }
363
474
  }
@@ -55,6 +55,15 @@ export interface SourceConnectHandoff {
55
55
  export interface SourceAccount {
56
56
  id: string;
57
57
  status: "active" | "pending" | "inactive";
58
+ /**
59
+ * The human name of the account, when the aggregator gives one - typically
60
+ * the email for an OAuth app. Null when it does not.
61
+ *
62
+ * Both gateways already receive this on a call they already make and drop it
63
+ * on the floor, which is why a connected row could only ever say "gmail" and
64
+ * never WHICH gmail.
65
+ */
66
+ label: string | null;
58
67
  }
59
68
  export interface PipedreamConfig {
60
69
  clientId?: string | undefined;
@@ -74,5 +74,15 @@ export declare class SourceSession {
74
74
  handleIngestionFilterDeleteRequest(request: Extract<SessionInboundMessage, {
75
75
  type: "ingestion/filter/delete";
76
76
  }>): Promise<void>;
77
+ /**
78
+ * Forget a source and the filters bound to it.
79
+ *
80
+ * `removed: false` with `error: null` says the id resolved to nothing, which
81
+ * is an answer: disconnecting twice is idempotent, and reporting the second
82
+ * call as a failure would make a double-tap look like a broken daemon.
83
+ */
84
+ handleSourceDisconnectRequest(request: Extract<SessionInboundMessage, {
85
+ type: "source/disconnect";
86
+ }>): Promise<void>;
77
87
  }
78
88
  //# sourceMappingURL=source-session.d.ts.map
@@ -187,5 +187,30 @@ export class SourceSession {
187
187
  this.emitSourceRpcError(request, error);
188
188
  }
189
189
  }
190
+ /**
191
+ * Forget a source and the filters bound to it.
192
+ *
193
+ * `removed: false` with `error: null` says the id resolved to nothing, which
194
+ * is an answer: disconnecting twice is idempotent, and reporting the second
195
+ * call as a failure would make a double-tap look like a broken daemon.
196
+ */
197
+ async handleSourceDisconnectRequest(request) {
198
+ try {
199
+ const result = await this.ingestionService.disconnect(request.sourceId);
200
+ this.host.emit({
201
+ type: "source/disconnect/response",
202
+ payload: {
203
+ requestId: request.requestId,
204
+ sourceId: request.sourceId,
205
+ removed: result.removed,
206
+ removedFilterIds: result.removedFilterIds,
207
+ error: null,
208
+ },
209
+ });
210
+ }
211
+ catch (error) {
212
+ this.emitSourceRpcError(request, error);
213
+ }
214
+ }
190
215
  }
191
216
  //# sourceMappingURL=source-session.js.map
@@ -1747,6 +1747,8 @@ export class Session {
1747
1747
  return this.sourceSession.handleSourceListRequest(msg);
1748
1748
  case "source/check":
1749
1749
  return this.sourceSession.handleSourceCheckRequest(msg);
1750
+ case "source/disconnect":
1751
+ return this.sourceSession.handleSourceDisconnectRequest(msg);
1750
1752
  // Epic 7 filter authoring. Routed to SourceSession rather than
1751
1753
  // ChatIngestionSession because these need no chat context: they are
1752
1754
  // store CRUD, and SourceSession is already the source-scoped surface.
@@ -3420,9 +3422,15 @@ export class Session {
3420
3422
  if (!normalized.cwd) {
3421
3423
  throw new Error("Import requires cwd from the selected provider session");
3422
3424
  }
3423
- // An imported agent mints its own workspace; ownership is its workspaceId,
3424
- // never an existing same-cwd workspace resolved by path.
3425
- const workspace = await this.workspaceProvisioning.createWorkspaceForDirectory(normalized.cwd);
3425
+ // Look before you mint. This used to call createWorkspaceForDirectory
3426
+ // unconditionally, so importing a session whose cwd ALREADY had a workspace
3427
+ // produced a second one and the imported agent surfaced there instead of in
3428
+ // the workspace on screen. Same class as the palette (0.3.38),
3429
+ // resolveOrCreateWorkspaceIdForCreateAgent (MR !44) and
3430
+ // ensureWorkspaceForCreate (MR !80); import was the last call site still
3431
+ // minting. findOrCreateWorkspaceForDirectory keeps the never-seen-cwd case
3432
+ // minting, which is the only case where a new workspace is correct.
3433
+ const workspace = await this.workspaceProvisioning.findOrCreateWorkspaceForDirectory(normalized.cwd);
3426
3434
  const { snapshot, timelineSize } = await importProviderSession({
3427
3435
  request: normalized,
3428
3436
  workspaceId: workspace.workspaceId,
@@ -1,5 +1,5 @@
1
- __d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{value:!0}),e.createDesktopAttachmentBridge=function(){const l=(0,s.createDesktopPreviewUrlResolver)({reader:(0,s.createDesktopFileReader)(),objectUrls:(0,s.createBrowserObjectUrlMinter)()});return{copyFile:t.copyDesktopAttachmentFile,writeBase64:t.writeDesktopAttachmentBase64,writeBytes:t.writeDesktopAttachmentBytes,deleteFile:t.deleteDesktopAttachmentFile,garbageCollect:t.garbageCollectDesktopAttachmentFiles,readFileBase64:s.readDesktopFileBase64,resolvePreviewUrl:t=>l.resolve(t),releasePreviewUrl:t=>l.release(t)}};var t=r(d[0]),s=r(d[1])},4921,[4926,4927]);
2
- __d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{value:!0}),e.writeDesktopAttachmentBase64=async function(n){return await(0,t.invokeDesktopCommand)("write_attachment_base64",{attachmentId:n.attachmentId,base64:n.base64,extension:n.extension??null})},e.writeDesktopAttachmentBytes=async function(n){return await(0,t.invokeDesktopCommand)("write_attachment_bytes",{attachmentId:n.attachmentId,bytes:n.bytes,extension:n.extension??null})},e.copyDesktopAttachmentFile=async function(n){return await(0,t.invokeDesktopCommand)("copy_attachment_file",{attachmentId:n.attachmentId,sourcePath:n.sourcePath,extension:n.extension??null})},e.deleteDesktopAttachmentFile=async function(n){return await(0,t.invokeDesktopCommand)("delete_attachment_file",{path:n.path})},e.garbageCollectDesktopAttachmentFiles=async function(n){return await(0,t.invokeDesktopCommand)("garbage_collect_attachment_files",{referencedIds:[...n.referencedIds]})};var t=r(d[0])},4926,[3419]);
3
- __d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{value:!0}),e.readDesktopFileBase64=s,e.createDesktopFileReader=function(){return{readFileBase64:s}},e.createBrowserObjectUrlMinter=function(){return{tryCreate({mimeType:t,base64:n}){if("undefined"==typeof URL||"function"!=typeof URL.createObjectURL)return null;const o=c(n),s=new ArrayBuffer(o.byteLength);new Uint8Array(s).set(o);const u=new Blob([s],{type:t});return URL.createObjectURL(u)},revoke(t){"undefined"!=typeof URL&&"function"==typeof URL.revokeObjectURL&&URL.revokeObjectURL(t)}}},e.createDesktopPreviewUrlResolver=function(t){const n=new Set;return{async resolve(o){const s=await t.reader.readFileBase64(o.storageKey),c=t.objectUrls.tryCreate({mimeType:o.mimeType,base64:s});return null===c?`data:${o.mimeType};base64,${s}`:(n.add(c),c)},async release({url:o}){n.has(o)&&(n.delete(o),t.objectUrls.revoke(o))}}};var t=r(d[0]),n=r(d[1]),o=r(d[2]);async function s(o){return await(0,n.invokeDesktopCommand)("read_file_base64",{path:(0,t.fileUriToPath)(o)})}function c(t){return new Uint8Array(o.Buffer.from(t,"base64"))}},4927,[4593,3419,52]);
4
- //# sourceMappingURL=/_expo/static/js/web/desktop-attachment-bridge-67f19cbfce758da6aa23c38c2dbd6a33.js.map
5
- //# debugId=c3a25a89-bf01-4243-ac6e-d215e65a50ef
1
+ __d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{value:!0}),e.createDesktopAttachmentBridge=function(){const l=(0,s.createDesktopPreviewUrlResolver)({reader:(0,s.createDesktopFileReader)(),objectUrls:(0,s.createBrowserObjectUrlMinter)()});return{copyFile:t.copyDesktopAttachmentFile,writeBase64:t.writeDesktopAttachmentBase64,writeBytes:t.writeDesktopAttachmentBytes,deleteFile:t.deleteDesktopAttachmentFile,garbageCollect:t.garbageCollectDesktopAttachmentFiles,readFileBase64:s.readDesktopFileBase64,resolvePreviewUrl:t=>l.resolve(t),releasePreviewUrl:t=>l.release(t)}};var t=r(d[0]),s=r(d[1])},4922,[4927,4928]);
2
+ __d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{value:!0}),e.writeDesktopAttachmentBase64=async function(n){return await(0,t.invokeDesktopCommand)("write_attachment_base64",{attachmentId:n.attachmentId,base64:n.base64,extension:n.extension??null})},e.writeDesktopAttachmentBytes=async function(n){return await(0,t.invokeDesktopCommand)("write_attachment_bytes",{attachmentId:n.attachmentId,bytes:n.bytes,extension:n.extension??null})},e.copyDesktopAttachmentFile=async function(n){return await(0,t.invokeDesktopCommand)("copy_attachment_file",{attachmentId:n.attachmentId,sourcePath:n.sourcePath,extension:n.extension??null})},e.deleteDesktopAttachmentFile=async function(n){return await(0,t.invokeDesktopCommand)("delete_attachment_file",{path:n.path})},e.garbageCollectDesktopAttachmentFiles=async function(n){return await(0,t.invokeDesktopCommand)("garbage_collect_attachment_files",{referencedIds:[...n.referencedIds]})};var t=r(d[0])},4927,[3419]);
3
+ __d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{value:!0}),e.readDesktopFileBase64=s,e.createDesktopFileReader=function(){return{readFileBase64:s}},e.createBrowserObjectUrlMinter=function(){return{tryCreate({mimeType:t,base64:n}){if("undefined"==typeof URL||"function"!=typeof URL.createObjectURL)return null;const o=c(n),s=new ArrayBuffer(o.byteLength);new Uint8Array(s).set(o);const u=new Blob([s],{type:t});return URL.createObjectURL(u)},revoke(t){"undefined"!=typeof URL&&"function"==typeof URL.revokeObjectURL&&URL.revokeObjectURL(t)}}},e.createDesktopPreviewUrlResolver=function(t){const n=new Set;return{async resolve(o){const s=await t.reader.readFileBase64(o.storageKey),c=t.objectUrls.tryCreate({mimeType:o.mimeType,base64:s});return null===c?`data:${o.mimeType};base64,${s}`:(n.add(c),c)},async release({url:o}){n.has(o)&&(n.delete(o),t.objectUrls.revoke(o))}}};var t=r(d[0]),n=r(d[1]),o=r(d[2]);async function s(o){return await(0,n.invokeDesktopCommand)("read_file_base64",{path:(0,t.fileUriToPath)(o)})}function c(t){return new Uint8Array(o.Buffer.from(t,"base64"))}},4928,[4593,3419,52]);
4
+ //# sourceMappingURL=/_expo/static/js/web/desktop-attachment-bridge-6beae08a69e097c7f552119696c67413.js.map
5
+ //# debugId=8d0f2f2e-6216-48e2-87c5-51b8c02d5b57
@@ -1,3 +1,3 @@
1
- __d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{value:!0}),e.createDesktopAttachmentStore=function(n){return{storageType:"desktop-file",async save(s){const o=s.id??(0,t.generateAttachmentId)(),l=s.fileName??null;if("file_uri"===s.source.kind)return await c(n,{id:o,uri:s.source.uri,mimeType:s.mimeType,fileName:l});if("data_url"===s.source.kind){const c=(0,t.parseDataUrl)(s.source.dataUrl),p=(0,t.normalizeMimeType)(s.mimeType??c.mimeType);return await y(n,{id:o,base64:c.base64,mimeType:p,fileName:l})}if("bytes"===s.source.kind){const c=(0,t.normalizeMimeType)(s.mimeType);return await p(n,{id:o,bytes:s.source.bytes,mimeType:c,fileName:l})}const f=(0,t.normalizeMimeType)(s.mimeType??s.source.blob.type),u=await(0,t.blobToBase64)(s.source.blob);return await y(n,{id:o,base64:u,mimeType:f,fileName:l})},encodeBase64:async({attachment:t})=>(f(t),await n.readFileBase64(t.storageKey)),resolvePreviewUrl:async({attachment:t})=>(f(t),await n.resolvePreviewUrl(t)),async releasePreviewUrl({attachment:t,url:s}){f(t),await n.releasePreviewUrl({url:s})},async delete({attachment:t}){f(t),await n.deleteFile({path:t.storageKey})},async garbageCollect({referencedIds:t}){await n.garbageCollect({referencedIds:Array.from(t)})}}};var t=r(d[0]);const n={"image/png":".png","image/jpeg":".jpg","image/jpg":".jpg","image/gif":".gif","image/webp":".webp","image/avif":".avif","image/heic":".heic","image/heif":".heif","image/tiff":".tiff","image/bmp":".bmp","image/svg+xml":".svg"};function s(t){const n=t.replace(/\\/g,"/").split("/"),s=n[n.length-1];return s&&s.length>0?s:null}function o(s){const o=(0,t.getFileExtensionFromName)(s.fileName);if(o)return o;const l=(0,t.getFileExtensionFromName)(s.sourcePath);return l||(n[s.mimeType]??".img")}function l(t){return{id:t.id,mimeType:t.mimeType,storageType:"desktop-file",storageKey:t.storagePath,fileName:t.fileName??null,byteSize:t.byteSize,createdAt:Date.now()}}async function c(n,c){const y=(0,t.fileUriToPath)(c.uri),p=c.fileName??s(y),f=(0,t.normalizeMimeType)(c.mimeType),u=o({fileName:p,sourcePath:y,mimeType:f}),T=await n.copyFile({attachmentId:c.id,sourcePath:y,extension:u});return l({id:c.id,mimeType:f,storagePath:T.path,byteSize:T.byteSize,fileName:p})}async function y(t,n){const s=o({fileName:n.fileName,mimeType:n.mimeType}),c=await t.writeBase64({attachmentId:n.id,base64:n.base64,extension:s});return l({id:n.id,mimeType:n.mimeType,storagePath:c.path,byteSize:c.byteSize,fileName:n.fileName})}async function p(t,n){const s=o({fileName:n.fileName,mimeType:n.mimeType}),c=await t.writeBytes({attachmentId:n.id,bytes:n.bytes,extension:s});return l({id:n.id,mimeType:n.mimeType,storagePath:c.path,byteSize:c.byteSize,fileName:n.fileName})}function f(t){if("desktop-file"!==t.storageType)throw new Error(`Unsupported desktop attachment storage type '${t.storageType}'.`)}},4920,[4593]);
2
- //# sourceMappingURL=/_expo/static/js/web/desktop-attachment-store-6888615a6b700f975c1f78501ad8577c.js.map
3
- //# debugId=991ff69a-0e82-495a-90bd-ab4e5ff58a77
1
+ __d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{value:!0}),e.createDesktopAttachmentStore=function(n){return{storageType:"desktop-file",async save(s){const o=s.id??(0,t.generateAttachmentId)(),l=s.fileName??null;if("file_uri"===s.source.kind)return await c(n,{id:o,uri:s.source.uri,mimeType:s.mimeType,fileName:l});if("data_url"===s.source.kind){const c=(0,t.parseDataUrl)(s.source.dataUrl),p=(0,t.normalizeMimeType)(s.mimeType??c.mimeType);return await y(n,{id:o,base64:c.base64,mimeType:p,fileName:l})}if("bytes"===s.source.kind){const c=(0,t.normalizeMimeType)(s.mimeType);return await p(n,{id:o,bytes:s.source.bytes,mimeType:c,fileName:l})}const f=(0,t.normalizeMimeType)(s.mimeType??s.source.blob.type),u=await(0,t.blobToBase64)(s.source.blob);return await y(n,{id:o,base64:u,mimeType:f,fileName:l})},encodeBase64:async({attachment:t})=>(f(t),await n.readFileBase64(t.storageKey)),resolvePreviewUrl:async({attachment:t})=>(f(t),await n.resolvePreviewUrl(t)),async releasePreviewUrl({attachment:t,url:s}){f(t),await n.releasePreviewUrl({url:s})},async delete({attachment:t}){f(t),await n.deleteFile({path:t.storageKey})},async garbageCollect({referencedIds:t}){await n.garbageCollect({referencedIds:Array.from(t)})}}};var t=r(d[0]);const n={"image/png":".png","image/jpeg":".jpg","image/jpg":".jpg","image/gif":".gif","image/webp":".webp","image/avif":".avif","image/heic":".heic","image/heif":".heif","image/tiff":".tiff","image/bmp":".bmp","image/svg+xml":".svg"};function s(t){const n=t.replace(/\\/g,"/").split("/"),s=n[n.length-1];return s&&s.length>0?s:null}function o(s){const o=(0,t.getFileExtensionFromName)(s.fileName);if(o)return o;const l=(0,t.getFileExtensionFromName)(s.sourcePath);return l||(n[s.mimeType]??".img")}function l(t){return{id:t.id,mimeType:t.mimeType,storageType:"desktop-file",storageKey:t.storagePath,fileName:t.fileName??null,byteSize:t.byteSize,createdAt:Date.now()}}async function c(n,c){const y=(0,t.fileUriToPath)(c.uri),p=c.fileName??s(y),f=(0,t.normalizeMimeType)(c.mimeType),u=o({fileName:p,sourcePath:y,mimeType:f}),T=await n.copyFile({attachmentId:c.id,sourcePath:y,extension:u});return l({id:c.id,mimeType:f,storagePath:T.path,byteSize:T.byteSize,fileName:p})}async function y(t,n){const s=o({fileName:n.fileName,mimeType:n.mimeType}),c=await t.writeBase64({attachmentId:n.id,base64:n.base64,extension:s});return l({id:n.id,mimeType:n.mimeType,storagePath:c.path,byteSize:c.byteSize,fileName:n.fileName})}async function p(t,n){const s=o({fileName:n.fileName,mimeType:n.mimeType}),c=await t.writeBytes({attachmentId:n.id,bytes:n.bytes,extension:s});return l({id:n.id,mimeType:n.mimeType,storagePath:c.path,byteSize:c.byteSize,fileName:n.fileName})}function f(t){if("desktop-file"!==t.storageType)throw new Error(`Unsupported desktop attachment storage type '${t.storageType}'.`)}},4921,[4593]);
2
+ //# sourceMappingURL=/_expo/static/js/web/desktop-attachment-store-7a589090b52127457319ba733fcca36f.js.map
3
+ //# debugId=950ba5c5-7992-4432-ad49-c7f52c12d7e4