@arbidocs/react 0.4.3 → 0.4.4

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/README.md CHANGED
@@ -126,3 +126,25 @@ queryClient.invalidateQueries({ queryKey: arbiQueryKeys.documents(workspaceId) }
126
126
  ## Peer dependencies
127
127
 
128
128
  - `react` >= 18
129
+
130
+
131
+ ## Tasks, agents, skills, contacts and credits
132
+
133
+ Hooks for an "AI workforce" and a messages screen. Writes invalidate through `arbiQueryKeys`, so
134
+ `useTasks`, `useAgents`, `useConfigs` and `useDirectMessages` refresh by themselves.
135
+
136
+ ```tsx
137
+ const create = useCreateTask() // mutateAsync(CreateTaskRequest) -> TaskResponse
138
+ const run = useRunTask() // approves a run parked for access (temporary grant), then runs
139
+ const runs = useTaskRuns(taskId) // polls while a run is in flight
140
+ const save = useSaveAgentConfig() // { agentExtId, sections: { Agents: { PERSONA, ... } } }
141
+ const grant = useGrantAgentAccess() // { agentExtId, workspaceId, mode?: { kind: 'standing' | 'temporary' } }
142
+ const contacts = usePendingContacts() // + useAddContacts(), useRemoveContacts()
143
+ ```
144
+
145
+ Form-to-request mappings (`taskComposerToCreateRequest`, `composePersona`, ...) are in
146
+ `@arbidocs/blocks/agents`. Usage is reported in **credits**, not dollars: use
147
+ `formatCredits(n)`, `creditsToUsd(n)` and the `metered` flag on `useProjectUsage` /
148
+ `useProjectsUsage` (`false` = this deployment does not record usage). `DirectMessages` takes
149
+ `listHeader`, `listLead`, `startByEmail` and `hideOwnContact`. See `docs/design/blocks-package.md`
150
+ ("Hooks and mappings a host should not rewrite").
package/dist/index.cjs CHANGED
@@ -25,6 +25,23 @@ var React__default = /*#__PURE__*/_interopDefault(React);
25
25
  var ReactMarkdown__default = /*#__PURE__*/_interopDefault(ReactMarkdown);
26
26
  var Cropper__default = /*#__PURE__*/_interopDefault(Cropper);
27
27
 
28
+ // src/credits.ts
29
+ var DEFAULT_USD_PER_CREDIT = 0.01;
30
+ function formatCredits(credits, options = {}) {
31
+ const { unit = true, locale = "en-US" } = options;
32
+ const whole = Math.round(Number.isFinite(credits) ? credits : 0);
33
+ const digits = whole.toLocaleString(locale);
34
+ return unit ? `${digits} ${whole === 1 ? "credit" : "credits"}` : digits;
35
+ }
36
+ function creditsToUsd(credits, usdPerCredit = DEFAULT_USD_PER_CREDIT) {
37
+ return credits * usdPerCredit;
38
+ }
39
+ function isUsageMetered(usage, subscription) {
40
+ const empty = usage.total_spend === 0 && usage.total_tokens === 0 && usage.total_requests === 0;
41
+ return !(empty && subscription === "dev");
42
+ }
43
+
44
+ // src/hooks.ts
28
45
  var arbiQueryKeys = {
29
46
  workspaces: () => ["arbi", "workspaces"],
30
47
  documents: (workspaceId) => ["arbi", "documents", workspaceId],
@@ -42,7 +59,14 @@ var arbiQueryKeys = {
42
59
  projects: () => ["arbi", "projects"],
43
60
  projectUsage: (projectExtId, monthsBack) => ["arbi", "projectUsage", projectExtId, monthsBack],
44
61
  projectInvoices: (projectExtId) => ["arbi", "projectInvoices", projectExtId],
45
- userUsageToday: () => ["arbi", "userUsageToday"]
62
+ userUsageToday: () => ["arbi", "userUsageToday"],
63
+ contacts: () => ["arbi", "contacts"],
64
+ skills: () => ["arbi", "skills"],
65
+ taskRuns: (taskId) => ["arbi", "taskRuns", taskId],
66
+ /** An agent's pinned config; `undefined` = the library default config. */
67
+ agentConfig: (agentExtId) => ["arbi", "agentConfig", agentExtId ?? "default"],
68
+ agentWorkspaces: (agentExtId) => ["arbi", "agentWorkspaces", agentExtId],
69
+ models: () => ["arbi", "models"]
46
70
  };
47
71
  function useWorkspaces(options) {
48
72
  const arbi = chunk7IOCMCC5_cjs.useArbi();
@@ -171,15 +195,20 @@ function useProjects(options) {
171
195
  }
172
196
  function useProjectUsage(projectExtId, monthsBack = 0, options) {
173
197
  const arbi = chunk7IOCMCC5_cjs.useArbi();
174
- return reactQuery.useQuery({
198
+ const usage = reactQuery.useQuery({
175
199
  queryKey: arbiQueryKeys.projectUsage(projectExtId ?? "", monthsBack),
176
200
  queryFn: () => arbi.projects.usage(projectExtId, monthsBack),
177
201
  enabled: (options?.enabled ?? true) && !!projectExtId && arbi.isLoggedIn
178
202
  });
203
+ const projects = useProjects({ enabled: !!projectExtId && (options?.enabled ?? true) });
204
+ const subscription = projects.data?.find((p) => p.external_id === projectExtId)?.subscription;
205
+ const metered = usage.data ? isUsageMetered(usage.data, subscription) : void 0;
206
+ return { ...usage, metered };
179
207
  }
180
208
  function useProjectsUsage(projectExtIds, monthsBack = 0, options) {
181
209
  const arbi = chunk7IOCMCC5_cjs.useArbi();
182
210
  const enabled = (options?.enabled ?? true) && arbi.isLoggedIn;
211
+ const projects = useProjects({ enabled: enabled && projectExtIds.length > 0 });
183
212
  const results = reactQuery.useQueries({
184
213
  queries: projectExtIds.map((id) => ({
185
214
  queryKey: arbiQueryKeys.projectUsage(id, monthsBack),
@@ -187,27 +216,34 @@ function useProjectsUsage(projectExtIds, monthsBack = 0, options) {
187
216
  enabled: enabled && !!id
188
217
  }))
189
218
  });
190
- const signature = results.map((r) => `${r.status}:${r.dataUpdatedAt ?? 0}`).join("|");
219
+ const signature = results.map((r) => `${r.status}:${r.dataUpdatedAt ?? 0}`).join("|") + `#${projects.dataUpdatedAt}`;
191
220
  return React.useMemo(() => {
192
221
  const dailyMap = /* @__PURE__ */ new Map();
193
222
  const modelMap = /* @__PURE__ */ new Map();
194
- let totalSpend = 0;
223
+ let totalCredits = 0;
224
+ let anyData = false;
225
+ let anyMetered = false;
195
226
  let totalTokens = 0;
196
227
  let totalRequests = 0;
197
- for (const r of results) {
228
+ results.forEach((r, i) => {
198
229
  const d = r.data;
199
- if (!d) continue;
200
- totalSpend += d.total_spend ?? 0;
230
+ if (!d) return;
231
+ anyData = true;
232
+ const subscription = projects.data?.find(
233
+ (p) => p.external_id === projectExtIds[i]
234
+ )?.subscription;
235
+ if (isUsageMetered(d, subscription)) anyMetered = true;
236
+ totalCredits += d.total_spend ?? 0;
201
237
  totalTokens += d.total_tokens ?? 0;
202
238
  totalRequests += d.total_requests ?? 0;
203
239
  for (const day of d.daily ?? []) {
204
240
  const cur = dailyMap.get(day.date) ?? {
205
241
  date: day.date,
206
- spend: 0,
242
+ credits: 0,
207
243
  totalTokens: 0,
208
244
  apiRequests: 0
209
245
  };
210
- cur.spend += day.spend ?? 0;
246
+ cur.credits += day.spend ?? 0;
211
247
  cur.totalTokens += day.total_tokens ?? 0;
212
248
  cur.apiRequests += day.api_requests ?? 0;
213
249
  dailyMap.set(day.date, cur);
@@ -215,28 +251,29 @@ function useProjectsUsage(projectExtIds, monthsBack = 0, options) {
215
251
  for (const [model, mg] of Object.entries(d.model_groups ?? {})) {
216
252
  const cur = modelMap.get(model) ?? {
217
253
  model,
218
- spend: 0,
254
+ credits: 0,
219
255
  totalTokens: 0,
220
256
  promptTokens: 0,
221
257
  completionTokens: 0,
222
258
  successfulRequests: 0
223
259
  };
224
- cur.spend += mg.spend ?? 0;
260
+ cur.credits += mg.spend ?? 0;
225
261
  cur.totalTokens += mg.total_tokens ?? 0;
226
262
  cur.promptTokens += mg.prompt_tokens ?? 0;
227
263
  cur.completionTokens += mg.completion_tokens ?? 0;
228
264
  cur.successfulRequests += mg.successful_requests ?? 0;
229
265
  modelMap.set(model, cur);
230
266
  }
231
- }
267
+ });
232
268
  return {
233
- totalSpend,
269
+ totalCredits,
234
270
  totalTokens,
235
271
  totalRequests,
236
272
  daily: [...dailyMap.values()].sort((a, b) => a.date.localeCompare(b.date)),
237
273
  modelGroups: [...modelMap.values()].sort(
238
- (a, b) => b.spend - a.spend || b.totalTokens - a.totalTokens
274
+ (a, b) => b.credits - a.credits || b.totalTokens - a.totalTokens
239
275
  ),
276
+ metered: !anyData || anyMetered,
240
277
  isLoading: results.some((r) => r.isLoading),
241
278
  isError: results.some((r) => r.isError)
242
279
  };
@@ -892,6 +929,186 @@ function useArbiShellWorkspaces(options = {}) {
892
929
  select: select ?? defaultSelect
893
930
  });
894
931
  }
932
+ function useCreateTask() {
933
+ const arbi = chunk7IOCMCC5_cjs.useArbi();
934
+ const qc = reactQuery.useQueryClient();
935
+ return reactQuery.useMutation({
936
+ mutationFn: (body) => arbi.tasks.create(body),
937
+ onSuccess: () => qc.invalidateQueries({ queryKey: arbiQueryKeys.tasks() })
938
+ });
939
+ }
940
+ function useUpdateTask() {
941
+ const arbi = chunk7IOCMCC5_cjs.useArbi();
942
+ const qc = reactQuery.useQueryClient();
943
+ return reactQuery.useMutation({
944
+ mutationFn: ({ taskId, patch }) => arbi.tasks.update(taskId, patch),
945
+ onSuccess: () => qc.invalidateQueries({ queryKey: arbiQueryKeys.tasks() })
946
+ });
947
+ }
948
+ function useRunTask() {
949
+ const arbi = chunk7IOCMCC5_cjs.useArbi();
950
+ const qc = reactQuery.useQueryClient();
951
+ return reactQuery.useMutation({
952
+ mutationFn: (taskId) => arbi.tasks.runWithAuth(taskId),
953
+ onSuccess: (_task, taskId) => Promise.all([
954
+ qc.invalidateQueries({ queryKey: arbiQueryKeys.tasks() }),
955
+ qc.invalidateQueries({ queryKey: arbiQueryKeys.taskRuns(taskId) })
956
+ ])
957
+ });
958
+ }
959
+ function useDeleteTask() {
960
+ const arbi = chunk7IOCMCC5_cjs.useArbi();
961
+ const qc = reactQuery.useQueryClient();
962
+ return reactQuery.useMutation({
963
+ mutationFn: (taskId) => arbi.tasks.delete(taskId),
964
+ onSuccess: () => qc.invalidateQueries({ queryKey: arbiQueryKeys.tasks() })
965
+ });
966
+ }
967
+ function useTaskRuns(taskId, options) {
968
+ const arbi = chunk7IOCMCC5_cjs.useArbi();
969
+ return reactQuery.useQuery({
970
+ queryKey: arbiQueryKeys.taskRuns(taskId ?? ""),
971
+ queryFn: () => arbi.tasks.listRuns(taskId),
972
+ enabled: (options?.enabled ?? true) && !!taskId && arbi.isLoggedIn,
973
+ refetchInterval: (q) => q.state.data?.some((r) => r.status === "running") ? 3e3 : false
974
+ });
975
+ }
976
+ function useCreateAgent() {
977
+ const arbi = chunk7IOCMCC5_cjs.useArbi();
978
+ const qc = reactQuery.useQueryClient();
979
+ return reactQuery.useMutation({
980
+ mutationFn: (name) => arbi.agents.create(name),
981
+ onSuccess: () => qc.invalidateQueries({ queryKey: arbiQueryKeys.agents() })
982
+ });
983
+ }
984
+ function useAgentConfig(agentExtId, options) {
985
+ const arbi = chunk7IOCMCC5_cjs.useArbi();
986
+ return reactQuery.useQuery({
987
+ queryKey: arbiQueryKeys.agentConfig(agentExtId),
988
+ queryFn: async () => agentExtId ? await arbi.agents.getConfig(agentExtId) : await arbi.agentConfig.get("default"),
989
+ enabled: (options?.enabled ?? true) && arbi.isLoggedIn
990
+ });
991
+ }
992
+ function useAgentModels(options) {
993
+ const arbi = chunk7IOCMCC5_cjs.useArbi();
994
+ return reactQuery.useQuery({
995
+ queryKey: arbiQueryKeys.models(),
996
+ queryFn: async () => (await arbi.agentConfig.getModels()).models.map((m) => m.model_name),
997
+ enabled: (options?.enabled ?? true) && arbi.isLoggedIn
998
+ });
999
+ }
1000
+ function useSaveAgentConfig() {
1001
+ const arbi = chunk7IOCMCC5_cjs.useArbi();
1002
+ const qc = reactQuery.useQueryClient();
1003
+ return reactQuery.useMutation({
1004
+ mutationFn: async ({ agentExtId, title, sections }) => {
1005
+ const pinned = await arbi.agents.getConfig(agentExtId);
1006
+ const base = pinned ?? await arbi.agentConfig.get("default");
1007
+ const merged = structuredClone(base);
1008
+ for (const [section, params] of Object.entries(sections)) {
1009
+ merged[section] = { ...merged[section] ?? {}, ...params };
1010
+ }
1011
+ return arbi.agentConfig.save({
1012
+ ...merged,
1013
+ agent_ext_id: agentExtId,
1014
+ title: title ?? `agent-${agentExtId}`
1015
+ });
1016
+ },
1017
+ onSuccess: (_saved, { agentExtId }) => Promise.all([
1018
+ qc.invalidateQueries({ queryKey: arbiQueryKeys.configs() }),
1019
+ qc.invalidateQueries({ queryKey: arbiQueryKeys.agentConfig(agentExtId) })
1020
+ ])
1021
+ });
1022
+ }
1023
+ function useAgentWorkspaces(agentExtId, options) {
1024
+ const arbi = chunk7IOCMCC5_cjs.useArbi();
1025
+ return reactQuery.useQuery({
1026
+ queryKey: arbiQueryKeys.agentWorkspaces(agentExtId ?? ""),
1027
+ queryFn: () => arbi.agents.listWorkspaces(agentExtId),
1028
+ enabled: (options?.enabled ?? true) && !!agentExtId && arbi.isLoggedIn
1029
+ });
1030
+ }
1031
+ function useGrantAgentAccess() {
1032
+ const arbi = chunk7IOCMCC5_cjs.useArbi();
1033
+ const qc = reactQuery.useQueryClient();
1034
+ return reactQuery.useMutation({
1035
+ mutationFn: async ({
1036
+ agentExtId,
1037
+ workspaceId,
1038
+ mode = { kind: "standing" }
1039
+ }) => {
1040
+ if (mode.kind === "temporary") {
1041
+ return arbi.agents.grantTemporaryAccess(agentExtId, workspaceId, mode.ttlSeconds);
1042
+ }
1043
+ const agent = (await arbi.agents.list()).find((a) => a.external_id === agentExtId);
1044
+ if (!agent?.email) throw new Error("The agent's address is unknown.");
1045
+ return arbi.workspaces.grantAccess({ workspaceId, emails: [agent.email] });
1046
+ },
1047
+ onSuccess: (_res, { agentExtId }) => qc.invalidateQueries({ queryKey: arbiQueryKeys.agentWorkspaces(agentExtId) })
1048
+ });
1049
+ }
1050
+ function useRevokeAgentAccess() {
1051
+ const arbi = chunk7IOCMCC5_cjs.useArbi();
1052
+ const qc = reactQuery.useQueryClient();
1053
+ return reactQuery.useMutation({
1054
+ mutationFn: async ({
1055
+ agentExtId,
1056
+ workspaceId
1057
+ }) => {
1058
+ const previous = arbi.hasWorkspace ? arbi.getWorkspaceId() : null;
1059
+ await arbi.selectWorkspace(workspaceId);
1060
+ try {
1061
+ await arbi.workspaces.removeUsers([agentExtId]);
1062
+ } finally {
1063
+ if (previous && previous !== workspaceId) await arbi.selectWorkspace(previous);
1064
+ }
1065
+ },
1066
+ onSuccess: (_res, { agentExtId }) => qc.invalidateQueries({ queryKey: arbiQueryKeys.agentWorkspaces(agentExtId) })
1067
+ });
1068
+ }
1069
+ function useSkills(options) {
1070
+ const arbi = chunk7IOCMCC5_cjs.useArbi();
1071
+ return reactQuery.useQuery({
1072
+ queryKey: [...arbiQueryKeys.skills(), options?.includeHidden ?? false],
1073
+ queryFn: () => arbi.assistant.listSkills({ includeHidden: options?.includeHidden }),
1074
+ enabled: (options?.enabled ?? true) && arbi.isLoggedIn,
1075
+ staleTime: 3e4
1076
+ });
1077
+ }
1078
+ function useContacts(options) {
1079
+ const arbi = chunk7IOCMCC5_cjs.useArbi();
1080
+ return reactQuery.useQuery({
1081
+ queryKey: arbiQueryKeys.contacts(),
1082
+ queryFn: () => arbi.contacts.list(),
1083
+ enabled: (options?.enabled ?? true) && arbi.isLoggedIn
1084
+ });
1085
+ }
1086
+ function usePendingContacts(options) {
1087
+ const contacts = useContacts(options);
1088
+ const pending = React.useMemo(
1089
+ () => (contacts.data ?? []).filter(
1090
+ (c) => c.status === "invitation_pending" || c.status === "invitation_expired"
1091
+ ),
1092
+ [contacts.data]
1093
+ );
1094
+ return { ...contacts, pending };
1095
+ }
1096
+ function useAddContacts() {
1097
+ const arbi = chunk7IOCMCC5_cjs.useArbi();
1098
+ const qc = reactQuery.useQueryClient();
1099
+ return reactQuery.useMutation({
1100
+ mutationFn: (emails) => arbi.contacts.add(emails),
1101
+ onSuccess: () => qc.invalidateQueries({ queryKey: arbiQueryKeys.contacts() })
1102
+ });
1103
+ }
1104
+ function useRemoveContacts() {
1105
+ const arbi = chunk7IOCMCC5_cjs.useArbi();
1106
+ const qc = reactQuery.useQueryClient();
1107
+ return reactQuery.useMutation({
1108
+ mutationFn: (contactIds) => arbi.contacts.remove(contactIds),
1109
+ onSuccess: () => qc.invalidateQueries({ queryKey: arbiQueryKeys.contacts() })
1110
+ });
1111
+ }
895
1112
  function useWorkspaceDocs(workspaceId) {
896
1113
  const { data: docs = [], isLoading, isError } = useDocuments(workspaceId);
897
1114
  const docIds = React.useMemo(
@@ -3067,7 +3284,6 @@ function isAgentUser(user) {
3067
3284
  return user.external_id.startsWith("agt-");
3068
3285
  }
3069
3286
  var DM_QUERY_KEY = ["arbi", "directMessages"];
3070
- var CONTACTS_QUERY_KEY = ["arbi", "contacts"];
3071
3287
  function mergeContacts(threads, contacts) {
3072
3288
  const byPeer = /* @__PURE__ */ new Map();
3073
3289
  for (const t of threads) byPeer.set(t.peer.external_id, t);
@@ -3133,7 +3349,7 @@ function useDirectMessages(options) {
3133
3349
  enabled
3134
3350
  });
3135
3351
  const contactsQuery = reactQuery.useQuery({
3136
- queryKey: CONTACTS_QUERY_KEY,
3352
+ queryKey: arbiQueryKeys.contacts(),
3137
3353
  queryFn: () => arbi.contacts.list(),
3138
3354
  enabled
3139
3355
  });
@@ -3218,6 +3434,7 @@ function ConversationList({
3218
3434
  classNames,
3219
3435
  emptyState,
3220
3436
  header,
3437
+ lead,
3221
3438
  className
3222
3439
  }) {
3223
3440
  return /* @__PURE__ */ jsxRuntime.jsxs(
@@ -3237,6 +3454,7 @@ function ConversationList({
3237
3454
  conversations.length === 1 ? "colleague" : "colleagues"
3238
3455
  ] })
3239
3456
  ] }) : header,
3457
+ lead,
3240
3458
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-0 flex-1 overflow-y-auto", children: conversations.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 py-6 text-xs text-muted-foreground", children: emptyState ?? "No conversations yet." }) : /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "w-full", children: conversations.map((c, i) => {
3241
3459
  const { peer, lastMessage, unreadCount } = c;
3242
3460
  const newSection = c.sectionLabel && c.sectionLabel !== conversations[i - 1]?.sectionLabel;
@@ -3523,14 +3741,117 @@ function DmComposer({
3523
3741
  error && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-xs text-destructive", "data-testid": "arbi-dm-error", children: error })
3524
3742
  ] });
3525
3743
  }
3744
+ var DEFAULT_START_BY_EMAIL_LABELS = {
3745
+ email: "Email address",
3746
+ emailPlaceholder: "name@example.com",
3747
+ message: "Message",
3748
+ messagePlaceholder: "Write the first message\u2026",
3749
+ send: "Send",
3750
+ sending: "Sending\u2026",
3751
+ invalidEmail: "Enter a valid email address."
3752
+ };
3753
+ var EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
3754
+ function StartByEmail({
3755
+ onStart,
3756
+ onStarted,
3757
+ classNames,
3758
+ labels,
3759
+ className
3760
+ }) {
3761
+ const l = { ...DEFAULT_START_BY_EMAIL_LABELS, ...labels };
3762
+ const uid = React.useId();
3763
+ const [email, setEmail] = React.useState("");
3764
+ const [text, setText] = React.useState("");
3765
+ const [busy, setBusy] = React.useState(false);
3766
+ const [error, setError] = React.useState(null);
3767
+ const address = email.trim().toLowerCase();
3768
+ const valid = EMAIL.test(address);
3769
+ const submit = async () => {
3770
+ if (!valid || !text.trim() || busy) return;
3771
+ setBusy(true);
3772
+ setError(null);
3773
+ try {
3774
+ const result = await onStart(address, text.trim());
3775
+ setEmail("");
3776
+ setText("");
3777
+ onStarted?.(result);
3778
+ } catch (e) {
3779
+ setError(e instanceof Error ? e.message : String(e));
3780
+ } finally {
3781
+ setBusy(false);
3782
+ }
3783
+ };
3784
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3785
+ "form",
3786
+ {
3787
+ "data-testid": "arbi-dm-start",
3788
+ className: chunkRZQACYNQ_cjs.cn("flex flex-col gap-2 border-b border-border px-4 py-3", className),
3789
+ onSubmit: (e) => {
3790
+ e.preventDefault();
3791
+ void submit();
3792
+ },
3793
+ children: [
3794
+ /* @__PURE__ */ jsxRuntime.jsx(chunkORKGCXG3_cjs.Label, { htmlFor: `${uid}-email`, children: l.email }),
3795
+ /* @__PURE__ */ jsxRuntime.jsx(
3796
+ chunkORKGCXG3_cjs.Input,
3797
+ {
3798
+ id: `${uid}-email`,
3799
+ type: "email",
3800
+ value: email,
3801
+ onChange: (e) => setEmail(e.target.value),
3802
+ placeholder: l.emailPlaceholder,
3803
+ "aria-invalid": email !== "" && !valid,
3804
+ "aria-describedby": email !== "" && !valid ? `${uid}-bad` : void 0,
3805
+ disabled: busy,
3806
+ "data-testid": "arbi-dm-start-email"
3807
+ }
3808
+ ),
3809
+ email !== "" && !valid && /* @__PURE__ */ jsxRuntime.jsx("p", { id: `${uid}-bad`, className: "text-xs text-destructive", children: l.invalidEmail }),
3810
+ /* @__PURE__ */ jsxRuntime.jsx(chunkORKGCXG3_cjs.Label, { htmlFor: `${uid}-text`, children: l.message }),
3811
+ /* @__PURE__ */ jsxRuntime.jsx(
3812
+ chunkORKGCXG3_cjs.Textarea,
3813
+ {
3814
+ id: `${uid}-text`,
3815
+ value: text,
3816
+ onChange: (e) => setText(e.target.value),
3817
+ placeholder: l.messagePlaceholder,
3818
+ rows: 2,
3819
+ disabled: busy,
3820
+ "data-testid": "arbi-dm-start-message"
3821
+ }
3822
+ ),
3823
+ error && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", className: "text-xs text-destructive", "data-testid": "arbi-dm-start-error", children: error }),
3824
+ /* @__PURE__ */ jsxRuntime.jsxs(
3825
+ chunkORKGCXG3_cjs.Button,
3826
+ {
3827
+ type: "submit",
3828
+ size: "sm",
3829
+ disabled: !valid || !text.trim() || busy,
3830
+ className: chunkRZQACYNQ_cjs.cn("self-end", classNames?.sendButton),
3831
+ "data-testid": "arbi-dm-start-send",
3832
+ children: [
3833
+ busy ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "size-4 animate-spin", "aria-hidden": true }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Send, { className: "size-4", "aria-hidden": true }),
3834
+ busy ? l.sending : l.send
3835
+ ]
3836
+ }
3837
+ )
3838
+ ]
3839
+ }
3840
+ );
3841
+ }
3526
3842
  function DirectMessages({
3527
3843
  className,
3528
3844
  classNames,
3529
3845
  emptyThread: emptyThread2,
3530
- emptyList
3846
+ emptyList,
3847
+ listHeader,
3848
+ listLead,
3849
+ startByEmail,
3850
+ hideOwnContact
3531
3851
  }) {
3532
3852
  const {
3533
- conversations,
3853
+ conversations: allConversations,
3854
+ myExtId,
3534
3855
  isLoading,
3535
3856
  isError,
3536
3857
  error,
@@ -3540,6 +3861,14 @@ function DirectMessages({
3540
3861
  markRead
3541
3862
  } = useDirectMessages();
3542
3863
  const [selectedPeerId, setSelectedPeerId] = React.useState(null);
3864
+ const conversations = React.useMemo(
3865
+ () => hideOwnContact && myExtId ? allConversations.filter((c) => c.peer.external_id !== myExtId) : allConversations,
3866
+ [allConversations, hideOwnContact, myExtId]
3867
+ );
3868
+ const openStarted = (created) => {
3869
+ const peer = Array.isArray(created) ? created[0]?.recipient?.external_id : void 0;
3870
+ if (peer) setSelectedPeerId(peer);
3871
+ };
3543
3872
  const loadError = isError ? error instanceof Error ? error.message : "Couldn\u2019t load messages." : null;
3544
3873
  const selected = React.useMemo(
3545
3874
  () => conversations.find((c) => c.peer.external_id === selectedPeerId) ?? null,
@@ -3583,7 +3912,20 @@ function DirectMessages({
3583
3912
  selectedPeerId: selected?.peer.external_id ?? null,
3584
3913
  onSelect: setSelectedPeerId,
3585
3914
  classNames,
3586
- emptyState: isLoading ? "Loading messages\u2026" : emptyList
3915
+ emptyState: isLoading ? "Loading messages\u2026" : emptyList,
3916
+ header: listHeader,
3917
+ lead: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3918
+ listLead,
3919
+ startByEmail && /* @__PURE__ */ jsxRuntime.jsx(
3920
+ StartByEmail,
3921
+ {
3922
+ onStart: sendMessageTo,
3923
+ onStarted: openStarted,
3924
+ classNames,
3925
+ labels: typeof startByEmail === "object" ? startByEmail.labels : void 0
3926
+ }
3927
+ )
3928
+ ] })
3587
3929
  }
3588
3930
  ),
3589
3931
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-0 min-w-0 flex-1 flex-col", children: [
@@ -5687,6 +6029,8 @@ exports.CommandPalette = CommandPalette;
5687
6029
  exports.CommandPaletteProvider = CommandPaletteProvider;
5688
6030
  exports.ConversationList = ConversationList;
5689
6031
  exports.DEFAULT_ASPECT_OPTIONS = DEFAULT_ASPECT_OPTIONS;
6032
+ exports.DEFAULT_START_BY_EMAIL_LABELS = DEFAULT_START_BY_EMAIL_LABELS;
6033
+ exports.DEFAULT_USD_PER_CREDIT = DEFAULT_USD_PER_CREDIT;
5690
6034
  exports.DateSeparator = DateSeparator;
5691
6035
  exports.DirectMessages = DirectMessages;
5692
6036
  exports.DmAvatar = DmAvatar;
@@ -5716,6 +6060,7 @@ exports.PhotoCapture = PhotoCapture;
5716
6060
  exports.ProviderIcon = ProviderIcon;
5717
6061
  exports.RUN_WINDOW_MS = RUN_WINDOW_MS;
5718
6062
  exports.SemanticSearch = SemanticSearch;
6063
+ exports.StartByEmail = StartByEmail;
5719
6064
  exports.TxtIcon = TxtIcon;
5720
6065
  exports.TypingIndicator = TypingIndicator;
5721
6066
  exports.VideoCapture = VideoCapture;
@@ -5724,14 +6069,17 @@ exports.XlsIcon = XlsIcon;
5724
6069
  exports.annotateThread = annotateThread;
5725
6070
  exports.arbiQueryKeys = arbiQueryKeys;
5726
6071
  exports.captureFrameAsBlob = captureFrameAsBlob;
6072
+ exports.creditsToUsd = creditsToUsd;
5727
6073
  exports.cropImage = cropImage;
5728
6074
  exports.dayKey = dayKey;
5729
6075
  exports.describeActivity = describeActivity;
6076
+ exports.formatCredits = formatCredits;
5730
6077
  exports.getNotificationTypeMeta = getNotificationTypeMeta;
5731
6078
  exports.getWorkspaceActionText = getWorkspaceActionText;
5732
6079
  exports.getWorkspaceKind = getWorkspaceKind;
5733
6080
  exports.getWorkspaceSentPhrase = getWorkspaceSentPhrase;
5734
6081
  exports.hasVideoTrack = hasVideoTrack;
6082
+ exports.isUsageMetered = isUsageMetered;
5735
6083
  exports.isWorkspaceNotificationType = isWorkspaceNotificationType;
5736
6084
  exports.parseActivityContent = parseActivityContent;
5737
6085
  exports.pickRecentWorkspaces = pickRecentWorkspaces;
@@ -5742,6 +6090,10 @@ exports.titleFromQuestion = titleFromQuestion;
5742
6090
  exports.toRedlineMarkdown = toRedlineMarkdown;
5743
6091
  exports.toShellWorkspace = toShellWorkspace;
5744
6092
  exports.useActivity = useActivity;
6093
+ exports.useAddContacts = useAddContacts;
6094
+ exports.useAgentConfig = useAgentConfig;
6095
+ exports.useAgentModels = useAgentModels;
6096
+ exports.useAgentWorkspaces = useAgentWorkspaces;
5745
6097
  exports.useAgents = useAgents;
5746
6098
  exports.useAiTask = useAiTask;
5747
6099
  exports.useArbiChat = useArbiChat;
@@ -5760,23 +6112,36 @@ exports.useAssistantQuery = useAssistantQuery;
5760
6112
  exports.useCameraStream = useCameraStream;
5761
6113
  exports.useCommandPalette = useCommandPalette;
5762
6114
  exports.useConfigs = useConfigs;
6115
+ exports.useContacts = useContacts;
5763
6116
  exports.useConversations = useConversations;
6117
+ exports.useCreateAgent = useCreateAgent;
6118
+ exports.useCreateTask = useCreateTask;
5764
6119
  exports.useDeleteAgents = useDeleteAgents;
6120
+ exports.useDeleteTask = useDeleteTask;
5765
6121
  exports.useDirectMessages = useDirectMessages;
5766
6122
  exports.useDocuments = useDocuments;
5767
6123
  exports.useFacts = useFacts;
6124
+ exports.useGrantAgentAccess = useGrantAgentAccess;
5768
6125
  exports.useHealth = useHealth;
5769
6126
  exports.useParsedDocument = useParsedDocument;
6127
+ exports.usePendingContacts = usePendingContacts;
5770
6128
  exports.useProjectInvoices = useProjectInvoices;
5771
6129
  exports.useProjectUsage = useProjectUsage;
5772
6130
  exports.useProjects = useProjects;
5773
6131
  exports.useProjectsUsage = useProjectsUsage;
6132
+ exports.useRemoveContacts = useRemoveContacts;
6133
+ exports.useRevokeAgentAccess = useRevokeAgentAccess;
6134
+ exports.useRunTask = useRunTask;
6135
+ exports.useSaveAgentConfig = useSaveAgentConfig;
5774
6136
  exports.useSemanticSearch = useSemanticSearch;
5775
6137
  exports.useSessions = useSessions;
5776
6138
  exports.useShellWorkspaces = useShellWorkspaces;
6139
+ exports.useSkills = useSkills;
5777
6140
  exports.useTags = useTags;
6141
+ exports.useTaskRuns = useTaskRuns;
5778
6142
  exports.useTasks = useTasks;
5779
6143
  exports.useThumbnails = useThumbnails;
6144
+ exports.useUpdateTask = useUpdateTask;
5780
6145
  exports.useUserUsageToday = useUserUsageToday;
5781
6146
  exports.useVideoRecorder = useVideoRecorder;
5782
6147
  exports.useWorkspaceDocs = useWorkspaceDocs;