@ai-matrx/vault 0.1.54 → 0.1.56

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.
package/dist/index.d.cts CHANGED
@@ -3,6 +3,7 @@ import { Look } from '@ai-matrx/design-system';
3
3
  import { ApiStream, NdjsonEvent, JsonOrStream, FollowRuntimeOperationToEndOptions, FollowRuntimeOperationToEndResult, Api } from '@ai-matrx/api';
4
4
  import * as account from '@ai-matrx/api/account';
5
5
  import { VaultPasswordHealthScanRequest, VaultPasswordHealthScanReferenceData, PasswordHealthDurableResult } from '@ai-matrx/api/account';
6
+ import { VaultPasswordHealthDiscoveryPageData } from '@ai-matrx/api/stream';
6
7
  import { OperationStatusResult } from '@ai-matrx/api/architecture';
7
8
  import { VaultIdentity } from './identity.cjs';
8
9
  export { CONFIRM_RETURN_KEY, Confirmed, IdentityAccount, IdentityGate, IdentityMethod, ReturnStore, SupabaseAuthLike, SupabaseIdentityOptions, createSupabaseIdentity, needsRecentSignIn, useConfirmed, wasCancelled } from './identity.cjs';
@@ -89,6 +90,7 @@ interface VaultItem {
89
90
  fields: VaultField[];
90
91
  custom: VaultCustomField[];
91
92
  authenticator: VaultAuthenticator | null;
93
+ authenticatorState: "known" | "pending" | "unavailable";
92
94
  capabilities: VaultItemCapabilities;
93
95
  attachments: VaultAttachment[];
94
96
  uriMatchMode: UriMatchMode;
@@ -222,6 +224,7 @@ type HealthScanOptions = VaultPasswordHealthScanRequest;
222
224
  type HealthResult = PasswordHealthDurableResult;
223
225
  type HealthReference = VaultPasswordHealthScanReferenceData;
224
226
  interface HealthPort {
227
+ discover(cursor: string | undefined, signal: AbortSignal): Promise<VaultPasswordHealthDiscoveryPageData>;
225
228
  start(requestId: string, signal: AbortSignal, options: HealthScanOptions): Promise<ApiStream<NdjsonEvent>>;
226
229
  resume(executionId: string, requestId: string, signal: AbortSignal): Promise<JsonOrStream<HealthReference, NdjsonEvent>>;
227
230
  read(executionId: string, signal: AbortSignal): Promise<HealthResult>;
@@ -321,7 +324,13 @@ type ItemRef = Pick<VaultItem, "id" | "owner">;
321
324
  */
322
325
  interface AuthenticatorPort {
323
326
  /** Every account the signed-in person holds a one-time password for. */
324
- list(): Promise<AuthenticatorEntry[]>;
327
+ list(): Promise<{
328
+ entries: AuthenticatorEntry[];
329
+ unavailableScopes: {
330
+ type: "user" | "organization" | "shared";
331
+ organizationId?: string | null;
332
+ }[];
333
+ }>;
325
334
  /** null when the item has none. */
326
335
  get(item: ItemRef): Promise<AuthenticatorEntry | null>;
327
336
  /** The current code and how long it lives. */
package/dist/index.d.ts CHANGED
@@ -3,6 +3,7 @@ import { Look } from '@ai-matrx/design-system';
3
3
  import { ApiStream, NdjsonEvent, JsonOrStream, FollowRuntimeOperationToEndOptions, FollowRuntimeOperationToEndResult, Api } from '@ai-matrx/api';
4
4
  import * as account from '@ai-matrx/api/account';
5
5
  import { VaultPasswordHealthScanRequest, VaultPasswordHealthScanReferenceData, PasswordHealthDurableResult } from '@ai-matrx/api/account';
6
+ import { VaultPasswordHealthDiscoveryPageData } from '@ai-matrx/api/stream';
6
7
  import { OperationStatusResult } from '@ai-matrx/api/architecture';
7
8
  import { VaultIdentity } from './identity.js';
8
9
  export { CONFIRM_RETURN_KEY, Confirmed, IdentityAccount, IdentityGate, IdentityMethod, ReturnStore, SupabaseAuthLike, SupabaseIdentityOptions, createSupabaseIdentity, needsRecentSignIn, useConfirmed, wasCancelled } from './identity.js';
@@ -89,6 +90,7 @@ interface VaultItem {
89
90
  fields: VaultField[];
90
91
  custom: VaultCustomField[];
91
92
  authenticator: VaultAuthenticator | null;
93
+ authenticatorState: "known" | "pending" | "unavailable";
92
94
  capabilities: VaultItemCapabilities;
93
95
  attachments: VaultAttachment[];
94
96
  uriMatchMode: UriMatchMode;
@@ -222,6 +224,7 @@ type HealthScanOptions = VaultPasswordHealthScanRequest;
222
224
  type HealthResult = PasswordHealthDurableResult;
223
225
  type HealthReference = VaultPasswordHealthScanReferenceData;
224
226
  interface HealthPort {
227
+ discover(cursor: string | undefined, signal: AbortSignal): Promise<VaultPasswordHealthDiscoveryPageData>;
225
228
  start(requestId: string, signal: AbortSignal, options: HealthScanOptions): Promise<ApiStream<NdjsonEvent>>;
226
229
  resume(executionId: string, requestId: string, signal: AbortSignal): Promise<JsonOrStream<HealthReference, NdjsonEvent>>;
227
230
  read(executionId: string, signal: AbortSignal): Promise<HealthResult>;
@@ -321,7 +324,13 @@ type ItemRef = Pick<VaultItem, "id" | "owner">;
321
324
  */
322
325
  interface AuthenticatorPort {
323
326
  /** Every account the signed-in person holds a one-time password for. */
324
- list(): Promise<AuthenticatorEntry[]>;
327
+ list(): Promise<{
328
+ entries: AuthenticatorEntry[];
329
+ unavailableScopes: {
330
+ type: "user" | "organization" | "shared";
331
+ organizationId?: string | null;
332
+ }[];
333
+ }>;
325
334
  /** null when the item has none. */
326
335
  get(item: ItemRef): Promise<AuthenticatorEntry | null>;
327
336
  /** The current code and how long it lives. */
package/dist/index.js CHANGED
@@ -146,7 +146,7 @@ function PasswordHealthView(props) {
146
146
  const latest = useRef(props);
147
147
  latest.current = props;
148
148
  const [result, setResult] = useState2(null);
149
- const [status, setStatus] = useState2(requestId || executionId ? "Checking status" : "No report");
149
+ const [status, setStatus] = useState2("Checking status");
150
150
  const [error, setError] = useState2(null);
151
151
  const [progress, setProgress] = useState2(null);
152
152
  const [busy, setBusy] = useState2(false);
@@ -156,9 +156,13 @@ function PasswordHealthView(props) {
156
156
  }, [port]);
157
157
  const [retry, setRetry] = useState2(0);
158
158
  const active = useRef(null);
159
- const starting = useRef(false);
159
+ const starting = useRef(null);
160
160
  const ownedAddress = useRef(null);
161
161
  const writeReference = (request, execution) => {
162
+ if (starting.current?.controller === active.current) {
163
+ starting.current.request = request;
164
+ starting.current.execution = execution;
165
+ }
162
166
  ownedAddress.current = { request, execution, items: latest.current.items, retry };
163
167
  latest.current.onReference(request, execution);
164
168
  };
@@ -186,7 +190,7 @@ function PasswordHealthView(props) {
186
190
  const value = await port.read(id, controller.signal);
187
191
  if (owns(controller)) {
188
192
  setStatus(STATUS_LABEL[value.execution_status]);
189
- setResult({ value, items: inventory });
193
+ setResult({ value, items: inventory, port });
190
194
  }
191
195
  return value;
192
196
  };
@@ -204,6 +208,27 @@ function PasswordHealthView(props) {
204
208
  }
205
209
  writeReference(request, id);
206
210
  }
211
+ if (!id && !request) {
212
+ let cursor;
213
+ const seen = /* @__PURE__ */ new Set();
214
+ do {
215
+ const page = await port.discover(cursor, controller.signal);
216
+ if (!owns(controller)) return;
217
+ const reference = page.references[0];
218
+ if (reference) {
219
+ id = reference.execution_id;
220
+ writeReference(reference.request_id, reference.execution_id);
221
+ break;
222
+ }
223
+ cursor = page.continuation ?? void 0;
224
+ if (cursor && seen.has(cursor)) throw new Error("Report unavailable");
225
+ if (cursor) seen.add(cursor);
226
+ } while (cursor);
227
+ if (!id) {
228
+ setStatus("No report");
229
+ return;
230
+ }
231
+ }
207
232
  if (!id || !owns(controller)) return;
208
233
  const first = await read(id, controller);
209
234
  if (!owns(controller) || terminal(first.execution_status)) return;
@@ -213,7 +238,7 @@ function PasswordHealthView(props) {
213
238
  if (!owns(controller)) return;
214
239
  const detail = event.detail;
215
240
  if (event.kind === "note" && detail?.label === "password_health_progress" && detail.__kind === "password_health_progress" && typeof detail.checked === "number" && Number.isSafeInteger(detail.checked) && detail.checked >= 0 && typeof detail.skipped === "number" && Number.isSafeInteger(detail.skipped) && detail.skipped >= 0) {
216
- setProgress({ checked: detail.checked, skipped: detail.skipped });
241
+ setProgress({ checked: detail.checked, skipped: detail.skipped, port });
217
242
  }
218
243
  const lifecycle = event.kind === "started" || event.kind === "resumed" ? "running" : event.kind === "created" ? "pending" : event.kind;
219
244
  const label = STATUS_LABEL[lifecycle];
@@ -228,14 +253,16 @@ function PasswordHealthView(props) {
228
253
  if (!inventoryReady) {
229
254
  ownedAddress.current = null;
230
255
  setResult(null);
231
- if (!starting.current) active.current?.abort();
256
+ if (!starting.current || !owns(starting.current.controller)) active.current?.abort();
232
257
  return;
233
258
  }
234
- if (!port || starting.current) return;
259
+ if (!port) return;
260
+ const start = starting.current;
261
+ if (start && owns(start.controller) && start.request === (requestId ?? "") && start.execution === (executionId ?? "")) return;
235
262
  const own = ownedAddress.current;
236
263
  if (own && own.request === requestId && own.execution === executionId && own.items === items && own.retry === retry && active.current && !active.current.signal.aborted) return;
237
264
  const controller = fresh();
238
- setStatus(requestId || executionId ? "Checking status" : "No report");
265
+ setStatus("Checking status");
239
266
  void recover(requestId, executionId, controller).catch((e) => {
240
267
  if (owns(controller)) {
241
268
  setResult(null);
@@ -257,7 +284,7 @@ function PasswordHealthView(props) {
257
284
  const run = async (resume2) => {
258
285
  if (!port) return;
259
286
  const controller = fresh();
260
- starting.current = true;
287
+ starting.current = { controller, request: "", execution: "" };
261
288
  const request = crypto.randomUUID();
262
289
  const options = { check_breaches: checkBreaches };
263
290
  let actualRequest = request;
@@ -286,7 +313,7 @@ function PasswordHealthView(props) {
286
313
  if (data && typeof data === "object" && "type" in data) {
287
314
  if (data.type === "vault_password_health_scan_reference") accept(data);
288
315
  if (data.type === "vault_password_health_progress") {
289
- setProgress(data);
316
+ setProgress({ ...data, port });
290
317
  setStatus("Running");
291
318
  }
292
319
  }
@@ -330,20 +357,22 @@ function PasswordHealthView(props) {
330
357
  setError(problem(e));
331
358
  setStatus((value) => value === "Checking status" ? "Unavailable" : value);
332
359
  } finally {
333
- starting.current = false;
360
+ if (starting.current?.controller === controller) starting.current = null;
334
361
  if (owns(controller)) {
335
362
  setBusy(false);
336
363
  if (startedItems !== latest.current.items) setRetry((v) => v + 1);
337
364
  }
338
365
  }
339
366
  };
340
- const current = inventoryReady && result?.items === items && result.value.availability === "current" ? result.value.snapshot : null;
341
- const available = result?.value.availability;
367
+ const ownedResult = port && result?.port === port ? result : null;
368
+ const shownProgress = port && progress?.port === port ? progress : null;
369
+ const current = inventoryReady && ownedResult?.items === items && ownedResult.value.availability === "current" ? ownedResult.value.snapshot : null;
370
+ const available = ownedResult?.value.availability;
342
371
  const outcomes = current?.check_breaches ? current.breach_outcomes ?? [] : [];
343
372
  const compromised = outcomes.filter((outcome) => outcome.status === "compromised").length;
344
373
  const notFound = outcomes.filter((outcome) => outcome.status === "not_found").length;
345
374
  const unknown = outcomes.filter((outcome) => outcome.status === "unknown").length;
346
- const resume = result && ["failed", "cancelled"].includes(result.value.execution_status);
375
+ const resume = ownedResult && ["failed", "cancelled"].includes(ownedResult.value.execution_status);
347
376
  return /* @__PURE__ */ jsxs2("div", { className: "flex h-full min-h-0 flex-col gap-4 overflow-auto p-4", "aria-label": "Password health", children: [
348
377
  props.header,
349
378
  /* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between gap-2", children: [
@@ -377,9 +406,9 @@ function PasswordHealthView(props) {
377
406
  /* @__PURE__ */ jsx2(InfoHint, { text: "The breach lookup could not establish a result." })
378
407
  ] }), children: unknown })
379
408
  ] }) : null,
380
- !current && progress ? /* @__PURE__ */ jsxs2(Fragment, { children: [
381
- /* @__PURE__ */ jsx2(VaultRow, { label: "Checked", children: progress.checked }),
382
- /* @__PURE__ */ jsx2(VaultRow, { label: "Skipped", children: progress.skipped })
409
+ !current && shownProgress ? /* @__PURE__ */ jsxs2(Fragment, { children: [
410
+ /* @__PURE__ */ jsx2(VaultRow, { label: "Checked", children: shownProgress.checked }),
411
+ /* @__PURE__ */ jsx2(VaultRow, { label: "Skipped", children: shownProgress.skipped })
383
412
  ] }) : null,
384
413
  current ? /* @__PURE__ */ jsxs2(Fragment, { children: [
385
414
  /* @__PURE__ */ jsx2(VaultRow, { label: "Checked", children: current.checked_subjects.length }),
@@ -2137,7 +2166,7 @@ function normalizeWebsite(raw) {
2137
2166
  }
2138
2167
  function AddOtpPanel({ port, items, definitions, itemId = null, onClose, onChanged, onAdded, onDone }) {
2139
2168
  const confirmed = useConfirmed();
2140
- const candidates = items.filter((i) => !i.authenticator && !i.deletion && (i.capabilities.canEdit || i.capabilities.canManage)).sort((a, b) => a.name.localeCompare(b.name, void 0, { sensitivity: "base" }));
2169
+ const candidates = items.filter((i) => i.authenticatorState === "known" && !i.authenticator && !i.deletion && (i.capabilities.canEdit || i.capabilities.canManage)).sort((a, b) => a.name.localeCompare(b.name, void 0, { sensitivity: "base" }));
2141
2170
  const [input, setInput] = useState11("");
2142
2171
  const [image, setImage] = useState11(null);
2143
2172
  const [target, setTarget] = useState11(itemId && candidates.some((c) => c.id === itemId) ? itemId : NEW);
@@ -2176,7 +2205,7 @@ function AddOtpPanel({ port, items, definitions, itemId = null, onClose, onChang
2176
2205
  }
2177
2206
  const it = item;
2178
2207
  const entry = await confirmed(() => image ? port.authenticator.enrolQrImage(it, image, image.name) : port.authenticator.enrol(it, input));
2179
- let next = { ...it, authenticator: { digits: entry.digits, period: entry.period, enabled: entry.enabled } };
2208
+ let next = { ...it, authenticatorState: "known", authenticator: { digits: entry.digits, period: entry.period, enabled: entry.enabled } };
2180
2209
  const better = authenticatorName(it.name, { issuer: entry.issuer, account: entry.account });
2181
2210
  if (!isNew && better !== it.name) {
2182
2211
  try {
@@ -2314,6 +2343,7 @@ function AuthenticatorView({ port, host, items: listed, definitions, query, onOp
2314
2343
  useEffect10(() => {
2315
2344
  if (added.size > 0 && [...added.keys()].every((id) => listed.some((i) => i.id === id && i.authenticator))) setAdded(/* @__PURE__ */ new Map());
2316
2345
  }, [listed, added]);
2346
+ const [incomplete, setIncomplete] = useState12(true);
2317
2347
  const [entries, setEntries] = useState12(/* @__PURE__ */ new Map());
2318
2348
  const [hidden, setHidden] = useState12(/* @__PURE__ */ new Set());
2319
2349
  const [adding, setAdding] = useState12(false);
@@ -2323,8 +2353,15 @@ function AuthenticatorView({ port, host, items: listed, definitions, query, onOp
2323
2353
  useEffect10(() => {
2324
2354
  let live = true;
2325
2355
  port.authenticator.list().then(
2326
- (list) => live && setEntries(new Map(list.map((e) => [e.itemId, e]))),
2327
- (e) => console.error("One-time password accounts could not be read", e)
2356
+ (list) => {
2357
+ if (live) {
2358
+ setEntries(new Map(list.entries.map((e) => [e.itemId, e])));
2359
+ setIncomplete(list.unavailableScopes.length > 0);
2360
+ }
2361
+ },
2362
+ () => {
2363
+ if (live) setIncomplete(true);
2364
+ }
2328
2365
  );
2329
2366
  return () => {
2330
2367
  live = false;
@@ -2342,7 +2379,14 @@ function AuthenticatorView({ port, host, items: listed, definitions, query, onOp
2342
2379
  ] }),
2343
2380
  total > 0 ? /* @__PURE__ */ jsx11(Button9, { variant: "outline", icon: /* @__PURE__ */ jsx11(PlusGlyph, {}), onClick: () => setAdding(true), children: "Add" }) : null
2344
2381
  ] }),
2345
- total === 0 ? /* @__PURE__ */ jsxs10("div", { className: "flex flex-1 flex-col items-center justify-center gap-3 px-6 pb-12 text-center", children: [
2382
+ total > 0 && incomplete ? /* @__PURE__ */ jsxs10("div", { className: "px-4 text-sm text-muted-foreground", children: [
2383
+ "Some codes unavailable ",
2384
+ /* @__PURE__ */ jsx11(Button9, { variant: "quiet", onClick: onChanged, children: "Retry" })
2385
+ ] }) : null,
2386
+ total === 0 && (incomplete || items.some((i) => i.authenticatorState !== "known")) ? /* @__PURE__ */ jsxs10("div", { className: "px-4 py-10 text-center text-sm text-muted-foreground", children: [
2387
+ "Codes unavailable ",
2388
+ /* @__PURE__ */ jsx11(Button9, { variant: "quiet", onClick: onChanged, children: "Retry" })
2389
+ ] }) : total === 0 ? /* @__PURE__ */ jsxs10("div", { className: "flex flex-1 flex-col items-center justify-center gap-3 px-6 pb-12 text-center", children: [
2346
2390
  /* @__PURE__ */ jsx11("span", { className: "flex size-12 items-center justify-center rounded-full bg-muted text-muted-foreground", children: /* @__PURE__ */ jsx11(ShieldGlyph, { className: "size-6" }) }),
2347
2391
  /* @__PURE__ */ jsx11("p", { className: "text-sm font-medium text-foreground", children: "No authenticator codes yet" }),
2348
2392
  /* @__PURE__ */ jsx11(Button9, { variant: "primary", icon: /* @__PURE__ */ jsx11(PlusGlyph, {}), onClick: () => setAdding(true), children: "Add authenticator" })
@@ -6392,7 +6436,10 @@ function ItemPane(props) {
6392
6436
  i
6393
6437
  )),
6394
6438
  /* @__PURE__ */ jsx25(AddRow, { label: draft.loginUrls.length === 0 ? "Website" : "", onAdd: () => patch({ loginUrls: [...draft.loginUrls, ""] }), children: draft.loginUrls.length === 0 ? "Add website" : "Add another website" }),
6395
- /* @__PURE__ */ jsx25(TotpEditRow, { value: draft.authenticator, onChange: (v) => patch({ authenticator: v }), hasExisting: Boolean(item?.authenticator || seedField), item, port, onEnrolled: onChanged }),
6439
+ !item || item.authenticatorState === "known" ? /* @__PURE__ */ jsx25(TotpEditRow, { value: draft.authenticator, onChange: (v) => patch({ authenticator: v }), hasExisting: Boolean(item?.authenticator || seedField), item, port, onEnrolled: onChanged }) : /* @__PURE__ */ jsxs23(VaultRow, { label: "Authenticator", children: [
6440
+ /* @__PURE__ */ jsx25("span", { className: "text-muted-foreground", children: item.authenticatorState === "pending" ? "Loading" : "Unavailable" }),
6441
+ item.authenticatorState === "unavailable" ? /* @__PURE__ */ jsx25(Button22, { variant: "quiet", onClick: () => onChanged?.(), children: "Retry" }) : null
6442
+ ] }),
6396
6443
  item && isLogin && !item.authenticator ? /* @__PURE__ */ jsx25(TwoFactorMethodRow, { port, item, onChanged }) : null,
6397
6444
  !item && isLogin && draft.authenticator.trim() === "" ? /* @__PURE__ */ jsx25(DraftTwoFactorRow, { custom: draft.custom, onCustom: (custom) => patch({ custom }) }) : null,
6398
6445
  recoveryEditing ? /* @__PURE__ */ jsx25(
@@ -6471,7 +6518,10 @@ function ItemPane(props) {
6471
6518
  }),
6472
6519
  extraFields.map((f) => isRecoveryCodesField(f) ? /* @__PURE__ */ jsx25(RecoveryCodesRow, { item, field: f, port, onChanged }, f.id) : /* @__PURE__ */ jsx25(SecretRow, { port, item, field: f, onTurnOn: turnOnOne(f), extra: fieldSettings(f) }, f.id)),
6473
6520
  item.loginUrls.length > 0 ? item.loginUrls.map((u, i) => /* @__PURE__ */ jsx25(PlainRow, { label: i === 0 ? "Website" : `Website ${i + 1}`, value: u, openUrl: host.openUrl }, `${u}-${i}`)) : /* @__PURE__ */ jsx25(AddRow, { label: "Website", onAdd: canEdit ? () => onMode("edit") : null, children: "Add website" }),
6474
- item.authenticator ? /* @__PURE__ */ jsx25(TotpRow, { port, item, onChanged }) : seedField && !seedField.active ? (
6521
+ item.authenticator ? /* @__PURE__ */ jsx25(TotpRow, { port, item, onChanged }) : item.authenticatorState !== "known" ? /* @__PURE__ */ jsxs23(VaultRow, { label: "Authenticator", children: [
6522
+ /* @__PURE__ */ jsx25("span", { className: "text-muted-foreground", children: item.authenticatorState === "pending" ? "Loading" : "Unavailable" }),
6523
+ item.authenticatorState === "unavailable" ? /* @__PURE__ */ jsx25(Button22, { variant: "quiet", onClick: () => onChanged?.(), children: "Retry" }) : null
6524
+ ] }) : seedField && !seedField.active ? (
6475
6525
  // The secret is still there, only off: turning it on brings the codes back. Never "Set up",
6476
6526
  // which would invite replacing a working two-factor secret.
6477
6527
  /* @__PURE__ */ jsx25(VaultRow, { label: "Authenticator", actions: canTurnOn ? /* @__PURE__ */ jsx25(TurnOnButton, { label: "Authenticator", onTurnOn: () => turnOn([seedField]) }) : null, children: /* @__PURE__ */ jsx25("span", { className: "text-muted-foreground", children: "Turned off" }) })
@@ -9150,10 +9200,34 @@ function Blank({ children }) {
9150
9200
 
9151
9201
  // src/health/api.ts
9152
9202
  import { followRuntimeOperationToEndWithApi } from "@ai-matrx/api";
9153
- import { startPasswordHealthScan, getPasswordHealthScan, resumePasswordHealthScan } from "@ai-matrx/api/account";
9203
+ import { startPasswordHealthScan, getPasswordHealthScan, resumePasswordHealthScan, discoverPasswordHealthScans } from "@ai-matrx/api/account";
9154
9204
  import { operationStatus } from "@ai-matrx/api/architecture";
9155
9205
  function createHealthPort(api) {
9156
9206
  return {
9207
+ discover: async (cursor, signal) => {
9208
+ const stream = await discoverPasswordHealthScans(api, cursor ? { query: { cursor } } : void 0, { signal, totalTimeoutMs: null });
9209
+ let page = null;
9210
+ let ended = false;
9211
+ try {
9212
+ for await (const event of stream) {
9213
+ if (signal.aborted || ended || event.event === "error") throw new Error("Report unavailable");
9214
+ const data = event.data;
9215
+ if (event.event === "completion" && data && typeof data === "object" && "status" in data && data.status !== "success") throw new Error("Report unavailable");
9216
+ if (event.event === "end") {
9217
+ if (!data || typeof data !== "object" || !("reason" in data) || data.reason !== "complete") throw new Error("Report unavailable");
9218
+ ended = true;
9219
+ }
9220
+ if (event.event !== "data") continue;
9221
+ if (!data || typeof data !== "object" || !("type" in data) || data.type !== "vault_password_health_discovery_page") continue;
9222
+ if (page || !("__kind" in data) || data.__kind !== "password_health_discovery_page" || !("references" in data) || !Array.isArray(data.references) || data.references.some((ref) => !ref || typeof ref !== "object" || ref.__kind !== "password_health_discovery_reference" || typeof ref.request_id !== "string" || !ref.request_id || typeof ref.execution_id !== "string" || !ref.execution_id) || !("continuation" in data) || data.continuation !== null && (typeof data.continuation !== "string" || !data.continuation)) throw new Error("Report unavailable");
9223
+ page = data;
9224
+ }
9225
+ if (signal.aborted || !ended || !page) throw new Error("Report unavailable");
9226
+ return page;
9227
+ } finally {
9228
+ stream.cancel();
9229
+ }
9230
+ },
9157
9231
  start: (requestId, signal, options) => startPasswordHealthScan(api, { body: options.check_breaches ? { check_breaches: true } : {} }, { requestId, signal, totalTimeoutMs: null }),
9158
9232
  resume: (executionId, requestId, signal) => resumePasswordHealthScan(api, { path: { execution_id: executionId } }, { requestId, signal, totalTimeoutMs: null }),
9159
9233
  read: (executionId, signal) => getPasswordHealthScan(api, { path: { execution_id: executionId } }, { signal }),
@@ -9276,7 +9350,7 @@ function createAuthenticatorPort(ctx) {
9276
9350
  return {
9277
9351
  async list() {
9278
9352
  const r = await apps.listAuthenticators(api);
9279
- return (r.entries ?? []).map(entryOf);
9353
+ return { entries: (r.entries ?? []).map(entryOf), unavailableScopes: (r.unavailable_scopes ?? []).map((s) => ({ type: s.type, organizationId: s.organization_id ?? null })) };
9280
9354
  },
9281
9355
  async get(item) {
9282
9356
  try {
@@ -9490,6 +9564,7 @@ function trashItem(r, ctx) {
9490
9564
  fields: [],
9491
9565
  custom: [],
9492
9566
  authenticator: null,
9567
+ authenticatorState: "known",
9493
9568
  capabilities: { canUse: false, canEdit: false, canReveal: false, canManage: true },
9494
9569
  attachments: [],
9495
9570
  uriMatchMode: "host",
@@ -9846,7 +9921,8 @@ function fieldOf(w, def) {
9846
9921
  active: w.is_active ?? true
9847
9922
  };
9848
9923
  }
9849
- function itemOf(w, owner, defs, auth) {
9924
+ var scopeUnavailable = (owner, scopes) => scopes.some((s) => s.type === (owner.kind === "me" ? "user" : owner.kind === "organization" ? "organization" : "shared") && (s.type !== "organization" || !s.organization_id || s.organization_id === owner.organizationId));
9925
+ function itemOf(w, owner, defs, auth, state = "known", unavailable = [], inventoryOwner = owner) {
9850
9926
  const def = defs.get(w.definition_key ?? "");
9851
9927
  const a = auth.get(w.id);
9852
9928
  const cap = w.capabilities ?? {};
@@ -9864,6 +9940,7 @@ function itemOf(w, owner, defs, auth) {
9864
9940
  fields: (w.fields ?? []).filter((f) => f.execution_purpose !== "passkey_private").map((f) => fieldOf(f, def)),
9865
9941
  custom: (w.non_secret_fields ?? []).map((c) => ({ key: c["key"] ?? "", label: c["label"] ?? humanize(c["key"] ?? ""), value: c["value"] ?? "" })),
9866
9942
  authenticator: a ? { digits: a.digits, period: a.period, enabled: a.enabled } : null,
9943
+ authenticatorState: a ? "known" : state === "pending" ? "pending" : state === "unavailable" || scopeUnavailable(inventoryOwner, unavailable) ? "unavailable" : "known",
9867
9944
  capabilities: { canUse: cap.can_use ?? false, canEdit: cap.can_edit ?? false, canReveal: cap.can_reveal ?? false, canManage: cap.can_manage ?? false },
9868
9945
  attachments: (w.attachments ?? []).map((a2) => ({ id: a2.id, label: a2.label, description: a2.description ?? "", fileName: a2.file_name, mediaType: a2.media_type ?? "application/octet-stream", sizeBytes: a2.size_bytes, handling: handlingOf(a2.handling), createdAt: a2.created_at, updatedAt: a2.updated_at })),
9869
9946
  uriMatchMode: MATCH_MODES2.find((m) => m === w.uri_match_mode) ?? "host",
@@ -9900,6 +9977,9 @@ function createApiVaultPort(hostApi, options = {}) {
9900
9977
  };
9901
9978
  let defs = /* @__PURE__ */ new Map();
9902
9979
  let auth = /* @__PURE__ */ new Map();
9980
+ let authState = "pending";
9981
+ let authUnavailable = [];
9982
+ let inventoryOwners = /* @__PURE__ */ new Map();
9903
9983
  let orgs = [];
9904
9984
  const orgOf = (id, userId, mine) => {
9905
9985
  if (id) return { kind: "organization", label: orgs.find((o) => o.id === id)?.name ?? "Organization", organizationId: id };
@@ -9913,7 +9993,7 @@ function createApiVaultPort(hostApi, options = {}) {
9913
9993
  };
9914
9994
  async function refetch(id, owner) {
9915
9995
  const w = await account9.getItem(api, { path: { item_id: id } });
9916
- return itemOf(w, owner, defs, auth);
9996
+ return itemOf(w, owner, defs, auth, authState, authUnavailable, inventoryOwners.get(id) ?? owner);
9917
9997
  }
9918
9998
  const visibleCache = /* @__PURE__ */ new Map();
9919
9999
  const ctx = {
@@ -9940,6 +10020,10 @@ function createApiVaultPort(hostApi, options = {}) {
9940
10020
  fields: createFieldsPort(ctx),
9941
10021
  lists: createListsPort(ctx, options.userState),
9942
10022
  async load(onProgress) {
10023
+ authState = "pending";
10024
+ auth = /* @__PURE__ */ new Map();
10025
+ authUnavailable = [];
10026
+ inventoryOwners = /* @__PURE__ */ new Map();
9943
10027
  const catalogP = retrying(() => apps2.getAppCatalogKind(api, { path: { app: "matrx", kind: "credential_definition" } }));
9944
10028
  const authP = retrying(() => apps2.listAuthenticators(api));
9945
10029
  const userP = retrying(() => account9.listItems(api, { query: { principal_type: "user" } }));
@@ -9962,9 +10046,10 @@ function createApiVaultPort(hostApi, options = {}) {
9962
10046
  const meId = raw.meId ?? mineItems.find((w) => w.user_id)?.user_id ?? null;
9963
10047
  const seen = /* @__PURE__ */ new Map();
9964
10048
  const deleted = /* @__PURE__ */ new Map();
9965
- const take = (w, owner) => {
10049
+ const take = (w, owner, inventoryOwner = owner) => {
9966
10050
  if (seen.has(w.id) || deleted.has(w.id) || isHiddenItem(w)) return;
9967
- const item = itemOf(w, owner, defs, auth);
10051
+ inventoryOwners.set(w.id, inventoryOwner);
10052
+ const item = itemOf(w, owner, defs, auth, authState, authUnavailable, inventoryOwner);
9968
10053
  (isDeleted(w) ? deleted : seen).set(w.id, item);
9969
10054
  };
9970
10055
  for (const w of mineItems) take(w, orgOf(w.organization_id, null, true));
@@ -9972,7 +10057,7 @@ function createApiVaultPort(hostApi, options = {}) {
9972
10057
  const via = orgs[i];
9973
10058
  for (const w of l?.items ?? []) take(w, ownerInOrgList(w, via, meId));
9974
10059
  });
9975
- for (const w of raw.shared?.items ?? []) take(w, w.organization_id ? orgOf(w.organization_id, null, false) : { kind: "shared", label: "Shared with me", organizationId: null });
10060
+ for (const w of raw.shared?.items ?? []) take(w, w.organization_id ? orgOf(w.organization_id, null, false) : { kind: "shared", label: "Shared with me", organizationId: null }, { kind: "shared", label: "Shared with me", organizationId: null });
9976
10061
  const pending = done ? [] : [...raw.orgsKnown ? orgs.filter((_, i) => raw.orgLists[i] === void 0).map((o) => o.name) : ["your organizations"], ...raw.shared ? [] : ["Shared with me"]];
9977
10062
  return {
9978
10063
  unavailable: [...unavailable],
@@ -10031,9 +10116,14 @@ function createApiVaultPort(hostApi, options = {}) {
10031
10116
  })();
10032
10117
  quiet(orgListsReady);
10033
10118
  const authDone = authP.then((a) => {
10034
- auth = new Map((a.entries ?? []).map((e) => [e.credential_item_id, e]));
10119
+ authUnavailable = a.unavailable_scopes ?? [];
10120
+ const fresh = new Map((a.entries ?? []).map((e) => [e.credential_item_id, e]));
10121
+ auth = fresh;
10122
+ authState = "known";
10123
+ if (authUnavailable.length) unavailable.push("One-time codes");
10035
10124
  progress();
10036
10125
  }, () => {
10126
+ authState = "unavailable";
10037
10127
  unavailable.push("One-time codes");
10038
10128
  });
10039
10129
  const sharedDone = sharedP.then((sh) => {