@arbidocs/sdk 0.3.116 → 0.3.118

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/browser.cjs CHANGED
@@ -5084,9 +5084,45 @@ __export(dm_exports, {
5084
5084
  listDecryptedDMs: () => listDecryptedDMs,
5085
5085
  markRead: () => markRead,
5086
5086
  requestWorkspaceAccess: () => requestWorkspaceAccess,
5087
+ resolveRecipient: () => resolveRecipient,
5087
5088
  sendDM: () => sendDM,
5088
5089
  sendEncryptedDM: () => sendEncryptedDM
5089
5090
  });
5091
+
5092
+ // src/operations/agents.ts
5093
+ async function listAgents(arbi) {
5094
+ return requireData(await arbi.fetch.GET("/v1/agents"), "Failed to fetch agents");
5095
+ }
5096
+ async function createAgent(arbi, name) {
5097
+ const body = { name };
5098
+ return requireData(await arbi.fetch.POST("/v1/agents", { body }), "Failed to create agent");
5099
+ }
5100
+ async function getAgentConfig(arbi, agentExtId) {
5101
+ return requireData(
5102
+ await arbi.fetch.GET("/v1/agents/{agent_ext_id}/config", {
5103
+ params: { path: { agent_ext_id: agentExtId } }
5104
+ }),
5105
+ "Failed to fetch agent config"
5106
+ );
5107
+ }
5108
+ async function listAgentWorkspaces(arbi, agentExtId) {
5109
+ return requireData(
5110
+ await arbi.fetch.GET("/v1/agents/{agent_ext_id}/workspaces", {
5111
+ params: { path: { agent_ext_id: agentExtId } }
5112
+ }),
5113
+ "Failed to fetch agent workspaces"
5114
+ );
5115
+ }
5116
+ async function deleteAgents(arbi, agentExtIds) {
5117
+ return requireData(
5118
+ await arbi.fetch.DELETE("/v1/agents", {
5119
+ body: { agent_ext_ids: agentExtIds }
5120
+ }),
5121
+ "Failed to delete agents"
5122
+ );
5123
+ }
5124
+
5125
+ // src/operations/dm.ts
5090
5126
  function createDmCryptoContext(arbi, signingPrivateKeyBytes, userExtId) {
5091
5127
  const ed25519PublicKey = signingPrivateKeyBytes.slice(32, 64);
5092
5128
  const encryptionKeypair = arbi.crypto.deriveEncryptionKeypairFromSigning({
@@ -5149,6 +5185,37 @@ async function listActivity(arbi, options) {
5149
5185
  "Failed to fetch activity"
5150
5186
  );
5151
5187
  }
5188
+ async function resolveRecipient(arbi, ref) {
5189
+ const isEmail = ref.includes("@");
5190
+ const matchKey = isEmail ? "email" : "external_id";
5191
+ const contacts = await listContacts(arbi);
5192
+ const contactMatch = contacts.find(
5193
+ (c2) => isEmail ? c2.email === ref : c2.user?.external_id === ref || c2.external_id === ref
5194
+ );
5195
+ if (contactMatch?.user?.encryption_public_key) {
5196
+ return {
5197
+ extId: contactMatch.user.external_id,
5198
+ pubKey: contactMatch.user.encryption_public_key
5199
+ };
5200
+ }
5201
+ try {
5202
+ const agents = await listAgents(arbi);
5203
+ const agentMatch = agents.find((a2) => a2[matchKey] === ref);
5204
+ if (agentMatch?.encryption_public_key) {
5205
+ return { extId: agentMatch.external_id, pubKey: agentMatch.encryption_public_key };
5206
+ }
5207
+ } catch {
5208
+ }
5209
+ try {
5210
+ const wsUsers = await listWorkspaceUsers(arbi);
5211
+ const wsMatch = wsUsers.find((wu) => wu.user[matchKey] === ref);
5212
+ if (wsMatch?.user.encryption_public_key) {
5213
+ return { extId: wsMatch.user.external_id, pubKey: wsMatch.user.encryption_public_key };
5214
+ }
5215
+ } catch {
5216
+ }
5217
+ return null;
5218
+ }
5152
5219
  async function sendEncryptedDM(arbi, messages, crypto) {
5153
5220
  const encrypted = await Promise.all(
5154
5221
  messages.map(async (msg) => ({
@@ -5345,39 +5412,6 @@ async function getFileContent(auth, fileId) {
5345
5412
  });
5346
5413
  }
5347
5414
 
5348
- // src/operations/agents.ts
5349
- async function listAgents(arbi) {
5350
- return requireData(await arbi.fetch.GET("/v1/agents"), "Failed to fetch agents");
5351
- }
5352
- async function createAgent(arbi, name) {
5353
- const body = { name };
5354
- return requireData(await arbi.fetch.POST("/v1/agents", { body }), "Failed to create agent");
5355
- }
5356
- async function getAgentConfig(arbi, agentExtId) {
5357
- return requireData(
5358
- await arbi.fetch.GET("/v1/agents/{agent_ext_id}/config", {
5359
- params: { path: { agent_ext_id: agentExtId } }
5360
- }),
5361
- "Failed to fetch agent config"
5362
- );
5363
- }
5364
- async function listAgentWorkspaces(arbi, agentExtId) {
5365
- return requireData(
5366
- await arbi.fetch.GET("/v1/agents/{agent_ext_id}/workspaces", {
5367
- params: { path: { agent_ext_id: agentExtId } }
5368
- }),
5369
- "Failed to fetch agent workspaces"
5370
- );
5371
- }
5372
- async function deleteAgents(arbi, agentExtIds) {
5373
- return requireData(
5374
- await arbi.fetch.DELETE("/v1/agents", {
5375
- body: { agent_ext_ids: agentExtIds }
5376
- }),
5377
- "Failed to delete agents"
5378
- );
5379
- }
5380
-
5381
5415
  // src/operations/sessions.ts
5382
5416
  async function listSessions(arbi) {
5383
5417
  return requireData(await arbi.fetch.GET("/v1/user/sessions"), "Failed to fetch sessions");
@@ -5526,6 +5560,7 @@ var Arbi = class {
5526
5560
  client = null;
5527
5561
  loginResult = null;
5528
5562
  currentWorkspaceId = null;
5563
+ dmCryptoContext = null;
5529
5564
  options;
5530
5565
  constructor(options) {
5531
5566
  this.options = options;
@@ -5612,6 +5647,7 @@ var Arbi = class {
5612
5647
  }
5613
5648
  this.loginResult = null;
5614
5649
  this.currentWorkspaceId = null;
5650
+ this.dmCryptoContext = null;
5615
5651
  }
5616
5652
  // ── Accessors ─────────────────────────────────────────────────────────
5617
5653
  /** Get the underlying ArbiClient (throws if not initialized). */
@@ -5667,6 +5703,16 @@ var Arbi = class {
5667
5703
  };
5668
5704
  // ── User (account-scoped usage) ───────────────────────────────────────
5669
5705
  user = {
5706
+ /**
5707
+ * The authenticated user's identity from the current session
5708
+ * (`external_id` + `email`), established at login. Returns `null` when not
5709
+ * logged in. This is the canonical "who am I" accessor — DM direction and
5710
+ * ownership checks read it instead of re-deriving identity per call.
5711
+ */
5712
+ identity: () => {
5713
+ const state = this.requireClient().session.getState();
5714
+ return state.userExtId && state.userEmail ? { externalId: state.userExtId, email: state.userEmail } : null;
5715
+ },
5670
5716
  /** Today's deployment-wide credit spend for the current user. */
5671
5717
  usageToday: () => getUserUsageToday(this.requireClient())
5672
5718
  };
@@ -5850,9 +5896,76 @@ var Arbi = class {
5850
5896
  groupByStatus: groupContactsByStatus
5851
5897
  };
5852
5898
  // ── Direct Messages ───────────────────────────────────────────────────
5899
+ /**
5900
+ * Build (and cache) the DM crypto context for the current session.
5901
+ *
5902
+ * Derives the user's curve25519 keypair from the stored signing key and pairs
5903
+ * it with their external_id — everything `list()`/`send()` need to encrypt and
5904
+ * decrypt. Cached for the session and cleared on logout. Consumers never touch
5905
+ * this: it's an internal of the `dm` surface.
5906
+ */
5907
+ async requireDmCrypto() {
5908
+ if (this.dmCryptoContext) return this.dmCryptoContext;
5909
+ const client = this.requireClient();
5910
+ const session = await client.storage.getSession();
5911
+ if (!session) {
5912
+ throw new ArbiError("Not authenticated \u2014 no session available for DM encryption");
5913
+ }
5914
+ const userExtId = client.session.getState().userExtId;
5915
+ if (!userExtId) {
5916
+ throw new ArbiError("No user identity in session \u2014 cannot set up DM encryption");
5917
+ }
5918
+ this.dmCryptoContext = createDmCryptoContext(client, session.signingPrivateKey, userExtId);
5919
+ return this.dmCryptoContext;
5920
+ }
5853
5921
  dm = {
5854
- list: () => listDMs(this.requireClient()),
5855
- send: (messages) => sendDM(this.requireClient(), messages),
5922
+ /**
5923
+ * List direct messages, newest first, with content already **decrypted**.
5924
+ * (A message that can't be decrypted comes back as `[Decryption failed]`.)
5925
+ */
5926
+ list: async () => listDecryptedDMs(this.requireClient(), await this.requireDmCrypto()),
5927
+ /**
5928
+ * Send an E2E-encrypted DM to a recipient (email or `usr-`/`agt-` id).
5929
+ * Resolves the recipient's public key and encrypts internally — the caller
5930
+ * passes plaintext and never handles keys or ciphertext.
5931
+ */
5932
+ send: async (recipient, text) => {
5933
+ const client = this.requireClient();
5934
+ const resolved = await resolveRecipient(client, recipient);
5935
+ if (!resolved) {
5936
+ throw new ArbiError(
5937
+ `Could not resolve DM recipient "${recipient}" (not a known contact, agent, or workspace member)`
5938
+ );
5939
+ }
5940
+ return sendEncryptedDM(
5941
+ client,
5942
+ [
5943
+ {
5944
+ recipient_ext_id: resolved.extId,
5945
+ content: text,
5946
+ recipient_encryption_public_key: resolved.pubKey
5947
+ }
5948
+ ],
5949
+ await this.requireDmCrypto()
5950
+ );
5951
+ },
5952
+ /**
5953
+ * Send to a recipient whose external_id + public key are already known
5954
+ * (e.g. from a message's `sender`), skipping directory resolution.
5955
+ */
5956
+ sendTo: async (recipientExtId, recipientPublicKey, text) => sendEncryptedDM(
5957
+ this.requireClient(),
5958
+ [
5959
+ {
5960
+ recipient_ext_id: recipientExtId,
5961
+ content: text,
5962
+ recipient_encryption_public_key: recipientPublicKey
5963
+ }
5964
+ ],
5965
+ await this.requireDmCrypto()
5966
+ ),
5967
+ /** Resolve a recipient reference to `{ extId, pubKey }` (contacts → agents → workspace). */
5968
+ resolveRecipient: (recipient) => resolveRecipient(this.requireClient(), recipient),
5856
5969
  markRead: (messageIds) => markRead(this.requireClient(), messageIds),
5857
5970
  delete: (messageIds) => deleteDMs(this.requireClient(), messageIds),
5858
5971
  /** List the current user's durable activity feed (never unread; excluded from DMs). */