openmates 0.27.0-alpha.50 → 0.27.0-alpha.51

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.
@@ -592,13 +592,13 @@ function collectGeneratedFiles(value) {
592
592
  const chosen = preferred.map((key) => entries.find(([name]) => name === key)).find(Boolean) ?? entries.find(([name]) => !/preview|thumbnail|poster/i.test(name));
593
593
  if (chosen) add(chosen[1], data, chosen[0]);
594
594
  }
595
- for (const field2 of ["artifacts", "files"]) {
596
- if (Array.isArray(data[field2])) for (const item of data[field2]) {
595
+ for (const field3 of ["artifacts", "files"]) {
596
+ if (Array.isArray(data[field3])) for (const item of data[field3]) {
597
597
  const meta = record(item);
598
598
  if (meta && text(meta.download_url)) add(meta, data, text(meta.variant) ?? "original");
599
599
  }
600
600
  }
601
- for (const field2 of RESULT_FIELDS) if (data[field2] !== void 0) visit2(data[field2]);
601
+ for (const field3 of RESULT_FIELDS) if (data[field3] !== void 0) visit2(data[field3]);
602
602
  };
603
603
  visit2(value);
604
604
  return files;
@@ -2591,11 +2591,11 @@ function parseSseMessage(rawEvent) {
2591
2591
  for (const line3 of rawEvent.split("\n")) {
2592
2592
  if (!line3 || line3.startsWith(":")) continue;
2593
2593
  const separator = line3.indexOf(":");
2594
- const field2 = separator >= 0 ? line3.slice(0, separator) : line3;
2594
+ const field3 = separator >= 0 ? line3.slice(0, separator) : line3;
2595
2595
  const value = separator >= 0 ? line3.slice(separator + 1).replace(/^ /, "") : "";
2596
- if (field2 === "id") message.id = value;
2597
- if (field2 === "event") message.event = value;
2598
- if (field2 === "data") message.data += `${message.data ? "\n" : ""}${value}`;
2596
+ if (field3 === "id") message.id = value;
2597
+ if (field3 === "event") message.event = value;
2598
+ if (field3 === "data") message.data += `${message.data ? "\n" : ""}${value}`;
2599
2599
  }
2600
2600
  return message.data ? message : null;
2601
2601
  }
@@ -5000,7 +5000,7 @@ function parseAvailableRecoveryOutputs(value) {
5000
5000
  "target_chat_id",
5001
5001
  "turn_id",
5002
5002
  "subject_id"
5003
- ].every((field2) => typeof raw[field2] === "string" && Boolean(raw[field2])) || !["message", "embed", "diff", "summary", "checkpoint"].includes(String(raw.output_kind)) || !Number.isSafeInteger(raw.output_version) || Number(raw.output_version) < 1 || !Number.isSafeInteger(raw.chat_key_version) || Number(raw.chat_key_version) < 1) return [];
5003
+ ].every((field3) => typeof raw[field3] === "string" && Boolean(raw[field3])) || !["message", "embed", "diff", "summary", "checkpoint"].includes(String(raw.output_kind)) || !Number.isSafeInteger(raw.output_version) || Number(raw.output_version) < 1 || !Number.isSafeInteger(raw.chat_key_version) || Number(raw.chat_key_version) < 1) return [];
5004
5004
  if (raw.message_role != null && raw.message_role !== "user" && raw.message_role !== "assistant") return [];
5005
5005
  if (raw.root_hashed_team_id !== void 0 && raw.root_hashed_team_id !== null && (typeof raw.root_hashed_team_id !== "string" || !/^[0-9a-f]{64}$/.test(raw.root_hashed_team_id))) return [];
5006
5006
  return [raw];
@@ -6360,7 +6360,7 @@ async function loadActiveCliProjectContext(client2, chatId, text7) {
6360
6360
  project_focus_documents: [],
6361
6361
  project_context_documents: []
6362
6362
  };
6363
- const revision = (item) => createHash8("sha256").update(["updated_at", "encrypted_metadata", "encrypted_note", "target_id_hash", "deleted_target_state"].map((field2) => item[field2] ? String(item[field2]) : "").join("\0")).digest("hex");
6363
+ const revision = (item) => createHash8("sha256").update(["updated_at", "encrypted_metadata", "encrypted_note", "target_id_hash", "deleted_target_state"].map((field3) => item[field3] ? String(item[field3]) : "").join("\0")).digest("hex");
6364
6364
  const candidates = [];
6365
6365
  const targets = /* @__PURE__ */ new Map();
6366
6366
  for (const item of detail.items) {
@@ -6681,7 +6681,7 @@ function boundedAuthoringHistory(rows2) {
6681
6681
  return firstUser && history.length > 8 ? [{ ...firstUser, content: firstUser.content.slice(0, 3e3) }, ...recent2] : recent2;
6682
6682
  }
6683
6683
  function projectItemRevision(item) {
6684
- return createHash9("sha256").update(["updated_at", "encrypted_metadata", "encrypted_note", "target_id_hash", "deleted_target_state"].map((field2) => item[field2] ? String(item[field2]) : "").join("\0")).digest("hex");
6684
+ return createHash9("sha256").update(["updated_at", "encrypted_metadata", "encrypted_note", "target_id_hash", "deleted_target_state"].map((field3) => item[field3] ? String(item[field3]) : "").join("\0")).digest("hex");
6685
6685
  }
6686
6686
  async function selectedFocus(client2, chatId, projectId, targetId, revision) {
6687
6687
  const focus = await client2.getActiveProjectFocus(chatId);
@@ -10093,6 +10093,10 @@ function selectNewChatSlugValue(input) {
10093
10093
  const prefix = normalizedMessage.slice(0, prefixLength).replace(/-+$/g, "");
10094
10094
  return `${prefix}-${normalizedChatId}`;
10095
10095
  }
10096
+ function teamChatTitleFromMessage(message) {
10097
+ const normalized = message.replace(/\s+/g, " ").trim();
10098
+ return Array.from(normalized).slice(0, 80).join("") || "New team chat";
10099
+ }
10096
10100
  function normalizeObjectSelectorLabel(value) {
10097
10101
  return value.trim().toLowerCase().replace(/\s+/g, " ");
10098
10102
  }
@@ -11224,6 +11228,7 @@ async function persistCompressionCheckpoints(ws, chatKeyBytes, checkpoints) {
11224
11228
  }
11225
11229
  var OpenMatesClient = class _OpenMatesClient {
11226
11230
  workflowDeliveriesBySocket = /* @__PURE__ */ new WeakMap();
11231
+ ownTeamDisplayNames = /* @__PURE__ */ new Map();
11227
11232
  apiUrl;
11228
11233
  session;
11229
11234
  http;
@@ -11240,6 +11245,19 @@ var OpenMatesClient = class _OpenMatesClient {
11240
11245
  onRefreshCookieRotated: (sessionId, sentToken, newToken) => this.persistOrdinaryRefreshCookie(sessionId, sentToken, newToken)
11241
11246
  });
11242
11247
  }
11248
+ captureTeamMutationOwner() {
11249
+ const session = this.requireSession();
11250
+ const { sessionId, hashedEmail, activeTeamId, masterKeyExportedB64 } = session;
11251
+ return () => Boolean(this.session && this.session.sessionId === sessionId && this.session.hashedEmail === hashedEmail && this.session.activeTeamId === activeTeamId && this.session.masterKeyExportedB64 === masterKeyExportedB64);
11252
+ }
11253
+ requireTeamMutationOwner(isCurrent) {
11254
+ if (!isCurrent()) throw new Error("Account or Team changed before the operation could be sent.");
11255
+ }
11256
+ captureTeamAccountOwner() {
11257
+ const session = this.requireSession();
11258
+ const { apiUrl, sessionId, hashedEmail, masterKeyExportedB64, createdAt } = session;
11259
+ return { hashedEmail, isCurrent: () => Boolean(this.session && this.session.sessionId === sessionId && this.session.hashedEmail === hashedEmail && this.session.masterKeyExportedB64 === masterKeyExportedB64 && this.session.createdAt === createdAt && this.session.apiUrl === apiUrl) };
11260
+ }
11243
11261
  async persistOrdinaryRefreshCookie(sessionId, sentToken, newToken) {
11244
11262
  await withSessionRefreshLock(async () => {
11245
11263
  const session = this.session;
@@ -11475,13 +11493,15 @@ var OpenMatesClient = class _OpenMatesClient {
11475
11493
  return `${path2}${separator}team_id=${encodeURIComponent(teamId)}`;
11476
11494
  }
11477
11495
  async listTeams() {
11478
- this.requireSession();
11496
+ const owner = this.captureTeamAccountOwner();
11479
11497
  const response = await this.http.get("/v1/teams", this.getCliRequestHeaders());
11480
11498
  if (!response.ok) throw new Error(`Team list failed with HTTP ${response.status}`);
11499
+ if (!owner.isCurrent()) throw new Error("Account changed while loading Teams.");
11481
11500
  const teams = response.data.teams ?? [];
11482
- await Promise.all(teams.map((team) => this.cacheTeamKeyFromRecord(team)));
11501
+ await Promise.all(teams.map((team) => this.cacheTeamKeyFromRecord(team, owner.isCurrent)));
11502
+ if (!owner.isCurrent()) throw new Error("Account changed while loading Teams.");
11483
11503
  const teamIds = teams.map((team) => team.team_id).filter((teamId) => typeof teamId === "string" && teamId.length > 0);
11484
- pruneLocalTeamArtifacts(this.requireSession().hashedEmail, teamIds);
11504
+ pruneLocalTeamArtifacts(owner.hashedEmail, teamIds);
11485
11505
  if (this.session?.activeTeamId && !teamIds.includes(this.session.activeTeamId)) {
11486
11506
  this.setActiveTeamId(null);
11487
11507
  }
@@ -11503,6 +11523,7 @@ var OpenMatesClient = class _OpenMatesClient {
11503
11523
  }
11504
11524
  async createTeam(input) {
11505
11525
  this.requireSession();
11526
+ const isCurrent = this.captureTeamMutationOwner();
11506
11527
  const name = input.name?.trim() || "Untitled team";
11507
11528
  if (input.encryptedName && !input.name && !input.nameApprovalToken) {
11508
11529
  throw new Error("Encrypted team creation requires a name approval token.");
@@ -11526,30 +11547,126 @@ var OpenMatesClient = class _OpenMatesClient {
11526
11547
  created_at: input.createdAt ?? now,
11527
11548
  updated_at: input.createdAt ?? now
11528
11549
  };
11550
+ this.requireTeamMutationOwner(isCurrent);
11529
11551
  const response = await this.http.post("/v1/teams", payload, this.getCliRequestHeaders());
11530
11552
  if (!response.ok || !response.data.team) throw new Error(`Team create failed with HTTP ${response.status}`);
11553
+ this.requireTeamMutationOwner(isCurrent);
11531
11554
  if (!input.encryptedTeamKey) {
11532
11555
  saveLocalTeamKey(this.requireSession().hashedEmail, teamId, bytesToBase64(teamKeyBytes));
11533
11556
  }
11534
11557
  return response.data.team;
11535
11558
  }
11536
11559
  async getTeam(teamId) {
11537
- this.requireSession();
11560
+ const owner = this.captureTeamAccountOwner();
11538
11561
  const response = await this.http.get(`/v1/teams/${encodeURIComponent(teamId)}`, this.getCliRequestHeaders());
11539
11562
  if (!response.ok || !response.data.team) throw new Error(`Team get failed with HTTP ${response.status}`);
11540
- await this.cacheTeamKeyFromRecord(response.data.team);
11563
+ if (!owner.isCurrent()) throw new Error("Account changed while loading Team.");
11564
+ await this.cacheTeamKeyFromRecord(response.data.team, owner.isCurrent);
11565
+ if (!owner.isCurrent()) throw new Error("Account changed while loading Team.");
11541
11566
  return response.data.team;
11542
11567
  }
11543
- async cacheTeamKeyFromRecord(team) {
11568
+ /** Team names and descriptions are decrypted only on this device. */
11569
+ async getTeamDetails(teamId) {
11570
+ const team = await this.getTeam(teamId);
11571
+ const key = await this.loadTeamKeyBytes(teamId);
11572
+ if (!key) throw new Error("Unable to decrypt Team details.");
11573
+ const name = typeof team.encrypted_name === "string" ? await decryptWithAesGcmCombined(team.encrypted_name, key) : null;
11574
+ const description = typeof team.encrypted_description === "string" ? await decryptWithAesGcmCombined(team.encrypted_description, key) : null;
11575
+ return { ...team, name: name ?? "", description: description ?? "" };
11576
+ }
11577
+ async getTeamSecurity(teamId) {
11578
+ this.requireSession();
11579
+ const response = await this.http.get(`/v1/teams/${encodeURIComponent(teamId)}/security`, this.getCliRequestHeaders());
11580
+ if (!response.ok) throw new Error(`Team security failed with HTTP ${response.status}`);
11581
+ const policy = response.data.security_policy ?? {};
11582
+ return {
11583
+ restrict_email_domains: policy.restrict_email_domains === true,
11584
+ allowed_email_domains: Array.isArray(policy.allowed_email_domains) ? policy.allowed_email_domains : [],
11585
+ require_invite_link_approval: policy.require_invite_link_approval !== false,
11586
+ require_strong_auth: policy.require_strong_auth === true
11587
+ };
11588
+ }
11589
+ async updateTeamSecurity(teamId, policy) {
11590
+ this.requireSession();
11591
+ const response = await this.http.patch(`/v1/teams/${encodeURIComponent(teamId)}/security`, policy, this.getCliRequestHeaders());
11592
+ if (!response.ok || !response.data.security_policy) throw new Error(`Team security update failed with HTTP ${response.status}`);
11593
+ return response.data.security_policy;
11594
+ }
11595
+ async decryptTeamMember(teamId, member) {
11596
+ if (!member.encrypted_member_profile) return { ...member, profile: null };
11597
+ const key = await this.loadTeamKeyBytes(teamId);
11598
+ if (!key) throw new Error("Unable to decrypt Team member profile.");
11599
+ const plaintext = await decryptWithAesGcmCombined(member.encrypted_member_profile, key);
11600
+ if (!plaintext) throw new Error("Unable to decrypt Team member profile.");
11601
+ const parsed = JSON.parse(plaintext);
11602
+ return { ...member, profile: {
11603
+ display_name: typeof parsed.display_name === "string" ? parsed.display_name : void 0,
11604
+ avatar: typeof parsed.avatar === "string" ? parsed.avatar : null
11605
+ } };
11606
+ }
11607
+ async listTeamMembers(teamId) {
11608
+ this.requireSession();
11609
+ const response = await this.http.get(`/v1/teams/${encodeURIComponent(teamId)}/members`, this.getCliRequestHeaders());
11610
+ if (!response.ok) throw new Error(`Team members failed with HTTP ${response.status}`);
11611
+ return Promise.all((response.data.members ?? []).map((member) => this.decryptTeamMember(teamId, member)));
11612
+ }
11613
+ async getTeamMember(teamId, memberUserId) {
11614
+ this.requireSession();
11615
+ const response = await this.http.get(`/v1/teams/${encodeURIComponent(teamId)}/members/${encodeURIComponent(memberUserId)}`, this.getCliRequestHeaders());
11616
+ if (!response.ok || !response.data.member) throw new Error(`Team member failed with HTTP ${response.status}`);
11617
+ return this.decryptTeamMember(teamId, response.data.member);
11618
+ }
11619
+ async updateOwnTeamMemberProfile(teamId, profile) {
11620
+ this.requireSession();
11621
+ const isCurrent = this.captureTeamMutationOwner();
11622
+ const key = await this.loadTeamKeyBytes(teamId);
11623
+ if (!key) throw new Error("Unable to encrypt Team member profile.");
11624
+ const encrypted = await encryptWithAesGcmCombined(JSON.stringify({
11625
+ display_name: profile.display_name?.trim() ?? "",
11626
+ avatar: profile.avatar ?? null
11627
+ }), key);
11628
+ this.requireTeamMutationOwner(isCurrent);
11629
+ const response = await this.http.patch(`/v1/teams/${encodeURIComponent(teamId)}/members/me/profile`, { encrypted_member_profile: encrypted }, this.getCliRequestHeaders());
11630
+ if (!response.ok || !response.data.member) throw new Error(`Team profile update failed with HTTP ${response.status}`);
11631
+ this.requireTeamMutationOwner(isCurrent);
11632
+ this.ownTeamDisplayNames.delete(`${this.requireSession().hashedEmail}:${teamId}`);
11633
+ return this.decryptTeamMember(teamId, response.data.member);
11634
+ }
11635
+ async ownTeamSenderName(teamId) {
11636
+ const isCurrent = this.captureTeamMutationOwner();
11637
+ const identity3 = await this.whoAmI();
11638
+ this.requireTeamMutationOwner(isCurrent);
11639
+ const fallback2 = typeof identity3.username === "string" && identity3.username.trim() ? identity3.username.trim() : "Team member";
11640
+ if (typeof identity3.id !== "string" || !identity3.id) return fallback2;
11641
+ const cacheKey = `${this.requireSession().hashedEmail}:${teamId}`;
11642
+ const cached = this.ownTeamDisplayNames.get(cacheKey);
11643
+ if (cached && cached.expiresAt > Date.now()) return cached.name;
11644
+ let name = fallback2;
11645
+ try {
11646
+ const member = await this.getTeamMember(teamId, identity3.id);
11647
+ this.requireTeamMutationOwner(isCurrent);
11648
+ if (member.profile?.display_name?.trim()) name = member.profile.display_name.trim();
11649
+ } catch {
11650
+ }
11651
+ this.requireTeamMutationOwner(isCurrent);
11652
+ this.ownTeamDisplayNames.delete(cacheKey);
11653
+ if (this.ownTeamDisplayNames.size >= 16) this.ownTeamDisplayNames.delete(this.ownTeamDisplayNames.keys().next().value);
11654
+ this.ownTeamDisplayNames.set(cacheKey, { name, expiresAt: Date.now() + 5 * 6e4 });
11655
+ return name;
11656
+ }
11657
+ async cacheTeamKeyFromRecord(team, ownerCurrent = this.captureTeamAccountOwner().isCurrent) {
11658
+ if (!ownerCurrent()) throw new Error("Account changed while caching Team key.");
11544
11659
  const session = this.requireSession();
11545
11660
  const teamId = typeof team.team_id === "string" ? team.team_id : null;
11546
11661
  const encryptedTeamKey = typeof team.encrypted_team_key === "string" ? team.encrypted_team_key : null;
11547
11662
  if (!teamId || !encryptedTeamKey || loadLocalTeamKey(session.hashedEmail, teamId)) return;
11548
11663
  const teamKey = await decryptBytesWithAesGcm(encryptedTeamKey, this.getMasterKeyBytes());
11664
+ if (!ownerCurrent()) throw new Error("Account changed while caching Team key.");
11549
11665
  if (teamKey) saveLocalTeamKey(session.hashedEmail, teamId, bytesToBase64(teamKey));
11550
11666
  }
11551
11667
  async updateTeam(teamId, input) {
11552
11668
  this.requireSession();
11669
+ const isCurrent = this.captureTeamMutationOwner();
11553
11670
  const payload = {
11554
11671
  updated_at: input.updated_at ?? Math.floor(Date.now() / 1e3),
11555
11672
  ...input
@@ -11571,6 +11688,7 @@ var OpenMatesClient = class _OpenMatesClient {
11571
11688
  payload.encrypted_profile_image_metadata = await encryptWithAesGcmCombined(JSON.stringify(input.profileImageMetadata), teamKey);
11572
11689
  delete payload.profileImageMetadata;
11573
11690
  }
11691
+ this.requireTeamMutationOwner(isCurrent);
11574
11692
  const response = await this.http.patch(`/v1/teams/${encodeURIComponent(teamId)}`, {
11575
11693
  ...payload
11576
11694
  }, this.getCliRequestHeaders());
@@ -11632,6 +11750,7 @@ var OpenMatesClient = class _OpenMatesClient {
11632
11750
  }
11633
11751
  async createTeamInvite(teamId, input) {
11634
11752
  this.requireSession();
11753
+ const isCurrent = this.captureTeamMutationOwner();
11635
11754
  const inviteId = typeof input.invite_id === "string" ? input.invite_id : randomUUID9();
11636
11755
  const payload = {
11637
11756
  invite_id: inviteId,
@@ -11656,6 +11775,7 @@ var OpenMatesClient = class _OpenMatesClient {
11656
11775
  origin
11657
11776
  };
11658
11777
  }
11778
+ this.requireTeamMutationOwner(isCurrent);
11659
11779
  const response = await this.http.post(`/v1/teams/${encodeURIComponent(teamId)}/invites`, {
11660
11780
  ...payload,
11661
11781
  invite_secret: void 0
@@ -12186,7 +12306,7 @@ var OpenMatesClient = class _OpenMatesClient {
12186
12306
  if (!userId) {
12187
12307
  throw new Error("Could not resolve current user id for local connected-account connector.");
12188
12308
  }
12189
- const { createHash: createHash34 } = await import("crypto");
12309
+ const { createHash: createHash35 } = await import("crypto");
12190
12310
  const accountId = randomUUID9();
12191
12311
  const masterKey = this.getMasterKeyBytes();
12192
12312
  const encryptLocalConnectorValue = async (value) => {
@@ -12195,9 +12315,9 @@ var OpenMatesClient = class _OpenMatesClient {
12195
12315
  };
12196
12316
  const payload = {
12197
12317
  id: accountId,
12198
- hashed_user_id: createHash34("sha256").update(userId).digest("hex"),
12318
+ hashed_user_id: createHash35("sha256").update(userId).digest("hex"),
12199
12319
  encrypted_provider_type: await encryptLocalConnectorValue(input.provider_id),
12200
- provider_type_hash: createHash34("sha256").update(input.provider_id).digest("hex"),
12320
+ provider_type_hash: createHash35("sha256").update(input.provider_id).digest("hex"),
12201
12321
  encrypted_account_label: await encryptLocalConnectorValue(input.label),
12202
12322
  encrypted_capabilities: await encryptLocalConnectorValue(input.capabilities),
12203
12323
  encrypted_app_permissions: await encryptLocalConnectorValue({
@@ -13050,13 +13170,77 @@ var OpenMatesClient = class _OpenMatesClient {
13050
13170
  }
13051
13171
  /** Observe lifecycle metadata without claiming or collecting AI responses. */
13052
13172
  async observeChatActivity(onChange) {
13053
- const { ws } = await this.openWsClient({ taskUpdateJobs: false });
13054
- const types = ["ai_task_initiated", "ai_typing_started", "ai_typing_ended", "ai_background_response_completed", "ai_task_cancel_requested", "ai_task_error", "pending_ai_response"];
13055
- const cleanup = types.map((type) => ws.onMessageType(type, onChange));
13056
- return () => {
13057
- cleanup.forEach((remove) => remove());
13058
- ws.close();
13173
+ const session = this.requireSession();
13174
+ const owner = [
13175
+ session.apiUrl,
13176
+ session.hashedEmail,
13177
+ session.sessionId,
13178
+ session.createdAt,
13179
+ session.masterKeyExportedB64
13180
+ ];
13181
+ const ownerCurrent = () => {
13182
+ const current2 = this.session;
13183
+ return !!current2 && current2.apiUrl === owner[0] && current2.hashedEmail === owner[1] && current2.sessionId === owner[2] && current2.createdAt === owner[3] && current2.masterKeyExportedB64 === owner[4];
13059
13184
  };
13185
+ const types = ["ai_task_initiated", "ai_typing_started", "ai_typing_ended", "ai_background_response_completed", "ai_task_cancel_requested", "ai_task_error", "pending_ai_response", "team_chat_message_created"];
13186
+ let stopped = false;
13187
+ let socket = null;
13188
+ let removeHandlers = [];
13189
+ let retry = null;
13190
+ let retryAttempt = 0;
13191
+ const stop = () => {
13192
+ stopped = true;
13193
+ if (retry) clearTimeout(retry);
13194
+ retry = null;
13195
+ removeHandlers.forEach((remove) => remove());
13196
+ removeHandlers = [];
13197
+ const active = socket;
13198
+ socket = null;
13199
+ active?.close();
13200
+ };
13201
+ const scheduleReconnect = () => {
13202
+ if (stopped || !ownerCurrent() || retry) return;
13203
+ const delay2 = Math.min(1e4, 250 * 2 ** Math.min(retryAttempt++, 6));
13204
+ retry = setTimeout(() => {
13205
+ retry = null;
13206
+ void connect();
13207
+ }, delay2);
13208
+ retry.unref?.();
13209
+ };
13210
+ const connect = async () => {
13211
+ if (stopped || !ownerCurrent()) return;
13212
+ let opened;
13213
+ try {
13214
+ opened = (await this.openWsClient({ taskUpdateJobs: false })).ws;
13215
+ } catch {
13216
+ scheduleReconnect();
13217
+ return;
13218
+ }
13219
+ if (stopped || !ownerCurrent()) {
13220
+ opened.close();
13221
+ return;
13222
+ }
13223
+ socket = opened;
13224
+ removeHandlers = types.map((type) => opened.onMessageType(type, () => {
13225
+ if (!ownerCurrent()) {
13226
+ stop();
13227
+ return;
13228
+ }
13229
+ if (!stopped) {
13230
+ retryAttempt = 0;
13231
+ onChange();
13232
+ }
13233
+ }));
13234
+ removeHandlers.push(opened.onClose(() => {
13235
+ if (socket !== opened) return;
13236
+ socket = null;
13237
+ removeHandlers.forEach((remove) => remove());
13238
+ removeHandlers = [];
13239
+ scheduleReconnect();
13240
+ }));
13241
+ };
13242
+ await connect();
13243
+ return stop;
13060
13244
  }
13061
13245
  /** Read encrypted local history without refreshing or opening a connection. */
13062
13246
  async listCachedChats(limit = 10, page = 1, options = {}) {
@@ -13695,6 +13879,7 @@ var OpenMatesClient = class _OpenMatesClient {
13695
13879
  role: String(m.role ?? "unknown"),
13696
13880
  content: content ?? "",
13697
13881
  senderName,
13882
+ hashedSenderUserId: typeof m.hashed_user_id === "string" ? m.hashed_user_id : null,
13698
13883
  category: msgCategory,
13699
13884
  modelName,
13700
13885
  createdAt: typeof m.created_at === "number" ? m.created_at : 0,
@@ -13999,12 +14184,35 @@ var OpenMatesClient = class _OpenMatesClient {
13999
14184
  }
14000
14185
  async getChatMessagesWindow(query, options = {}) {
14001
14186
  const teamId = this.resolveTeamContext(options);
14002
- const cache = await this.ensureSynced(false, [], options);
14003
- const masterKey = this.getMasterKeyBytes();
14004
- const wrappingKey = await this.getChatWrappingKey(teamId, masterKey);
14187
+ const session = this.requireSession();
14188
+ const owner = [
14189
+ session.apiUrl,
14190
+ session.hashedEmail,
14191
+ session.sessionId,
14192
+ session.createdAt,
14193
+ session.masterKeyExportedB64
14194
+ ];
14195
+ const masterKey = Buffer.from(this.getMasterKeyBytes());
14196
+ const selectedTeamId = this.getActiveTeamId();
14197
+ const current2 = () => {
14198
+ const active = this.session;
14199
+ if (!active || active.apiUrl !== owner[0] || active.hashedEmail !== owner[1] || active.sessionId !== owner[2] || active.createdAt !== owner[3] || active.masterKeyExportedB64 !== owner[4] || options.preferCache && this.getActiveTeamId() !== selectedTeamId)
14200
+ throw new Error("Chat workspace changed");
14201
+ };
14202
+ const cache = options.preferCache ? loadSyncCache(teamId) : await this.ensureSynced(false, [], options);
14203
+ current2();
14204
+ if (!cache) throw new Error("Chat cache unavailable. Open the chat to refresh it.");
14205
+ const localTeamKey = options.preferCache && teamId ? loadLocalTeamKey(owner[1], teamId) : null;
14206
+ if (options.preferCache && teamId && !localTeamKey) throw new Error("Team key unavailable offline. Reconnect to refresh Team access.");
14207
+ const wrappingKey = localTeamKey ? base64ToBytes(localTeamKey) : await this.getChatWrappingKey(teamId, masterKey);
14208
+ current2();
14005
14209
  const found = await this.resolveCachedChatForQuery(query, cache, wrappingKey, teamId);
14210
+ current2();
14006
14211
  const chatItem = await this.decryptChatListItem(found, wrappingKey, cache, teamId);
14212
+ current2();
14007
14213
  const chatKeyBytes = await this.resolveChatKey(cache, found, wrappingKey, teamId);
14214
+ current2();
14215
+ if (!chatKeyBytes) throw new Error("Chat key unavailable. Use /refresh to retry.");
14008
14216
  const chatId = String(found.details.id ?? chatItem.id);
14009
14217
  const params = new URLSearchParams({
14010
14218
  direction: options.direction ?? "latest",
@@ -14016,13 +14224,40 @@ var OpenMatesClient = class _OpenMatesClient {
14016
14224
  if (options.afterMessageId) params.set("after_message_id", options.afterMessageId);
14017
14225
  if (options.anchorMessageId) params.set("anchor_message_id", options.anchorMessageId);
14018
14226
  if (options.respectCompressionBoundary === false) params.set("respect_compression_boundary", "false");
14227
+ current2();
14019
14228
  const response = await this.http.get(
14020
14229
  this.appendTeamQuery(`/v1/chats/${encodeURIComponent(chatId)}/messages/window?${params.toString()}`, { teamId }),
14021
14230
  this.getCliRequestHeaders()
14022
14231
  );
14232
+ current2();
14023
14233
  if (!response.ok) throw new Error(`Chat message window failed with HTTP ${response.status}`);
14024
14234
  const rawMessages = Array.isArray(response.data.messages) ? response.data.messages : [];
14025
14235
  const messages = await this.decryptRawChatMessages(rawMessages, chatItem, chatKeyBytes, cache.embeds);
14236
+ current2();
14237
+ if (options.preferCache && teamId && (options.direction ?? "latest") === "latest") {
14238
+ const latestCache = loadSyncCache(teamId);
14239
+ const row3 = latestCache?.chats.find((chat) => String(chat.details.id) === chatId);
14240
+ if (row3 && row3.details.messages_v === found.details.messages_v && row3.details.encrypted_chat_key === found.details.encrypted_chat_key && row3.messages.length === found.messages.length && row3.messages.every((raw, index) => raw === found.messages[index])) {
14241
+ const canonicalId = (raw) => {
14242
+ const value = typeof raw === "string" ? JSON.parse(raw) : raw;
14243
+ return String(value.client_message_id ?? value.id ?? value.message_id ?? "");
14244
+ };
14245
+ const existingById = new Map(row3.messages.map((raw, index) => [canonicalId(raw), index]));
14246
+ const overlap = rawMessages.find((raw) => !!canonicalId(raw) && existingById.has(canonicalId(raw)));
14247
+ const older = response.data.has_more_before !== true ? [] : overlap ? row3.messages.slice(0, existingById.get(canonicalId(overlap))) : row3.messages;
14248
+ const byId = /* @__PURE__ */ new Map();
14249
+ for (const raw of [...older, ...rawMessages]) {
14250
+ const value = typeof raw === "string" ? JSON.parse(raw) : raw;
14251
+ const id = String(value.client_message_id ?? value.id ?? value.message_id ?? "");
14252
+ const serialized = typeof raw === "string" ? raw : JSON.stringify(raw);
14253
+ byId.set(id || `unidentified:${serialized}`, { raw: serialized, createdAt: Number(value.created_at ?? 0) });
14254
+ }
14255
+ const merged = [...byId.values()].sort((a, b) => a.createdAt - b.createdAt).map((message) => message.raw);
14256
+ current2();
14257
+ if (merged.length !== row3.messages.length || merged.some((raw, index) => raw !== row3.messages[index]))
14258
+ saveSyncCache({ ...latestCache, chats: latestCache.chats.map((chat) => chat === row3 ? { ...row3, messages: merged } : chat) }, teamId);
14259
+ }
14260
+ }
14026
14261
  return {
14027
14262
  chat: chatItem,
14028
14263
  messages,
@@ -14248,8 +14483,8 @@ var OpenMatesClient = class _OpenMatesClient {
14248
14483
  );
14249
14484
  }
14250
14485
  const embedId = String(embed.embed_id ?? embed.id ?? "");
14251
- const { createHash: createHash34 } = await import("crypto");
14252
- const hashedEmbedId = createHash34("sha256").update(embedId).digest("hex");
14486
+ const { createHash: createHash35 } = await import("crypto");
14487
+ const hashedEmbedId = createHash35("sha256").update(embedId).digest("hex");
14253
14488
  const embedKeyBytes = await this.resolveEmbedKey(
14254
14489
  cache,
14255
14490
  wrappingKey,
@@ -14257,8 +14492,8 @@ var OpenMatesClient = class _OpenMatesClient {
14257
14492
  embedId,
14258
14493
  hashedEmbedId
14259
14494
  );
14260
- const decryptField2 = async (field2) => {
14261
- const val = embed[field2];
14495
+ const decryptField2 = async (field3) => {
14496
+ const val = embed[field3];
14262
14497
  if (typeof val !== "string" || !embedKeyBytes) return null;
14263
14498
  return decryptWithAesGcmCombined(val, embedKeyBytes);
14264
14499
  };
@@ -14363,7 +14598,7 @@ var OpenMatesClient = class _OpenMatesClient {
14363
14598
  async resolveEmbedKey(cache, masterKey, embed, embedId, hashedEmbedId, visited = /* @__PURE__ */ new Set()) {
14364
14599
  if (visited.has(embedId)) return null;
14365
14600
  visited.add(embedId);
14366
- const { createHash: createHash34 } = await import("crypto");
14601
+ const { createHash: createHash35 } = await import("crypto");
14367
14602
  const masterKeyEntry = cache.embedKeys.find(
14368
14603
  (ek) => ek.hashed_embed_id === hashedEmbedId && String(ek.key_type) === "master"
14369
14604
  );
@@ -14380,7 +14615,7 @@ var OpenMatesClient = class _OpenMatesClient {
14380
14615
  if (chatKeyEntry && typeof chatKeyEntry.encrypted_embed_key === "string") {
14381
14616
  const hashedChatId = String(chatKeyEntry.hashed_chat_id ?? "");
14382
14617
  const owningChat = cache.chats.find((c) => {
14383
- const chatHash = createHash34("sha256").update(String(c.details.id ?? "")).digest("hex");
14618
+ const chatHash = createHash35("sha256").update(String(c.details.id ?? "")).digest("hex");
14384
14619
  return chatHash === hashedChatId;
14385
14620
  });
14386
14621
  if (owningChat) {
@@ -14400,7 +14635,7 @@ var OpenMatesClient = class _OpenMatesClient {
14400
14635
  const parentFullId = String(
14401
14636
  parentEmbed2.embed_id ?? parentEmbed2.id ?? ""
14402
14637
  );
14403
- const parentHashedId = createHash34("sha256").update(parentFullId).digest("hex");
14638
+ const parentHashedId = createHash35("sha256").update(parentFullId).digest("hex");
14404
14639
  const parentKey = await this.resolveEmbedKey(
14405
14640
  cache,
14406
14641
  masterKey,
@@ -14581,8 +14816,8 @@ var OpenMatesClient = class _OpenMatesClient {
14581
14816
  }
14582
14817
  if (!rootKey) throw new Error(`Recovery root chat ${output.root_chat_id} key is unavailable.`);
14583
14818
  const ready2 = await request("recovery_output_get", "recovery_output_ready", output.record_id, {});
14584
- for (const field2 of ["record_id", "root_chat_id", "target_chat_id", "turn_id", "subject_id", "output_kind", "output_version", "chat_key_version"]) {
14585
- if (ready2[field2] !== output[field2]) throw new Error(`Recovery output ${output.record_id} identity mismatch: ${field2}.`);
14819
+ for (const field3 of ["record_id", "root_chat_id", "target_chat_id", "turn_id", "subject_id", "output_kind", "output_version", "chat_key_version"]) {
14820
+ if (ready2[field3] !== output[field3]) throw new Error(`Recovery output ${output.record_id} identity mismatch: ${field3}.`);
14586
14821
  }
14587
14822
  if (output.root_hashed_team_id !== void 0 && ready2.root_hashed_team_id !== output.root_hashed_team_id) {
14588
14823
  throw new Error(`Recovery output ${output.record_id} Team identity changed after discovery.`);
@@ -14833,7 +15068,7 @@ var OpenMatesClient = class _OpenMatesClient {
14833
15068
  let sealedSource;
14834
15069
  if (content.type === "mail") {
14835
15070
  const fields = ["receiver", "subject", "content", "footer"];
14836
- if (fields.some((field2) => sealedEmbed[field2] != null && typeof sealedEmbed[field2] !== "string")) {
15071
+ if (fields.some((field3) => sealedEmbed[field3] != null && typeof sealedEmbed[field3] !== "string")) {
14837
15072
  throw new Error("Sealed recovery mail had invalid historical fields.");
14838
15073
  }
14839
15074
  const receiver = String(sealedEmbed.receiver ?? "");
@@ -14867,11 +15102,11 @@ var OpenMatesClient = class _OpenMatesClient {
14867
15102
  sheet: "table",
14868
15103
  mermaid: "diagram_code"
14869
15104
  };
14870
- const field2 = historicalFieldByType[content.type];
14871
- if (!field2 || typeof sealedEmbed[field2] !== "string") {
15105
+ const field3 = historicalFieldByType[content.type];
15106
+ if (!field3 || typeof sealedEmbed[field3] !== "string") {
14872
15107
  throw new Error("Recovery cannot verify this embed's historical source format.");
14873
15108
  }
14874
- sealedSource = sealedEmbed[field2];
15109
+ sealedSource = sealedEmbed[field3];
14875
15110
  }
14876
15111
  if (targetRow.version_number !== output.output_version || historicalContent !== sealedSource) {
14877
15112
  throw new Error("Canonical historical recovery embed does not match sealed output.");
@@ -15118,7 +15353,7 @@ var OpenMatesClient = class _OpenMatesClient {
15118
15353
  }
15119
15354
  async sendMessage(params) {
15120
15355
  const teamId = this.resolveTeamContext({ teamId: params.teamId, personal: params.personal });
15121
- const teamSenderName = teamId ? String((await this.whoAmI()).username ?? "").trim() || "Team member" : "User";
15356
+ const teamSenderName = teamId ? await this.ownTeamSenderName(teamId) : "User";
15122
15357
  if (params.chatId && params.newChatId) {
15123
15358
  throw new Error("chatId and newChatId cannot be used together.");
15124
15359
  }
@@ -15182,9 +15417,9 @@ var OpenMatesClient = class _OpenMatesClient {
15182
15417
  for (const embed of params.preparedEmbeds ?? []) {
15183
15418
  if (!["code-code", "docs-doc", "sheets-sheet"].includes(embed.type)) continue;
15184
15419
  const decoded = toonDecode(embed.content);
15185
- for (const field2 of ["code", "html", "table", "text"]) if (typeof decoded[field2] === "string") {
15186
- const value = await prepareCliMessagePrivacy(decoded[field2], availableMemories, piiMappings, { kind: "document" }, params.onPrivacyProgress);
15187
- decoded[field2] = value.message;
15420
+ for (const field3 of ["code", "html", "table", "text"]) if (typeof decoded[field3] === "string") {
15421
+ const value = await prepareCliMessagePrivacy(decoded[field3], availableMemories, piiMappings, { kind: "document" }, params.onPrivacyProgress);
15422
+ decoded[field3] = value.message;
15188
15423
  piiMappings = value.mappings;
15189
15424
  }
15190
15425
  embed.content = toonEncodeContent(decoded);
@@ -15625,7 +15860,7 @@ var OpenMatesClient = class _OpenMatesClient {
15625
15860
  if (!chatSlugLookupKey) {
15626
15861
  throw new Error("Encrypted chat slug lookup key was not initialized.");
15627
15862
  }
15628
- const initialTitle = teamId && !shouldWaitForAi ? "New team chat" : "";
15863
+ const initialTitle = teamId && !shouldWaitForAi ? teamChatTitleFromMessage(params.message) : "";
15629
15864
  const slugMetadata = await buildEncryptedObjectSlugMetadata({
15630
15865
  value: selectNewChatSlugValue({
15631
15866
  message: finalMessage,
@@ -15744,7 +15979,7 @@ var OpenMatesClient = class _OpenMatesClient {
15744
15979
  if (!params.incognito && typeof confirmedPayload.new_messages_v === "number" && Number.isSafeInteger(confirmedPayload.new_messages_v)) {
15745
15980
  terminalExpectedMessagesV = confirmedPayload.new_messages_v;
15746
15981
  }
15747
- if (!params.incognito) {
15982
+ if (!params.incognito && (!teamId || shouldWaitForAi)) {
15748
15983
  clearSyncCache(teamId);
15749
15984
  }
15750
15985
  let assistant = "";
@@ -16352,6 +16587,7 @@ var OpenMatesClient = class _OpenMatesClient {
16352
16587
  status: "completed",
16353
16588
  chatId,
16354
16589
  messageId: assistantMessageId,
16590
+ userMessageId: messageId,
16355
16591
  assistant,
16356
16592
  category,
16357
16593
  modelName,
@@ -18500,8 +18736,21 @@ var OpenMatesClient = class _OpenMatesClient {
18500
18736
  // -------------------------------------------------------------------------
18501
18737
  // User tasks
18502
18738
  // -------------------------------------------------------------------------
18503
- async listUserTasks(filters = {}) {
18504
- this.requireSession();
18739
+ async listUserTasks(filters = {}, options = {}) {
18740
+ const session = this.requireSession();
18741
+ const owner = [
18742
+ session.apiUrl,
18743
+ session.hashedEmail,
18744
+ session.sessionId,
18745
+ session.createdAt,
18746
+ session.masterKeyExportedB64,
18747
+ session.activeTeamId
18748
+ ];
18749
+ const current2 = () => {
18750
+ const active = this.session;
18751
+ if (!active || active.apiUrl !== owner[0] || active.hashedEmail !== owner[1] || active.sessionId !== owner[2] || active.createdAt !== owner[3] || active.masterKeyExportedB64 !== owner[4] || active.activeTeamId !== owner[5])
18752
+ throw new Error("Task workspace changed");
18753
+ };
18505
18754
  const params = new URLSearchParams();
18506
18755
  if (filters.status) params.set("status", filters.status);
18507
18756
  if (filters.chatId) params.set("chat_id", filters.chatId);
@@ -18510,25 +18759,102 @@ var OpenMatesClient = class _OpenMatesClient {
18510
18759
  if (filters.externalChatProvider) params.set("external_chat_provider", filters.externalChatProvider);
18511
18760
  if (filters.externalChatLookupHash) params.set("external_chat_lookup_hash", filters.externalChatLookupHash);
18512
18761
  if (filters.priority !== void 0) params.set("priority", String(filters.priority));
18513
- const maximumTaskListPage = 500;
18762
+ const maximumTaskListPage = 100;
18514
18763
  const limit = Math.min(filters.limit ?? maximumTaskListPage, maximumTaskListPage);
18515
18764
  if (!Number.isSafeInteger(limit) || limit <= 0) throw new Error("Invalid task list limit");
18516
- if (Number.isSafeInteger(limit) && limit !== void 0 && limit > 0) params.set("limit", String(limit));
18765
+ params.set("limit", String(limit));
18766
+ params.set("paginate", "true");
18517
18767
  const teamId = this.resolveTeamContext({ teamId: filters.teamId, personal: filters.personal });
18518
18768
  if (teamId) params.set("team_id", teamId);
18519
- const query = params.toString();
18520
- const response = await this.http.get(
18521
- `/v1/user-tasks${query ? `?${query}` : ""}`,
18522
- this.getCliRequestHeaders()
18523
- );
18524
- if (!response.ok) {
18525
- throw new Error(`User task list failed with HTTP ${response.status}`);
18526
- }
18527
- const tasks = response.data.tasks ?? [];
18528
- if (tasks.length >= limit) {
18529
- throw new Error(`TASK_LIST_INCOMPLETE: server limit ${limit} reached; refusing a truncated task inventory.`);
18769
+ const mayPageProjections = !teamId && !filters.chatId && !filters.projectId && !filters.externalChatProvider && !filters.externalChatLookupHash && !filters.labelHashes?.length && filters.priority === void 0;
18770
+ const projectionCursorPrefix = "workflow-tasks:";
18771
+ const isProjection = (task) => {
18772
+ const projection = task;
18773
+ return projection.source === "workflow_run" && projection.read_only === true;
18774
+ };
18775
+ const allTasks = [];
18776
+ const seenTaskIds = /* @__PURE__ */ new Set();
18777
+ const seenCursors = /* @__PURE__ */ new Set();
18778
+ let cursor = null;
18779
+ while (true) {
18780
+ current2();
18781
+ const pageParams = new URLSearchParams(params);
18782
+ if (cursor) pageParams.set("cursor", cursor);
18783
+ const response = await this.http.get(
18784
+ `/v1/user-tasks?${pageParams.toString()}`,
18785
+ this.getCliRequestHeaders()
18786
+ );
18787
+ current2();
18788
+ if (!response.ok) throw new Error(`User task list failed with HTTP ${response.status}`);
18789
+ if (!response.data || typeof response.data !== "object")
18790
+ throw new Error("TASK_LIST_INCOMPLETE: invalid task response.");
18791
+ const page = response.data.tasks;
18792
+ if (!Array.isArray(page) || page.some((task) => !task || typeof task.task_id !== "string" || !task.task_id))
18793
+ throw new Error("TASK_LIST_INCOMPLETE: invalid task page.");
18794
+ const hasPaging = Object.hasOwn(response.data, "complete") || Object.hasOwn(response.data, "next_cursor");
18795
+ let complete;
18796
+ let nextCursor;
18797
+ if (!hasPaging) {
18798
+ if (cursor || page.length >= limit)
18799
+ throw new Error("TASK_LIST_INCOMPLETE: legacy task page cannot prove completeness.");
18800
+ complete = true;
18801
+ nextCursor = null;
18802
+ } else {
18803
+ if (typeof response.data.complete !== "boolean" || !Object.hasOwn(response.data, "next_cursor"))
18804
+ throw new Error("TASK_LIST_INCOMPLETE: invalid task paging metadata.");
18805
+ complete = response.data.complete;
18806
+ nextCursor = response.data.next_cursor ?? null;
18807
+ if (response.data.next_cursor !== null && typeof response.data.next_cursor !== "string")
18808
+ throw new Error("TASK_LIST_INCOMPLETE: invalid task cursor.");
18809
+ if (complete && nextCursor !== null)
18810
+ throw new Error("TASK_LIST_INCOMPLETE: completed task page has a cursor.");
18811
+ if (!complete) {
18812
+ if (page.length !== limit || !nextCursor || nextCursor.length > 512 || seenCursors.has(nextCursor))
18813
+ throw new Error("TASK_LIST_INCOMPLETE: task cursor did not advance.");
18814
+ const inProjections2 = cursor?.startsWith(projectionCursorPrefix) ?? false;
18815
+ if (nextCursor.startsWith(projectionCursorPrefix)) {
18816
+ const last = page.at(-1);
18817
+ const nextId = nextCursor.slice(projectionCursorPrefix.length);
18818
+ if (!mayPageProjections || (isProjection(last) ? nextId !== last.task_id : inProjections2 || nextId !== "" || page.some(isProjection)) || inProjections2 && nextId <= cursor.slice(projectionCursorPrefix.length))
18819
+ throw new Error("TASK_LIST_INCOMPLETE: workflow task cursor did not advance.");
18820
+ } else if (inProjections2 || page.some(isProjection) || nextCursor !== page.at(-1)?.task_id || cursor !== null && nextCursor <= cursor) {
18821
+ throw new Error("TASK_LIST_INCOMPLETE: task cursor did not advance.");
18822
+ }
18823
+ }
18824
+ }
18825
+ const inProjections = cursor?.startsWith(projectionCursorPrefix) ?? false;
18826
+ if (!mayPageProjections && page.some(isProjection))
18827
+ throw new Error("TASK_LIST_INCOMPLETE: workflow tasks in a filtered inventory.");
18828
+ if (inProjections && (!mayPageProjections || page.some((task) => !isProjection(task))))
18829
+ throw new Error("TASK_LIST_INCOMPLETE: invalid workflow task phase.");
18830
+ const priorId = inProjections ? cursor.slice(projectionCursorPrefix.length) : cursor;
18831
+ if (priorId !== null && page.length && page[0].task_id <= priorId)
18832
+ throw new Error("TASK_LIST_INCOMPLETE: task page repeated an earlier cursor.");
18833
+ if (page.length > limit)
18834
+ throw new Error("TASK_LIST_INCOMPLETE: task page exceeded its limit.");
18835
+ let projectionRows = inProjections;
18836
+ let previousId = priorId;
18837
+ for (const task of page) {
18838
+ if (isProjection(task) && !projectionRows) {
18839
+ projectionRows = true;
18840
+ previousId = null;
18841
+ } else if (projectionRows && !isProjection(task)) {
18842
+ throw new Error("TASK_LIST_INCOMPLETE: canonical task after workflow task phase.");
18843
+ }
18844
+ if (hasPaging && previousId !== null && task.task_id <= previousId)
18845
+ throw new Error("TASK_LIST_INCOMPLETE: task page is not ordered.");
18846
+ previousId = task.task_id;
18847
+ if (seenTaskIds.has(task.task_id)) throw new Error("TASK_LIST_INCOMPLETE: duplicate task in inventory.");
18848
+ seenTaskIds.add(task.task_id);
18849
+ }
18850
+ allTasks.push(...page);
18851
+ current2();
18852
+ await options.onPage?.([...allTasks], complete);
18853
+ current2();
18854
+ if (complete) return allTasks;
18855
+ seenCursors.add(nextCursor);
18856
+ cursor = nextCursor;
18530
18857
  }
18531
- return tasks;
18532
18858
  }
18533
18859
  /** Submit a confirmed Codex runtime deletion, never a missing-list inference. */
18534
18860
  async unlinkDeletedTaskChat(lookupHash, eventId, context = {}) {
@@ -19722,9 +20048,9 @@ var OpenMatesClient = class _OpenMatesClient {
19722
20048
  }
19723
20049
  let feature = null;
19724
20050
  let followUpSuggestions = [];
19725
- for (const [field2, kind] of [["encrypted_feature_metadata", "feature"], ["encrypted_follow_up_suggestions", "suggestions"]]) {
19726
- if (typeof r[field2] !== "string") continue;
19727
- const plain = await decryptWithAesGcmCombined(r[field2], masterKey);
20051
+ for (const [field3, kind] of [["encrypted_feature_metadata", "feature"], ["encrypted_follow_up_suggestions", "suggestions"]]) {
20052
+ if (typeof r[field3] !== "string") continue;
20053
+ const plain = await decryptWithAesGcmCombined(r[field3], masterKey);
19728
20054
  if (!plain) continue;
19729
20055
  try {
19730
20056
  const value = JSON.parse(plain);
@@ -20035,11 +20361,11 @@ ${known}`
20035
20361
  Required: ${schema.required.join(", ")}`
20036
20362
  );
20037
20363
  }
20038
- for (const [field2, def] of Object.entries(schema.properties)) {
20039
- const val = params.itemValue[field2];
20364
+ for (const [field3, def] of Object.entries(schema.properties)) {
20365
+ const val = params.itemValue[field3];
20040
20366
  if (val !== void 0 && def.enum && !def.enum.includes(String(val))) {
20041
20367
  throw new Error(
20042
- `Invalid value '${String(val)}' for field '${field2}'. Allowed values: ${def.enum.join(", ")}`
20368
+ `Invalid value '${String(val)}' for field '${field3}'. Allowed values: ${def.enum.join(", ")}`
20043
20369
  );
20044
20370
  }
20045
20371
  }
@@ -20203,7 +20529,7 @@ Required: ${schema.required.join(", ")}`
20203
20529
  const session = this.requireSession();
20204
20530
  const masterKey = base64ToBytes(session.masterKeyExportedB64);
20205
20531
  const cache = await this.ensureSynced();
20206
- const { createHash: createHash34 } = await import("crypto");
20532
+ const { createHash: createHash35 } = await import("crypto");
20207
20533
  const embed = cache.embeds.find(
20208
20534
  (e) => String(e.embed_id ?? "").startsWith(embedIdOrShort) || String(e.id ?? "").startsWith(embedIdOrShort)
20209
20535
  );
@@ -20211,7 +20537,7 @@ Required: ${schema.required.join(", ")}`
20211
20537
  throw new Error(`Embed '${embedIdOrShort}' not found in local cache.`);
20212
20538
  }
20213
20539
  const embedId = String(embed.embed_id ?? embed.id ?? "");
20214
- const hashedEmbedId = createHash34("sha256").update(embedId).digest("hex");
20540
+ const hashedEmbedId = createHash35("sha256").update(embedId).digest("hex");
20215
20541
  const embedKeyBytes = await this.resolveEmbedKey(
20216
20542
  cache,
20217
20543
  masterKey,
@@ -20378,8 +20704,8 @@ Required: ${schema.required.join(", ")}`
20378
20704
  if (!embed) {
20379
20705
  throw new Error(`Embed '${embedId}' not found in local cache. Run 'openmates chats list' to sync first.`);
20380
20706
  }
20381
- const { createHash: createHash34 } = await import("crypto");
20382
- const hashedEmbedId = createHash34("sha256").update(embedId).digest("hex");
20707
+ const { createHash: createHash35 } = await import("crypto");
20708
+ const hashedEmbedId = createHash35("sha256").update(embedId).digest("hex");
20383
20709
  const embedKey = await this.resolveEmbedKey(
20384
20710
  cache,
20385
20711
  masterKey,
@@ -20510,8 +20836,8 @@ Required: ${schema.required.join(", ")}`
20510
20836
  if (!embed) {
20511
20837
  throw new Error(`Embed '${embedId}' not found in local cache. Run 'openmates chats list' to sync first.`);
20512
20838
  }
20513
- const { createHash: createHash34 } = await import("crypto");
20514
- const hashedEmbedId = createHash34("sha256").update(embedId).digest("hex");
20839
+ const { createHash: createHash35 } = await import("crypto");
20840
+ const hashedEmbedId = createHash35("sha256").update(embedId).digest("hex");
20515
20841
  const embedKey = await this.resolveEmbedKey(
20516
20842
  cache,
20517
20843
  masterKey,
@@ -21042,9 +21368,9 @@ Required: ${schema.required.join(", ")}`
21042
21368
  chats.splice(0, chats.length, ...reconciledChats);
21043
21369
  if (totalChatCount === 0) totalChatCount = chats.length;
21044
21370
  if (reconciliation.deleted_chat_ids?.length) {
21045
- const { createHash: createHash34 } = await import("crypto");
21371
+ const { createHash: createHash35 } = await import("crypto");
21046
21372
  const deletedHashes = new Set(
21047
- reconciliation.deleted_chat_ids.map((id) => createHash34("sha256").update(id).digest("hex"))
21373
+ reconciliation.deleted_chat_ids.map((id) => createHash35("sha256").update(id).digest("hex"))
21048
21374
  );
21049
21375
  const keptEmbeds = embeds.filter((embed) => !deletedHashes.has(String(embed.hashed_chat_id ?? "")));
21050
21376
  const keptKeys = embedKeys.filter((key) => !deletedHashes.has(String(key.hashed_chat_id ?? "")));
@@ -28024,10 +28350,10 @@ function assertAccountExportTextSafe(content, relativePath2) {
28024
28350
  for (const pattern of ACCOUNT_EXPORT_FORBIDDEN_VALUE_PATTERNS) {
28025
28351
  if (pattern.test(content)) throw new Error(`Account export file ${relativePath2} contains forbidden secret-like content`);
28026
28352
  }
28027
- ACCOUNT_EXPORT_FORBIDDEN_FIELD_NAMES.forEach((field2) => {
28028
- const escapedField = field2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
28353
+ ACCOUNT_EXPORT_FORBIDDEN_FIELD_NAMES.forEach((field3) => {
28354
+ const escapedField = field3.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
28029
28355
  if (new RegExp(`(^|[^A-Za-z0-9_])['"]?${escapedField}['"]?\\s*:`, "i").test(content)) {
28030
- throw new Error(`Account export file ${relativePath2} contains forbidden secret field '${field2}'`);
28356
+ throw new Error(`Account export file ${relativePath2} contains forbidden secret field '${field3}'`);
28031
28357
  }
28032
28358
  });
28033
28359
  if (/(^|[^A-Za-z0-9_])['"]?encrypted_[A-Za-z0-9_]*['"]?\s*:/i.test(content)) {
@@ -31203,11 +31529,11 @@ var OpenMatesPlans = class {
31203
31529
  this.review = { submit: (planId) => this.submitForReview(planId) };
31204
31530
  this.approval = { status: (planId) => this.show(planId) };
31205
31531
  }
31206
- textSectionFacade(field2) {
31532
+ textSectionFacade(field3) {
31207
31533
  return {
31208
- add: (planId, value) => this.update(planId, { [field2]: value }),
31209
- update: (planId, value) => this.update(planId, { [field2]: value }),
31210
- remove: (planId) => this.update(planId, { [field2]: "" })
31534
+ add: (planId, value) => this.update(planId, { [field3]: value }),
31535
+ update: (planId, value) => this.update(planId, { [field3]: value }),
31536
+ remove: (planId) => this.update(planId, { [field3]: "" })
31211
31537
  };
31212
31538
  }
31213
31539
  async list(filters = {}) {
@@ -32156,6 +32482,36 @@ var OpenMatesTeams = class {
32156
32482
  async members(teamId) {
32157
32483
  return this.client.get(`/v1/teams/${encodeURIComponent(teamId)}/members`);
32158
32484
  }
32485
+ async decryptMemberProfile(teamKey, member) {
32486
+ const encrypted = member.encrypted_member_profile;
32487
+ if (typeof encrypted !== "string" || !encrypted) return { ...member, profile: null };
32488
+ const plaintext = await decryptWithAesGcmCombined(encrypted, teamKey);
32489
+ if (!plaintext) throw new OpenMatesConfigError("Failed to decrypt Team member profile");
32490
+ const parsed = JSON.parse(plaintext);
32491
+ return { ...member, profile: {
32492
+ display_name: typeof parsed.display_name === "string" ? parsed.display_name : void 0,
32493
+ avatar: typeof parsed.avatar === "string" ? parsed.avatar : null
32494
+ } };
32495
+ }
32496
+ async listMembers(teamId) {
32497
+ const teamKey = await teamKeyForRecord(this.client, await this.get(teamId));
32498
+ const response = await this.client.get(`/v1/teams/${encodeURIComponent(teamId)}/members`);
32499
+ return Promise.all((response.members ?? []).map((member) => this.decryptMemberProfile(teamKey, member)));
32500
+ }
32501
+ async getMember(teamId, memberUserId) {
32502
+ const teamKey = await teamKeyForRecord(this.client, await this.get(teamId));
32503
+ const response = await this.client.get(`/v1/teams/${encodeURIComponent(teamId)}/members/${encodeURIComponent(memberUserId)}`);
32504
+ if (!response.member) throw new OpenMatesConfigError("Team member response is missing member");
32505
+ return this.decryptMemberProfile(teamKey, response.member);
32506
+ }
32507
+ async updateOwnMemberProfile(teamId, profile) {
32508
+ const teamKey = await teamKeyForRecord(this.client, await this.get(teamId));
32509
+ const response = await this.client.patch(`/v1/teams/${encodeURIComponent(teamId)}/members/me/profile`, {
32510
+ encrypted_member_profile: await encryptWithAesGcmCombined(JSON.stringify({ display_name: profile.display_name?.trim() ?? "", avatar: profile.avatar ?? null }), teamKey)
32511
+ });
32512
+ if (!response.member) throw new OpenMatesConfigError("Team profile response is missing member");
32513
+ return this.decryptMemberProfile(teamKey, response.member);
32514
+ }
32159
32515
  async invites(teamId) {
32160
32516
  return this.client.get(`/v1/teams/${encodeURIComponent(teamId)}/invites`);
32161
32517
  }
@@ -32468,7 +32824,7 @@ async function flushPendingTaskActivities(client2, teamId, stateDir2 = resolveSt
32468
32824
  import { existsSync as existsSync25, readFileSync as readFileSync25, realpathSync as realpathSync13, readdirSync as readdirSync13, statSync as statSync13, writeFileSync as writeFileSync18 } from "fs";
32469
32825
  import { fileURLToPath as fileURLToPath4 } from "url";
32470
32826
  import { basename as basename10, dirname as dirname18, join as join30 } from "path";
32471
- import { createHash as createHash33, randomBytes as randomBytes13, randomUUID as randomUUID24 } from "crypto";
32827
+ import { createHash as createHash34, randomBytes as randomBytes13, randomUUID as randomUUID24 } from "crypto";
32472
32828
  import { arch as arch2, platform as platform2 } from "os";
32473
32829
  import { parse as parseYaml, parseDocument as parseYamlDocument, stringify as stringifyYaml } from "yaml";
32474
32830
 
@@ -33656,6 +34012,37 @@ function wrapCells(value, width) {
33656
34012
  }
33657
34013
  return result;
33658
34014
  }
34015
+ function wrapWords(value, width) {
34016
+ width = Math.max(1, width);
34017
+ return terminalText(value).split("\n").flatMap((paragraph) => {
34018
+ const words = paragraph.trim().split(/\s+/u).filter(Boolean);
34019
+ if (!words.length) return [""];
34020
+ const indent = (paragraph.match(/^ */u)?.[0] ?? "").slice(0, Math.max(0, width - 2));
34021
+ const available2 = width - cells(indent);
34022
+ const lines = [];
34023
+ let line3 = indent;
34024
+ for (const word of words) {
34025
+ const separator = line3.length > indent.length ? " " : "";
34026
+ if (cells(line3 + separator + word) <= width) {
34027
+ line3 += separator + word;
34028
+ continue;
34029
+ }
34030
+ if (line3.length > indent.length) {
34031
+ lines.push(line3);
34032
+ line3 = indent;
34033
+ }
34034
+ if (cells(word) <= available2) {
34035
+ line3 += word;
34036
+ continue;
34037
+ }
34038
+ const parts = wrapCells(word, available2);
34039
+ lines.push(...parts.slice(0, -1).map((part) => indent + part));
34040
+ line3 = indent + parts[parts.length - 1];
34041
+ }
34042
+ lines.push(line3);
34043
+ return lines;
34044
+ });
34045
+ }
33659
34046
  function eraseGrapheme(value) {
33660
34047
  const items = [...segments.segment(value)];
33661
34048
  return items.length ? value.slice(0, items[items.length - 1].index) : "";
@@ -35031,7 +35418,7 @@ function renderDirectTypeFullscreen(embed, c, write) {
35031
35418
  const venue = isRecord2(c.venue) ? c.venue : {};
35032
35419
  const organizer = isRecord2(c.organizer) ? c.organizer : {};
35033
35420
  const fee = isRecord2(c.fee) ? c.fee : {};
35034
- const field2 = (label3, value) => {
35421
+ const field3 = (label3, value) => {
35035
35422
  if (value !== null && value !== void 0 && String(value).trim()) {
35036
35423
  writeEmbedLine(write, label3);
35037
35424
  writeEmbedLine(write, String(value));
@@ -35040,13 +35427,13 @@ function renderDirectTypeFullscreen(embed, c, write) {
35040
35427
  };
35041
35428
  writeEmbedLine(write, str(c.title) ?? str(c.name) ?? "Event");
35042
35429
  writeEmbedLine(write);
35043
- field2("When", [c.date_start ?? c.start_time ?? c.date, c.date_end, c.timezone].filter(Boolean).join(" \xB7 "));
35044
- field2("Location", c.event_type === "online" ? "Online event" : [venue.name ?? c.venue_name, venue.address ?? c.venue_address, venue.city ?? c.venue_city, venue.country ?? c.venue_country, str(c.location)].filter(Boolean).join(", "));
35045
- field2("Organizer", organizer.name ?? c.organizer_name);
35046
- field2("Description", c.description);
35047
- field2("Admission", c.is_paid === false ? "Free" : [fee.amount ?? c.fee_amount, fee.currency ?? c.fee_currency].filter((v) => v !== void 0 && v !== null).join(" "));
35048
- field2("Attendees", c.rsvp_count);
35049
- field2("Event page", c.url);
35430
+ field3("When", [c.date_start ?? c.start_time ?? c.date, c.date_end, c.timezone].filter(Boolean).join(" \xB7 "));
35431
+ field3("Location", c.event_type === "online" ? "Online event" : [venue.name ?? c.venue_name, venue.address ?? c.venue_address, venue.city ?? c.venue_city, venue.country ?? c.venue_country, str(c.location)].filter(Boolean).join(", "));
35432
+ field3("Organizer", organizer.name ?? c.organizer_name);
35433
+ field3("Description", c.description);
35434
+ field3("Admission", c.is_paid === false ? "Free" : [fee.amount ?? c.fee_amount, fee.currency ?? c.fee_currency].filter((v) => v !== void 0 && v !== null).join(" "));
35435
+ field3("Attendees", c.rsvp_count);
35436
+ field3("Event page", c.url);
35050
35437
  break;
35051
35438
  }
35052
35439
  default: {
@@ -36090,9 +36477,9 @@ function reverseProxyBlocks(content) {
36090
36477
  return blocks;
36091
36478
  }
36092
36479
  function headerUpAffectsTargetEnv(line3) {
36093
- const field2 = /^header_up\s+(\S+)/i.exec(line3)?.[1]?.replace(/^['"]|['"]$/g, "").replace(/^[+-]/, "").toLowerCase();
36094
- if (!field2) return false;
36095
- const escaped = field2.split("*").map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join(".*");
36480
+ const field3 = /^header_up\s+(\S+)/i.exec(line3)?.[1]?.replace(/^['"]|['"]$/g, "").replace(/^[+-]/, "").toLowerCase();
36481
+ if (!field3) return false;
36482
+ const escaped = field3.split("*").map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join(".*");
36096
36483
  return new RegExp(`^${escaped}$`).test("x-target-env");
36097
36484
  }
36098
36485
  function officialUploadOrigins(content) {
@@ -86597,7 +86984,7 @@ function searchExampleChats(query) {
86597
86984
  if (!normalized) return [];
86598
86985
  return ALL_EXAMPLE_CHATS.map(toListItem).filter((chat) => {
86599
86986
  const fields = [chat.id, chat.slug, chat.title, chat.summary, chat.category];
86600
- return fields.some((field2) => String(field2 ?? "").toLowerCase().includes(normalized));
86987
+ return fields.some((field3) => String(field3 ?? "").toLowerCase().includes(normalized));
86601
86988
  });
86602
86989
  }
86603
86990
  function listExampleChatsForSkill(appId, skillId) {
@@ -89314,7 +89701,7 @@ import { createHash as createHash19, randomBytes as randomBytes9, randomUUID as
89314
89701
 
89315
89702
  // src/tuiForms.ts
89316
89703
  function formValue(form, name) {
89317
- return form.fields.find((field2) => field2.name === name)?.value ?? "";
89704
+ return form.fields.find((field3) => field3.name === name)?.value ?? "";
89318
89705
  }
89319
89706
 
89320
89707
  // src/tuiCarousel.ts
@@ -90026,16 +90413,16 @@ function relationLabel(reference) {
90026
90413
  }
90027
90414
  function buildTaskForm(kind, task) {
90028
90415
  const normalized = kind.startsWith("task-") ? kind : `task-${kind}`;
90029
- const field2 = (name, labelText, value = "", options, multiline = false) => ({ name, label: labelText, value, ...options ? { options } : {}, ...multiline ? { multiline } : {} });
90416
+ const field3 = (name, labelText, value = "", options, multiline = false) => ({ name, label: labelText, value, ...options ? { options } : {}, ...multiline ? { multiline } : {} });
90030
90417
  const fields = normalized === "task-create" ? [
90031
- field2("title", "Title"),
90032
- field2("description", "Description", "", void 0, true),
90033
- field2("status", "Status", "todo", TASK_STATUSES),
90034
- field2("assignee", "Assignee", "user", ["user", "openmates", "codex", "unassigned"])
90418
+ field3("title", "Title"),
90419
+ field3("description", "Description", "", void 0, true),
90420
+ field3("status", "Status", "todo", TASK_STATUSES),
90421
+ field3("assignee", "Assignee", "user", ["user", "openmates", "codex", "unassigned"])
90035
90422
  ] : normalized === "task-edit" ? [
90036
- field2("title", "Title", task?.title),
90037
- field2("description", "Description", task?.description, void 0, true)
90038
- ] : normalized === "task-status" ? [field2("status", "Status", task?.status ?? "todo", TASK_STATUSES)] : normalized === "task-assignee" ? [field2("assignee", "Assignee", task?.assigneeIdentity ?? (task?.assigneeType === "unassigned" ? "unassigned" : "user"), ["user", "openmates", "codex", "unassigned"])] : normalized === "task-block" ? [field2("reason", "Reason code", "needs_user_input", ["needs_user_input", "waiting_for_approval", "missing_credentials", "ambiguous_requirement", "external_dependency", "environment_unavailable", "verification_failed", "other"]), field2("explanation", "Explanation (optional)", "", void 0, true)] : normalized === "task-activity" ? [field2("message", "Activity message", "", void 0, true)] : normalized === "task-delete" ? [field2("confirm", `Type DELETE to delete ${task ? label(task) : "task"}`)] : normalized === "task-reorder" ? [field2("position", "Position", String(task?.position ?? ""))] : normalized === "task-dependency-add" ? [field2("target", "Target task or plan reference")] : normalized === "task-dependency-remove" ? [field2("target", "Target task or plan reference")] : normalized === "task-activity-delete" ? [field2("entry", "Activity entry ID")] : [];
90423
+ field3("title", "Title", task?.title),
90424
+ field3("description", "Description", task?.description, void 0, true)
90425
+ ] : normalized === "task-status" ? [field3("status", "Status", task?.status ?? "todo", TASK_STATUSES)] : normalized === "task-assignee" ? [field3("assignee", "Assignee", task?.assigneeIdentity ?? (task?.assigneeType === "unassigned" ? "unassigned" : "user"), ["user", "openmates", "codex", "unassigned"])] : normalized === "task-block" ? [field3("reason", "Reason code", "needs_user_input", ["needs_user_input", "waiting_for_approval", "missing_credentials", "ambiguous_requirement", "external_dependency", "environment_unavailable", "verification_failed", "other"]), field3("explanation", "Explanation (optional)", "", void 0, true)] : normalized === "task-activity" ? [field3("message", "Activity message", "", void 0, true)] : normalized === "task-delete" ? [field3("confirm", `Type DELETE to delete ${task ? label(task) : "task"}`)] : normalized === "task-reorder" ? [field3("position", "Position", String(task?.position ?? ""))] : normalized === "task-dependency-add" ? [field3("target", "Target task or plan reference")] : normalized === "task-dependency-remove" ? [field3("target", "Target task or plan reference")] : normalized === "task-activity-delete" ? [field3("entry", "Activity entry ID")] : [];
90039
90426
  const titles = {
90040
90427
  "task-create": "Create task",
90041
90428
  "task-edit": "Edit task",
@@ -90176,7 +90563,7 @@ function renderWorkflowCarousel(workflows, width, selectedIndex, focused) {
90176
90563
  truncateCells(clean2(workflow.trigger_summary || "Manual"), 32)
90177
90564
  ].filter(Boolean).join("\n"),
90178
90565
  footer: `${workflow.enabled ? "Enabled" : "Paused"}${workflow.last_run_status ? ` \xB7 Last: ${clean2(workflow.last_run_status)}` : ""}`,
90179
- background: PRIMARY_GRADIENT.start,
90566
+ background: workflowIdentityColor(workflow),
90180
90567
  action: { kind: "command", command: `/workflow ${workflow.id}` }
90181
90568
  }));
90182
90569
  return [
@@ -90587,29 +90974,29 @@ function buildWorkflowNodeForm(workflow, node, capabilities, kind = "workflow-no
90587
90974
  fields: [{ name: "title", label: "Node title", value: node.title ?? "" }, ...fields]
90588
90975
  };
90589
90976
  }
90590
- function parsedField(raw, oldValue, field2) {
90591
- if (field2.required && !raw.trim()) throw new Error("This field is required.");
90592
- if (field2.options?.length && raw && !field2.options.includes(raw)) throw new Error(`Choose ${field2.options.join(", ")}.`);
90593
- if (field2.valueType === "boolean" || typeof oldValue === "boolean") {
90977
+ function parsedField(raw, oldValue, field3) {
90978
+ if (field3.required && !raw.trim()) throw new Error("This field is required.");
90979
+ if (field3.options?.length && raw && !field3.options.includes(raw)) throw new Error(`Choose ${field3.options.join(", ")}.`);
90980
+ if (field3.valueType === "boolean" || typeof oldValue === "boolean") {
90594
90981
  if (raw !== "true" && raw !== "false") throw new Error("Use true or false for boolean fields.");
90595
90982
  return raw === "true";
90596
90983
  }
90597
- if (field2.valueType === "number" || field2.valueType === "integer" || typeof oldValue === "number") {
90984
+ if (field3.valueType === "number" || field3.valueType === "integer" || typeof oldValue === "number") {
90598
90985
  const value = Number(raw);
90599
90986
  if (!raw.trim() || !Number.isFinite(value)) throw new Error("Enter a valid number.");
90600
- if (field2.valueType === "integer" && !Number.isInteger(value)) throw new Error("Enter a whole number.");
90601
- if (field2.minimum !== void 0 && value < field2.minimum) throw new Error(`Enter at least ${field2.minimum}.`);
90602
- if (field2.maximum !== void 0 && value > field2.maximum) throw new Error(`Enter at most ${field2.maximum}.`);
90987
+ if (field3.valueType === "integer" && !Number.isInteger(value)) throw new Error("Enter a whole number.");
90988
+ if (field3.minimum !== void 0 && value < field3.minimum) throw new Error(`Enter at least ${field3.minimum}.`);
90989
+ if (field3.maximum !== void 0 && value > field3.maximum) throw new Error(`Enter at most ${field3.maximum}.`);
90603
90990
  return value;
90604
90991
  }
90605
- if (field2.valueType === "object" || field2.valueType === "array" || oldValue !== null && typeof oldValue === "object") {
90992
+ if (field3.valueType === "object" || field3.valueType === "array" || oldValue !== null && typeof oldValue === "object") {
90606
90993
  const value = JSON.parse(raw);
90607
- if (field2.valueType === "array" && !Array.isArray(value)) throw new Error("Enter a JSON array.");
90608
- if (field2.valueType === "object" && (!value || typeof value !== "object" || Array.isArray(value))) throw new Error("Enter a JSON object.");
90994
+ if (field3.valueType === "array" && !Array.isArray(value)) throw new Error("Enter a JSON array.");
90995
+ if (field3.valueType === "object" && (!value || typeof value !== "object" || Array.isArray(value))) throw new Error("Enter a JSON object.");
90609
90996
  return value;
90610
90997
  }
90611
- if (field2.format === "time" && !/^([01]\d|2[0-3]):[0-5]\d$/.test(raw)) throw new Error("Use HH:MM time.");
90612
- if (field2.format === "date-time" && Number.isNaN(Date.parse(raw))) throw new Error("Enter a valid date and time.");
90998
+ if (field3.format === "time" && !/^([01]\d|2[0-3]):[0-5]\d$/.test(raw)) throw new Error("Use HH:MM time.");
90999
+ if (field3.format === "date-time" && Number.isNaN(Date.parse(raw))) throw new Error("Enter a valid date and time.");
90613
91000
  return raw;
90614
91001
  }
90615
91002
  function setPath(config, path2, value) {
@@ -90640,7 +91027,7 @@ async function submitWorkflowNodeForm(client2, workflow, form) {
90640
91027
  }
90641
91028
  config.schedule = schedule;
90642
91029
  }
90643
- const advanced = form.fields.find((field2) => field2.name === "config.input" && field2.label === "Advanced input JSON");
91030
+ const advanced = form.fields.find((field3) => field3.name === "config.input" && field3.label === "Advanced input JSON");
90644
91031
  if (advanced && advanced.value !== fieldValue(node.config?.input ?? {})) {
90645
91032
  try {
90646
91033
  setPath(config, "input", parsedField(advanced.value, node.config?.input, advanced));
@@ -90648,10 +91035,10 @@ async function submitWorkflowNodeForm(client2, workflow, form) {
90648
91035
  throw new Error(`${advanced.label}: ${error instanceof Error ? error.message : "Invalid value"}`);
90649
91036
  }
90650
91037
  }
90651
- for (const field2 of form.fields) {
90652
- if (!field2.name.startsWith("config.")) continue;
90653
- if (field2 === advanced) continue;
90654
- const path2 = field2.name.slice("config.".length);
91038
+ for (const field3 of form.fields) {
91039
+ if (!field3.name.startsWith("config.")) continue;
91040
+ if (field3 === advanced) continue;
91041
+ const path2 = field3.name.slice("config.".length);
90655
91042
  if (path2.startsWith("schedule.") && ![
90656
91043
  "schedule.type",
90657
91044
  "schedule.timezone",
@@ -90659,12 +91046,12 @@ async function submitWorkflowNodeForm(client2, workflow, form) {
90659
91046
  ].includes(path2)) continue;
90660
91047
  if (checkMode === "ai" && path2.startsWith("predicate.") || checkMode === "exact" && path2 === "question") continue;
90661
91048
  const oldValue = valueAt(node.config ?? {}, path2);
90662
- const required3 = path2.startsWith("schedule.") ? path2 !== "schedule.type" && path2 !== "schedule.timezone" ? true : field2.required : path2 === "question" && checkMode === "ai" || path2.startsWith("predicate.") && checkMode === "exact" ? true : field2.required;
90663
- if (oldValue === void 0 && !field2.value.trim() && !required3) continue;
91049
+ const required3 = path2.startsWith("schedule.") ? path2 !== "schedule.type" && path2 !== "schedule.timezone" ? true : field3.required : path2 === "question" && checkMode === "ai" || path2.startsWith("predicate.") && checkMode === "exact" ? true : field3.required;
91050
+ if (oldValue === void 0 && !field3.value.trim() && !required3) continue;
90664
91051
  try {
90665
- setPath(config, path2, parsedField(field2.value, oldValue, { ...field2, required: required3 }));
91052
+ setPath(config, path2, parsedField(field3.value, oldValue, { ...field3, required: required3 }));
90666
91053
  } catch (error) {
90667
- throw new Error(`${field2.label}: ${error instanceof Error ? error.message : "Invalid value"}`);
91054
+ throw new Error(`${field3.label}: ${error instanceof Error ? error.message : "Invalid value"}`);
90668
91055
  }
90669
91056
  }
90670
91057
  const graph = {
@@ -90674,6 +91061,693 @@ async function submitWorkflowNodeForm(client2, workflow, form) {
90674
91061
  return client2.updateWorkflow(workflow.id, { graph });
90675
91062
  }
90676
91063
 
91064
+ // src/tuiCachedWorkspaces.ts
91065
+ import { createHash as createHash20 } from "crypto";
91066
+
91067
+ // src/tuiWorkspaceCache.ts
91068
+ import { createHmac as createHmac3 } from "crypto";
91069
+ import { chmodSync as chmodSync8, mkdirSync as mkdirSync15, readFileSync as readFileSync16, renameSync as renameSync8, rmSync as rmSync9, statSync as statSync3, writeFileSync as writeFileSync11 } from "fs";
91070
+ import { join as join18 } from "path";
91071
+ import { deserialize, serialize } from "v8";
91072
+ import lockfile4 from "proper-lockfile";
91073
+ var TUI_WORKSPACE_CACHE_FILE = "tui_workspace_cache.json";
91074
+ var VERSION = 1;
91075
+ var MAX_ENTRIES = 32;
91076
+ var MAX_ENTRY_BYTES = 4 * 1024 * 1024;
91077
+ var MAX_PLAINTEXT_BYTES = 16 * 1024 * 1024;
91078
+ var MAX_FILE_BYTES2 = 24 * 1024 * 1024;
91079
+ var KEY_PATTERN = /^[\x21-\x7e]{1,200}$/;
91080
+ function contextSignature(session, stateDir2) {
91081
+ return createHmac3("sha256", Buffer.from(session.masterKeyExportedB64, "base64")).update(JSON.stringify([
91082
+ VERSION,
91083
+ stateDir2,
91084
+ process.env.OPENMATES_PROFILE ?? "",
91085
+ session.apiUrl,
91086
+ session.hashedEmail,
91087
+ session.activeTeamId ?? null
91088
+ ])).digest("hex");
91089
+ }
91090
+ function isRecord4(value) {
91091
+ return !!value && typeof value === "object" && !Array.isArray(value);
91092
+ }
91093
+ function createTuiWorkspaceCache(session, currentSession = loadSession) {
91094
+ const stateDir2 = resolveStateDir();
91095
+ const path2 = join18(stateDir2, TUI_WORKSPACE_CACHE_FILE);
91096
+ const context = contextSignature(session, stateDir2);
91097
+ const loginCreatedAt = session.createdAt;
91098
+ const keyBytes = base64ToBytes(session.masterKeyExportedB64);
91099
+ if (keyBytes.length !== 32) throw new Error("Workspace cache requires a 256-bit master key.");
91100
+ const isCurrent = () => {
91101
+ try {
91102
+ const live = currentSession();
91103
+ const persisted = loadSession();
91104
+ return !!live && !!persisted && live.createdAt === loginCreatedAt && persisted.createdAt === loginCreatedAt && contextSignature(live, stateDir2) === context && contextSignature(persisted, stateDir2) === context;
91105
+ } catch {
91106
+ return false;
91107
+ }
91108
+ };
91109
+ const read = async () => {
91110
+ const empty2 = () => ({ version: VERSION, entries: {} });
91111
+ try {
91112
+ if (statSync3(path2).size > MAX_FILE_BYTES2) return empty2();
91113
+ const disk = readFileSync16(path2, "utf8");
91114
+ if (Buffer.byteLength(disk) > MAX_FILE_BYTES2) return empty2();
91115
+ const envelope = JSON.parse(disk);
91116
+ if (!isRecord4(envelope) || envelope.version !== VERSION || envelope.context !== context || typeof envelope.ciphertext !== "string") return empty2();
91117
+ const plaintext = await decryptWithAesGcmCombined(envelope.ciphertext, keyBytes, context);
91118
+ if (plaintext === null || Buffer.byteLength(plaintext) > MAX_PLAINTEXT_BYTES) return empty2();
91119
+ const parsed = JSON.parse(plaintext);
91120
+ if (!isRecord4(parsed) || parsed.version !== VERSION || !isRecord4(parsed.entries)) return empty2();
91121
+ const entries = {};
91122
+ for (const [key, value] of Object.entries(parsed.entries)) {
91123
+ if (!KEY_PATTERN.test(key) || !isRecord4(value) || typeof value.data !== "string" || typeof value.touchedAt !== "number" || !Number.isFinite(value.touchedAt) || value.touchedAt > Date.now() || value.data.length > MAX_ENTRY_BYTES * 2) continue;
91124
+ entries[key] = { data: value.data, touchedAt: value.touchedAt };
91125
+ }
91126
+ return { version: VERSION, entries };
91127
+ } catch {
91128
+ return empty2();
91129
+ }
91130
+ };
91131
+ const mutate = async (change) => {
91132
+ if (!isCurrent()) return false;
91133
+ mkdirSync15(stateDir2, { recursive: true, mode: 448 });
91134
+ chmodSync8(stateDir2, 448);
91135
+ const release2 = await lockfile4.lock(`${path2}.write`, {
91136
+ realpath: false,
91137
+ stale: 3e4,
91138
+ retries: { retries: 20, factor: 1, minTimeout: 20, maxTimeout: 20 }
91139
+ });
91140
+ try {
91141
+ if (!isCurrent()) return false;
91142
+ const snapshot2 = await read();
91143
+ if (!isCurrent()) return false;
91144
+ change(snapshot2);
91145
+ let plaintext = JSON.stringify(snapshot2);
91146
+ while (Object.keys(snapshot2.entries).length > MAX_ENTRIES || Buffer.byteLength(plaintext) > MAX_PLAINTEXT_BYTES) {
91147
+ const oldest = Object.entries(snapshot2.entries).sort((a, b) => a[1].touchedAt - b[1].touchedAt)[0];
91148
+ if (!oldest) return false;
91149
+ delete snapshot2.entries[oldest[0]];
91150
+ plaintext = JSON.stringify(snapshot2);
91151
+ }
91152
+ const ciphertext = await encryptWithAesGcmCombined(plaintext, keyBytes, context);
91153
+ if (!isCurrent()) return false;
91154
+ const envelope = { version: VERSION, context, ciphertext };
91155
+ const encoded = JSON.stringify(envelope);
91156
+ if (Buffer.byteLength(encoded) > MAX_FILE_BYTES2) return false;
91157
+ const temporary = `${path2}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
91158
+ try {
91159
+ writeFileSync11(temporary, encoded, { mode: 384, flag: "wx" });
91160
+ chmodSync8(temporary, 384);
91161
+ if (!isCurrent()) return false;
91162
+ renameSync8(temporary, path2);
91163
+ } finally {
91164
+ rmSync9(temporary, { force: true });
91165
+ }
91166
+ return true;
91167
+ } finally {
91168
+ await release2();
91169
+ }
91170
+ };
91171
+ return {
91172
+ isCurrent,
91173
+ async get(key) {
91174
+ if (!KEY_PATTERN.test(key)) throw new Error("Invalid workspace cache key.");
91175
+ if (!isCurrent()) return null;
91176
+ const snapshot2 = await read();
91177
+ if (!isCurrent()) return null;
91178
+ const entry = snapshot2.entries[key];
91179
+ if (!entry) return null;
91180
+ try {
91181
+ const bytes2 = Buffer.from(entry.data, "base64");
91182
+ if (bytes2.length > MAX_ENTRY_BYTES) return null;
91183
+ return deserialize(bytes2);
91184
+ } catch {
91185
+ return null;
91186
+ }
91187
+ },
91188
+ async set(key, value) {
91189
+ if (!KEY_PATTERN.test(key)) throw new Error("Invalid workspace cache key.");
91190
+ const bytes2 = serialize(value);
91191
+ if (bytes2.length > MAX_ENTRY_BYTES) return false;
91192
+ return mutate((snapshot2) => {
91193
+ snapshot2.entries[key] = { data: bytes2.toString("base64"), touchedAt: Date.now() };
91194
+ });
91195
+ },
91196
+ async invalidate(key) {
91197
+ if (key !== void 0 && !KEY_PATTERN.test(key)) throw new Error("Invalid workspace cache key.");
91198
+ return mutate((snapshot2) => {
91199
+ if (key === void 0) snapshot2.entries = {};
91200
+ else delete snapshot2.entries[key];
91201
+ });
91202
+ }
91203
+ };
91204
+ }
91205
+
91206
+ // src/tuiCachedWorkspaces.ts
91207
+ var states = /* @__PURE__ */ new WeakMap();
91208
+ function stateFor(client2) {
91209
+ let state = states.get(client2);
91210
+ if (!state) {
91211
+ state = { pending: /* @__PURE__ */ new Map(), generations: /* @__PURE__ */ new Map(), globalGeneration: 0, cacheOperations: Promise.resolve() };
91212
+ states.set(client2, state);
91213
+ }
91214
+ return state;
91215
+ }
91216
+ function queueCacheOperation(state, operation) {
91217
+ const result = state.cacheOperations.then(operation);
91218
+ state.cacheOperations = result.then(() => {
91219
+ }, () => {
91220
+ });
91221
+ return result;
91222
+ }
91223
+ function reportError(onError, error, hasCached) {
91224
+ try {
91225
+ onError?.(error, hasCached);
91226
+ } catch {
91227
+ }
91228
+ }
91229
+ function rawSessionFrom(client2) {
91230
+ if (typeof client2.hasSession !== "function" || typeof client2.getSession !== "function") return null;
91231
+ try {
91232
+ if (!client2.hasSession()) return null;
91233
+ return client2.getSession();
91234
+ } catch {
91235
+ return null;
91236
+ }
91237
+ }
91238
+ function sessionFrom(client2) {
91239
+ const session = rawSessionFrom(client2);
91240
+ if (!session || typeof client2.apiUrl !== "string" || client2.apiUrl.replace(/\/$/, "") !== session.apiUrl.replace(/\/$/, "")) return null;
91241
+ return session;
91242
+ }
91243
+ function identity2(session) {
91244
+ return createHash20("sha256").update(JSON.stringify([
91245
+ session.apiUrl,
91246
+ session.hashedEmail,
91247
+ session.activeTeamId ?? null,
91248
+ session.createdAt,
91249
+ session.masterKeyExportedB64
91250
+ ])).digest("hex");
91251
+ }
91252
+ function contextFor2(client2) {
91253
+ const session = sessionFrom(client2);
91254
+ if (!session) {
91255
+ const raw = rawSessionFrom(client2);
91256
+ if (raw) {
91257
+ const token2 = `${identity2(raw)}:${client2.apiUrl ?? ""}`;
91258
+ return {
91259
+ token: token2,
91260
+ cache: null,
91261
+ isCurrent: () => {
91262
+ const current2 = rawSessionFrom(client2);
91263
+ return !!current2 && `${identity2(current2)}:${client2.apiUrl ?? ""}` === token2;
91264
+ }
91265
+ };
91266
+ }
91267
+ const hasSessionApi = typeof client2.hasSession === "function" && typeof client2.getSession === "function";
91268
+ return { token: "uncached", cache: null, isCurrent: () => !hasSessionApi };
91269
+ }
91270
+ const token = identity2(session);
91271
+ const isCurrent = () => {
91272
+ const current2 = sessionFrom(client2);
91273
+ return !!current2 && identity2(current2) === token;
91274
+ };
91275
+ try {
91276
+ const cache = createTuiWorkspaceCache(session, () => sessionFrom(client2));
91277
+ return { token, cache: cache.isCurrent() ? cache : null, isCurrent };
91278
+ } catch {
91279
+ return { token, cache: null, isCurrent };
91280
+ }
91281
+ }
91282
+ function pendingFor(client2, key, context, fetch2) {
91283
+ const state = stateFor(client2);
91284
+ const generation = state.generations.get(key) ?? 0;
91285
+ const globalGeneration = state.globalGeneration;
91286
+ const token = `${context.token}\0${key}`;
91287
+ const isCurrent = () => context.isCurrent() && (state.generations.get(key) ?? 0) === generation && state.globalGeneration === globalGeneration;
91288
+ const existing = state.pending.get(token);
91289
+ if (existing && existing.generation === generation && existing.globalGeneration === globalGeneration && isCurrent()) {
91290
+ return { promise: existing.promise, isCurrent };
91291
+ }
91292
+ const promise = Promise.resolve().then(fetch2).then(async (value) => {
91293
+ if (isCurrent() && context.cache) {
91294
+ try {
91295
+ await queueCacheOperation(state, () => isCurrent() ? context.cache.set(key, value) : Promise.resolve(false));
91296
+ } catch {
91297
+ }
91298
+ }
91299
+ return value;
91300
+ });
91301
+ const pending = { key, context: context.token, generation, globalGeneration, promise };
91302
+ state.pending.set(token, pending);
91303
+ void promise.then(
91304
+ () => {
91305
+ if (state.pending.get(token) === pending) state.pending.delete(token);
91306
+ },
91307
+ () => {
91308
+ if (state.pending.get(token) === pending) state.pending.delete(token);
91309
+ }
91310
+ );
91311
+ return { promise, isCurrent };
91312
+ }
91313
+ async function loadCachedTuiWorkspace(client2, key, fetch2, publish, onError) {
91314
+ const context = contextFor2(client2);
91315
+ let cached = null;
91316
+ if (context.cache) {
91317
+ try {
91318
+ cached = await context.cache.get(key);
91319
+ } catch {
91320
+ }
91321
+ }
91322
+ if (!context.isCurrent()) return null;
91323
+ if (cached !== null) {
91324
+ try {
91325
+ await publish(cached, "cache");
91326
+ } catch (error) {
91327
+ reportError(onError, error, true);
91328
+ }
91329
+ }
91330
+ const { promise, isCurrent } = pendingFor(client2, key, context, fetch2);
91331
+ const deliver = async () => {
91332
+ try {
91333
+ const value = await promise;
91334
+ if (!isCurrent()) return null;
91335
+ await publish(value, "sync");
91336
+ return value;
91337
+ } catch (error) {
91338
+ reportError(onError, error, cached !== null);
91339
+ return null;
91340
+ }
91341
+ };
91342
+ if (cached !== null) {
91343
+ void deliver();
91344
+ return cached;
91345
+ }
91346
+ return deliver();
91347
+ }
91348
+ async function loadProgressiveCachedTuiWorkspace(client2, key, fetch2, publish, onError, mergePartial = (_previous, page) => page) {
91349
+ const context = contextFor2(client2);
91350
+ const state = stateFor(client2);
91351
+ let cached = null;
91352
+ if (context.cache) {
91353
+ try {
91354
+ cached = await context.cache.get(key);
91355
+ } catch {
91356
+ }
91357
+ }
91358
+ if (!context.isCurrent()) return null;
91359
+ if (cached !== null) {
91360
+ try {
91361
+ await publish(cached, "cache", false);
91362
+ } catch (error) {
91363
+ reportError(onError, error, true);
91364
+ }
91365
+ }
91366
+ const generation = state.generations.get(key) ?? 0;
91367
+ const globalGeneration = state.globalGeneration;
91368
+ const token = `${context.token}\0${key}`;
91369
+ const isCurrent = () => context.isCurrent() && (state.generations.get(key) ?? 0) === generation && state.globalGeneration === globalGeneration;
91370
+ let hasUsable = cached !== null;
91371
+ let finalPublished = false;
91372
+ const listener = async (value, complete) => {
91373
+ if (!isCurrent()) return;
91374
+ hasUsable = true;
91375
+ finalPublished = complete;
91376
+ try {
91377
+ await publish(value, "sync", complete);
91378
+ } catch (error) {
91379
+ reportError(onError, error, true);
91380
+ }
91381
+ };
91382
+ let pending = state.pending.get(token);
91383
+ if (!pending || pending.generation !== generation || pending.globalGeneration !== globalGeneration || !isCurrent()) {
91384
+ const listeners = /* @__PURE__ */ new Set();
91385
+ let latest = cached;
91386
+ const progress = async (page, complete) => {
91387
+ if (!isCurrent()) return;
91388
+ const value = complete ? page : mergePartial(latest, page);
91389
+ latest = value;
91390
+ if (context.cache) {
91391
+ try {
91392
+ await queueCacheOperation(state, () => isCurrent() ? context.cache.set(key, value) : Promise.resolve(false));
91393
+ } catch {
91394
+ }
91395
+ }
91396
+ if (isCurrent()) await Promise.all([...listeners].map((notify) => notify(value, complete)));
91397
+ };
91398
+ const promise = Promise.resolve().then(() => fetch2(progress)).then(async (value) => {
91399
+ if (isCurrent() && context.cache) {
91400
+ try {
91401
+ await queueCacheOperation(state, () => isCurrent() ? context.cache.set(key, value) : Promise.resolve(false));
91402
+ } catch {
91403
+ }
91404
+ }
91405
+ return value;
91406
+ });
91407
+ pending = { key, context: context.token, generation, globalGeneration, promise, progressListeners: listeners };
91408
+ state.pending.set(token, pending);
91409
+ const created = pending;
91410
+ void promise.then(
91411
+ () => {
91412
+ if (state.pending.get(token) === created) state.pending.delete(token);
91413
+ },
91414
+ () => {
91415
+ if (state.pending.get(token) === created) state.pending.delete(token);
91416
+ }
91417
+ );
91418
+ }
91419
+ pending.progressListeners?.add(listener);
91420
+ const deliver = async () => {
91421
+ try {
91422
+ const value = await pending.promise;
91423
+ if (!isCurrent()) return null;
91424
+ if (!finalPublished) await publish(value, "sync", true);
91425
+ return value;
91426
+ } catch (error) {
91427
+ if (isCurrent()) reportError(onError, error, hasUsable);
91428
+ return null;
91429
+ } finally {
91430
+ pending.progressListeners?.delete(listener);
91431
+ }
91432
+ };
91433
+ if (cached !== null) {
91434
+ void deliver();
91435
+ return cached;
91436
+ }
91437
+ return deliver();
91438
+ }
91439
+ function captureTuiWorkspaceOwner(client2) {
91440
+ return contextFor2(client2).isCurrent;
91441
+ }
91442
+ async function readCachedTuiWorkspace(client2, key, ownerCurrent = () => true) {
91443
+ if (!ownerCurrent()) return null;
91444
+ const context = contextFor2(client2);
91445
+ if (!context.cache) return null;
91446
+ try {
91447
+ const value = await context.cache.get(key);
91448
+ return ownerCurrent() && context.isCurrent() ? value : null;
91449
+ } catch {
91450
+ return null;
91451
+ }
91452
+ }
91453
+ async function invalidateCachedTuiWorkspace(client2, key, ownerCurrent = () => true) {
91454
+ if (!ownerCurrent()) return false;
91455
+ const state = stateFor(client2);
91456
+ if (key === void 0) state.globalGeneration++;
91457
+ else state.generations.set(key, (state.generations.get(key) ?? 0) + 1);
91458
+ const context = contextFor2(client2);
91459
+ if (!context.cache) return false;
91460
+ try {
91461
+ return await queueCacheOperation(state, () => ownerCurrent() && context.isCurrent() ? context.cache.invalidate(key) : Promise.resolve(false));
91462
+ } catch {
91463
+ return false;
91464
+ }
91465
+ }
91466
+ async function writeCachedTuiWorkspace(client2, key, value, ownerCurrent = () => true) {
91467
+ if (!ownerCurrent()) return false;
91468
+ const state = stateFor(client2);
91469
+ state.generations.set(key, (state.generations.get(key) ?? 0) + 1);
91470
+ const context = contextFor2(client2);
91471
+ if (!context.cache) return false;
91472
+ try {
91473
+ return await queueCacheOperation(state, () => ownerCurrent() && context.isCurrent() ? context.cache.set(key, value) : Promise.resolve(false));
91474
+ } catch {
91475
+ return false;
91476
+ }
91477
+ }
91478
+
91479
+ // src/tuiTeamsSettings.ts
91480
+ var field = (id, label3, required3 = false) => ({ id, label: label3, kind: "text", required: required3 });
91481
+ var activeTeam = (client2) => {
91482
+ const id = client2.getActiveTeamId();
91483
+ if (!id) throw new Error("Select a Team with Teams \u2192 Select Team first.");
91484
+ return id;
91485
+ };
91486
+ var teamRole = async (client2) => {
91487
+ const teamId = activeTeam(client2), isCurrent = captureTuiWorkspaceOwner(client2);
91488
+ const team = await client2.getTeam(teamId);
91489
+ if (!isCurrent() || client2.getActiveTeamId() !== teamId) throw new Error("Team changed while checking permissions.");
91490
+ if (!team.role) throw new Error("Team role is unavailable.");
91491
+ return team.role;
91492
+ };
91493
+ var requireRole = async (client2, allowed) => {
91494
+ const id = activeTeam(client2), isCurrent = captureTuiWorkspaceOwner(client2);
91495
+ const team = await client2.getTeam(id);
91496
+ if (!isCurrent() || client2.getActiveTeamId() !== id) throw new Error("Team changed while checking permissions.");
91497
+ if (!allowed.includes(team.role)) throw new Error("Your Team role cannot perform this action.");
91498
+ return id;
91499
+ };
91500
+ var displayMember = (member) => ({
91501
+ user_id: member.user_id,
91502
+ display_name: member.profile?.display_name ?? "",
91503
+ role: member.role,
91504
+ status: member.status ?? "",
91505
+ joined_at: member.joined_at ?? null
91506
+ });
91507
+ var TEAM_SETTINGS_PAGES = [
91508
+ {
91509
+ route: "teams",
91510
+ title: "Teams",
91511
+ parent: "main",
91512
+ auth: true,
91513
+ description: "Manage the selected Team or create one.",
91514
+ load: async (c) => ({ active_team_id: c.getActiveTeamId(), teams: await Promise.all((await c.listTeams()).map(async (team) => ({
91515
+ team_id: team.team_id ?? "",
91516
+ name: (await c.getTeamDetails(team.team_id ?? "")).name,
91517
+ role: team.role ?? ""
91518
+ }))) }),
91519
+ rows: (value) => {
91520
+ const list = value?.teams ?? [];
91521
+ return list.filter((team) => team.team_id).map((team) => ({ id: `team:${team.team_id}`, label: `${team.name || team.team_id} \xB7 ${team.role} \xB7 ${team.team_id}`, switchTeam: team.team_id }));
91522
+ }
91523
+ },
91524
+ {
91525
+ route: "teams/select",
91526
+ title: "Select Team",
91527
+ parent: "teams",
91528
+ auth: true,
91529
+ description: "Enter a Team ID shown above. Switching refreshes the Settings owner.",
91530
+ fields: [field("team_id", "Team ID", true)],
91531
+ actions: [{ id: "select", label: "Select Team", requiredFields: ["team_id"], run: async (c, d) => {
91532
+ const isCurrent = captureTuiWorkspaceOwner(c);
91533
+ const team = await c.getTeam(d.team_id.trim());
91534
+ if (!isCurrent()) throw new Error("Workspace changed while selecting Team.");
91535
+ c.setActiveTeamId(team.team_id ?? d.team_id.trim());
91536
+ return { success: true };
91537
+ } }, { id: "personal", label: "Switch to Personal", run: async (c) => {
91538
+ c.setActiveTeamId(null);
91539
+ return { success: true };
91540
+ } }]
91541
+ },
91542
+ {
91543
+ route: "teams/create",
91544
+ title: "Create Team",
91545
+ parent: "teams",
91546
+ auth: true,
91547
+ description: "Team name approval and encryption happen before creation.",
91548
+ fields: [field("name", "Name", true), field("description", "Description")],
91549
+ actions: [{ id: "create", label: "Create Team", run: async (c, d) => {
91550
+ const team = await c.createTeam({ name: d.name.trim(), description: d.description.trim() || void 0 });
91551
+ return { team_id: team.team_id ?? "" };
91552
+ } }]
91553
+ },
91554
+ {
91555
+ route: "teams/details",
91556
+ title: "Team Details",
91557
+ parent: "teams",
91558
+ auth: true,
91559
+ load: async (c) => {
91560
+ const team = await c.getTeamDetails(activeTeam(c));
91561
+ return {
91562
+ team_id: team.team_id ?? "",
91563
+ name: team.name,
91564
+ description: team.description,
91565
+ role: team.role ?? "",
91566
+ status: team.status ?? ""
91567
+ };
91568
+ },
91569
+ fields: [field("name", "Name", true), field("description", "Description")],
91570
+ saveRoles: ["owner", "admin"],
91571
+ defaults: (value) => {
91572
+ const v = value;
91573
+ return { name: v.name ?? "", description: v.description ?? "" };
91574
+ },
91575
+ save: async (c, d) => {
91576
+ await requireRole(c, ["owner", "admin"]);
91577
+ const team = await c.updateTeam(activeTeam(c), { name: d.name.trim(), description: d.description.trim() });
91578
+ return { team_id: team.team_id };
91579
+ }
91580
+ },
91581
+ {
91582
+ route: "teams/members",
91583
+ title: "Members",
91584
+ parent: "teams",
91585
+ auth: true,
91586
+ load: async (c) => ({ role: await teamRole(c), members: (await c.listTeamMembers(activeTeam(c))).map(displayMember) }),
91587
+ rows: (value) => {
91588
+ const list = value?.members ?? [];
91589
+ return list.filter((member) => member.user_id).map((member) => ({
91590
+ id: `member:${member.user_id}`,
91591
+ label: `${member.display_name || member.user_id} \xB7 ${member.role} \xB7 ${member.user_id}`,
91592
+ route: "teams/member",
91593
+ field: "user_id",
91594
+ value: member.user_id,
91595
+ details: { selected_name: member.display_name, selected_role: member.role, selected_user_id: member.user_id }
91596
+ }));
91597
+ }
91598
+ },
91599
+ {
91600
+ route: "teams/member",
91601
+ title: "Member Role",
91602
+ parent: "teams/members",
91603
+ auth: true,
91604
+ description: "Owner and admins can change roles or remove a member.",
91605
+ load: async (c) => ({ role: await teamRole(c) }),
91606
+ requiresLoad: true,
91607
+ fields: [field("user_id", "Member user ID", true), { id: "role", label: "New role", kind: "choice", options: ["member", "viewer", "admin"] }],
91608
+ actions: [
91609
+ { id: "change-role", label: "Change member role", confirm: "Change this member's Team role?", roles: ["owner", "admin"], requiredFields: ["user_id"], run: async (c, d) => {
91610
+ const id = await requireRole(c, ["owner", "admin"]);
91611
+ const member = await c.updateTeamMemberRole(id, d.user_id.trim(), d.role);
91612
+ return displayMember(member);
91613
+ } },
91614
+ { id: "remove-member", label: "Remove member", confirm: "Remove this member from the Team?", roles: ["owner", "admin"], requiredFields: ["user_id"], run: async (c, d) => c.removeTeamMember(await requireRole(c, ["owner", "admin"]), d.user_id.trim()) }
91615
+ ]
91616
+ },
91617
+ {
91618
+ route: "teams/profile",
91619
+ title: "My Team Profile",
91620
+ parent: "teams",
91621
+ auth: true,
91622
+ load: async (c) => {
91623
+ const id = activeTeam(c), isCurrent = captureTuiWorkspaceOwner(c);
91624
+ const identity3 = await c.whoAmI();
91625
+ if (!isCurrent() || c.getActiveTeamId() !== id) throw new Error("Team changed while loading your profile.");
91626
+ const member = await c.getTeamMember(id, String(identity3.id));
91627
+ return { user_id: member.user_id, display_name: member.profile?.display_name ?? "", avatar: member.profile?.avatar ?? "", role: member.role };
91628
+ },
91629
+ fields: [field("display_name", "Display name"), field("avatar", "Avatar")],
91630
+ defaults: (value) => {
91631
+ const v = value;
91632
+ return { display_name: v.display_name ?? "", avatar: v.avatar ?? "" };
91633
+ },
91634
+ save: async (c, d) => displayMember(await c.updateOwnTeamMemberProfile(activeTeam(c), { display_name: d.display_name, avatar: d.avatar || null }))
91635
+ },
91636
+ {
91637
+ route: "teams/invite",
91638
+ title: "Invite Member",
91639
+ parent: "teams",
91640
+ auth: true,
91641
+ teamRoles: ["owner", "admin"],
91642
+ load: async (c) => ({ role: await teamRole(c) }),
91643
+ requiresLoad: true,
91644
+ fields: [{ ...field("email", "Recipient email", true), validate: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) ? null : "Enter a valid email address." }, { id: "role", label: "Role", kind: "choice", options: ["member", "viewer", "admin"] }],
91645
+ actions: [{ id: "invite", label: "Create invite", roles: ["owner", "admin"], requiredFields: ["email"], run: async (c, d) => {
91646
+ const id = await requireRole(c, ["owner", "admin"]);
91647
+ const invite = await c.createTeamInvite(id, { recipient_email: d.email.trim(), role: d.role });
91648
+ return { invite_id: invite.invite_id ?? "", invite_url: invite.invite_url ?? "" };
91649
+ } }]
91650
+ },
91651
+ {
91652
+ route: "teams/access",
91653
+ title: "Access Requests",
91654
+ parent: "teams",
91655
+ auth: true,
91656
+ teamRoles: ["owner", "admin"],
91657
+ load: async (c) => ({ role: await teamRole(c), requests: (await c.listTeamAccessRequests(activeTeam(c))).map((row3) => ({
91658
+ access_request_id: row3.access_request_id ?? "",
91659
+ status: row3.status ?? ""
91660
+ })) }),
91661
+ fields: [field("request_id", "Request ID", true)],
91662
+ actions: [
91663
+ { id: "approve", label: "Approve access", confirm: "Approve this Team access request?", roles: ["owner", "admin"], requiredFields: ["request_id"], run: async (c, d) => {
91664
+ await c.approveTeamAccessRequest(await requireRole(c, ["owner", "admin"]), d.request_id.trim());
91665
+ return { success: true };
91666
+ } },
91667
+ { id: "reject", label: "Reject access", confirm: "Reject this Team access request?", roles: ["owner", "admin"], requiredFields: ["request_id"], run: async (c, d) => c.rejectTeamAccessRequest(await requireRole(c, ["owner", "admin"]), d.request_id.trim()) }
91668
+ ]
91669
+ },
91670
+ {
91671
+ route: "teams/billing",
91672
+ title: "Team Billing",
91673
+ parent: "teams",
91674
+ auth: true,
91675
+ teamRoles: ["owner", "admin"],
91676
+ description: "Billing applies to the selected Team. Purchases and billing settings are in the browser.",
91677
+ load: async (c) => {
91678
+ const id = await requireRole(c, ["owner", "admin"]), billing = await c.getTeamBilling(id);
91679
+ return {
91680
+ team_id: id,
91681
+ balance_credits: billing.balance_credits ?? 0
91682
+ };
91683
+ }
91684
+ },
91685
+ {
91686
+ route: "teams/billing/usage",
91687
+ title: "Team Usage",
91688
+ parent: "teams/billing",
91689
+ auth: true,
91690
+ teamRoles: ["owner", "admin"],
91691
+ description: "Credit usage for the selected Team only.",
91692
+ load: async (c) => ({ usage: (await c.listTeamUsage(await requireRole(c, ["owner", "admin"]))).map((row3) => ({
91693
+ workspace_type: typeof row3.workspace_type === "string" ? row3.workspace_type : "",
91694
+ credit_amount: typeof row3.credit_amount === "number" ? row3.credit_amount : 0,
91695
+ created_at: typeof row3.created_at === "string" || typeof row3.created_at === "number" ? row3.created_at : ""
91696
+ })) })
91697
+ },
91698
+ {
91699
+ route: "teams/billing/manage",
91700
+ title: "Manage Team Billing",
91701
+ parent: "teams/billing",
91702
+ auth: true,
91703
+ teamRoles: ["owner", "admin"],
91704
+ webOnly: "teams",
91705
+ webReason: "Open Teams, select this Team, then Billing to manage purchases, invoices and payment methods."
91706
+ },
91707
+ {
91708
+ route: "teams/security",
91709
+ title: "Team Security",
91710
+ parent: "teams",
91711
+ auth: true,
91712
+ teamRoles: ["owner", "admin"],
91713
+ requiresLoad: true,
91714
+ description: "Owner and admins can edit Team access policy. Team deletion uses the browser verification flow.",
91715
+ load: async (c) => ({ ...await c.getTeamSecurity(await requireRole(c, ["owner", "admin"])), role: await teamRole(c) }),
91716
+ fields: [
91717
+ { id: "restrict_email_domains", label: "Restrict email domains", kind: "boolean" },
91718
+ field("allowed_email_domains", "Allowed domains"),
91719
+ { id: "require_invite_link_approval", label: "Approve invite links", kind: "boolean" },
91720
+ { id: "require_strong_auth", label: "Require strong auth", kind: "boolean" }
91721
+ ],
91722
+ saveRoles: ["owner", "admin"],
91723
+ saveConfirm: "Save Team security policy? Turning restrictions off can broaden Team access. Review each field before confirming.",
91724
+ defaults: (value) => {
91725
+ const policy = value;
91726
+ return {
91727
+ restrict_email_domains: policy.restrict_email_domains ? "on" : "off",
91728
+ allowed_email_domains: (policy.allowed_email_domains ?? []).join(", "),
91729
+ require_invite_link_approval: policy.require_invite_link_approval === false ? "off" : "on",
91730
+ require_strong_auth: policy.require_strong_auth ? "on" : "off"
91731
+ };
91732
+ },
91733
+ save: async (c, d) => c.updateTeamSecurity(await requireRole(c, ["owner", "admin"]), {
91734
+ restrict_email_domains: d.restrict_email_domains === "on",
91735
+ allowed_email_domains: [...new Set(d.allowed_email_domains.split(",").map((v) => v.trim().toLowerCase()).filter(Boolean))],
91736
+ require_invite_link_approval: d.require_invite_link_approval === "on",
91737
+ require_strong_auth: d.require_strong_auth === "on"
91738
+ })
91739
+ },
91740
+ {
91741
+ route: "teams/security/delete",
91742
+ title: "Delete Team",
91743
+ parent: "teams/security",
91744
+ auth: true,
91745
+ teamRoles: ["owner"],
91746
+ webOnly: "teams",
91747
+ webReason: "Open Teams, select this Team, then use its verified deletion flow in the browser."
91748
+ }
91749
+ ];
91750
+
90677
91751
  // src/tuiSettingsCatalog.ts
90678
91752
  var object2 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
90679
91753
  var string2 = (value) => value == null ? "" : String(value);
@@ -90681,7 +91755,7 @@ var bool = (value) => value === true ? "on" : "off";
90681
91755
  var required = (label3) => (value) => value.trim() ? null : `${label3} is required.`;
90682
91756
  var positive = (label3) => (value) => Number.isSafeInteger(Number(value)) && Number(value) > 0 ? null : `${label3} must be a positive whole number.`;
90683
91757
  var email = (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) ? null : "Enter a valid email address.";
90684
- var field = (id, label3, kind = "text", extras = {}) => ({ id, label: label3, kind, ...extras });
91758
+ var field2 = (id, label3, kind = "text", extras = {}) => ({ id, label: label3, kind, ...extras });
90685
91759
  var branch = (route2, title, parent, description) => ({ route: route2, title, parent, description, auth: route2 !== "main" });
90686
91760
  var web = (route2, title, parent, webReason, billing = false) => ({ route: route2, title, parent, auth: true, billing, webOnly: route2, webReason });
90687
91761
  var user = (client2) => client2.whoAmI();
@@ -90691,7 +91765,7 @@ var SETTINGS_PAGES = [
90691
91765
  branch("ai", "AI", "main"),
90692
91766
  branch("privacy", "Privacy", "main"),
90693
91767
  web("projects", "Projects", "main", "Project settings are available in the browser."),
90694
- web("teams", "Teams", "main", "Team settings and roles are available in the browser."),
91768
+ ...TEAM_SETTINGS_PAGES,
90695
91769
  branch("mates", "Mates", "main"),
90696
91770
  branch("billing", "Billing & Usage", "main"),
90697
91771
  branch("notifications", "Notifications", "main"),
@@ -90709,7 +91783,7 @@ var SETTINGS_PAGES = [
90709
91783
  parent: "account",
90710
91784
  auth: true,
90711
91785
  load: user,
90712
- fields: [field("timezone", "Timezone", "text", { required: true, hint: "IANA name, for example Europe/Berlin", validate: (v) => {
91786
+ fields: [field2("timezone", "Timezone", "text", { required: true, hint: "IANA name, for example Europe/Berlin", validate: (v) => {
90713
91787
  try {
90714
91788
  new Intl.DateTimeFormat("en", { timeZone: v });
90715
91789
  return null;
@@ -90726,7 +91800,7 @@ var SETTINGS_PAGES = [
90726
91800
  parent: "account",
90727
91801
  auth: true,
90728
91802
  load: (c) => c.getTopicPreferences(),
90729
- fields: [field("tags", "Interest tags", "text", { required: true, hint: "Comma separated tag IDs" })],
91803
+ fields: [field2("tags", "Interest tags", "text", { required: true, hint: "Comma separated tag IDs" })],
90730
91804
  defaults: (v) => ({ tags: Array.isArray(object2(v).selectedTagIds) ? object2(v).selectedTagIds.join(", ") : "" }),
90731
91805
  save: (c, d) => c.setTopicPreferences(d.tags.split(",").map((v) => v.trim()).filter(Boolean)),
90732
91806
  actions: [{ id: "clear", label: "Clear interests", confirm: "Clear all account interests?", run: (c) => c.clearTopicPreferences() }]
@@ -90737,7 +91811,7 @@ var SETTINGS_PAGES = [
90737
91811
  parent: "account",
90738
91812
  auth: true,
90739
91813
  load: user,
90740
- fields: [field("username", "Username", "text", { required: true, validate: (v) => /^[a-zA-Z0-9_]{3,30}$/.test(v) ? null : "Use 3\u201330 letters, numbers or underscores." })],
91814
+ fields: [field2("username", "Username", "text", { required: true, validate: (v) => /^[a-zA-Z0-9_]{3,30}$/.test(v) ? null : "Use 3\u201330 letters, numbers or underscores." })],
90741
91815
  defaults: (v) => ({ username: string2(object2(v).username) }),
90742
91816
  save: (c, d) => c.updateUsername(d.username)
90743
91817
  },
@@ -90748,7 +91822,7 @@ var SETTINGS_PAGES = [
90748
91822
  title: "Delete Stored File",
90749
91823
  parent: "account/storage",
90750
91824
  auth: true,
90751
- fields: [field("file_id", "File ID", "text", { required: true })],
91825
+ fields: [field2("file_id", "File ID", "text", { required: true })],
90752
91826
  actions: [{ id: "delete", label: "Delete file", confirm: "Delete this stored file permanently?", run: (c, d) => d.file_id.trim() ? c.settingsDelete("storage/files", { scope: "single", file_id: d.file_id.trim() }) : Promise.reject(new Error("Enter a file ID.")) }]
90753
91827
  },
90754
91828
  { route: "account/chats", title: "Chat Statistics", parent: "account", auth: true, load: (c) => c.settingsGet("chats") },
@@ -90759,7 +91833,7 @@ var SETTINGS_PAGES = [
90759
91833
  auth: true,
90760
91834
  load: user,
90761
91835
  description: "Changes the web app language.",
90762
- fields: [field("language", "Language code", "text", { required: true, validate: (v) => /^[a-z]{2}(?:-[A-Z]{2})?$/.test(v) ? null : "Use a language code such as en or de." })],
91836
+ fields: [field2("language", "Language code", "text", { required: true, validate: (v) => /^[a-z]{2}(?:-[A-Z]{2})?$/.test(v) ? null : "Use a language code such as en or de." })],
90763
91837
  defaults: (v) => ({ language: string2(object2(v).language) || "en" }),
90764
91838
  save: (c, d) => c.settingsPost("user/language", { language: d.language })
90765
91839
  },
@@ -90770,7 +91844,7 @@ var SETTINGS_PAGES = [
90770
91844
  auth: true,
90771
91845
  load: user,
90772
91846
  description: "Changes the web app theme.",
90773
- fields: [field("enabled", "Dark mode", "boolean")],
91847
+ fields: [field2("enabled", "Dark mode", "boolean")],
90774
91848
  defaults: (v) => ({ enabled: bool(object2(v).dark_mode) }),
90775
91849
  save: (c, d) => c.settingsPost("user/darkmode", { dark_mode: d.enabled === "on" })
90776
91850
  },
@@ -90781,7 +91855,7 @@ var SETTINGS_PAGES = [
90781
91855
  auth: true,
90782
91856
  load: user,
90783
91857
  description: "Changes the web app font.",
90784
- fields: [field("font", "Font", "choice", { options: ["lexend", "figtree", "rubik", "inter", "public_sans", "atkinson", "ibm_plex_sans", "source_serif", "jetbrains_mono", "ibm_plex_mono", "system", "serif", "mono"] })],
91858
+ fields: [field2("font", "Font", "choice", { options: ["lexend", "figtree", "rubik", "inter", "public_sans", "atkinson", "ibm_plex_sans", "source_serif", "jetbrains_mono", "ibm_plex_mono", "system", "serif", "mono"] })],
90785
91859
  defaults: (v) => ({ font: string2(object2(v).ui_font) || "lexend" }),
90786
91860
  save: (c, d) => c.settingsPost("user/ui-font", { ui_font: d.font })
90787
91861
  },
@@ -90791,7 +91865,7 @@ var SETTINGS_PAGES = [
90791
91865
  parent: "ai",
90792
91866
  auth: true,
90793
91867
  load: user,
90794
- fields: [field("simple", "Simple requests", "text", { required: true, hint: "Model ID or auto" }), field("complex", "Complex requests", "text", { required: true, hint: "Model ID or auto" }), field("most_demanding", "Most demanding", "text", { required: true, hint: "Model ID or auto" })],
91868
+ fields: [field2("simple", "Simple requests", "text", { required: true, hint: "Model ID or auto" }), field2("complex", "Complex requests", "text", { required: true, hint: "Model ID or auto" }), field2("most_demanding", "Most demanding", "text", { required: true, hint: "Model ID or auto" })],
90795
91869
  defaults: (v) => {
90796
91870
  const o = object2(v);
90797
91871
  return { simple: string2(o.default_ai_model_simple) || "auto", complex: string2(o.default_ai_model_complex) || "auto", most_demanding: string2(o.default_ai_model_most_demanding) || "auto" };
@@ -90804,7 +91878,7 @@ var SETTINGS_PAGES = [
90804
91878
  parent: "privacy",
90805
91879
  auth: true,
90806
91880
  load: user,
90807
- fields: [field("period", "Delete after", "choice", { options: ["30d", "60d", "90d", "6m", "1y", "2y", "5y", "never"] })],
91881
+ fields: [field2("period", "Delete after", "choice", { options: ["30d", "60d", "90d", "6m", "1y", "2y", "5y", "never"] })],
90808
91882
  defaults: (v) => {
90809
91883
  const days = object2(v).auto_delete_chats_after_days;
90810
91884
  return { period: days == null ? "never" : { 30: "30d", 60: "60d", 90: "90d", 180: "6m", 365: "1y", 730: "2y", 1825: "5y" }[Number(days)] ?? "90d" };
@@ -90816,7 +91890,7 @@ var SETTINGS_PAGES = [
90816
91890
  title: "Share Debug Logs",
90817
91891
  parent: "privacy",
90818
91892
  auth: true,
90819
- fields: [field("duration", "Sharing duration", "choice", { options: ["1h", "24h", "7d"] })],
91893
+ fields: [field2("duration", "Sharing duration", "choice", { options: ["1h", "24h", "7d"] })],
90820
91894
  actions: [{ id: "share", label: "Share debug logs", confirm: "Share diagnostic logs with support?", run: (c, d) => c.settingsPost("debug-session", { duration: d.duration || "1h" }) }]
90821
91895
  },
90822
91896
  { route: "billing/overview", title: "Overview", parent: "billing", auth: true, billing: true, load: (c) => c.settingsGet("billing") },
@@ -90828,7 +91902,7 @@ var SETTINGS_PAGES = [
90828
91902
  auth: true,
90829
91903
  billing: true,
90830
91904
  load: (c) => c.listInvoices(),
90831
- fields: [field("invoice_id", "Invoice ID for refund", "text")],
91905
+ fields: [field2("invoice_id", "Invoice ID for refund", "text")],
90832
91906
  actions: [{ id: "refund", label: "Request refund", confirm: "Request a refund for this invoice?", run: (c, d) => d.invoice_id.trim() ? c.requestRefund(d.invoice_id.trim()) : Promise.reject(new Error("Enter an invoice ID.")) }]
90833
91907
  },
90834
91908
  {
@@ -90838,7 +91912,7 @@ var SETTINGS_PAGES = [
90838
91912
  auth: true,
90839
91913
  billing: true,
90840
91914
  load: (c) => c.listBankTransferOrders(),
90841
- fields: [field("credits", "Credits to buy", "number", { required: true, validate: positive("Credits") })],
91915
+ fields: [field2("credits", "Credits to buy", "number", { required: true, validate: positive("Credits") })],
90842
91916
  actions: [{ id: "create-order", label: "Create bank transfer order", confirm: "Create a bank transfer order for these credits?", run: (c, d) => c.createBankTransferOrder(Number(d.credits)) }]
90843
91917
  },
90844
91918
  {
@@ -90848,7 +91922,7 @@ var SETTINGS_PAGES = [
90848
91922
  auth: true,
90849
91923
  billing: true,
90850
91924
  load: (c) => c.listRedeemedGiftCards(),
90851
- fields: [field("code", "Gift card code", "text", { required: true, validate: required("Gift card code") })],
91925
+ fields: [field2("code", "Gift card code", "text", { required: true, validate: required("Gift card code") })],
90852
91926
  actions: [{ id: "redeem", label: "Redeem gift card", run: (c, d) => c.redeemGiftCard(d.code) }]
90853
91927
  },
90854
91928
  {
@@ -90858,7 +91932,7 @@ var SETTINGS_PAGES = [
90858
91932
  auth: true,
90859
91933
  billing: true,
90860
91934
  load: (c) => c.listPurchasedGiftCards(),
90861
- fields: [field("credits", "Gift card credits", "number", { required: true, validate: positive("Gift card credits") })],
91935
+ fields: [field2("credits", "Gift card credits", "number", { required: true, validate: positive("Gift card credits") })],
90862
91936
  actions: [{ id: "create-order", label: "Create gift card order", confirm: "Create a gift card bank transfer order?", run: (c, d) => c.createGiftCardBankTransferOrder(Number(d.credits)) }]
90863
91937
  },
90864
91938
  {
@@ -90870,7 +91944,7 @@ var SETTINGS_PAGES = [
90870
91944
  description: "The server fixes the trigger threshold at 100 credits.",
90871
91945
  requiresLoad: true,
90872
91946
  load: user,
90873
- fields: [field("enabled", "Enabled", "boolean"), field("amount", "Credit amount", "number", { required: true, validate: (v) => Number.isSafeInteger(Number(v)) && Number(v) >= 0 ? null : "Credit amount must be a non-negative whole number." }), field("currency", "Currency", "choice", { options: ["eur", "usd"] }), field("email", "Receipt email", "text", { validate: (v) => v ? email(v) : null })],
91947
+ fields: [field2("enabled", "Enabled", "boolean"), field2("amount", "Credit amount", "number", { required: true, validate: (v) => Number.isSafeInteger(Number(v)) && Number(v) >= 0 ? null : "Credit amount must be a non-negative whole number." }), field2("currency", "Currency", "choice", { options: ["eur", "usd"] }), field2("email", "Receipt email", "text", { validate: (v) => v ? email(v) : null })],
90874
91948
  defaults: (v) => {
90875
91949
  const o = object2(v);
90876
91950
  return { enabled: bool(o.auto_topup_low_balance_enabled ?? o.auto_topup_enabled), amount: String(o.auto_topup_low_balance_amount ?? o.auto_topup_amount ?? 0), currency: String(o.auto_topup_low_balance_currency ?? o.auto_topup_currency ?? "eur").toLowerCase(), email: String(o.email ?? "") };
@@ -90885,7 +91959,7 @@ var SETTINGS_PAGES = [
90885
91959
  requiresLoad: true,
90886
91960
  description: "Notifications use your verified account email. Enter it only if you need to confirm the address.",
90887
91961
  load: (c) => c.getEmailNotificationSettings(),
90888
- fields: [field("enabled", "Enabled", "boolean"), field("email", "Verified email (optional)", "text", { validate: (v) => v ? email(v) : null }), field("ai", "AI responses", "boolean"), field("backup", "Backup reminder", "boolean"), field("webhook", "Webhook chats", "boolean")],
91962
+ fields: [field2("enabled", "Enabled", "boolean"), field2("email", "Verified email (optional)", "text", { validate: (v) => v ? email(v) : null }), field2("ai", "AI responses", "boolean"), field2("backup", "Backup reminder", "boolean"), field2("webhook", "Webhook chats", "boolean")],
90889
91963
  defaults: (v) => {
90890
91964
  const o = object2(v), p = object2(o.preferences);
90891
91965
  return { enabled: bool(o.enabled), email: "", ai: bool(p.aiResponses), backup: bool(p.backupReminder), webhook: bool(p.webhookChats) };
@@ -90898,7 +91972,7 @@ var SETTINGS_PAGES = [
90898
91972
  parent: "developers",
90899
91973
  auth: true,
90900
91974
  load: (c) => c.listApiKeys(),
90901
- fields: [field("name", "New key name", "text", { required: true, validate: required("Key name") }), field("key_id", "Key ID to revoke", "text")],
91975
+ fields: [field2("name", "New key name", "text", { required: true, validate: required("Key name") }), field2("key_id", "Key ID to revoke", "text")],
90902
91976
  actions: [
90903
91977
  { id: "create", label: "Create full-access key", confirm: "Create a full-access API key with unlimited credits and no expiration? The key is shown once.", run: (c, d) => c.createApiKey({ name: d.name }) },
90904
91978
  { id: "revoke", label: "Revoke key ID", confirm: "Revoke this API key permanently?", run: (c, d) => d.key_id.trim() ? c.revokeApiKey(d.key_id.trim()) : Promise.reject(new Error("Enter a key ID to revoke.")) }
@@ -90917,7 +91991,7 @@ var SETTINGS_PAGES = [
90917
91991
  parent: "newsletter",
90918
91992
  auth: true,
90919
91993
  load: (c) => c.getNewsletterCategories(),
90920
- fields: [field("updates", "Updates", "boolean"), field("tips", "Tips", "boolean"), field("daily", "Daily inspiration", "boolean")],
91994
+ fields: [field2("updates", "Updates", "boolean"), field2("tips", "Tips", "boolean"), field2("daily", "Daily inspiration", "boolean")],
90921
91995
  defaults: (v) => {
90922
91996
  const o = object2(object2(v).categories ?? v);
90923
91997
  return { updates: bool(o.updates_and_announcements), tips: bool(o.tips_and_tricks), daily: bool(o.daily_inspirations) };
@@ -90928,7 +92002,7 @@ var SETTINGS_PAGES = [
90928
92002
  route: "newsletter/subscribe",
90929
92003
  title: "Subscribe",
90930
92004
  parent: "newsletter",
90931
- fields: [field("email", "Email", "text", { required: true, validate: email }), field("language", "Language", "text", { required: true, validate: (v) => /^[a-z]{2}$/.test(v) ? null : "Use a two-letter language code." }), field("darkmode", "Dark newsletter", "boolean")],
92005
+ fields: [field2("email", "Email", "text", { required: true, validate: email }), field2("language", "Language", "text", { required: true, validate: (v) => /^[a-z]{2}$/.test(v) ? null : "Use a two-letter language code." }), field2("darkmode", "Dark newsletter", "boolean")],
90932
92006
  save: (c, d) => c.subscribeNewsletter(d.email, d.language, d.darkmode === "on")
90933
92007
  },
90934
92008
  {
@@ -90937,7 +92011,7 @@ var SETTINGS_PAGES = [
90937
92011
  parent: "settings_memories",
90938
92012
  auth: true,
90939
92013
  load: (c) => c.listMemories({ personal: true }),
90940
- fields: [field("memory_id", "Memory ID to delete", "text")],
92014
+ fields: [field2("memory_id", "Memory ID to delete", "text")],
90941
92015
  actions: [{ id: "delete", label: "Delete memory", confirm: "Delete this memory permanently?", run: (c, d) => d.memory_id.trim() ? c.deleteMemory(d.memory_id.trim(), { personal: true }) : Promise.reject(new Error("Enter a memory ID.")) }]
90942
92016
  },
90943
92017
  web("account/email", "Email", "account", "Changing email requires browser identity verification."),
@@ -90959,7 +92033,7 @@ var SETTINGS_PAGES = [
90959
92033
  ];
90960
92034
  var settingsPage = (route2) => SETTINGS_PAGES.find((page) => page.route === route2);
90961
92035
  function settingsChildren(route2, options) {
90962
- return SETTINGS_PAGES.filter((page) => page.parent === route2 && (!page.auth || options.authenticated) && (!page.billing || options.paymentEnabled) && (!page.admin || options.isAdmin === true) && (!options.restricted || !["account", "teams", "settings_memories", "billing"].includes(page.route.split("/")[0])) && (page.route !== "pricing" || !options.authenticated) && (page.route !== "projects" || options.features?.has("projects") !== false) && (page.route !== "teams" || options.features?.has("teams") !== false));
92036
+ return SETTINGS_PAGES.filter((page) => page.parent === route2 && (!page.auth || options.authenticated) && (!page.billing || options.paymentEnabled) && (!page.admin || options.isAdmin === true) && (!options.restricted || !["account", "settings_memories", "billing"].includes(page.route.split("/")[0])) && (page.route !== "pricing" || !options.authenticated) && (page.route !== "projects" || options.features?.has("projects") !== false) && (!page.route.startsWith("teams") || options.features?.has("teams") !== false) && (!page.teamRoles || page.teamRoles.includes(options.teamRole ?? "")));
90963
92037
  }
90964
92038
 
90965
92039
  // src/tuiSettings.ts
@@ -90994,6 +92068,7 @@ function createTuiSettingsState(owner, options = {}) {
90994
92068
  authenticated: options.authenticated ?? true,
90995
92069
  restricted: options.restricted ?? false,
90996
92070
  isAdmin: false,
92071
+ teamRole: "",
90997
92072
  paymentEnabled: options.paymentEnabled ?? false,
90998
92073
  paymentChecked: false,
90999
92074
  features: options.features,
@@ -91056,10 +92131,11 @@ function syncTuiSettingsOwner(ctx) {
91056
92131
  function available(page, state) {
91057
92132
  if (page.auth && !state.authenticated) return false;
91058
92133
  if (page.admin && !state.isAdmin) return false;
91059
- if (state.restricted && ["account", "teams", "settings_memories", "billing"].includes(page.route.split("/")[0])) return false;
92134
+ if (page.teamRoles && !page.teamRoles.includes(state.teamRole)) return false;
92135
+ if (state.restricted && ["account", "settings_memories", "billing"].includes(page.route.split("/")[0])) return false;
91060
92136
  if (page.billing && !state.paymentEnabled) return false;
91061
92137
  if (page.route === "projects" && state.features?.has("projects") === false) return false;
91062
- if (page.route === "teams" && state.features?.has("teams") === false) return false;
92138
+ if (page.route.startsWith("teams") && state.features?.has("teams") === false) return false;
91063
92139
  return true;
91064
92140
  }
91065
92141
  function nearestRoute(route2, state) {
@@ -91083,8 +92159,16 @@ async function loadPage(ctx, page) {
91083
92159
  ctx.render();
91084
92160
  try {
91085
92161
  const value = await page.load(ctx.client);
91086
- if (!ownerValid(ctx, owner) || state.generation !== generation) return;
92162
+ if (!ownerValid(ctx, owner)) {
92163
+ ctx.render();
92164
+ return;
92165
+ }
92166
+ if (state.generation !== generation) return;
91087
92167
  state.data[page.route] = value;
92168
+ if (page.route === "teams") {
92169
+ const teams = asObject(value).teams;
92170
+ state.teamRole = Array.isArray(teams) ? String(asObject(teams.find((team) => asObject(team).team_id === asObject(value).active_team_id)).role ?? "") : "";
92171
+ }
91088
92172
  if (page.defaults) {
91089
92173
  const defaults = page.defaults(value);
91090
92174
  const changed = state.dirtyFields[page.route];
@@ -91126,7 +92210,7 @@ async function openTuiSettingsPage(state, route2, ctx) {
91126
92210
  state.lastResult[next] = void 0;
91127
92211
  state.generation++;
91128
92212
  const page = settingsPage(next);
91129
- if (page.fields && !state.drafts[next]) state.drafts[next] = Object.fromEntries(page.fields.map((field2) => [field2.id, field2.kind === "boolean" ? "off" : field2.options?.[0] ?? ""]));
92213
+ if (page.fields && !state.drafts[next]) state.drafts[next] = Object.fromEntries(page.fields.map((field3) => [field3.id, field3.kind === "boolean" ? "off" : field3.options?.[0] ?? ""]));
91130
92214
  if (next === "newsletter/subscribe" && !state.dirty[next] && !state.drafts[next]?.language) state.drafts[next].language = "en";
91131
92215
  ctx.render();
91132
92216
  if (next === "main" && state.authenticated) {
@@ -91157,20 +92241,36 @@ async function loadCapabilities(ctx) {
91157
92241
  } catch {
91158
92242
  }
91159
92243
  }
91160
- function validateField(field2, value) {
91161
- if (field2.required && !value.trim()) return `${field2.label} is required.`;
91162
- if (field2.kind === "number" && (!Number.isFinite(Number(value)) || !Number.isSafeInteger(Number(value)))) return `${field2.label} must be a whole number.`;
91163
- if (field2.kind === "choice" && field2.options && !field2.options.includes(value)) return `Choose a listed ${field2.label.toLowerCase()}.`;
91164
- if (field2.kind === "boolean" && value !== "on" && value !== "off") return `${field2.label} must be on or off.`;
91165
- return field2.validate?.(value) ?? null;
92244
+ function validateField(field3, value) {
92245
+ if (field3.required && !value.trim()) return `${field3.label} is required.`;
92246
+ if (field3.kind === "number" && (!Number.isFinite(Number(value)) || !Number.isSafeInteger(Number(value)))) return `${field3.label} must be a whole number.`;
92247
+ if (field3.kind === "choice" && field3.options && !field3.options.includes(value)) return `Choose a listed ${field3.label.toLowerCase()}.`;
92248
+ if (field3.kind === "boolean" && value !== "on" && value !== "off") return `${field3.label} must be on or off.`;
92249
+ return field3.validate?.(value) ?? null;
92250
+ }
92251
+ function visibleActions(page, state) {
92252
+ const loaded = asObject(state.data[page.route]);
92253
+ return (page.actions ?? []).filter((action2) => (!action2.authenticatedOnly || state.authenticated) && (!action2.roles || action2.roles.includes(String(loaded.role ?? ""))));
92254
+ }
92255
+ function visibleSave(page, state) {
92256
+ return Boolean(page.save) && (!page.saveRoles || page.saveRoles.includes(String(asObject(state.data[page.route]).role ?? "")));
92257
+ }
92258
+ function editableFields(page, state) {
92259
+ return page.saveRoles && !visibleSave(page, state) ? [] : page.fields ?? [];
91166
92260
  }
91167
92261
  function validateTuiSettingsDraft(page, draft) {
91168
- for (const field2 of page.fields ?? []) {
91169
- const error = validateField(field2, draft[field2.id] ?? "");
92262
+ for (const field3 of page.fields ?? []) {
92263
+ const error = validateField(field3, draft[field3.id] ?? "");
91170
92264
  if (error) return error;
91171
92265
  }
91172
92266
  if (page.route === "billing/auto-topup/low-balance" && draft.enabled === "on" && !draft.email) return "Email is required when auto top-up is enabled.";
91173
92267
  if (page.route === "billing/auto-topup/low-balance" && draft.enabled === "on" && Number(draft.amount) <= 0) return "Credit amount must be positive when auto top-up is enabled.";
92268
+ if (page.route === "teams/security") {
92269
+ const domains = draft.allowed_email_domains?.split(",").map((value) => value.trim().toLowerCase()).filter(Boolean) ?? [];
92270
+ if (draft.restrict_email_domains === "on" && !domains.length) return "Add at least one allowed email domain before enabling restrictions.";
92271
+ if (domains.length > 50) return "Use at most 50 allowed email domains.";
92272
+ if (domains.some((domain) => !/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+$/.test(domain))) return "Enter valid comma-separated email domains.";
92273
+ }
91174
92274
  return null;
91175
92275
  }
91176
92276
  async function runMutation(ctx, actionId) {
@@ -91183,19 +92283,20 @@ async function runMutation(ctx, actionId) {
91183
92283
  ctx.render();
91184
92284
  return;
91185
92285
  }
91186
- const action2 = page.actions?.find((item) => item.id === actionId);
92286
+ const action2 = visibleActions(page, state).find((item) => item.id === actionId);
92287
+ if (actionId === "save" && !visibleSave(page, state)) return;
91187
92288
  if (actionId !== "save" && !action2 || action2?.authenticatedOnly && !state.authenticated) return;
91188
92289
  const draft = state.drafts[page.route] ?? {};
91189
92290
  const requiredForAction = { create: "name", redeem: "code", revoke: "key_id", delete: page.route === "settings_memories/list" ? "memory_id" : "file_id", refund: "invoice_id", "create-order": "credits" };
91190
92291
  const fieldId = requiredForAction[actionId];
91191
- const field2 = fieldId ? page.fields?.find((item) => item.id === fieldId) : void 0;
91192
- const validation = actionId === "save" ? validateTuiSettingsDraft(page, draft) : field2 ? validateField({ ...field2, required: true }, draft[field2.id] ?? "") : null;
92292
+ const field3 = fieldId ? page.fields?.find((item) => item.id === fieldId) : void 0;
92293
+ const validation = actionId === "save" ? validateTuiSettingsDraft(page, draft) : action2?.requiredFields?.map((id) => page.fields?.find((candidate) => candidate.id === id)).filter((item) => Boolean(item)).map((item) => validateField({ ...item, required: true }, draft[item.id] ?? "")).find(Boolean) ?? (field3 ? validateField({ ...field3, required: true }, draft[field3.id] ?? "") : null);
91193
92294
  if (validation) {
91194
92295
  state.error = validation;
91195
92296
  ctx.render();
91196
92297
  return;
91197
92298
  }
91198
- if (action2?.confirm && state.confirmation !== actionId) {
92299
+ if ((action2?.confirm || actionId === "save" && page.saveConfirm) && state.confirmation !== actionId) {
91199
92300
  state.confirmation = actionId;
91200
92301
  state.confirmationChoice = "confirm";
91201
92302
  state.error = null;
@@ -91210,7 +92311,7 @@ async function runMutation(ctx, actionId) {
91210
92311
  ctx.render();
91211
92312
  try {
91212
92313
  const result = await (action2 ? action2.run(ctx.client, { ...draft }) : page.save(ctx.client, { ...draft }));
91213
- if (actionId === "logout") {
92314
+ if (actionId === "logout" || page.route === "teams/select" && (actionId === "select" || actionId === "personal")) {
91214
92315
  ctx.render();
91215
92316
  return;
91216
92317
  }
@@ -91242,9 +92343,10 @@ function actionIds(state) {
91242
92343
  const page = settingsPage(state.route);
91243
92344
  return [
91244
92345
  ...settingsChildren(state.route, state).map((child) => `route:${child.route}`),
91245
- ...(page.fields ?? []).map((field2) => `field:${field2.id}`),
91246
- ...page.save ? ["save"] : [],
91247
- ...(page.actions ?? []).filter((action2) => !action2.authenticatedOnly || state.authenticated).map((action2) => `action:${action2.id}`),
92346
+ ...(page.rows?.(state.data[page.route]) ?? []).map((row3) => `row:${row3.id}`),
92347
+ ...editableFields(page, state).map((field3) => `field:${field3.id}`),
92348
+ ...visibleSave(page, state) ? ["save"] : [],
92349
+ ...visibleActions(page, state).map((action2) => `action:${action2.id}`),
91248
92350
  ...state.oneTimeSecret ? ["reveal-secret"] : [],
91249
92351
  ...page.webOnly ? ["web"] : []
91250
92352
  ];
@@ -91294,17 +92396,52 @@ async function handleTuiSettingsCommand(ctx, arg) {
91294
92396
  await openTuiSettingsPage(state, arg.slice(6), ctx);
91295
92397
  return true;
91296
92398
  }
92399
+ if (arg.startsWith("row:")) {
92400
+ const page = settingsPage(state.route);
92401
+ const row3 = page?.rows?.(state.data[page.route]).find((candidate) => candidate.id === arg.slice(4));
92402
+ if (!row3) return true;
92403
+ if (row3.switchTeam) {
92404
+ if (ctx.client.getActiveTeamId() === row3.switchTeam) await openTuiSettingsPage(state, "teams/details", ctx);
92405
+ else {
92406
+ const owner = currentOwner(ctx), generation = ++state.generation;
92407
+ state.busy = true;
92408
+ state.error = null;
92409
+ ctx.render();
92410
+ try {
92411
+ const team = await ctx.client.getTeam(row3.switchTeam);
92412
+ if (!ownerValid(ctx, owner) || state.generation !== generation) return true;
92413
+ ctx.client.setActiveTeamId(team.team_id ?? row3.switchTeam);
92414
+ ctx.render();
92415
+ } catch (error) {
92416
+ if (ownerValid(ctx, owner) && state.generation === generation) state.error = safeText2(error instanceof Error ? error.message : error);
92417
+ } finally {
92418
+ if (ownerValid(ctx, owner) && state.generation === generation) {
92419
+ state.busy = false;
92420
+ ctx.render();
92421
+ }
92422
+ }
92423
+ }
92424
+ return true;
92425
+ }
92426
+ if (row3.route) {
92427
+ await openTuiSettingsPage(state, row3.route, ctx);
92428
+ if (row3.field && row3.value !== void 0) (state.drafts[row3.route] ??= {})[row3.field] = row3.value;
92429
+ if (row3.details) state.data[row3.route] = { ...asObject(state.data[row3.route]), ...row3.details };
92430
+ ctx.render();
92431
+ }
92432
+ return true;
92433
+ }
91297
92434
  if (arg.startsWith("field:")) {
91298
92435
  const id = arg.slice(6), page = settingsPage(state.route);
91299
- const field2 = page.fields?.find((item) => item.id === id);
91300
- if (!field2) return true;
91301
- if (state.loading && (field2.kind === "boolean" || field2.kind === "choice")) return true;
92436
+ const field3 = editableFields(page, state).find((item) => item.id === id);
92437
+ if (!field3) return true;
92438
+ if (state.loading && (field3.kind === "boolean" || field3.kind === "choice")) return true;
91302
92439
  const draft = state.drafts[page.route] ??= {};
91303
- if (field2.kind === "boolean") {
92440
+ if (field3.kind === "boolean") {
91304
92441
  draft[id] = draft[id] === "on" ? "off" : "on";
91305
92442
  markDraftFieldChanged(state, id);
91306
- } else if (field2.kind === "choice") {
91307
- const choices = field2.options ?? [];
92443
+ } else if (field3.kind === "choice") {
92444
+ const choices = field3.options ?? [];
91308
92445
  draft[id] = choices[(choices.indexOf(draft[id] ?? "") + 1) % choices.length] ?? "";
91309
92446
  markDraftFieldChanged(state, id);
91310
92447
  } else state.editing = id;
@@ -91360,8 +92497,8 @@ async function handleTuiSettingsKey(ctx, chunk, key) {
91360
92497
  if (key.ctrl && name === "u") {
91361
92498
  const selected = actionIds(state)[state.selection] ?? "";
91362
92499
  const id = state.editing ?? (selected.startsWith("field:") ? selected.slice(6) : "");
91363
- const field2 = settingsPage(state.route)?.fields?.find((item) => item.id === id);
91364
- if (field2 && (field2.kind === "text" || field2.kind === "number")) {
92500
+ const field3 = settingsPage(state.route)?.fields?.find((item) => item.id === id);
92501
+ if (field3 && (field3.kind === "text" || field3.kind === "number")) {
91365
92502
  const draft = state.drafts[state.route] ??= {};
91366
92503
  draft[id] = "";
91367
92504
  markDraftFieldChanged(state, id);
@@ -91433,7 +92570,11 @@ var summaryFields = {
91433
92570
  "account/storage": [["used_bytes", "Used bytes"], ["total_bytes", "Total bytes"], ["file_count", "Files"]],
91434
92571
  "account/chats": [["chat_count", "Chats"], ["message_count", "Messages"]],
91435
92572
  "billing/overview": [["credits", "Credits"], ["balance", "Balance"], ["currency", "Currency"]],
91436
- "billing/usage": [["total_credits", "Credits used"], ["total_cost", "Total cost"], ["currency", "Currency"]]
92573
+ "billing/usage": [["total_credits", "Credits used"], ["total_cost", "Total cost"], ["currency", "Currency"]],
92574
+ "teams/details": [["team_id", "Team ID"], ["name", "Name"], ["description", "Description"], ["role", "Your role"]],
92575
+ "teams/profile": [["user_id", "User ID"], ["display_name", "Display name"], ["role", "Role"]],
92576
+ "teams/member": [["selected_name", "Name"], ["selected_user_id", "User ID"], ["selected_role", "Role"]],
92577
+ "teams/billing": [["team_id", "Team ID"], ["balance_credits", "Team credits"]]
91437
92578
  };
91438
92579
  var listFields = {
91439
92580
  "account/storage/files": { key: "files", fields: [["filename", "File"], ["id", "File ID"], ["size", "Bytes"]] },
@@ -91442,12 +92583,18 @@ var listFields = {
91442
92583
  "billing/gift-cards": { key: "gift_cards", fields: [["id", "Gift card ID"], ["credits", "Credits"], ["status", "Status"]] },
91443
92584
  "billing/gift-cards/bank-transfer": { key: "orders", fields: [["id", "Order ID"], ["status", "Status"], ["credits", "Credits"]] },
91444
92585
  "developers/api-keys": { key: "api_keys", fields: [["name", "Name"], ["id", "Key ID"], ["full_access", "Full access"], ["expires_at", "Expires"]] },
91445
- "settings_memories/list": { key: "memories", fields: [["title", "Title"], ["id", "Memory ID"]] }
92586
+ "settings_memories/list": { key: "memories", fields: [["title", "Title"], ["id", "Memory ID"]] },
92587
+ "teams": { key: "teams", fields: [["name", "Team"], ["team_id", "Team ID"], ["role", "Your role"]] },
92588
+ "teams/members": { key: "members", fields: [["display_name", "Name"], ["user_id", "User ID"], ["role", "Role"], ["status", "Status"]] },
92589
+ "teams/access": { key: "requests", fields: [["access_request_id", "Request ID"], ["status", "Status"]] },
92590
+ "teams/billing/usage": { key: "usage", fields: [["workspace_type", "Workspace"], ["credit_amount", "Credits"], ["created_at", "Time"]] }
91446
92591
  };
91447
92592
  var resultFields = {
91448
92593
  "billing/bank-transfer": [["order_id", "Order ID"], ["payment_reference", "Reference"], ["iban", "IBAN"], ["recipient", "Recipient"], ["amount", "Amount"], ["currency", "Currency"]],
91449
92594
  "billing/gift-cards/bank-transfer": [["order_id", "Order ID"], ["payment_reference", "Reference"], ["iban", "IBAN"], ["recipient", "Recipient"], ["amount", "Amount"], ["currency", "Currency"]],
91450
- "billing/gift-cards": [["credits", "Credits redeemed"]]
92595
+ "billing/gift-cards": [["credits", "Credits redeemed"]],
92596
+ "teams/create": [["team_id", "Team ID"]],
92597
+ "teams/invite": [["invite_id", "Invite ID"], ["invite_url", "Invite link"]]
91451
92598
  };
91452
92599
  function scalarValue(value) {
91453
92600
  if (typeof value === "boolean") return value ? "Yes" : "No";
@@ -91463,6 +92610,7 @@ function allowedRows(value, fields, width) {
91463
92610
  }
91464
92611
  function settingsDataLines(page, value, width, result = false) {
91465
92612
  if (result) return allowedRows(value, resultFields[page.route] ?? [], width);
92613
+ if (page.rows) return [];
91466
92614
  if (page.save && page.defaults && page.fields) {
91467
92615
  const defaults = page.defaults(value);
91468
92616
  return allowedRows(defaults, page.fields.map(({ id, label: label3 }) => [id, label3]), width);
@@ -91493,50 +92641,59 @@ function renderTuiSettings(state, width) {
91493
92641
  if (state.ownerStale) return [line("Settings"), line("Account or team changed. Reopen Settings."), line("Close Settings", "close")];
91494
92642
  if (state.confirmation) {
91495
92643
  lines.push(
91496
- ...wrapCells(page.actions?.find((action2) => action2.id === state.confirmation)?.confirm ?? "Confirm operation?", width),
92644
+ ...wrapWords((state.confirmation === "save" ? page.saveConfirm : page.actions?.find((action2) => action2.id === state.confirmation)?.confirm) ?? "Confirm operation?", width),
91497
92645
  "",
91498
92646
  line(state.confirmationChoice === "confirm" ? "\u203A Confirm" : " Confirm", "confirm", state.confirmationChoice === "confirm"),
91499
92647
  line(state.confirmationChoice === "cancel" ? "\u203A Cancel" : " Cancel", "cancel", state.confirmationChoice === "cancel"),
91500
92648
  "",
91501
- ...wrapCells("Tab choose \xB7 Enter select \xB7 Esc cancel", width)
92649
+ ...wrapWords("Tab choose \xB7 Enter select \xB7 Esc cancel", width)
91502
92650
  );
91503
92651
  return lines;
91504
92652
  }
91505
92653
  if (state.route === "main") {
91506
92654
  const profile = state.authenticated ? [state.profile.username || "Signed in", state.profile.team || "Personal", state.restricted ? "Paired session" : "", state.profile.email].filter(Boolean).join(" \xB7 ") : "Signed out";
91507
- lines.push(...wrapCells(profile, width), "");
92655
+ const profileLines = wrapWords(profile, width);
92656
+ lines.push(profileLines[0] ?? "", profileLines.length > 2 ? truncateCells(profileLines.slice(1).join(" "), width) : profileLines[1] ?? "", "");
91508
92657
  }
91509
- if (page.description) lines.push(...wrapCells(page.description, width), "");
91510
- if (state.message) lines.push(...wrapCells(`\u2713 ${state.message}`, width), "");
91511
- if (state.error) lines.push(...wrapCells(`Error: ${state.error}`, width), "");
92658
+ if (page.description) lines.push(...wrapWords(page.description, width), "");
92659
+ if (state.message) lines.push(...wrapWords(`\u2713 ${state.message}`, width), "");
92660
+ if (state.error) lines.push(...wrapWords(`Error: ${state.error}`, width), "");
91512
92661
  if (state.busy) lines.push("Working\u2026", "");
91513
92662
  const ids = actionIds(state);
91514
92663
  for (const child of settingsChildren(state.route, state)) {
91515
92664
  const id = `route:${child.route}`, index = ids.indexOf(id);
91516
92665
  lines.push(line(truncateCells(`${index + 1}. ${state.selection === index ? "\u203A " : " "}${child.title}${child.webOnly ? " [Web]" : ""}`, width), id, state.selection === index));
91517
92666
  }
92667
+ const rows2 = page.rows?.(state.data[page.route]) ?? [];
92668
+ if (rows2.length) {
92669
+ lines.push("", "Select an item:");
92670
+ for (const row3 of rows2) {
92671
+ const id = `row:${row3.id}`, index = ids.indexOf(id);
92672
+ lines.push(line(truncateCells(`${index + 1}. ${state.selection === index ? "\u203A " : " "}${safeText2(row3.label)}`, width), id, state.selection === index));
92673
+ }
92674
+ }
91518
92675
  const draft = state.drafts[page.route] ?? {};
91519
- if (page.fields?.length) {
92676
+ if (editableFields(page, state).length) {
91520
92677
  lines.push("", "Edit fields, then Save:");
91521
- for (const field2 of page.fields) {
91522
- const id = `field:${field2.id}`, index = ids.indexOf(id), value = draft[field2.id] ?? "";
91523
- const pendingChoice = state.loading && (field2.kind === "boolean" || field2.kind === "choice");
92678
+ for (const field3 of editableFields(page, state)) {
92679
+ const id = `field:${field3.id}`, index = ids.indexOf(id), value = draft[field3.id] ?? "";
92680
+ const pendingChoice = state.loading && (field3.kind === "boolean" || field3.kind === "choice");
91524
92681
  const visibleValue = pendingChoice && state.data[page.route] === void 0 ? "Loading\u2026" : value || "\u2014";
91525
- const suffix = pendingChoice ? "" : field2.kind === "boolean" || field2.kind === "choice" ? " \u21BB" : state.editing === field2.id ? " [edit]" : "";
91526
- lines.push(line(truncateCells(`${index + 1}. ${state.selection === index ? "\u203A " : " "}${field2.label}: ${visibleValue}${suffix}`, width), pendingChoice ? void 0 : id, state.selection === index));
91527
- if (field2.hint && state.editing === field2.id) lines.push(...wrapCells(` ${field2.hint}`, width));
92682
+ const suffix = pendingChoice ? "" : field3.kind === "boolean" || field3.kind === "choice" ? " \u21BB" : state.editing === field3.id ? " [edit]" : "";
92683
+ lines.push(line(truncateCells(`${index + 1}. ${state.selection === index ? "\u203A " : " "}${field3.label}: ${visibleValue}${suffix}`, width), pendingChoice ? void 0 : id, state.selection === index));
92684
+ if (field3.hint && state.editing === field3.id) lines.push(...wrapWords(` ${field3.hint}`, width));
91528
92685
  }
91529
92686
  }
91530
- if (page.save) {
92687
+ if (visibleSave(page, state)) {
91531
92688
  const index = ids.indexOf("save");
91532
92689
  lines.push("", line(`${index + 1}. Save${state.dirty[page.route] ? " changes" : ""}`, "save", state.selection === index));
91533
92690
  }
91534
- for (const action2 of (page.actions ?? []).filter((action3) => !action3.authenticatedOnly || state.authenticated)) {
92691
+ for (const action2 of visibleActions(page, state)) {
91535
92692
  const id = `action:${action2.id}`, index = ids.indexOf(id);
91536
92693
  lines.push(line(`${index + 1}. ${action2.label}`, id, state.selection === index));
91537
92694
  }
91538
92695
  if (page.webOnly) {
91539
- lines.push("", ...wrapCells(page.webReason ?? "This action requires the browser.", width));
92696
+ lines.push("", ...wrapWords(page.webReason ?? "This action requires the browser.", width));
91540
92697
  const index = ids.indexOf("web");
91541
92698
  lines.push(line(`${index + 1}. Open web destination`, "web", state.selection === index), ...wrapCells(settingsWebDestination(state.webUrl, page.webOnly), width));
91542
92699
  }
@@ -91553,7 +92710,7 @@ function renderTuiSettings(state, width) {
91553
92710
  lines.push("", line(`${index + 1}. ${state.secretRevealed ? "Hide" : "Reveal"} new API key`, "reveal-secret", state.selection === index));
91554
92711
  if (state.secretRevealed) lines.push("Shown once; store securely:", ...wrapCells(state.oneTimeSecret, width));
91555
92712
  }
91556
- lines.push("", ...wrapCells("\u2191/\u2193 or Tab select \xB7 Enter open/edit \xB7 Ctrl+S save \xB7 Ctrl+U clear field \xB7 PgUp/PgDn scroll \xB7 Esc back/close", width));
92713
+ lines.push("", ...wrapWords("\u2191/\u2193 or Tab select \xB7 Enter open/edit \xB7 Ctrl+S save \xB7 Ctrl+U clear field \xB7 PgUp/PgDn scroll \xB7 Esc back/close", width));
91557
92714
  return lines;
91558
92715
  }
91559
92716
 
@@ -91858,332 +93015,6 @@ function renderTuiModelSelector(state, width) {
91858
93015
 
91859
93016
  // src/tuiModelSelectorShell.ts
91860
93017
  import { createHash as createHash21 } from "crypto";
91861
-
91862
- // src/tuiCachedWorkspaces.ts
91863
- import { createHash as createHash20 } from "crypto";
91864
-
91865
- // src/tuiWorkspaceCache.ts
91866
- import { createHmac as createHmac3 } from "crypto";
91867
- import { chmodSync as chmodSync8, mkdirSync as mkdirSync15, readFileSync as readFileSync16, renameSync as renameSync8, rmSync as rmSync9, statSync as statSync3, writeFileSync as writeFileSync11 } from "fs";
91868
- import { join as join18 } from "path";
91869
- import { deserialize, serialize } from "v8";
91870
- import lockfile4 from "proper-lockfile";
91871
- var TUI_WORKSPACE_CACHE_FILE = "tui_workspace_cache.json";
91872
- var VERSION = 1;
91873
- var MAX_ENTRIES = 32;
91874
- var MAX_ENTRY_BYTES = 4 * 1024 * 1024;
91875
- var MAX_PLAINTEXT_BYTES = 16 * 1024 * 1024;
91876
- var MAX_FILE_BYTES2 = 24 * 1024 * 1024;
91877
- var KEY_PATTERN = /^[\x21-\x7e]{1,200}$/;
91878
- function contextSignature(session, stateDir2) {
91879
- return createHmac3("sha256", Buffer.from(session.masterKeyExportedB64, "base64")).update(JSON.stringify([
91880
- VERSION,
91881
- stateDir2,
91882
- process.env.OPENMATES_PROFILE ?? "",
91883
- session.apiUrl,
91884
- session.hashedEmail,
91885
- session.activeTeamId ?? null
91886
- ])).digest("hex");
91887
- }
91888
- function isRecord4(value) {
91889
- return !!value && typeof value === "object" && !Array.isArray(value);
91890
- }
91891
- function createTuiWorkspaceCache(session, currentSession = loadSession) {
91892
- const stateDir2 = resolveStateDir();
91893
- const path2 = join18(stateDir2, TUI_WORKSPACE_CACHE_FILE);
91894
- const context = contextSignature(session, stateDir2);
91895
- const loginCreatedAt = session.createdAt;
91896
- const keyBytes = base64ToBytes(session.masterKeyExportedB64);
91897
- if (keyBytes.length !== 32) throw new Error("Workspace cache requires a 256-bit master key.");
91898
- const isCurrent = () => {
91899
- try {
91900
- const live = currentSession();
91901
- const persisted = loadSession();
91902
- return !!live && !!persisted && live.createdAt === loginCreatedAt && persisted.createdAt === loginCreatedAt && contextSignature(live, stateDir2) === context && contextSignature(persisted, stateDir2) === context;
91903
- } catch {
91904
- return false;
91905
- }
91906
- };
91907
- const read = async () => {
91908
- const empty2 = () => ({ version: VERSION, entries: {} });
91909
- try {
91910
- if (statSync3(path2).size > MAX_FILE_BYTES2) return empty2();
91911
- const disk = readFileSync16(path2, "utf8");
91912
- if (Buffer.byteLength(disk) > MAX_FILE_BYTES2) return empty2();
91913
- const envelope = JSON.parse(disk);
91914
- if (!isRecord4(envelope) || envelope.version !== VERSION || envelope.context !== context || typeof envelope.ciphertext !== "string") return empty2();
91915
- const plaintext = await decryptWithAesGcmCombined(envelope.ciphertext, keyBytes, context);
91916
- if (plaintext === null || Buffer.byteLength(plaintext) > MAX_PLAINTEXT_BYTES) return empty2();
91917
- const parsed = JSON.parse(plaintext);
91918
- if (!isRecord4(parsed) || parsed.version !== VERSION || !isRecord4(parsed.entries)) return empty2();
91919
- const entries = {};
91920
- for (const [key, value] of Object.entries(parsed.entries)) {
91921
- if (!KEY_PATTERN.test(key) || !isRecord4(value) || typeof value.data !== "string" || typeof value.touchedAt !== "number" || !Number.isFinite(value.touchedAt) || value.touchedAt > Date.now() || value.data.length > MAX_ENTRY_BYTES * 2) continue;
91922
- entries[key] = { data: value.data, touchedAt: value.touchedAt };
91923
- }
91924
- return { version: VERSION, entries };
91925
- } catch {
91926
- return empty2();
91927
- }
91928
- };
91929
- const mutate = async (change) => {
91930
- if (!isCurrent()) return false;
91931
- mkdirSync15(stateDir2, { recursive: true, mode: 448 });
91932
- chmodSync8(stateDir2, 448);
91933
- const release2 = await lockfile4.lock(`${path2}.write`, {
91934
- realpath: false,
91935
- stale: 3e4,
91936
- retries: { retries: 20, factor: 1, minTimeout: 20, maxTimeout: 20 }
91937
- });
91938
- try {
91939
- if (!isCurrent()) return false;
91940
- const snapshot2 = await read();
91941
- if (!isCurrent()) return false;
91942
- change(snapshot2);
91943
- let plaintext = JSON.stringify(snapshot2);
91944
- while (Object.keys(snapshot2.entries).length > MAX_ENTRIES || Buffer.byteLength(plaintext) > MAX_PLAINTEXT_BYTES) {
91945
- const oldest = Object.entries(snapshot2.entries).sort((a, b) => a[1].touchedAt - b[1].touchedAt)[0];
91946
- if (!oldest) return false;
91947
- delete snapshot2.entries[oldest[0]];
91948
- plaintext = JSON.stringify(snapshot2);
91949
- }
91950
- const ciphertext = await encryptWithAesGcmCombined(plaintext, keyBytes, context);
91951
- if (!isCurrent()) return false;
91952
- const envelope = { version: VERSION, context, ciphertext };
91953
- const encoded = JSON.stringify(envelope);
91954
- if (Buffer.byteLength(encoded) > MAX_FILE_BYTES2) return false;
91955
- const temporary = `${path2}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
91956
- try {
91957
- writeFileSync11(temporary, encoded, { mode: 384, flag: "wx" });
91958
- chmodSync8(temporary, 384);
91959
- if (!isCurrent()) return false;
91960
- renameSync8(temporary, path2);
91961
- } finally {
91962
- rmSync9(temporary, { force: true });
91963
- }
91964
- return true;
91965
- } finally {
91966
- await release2();
91967
- }
91968
- };
91969
- return {
91970
- isCurrent,
91971
- async get(key) {
91972
- if (!KEY_PATTERN.test(key)) throw new Error("Invalid workspace cache key.");
91973
- if (!isCurrent()) return null;
91974
- const snapshot2 = await read();
91975
- if (!isCurrent()) return null;
91976
- const entry = snapshot2.entries[key];
91977
- if (!entry) return null;
91978
- try {
91979
- const bytes2 = Buffer.from(entry.data, "base64");
91980
- if (bytes2.length > MAX_ENTRY_BYTES) return null;
91981
- return deserialize(bytes2);
91982
- } catch {
91983
- return null;
91984
- }
91985
- },
91986
- async set(key, value) {
91987
- if (!KEY_PATTERN.test(key)) throw new Error("Invalid workspace cache key.");
91988
- const bytes2 = serialize(value);
91989
- if (bytes2.length > MAX_ENTRY_BYTES) return false;
91990
- return mutate((snapshot2) => {
91991
- snapshot2.entries[key] = { data: bytes2.toString("base64"), touchedAt: Date.now() };
91992
- });
91993
- },
91994
- async invalidate(key) {
91995
- if (key !== void 0 && !KEY_PATTERN.test(key)) throw new Error("Invalid workspace cache key.");
91996
- return mutate((snapshot2) => {
91997
- if (key === void 0) snapshot2.entries = {};
91998
- else delete snapshot2.entries[key];
91999
- });
92000
- }
92001
- };
92002
- }
92003
-
92004
- // src/tuiCachedWorkspaces.ts
92005
- var states = /* @__PURE__ */ new WeakMap();
92006
- function stateFor(client2) {
92007
- let state = states.get(client2);
92008
- if (!state) {
92009
- state = { pending: /* @__PURE__ */ new Map(), generations: /* @__PURE__ */ new Map(), globalGeneration: 0, cacheOperations: Promise.resolve() };
92010
- states.set(client2, state);
92011
- }
92012
- return state;
92013
- }
92014
- function queueCacheOperation(state, operation) {
92015
- const result = state.cacheOperations.then(operation);
92016
- state.cacheOperations = result.then(() => {
92017
- }, () => {
92018
- });
92019
- return result;
92020
- }
92021
- function reportError(onError, error, hasCached) {
92022
- try {
92023
- onError?.(error, hasCached);
92024
- } catch {
92025
- }
92026
- }
92027
- function rawSessionFrom(client2) {
92028
- if (typeof client2.hasSession !== "function" || typeof client2.getSession !== "function") return null;
92029
- try {
92030
- if (!client2.hasSession()) return null;
92031
- return client2.getSession();
92032
- } catch {
92033
- return null;
92034
- }
92035
- }
92036
- function sessionFrom(client2) {
92037
- const session = rawSessionFrom(client2);
92038
- if (!session || typeof client2.apiUrl !== "string" || client2.apiUrl.replace(/\/$/, "") !== session.apiUrl.replace(/\/$/, "")) return null;
92039
- return session;
92040
- }
92041
- function identity2(session) {
92042
- return createHash20("sha256").update(JSON.stringify([
92043
- session.apiUrl,
92044
- session.hashedEmail,
92045
- session.activeTeamId ?? null,
92046
- session.createdAt,
92047
- session.masterKeyExportedB64
92048
- ])).digest("hex");
92049
- }
92050
- function contextFor2(client2) {
92051
- const session = sessionFrom(client2);
92052
- if (!session) {
92053
- const raw = rawSessionFrom(client2);
92054
- if (raw) {
92055
- const token2 = `${identity2(raw)}:${client2.apiUrl ?? ""}`;
92056
- return {
92057
- token: token2,
92058
- cache: null,
92059
- isCurrent: () => {
92060
- const current2 = rawSessionFrom(client2);
92061
- return !!current2 && `${identity2(current2)}:${client2.apiUrl ?? ""}` === token2;
92062
- }
92063
- };
92064
- }
92065
- const hasSessionApi = typeof client2.hasSession === "function" && typeof client2.getSession === "function";
92066
- return { token: "uncached", cache: null, isCurrent: () => !hasSessionApi };
92067
- }
92068
- const token = identity2(session);
92069
- const isCurrent = () => {
92070
- const current2 = sessionFrom(client2);
92071
- return !!current2 && identity2(current2) === token;
92072
- };
92073
- try {
92074
- const cache = createTuiWorkspaceCache(session, () => sessionFrom(client2));
92075
- return { token, cache: cache.isCurrent() ? cache : null, isCurrent };
92076
- } catch {
92077
- return { token, cache: null, isCurrent };
92078
- }
92079
- }
92080
- function pendingFor(client2, key, context, fetch2) {
92081
- const state = stateFor(client2);
92082
- const generation = state.generations.get(key) ?? 0;
92083
- const globalGeneration = state.globalGeneration;
92084
- const token = `${context.token}\0${key}`;
92085
- const isCurrent = () => context.isCurrent() && (state.generations.get(key) ?? 0) === generation && state.globalGeneration === globalGeneration;
92086
- const existing = state.pending.get(token);
92087
- if (existing && existing.generation === generation && existing.globalGeneration === globalGeneration && isCurrent()) {
92088
- return { promise: existing.promise, isCurrent };
92089
- }
92090
- const promise = Promise.resolve().then(fetch2).then(async (value) => {
92091
- if (isCurrent() && context.cache) {
92092
- try {
92093
- await queueCacheOperation(state, () => isCurrent() ? context.cache.set(key, value) : Promise.resolve(false));
92094
- } catch {
92095
- }
92096
- }
92097
- return value;
92098
- });
92099
- const pending = { key, context: context.token, generation, globalGeneration, promise };
92100
- state.pending.set(token, pending);
92101
- void promise.then(
92102
- () => {
92103
- if (state.pending.get(token) === pending) state.pending.delete(token);
92104
- },
92105
- () => {
92106
- if (state.pending.get(token) === pending) state.pending.delete(token);
92107
- }
92108
- );
92109
- return { promise, isCurrent };
92110
- }
92111
- async function loadCachedTuiWorkspace(client2, key, fetch2, publish, onError) {
92112
- const context = contextFor2(client2);
92113
- let cached = null;
92114
- if (context.cache) {
92115
- try {
92116
- cached = await context.cache.get(key);
92117
- } catch {
92118
- }
92119
- }
92120
- if (!context.isCurrent()) return null;
92121
- if (cached !== null) {
92122
- try {
92123
- await publish(cached, "cache");
92124
- } catch (error) {
92125
- reportError(onError, error, true);
92126
- }
92127
- }
92128
- const { promise, isCurrent } = pendingFor(client2, key, context, fetch2);
92129
- const deliver = async () => {
92130
- try {
92131
- const value = await promise;
92132
- if (!isCurrent()) return null;
92133
- await publish(value, "sync");
92134
- return value;
92135
- } catch (error) {
92136
- reportError(onError, error, cached !== null);
92137
- return null;
92138
- }
92139
- };
92140
- if (cached !== null) {
92141
- void deliver();
92142
- return cached;
92143
- }
92144
- return deliver();
92145
- }
92146
- function captureTuiWorkspaceOwner(client2) {
92147
- return contextFor2(client2).isCurrent;
92148
- }
92149
- async function readCachedTuiWorkspace(client2, key, ownerCurrent = () => true) {
92150
- if (!ownerCurrent()) return null;
92151
- const context = contextFor2(client2);
92152
- if (!context.cache) return null;
92153
- try {
92154
- const value = await context.cache.get(key);
92155
- return ownerCurrent() && context.isCurrent() ? value : null;
92156
- } catch {
92157
- return null;
92158
- }
92159
- }
92160
- async function invalidateCachedTuiWorkspace(client2, key, ownerCurrent = () => true) {
92161
- if (!ownerCurrent()) return false;
92162
- const state = stateFor(client2);
92163
- if (key === void 0) state.globalGeneration++;
92164
- else state.generations.set(key, (state.generations.get(key) ?? 0) + 1);
92165
- const context = contextFor2(client2);
92166
- if (!context.cache) return false;
92167
- try {
92168
- return await queueCacheOperation(state, () => ownerCurrent() && context.isCurrent() ? context.cache.invalidate(key) : Promise.resolve(false));
92169
- } catch {
92170
- return false;
92171
- }
92172
- }
92173
- async function writeCachedTuiWorkspace(client2, key, value, ownerCurrent = () => true) {
92174
- if (!ownerCurrent()) return false;
92175
- const state = stateFor(client2);
92176
- state.generations.set(key, (state.generations.get(key) ?? 0) + 1);
92177
- const context = contextFor2(client2);
92178
- if (!context.cache) return false;
92179
- try {
92180
- return await queueCacheOperation(state, () => ownerCurrent() && context.isCurrent() ? context.cache.set(key, value) : Promise.resolve(false));
92181
- } catch {
92182
- return false;
92183
- }
92184
- }
92185
-
92186
- // src/tuiModelSelectorShell.ts
92187
93018
  function isTuiAiComposer(state) {
92188
93019
  return state.workspace === "chats" && ["start", "chats", "chat", "example"].includes(state.screen);
92189
93020
  }
@@ -92504,6 +93335,29 @@ function createTuiShareQr(url, origin, target) {
92504
93335
  return { url, lines, width, height: lines.length };
92505
93336
  }
92506
93337
 
93338
+ // src/tuiTaskList.ts
93339
+ function mergePartialTuiTasks(previous, page) {
93340
+ if (!previous?.length) return page;
93341
+ const freshIds = new Set(page.map((task) => task.taskId));
93342
+ return [...page, ...previous.filter((task) => !freshIds.has(task.taskId))];
93343
+ }
93344
+ function loadTuiTaskList(client2, key, filters, publish, onError) {
93345
+ return loadProgressiveCachedTuiWorkspace(client2, key, async (progress) => {
93346
+ const masterKey = client2.getMasterKeyBytes();
93347
+ let decrypted = [];
93348
+ let received = 0;
93349
+ const records = await client2.listUserTasks(filters, {
93350
+ onPage: async (cumulative, complete) => {
93351
+ const next = await decryptUserTasks(cumulative.slice(received), masterKey);
93352
+ received = cumulative.length;
93353
+ decrypted = [...decrypted, ...next];
93354
+ await progress(decrypted, complete);
93355
+ }
93356
+ });
93357
+ return received === records.length ? decrypted : decryptUserTasks(records, masterKey);
93358
+ }, publish, onError, mergePartialTuiTasks);
93359
+ }
93360
+
92507
93361
  // src/messageSegments.ts
92508
93362
  function parseMessageSegments(content, options = {}) {
92509
93363
  const segments2 = [];
@@ -93460,17 +94314,36 @@ async function loadTuiChatSettings(context, dialog) {
93460
94314
  dialog.items = [];
93461
94315
  dialog.error = void 0;
93462
94316
  context.render();
94317
+ if (tab === "tasks") {
94318
+ const current2 = () => dialog.ownerCurrent() && context.state.chrome === dialog && dialog.tab === tab && dialog.request === request && context.state.activeChatId === dialog.chatId;
94319
+ await loadTuiTaskList(context.client, `chat:${dialog.chatId}:tasks`, { chatId: dialog.chatId }, (tasks, source, complete) => {
94320
+ if (!current2()) return;
94321
+ const tabs = visibleChatSettingsTabs(context.state), selectedCommand = dialog.field >= tabs.length ? chatSettingsActions(dialog, context.state)[dialog.field - tabs.length]?.command : void 0;
94322
+ dialog.tasks = tasks;
94323
+ dialog.loading = !complete;
94324
+ dialog.error = complete ? void 0 : source === "cache" ? "Showing saved tasks. Syncing\u2026" : `Loading more tasks\u2026 ${tasks.length} available.`;
94325
+ if (selectedCommand) {
94326
+ const index = chatSettingsActions(dialog, context.state).findIndex((action2) => action2.command === selectedCommand);
94327
+ if (index >= 0) dialog.field = tabs.length + index;
94328
+ }
94329
+ context.render();
94330
+ }, (_error, hasUsable) => {
94331
+ if (!current2()) return;
94332
+ dialog.loading = false;
94333
+ dialog.error = hasUsable ? "Showing saved or partially synced tasks. Refresh failed." : "Could not load tasks. Retry when connected.";
94334
+ context.render();
94335
+ });
94336
+ return;
94337
+ }
93463
94338
  try {
93464
94339
  const key = tab === "usage" ? null : context.client.getMasterKeyBytes();
93465
94340
  const plans = tab === "plan" ? await decryptUserPlans(await context.client.listUserPlans({ chatId: dialog.chatId }), key) : null;
93466
- const tasks = tab === "tasks" ? await decryptUserTasks(await context.client.listUserTasks({ chatId: dialog.chatId }), key) : null;
93467
94341
  const usage = tab === "usage" ? await Promise.all([
93468
94342
  context.client.settingsGet(`usage/chat-entries?${new URLSearchParams({ chat_id: dialog.chatId, limit: "500" })}`),
93469
94343
  context.client.settingsGet(`usage/chat-total?${new URLSearchParams({ chat_id: dialog.chatId })}`)
93470
94344
  ]) : null;
93471
94345
  if (!dialog.ownerCurrent() || context.state.chrome !== dialog || dialog.tab !== tab || dialog.request !== request || context.state.activeChatId !== dialog.chatId) return;
93472
94346
  if (plans) dialog.items = plans.map((plan) => `${plan.title} \xB7 ${plan.status}${plan.goal ? ` \xB7 ${plan.goal}` : ""}`);
93473
- if (tasks) dialog.tasks = tasks;
93474
94347
  if (usage) {
93475
94348
  const [entries, total] = usage;
93476
94349
  if (!Array.isArray(entries?.entries) || !entries.entries.every((item) => item && typeof item === "object")) throw Error("Invalid usage rows.");
@@ -93504,11 +94377,14 @@ async function submitTuiChatTask(context, dialog, taskId) {
93504
94377
  dialog.busy = true;
93505
94378
  dialog.error = void 0;
93506
94379
  context.render();
94380
+ let phase = "prepare";
93507
94381
  try {
93508
94382
  const key = context.client.getMasterKeyBytes();
93509
94383
  const input = task ? await buildUpdateUserTaskInput(task, key, { status: task.status === "done" ? "todo" : "done" }) : await buildCreateUserTaskInput(key, { title, chatId: dialog.chatId, assign: "user" });
93510
94384
  if (!dialog.ownerCurrent() || context.state.chrome !== dialog || context.state.activeChatId !== dialog.chatId) return;
94385
+ phase = "save";
93511
94386
  const record10 = task ? await context.client.updateUserTask(task.taskId, input) : await context.client.createUserTask(input);
94387
+ phase = "decrypt";
93512
94388
  const updated = await decryptUserTask(record10, key);
93513
94389
  if (!dialog.ownerCurrent() || context.state.chrome !== dialog || context.state.activeChatId !== dialog.chatId) return;
93514
94390
  dialog.tasks = [updated, ...dialog.tasks.filter((item) => item.taskId !== updated.taskId)];
@@ -93516,8 +94392,10 @@ async function submitTuiChatTask(context, dialog, taskId) {
93516
94392
  dialog.editingTask = false;
93517
94393
  dialog.taskTitle = "";
93518
94394
  dialog.field = visibleChatSettingsTabs(context.state).length;
93519
- } catch {
93520
- if (dialog.ownerCurrent() && context.state.chrome === dialog) dialog.error = task ? "Could not update task. Retry." : "Could not create task. Review the title and retry.";
94395
+ } catch (error) {
94396
+ const status = error && typeof error === "object" && "status" in error ? error.status : void 0;
94397
+ const detail = typeof status === "number" && Number.isInteger(status) && status >= 100 && status <= 599 ? `${phase}: HTTP ${status}` : phase;
94398
+ if (dialog.ownerCurrent() && context.state.chrome === dialog) dialog.error = task ? `Could not update task (${detail}). Retry.` : `Could not create task (${detail}). Review the title and retry.`;
93521
94399
  } finally {
93522
94400
  dialog.busy = false;
93523
94401
  if (dialog.ownerCurrent() && context.state.chrome === dialog) context.render();
@@ -93535,7 +94413,7 @@ function chatSettingsActions(dialog, state) {
93535
94413
  return [{ label: `Open ${alias}`, command: `/header-action settings-file ${alias}` }, ...downloadable ? [{ label: `Download ${alias}`, command: `/header-action settings-file-download ${alias}` }] : []];
93536
94414
  }), { label: "Copy web settings link", command: "/header-action settings-web" }];
93537
94415
  }
93538
- if (dialog.tab === "tasks") return dialog.loading ? [] : dialog.editingTask ? [{ label: "Save task", command: "/header-action settings-task-save" }, { label: "Cancel task", command: "/header-action settings-task-cancel" }] : [{ label: "Create task", command: "/header-action settings-task-create" }, ...dialog.tasks.slice(0, 20).flatMap((task) => [
94416
+ if (dialog.tab === "tasks") return dialog.loading && !dialog.tasks.length ? [] : dialog.editingTask ? [{ label: "Save task", command: "/header-action settings-task-save" }, { label: "Cancel task", command: "/header-action settings-task-cancel" }] : [{ label: "Create task", command: "/header-action settings-task-create" }, ...dialog.tasks.slice(0, 20).flatMap((task) => [
93539
94417
  { label: `Open task ${task.title}`, command: `/header-action settings-task-open ${task.taskId}` },
93540
94418
  ...task.readOnly ? [] : [{ label: `${task.status === "done" ? "Undo done" : "Mark done"} \xB7 ${task.title}`, command: `/header-action settings-task-toggle ${task.taskId}` }]
93541
94419
  ]), ...dialog.error ? [{ label: "Retry", command: "/header-action settings-retry" }] : []];
@@ -93554,7 +94432,7 @@ function renderTuiChatSettings(dialog, state, width) {
93554
94432
  else if (!dialog.items.length) lines.push(row("No plans linked to this chat."));
93555
94433
  else for (const item of dialog.items.slice(0, 20)) lines.push(...wrapCells(`\u2022 ${item}`, max).map((line3) => row(line3)));
93556
94434
  } else if (dialog.tab === "tasks") {
93557
- if (dialog.loading) lines.push(row("Loading tasks\u2026"));
94435
+ if (dialog.loading) lines.push(row(dialog.error ?? "Loading tasks\u2026"));
93558
94436
  else if (dialog.error && !dialog.editingTask) lines.push(row(dialog.error));
93559
94437
  else lines.push(row(`${dialog.tasks.filter((task) => task.status === "done").length}/${dialog.tasks.length} tasks done`));
93560
94438
  if (dialog.editingTask) lines.push(row(`Task title: ${truncateCells(dialog.taskTitle, max - 14)}_`, "/header-action settings-task-focus"));
@@ -93589,7 +94467,7 @@ var DURATIONS = [0, 60, 3600, 86400, 604800, 1209600, 2592e3];
93589
94467
  var DURATION_LABELS = ["Never", "1 minute", "1 hour", "24 hours", "7 days", "14 days", "30 days"];
93590
94468
  var action = (command) => ({ kind: "command", command });
93591
94469
  var row2 = (text7, command) => ({ text: text7, ...command ? { action: action(command) } : {} });
93592
- function wrapWords(text7, width) {
94470
+ function wrapWords2(text7, width) {
93593
94471
  const lines = [];
93594
94472
  let line3 = "";
93595
94473
  for (const word of text7.split(/\s+/)) {
@@ -94180,7 +95058,7 @@ function renderHeaderDialog(dialog, width, height = 24, state) {
94180
95058
  return [row2("Share QR code"), row2(""), ...dialog.qr.width <= available2 && neededRows <= height ? dialog.qr.lines.map((line3) => {
94181
95059
  const padding = " ".repeat(Math.floor((width - dialog.qr.width) / 2));
94182
95060
  return { text: padding + line3, spans: [{ text: padding }, { text: line3, color: "#ffffff", background: "#000000" }] };
94183
- }) : wrapWords(`QR needs ${dialog.qr.width + 2} columns and ${neededRows} rows. Resize terminal or copy the link.`, Math.max(1, width - 2)).map((line3) => row2(line3)), row2("Back to share", "/header-action qr-back"), row2("Copy Link", "/header-action copy-link")];
95061
+ }) : wrapWords2(`QR needs ${dialog.qr.width + 2} columns and ${neededRows} rows. Resize terminal or copy the link.`, Math.max(1, width - 2)).map((line3) => row2(line3)), row2("Back to share", "/header-action qr-back"), row2("Copy Link", "/header-action copy-link")];
94184
95062
  }
94185
95063
  if (dialog.kind === "copy") return dialog.ownerCurrent && !dialog.ownerCurrent() ? [row2("This content is no longer available.")] : [row2(dialog.title), ...wrapCells(dialog.text, max).map((line3) => row2(line3)), row2("Select text above if clipboard delivery was not confirmed."), row2("Copy again", "/header-action copy"), row2("Close", "/header-action close")];
94186
95064
  if (dialog.kind === "share") {
@@ -94232,7 +95110,7 @@ function initialAnswer(question) {
94232
95110
  case "choice":
94233
95111
  return { selection: [] };
94234
95112
  case "input":
94235
- return { inputs: Object.fromEntries((question.fields ?? []).map((field2) => [field2.id, ""])) };
95113
+ return { inputs: Object.fromEntries((question.fields ?? []).map((field3) => [field3.id, ""])) };
94236
95114
  case "slider":
94237
95115
  return { value: question.default ?? Math.round(((question.min ?? 0) + (question.max ?? 100)) / 2) };
94238
95116
  case "swipe":
@@ -94250,7 +95128,7 @@ function controls(editor) {
94250
95128
  ...a.selection.some((id) => isCustomChoiceOption(q, id)) ? [{ id: "custom", label: q.custom_placeholder ?? "Your own answer", value: String(a.custom_answer ?? ""), text: true }] : []
94251
95129
  ];
94252
95130
  case "input":
94253
- return (q.fields ?? []).map((field2) => ({ id: field2.id, label: field2.label + (field2.required ? " \xB7 required" : ""), value: String(a.inputs[field2.id] ?? ""), text: true }));
95131
+ return (q.fields ?? []).map((field3) => ({ id: field3.id, label: field3.label + (field3.required ? " \xB7 required" : ""), value: String(a.inputs[field3.id] ?? ""), text: true }));
94254
95132
  case "slider":
94255
95133
  return [{ id: "value", label: `${q.min} \u2190 Value \u2192 ${q.max}`, value: `${a.value}${q.labels?.[Number(a.value)] ? ` (${q.labels[Number(a.value)]})` : ""}` }];
94256
95134
  case "rating":
@@ -94373,7 +95251,7 @@ async function handleQuestionKey(context, chunk, key) {
94373
95251
  return false;
94374
95252
  }
94375
95253
  if (editor.busy) return true;
94376
- const q = editor.question.payload, items = controls(editor), field2 = items[editor.fieldIndex];
95254
+ const q = editor.question.payload, items = controls(editor), field3 = items[editor.fieldIndex];
94377
95255
  const clear = () => {
94378
95256
  editor.answer = initialAnswer(q);
94379
95257
  editor.fieldIndex = 0;
@@ -94431,22 +95309,22 @@ async function handleQuestionKey(context, chunk, key) {
94431
95309
  editor.fieldIndex = (editor.fieldIndex + delta + items.length + 2) % (items.length + 2);
94432
95310
  } else if (key.name === "return" && editor.fieldIndex === items.length) await submit();
94433
95311
  else if (key.name === "return" && editor.fieldIndex === items.length + 1) clear();
94434
- else if (field2?.text) {
94435
- let value = field2.value ?? "";
95312
+ else if (field3?.text) {
95313
+ let value = field3.value ?? "";
94436
95314
  if (key.name === "backspace") value = eraseGrapheme(value);
94437
95315
  else if (key.name === "return") {
94438
95316
  editor.fieldIndex++;
94439
95317
  render();
94440
95318
  return true;
94441
95319
  } else if (!key.ctrl && !key.meta && chunk) value += terminalText(chunk);
94442
- if (q.type === "input") editor.answer.inputs[field2.id] = value;
94443
- else editor.answer[field2.id === "custom" ? "custom_answer" : field2.id] = value;
95320
+ if (q.type === "input") editor.answer.inputs[field3.id] = value;
95321
+ else editor.answer[field3.id === "custom" ? "custom_answer" : field3.id] = value;
94444
95322
  editor.error = void 0;
94445
- } else if (field2 && q.type === "choice" && ["space", "return"].includes(key.name ?? "")) {
95323
+ } else if (field3 && q.type === "choice" && ["space", "return"].includes(key.name ?? "")) {
94446
95324
  const selected = editor.answer.selection;
94447
- editor.answer.selection = q.multiple ? selected.includes(field2.id) ? selected.filter((id) => id !== field2.id) : [...selected, field2.id] : [field2.id];
95325
+ editor.answer.selection = q.multiple ? selected.includes(field3.id) ? selected.filter((id) => id !== field3.id) : [...selected, field3.id] : [field3.id];
94448
95326
  editor.error = void 0;
94449
- } else if (field2 && ["left", "right"].includes(key.name ?? "")) {
95327
+ } else if (field3 && ["left", "right"].includes(key.name ?? "")) {
94450
95328
  const delta = key.name === "left" ? -1 : 1;
94451
95329
  if (q.type === "slider") {
94452
95330
  const min = q.min, max = q.max, step = q.step ?? 1;
@@ -94454,7 +95332,7 @@ async function handleQuestionKey(context, chunk, key) {
94454
95332
  editor.touched = true;
94455
95333
  } else if (q.type === "rating") editor.answer.rating = Math.max(1, Math.min(q.max_stars ?? q.max ?? q.scale ?? 5, Number(editor.answer.rating) + delta));
94456
95334
  else if (q.type === "swipe") {
94457
- editor.answer.swipes[field2.id] = delta < 0 ? "dislike" : "like";
95335
+ editor.answer.swipes[field3.id] = delta < 0 ? "dislike" : "like";
94458
95336
  editor.fieldIndex = Math.min(items.length, editor.fieldIndex + 1);
94459
95337
  }
94460
95338
  editor.error = void 0;
@@ -94915,7 +95793,7 @@ async function createTuiChatProject(state, client2, chatIds) {
94915
95793
  if (!name || name.length > 200) throw new Error("Could not generate a project title.");
94916
95794
  if (context !== client2.getActiveTeamId() || !master.equals(Buffer.from(client2.getMasterKeyBytes()))) throw new Error("Chat workspace changed");
94917
95795
  const form = buildProjectForm();
94918
- form.fields.find((field2) => field2.name === "name").value = name;
95796
+ form.fields.find((field3) => field3.name === "name").value = name;
94919
95797
  const project = await submitProjectForm(client2, form, true);
94920
95798
  await placeTuiChats(state, client2, chatIds, { projectId: project.id, folderId: null }, "move");
94921
95799
  return project.id;
@@ -94932,13 +95810,14 @@ function workspaceGeometry(state, rawWidth) {
94932
95810
  const dialog = Boolean(state.form || state.paletteOpen || state.questionEditor || state.chrome);
94933
95811
  const settingsSplit = Boolean(state.settings && width >= 120 && !dialog);
94934
95812
  const settingsFullscreen = Boolean(state.settings && width < 120 && !dialog);
94935
- const sidebarWidth = settingsSplit ? 44 : state.sidebarOpen && width >= 90 && !dialog && !state.settings ? 27 : 0;
94936
- const paneWidth = Math.max(1, width - gutter * 2 - sidebarWidth);
95813
+ const sidebarWidth = state.sidebarOpen && width >= 90 && !dialog && !state.settings ? 27 : 0;
95814
+ const settingsWidth = settingsSplit ? 44 : 0;
95815
+ const paneWidth = Math.max(1, width - gutter * 2 - sidebarWidth - settingsWidth);
94937
95816
  const contentWidth = Math.min(WORKSPACE_MAX_COLUMNS, paneWidth);
94938
95817
  const inset = Math.floor((paneWidth - contentWidth) / 2);
94939
95818
  const composerWidth = Math.min(CONTENT_MAX_COLUMNS, paneWidth);
94940
95819
  const composerInset = Math.floor((paneWidth - composerWidth) / 2);
94941
- return { width, gutter, sidebarWidth, paneWidth, contentWidth, inset, composerWidth, composerInset, settingsSplit, settingsFullscreen };
95820
+ return { width, gutter, sidebarWidth, paneWidth, contentWidth, inset, composerWidth, composerInset, settingsWidth, settingsSplit, settingsFullscreen };
94942
95821
  }
94943
95822
  function composerViewport(state, width) {
94944
95823
  const wrapWidth = Math.max(1, width - 6);
@@ -95048,18 +95927,18 @@ function overlayLines(state, width, height) {
95048
95927
  { text: "Step configuration \xB7 Tab field \xB7 Ctrl+S save", color: "#a0a0a0", inset },
95049
95928
  ""
95050
95929
  ];
95051
- for (const [index, field2] of form.fields.entries()) {
95930
+ for (const [index, field3] of form.fields.entries()) {
95052
95931
  const focused = index === form.fieldIndex;
95053
- const suffix = [field2.required ? "required" : "", field2.valueType && field2.valueType !== "string" ? field2.valueType : "", field2.options ? "\u2190/\u2192 choose" : ""].filter(Boolean).join(" \xB7 ");
95054
- const action2 = { kind: "select", target: "form-field", index, id: field2.name };
95055
- rows3.push({ text: `${focused ? "\u203A" : " "} ${field2.label}${suffix ? ` \xB7 ${suffix}` : ""}`, bold: true, color: focused ? "#32ade6" : "#e6e6e6", inset, action: action2 });
95932
+ const suffix = [field3.required ? "required" : "", field3.valueType && field3.valueType !== "string" ? field3.valueType : "", field3.options ? "\u2190/\u2192 choose" : ""].filter(Boolean).join(" \xB7 ");
95933
+ const action2 = { kind: "select", target: "form-field", index, id: field3.name };
95934
+ rows3.push({ text: `${focused ? "\u203A" : " "} ${field3.label}${suffix ? ` \xB7 ${suffix}` : ""}`, bold: true, color: focused ? "#32ade6" : "#e6e6e6", inset, action: action2 });
95056
95935
  if (panelWidth >= 6) {
95057
95936
  const edge = "\u2500".repeat(panelWidth - 2), border = focused ? "#32ade6" : "#626878";
95058
95937
  rows3.push({ text: `\u256D${edge}\u256E`, color: border, inset });
95059
- rows3.push(...wrapCells(`${field2.secret ? "\u2022".repeat(Array.from(field2.value).length) : field2.value || "(empty)"}${focused && !form.busy ? "_" : ""}`, panelWidth - 4).map((text7) => ({ text: `\u2502 ${padCells(text7, panelWidth - 4)} \u2502`, color: focused ? "#ffffff" : "#cfcfcf", ...focused ? { background: "#2b3548" } : {}, inset, action: action2 })));
95938
+ rows3.push(...wrapCells(`${field3.secret ? "\u2022".repeat(Array.from(field3.value).length) : field3.value || "(empty)"}${focused && !form.busy ? "_" : ""}`, panelWidth - 4).map((text7) => ({ text: `\u2502 ${padCells(text7, panelWidth - 4)} \u2502`, color: focused ? "#ffffff" : "#cfcfcf", ...focused ? { background: "#2b3548" } : {}, inset, action: action2 })));
95060
95939
  rows3.push({ text: `\u2570${edge}\u256F`, color: border, inset }, "");
95061
- } else rows3.push(...wrapCells(field2.value, panelWidth).map((line3) => pointerLine(line3, action2)));
95062
- if (field2.options) rows3.push({ text: "\u2039 Previous Next \u203A", inset, spans: [
95940
+ } else rows3.push(...wrapCells(field3.value, panelWidth).map((line3) => pointerLine(line3, action2)));
95941
+ if (field3.options) rows3.push({ text: "\u2039 Previous Next \u203A", inset, spans: [
95063
95942
  { text: "\u2039 Previous", action: { ...action2, activate: true, name: "left" } },
95064
95943
  { text: " " },
95065
95944
  { text: "Next \u203A", action: { ...action2, activate: true, name: "right" } }
@@ -95079,11 +95958,11 @@ function overlayLines(state, width, height) {
95079
95958
  return rows3;
95080
95959
  }
95081
95960
  const rows2 = [form.title, ""];
95082
- for (const [index, field2] of form.fields.entries()) {
95083
- const action2 = { kind: "select", target: "form-field", index, id: field2.name };
95084
- rows2.push(pointerLine(`${index === form.fieldIndex ? ">" : " "} ${field2.label}${field2.options ? " [\u2190/\u2192]" : ""}`, action2));
95085
- rows2.push(...wrapCells(` ${field2.secret ? "\u2022".repeat(Array.from(field2.value).length) : field2.value || "(empty)"}${index === form.fieldIndex && !form.busy ? "_" : ""}`, width).map((line3) => pointerLine(line3, action2)));
95086
- if (field2.options) rows2.push({ text: "\u2039 Previous Next \u203A", spans: [
95961
+ for (const [index, field3] of form.fields.entries()) {
95962
+ const action2 = { kind: "select", target: "form-field", index, id: field3.name };
95963
+ rows2.push(pointerLine(`${index === form.fieldIndex ? ">" : " "} ${field3.label}${field3.options ? " [\u2190/\u2192]" : ""}`, action2));
95964
+ rows2.push(...wrapCells(` ${field3.secret ? "\u2022".repeat(Array.from(field3.value).length) : field3.value || "(empty)"}${index === form.fieldIndex && !form.busy ? "_" : ""}`, width).map((line3) => pointerLine(line3, action2)));
95965
+ if (field3.options) rows2.push({ text: "\u2039 Previous Next \u203A", spans: [
95087
95966
  { text: "\u2039 Previous", action: { ...action2, activate: true, name: "left" } },
95088
95967
  { text: " " },
95089
95968
  { text: "Next \u203A", action: { ...action2, activate: true, name: "right" } }
@@ -95099,7 +95978,7 @@ function overlayLines(state, width, height) {
95099
95978
  return rows2;
95100
95979
  }
95101
95980
  function renderWorkspaceFrame(state, rawWidth, rawHeight, body, options = {}) {
95102
- const { width, gutter, sidebarWidth, paneWidth, contentWidth, inset, composerWidth, composerInset, settingsSplit, settingsFullscreen } = workspaceGeometry(state, rawWidth);
95981
+ const { width, gutter, sidebarWidth, paneWidth, contentWidth, inset, composerWidth, composerInset, settingsWidth, settingsSplit, settingsFullscreen } = workspaceGeometry(state, rawWidth);
95103
95982
  const height = Math.max(1, Math.floor(rawHeight));
95104
95983
  beginPointerFrame(state, Math.max(1, Math.floor(rawWidth)), height);
95105
95984
  const modal = Boolean(state.form || state.paletteOpen || state.questionEditor || state.chrome);
@@ -95107,7 +95986,7 @@ function renderWorkspaceFrame(state, rawWidth, rawHeight, body, options = {}) {
95107
95986
  const mode = options.colorMode ?? "none";
95108
95987
  const ascii = options.ascii ?? false;
95109
95988
  const line3 = ascii ? "-" : "\u2500", vertical = ascii ? "|" : "\u2502";
95110
- const place = (rendered, side2 = " ".repeat(sidebarWidth)) => " ".repeat(gutter) + side2 + " ".repeat(inset) + rendered + " ".repeat(paneWidth - inset - contentWidth + gutter);
95989
+ const place = (rendered, side2 = " ".repeat(sidebarWidth), settings = " ".repeat(settingsWidth)) => " ".repeat(gutter) + side2 + " ".repeat(inset) + rendered + " ".repeat(paneWidth - inset - contentWidth) + settings + " ".repeat(gutter);
95111
95990
  const navParts = [{ text: "OpenMates ", color: "#cfcfcf", bold: true, action: { kind: "command", command: "/chats" } }, ...WORKSPACES.map((workspace, index) => {
95112
95991
  const focused = state.focus === "navigation" && index === state.navigationIndex;
95113
95992
  const label3 = state.workspace === workspace ? `[${titleCase(workspace)}]` : titleCase(workspace);
@@ -95197,21 +96076,24 @@ function renderWorkspaceFrame(state, rawWidth, rawHeight, body, options = {}) {
95197
96076
  const heroRows = options.headerRows ?? 0;
95198
96077
  const category = state.screen === "example" ? state.activeExample?.chat.category : state.activeChat?.category;
95199
96078
  const background = chatBackground(category);
95200
- const allSide = settingsSplit ? renderTuiSettings(state.settings, sidebarWidth - 2) : sidebarWidth ? pointerSidebarLines(state) : [];
96079
+ const allSide = sidebarWidth ? pointerSidebarLines(state) : [];
96080
+ const allSettings = settingsSplit ? renderTuiSettings(state.settings, settingsWidth - 2) : [];
95201
96081
  const sideMarker = allSide.findIndex((row3) => /(?:^|\s)› /.test(lineText(row3)) || lineText(row3).startsWith("> "));
95202
96082
  let sideStart = Math.max(0, sideMarker - bodyHeight + 3);
95203
- let side = sideStart ? [allSide[0], "", ...allSide.slice(sideStart)] : allSide;
96083
+ const side = sideStart ? [allSide[0], "", ...allSide.slice(sideStart)] : allSide;
96084
+ let settingsRows = [];
95204
96085
  if (settingsSplit) {
95205
- const panel2 = state.settings, fixed2 = allSide.slice(0, 4), scrolling = allSide.slice(4), available2 = Math.max(1, bodyHeight - fixed2.length);
96086
+ const panel2 = state.settings, fixed2 = allSettings.slice(0, 4), scrolling = allSettings.slice(4), available2 = Math.max(1, bodyHeight - fixed2.length);
96087
+ const settingsMarker = allSettings.findIndex((row3) => /(?:^|\s)› /.test(lineText(row3)));
95206
96088
  sideStart = Math.min(Math.max(0, scrolling.length - available2), Math.max(0, panel2.scrollOffset));
95207
96089
  if (panel2.followSelection) {
95208
- const marker = Math.max(-1, sideMarker - 4);
96090
+ const marker = Math.max(-1, settingsMarker - 4);
95209
96091
  if (marker >= sideStart + available2) sideStart = Math.min(Math.max(0, scrolling.length - available2), Math.max(0, marker - available2 + 3));
95210
96092
  if (marker >= 0 && marker < sideStart) sideStart = marker;
95211
96093
  }
95212
96094
  panel2.scrollOffset = sideStart;
95213
96095
  panel2.followSelection = false;
95214
- side = [...fixed2, ...scrolling.slice(sideStart)];
96096
+ settingsRows = [...fixed2, ...scrolling.slice(sideStart)];
95215
96097
  }
95216
96098
  const rows2 = [];
95217
96099
  for (let i = 0; i < bodyHeight; i++) {
@@ -95247,25 +96129,29 @@ function renderWorkspaceFrame(state, rawWidth, rawHeight, body, options = {}) {
95247
96129
  } else if (!overlay2 && !sidebarOverlay && index < heroRows) rendered = backgroundLine(raw, contentWidth, background, mode);
95248
96130
  else if (raw.startsWith("> ") || raw.includes("\u203A")) rendered = foreground(rendered, "#ff553b", mode, true);
95249
96131
  else if (/^(You|Sophia|Tasks|Projects|Workflows|Recent chats|Suggestions|Actions)$/.test(raw)) rendered = foreground(rendered, "#5a85eb", mode, true);
95250
- const sideValue = side[i], sideText = sideValue ? lineText(sideValue) : "";
95251
- let sidebarRow;
95252
- if (sidebarWidth) {
95253
- const style2 = typeof sideValue === "string" ? void 0 : sideValue;
95254
- if (style2?.action) addPointerTarget(state, gutter, i + 2, Math.min(cells(sideText), sidebarWidth - 2), style2.action);
96132
+ const renderSide = (value2, sideWidth, column, dividerAtEnd) => {
96133
+ const text7 = value2 ? lineText(value2) : "";
96134
+ const style2 = typeof value2 === "string" ? void 0 : value2;
96135
+ const usable = sideWidth - 2;
96136
+ if (style2?.action) addPointerTarget(state, column, i + 2, Math.min(cells(text7), usable), style2.action);
96137
+ let renderedSide = "";
95255
96138
  if (style2?.spans) {
95256
96139
  let used = 0;
95257
- sidebarRow = "";
95258
96140
  for (const part of style2.spans) {
95259
- const text7 = truncateCells(part.text, Math.max(0, sidebarWidth - 2 - used)), size = cells(text7);
95260
- if (part.action) addPointerTarget(state, gutter + used, i + 2, size, part.action);
95261
- sidebarRow += foreground(text7, part.color ?? "#cfcfcf", mode, part.bold);
96141
+ const partText = truncateCells(part.text, Math.max(0, usable - used)), size = cells(partText);
96142
+ if (part.action) addPointerTarget(state, column + used, i + 2, size, part.action);
96143
+ renderedSide += foreground(partText, part.color ?? "#cfcfcf", mode, part.bold);
95262
96144
  used += size;
95263
96145
  }
95264
- sidebarRow += " ".repeat(Math.max(0, sidebarWidth - 2 - used));
95265
- } else sidebarRow = foreground(padCells(sideText, sidebarWidth - 2), style2?.color ?? "#cfcfcf", mode, style2?.bold);
95266
- sidebarRow += foreground(`${vertical} `, "#626878", mode);
95267
- }
95268
- rows2.push(place(rendered, sidebarRow));
96146
+ renderedSide += " ".repeat(Math.max(0, usable - used));
96147
+ } else renderedSide = foreground(padCells(text7, usable), style2?.color ?? "#cfcfcf", mode, style2?.bold);
96148
+ const border = foreground(`${vertical} `, "#626878", mode);
96149
+ return dividerAtEnd ? renderedSide + border : border + renderedSide;
96150
+ };
96151
+ const sidebarRow = sidebarWidth ? renderSide(side[i], sidebarWidth, gutter, true) : void 0;
96152
+ const settingsColumn2 = gutter + sidebarWidth + paneWidth + 2;
96153
+ const settingsRow = settingsWidth ? renderSide(settingsRows[i], settingsWidth, settingsColumn2, false) : void 0;
96154
+ rows2.push(place(rendered, sidebarRow, settingsRow));
95269
96155
  }
95270
96156
  if (state.modelSelector?.open && showComposer && aiComposer && !modal && !settingsFullscreen && bodyHeight > 0) {
95271
96157
  const popWidth = Math.max(1, Math.min(46, composerWidth));
@@ -95292,14 +96178,14 @@ function renderWorkspaceFrame(state, rawWidth, rawHeight, body, options = {}) {
95292
96178
  const hintColor = state.focus === "composer" ? "#a0a0a0" : "#ff553b";
95293
96179
  const composer = [];
95294
96180
  if (showComposer) {
95295
- if (!modal && !sidebarOverlay) for (let i = 0; i < input.count; i++) addPointerTarget(
96181
+ if (!modal && !sidebarOverlay && !state.settings) for (let i = 0; i < input.count; i++) addPointerTarget(
95296
96182
  state,
95297
96183
  gutter + sidebarWidth + composerInset + (composerWidth < 6 ? 0 : 2),
95298
96184
  2 + bodyHeight + (composerWidth < 6 ? 0 : 1) + i,
95299
96185
  Math.max(1, composerWidth - (composerWidth < 6 ? 0 : 4)),
95300
96186
  { kind: "focus", focus: "composer" }
95301
96187
  );
95302
- const placeInput = (rendered) => " ".repeat(gutter + sidebarWidth + composerInset) + rendered + " ".repeat(paneWidth - composerInset - composerWidth + gutter);
96188
+ const placeInput = (rendered) => " ".repeat(gutter + sidebarWidth + composerInset) + rendered + " ".repeat(paneWidth - composerInset - composerWidth + settingsWidth + gutter);
95303
96189
  const inputRows = input.lines.map((text7, i) => `${i ? " " : "> "}${text7 || (!state.input ? placeholder : "")}`);
95304
96190
  if (composerWidth < 6) composer.push(...inputRows.map((text7) => placeInput(padCells(text7, composerWidth))), ...aiComposer ? [placeInput(padCells("AI", composerWidth))] : [], placeInput(foreground(padCells(hint, composerWidth), hintColor, mode)), placeInput(" ".repeat(composerWidth)), placeInput(" ".repeat(composerWidth)));
95305
96191
  else {
@@ -96156,7 +97042,7 @@ function prepareSkillInput(schema, input, timezone, now = /* @__PURE__ */ new Da
96156
97042
  function validateSkillInput(schema, input) {
96157
97043
  const issues = [];
96158
97044
  function walk(node, value, path2, required3) {
96159
- const field2 = resolveSkillSchema(node, schema);
97045
+ const field3 = resolveSkillSchema(node, schema);
96160
97046
  if (value === null) {
96161
97047
  const nullable = node.type === "null" || (node.anyOf ?? node.oneOf)?.some((part) => part.type === "null");
96162
97048
  if (!nullable) issues.push({ path: path2, code: required3 ? "required" : "type" });
@@ -96166,40 +97052,40 @@ function validateSkillInput(schema, input) {
96166
97052
  if (required3) issues.push({ path: path2, code: "required" });
96167
97053
  return;
96168
97054
  }
96169
- if (field2.enum && !field2.enum.some((item) => item === value)) issues.push({ path: path2, code: "enum" });
96170
- if (field2.type === "object" || field2.properties) {
97055
+ if (field3.enum && !field3.enum.some((item) => item === value)) issues.push({ path: path2, code: "enum" });
97056
+ if (field3.type === "object" || field3.properties) {
96171
97057
  if (!value || typeof value !== "object" || Array.isArray(value)) {
96172
97058
  issues.push({ path: path2, code: "type" });
96173
97059
  return;
96174
97060
  }
96175
- for (const [name, child] of Object.entries(field2.properties ?? {})) walk(child, object3(value)[name], path2 ? `${path2}.${name}` : name, (field2.required ?? []).includes(name));
96176
- } else if (field2.type === "array") {
97061
+ for (const [name, child] of Object.entries(field3.properties ?? {})) walk(child, object3(value)[name], path2 ? `${path2}.${name}` : name, (field3.required ?? []).includes(name));
97062
+ } else if (field3.type === "array") {
96177
97063
  if (!Array.isArray(value)) {
96178
97064
  issues.push({ path: path2, code: "type" });
96179
97065
  return;
96180
97066
  }
96181
- if (field2.minItems !== void 0 && value.length < field2.minItems) issues.push({ path: path2, code: "minItems" });
96182
- if (field2.maxItems !== void 0 && value.length > field2.maxItems) issues.push({ path: path2, code: "maxItems" });
96183
- value.forEach((item, index) => walk(field2.items ?? {}, item, `${path2}[${index}]`, true));
96184
- } else if (field2.type === "integer" || field2.type === "number") {
96185
- if (typeof value !== "number" || !Number.isFinite(value) || field2.type === "integer" && !Number.isInteger(value)) {
97067
+ if (field3.minItems !== void 0 && value.length < field3.minItems) issues.push({ path: path2, code: "minItems" });
97068
+ if (field3.maxItems !== void 0 && value.length > field3.maxItems) issues.push({ path: path2, code: "maxItems" });
97069
+ value.forEach((item, index) => walk(field3.items ?? {}, item, `${path2}[${index}]`, true));
97070
+ } else if (field3.type === "integer" || field3.type === "number") {
97071
+ if (typeof value !== "number" || !Number.isFinite(value) || field3.type === "integer" && !Number.isInteger(value)) {
96186
97072
  issues.push({ path: path2, code: "type" });
96187
97073
  return;
96188
97074
  }
96189
- if (field2.minimum !== void 0 && value < field2.minimum) issues.push({ path: path2, code: "minimum" });
96190
- if (field2.maximum !== void 0 && value > field2.maximum) issues.push({ path: path2, code: "maximum" });
96191
- } else if (field2.type === "boolean") {
97075
+ if (field3.minimum !== void 0 && value < field3.minimum) issues.push({ path: path2, code: "minimum" });
97076
+ if (field3.maximum !== void 0 && value > field3.maximum) issues.push({ path: path2, code: "maximum" });
97077
+ } else if (field3.type === "boolean") {
96192
97078
  if (typeof value !== "boolean") issues.push({ path: path2, code: "type" });
96193
- } else if (field2.type === "string" || !field2.type) {
97079
+ } else if (field3.type === "string" || !field3.type) {
96194
97080
  if (typeof value !== "string") {
96195
97081
  issues.push({ path: path2, code: "type" });
96196
97082
  return;
96197
97083
  }
96198
- if (field2.minLength !== void 0 && value.length < field2.minLength) issues.push({ path: path2, code: "minLength" });
96199
- if (field2.maxLength !== void 0 && value.length > field2.maxLength) issues.push({ path: path2, code: "maxLength" });
96200
- if (field2.pattern) {
97084
+ if (field3.minLength !== void 0 && value.length < field3.minLength) issues.push({ path: path2, code: "minLength" });
97085
+ if (field3.maxLength !== void 0 && value.length > field3.maxLength) issues.push({ path: path2, code: "maxLength" });
97086
+ if (field3.pattern) {
96201
97087
  try {
96202
- if (!new RegExp(field2.pattern).test(value)) issues.push({ path: path2, code: "pattern" });
97088
+ if (!new RegExp(field3.pattern).test(value)) issues.push({ path: path2, code: "pattern" });
96203
97089
  } catch {
96204
97090
  }
96205
97091
  }
@@ -96453,9 +97339,9 @@ function renderTuiAppsSkill(skill, options) {
96453
97339
  if (options.tab === "overview") {
96454
97340
  lines.push("Use this skill manually. Your mates can also use it in chats.", "");
96455
97341
  for (const path2 of skillLeafPaths(skill.schema)) {
96456
- const field2 = schemaForPath(skill.schema, path2);
97342
+ const field3 = schemaForPath(skill.schema, path2);
96457
97343
  const value = getSkillPath(skill.defaults, path2);
96458
- lines.push(truncateCells(`${path2}${isRequired(skill.schema, path2) ? " *" : ""} \xB7 ${field2?.type ?? "value"}${value !== void 0 ? ` \xB7 ${typeof value === "string" ? value : JSON.stringify(value)}` : ""}`, width));
97344
+ lines.push(truncateCells(`${path2}${isRequired(skill.schema, path2) ? " *" : ""} \xB7 ${field3?.type ?? "value"}${value !== void 0 ? ` \xB7 ${typeof value === "string" ? value : JSON.stringify(value)}` : ""}`, width));
96459
97345
  }
96460
97346
  lines.push("", skill.executionAvailable && options.pointer ? pointerLine("Enter opens the input form. Running may spend credits.", { kind: "command", command: "/app-run" }) : skill.executionAvailable ? "Enter opens the input form. Running may spend credits." : `Unavailable: ${skill.unavailableReason ?? "This skill cannot run here."}`);
96461
97347
  const fixed = skill.pricing?.fixed;
@@ -96508,8 +97394,8 @@ function buildTuiAppsSkillForm(skill) {
96508
97394
  };
96509
97395
  }
96510
97396
  function parseField(raw, schema) {
96511
- const field2 = schema ?? {};
96512
- const type = field2.type;
97397
+ const field3 = schema ?? {};
97398
+ const type = field3.type;
96513
97399
  if (!raw.trim()) return void 0;
96514
97400
  if (type === "number" || type === "integer") {
96515
97401
  const parsed = Number(raw);
@@ -96527,16 +97413,16 @@ function prepareTuiAppsSkillRun(skill, form, timezone = Intl.DateTimeFormat().re
96527
97413
  if (form.kind !== "app-skill-input" || form.contextId !== `${skill.appId}/${skill.skillId}`) throw new Error("App skill form context changed.");
96528
97414
  if (!skill.executionAvailable) throw new Error(skill.unavailableReason ?? "This skill is unavailable.");
96529
97415
  let input = structuredClone(skill.defaults);
96530
- for (const field2 of form.fields) {
96531
- const previous = getSkillPath(input, field2.name);
96532
- if (!field2.value.trim() && previous === void 0) continue;
97416
+ for (const field3 of form.fields) {
97417
+ const previous = getSkillPath(input, field3.name);
97418
+ if (!field3.value.trim() && previous === void 0) continue;
96533
97419
  let value;
96534
97420
  try {
96535
- value = parseField(field2.value, schemaForPath(skill.schema, field2.name));
97421
+ value = parseField(field3.value, schemaForPath(skill.schema, field3.name));
96536
97422
  } catch (error) {
96537
- throw new Error(`${field2.label}: ${error instanceof Error ? error.message : "Invalid value"}`);
97423
+ throw new Error(`${field3.label}: ${error instanceof Error ? error.message : "Invalid value"}`);
96538
97424
  }
96539
- input = setSkillPath(input, field2.name, value);
97425
+ input = setSkillPath(input, field3.name, value);
96540
97426
  }
96541
97427
  input = prepareSkillInput(skill.schema, input, timezone);
96542
97428
  const issues = validateSkillInput(skill.schema, input);
@@ -97065,6 +97951,9 @@ function createInitialTuiState() {
97065
97951
  embedAliases: {},
97066
97952
  chatSelectedEmbedId: null,
97067
97953
  username: null,
97954
+ currentUserHash: null,
97955
+ activeTeamId: null,
97956
+ activeTeamName: null,
97068
97957
  inspirations: [],
97069
97958
  inspirationIndices: {},
97070
97959
  homeLoading: false,
@@ -97164,7 +98053,8 @@ function createInitialTuiState() {
97164
98053
  chatContextAuthoringJobs: {},
97165
98054
  chatContextAuthoringControls: {},
97166
98055
  status: null,
97167
- isBusy: false
98056
+ isBusy: false,
98057
+ isAwaitingAi: false
97168
98058
  };
97169
98059
  }
97170
98060
  function programmaticQuickstart() {
@@ -97209,12 +98099,13 @@ function rankExamples(examples, interests) {
97209
98099
  return { example, index, score };
97210
98100
  }).sort((a, b) => b.score - a.score || a.index - b.index).map((entry) => entry.example);
97211
98101
  }
98102
+ var TUI_SESSION_ENDED_STATUS = "Session ended. Sign in to reopen your work.";
97212
98103
  function resetEndedTuiSession(state, hasSession) {
97213
98104
  if (!state.signedIn || hasSession) return false;
97214
98105
  state.homeAbortController?.abort();
97215
98106
  Object.values(state.chatContextAuthoringControls).forEach((control) => control.stop());
97216
98107
  const routeVersion = state.routeVersion + 1, homeLoadVersion = state.homeLoadVersion + 1;
97217
- Object.assign(state, createInitialTuiState(), { routeVersion, homeLoadVersion, status: "Session ended. Sign in to reopen your work." });
98108
+ Object.assign(state, createInitialTuiState(), { routeVersion, homeLoadVersion, status: TUI_SESSION_ENDED_STATUS });
97218
98109
  return true;
97219
98110
  }
97220
98111
  function renderTuiFrame(state, width, height, options = {}) {
@@ -97223,16 +98114,21 @@ function renderTuiFrame(state, width, height, options = {}) {
97223
98114
  if (state.startup || state.privacyOffer) return renderStartupFrame(state, width, height, options.colorMode ?? "none");
97224
98115
  const bodyWidth = workspaceGeometry(state, width).contentWidth;
97225
98116
  const header2 = fullscreenHeaderLines(state, bodyWidth);
98117
+ const teamBanner = state.signedIn && state.activeTeamId ? [{ text: teamIdentityLabel(state), color: "#5a85eb", bold: true }] : [];
97226
98118
  const embedHero = state.screen === "embed" ? renderEmbedHero(state, bodyWidth, height) : null;
97227
98119
  const stickyRows = state.screen === "embed" ? embedHero.length + 1 : state.screen === "results-view" ? 2 : state.screen === "project" && state.activeProject ? renderProjectIdentity(state.activeProject, { width: bodyWidth }).length + renderProjectTabs(state.projectTab, bodyWidth).length : state.screen === "app" && state.activeApp ? renderTuiAppIdentity(state.activeApp, bodyWidth).length + renderTuiAppTabs(state.appTab, bodyWidth).length : state.screen === "app-skill" && state.activeAppSkill ? renderTuiAppsSkillIdentity(state.activeAppSkill, bodyWidth).length + renderTuiAppsSkillTabs(state.appSkillTab, bodyWidth).length : state.screen === "workflow" && state.activeWorkflow ? renderWorkflowIdentity(state.activeWorkflow, { width: bodyWidth, run: state.workflowTab === "runs" ? state.workflowRuns[state.selectedWorkflowRunIndex] : void 0 }).length + (bodyWidth < 36 ? 7 : 4) : 0;
97228
98120
  return renderWorkspaceFrame(
97229
98121
  state,
97230
98122
  width,
97231
98123
  height,
97232
- [...header2, ...renderBody(state, bodyWidth, height, embedHero ?? void 0)],
97233
- { ...options, stickyRows: stickyRows + header2.length, stickyFallbackRows: embedHero ? header2.length : void 0 }
98124
+ [...header2, ...teamBanner, ...renderBody(state, bodyWidth, height, embedHero ?? void 0)],
98125
+ { ...options, stickyRows: stickyRows + header2.length + teamBanner.length, stickyFallbackRows: embedHero ? header2.length + teamBanner.length : void 0 }
97234
98126
  );
97235
98127
  }
98128
+ function teamIdentityLabel(state) {
98129
+ const name = terminalText(state.activeTeamName?.trim() || "Team").replace(/\s+/g, " ").slice(0, 80);
98130
+ return `[${Array.from(name)[0]?.toLocaleUpperCase() || "?"}] Team \xB7 ${name}`;
98131
+ }
97236
98132
  function renderStartupFrame(state, width, height, colorMode) {
97237
98133
  width = Math.max(1, Math.floor(width));
97238
98134
  height = Math.max(1, Math.floor(height));
@@ -97302,7 +98198,7 @@ function renderScreenBody(state, width, height, embedHero) {
97302
98198
  case "chat":
97303
98199
  return renderChat(state, width, height);
97304
98200
  case "chats":
97305
- return renderHomeChatCards(state, width, height);
98201
+ return [...renderHomeChatCards(state, width, height), ...state.signedIn && state.activeTeamId ? ["", teamAiReminder()] : []];
97306
98202
  case "apps": {
97307
98203
  const apps = homeTuiApps(state.apps, state.filter, state.homeShowAll);
97308
98204
  return [
@@ -97376,7 +98272,7 @@ function renderScreenBody(state, width, height, embedHero) {
97376
98272
  return renderTaskDetail2(state, width);
97377
98273
  case "start":
97378
98274
  default:
97379
- return renderHomeChatCards(state, width, height);
98275
+ return [...renderHomeChatCards(state, width, height), ...state.signedIn && state.activeTeamId ? ["", teamAiReminder()] : []];
97380
98276
  }
97381
98277
  }
97382
98278
  function renderHelp(width) {
@@ -97535,7 +98431,7 @@ function renderChat(state, width, height) {
97535
98431
  lines.push("");
97536
98432
  continue;
97537
98433
  }
97538
- lines.push({ text: messageLabel(message.role, message.title, message.category, state.activeChat), color: "#5a85eb", bold: true });
98434
+ lines.push({ text: messageLabel(message.role, message.title, message.category, state.activeChat, message.remoteUser), color: "#5a85eb", bold: true });
97539
98435
  const embeds = new Map(Object.entries(state.chatEmbeds));
97540
98436
  lines.push(...renderMessageContentStyled(message.content, width, embeds, state, message.role === "assistant" ? message.embedIds : void 0, viewOffset, questionOffset, message.role === "assistant"));
97541
98437
  viewOffset += messageResultsViews(message.content).length;
@@ -97543,11 +98439,15 @@ function renderChat(state, width, height) {
97543
98439
  lines.push("");
97544
98440
  }
97545
98441
  if (state.projectFocusPending) lines.push("Project access starts after the countdown. /project-focus-reject to cancel.");
97546
- if (state.isBusy) lines.push(`${messageLabel("assistant", null, null, state.activeChat)} is typing...`);
98442
+ if (state.isAwaitingAi) lines.push(`${messageLabel("assistant", null, null, state.activeChat)} is typing...`);
98443
+ if (state.signedIn && state.activeTeamId) lines.push(teamAiReminder());
97547
98444
  if (state.followUpSuggestions.length) lines.push("", "Suggestions", ...state.followUpSuggestions.slice(0, 3).map((s, i) => ` ${i + 1}. ${s}`));
97548
98445
  if (state.status) lines.push("", state.status);
97549
98446
  return lines.flatMap((line3) => typeof line3 === "string" ? wrap(line3, width) : [line3]);
97550
98447
  }
98448
+ function teamAiReminder() {
98449
+ return "To ask AI, mention @openmates or a configured Mate.";
98450
+ }
97551
98451
  function renderEmbedHero(state, width, height) {
97552
98452
  const app = state.detailEmbed?.appId ?? "", gradient = APP_GRADIENTS[app] ?? PRIMARY_GRADIENT;
97553
98453
  const title = state.detailTitle || "Embed";
@@ -97675,8 +98575,13 @@ function interestScore(haystack, interest) {
97675
98575
  }
97676
98576
  return score;
97677
98577
  }
97678
- function messageLabel(role, sender, category, chat) {
97679
- if (role === "user") return "You";
98578
+ function messageLabel(role, sender, category, chat, remoteUser = false) {
98579
+ if (role === "user") {
98580
+ if (!remoteUser) return "You";
98581
+ const name = terminalText(sender?.trim() || "Team member").replace(/\s+/g, " ").slice(0, 80);
98582
+ const initial = Array.from(name)[0]?.toLocaleUpperCase() || "?";
98583
+ return `[${initial}] ${name}`;
98584
+ }
97680
98585
  if (role === "system") return "System";
97681
98586
  if (sender && !/^(assistant|ai assistant|ai|openmates)$/i.test(sender.trim())) return sender;
97682
98587
  return MATE_NAMES[category ?? ""] || chat?.mateName || MATE_NAMES[chat?.category ?? ""] || "Assistant";
@@ -97873,6 +98778,128 @@ async function persistCliProjectAuthoringJob(client2, job, approvedDigest) {
97873
98778
  });
97874
98779
  }
97875
98780
 
98781
+ // src/tuiTeamChat.ts
98782
+ import { createHash as createHash25 } from "crypto";
98783
+ function tuiChatMessages(messages, teamId, username, currentUserHash) {
98784
+ const ownName = username?.trim().toLocaleLowerCase();
98785
+ return messages.map((message) => {
98786
+ const role = message.role === "user" ? "user" : message.role === "system" ? "system" : "assistant";
98787
+ const name = message.senderName ? terminalText(message.senderName).trim() : "";
98788
+ const remoteUser = !!teamId && role === "user" && (message.hashedSenderUserId ? !currentUserHash || message.hashedSenderUserId !== currentUserHash : !!name && (!ownName || name.toLocaleLowerCase() !== ownName));
98789
+ return {
98790
+ id: message.clientMessageId || message.id,
98791
+ role,
98792
+ content: message.content,
98793
+ title: name || null,
98794
+ remoteUser,
98795
+ senderUserHash: message.hashedSenderUserId ?? null,
98796
+ category: message.category,
98797
+ embedIds: message.embedIds
98798
+ };
98799
+ });
98800
+ }
98801
+ function mergeTuiTeamChatWindow(existing, latest, hasMoreBefore) {
98802
+ if (!hasMoreBefore) return { messages: latest, hasGap: false };
98803
+ const existingById = new Map(existing.flatMap((message, index) => message.id ? [[message.id, index]] : []));
98804
+ const overlap = latest.findIndex((message) => !!message.id && existingById.has(message.id));
98805
+ if (overlap < 0) return { messages: [...existing, ...latest], hasGap: true };
98806
+ const older = existing.slice(0, existingById.get(latest[overlap].id));
98807
+ const trailing = existing.slice(older.length).filter((message) => !message.id);
98808
+ return { messages: [...older, ...latest, ...trailing], hasGap: false };
98809
+ }
98810
+ function reconcileTuiTeamSenderLabels(state) {
98811
+ if (!state.currentUserHash || state.screen !== "chat" || state.isBusy) return;
98812
+ for (const message of state.messages) {
98813
+ if (message.role === "user" && message.senderUserHash)
98814
+ message.remoteUser = message.senderUserHash !== state.currentUserHash;
98815
+ }
98816
+ }
98817
+ async function loadTuiTeamIdentity(state, client2, render, isCurrent = () => true) {
98818
+ const teamId = client2.getActiveTeamId();
98819
+ if (!client2.hasSession() || !teamId) return null;
98820
+ const ownerCurrent = captureTuiWorkspaceOwner(client2);
98821
+ const [profile, team] = await Promise.allSettled([
98822
+ typeof client2.whoAmI === "function" ? client2.whoAmI() : Promise.reject(new Error("Profile unavailable")),
98823
+ typeof client2.getTeamDetails === "function" ? client2.getTeamDetails(teamId) : Promise.reject(new Error("Team unavailable"))
98824
+ ]);
98825
+ if (!isCurrent() || !ownerCurrent() || client2.getActiveTeamId() !== teamId) return null;
98826
+ let profileLoaded = false;
98827
+ if (profile.status === "fulfilled") {
98828
+ const user2 = profile.value;
98829
+ const id = String(user2.id ?? "");
98830
+ if (id) {
98831
+ state.currentUserHash = createHash25("sha256").update(id).digest("hex");
98832
+ profileLoaded = true;
98833
+ }
98834
+ if (typeof user2.username === "string") state.username = terminalText(user2.username);
98835
+ }
98836
+ if (team.status === "fulfilled") state.activeTeamName = terminalText(team.value.name).trim() || null;
98837
+ reconcileTuiTeamSenderLabels(state);
98838
+ render();
98839
+ return { profileLoaded, teamLoaded: team.status === "fulfilled" };
98840
+ }
98841
+ function createTuiTeamChatRefresh(state, client2, render, pollIntervalMs = 5e3) {
98842
+ let closed = false, inFlight = false, pending = false;
98843
+ let timer = null;
98844
+ const poll = setInterval(() => request(), pollIntervalMs);
98845
+ poll.unref?.();
98846
+ const eligible = () => !closed && state.signedIn && client2.hasSession() && state.workspace === "chats" && state.screen === "chat" && !!state.activeChatId && !!state.activeTeamId && state.activeTeamId === client2.getActiveTeamId();
98847
+ const refresh = async () => {
98848
+ timer = null;
98849
+ if (!pending || inFlight || !eligible()) return;
98850
+ if (state.isBusy) {
98851
+ request();
98852
+ return;
98853
+ }
98854
+ pending = false;
98855
+ inFlight = true;
98856
+ const chatId = state.activeChatId, teamId = client2.getActiveTeamId();
98857
+ const route2 = state.routeVersion, messages = state.messages;
98858
+ const ownerCurrent = captureTuiWorkspaceOwner(client2);
98859
+ const current2 = () => eligible() && ownerCurrent() && state.routeVersion === route2 && state.activeChatId === chatId && client2.getActiveTeamId() === teamId && state.messages === messages && !state.isBusy;
98860
+ try {
98861
+ const window = typeof client2.getChatMessagesWindow === "function" ? await client2.getChatMessagesWindow(chatId, {
98862
+ teamId,
98863
+ direction: "latest",
98864
+ limit: 100,
98865
+ respectCompressionBoundary: false,
98866
+ preferCache: true
98867
+ }) : null;
98868
+ const result = window ?? await client2.getChatMessages(chatId, { teamId });
98869
+ if (!current2() || result.chat.id !== chatId) return;
98870
+ const fetched = tuiChatMessages(result.messages, teamId, state.username, state.currentUserHash);
98871
+ const merged = window ? mergeTuiTeamChatWindow(messages, fetched, window.hasMoreBefore) : { messages: fetched, hasGap: false };
98872
+ const next = merged.messages;
98873
+ if (merged.hasGap) state.status = "Some older Team messages may be missing. Use /refresh to load full history.";
98874
+ if (next.length === messages.length && next.every((message, index) => message.id === messages[index].id && message.role === messages[index].role && message.content === messages[index].content && message.category === messages[index].category && message.title === messages[index].title && message.remoteUser === messages[index].remoteUser && message.senderUserHash === messages[index].senderUserHash && (message.embedIds ?? []).length === (messages[index].embedIds ?? []).length && (message.embedIds ?? []).every((id, embedIndex) => id === messages[index].embedIds?.[embedIndex]))) return;
98875
+ state.messages = next;
98876
+ state.activeChat = result.chat;
98877
+ registerChatEmbedAliases(state);
98878
+ if (typeof client2.getEmbed === "function") void hydrateChatEmbedPreviews(state, client2, render);
98879
+ render();
98880
+ } catch {
98881
+ } finally {
98882
+ inFlight = false;
98883
+ if (state.isBusy && eligible()) pending = true;
98884
+ if (pending && eligible()) request();
98885
+ }
98886
+ };
98887
+ const request = () => {
98888
+ if (!eligible()) return;
98889
+ pending = true;
98890
+ if (timer || inFlight) return;
98891
+ timer = setTimeout(() => {
98892
+ void refresh();
98893
+ }, state.isBusy ? Math.min(1e3, pollIntervalMs) : 250);
98894
+ };
98895
+ return { request, dispose: () => {
98896
+ closed = true;
98897
+ clearInterval(poll);
98898
+ if (timer) clearTimeout(timer);
98899
+ timer = null;
98900
+ } };
98901
+ }
98902
+
97876
98903
  // src/tuiWorkspaceController.ts
97877
98904
  function chatDraftKey(state) {
97878
98905
  return state.screen === "example" ? `example:${state.activeExample?.chat.id}` : state.activeChatId ?? "new";
@@ -97972,7 +98999,7 @@ async function openSavedChat(context, id) {
97972
98999
  state.activeChat = result.chat;
97973
99000
  state.activeExample = null;
97974
99001
  state.selectedProjectId = null;
97975
- state.messages = result.messages.map((m) => ({ id: m.id, role: m.role === "user" ? "user" : m.role === "system" ? "system" : "assistant", content: m.content, title: m.senderName, category: m.category, embedIds: m.embedIds }));
99002
+ state.messages = tuiChatMessages(result.messages, client2.getActiveTeamId?.() ?? null, state.username, state.currentUserHash);
97976
99003
  registerChatEmbedAliases(state);
97977
99004
  if (typeof client2.getEmbed === "function") void hydrateChatEmbedPreviews(state, client2, render);
97978
99005
  state.projectFocusPending = null;
@@ -98068,7 +99095,7 @@ async function projectFiles(context, project, options = {}) {
98068
99095
  }
98069
99096
  async function projectTasks(context, id) {
98070
99097
  const { state, client: client2, render } = context, request = state.routeVersion;
98071
- await loadCachedTuiWorkspace(client2, `project:${id}:tasks`, async () => decryptUserTasks(await client2.listUserTasks({ projectId: id }), client2.getMasterKeyBytes()), (tasks) => {
99098
+ await loadTuiTaskList(client2, `project:${id}:tasks`, { projectId: id }, (tasks, source, complete) => {
98072
99099
  if (request !== state.routeVersion || state.activeProject?.id !== id || state.projectTab !== "tasks") return;
98073
99100
  const selected = filterTasks(state.tasks, state.filter)[state.selectedIndex]?.taskId;
98074
99101
  state.tasks = tasks;
@@ -98076,9 +99103,13 @@ async function projectTasks(context, id) {
98076
99103
  const index = filterTasks(tasks, state.filter).findIndex((task) => task.taskId === selected);
98077
99104
  if (index >= 0) state.selectedIndex = index;
98078
99105
  }
98079
- state.status = null;
99106
+ state.status = complete ? null : source === "cache" ? "Showing saved Project Tasks. Syncing\u2026" : `Loading more Project Tasks\u2026 ${tasks.length} available.`;
98080
99107
  render();
98081
- }, (error, cached) => workspaceLoadError(context, request, "Project Tasks", error, cached));
99108
+ }, (error, cached) => {
99109
+ if (request !== state.routeVersion || state.activeProject?.id !== id || state.projectTab !== "tasks") return;
99110
+ state.status = cached ? "Showing saved or partially synced Project Tasks. Refresh failed." : `Could not load Project Tasks: ${error instanceof Error ? error.message : String(error)}`;
99111
+ render();
99112
+ });
98082
99113
  }
98083
99114
  async function refreshOpenProject(context) {
98084
99115
  const { state, client: client2, render } = context, id = state.activeProject.id;
@@ -99043,8 +100074,8 @@ async function handleWorkspaceKey(context, chunk, key) {
99043
100074
  return true;
99044
100075
  }
99045
100076
  if (state.form) {
99046
- const form = state.form, field2 = form.fields[form.fieldIndex];
99047
- if (!field2) {
100077
+ const form = state.form, field3 = form.fields[form.fieldIndex];
100078
+ if (!field3) {
99048
100079
  if (key.name === "escape" && !form.busy) state.form = null;
99049
100080
  else if (key.name === "return" || key.ctrl && key.name === "s") await saveForm(context);
99050
100081
  render();
@@ -99054,16 +100085,16 @@ async function handleWorkspaceKey(context, chunk, key) {
99054
100085
  else if (key.ctrl && key.name === "s") await saveForm(context);
99055
100086
  else if (form.busy) return true;
99056
100087
  else if (key.name === "tab") form.fieldIndex = (form.fieldIndex + (key.shift ? -1 : 1) + form.fields.length) % form.fields.length;
99057
- else if ((key.name === "left" || key.name === "right") && field2.options) {
99058
- const index = field2.options.indexOf(field2.value);
99059
- field2.value = field2.options[(index + (key.name === "left" ? -1 : 1) + field2.options.length) % field2.options.length];
99060
- } else if (key.name === "backspace") field2.value = eraseGrapheme(field2.value);
100088
+ else if ((key.name === "left" || key.name === "right") && field3.options) {
100089
+ const index = field3.options.indexOf(field3.value);
100090
+ field3.value = field3.options[(index + (key.name === "left" ? -1 : 1) + field3.options.length) % field3.options.length];
100091
+ } else if (key.name === "backspace") field3.value = eraseGrapheme(field3.value);
99061
100092
  else if (key.name === "return") {
99062
- if (key.meta && field2.multiline) field2.value += "\n";
100093
+ if (key.meta && field3.multiline) field3.value += "\n";
99063
100094
  else if (form.fieldIndex < form.fields.length - 1) form.fieldIndex++;
99064
100095
  else await saveForm(context);
99065
- } else if (key.ctrl && key.name === "u") field2.value = "";
99066
- else if (!key.ctrl && !key.meta && chunk && !field2.options) field2.value += terminalText(chunk);
100096
+ } else if (key.ctrl && key.name === "u") field3.value = "";
100097
+ else if (!key.ctrl && !key.meta && chunk && !field3.options) field3.value += terminalText(chunk);
99067
100098
  render();
99068
100099
  return true;
99069
100100
  }
@@ -99710,7 +100741,7 @@ async function handleTuiPointer(context, action2, keyboard) {
99710
100741
  case "form-field": {
99711
100742
  const form = state.form;
99712
100743
  if (!form || form.busy) return;
99713
- index = action2.id ? form.fields.findIndex((field2) => field2.name === action2.id) : index;
100744
+ index = action2.id ? form.fields.findIndex((field3) => field3.name === action2.id) : index;
99714
100745
  if (index < 0 || !form.fields[index]) return;
99715
100746
  form.fieldIndex = index;
99716
100747
  state.focus = "content";
@@ -100170,11 +101201,18 @@ async function runTui(client2, terminal = new TuiTerminal(), startupServices) {
100170
101201
  let stopActivity;
100171
101202
  let activityTimer;
100172
101203
  let refreshingActivity = false;
101204
+ let identityScope = "";
101205
+ let identityEpoch = 0;
101206
+ let identityRetryTimer = null;
101207
+ let workspaceScope = null;
101208
+ const scopedDrafts = /* @__PURE__ */ new Map();
101209
+ let teamChatRefresh = null;
100173
101210
  let modelShell = null;
100174
101211
  const modelPreferences = createChatModelPreferences(client2);
100175
101212
  const refreshActivity = async () => {
100176
101213
  if (closed || state.startup || refreshingActivity || !state.signedIn) return;
100177
101214
  refreshingActivity = true;
101215
+ teamChatRefresh?.request();
100178
101216
  try {
100179
101217
  await refreshTuiChatSidebar(state, client2, render);
100180
101218
  } catch {
@@ -100205,6 +101243,65 @@ async function runTui(client2, terminal = new TuiTerminal(), startupServices) {
100205
101243
  const render = () => {
100206
101244
  if (closed) return;
100207
101245
  syncSessionView();
101246
+ const teamId = state.signedIn ? client2.getActiveTeamId?.() ?? null : null;
101247
+ const session = state.signedIn && typeof client2.getSession === "function" ? client2.getSession() : null;
101248
+ const nextScope = session ? JSON.stringify([session.apiUrl, session.hashedEmail, session.createdAt, session.masterKeyExportedB64, teamId]) : null;
101249
+ if (workspaceScope !== null && nextScope !== workspaceScope) {
101250
+ const endedStatus = !session && state.status === TUI_SESSION_ENDED_STATUS ? TUI_SESSION_ENDED_STATUS : null;
101251
+ identityEpoch++;
101252
+ identityScope = "";
101253
+ if (identityRetryTimer) clearTimeout(identityRetryTimer);
101254
+ identityRetryTimer = null;
101255
+ if (session) {
101256
+ rememberDraft(state);
101257
+ scopedDrafts.set(workspaceScope, { ...state.drafts });
101258
+ } else scopedDrafts.clear();
101259
+ state.homeAbortController?.abort();
101260
+ Object.values(state.chatContextAuthoringControls).forEach((control) => control.stop());
101261
+ client2.clearInteractiveChatViewer();
101262
+ const routeVersion = state.routeVersion + 1, homeLoadVersion = state.homeLoadVersion + 1;
101263
+ Object.assign(state, createInitialTuiState(), {
101264
+ signedIn: !!session,
101265
+ activeTeamId: teamId,
101266
+ drafts: nextScope ? { ...scopedDrafts.get(nextScope) ?? {} } : {},
101267
+ routeVersion,
101268
+ homeLoadVersion,
101269
+ status: endedStatus
101270
+ });
101271
+ state.input = state.drafts.new ?? "";
101272
+ if (!session) hydrateExamples(state);
101273
+ if (session) queueMicrotask(() => {
101274
+ if (closed || workspaceScope !== nextScope) return;
101275
+ void loadHomeData(state, client2, render);
101276
+ void refreshTuiChatSidebar(state, client2, render, true).catch(() => {
101277
+ });
101278
+ });
101279
+ }
101280
+ workspaceScope = nextScope;
101281
+ state.activeTeamId = teamId;
101282
+ if (teamId && state.signedIn && nextScope) {
101283
+ if (nextScope !== identityScope) {
101284
+ identityScope = nextScope;
101285
+ const epoch = ++identityEpoch;
101286
+ const loadIdentity = (attempt) => {
101287
+ void loadTuiTeamIdentity(state, client2, render, () => !closed && identityEpoch === epoch && workspaceScope === nextScope).then((result2) => {
101288
+ if (!result2 || closed || identityEpoch !== epoch || workspaceScope !== nextScope) return;
101289
+ if ((!result2.profileLoaded || !result2.teamLoaded) && attempt < 2) {
101290
+ identityRetryTimer = setTimeout(() => {
101291
+ identityRetryTimer = null;
101292
+ loadIdentity(attempt + 1);
101293
+ }, attempt === 0 ? 1e3 : 4e3);
101294
+ }
101295
+ });
101296
+ };
101297
+ loadIdentity(0);
101298
+ }
101299
+ } else {
101300
+ identityScope = "";
101301
+ identityEpoch++;
101302
+ if (identityRetryTimer) clearTimeout(identityRetryTimer);
101303
+ identityRetryTimer = null;
101304
+ }
100208
101305
  modelShell?.sync();
100209
101306
  if (state.screen !== "chat") client2.clearInteractiveChatViewer();
100210
101307
  if (renderTimer) return;
@@ -100215,6 +101312,7 @@ async function runTui(client2, terminal = new TuiTerminal(), startupServices) {
100215
101312
  terminal.render(renderTuiFrame(state, terminal.width, terminal.height, { colorMode: terminal.colorMode, ascii: terminal.ascii }), tuiComposerCursor(state, terminal.width, terminal.height), state.textSelection);
100216
101313
  }, 16);
100217
101314
  };
101315
+ teamChatRefresh = createTuiTeamChatRefresh(state, client2, render);
100218
101316
  modelShell = createTuiModelSelectorShell({ state, client: client2, preferences: modelPreferences, render });
100219
101317
  const stopHomeSync = startHomeSync(state, client2, render, () => closed);
100220
101318
  const finish = (result2) => {
@@ -100223,9 +101321,11 @@ async function runTui(client2, terminal = new TuiTerminal(), startupServices) {
100223
101321
  Object.values(state.chatContextAuthoringControls).forEach((control) => control.stop());
100224
101322
  stopHomeSync();
100225
101323
  stopActivity?.();
101324
+ teamChatRefresh?.dispose();
100226
101325
  clearTimeout(activityTimer);
100227
101326
  clearInterval(activityPoll);
100228
101327
  clearInterval(activityAnimation);
101328
+ if (identityRetryTimer) clearTimeout(identityRetryTimer);
100229
101329
  modelShell?.dispose();
100230
101330
  client2.endInteractiveViewerSession();
100231
101331
  if (renderTimer) clearTimeout(renderTimer);
@@ -100248,6 +101348,7 @@ async function runTui(client2, terminal = new TuiTerminal(), startupServices) {
100248
101348
  warmTuiWorkspaceLists(client2);
100249
101349
  if (state.signedIn && typeof client2.observeChatActivity === "function") {
100250
101350
  void client2.observeChatActivity(() => {
101351
+ teamChatRefresh?.request();
100251
101352
  clearTimeout(activityTimer);
100252
101353
  activityTimer = setTimeout(() => {
100253
101354
  void refreshActivity();
@@ -100637,49 +101738,63 @@ async function handleCommand(params) {
100637
101738
  async function sendTuiMessage(params) {
100638
101739
  const { state, client: client2, render } = params;
100639
101740
  if (state.isBusy) return;
101741
+ const preparingOwner = client2.hasSession() ? captureTuiWorkspaceOwner(client2) : () => !client2.hasSession();
101742
+ const preparingRoute = state.routeVersion;
101743
+ const preparingMessages = state.messages;
101744
+ const preparingChatId = state.activeChatId;
101745
+ const ownsPreparation = () => preparingOwner() && state.routeVersion === preparingRoute && state.messages === preparingMessages && state.activeChatId === preparingChatId;
100640
101746
  const resolved = !params.questionAnswer && params.modelShell ? await params.modelShell.consumeMention(params.message, true) : { message: params.message, blocked: false };
100641
- if (resolved.blocked) return;
101747
+ if (resolved.blocked || !ownsPreparation() || state.isBusy) return;
100642
101748
  const message = resolved.message.trim();
100643
101749
  if (!message) return;
100644
- const modelSelection = !params.questionAnswer && isTuiAiComposer(state) && client2.hasSession() ? params.modelShell?.selectionForSend() ?? null : "auto";
100645
- if (modelSelection === null) {
100646
- state.status = "Model selection is loading. Retry or choose Auto before sending.";
100647
- render();
100648
- return;
100649
- }
100650
- const separator = modelSelection.indexOf("/");
100651
- const modelDirective = separator > 0 ? `@ai-model:${modelSelection.slice(separator + 1)}:${modelSelection.slice(0, separator)} ` : "";
100652
101750
  client2.clearInteractiveChatViewer();
100653
101751
  state.isBusy = true;
100654
101752
  state.status = "Preparing message\u2026";
100655
101753
  render();
100656
- const preparingRoute = state.routeVersion;
101754
+ if (!ownsPreparation()) return;
100657
101755
  let prepared;
100658
101756
  try {
100659
101757
  prepared = params.questionAnswer ? { message, preparedEmbeds: [], displayNames: [] } : await prepareTuiMessage(client2, message);
100660
101758
  } catch (error) {
100661
- state.isBusy = false;
100662
- if (preparingRoute === state.routeVersion) {
101759
+ if (ownsPreparation()) {
101760
+ state.isBusy = false;
101761
+ if (client2.getActiveTeamId?.()) reconcileTuiTeamSenderLabels(state);
100663
101762
  state.input = message;
100664
101763
  state.inputCursor = null;
100665
101764
  rememberDraft(state);
100666
101765
  state.status = error instanceof Error ? error.message : String(error);
101766
+ render();
100667
101767
  }
100668
- render();
100669
101768
  return;
100670
101769
  }
100671
- if (preparingRoute !== state.routeVersion) {
100672
- state.isBusy = false;
100673
- render();
101770
+ if (!ownsPreparation()) return;
101771
+ const sendTeamId = client2.getActiveTeamId?.() ?? null;
101772
+ const waitForAi = !client2.hasSession() || shouldWaitForTeamAi(prepared.message, sendTeamId);
101773
+ const modelSelection = waitForAi && !params.questionAnswer && isTuiAiComposer(state) && client2.hasSession() ? params.modelShell?.selectionForSend() ?? null : "auto";
101774
+ if (modelSelection === null) {
101775
+ if (ownsPreparation()) {
101776
+ state.isBusy = false;
101777
+ state.input = message;
101778
+ state.inputCursor = null;
101779
+ rememberDraft(state);
101780
+ if (sendTeamId) reconcileTuiTeamSenderLabels(state);
101781
+ state.status = "Model selection is loading. Retry or choose Auto before sending.";
101782
+ render();
101783
+ }
100674
101784
  return;
100675
101785
  }
101786
+ const separator = modelSelection.indexOf("/");
101787
+ const modelDirective = separator > 0 ? `@ai-model:${modelSelection.slice(separator + 1)}:${modelSelection.slice(0, separator)} ` : "";
100676
101788
  const previousMessages = state.messages, previousActiveChat = state.activeChat, previousHeaderState = state.headerState;
101789
+ const previousScreen = state.screen;
100677
101790
  const sourceExample = state.screen === "example" ? state.activeExample : null;
100678
101791
  const history = sourceExample ? buildExampleContinuationHistory(sourceExample) : void 0;
100679
101792
  if (sourceExample) state.messages = sourceExample.messages.map((m) => ({ role: m.role === "user" ? "user" : "assistant", content: m.content, title: m.senderName }));
100680
101793
  const messages = state.messages;
100681
101794
  const existingChatId = sourceExample ? null : state.activeChatId;
100682
101795
  const chatId = existingChatId ?? randomUUID21();
101796
+ let ownedMessages = messages, ownedChatId = chatId;
101797
+ const ownsSend = () => preparingOwner() && state.routeVersion === preparingRoute && state.messages === ownedMessages && state.activeChatId === ownedChatId;
100683
101798
  const draftKey = sourceExample ? `example:${sourceExample.chat.id}` : existingChatId ?? "new";
100684
101799
  state.drafts[draftKey] = "";
100685
101800
  state.input = "";
@@ -100688,7 +101803,7 @@ async function sendTuiMessage(params) {
100688
101803
  state.activeChatId = chatId;
100689
101804
  state.headerState = existingChatId ? "ready" : "loading";
100690
101805
  state.headerError = null;
100691
- if (!existingChatId) params.modelShell?.adoptNewChat(chatId);
101806
+ if (!existingChatId && waitForAi) params.modelShell?.adoptNewChat(chatId);
100692
101807
  if (!existingChatId) state.activeChat = null;
100693
101808
  state.screen = "chat";
100694
101809
  state.aiTaskId = null;
@@ -100697,18 +101812,21 @@ async function sendTuiMessage(params) {
100697
101812
  state.messages.push(userMessage);
100698
101813
  const privacyCallbacks = {
100699
101814
  onPrivacyPrepared: (safe2) => {
100700
- userMessage.content = safe2.replace(/^@ai-model:[^\s]+\s*/, "");
100701
- render();
101815
+ if (ownsSend()) {
101816
+ userMessage.content = safe2.replace(/^@ai-model:[^\s]+\s*/, "");
101817
+ render();
101818
+ }
100702
101819
  },
100703
101820
  onPrivacyProgress: (done, total) => {
100704
- if (state.messages === messages) {
101821
+ if (ownsSend()) {
100705
101822
  state.status = `Offline personal-data scan: ${Math.floor(done * 100 / total)}%`;
100706
101823
  render();
100707
101824
  }
100708
101825
  }
100709
101826
  };
100710
- const assistantMessage = { role: "assistant", content: "", title: "Assistant" };
100711
- state.messages.push(assistantMessage);
101827
+ const assistantMessage = waitForAi ? { role: "assistant", content: "", title: "Assistant" } : null;
101828
+ if (assistantMessage) state.messages.push(assistantMessage);
101829
+ state.isAwaitingAi = waitForAi;
100712
101830
  render();
100713
101831
  let questionSendError = null;
100714
101832
  try {
@@ -100718,12 +101836,13 @@ async function sendTuiMessage(params) {
100718
101836
  ...privacyCallbacks,
100719
101837
  messageHistory: anonymousHistory
100720
101838
  });
100721
- assistantMessage.content = result.assistant;
100722
- if (state.messages === messages) {
101839
+ if (assistantMessage) assistantMessage.content = result.assistant;
101840
+ if (ownsSend()) {
101841
+ ownedChatId = result.chatId;
100723
101842
  state.activeChatId = result.chatId;
100724
101843
  state.activeChat = { id: result.chatId, shortId: result.chatId.slice(0, 8), title: null, summary: null, updatedAt: null, createdAt: Math.floor(Date.now() / 1e3), category: result.category, mateName: result.mateName };
100725
101844
  state.followUpSuggestions = result.followUpSuggestions ?? [];
100726
- if (result.mateName) assistantMessage.title = result.mateName;
101845
+ if (result.mateName && assistantMessage) assistantMessage.title = result.mateName;
100727
101846
  }
100728
101847
  } else {
100729
101848
  const result = await client2.sendMessage({
@@ -100737,36 +101856,39 @@ async function sendTuiMessage(params) {
100737
101856
  preparedEmbeds: prepared.preparedEmbeds,
100738
101857
  messageHistory: history,
100739
101858
  onProjectFocusPending: (countdown) => {
100740
- if (state.messages === messages) {
101859
+ if (ownsSend()) {
100741
101860
  state.projectFocusPending = countdown;
100742
101861
  render();
100743
101862
  }
100744
101863
  },
100745
101864
  onChatContextApplied: (event) => {
100746
- if (state.messages !== messages || messages.some((message2) => message2.id === event.event_id || message2.role === "system" && parseChatContextContent(message2.content)?.event_id === event.event_id)) return;
101865
+ if (!ownsSend() || messages.some((message2) => message2.id === event.event_id || message2.role === "system" && parseChatContextContent(message2.content)?.event_id === event.event_id)) return;
100747
101866
  messages.push({ id: event.event_id, role: "system", content: JSON.stringify(event) });
100748
101867
  render();
100749
101868
  },
100750
101869
  onStream: (event) => {
100751
- if (state.messages === messages && event.taskId) state.aiTaskId = event.taskId;
100752
- if (event.kind === "chunk" || event.kind === "done") {
101870
+ if (!ownsSend()) return;
101871
+ if (event.taskId) state.aiTaskId = event.taskId;
101872
+ if (assistantMessage && (event.kind === "chunk" || event.kind === "done")) {
100753
101873
  assistantMessage.content = event.content;
100754
- if (state.messages === messages && event.category) state.activeChat = { id: chatId, shortId: chatId.slice(0, 8), title: state.activeChat?.title ?? null, summary: state.activeChat?.summary ?? null, updatedAt: null, createdAt: state.activeChat?.createdAt ?? Math.floor(Date.now() / 1e3), category: event.category, mateName: null };
101874
+ if (event.category) state.activeChat = { id: chatId, shortId: chatId.slice(0, 8), title: state.activeChat?.title ?? null, summary: state.activeChat?.summary ?? null, updatedAt: null, createdAt: state.activeChat?.createdAt ?? Math.floor(Date.now() / 1e3), category: event.category, mateName: null };
100755
101875
  render();
100756
101876
  }
100757
101877
  }
100758
101878
  });
100759
- assistantMessage.content = result.assistant;
100760
- if (state.messages === messages) {
101879
+ if (assistantMessage) assistantMessage.content = result.assistant;
101880
+ if (ownsSend()) {
101881
+ ownedChatId = result.chatId;
100761
101882
  state.activeChatId = result.chatId;
100762
101883
  state.followUpSuggestions = result.followUpSuggestions ?? [];
101884
+ if (result.userMessageId) userMessage.id = result.userMessageId;
100763
101885
  if (!existingChatId) void params.modelShell?.persistCreatedChat(result.chatId, modelSelection);
100764
101886
  if (state.screen === "chat") void client2.setInteractiveChatViewer(result.chatId).catch(() => {
100765
101887
  });
100766
- if (result.mateName) assistantMessage.title = result.mateName;
101888
+ if (result.mateName && assistantMessage) assistantMessage.title = result.mateName;
100767
101889
  if (typeof client2.getChatMetadata === "function") {
100768
101890
  const completedLength = messages.length;
100769
- const ownsMetadata = () => state.routeVersion === preparingRoute && state.messages === messages && state.activeChatId === result.chatId && messages.length === completedLength;
101891
+ const ownsMetadata = () => ownsSend() && state.activeChatId === result.chatId && messages.length === completedLength;
100770
101892
  void (async () => {
100771
101893
  try {
100772
101894
  const metadata = await client2.getChatMetadata(result.chatId);
@@ -100782,40 +101904,58 @@ async function sendTuiMessage(params) {
100782
101904
  }
100783
101905
  }
100784
101906
  }
100785
- if (state.messages === messages) {
101907
+ if (ownsSend()) {
100786
101908
  state.status = null;
100787
101909
  state.headerState = "ready";
100788
101910
  }
100789
101911
  } catch (error) {
100790
101912
  if (params.questionAnswer) {
100791
101913
  questionSendError = error instanceof Error ? error : new Error(String(error));
100792
- for (const optimistic of [userMessage, assistantMessage]) {
101914
+ for (const optimistic of [userMessage, assistantMessage].filter((item) => item !== null)) {
100793
101915
  const index = messages.indexOf(optimistic);
100794
101916
  if (index >= 0) messages.splice(index, 1);
100795
101917
  }
100796
- if (state.messages === messages && state.routeVersion === preparingRoute) {
101918
+ if (ownsSend()) {
100797
101919
  state.messages = previousMessages;
100798
101920
  state.activeChatId = existingChatId;
100799
101921
  state.activeChat = previousActiveChat;
101922
+ ownedMessages = previousMessages;
101923
+ ownedChatId = existingChatId;
100800
101924
  state.headerState = previousHeaderState;
100801
101925
  state.screen = sourceExample ? "example" : "chat";
100802
101926
  state.status = null;
100803
101927
  }
100804
- } else {
101928
+ } else if (assistantMessage) {
100805
101929
  assistantMessage.title = "Error";
100806
101930
  assistantMessage.content = error instanceof Error ? error.message : String(error);
100807
- if (state.messages === messages) {
101931
+ if (ownsSend()) {
100808
101932
  state.headerState = "error";
100809
101933
  state.headerError = /credit/i.test(assistantMessage.content) ? "Not enough credits" : assistantMessage.content;
100810
101934
  }
101935
+ } else if (ownsSend()) {
101936
+ state.messages = previousMessages;
101937
+ state.activeChatId = existingChatId;
101938
+ state.activeChat = previousActiveChat;
101939
+ ownedMessages = previousMessages;
101940
+ ownedChatId = existingChatId;
101941
+ state.headerState = previousHeaderState;
101942
+ state.screen = previousScreen;
101943
+ state.input = message;
101944
+ state.inputCursor = null;
101945
+ rememberDraft(state);
101946
+ state.status = error instanceof Error ? error.message : String(error);
100811
101947
  }
100812
101948
  } finally {
100813
- state.isBusy = false;
100814
- state.projectFocusPending = null;
100815
- state.aiTaskId = null;
100816
- registerChatEmbedAliases(state);
100817
- if (state.screen === "chat" && typeof client2.getEmbed === "function") void hydrateChatEmbedPreviews(state, client2, render);
100818
- render();
101949
+ if (ownsSend()) {
101950
+ state.isBusy = false;
101951
+ if (sendTeamId) reconcileTuiTeamSenderLabels(state);
101952
+ state.isAwaitingAi = false;
101953
+ state.projectFocusPending = null;
101954
+ state.aiTaskId = null;
101955
+ registerChatEmbedAliases(state);
101956
+ if (state.screen === "chat" && typeof client2.getEmbed === "function") void hydrateChatEmbedPreviews(state, client2, render);
101957
+ render();
101958
+ }
100819
101959
  }
100820
101960
  if (questionSendError) throw questionSendError;
100821
101961
  }
@@ -100901,13 +102041,8 @@ function warmTuiWorkspaceLists(client2) {
100901
102041
  void Promise.allSettled([
100902
102042
  loadCachedTuiWorkspace(client2, "projects:list", () => loadTuiProjects(client2), () => {
100903
102043
  }),
100904
- loadCachedTuiWorkspace(
100905
- client2,
100906
- tasksListCacheKey,
100907
- async () => decryptUserTasks(await client2.listUserTasks(), client2.getMasterKeyBytes()),
100908
- () => {
100909
- }
100910
- ),
102044
+ loadTuiTaskList(client2, tasksListCacheKey, {}, () => {
102045
+ }),
100911
102046
  loadCachedTuiWorkspace(client2, workflowsListCacheKey, () => client2.listWorkflows(), () => {
100912
102047
  })
100913
102048
  ]);
@@ -100955,33 +102090,31 @@ async function openTaskList(params) {
100955
102090
  state.screen = "status";
100956
102091
  state.status = "Loading tasks...";
100957
102092
  render();
100958
- const current2 = () => state.routeVersion === routeVersion && (state.screen === "status" || state.screen === "tasks");
100959
- await loadCachedTuiWorkspace(
100960
- client2,
100961
- tasksListCacheKey,
100962
- async () => decryptUserTasks(await client2.listUserTasks(), client2.getMasterKeyBytes()),
100963
- (tasks) => {
100964
- if (!current2()) return;
100965
- const opening = state.screen === "status";
100966
- const selectedId = opening ? null : state.tasks[state.selectedIndex]?.taskId;
100967
- state.tasks = tasks;
100968
- const selectedIndex = selectedId ? tasks.findIndex((task) => task.taskId === selectedId) : -1;
100969
- state.selectedIndex = selectedIndex >= 0 ? selectedIndex : clamp(state.selectedIndex, 0, Math.max(0, tasks.length - 1));
100970
- if (opening) {
100971
- state.scrollOffset = 0;
100972
- state.activeTask = null;
100973
- }
100974
- state.status = null;
100975
- state.screen = "tasks";
100976
- render();
100977
- },
100978
- (error, hasCached) => {
100979
- if (!current2()) return;
100980
- state.status = hasCached ? "Showing cached tasks. Refresh failed." : workflowError(error, "Could not load tasks. Use /login first if you are not signed in.");
100981
- if (!hasCached) state.screen = "status";
100982
- render();
102093
+ const ownerCurrent = captureTuiWorkspaceOwner(client2);
102094
+ const current2 = () => ownerCurrent() && state.routeVersion === routeVersion && (state.screen === "status" || state.screen === "tasks");
102095
+ await loadTuiTaskList(client2, tasksListCacheKey, {}, (tasks, source, complete) => {
102096
+ if (!current2()) return;
102097
+ const opening = state.screen === "status";
102098
+ const selectedId = opening ? null : state.tasks[state.selectedIndex]?.taskId;
102099
+ state.tasks = tasks;
102100
+ const selectedIndex = selectedId ? tasks.findIndex((task) => task.taskId === selectedId) : -1;
102101
+ state.selectedIndex = selectedIndex >= 0 ? selectedIndex : clamp(state.selectedIndex, 0, Math.max(0, tasks.length - 1));
102102
+ if (opening) {
102103
+ state.scrollOffset = 0;
102104
+ state.activeTask = null;
100983
102105
  }
100984
- );
102106
+ state.status = complete ? null : source === "cache" ? "Showing saved tasks. Syncing\u2026" : `Loading more tasks\u2026 ${tasks.length} available.`;
102107
+ state.screen = "tasks";
102108
+ render();
102109
+ }, (error, hasCached) => {
102110
+ if (!current2()) return;
102111
+ state.status = hasCached ? "Showing saved or partially synced tasks. Refresh failed." : workflowError(
102112
+ error,
102113
+ client2.hasSession() ? "Could not load tasks. Use /refresh to retry." : "Could not load tasks. Use /login first."
102114
+ );
102115
+ if (!hasCached) state.screen = "status";
102116
+ render();
102117
+ });
100985
102118
  }
100986
102119
  var workflowOpenRequests = /* @__PURE__ */ new WeakMap();
100987
102120
  function nextWorkflowOpenRequest(state) {
@@ -101151,7 +102284,7 @@ async function saveWorkflowNodeTitle(params) {
101151
102284
  }
101152
102285
  render();
101153
102286
  }
101154
- function startWorkflowNodeEdit(state, field2) {
102287
+ function startWorkflowNodeEdit(state, field3) {
101155
102288
  const workflow = state.activeWorkflow;
101156
102289
  if (!workflow || state.workflowTab !== "graph") {
101157
102290
  state.status = "Switch to the Graph tab before editing node details.";
@@ -101161,11 +102294,11 @@ function startWorkflowNodeEdit(state, field2) {
101161
102294
  if (!node) return;
101162
102295
  state.workflowEdit = {
101163
102296
  nodeId: node.id,
101164
- field: field2,
101165
- value: field2 === "config" ? JSON.stringify(node.config ?? {}) : node.title ?? ""
102297
+ field: field3,
102298
+ value: field3 === "config" ? JSON.stringify(node.config ?? {}) : node.title ?? ""
101166
102299
  };
101167
102300
  state.expandedWorkflowNodeId = node.id;
101168
- state.status = `Editing ${field2} for ${node.id}.`;
102301
+ state.status = `Editing ${field3} for ${node.id}.`;
101169
102302
  }
101170
102303
  async function runActiveWorkflow(params) {
101171
102304
  const { state, client: client2, render } = params;
@@ -101249,7 +102382,7 @@ function clamp(value, min, max) {
101249
102382
  }
101250
102383
 
101251
102384
  // src/codexTaskBridge.ts
101252
- import { createHash as createHash25 } from "crypto";
102385
+ import { createHash as createHash26 } from "crypto";
101253
102386
  import { spawn as spawn3 } from "child_process";
101254
102387
  import { existsSync as existsSync14, readFileSync as readFileSync18, readdirSync as readdirSync7 } from "fs";
101255
102388
  import { isAbsolute as isAbsolute4, join as join20, resolve as resolve12 } from "path";
@@ -101292,7 +102425,7 @@ async function flushCodexChatDeletions(client2, teamId, stateRoot, notice) {
101292
102425
  const session = client2.getSession();
101293
102426
  const scope2 = [session.apiUrl, session.hashedEmail, teamId || "personal"];
101294
102427
  const account = JSON.stringify(scope2);
101295
- const directory = join20(stateRoot, "codex-chat-deletions", createHash25("sha256").update(account).digest("hex"));
102428
+ const directory = join20(stateRoot, "codex-chat-deletions", createHash26("sha256").update(account).digest("hex"));
101296
102429
  if (!existsSync14(directory)) return;
101297
102430
  const release2 = await lockfile6.lock(directory, { retries: 0, stale: 24e4 });
101298
102431
  try {
@@ -101326,14 +102459,14 @@ async function flushCodexChatDeletions(client2, teamId, stateRoot, notice) {
101326
102459
  }
101327
102460
 
101328
102461
  // src/taskCommandDelivery.ts
101329
- import { createHash as createHash26 } from "crypto";
102462
+ import { createHash as createHash27 } from "crypto";
101330
102463
  import { existsSync as existsSync15, readFileSync as readFileSync19, readdirSync as readdirSync8 } from "fs";
101331
102464
  import { join as join21 } from "path";
101332
102465
  import lockfile7 from "proper-lockfile";
101333
102466
  async function flushTaskCommands(client2, teamId, root = resolveStateDir(), onNotice) {
101334
102467
  const session = client2.getSession();
101335
102468
  const scope2 = [session.apiUrl, session.hashedEmail, teamId || "personal"];
101336
- const directory = join21(root, "task-command-delivery", createHash26("sha256").update(JSON.stringify(scope2)).digest("hex"));
102469
+ const directory = join21(root, "task-command-delivery", createHash27("sha256").update(JSON.stringify(scope2)).digest("hex"));
101337
102470
  if (!existsSync15(directory)) return;
101338
102471
  const release2 = await lockfile7.lock(directory, { retries: 0, stale: 24e4 });
101339
102472
  try {
@@ -101443,7 +102576,7 @@ import {
101443
102576
  writeFileSync as writeFileSync16
101444
102577
  } from "fs";
101445
102578
  import { spawn as spawn7 } from "child_process";
101446
- import { createHash as createHash32 } from "crypto";
102579
+ import { createHash as createHash33 } from "crypto";
101447
102580
  import { extname as extname4, join as join28, resolve as resolve18, relative as relative11 } from "path";
101448
102581
 
101449
102582
  // src/projectSourceRootPolicy.ts
@@ -101478,7 +102611,7 @@ function isEqualOrInside(parent, candidate) {
101478
102611
  }
101479
102612
 
101480
102613
  // src/projectPathPolicy.ts
101481
- import { createHash as createHash27, randomBytes as randomBytes11 } from "crypto";
102614
+ import { createHash as createHash28, randomBytes as randomBytes11 } from "crypto";
101482
102615
  import {
101483
102616
  chmodSync as chmodSync10,
101484
102617
  closeSync as closeSync6,
@@ -101759,7 +102892,7 @@ function isInside2(root, candidate) {
101759
102892
  return relation === "" || !relation.startsWith("..") && !isAbsolute5(relation);
101760
102893
  }
101761
102894
  function sha2562(value) {
101762
- return createHash27("sha256").update(value).digest("hex");
102895
+ return createHash28("sha256").update(value).digest("hex");
101763
102896
  }
101764
102897
  function stableJson2(value) {
101765
102898
  return JSON.stringify(value, (_key, item) => {
@@ -101769,7 +102902,7 @@ function stableJson2(value) {
101769
102902
  }
101770
102903
 
101771
102904
  // src/remoteFileWrites.ts
101772
- import { createHash as createHash28, randomBytes as randomBytes12 } from "crypto";
102905
+ import { createHash as createHash29, randomBytes as randomBytes12 } from "crypto";
101773
102906
  import {
101774
102907
  chmodSync as chmodSync11,
101775
102908
  closeSync as closeSync8,
@@ -102034,7 +103167,7 @@ var MAX_FILE_BYTES4 = 200 * 1024;
102034
103167
  var LOCK_ROOT = join24(tmpdir5(), `openmates-remote-file-locks-${typeof process.getuid === "function" ? process.getuid() : "user"}`);
102035
103168
  var DEFAULT_JOURNAL_ROOT = join24(tmpdir5(), `openmates-remote-file-journal-${typeof process.getuid === "function" ? process.getuid() : "user"}`);
102036
103169
  function sha256ProjectFile(content) {
102037
- return createHash28("sha256").update(content).digest("hex");
103170
+ return createHash29("sha256").update(content).digest("hex");
102038
103171
  }
102039
103172
  async function executeRemoteFileMutation(options) {
102040
103173
  const mutation = validateMutation(options.mutation);
@@ -102722,13 +103855,13 @@ import { existsSync as existsSync22, realpathSync as realpathSync11, statSync as
102722
103855
  import { basename as basename9, dirname as dirname16, relative as relative10, resolve as resolve17 } from "path";
102723
103856
 
102724
103857
  // src/remoteCommandRuntime.ts
102725
- import { createHash as createHash30, randomUUID as randomUUID22 } from "crypto";
103858
+ import { createHash as createHash31, randomUUID as randomUUID22 } from "crypto";
102726
103859
  import { spawn as spawn6, spawnSync as spawnSync5 } from "child_process";
102727
103860
  import { existsSync as existsSync20, lstatSync as lstatSync10, readdirSync as readdirSync11, realpathSync as realpathSync8, statSync as statSync8 } from "fs";
102728
103861
  import { isAbsolute as isAbsolute8, join as join25, relative as relative7, resolve as resolve16, sep } from "path";
102729
103862
 
102730
103863
  // src/remoteCommandAppArmor.ts
102731
- import { createHash as createHash29 } from "crypto";
103864
+ import { createHash as createHash30 } from "crypto";
102732
103865
  import { spawn as spawn5 } from "child_process";
102733
103866
  import { lstatSync as lstatSync9, readFileSync as readFileSync21, realpathSync as realpathSync7, statSync as statSync7 } from "fs";
102734
103867
  import { isAbsolute as isAbsolute7, relative as relative6 } from "path";
@@ -102945,7 +104078,7 @@ function hasControl2(value) {
102945
104078
  });
102946
104079
  }
102947
104080
  function sha2563(value) {
102948
- return createHash29("sha256").update(value).digest("hex");
104081
+ return createHash30("sha256").update(value).digest("hex");
102949
104082
  }
102950
104083
 
102951
104084
  // src/remoteCommandRuntime.ts
@@ -103786,7 +104919,7 @@ function stableJson4(value) {
103786
104919
  return JSON.stringify(value);
103787
104920
  }
103788
104921
  function sha2564(value) {
103789
- return createHash30("sha256").update(value).digest("hex");
104922
+ return createHash31("sha256").update(value).digest("hex");
103790
104923
  }
103791
104924
  function deepFreeze(value) {
103792
104925
  if (value && typeof value === "object") {
@@ -104064,7 +105197,7 @@ function capabilityError(message) {
104064
105197
  }
104065
105198
 
104066
105199
  // src/remoteCommandResourceGrants.ts
104067
- import { createHash as createHash31 } from "crypto";
105200
+ import { createHash as createHash32 } from "crypto";
104068
105201
  import { chmodSync as chmodSync13, existsSync as existsSync21, lstatSync as lstatSync11, mkdirSync as mkdirSync21, readFileSync as readFileSync22, realpathSync as realpathSync10, renameSync as renameSync12, statSync as statSync10, writeFileSync as writeFileSync15 } from "fs";
104069
105202
  import { isAbsolute as isAbsolute10, join as join27, relative as relative9 } from "path";
104070
105203
  var STORE_FILE = "remote-command-resource-grants.json";
@@ -104159,7 +105292,7 @@ function resolveRemoteCommandResourceGrants(context) {
104159
105292
  return { writable_targets, network_profiles, credential_profiles };
104160
105293
  }
104161
105294
  function definitionDigest(kind, definition) {
104162
- return createHash31("sha256").update(JSON.stringify(["openmates-command-resource-v1", kind, definition])).digest("hex");
105295
+ return createHash32("sha256").update(JSON.stringify(["openmates-command-resource-v1", kind, definition])).digest("hex");
104163
105296
  }
104164
105297
  function upsert(grant) {
104165
105298
  validateIdentity(grant.project_id, grant.source_id, grant.profile_id);
@@ -105329,7 +106462,7 @@ function readRemoteAccessTextFile(options) {
105329
106462
  truncated,
105330
106463
  sizeBytes: stats.size,
105331
106464
  lineCount: countTextLines(content),
105332
- expected_base: truncated ? null : createHash32("sha256").update(bytes2.subarray(0, bytesRead)).digest("hex")
106465
+ expected_base: truncated ? null : createHash33("sha256").update(bytes2.subarray(0, bytesRead)).digest("hex")
105333
106466
  };
105334
106467
  } catch (error) {
105335
106468
  const code = error.code;
@@ -105379,7 +106512,7 @@ function readRemoteAccessImageChunk(options) {
105379
106512
  mime_type: mimeType,
105380
106513
  size_bytes: stats.size,
105381
106514
  offset: options.offset,
105382
- content_hash: createHash32("sha256").update(bytes2).digest("hex")
106515
+ content_hash: createHash33("sha256").update(bytes2).digest("hex")
105383
106516
  };
105384
106517
  } catch (error) {
105385
106518
  if (error.code === "ELOOP") throw new Error("Remote source path is a symbolic link");
@@ -105427,8 +106560,8 @@ function readRemoteAccessFileChunk(options) {
105427
106560
  content_base64: bytes2.toString("base64"),
105428
106561
  size_bytes: stats.size,
105429
106562
  offset: options.offset,
105430
- file_identity: createHash32("sha256").update(`${stats.dev}:${stats.ino}:${stats.size}:${stats.mtimeMs}:${stats.ctimeMs}`).digest("hex"),
105431
- chunk_hash: createHash32("sha256").update(bytes2).digest("hex")
106563
+ file_identity: createHash33("sha256").update(`${stats.dev}:${stats.ino}:${stats.size}:${stats.mtimeMs}:${stats.ctimeMs}`).digest("hex"),
106564
+ chunk_hash: createHash33("sha256").update(bytes2).digest("hex")
105432
106565
  };
105433
106566
  } catch (error) {
105434
106567
  if (error.code === "ELOOP") throw new Error("Remote source path is a symbolic link");
@@ -106702,8 +107835,8 @@ function validateWorkRecoveryDocument(value) {
106702
107835
  const document = value;
106703
107836
  if (document.schema_version !== 1) throw new Error("Unsupported work recovery schema_version.");
106704
107837
  if (!document.project?.project_id) throw new Error("Work recovery document requires project.project_id.");
106705
- for (const field2 of ["plans", "tasks", "dependencies", "assumptions", "revisions"]) {
106706
- if (!Array.isArray(document[field2])) throw new Error(`Work recovery document requires ${field2}.`);
107838
+ for (const field3 of ["plans", "tasks", "dependencies", "assumptions", "revisions"]) {
107839
+ if (!Array.isArray(document[field3])) throw new Error(`Work recovery document requires ${field3}.`);
106707
107840
  }
106708
107841
  const plans = document.plans;
106709
107842
  const tasks = document.tasks;
@@ -108684,7 +109817,7 @@ async function handlePlans(client2, subcommand, rest, flags, redactor) {
108684
109817
  if (action2 === "diff") throw new Error("Revision diffs are reviewed in the authenticated web review page; use submit-for-review to print its URL.");
108685
109818
  if (action2 !== "submit-for-review") throw new Error("Usage: openmates plans revisions list|submit-for-review|status|diff <plan-id>");
108686
109819
  const snapshot2 = JSON.stringify(planToJson(plan));
108687
- const revision = await client2.submitPlanRevision(plan.planId, { fingerprint: createHash33("sha256").update(snapshot2).digest("hex"), encrypted_snapshot: await encryptPlanAssumptionSources(plan, masterKey, snapshot2), created_at: nowSeconds2() });
109820
+ const revision = await client2.submitPlanRevision(plan.planId, { fingerprint: createHash34("sha256").update(snapshot2).digest("hex"), encrypted_snapshot: await encryptPlanAssumptionSources(plan, masterKey, snapshot2), created_at: nowSeconds2() });
108688
109821
  const reviewUrl = `${deriveAppUrl(client2.apiUrl)}/plans/${plan.planId}/review?revision=${revision.revision_id}`;
108689
109822
  if (flags.json === true) printJson2({ revision, review_url: reviewUrl });
108690
109823
  else console.log(`Plan revision submitted for web review: ${reviewUrl}`);
@@ -109604,7 +110737,7 @@ async function handleProjects(client2, subcommand, rest, flags, redactor) {
109604
110737
  encrypted_settings: await encryptWithAesGcmCombined(JSON.stringify({ default_focus: defaultFocus }), projectKey),
109605
110738
  key_wrappers: wrapping.teamId ? [{
109606
110739
  key_type: "team",
109607
- hashed_team_id: createHash33("sha256").update(wrapping.teamId).digest("hex"),
110740
+ hashed_team_id: createHash34("sha256").update(wrapping.teamId).digest("hex"),
109608
110741
  team_key_epoch: 1,
109609
110742
  encrypted_project_key: await encryptBytesWithAesGcm(projectKey, wrapping.key),
109610
110743
  wrapper_version: 1,
@@ -110208,6 +111341,17 @@ function buyerAddressFromFlags(flags) {
110208
111341
  if (!/^[a-zA-Z]{2}$/.test(String(address.country))) throw new Error("Billing address country must be a two-letter country code.");
110209
111342
  return { ...address, country: String(address.country).toUpperCase() };
110210
111343
  }
111344
+ function publicTeamMember(member) {
111345
+ return {
111346
+ user_id: member.user_id,
111347
+ hashed_user_id: member.hashed_user_id,
111348
+ display_name: member.profile?.display_name ?? "",
111349
+ avatar: member.profile?.avatar ?? null,
111350
+ role: member.role,
111351
+ status: member.status ?? "",
111352
+ joined_at: member.joined_at ?? null
111353
+ };
111354
+ }
110211
111355
  async function handleTeams(client2, subcommand, rest, flags) {
110212
111356
  if (!subcommand || subcommand === "help" || flags.help === true) {
110213
111357
  printTeamsHelp();
@@ -110237,6 +111381,49 @@ async function handleTeams(client2, subcommand, rest, flags) {
110237
111381
  else printTeamRecord(team, client2.getActiveTeamId());
110238
111382
  return;
110239
111383
  }
111384
+ if (subcommand === "members") {
111385
+ const action2 = rest[0];
111386
+ const teamId = requiredStringFlag(rest[1], "<team-id>");
111387
+ if (action2 === "list") {
111388
+ const members = (await client2.listTeamMembers(teamId)).map(publicTeamMember);
111389
+ if (flags.json === true) printJson2({ members });
111390
+ else if (!members.length) console.log("No Team members.");
111391
+ else for (const member of members) console.log(`${member.display_name || member.user_id} ${member.role} ${member.status || ""} ${member.user_id}`.trim());
111392
+ return;
111393
+ }
111394
+ if (action2 === "show") {
111395
+ const userId = requiredStringFlag(rest[2], "<member-user-id>");
111396
+ const member = publicTeamMember(await client2.getTeamMember(teamId, userId));
111397
+ if (flags.json === true) printJson2({ member });
111398
+ else console.log(`${member.display_name || member.user_id} ${member.role} ${member.status || ""} ${member.user_id}`.trim());
111399
+ return;
111400
+ }
111401
+ throw new Error("Use teams members list <team-id> or teams members show <team-id> <member-user-id>.");
111402
+ }
111403
+ if (subcommand === "profile") {
111404
+ const action2 = rest[0];
111405
+ const teamId = requiredStringFlag(rest[1], "<team-id>");
111406
+ const identity3 = await client2.whoAmI();
111407
+ const ownUserId = requiredStringFlag(typeof identity3.id === "string" ? identity3.id : void 0, "authenticated user ID");
111408
+ if (action2 === "show" || action2 === "get") {
111409
+ const member = publicTeamMember(await client2.getTeamMember(teamId, ownUserId));
111410
+ if (flags.json === true) printJson2({ member });
111411
+ else console.log(`${member.display_name || member.user_id} ${member.role} ${member.user_id}`);
111412
+ return;
111413
+ }
111414
+ if (action2 === "edit") {
111415
+ const current2 = await client2.getTeamMember(teamId, ownUserId);
111416
+ if (typeof flags.name !== "string" && typeof flags.avatar !== "string") throw new Error("Provide --name or --avatar.");
111417
+ const member = publicTeamMember(await client2.updateOwnTeamMemberProfile(teamId, {
111418
+ display_name: typeof flags.name === "string" ? flags.name : current2.profile?.display_name,
111419
+ avatar: typeof flags.avatar === "string" ? flags.avatar : current2.profile?.avatar
111420
+ }));
111421
+ if (flags.json === true) printJson2({ member });
111422
+ else console.log(`Team profile saved: ${member.display_name || member.user_id}`);
111423
+ return;
111424
+ }
111425
+ throw new Error("Use teams profile show <team-id> or teams profile edit <team-id> [--name <name>] [--avatar <value>].");
111426
+ }
110240
111427
  if (subcommand === "create") {
110241
111428
  const name = requiredStringFlag(flags.name, "--name <name>");
110242
111429
  const team = await client2.createTeam({
@@ -111070,7 +112257,7 @@ async function createEncryptedRemoteAccessProject(client2, name, rootPath, write
111070
112257
  encrypted_settings: await encryptWithAesGcmCombined(JSON.stringify({ default_focus: defaultFocus }), projectKey),
111071
112258
  key_wrappers: wrapping.teamId ? [{
111072
112259
  key_type: "team",
111073
- hashed_team_id: createHash33("sha256").update(wrapping.teamId).digest("hex"),
112260
+ hashed_team_id: createHash34("sha256").update(wrapping.teamId).digest("hex"),
111074
112261
  team_key_epoch: 1,
111075
112262
  encrypted_project_key: await encryptBytesWithAesGcm(projectKey, wrapping.key),
111076
112263
  wrapper_version: 1,
@@ -112406,8 +113593,8 @@ function extractApiKeyChatModelName(value, depth = 0) {
112406
113593
  if (!value || typeof value !== "object" || depth > 3) return null;
112407
113594
  const record10 = value;
112408
113595
  for (const key of ["model_name", "modelName", "model"]) {
112409
- const field2 = record10[key];
112410
- if (typeof field2 === "string" && field2) return field2;
113596
+ const field3 = record10[key];
113597
+ if (typeof field3 === "string" && field3) return field3;
112411
113598
  }
112412
113599
  return extractApiKeyChatModelName(record10.data, depth + 1) ?? extractApiKeyChatModelName(record10.raw, depth + 1) ?? null;
112413
113600
  }
@@ -112461,7 +113648,7 @@ function normalizeApiKeyLoadedChat(payload) {
112461
113648
  const messages = rawMessages.map((message) => {
112462
113649
  const messageId = String(message.client_message_id ?? message.message_id ?? message.id ?? "");
112463
113650
  const directEmbedIds = Array.isArray(message.embedIds) ? message.embedIds : Array.isArray(message.embed_ids) ? message.embed_ids : [];
112464
- const hashedMessageId = messageId ? createHash33("sha256").update(messageId).digest("hex") : null;
113651
+ const hashedMessageId = messageId ? createHash34("sha256").update(messageId).digest("hex") : null;
112465
113652
  const matchedEmbedIds = hashedMessageId ? rawEmbeds.filter((embed) => embed.hashed_message_id === hashedMessageId && !embed.parent_embed_id).map((embed) => String(embed.embed_id ?? embed.id ?? "")).filter(Boolean) : [];
112466
113653
  return {
112467
113654
  id: messageId,
@@ -119431,6 +120618,10 @@ function printTeamsHelp() {
119431
120618
  openmates teams approve-access <team-id> <access-request-id> [--encrypted-team-key <value>] [--json]
119432
120619
  openmates teams reject-access <team-id> <access-request-id> [--json]
119433
120620
  openmates teams role <team-id> --user <user-id> --role admin|member|viewer [--json]
120621
+ openmates teams members list <team-id> [--json]
120622
+ openmates teams members show <team-id> <member-user-id> [--json]
120623
+ openmates teams profile show <team-id> [--json]
120624
+ openmates teams profile edit <team-id> [--name <display-name>] [--avatar <value>] [--json]
119434
120625
  openmates teams remove-member <team-id> --user <user-id> [--json]
119435
120626
  openmates teams billing <team-id> [--json]
119436
120627
  openmates teams storage <team-id> [--json]