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.
- package/dist/{chunk-DWN2L3GB.js → chunk-VFL7VCMN.js} +1936 -745
- package/dist/{cli-CNsuzfYG.d.ts → cli-CRk4jJXf.d.ts} +44 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +15 -2
- package/dist/index.js +1 -1
- package/fixtures/privacy-runtime/linux-arm64/libggml-base.so +0 -0
- package/fixtures/privacy-runtime/linux-arm64/libggml-base.so.0 +0 -0
- package/fixtures/privacy-runtime/linux-arm64/libggml-base.so.0.15.1 +0 -0
- package/fixtures/privacy-runtime/linux-arm64/libggml-cpu.so +0 -0
- package/fixtures/privacy-runtime/linux-arm64/libggml-cpu.so.0 +0 -0
- package/fixtures/privacy-runtime/linux-arm64/libggml-cpu.so.0.15.1 +0 -0
- package/fixtures/privacy-runtime/linux-arm64/libggml.so +0 -0
- package/fixtures/privacy-runtime/linux-arm64/libggml.so.0 +0 -0
- package/fixtures/privacy-runtime/linux-arm64/libggml.so.0.15.1 +0 -0
- package/fixtures/privacy-runtime/linux-arm64/manifest.json +9 -9
- package/package.json +5 -4
|
@@ -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
|
|
596
|
-
if (Array.isArray(data[
|
|
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
|
|
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
|
|
2594
|
+
const field3 = separator >= 0 ? line3.slice(0, separator) : line3;
|
|
2595
2595
|
const value = separator >= 0 ? line3.slice(separator + 1).replace(/^ /, "") : "";
|
|
2596
|
-
if (
|
|
2597
|
-
if (
|
|
2598
|
-
if (
|
|
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((
|
|
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((
|
|
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((
|
|
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.
|
|
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(
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
12318
|
+
hashed_user_id: createHash35("sha256").update(userId).digest("hex"),
|
|
12199
12319
|
encrypted_provider_type: await encryptLocalConnectorValue(input.provider_id),
|
|
12200
|
-
provider_type_hash:
|
|
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
|
|
13054
|
-
const
|
|
13055
|
-
|
|
13056
|
-
|
|
13057
|
-
|
|
13058
|
-
|
|
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
|
|
14003
|
-
const
|
|
14004
|
-
|
|
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:
|
|
14252
|
-
const hashedEmbedId =
|
|
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 (
|
|
14261
|
-
const val = embed[
|
|
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:
|
|
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 =
|
|
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 =
|
|
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
|
|
14585
|
-
if (ready2[
|
|
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((
|
|
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
|
|
14871
|
-
if (!
|
|
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[
|
|
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 ?
|
|
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
|
|
15186
|
-
const value = await prepareCliMessagePrivacy(decoded[
|
|
15187
|
-
decoded[
|
|
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 ?
|
|
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 =
|
|
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
|
-
|
|
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
|
|
18520
|
-
const
|
|
18521
|
-
|
|
18522
|
-
|
|
18523
|
-
|
|
18524
|
-
|
|
18525
|
-
|
|
18526
|
-
|
|
18527
|
-
const
|
|
18528
|
-
|
|
18529
|
-
|
|
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 [
|
|
19726
|
-
if (typeof r[
|
|
19727
|
-
const plain = await decryptWithAesGcmCombined(r[
|
|
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 [
|
|
20039
|
-
const val = params.itemValue[
|
|
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 '${
|
|
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:
|
|
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 =
|
|
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:
|
|
20382
|
-
const hashedEmbedId =
|
|
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:
|
|
20514
|
-
const hashedEmbedId =
|
|
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:
|
|
21371
|
+
const { createHash: createHash35 } = await import("crypto");
|
|
21046
21372
|
const deletedHashes = new Set(
|
|
21047
|
-
reconciliation.deleted_chat_ids.map((id) =>
|
|
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((
|
|
28028
|
-
const escapedField =
|
|
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 '${
|
|
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(
|
|
31532
|
+
textSectionFacade(field3) {
|
|
31207
31533
|
return {
|
|
31208
|
-
add: (planId, value) => this.update(planId, { [
|
|
31209
|
-
update: (planId, value) => this.update(planId, { [
|
|
31210
|
-
remove: (planId) => this.update(planId, { [
|
|
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
|
|
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
|
|
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
|
-
|
|
35044
|
-
|
|
35045
|
-
|
|
35046
|
-
|
|
35047
|
-
|
|
35048
|
-
|
|
35049
|
-
|
|
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
|
|
36094
|
-
if (!
|
|
36095
|
-
const escaped =
|
|
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((
|
|
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((
|
|
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
|
|
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
|
-
|
|
90032
|
-
|
|
90033
|
-
|
|
90034
|
-
|
|
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
|
-
|
|
90037
|
-
|
|
90038
|
-
] : normalized === "task-status" ? [
|
|
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:
|
|
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,
|
|
90591
|
-
if (
|
|
90592
|
-
if (
|
|
90593
|
-
if (
|
|
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 (
|
|
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 (
|
|
90601
|
-
if (
|
|
90602
|
-
if (
|
|
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 (
|
|
90992
|
+
if (field3.valueType === "object" || field3.valueType === "array" || oldValue !== null && typeof oldValue === "object") {
|
|
90606
90993
|
const value = JSON.parse(raw);
|
|
90607
|
-
if (
|
|
90608
|
-
if (
|
|
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 (
|
|
90612
|
-
if (
|
|
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((
|
|
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
|
|
90652
|
-
if (!
|
|
90653
|
-
if (
|
|
90654
|
-
const path2 =
|
|
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 :
|
|
90663
|
-
if (oldValue === void 0 && !
|
|
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(
|
|
91052
|
+
setPath(config, path2, parsedField(field3.value, oldValue, { ...field3, required: required3 }));
|
|
90666
91053
|
} catch (error) {
|
|
90667
|
-
throw new Error(`${
|
|
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
|
|
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
|
-
|
|
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: [
|
|
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: [
|
|
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: [
|
|
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: [
|
|
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: [
|
|
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: [
|
|
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: [
|
|
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: [
|
|
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: [
|
|
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: [
|
|
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: [
|
|
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: [
|
|
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: [
|
|
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: [
|
|
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: [
|
|
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: [
|
|
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: [
|
|
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: [
|
|
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: [
|
|
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: [
|
|
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", "
|
|
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 (
|
|
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
|
|
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)
|
|
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((
|
|
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(
|
|
91161
|
-
if (
|
|
91162
|
-
if (
|
|
91163
|
-
if (
|
|
91164
|
-
if (
|
|
91165
|
-
return
|
|
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
|
|
91169
|
-
const error = validateField(
|
|
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.
|
|
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
|
|
91192
|
-
const validation = actionId === "save" ? validateTuiSettingsDraft(page, draft) :
|
|
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.
|
|
91246
|
-
...page.
|
|
91247
|
-
...(page
|
|
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
|
|
91300
|
-
if (!
|
|
91301
|
-
if (state.loading && (
|
|
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 (
|
|
92440
|
+
if (field3.kind === "boolean") {
|
|
91304
92441
|
draft[id] = draft[id] === "on" ? "off" : "on";
|
|
91305
92442
|
markDraftFieldChanged(state, id);
|
|
91306
|
-
} else if (
|
|
91307
|
-
const choices =
|
|
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
|
|
91364
|
-
if (
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
|
|
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(...
|
|
91510
|
-
if (state.message) lines.push(...
|
|
91511
|
-
if (state.error) lines.push(...
|
|
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.
|
|
92676
|
+
if (editableFields(page, state).length) {
|
|
91520
92677
|
lines.push("", "Edit fields, then Save:");
|
|
91521
|
-
for (const
|
|
91522
|
-
const id = `field:${
|
|
91523
|
-
const pendingChoice = state.loading && (
|
|
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 ? "" :
|
|
91526
|
-
lines.push(line(truncateCells(`${index + 1}. ${state.selection === index ? "\u203A " : " "}${
|
|
91527
|
-
if (
|
|
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
|
|
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
|
|
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("", ...
|
|
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("", ...
|
|
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
|
-
|
|
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
|
|
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
|
-
}) :
|
|
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((
|
|
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((
|
|
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),
|
|
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 (
|
|
94435
|
-
let 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[
|
|
94443
|
-
else editor.answer[
|
|
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 (
|
|
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(
|
|
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 (
|
|
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[
|
|
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((
|
|
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 =
|
|
94936
|
-
const
|
|
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,
|
|
95930
|
+
for (const [index, field3] of form.fields.entries()) {
|
|
95052
95931
|
const focused = index === form.fieldIndex;
|
|
95053
|
-
const suffix = [
|
|
95054
|
-
const action2 = { kind: "select", target: "form-field", index, id:
|
|
95055
|
-
rows3.push({ text: `${focused ? "\u203A" : " "} ${
|
|
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(`${
|
|
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(
|
|
95062
|
-
if (
|
|
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,
|
|
95083
|
-
const action2 = { kind: "select", target: "form-field", index, id:
|
|
95084
|
-
rows2.push(pointerLine(`${index === form.fieldIndex ? ">" : " "} ${
|
|
95085
|
-
rows2.push(...wrapCells(` ${
|
|
95086
|
-
if (
|
|
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 =
|
|
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
|
-
|
|
96083
|
+
const side = sideStart ? [allSide[0], "", ...allSide.slice(sideStart)] : allSide;
|
|
96084
|
+
let settingsRows = [];
|
|
95204
96085
|
if (settingsSplit) {
|
|
95205
|
-
const panel2 = state.settings, fixed2 =
|
|
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,
|
|
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
|
-
|
|
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
|
|
95251
|
-
|
|
95252
|
-
|
|
95253
|
-
const
|
|
95254
|
-
if (style2?.action) addPointerTarget(state,
|
|
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
|
|
95260
|
-
if (part.action) addPointerTarget(state,
|
|
95261
|
-
|
|
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
|
-
|
|
95265
|
-
} else
|
|
95266
|
-
|
|
95267
|
-
|
|
95268
|
-
|
|
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
|
|
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 (
|
|
96170
|
-
if (
|
|
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(
|
|
96176
|
-
} else if (
|
|
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 (
|
|
96182
|
-
if (
|
|
96183
|
-
value.forEach((item, index) => walk(
|
|
96184
|
-
} else if (
|
|
96185
|
-
if (typeof value !== "number" || !Number.isFinite(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 (
|
|
96190
|
-
if (
|
|
96191
|
-
} else if (
|
|
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 (
|
|
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 (
|
|
96199
|
-
if (
|
|
96200
|
-
if (
|
|
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(
|
|
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
|
|
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 ${
|
|
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
|
|
96512
|
-
const 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
|
|
96531
|
-
const previous = getSkillPath(input,
|
|
96532
|
-
if (!
|
|
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(
|
|
97421
|
+
value = parseField(field3.value, schemaForPath(skill.schema, field3.name));
|
|
96536
97422
|
} catch (error) {
|
|
96537
|
-
throw new Error(`${
|
|
97423
|
+
throw new Error(`${field3.label}: ${error instanceof Error ? error.message : "Invalid value"}`);
|
|
96538
97424
|
}
|
|
96539
|
-
input = setSkillPath(input,
|
|
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:
|
|
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.
|
|
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")
|
|
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.
|
|
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
|
|
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) =>
|
|
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,
|
|
99047
|
-
if (!
|
|
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") &&
|
|
99058
|
-
const index =
|
|
99059
|
-
|
|
99060
|
-
} else if (key.name === "backspace")
|
|
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 &&
|
|
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")
|
|
99066
|
-
else if (!key.ctrl && !key.meta && 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((
|
|
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
|
-
|
|
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
|
-
|
|
100662
|
-
|
|
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 (
|
|
100672
|
-
|
|
100673
|
-
|
|
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
|
-
|
|
100701
|
-
|
|
101815
|
+
if (ownsSend()) {
|
|
101816
|
+
userMessage.content = safe2.replace(/^@ai-model:[^\s]+\s*/, "");
|
|
101817
|
+
render();
|
|
101818
|
+
}
|
|
100702
101819
|
},
|
|
100703
101820
|
onPrivacyProgress: (done, total) => {
|
|
100704
|
-
if (
|
|
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 (
|
|
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 (
|
|
101859
|
+
if (ownsSend()) {
|
|
100741
101860
|
state.projectFocusPending = countdown;
|
|
100742
101861
|
render();
|
|
100743
101862
|
}
|
|
100744
101863
|
},
|
|
100745
101864
|
onChatContextApplied: (event) => {
|
|
100746
|
-
if (
|
|
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 (
|
|
100752
|
-
if (event.
|
|
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 (
|
|
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 (
|
|
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 = () =>
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
100814
|
-
|
|
100815
|
-
|
|
100816
|
-
|
|
100817
|
-
|
|
100818
|
-
|
|
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
|
-
|
|
100905
|
-
|
|
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
|
|
100959
|
-
|
|
100960
|
-
|
|
100961
|
-
|
|
100962
|
-
|
|
100963
|
-
|
|
100964
|
-
|
|
100965
|
-
|
|
100966
|
-
|
|
100967
|
-
|
|
100968
|
-
|
|
100969
|
-
state.
|
|
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,
|
|
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:
|
|
101165
|
-
value:
|
|
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 ${
|
|
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
|
|
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",
|
|
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
|
|
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",
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 :
|
|
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:
|
|
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:
|
|
105431
|
-
chunk_hash:
|
|
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
|
|
106706
|
-
if (!Array.isArray(document[
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
112410
|
-
if (typeof
|
|
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 ?
|
|
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]
|