superceo 0.3.11 → 0.3.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +604 -74
- package/dist/index.js.map +4 -4
- package/dist/ui-dist/assets/{_basePickBy-C249Trf_.js → _basePickBy-Rxb9gBzf.js} +1 -1
- package/dist/ui-dist/assets/{_baseUniq-CTvApBUJ.js → _baseUniq-BAaLJmda.js} +1 -1
- package/dist/ui-dist/assets/{arc-BYA6teu5.js → arc-DyBpKcFk.js} +1 -1
- package/dist/ui-dist/assets/{architectureDiagram-VXUJARFQ-eXB6QfnC.js → architectureDiagram-VXUJARFQ-DnEQAiKY.js} +1 -1
- package/dist/ui-dist/assets/{blockDiagram-VD42YOAC-DMIzX_AO.js → blockDiagram-VD42YOAC-CDBBeg4n.js} +1 -1
- package/dist/ui-dist/assets/{c4Diagram-YG6GDRKO-C80xMgwJ.js → c4Diagram-YG6GDRKO-BaKiTPWH.js} +1 -1
- package/dist/ui-dist/assets/channel-DWDjs7Td.js +1 -0
- package/dist/ui-dist/assets/{chunk-4BX2VUAB-DKXvVYvA.js → chunk-4BX2VUAB-DBYSE0jd.js} +1 -1
- package/dist/ui-dist/assets/{chunk-55IACEB6-D7O50kd1.js → chunk-55IACEB6-B2SGBd9r.js} +1 -1
- package/dist/ui-dist/assets/{chunk-B4BG7PRW-BU5sraLo.js → chunk-B4BG7PRW-BmMNKP5-.js} +1 -1
- package/dist/ui-dist/assets/{chunk-DI55MBZ5-DFWgzS-Q.js → chunk-DI55MBZ5-B6smpJGv.js} +1 -1
- package/dist/ui-dist/assets/{chunk-FMBD7UC4-Ca_9o5bl.js → chunk-FMBD7UC4-B8uduxyR.js} +1 -1
- package/dist/ui-dist/assets/{chunk-QN33PNHL-0UwO6Gni.js → chunk-QN33PNHL-YCxAvbU_.js} +1 -1
- package/dist/ui-dist/assets/{chunk-QZHKN3VN-DGWw0Cd_.js → chunk-QZHKN3VN-7rzCBJuM.js} +1 -1
- package/dist/ui-dist/assets/{chunk-TZMSLE5B-D1laE8TU.js → chunk-TZMSLE5B-BCPoM1RZ.js} +1 -1
- package/dist/ui-dist/assets/classDiagram-2ON5EDUG-cZtrh3Jb.js +1 -0
- package/dist/ui-dist/assets/classDiagram-v2-WZHVMYZB-cZtrh3Jb.js +1 -0
- package/dist/ui-dist/assets/clone-CbnB17HW.js +1 -0
- package/dist/ui-dist/assets/{cose-bilkent-S5V4N54A-y8dU8mjf.js → cose-bilkent-S5V4N54A-CmWVX5Ss.js} +1 -1
- package/dist/ui-dist/assets/{dagre-6UL2VRFP-B6dcqfp3.js → dagre-6UL2VRFP-Ce5_bTvK.js} +1 -1
- package/dist/ui-dist/assets/{diagram-PSM6KHXK-IeQCXN28.js → diagram-PSM6KHXK-717OCB12.js} +1 -1
- package/dist/ui-dist/assets/{diagram-QEK2KX5R-BnK9g2ZV.js → diagram-QEK2KX5R-exBLiSKc.js} +1 -1
- package/dist/ui-dist/assets/{diagram-S2PKOQOG-E0gOOmgp.js → diagram-S2PKOQOG-D-I7HtpT.js} +1 -1
- package/dist/ui-dist/assets/{erDiagram-Q2GNP2WA-L6tsRO7_.js → erDiagram-Q2GNP2WA-j6PHZESx.js} +1 -1
- package/dist/ui-dist/assets/{flowDiagram-NV44I4VS-DSCF8l5y.js → flowDiagram-NV44I4VS-BGG4niPV.js} +1 -1
- package/dist/ui-dist/assets/{ganttDiagram-JELNMOA3-BMi4KHYr.js → ganttDiagram-JELNMOA3-C1BEo9cO.js} +1 -1
- package/dist/ui-dist/assets/{gitGraphDiagram-V2S2FVAM-VWUlait1.js → gitGraphDiagram-V2S2FVAM-BohL-82C.js} +1 -1
- package/dist/ui-dist/assets/{graph-BkLhHGvA.js → graph-Bnq593xE.js} +1 -1
- package/dist/ui-dist/assets/{index-t-1Meejd.js → index-77jd8y5K.js} +1 -1
- package/dist/ui-dist/assets/{index-DAqMGxyt.js → index-8PniqIjO.js} +1 -1
- package/dist/ui-dist/assets/{index-8z_amSP8.js → index-B09qV1Ec.js} +1 -1
- package/dist/ui-dist/assets/{index-CsxGT9oF.js → index-B1NPpgJm.js} +1 -1
- package/dist/ui-dist/assets/{index-Dsc0nyfB.js → index-BDnkSu-e.js} +1 -1
- package/dist/ui-dist/assets/{index-DDafGMZD.js → index-BK_XMcKr.js} +1 -1
- package/dist/ui-dist/assets/{index-Z_Q1PiZK.js → index-BVJz4Ljo.js} +1 -1
- package/dist/ui-dist/assets/{index-B44_Ufp1.js → index-BhniS9Pw.js} +1 -1
- package/dist/ui-dist/assets/{index-D12ZhCQn.js → index-C1GfzFGz.js} +1 -1
- package/dist/ui-dist/assets/{index-gBwC1Di-.js → index-CEgBGc6I.js} +1 -1
- package/dist/ui-dist/assets/{index-Db3ILatz.js → index-CX4G6uj_.js} +1 -1
- package/dist/ui-dist/assets/{index-C49iJjtK.js → index-C_LtgAgH.js} +1 -1
- package/dist/ui-dist/assets/{index-DKLhgppN.js → index-Cbe9HK0x.js} +1 -1
- package/dist/ui-dist/assets/{index-cLSOW2sv.js → index-Ch71R67s.js} +1 -1
- package/dist/ui-dist/assets/{index-C4H63FOl.js → index-ClxePvd_.js} +1 -1
- package/dist/ui-dist/assets/{index-DJ0sopR2.js → index-DYiIzKcz.js} +1 -1
- package/dist/ui-dist/assets/{index-JaMVMSa5.js → index-DwJ34W2C.js} +1 -1
- package/dist/ui-dist/assets/{index-CqPKryHo.js → index-DxHEePNM.js} +1 -1
- package/dist/ui-dist/assets/{index-D1fW-KBm.js → index-NoR-w-e6.js} +1 -1
- package/dist/ui-dist/assets/{index-DcIq-XSz.js → index-ZAoHuzda.js} +1 -1
- package/dist/ui-dist/assets/{index-BO_CwIEi.js → index-csURMFV9.js} +1 -1
- package/dist/ui-dist/assets/{index-D0kQZyqp.js → index-rSohbXog.js} +1 -1
- package/dist/ui-dist/assets/{index-Cr0yomNi.js → index-udadcFGS.js} +209 -209
- package/dist/ui-dist/assets/index-vqcwK_4v.css +1 -0
- package/dist/ui-dist/assets/{infoDiagram-HS3SLOUP-rWeZYbAw.js → infoDiagram-HS3SLOUP-mkdDnz14.js} +1 -1
- package/dist/ui-dist/assets/{journeyDiagram-XKPGCS4Q-Dyp5viM4.js → journeyDiagram-XKPGCS4Q-tRoZMyAX.js} +1 -1
- package/dist/ui-dist/assets/{kanban-definition-3W4ZIXB7-DlNGXRZC.js → kanban-definition-3W4ZIXB7-DF0tg_AC.js} +1 -1
- package/dist/ui-dist/assets/{layout-DMJwpl6V.js → layout-DOw-Nwl6.js} +1 -1
- package/dist/ui-dist/assets/{linear-Kfsxpfkr.js → linear-RRKJ_mN2.js} +1 -1
- package/dist/ui-dist/assets/{mermaid.core-BtiUU7W0.js → mermaid.core-BrQzIZWe.js} +4 -4
- package/dist/ui-dist/assets/{mindmap-definition-VGOIOE7T-JDTJSzUM.js → mindmap-definition-VGOIOE7T-DYP9ZkES.js} +1 -1
- package/dist/ui-dist/assets/{pieDiagram-ADFJNKIX-DKBlnQj4.js → pieDiagram-ADFJNKIX-ZYJvIY3k.js} +1 -1
- package/dist/ui-dist/assets/{quadrantDiagram-AYHSOK5B-DY-rSPWn.js → quadrantDiagram-AYHSOK5B-BRVh1Lsd.js} +1 -1
- package/dist/ui-dist/assets/{requirementDiagram-UZGBJVZJ-N_qgViLJ.js → requirementDiagram-UZGBJVZJ-7TlLv-to.js} +1 -1
- package/dist/ui-dist/assets/{sankeyDiagram-TZEHDZUN-DeaHu7Vu.js → sankeyDiagram-TZEHDZUN-CheflZmO.js} +1 -1
- package/dist/ui-dist/assets/{sequenceDiagram-WL72ISMW-DPdnahqv.js → sequenceDiagram-WL72ISMW-ZlJ6JLaY.js} +1 -1
- package/dist/ui-dist/assets/{stateDiagram-FKZM4ZOC-DZ-wCMkK.js → stateDiagram-FKZM4ZOC-bu1_QKha.js} +1 -1
- package/dist/ui-dist/assets/stateDiagram-v2-4FDKWEC3-Bysna4Rj.js +1 -0
- package/dist/ui-dist/assets/{timeline-definition-IT6M3QCI-C8-Fsp5u.js → timeline-definition-IT6M3QCI-DNx9351g.js} +1 -1
- package/dist/ui-dist/assets/{treemap-GDKQZRPO-IJhOKCM2.js → treemap-GDKQZRPO-ApPDTXVj.js} +1 -1
- package/dist/ui-dist/assets/{xychartDiagram-PRI3JC2R-DFq2CfSA.js → xychartDiagram-PRI3JC2R-C2RFLY3-.js} +1 -1
- package/dist/ui-dist/index.html +2 -2
- package/onboarding-assets/ceo/AGENTS.md +66 -0
- package/onboarding-assets/ceo/HEARTBEAT.md +86 -0
- package/onboarding-assets/ceo/SOUL.md +33 -0
- package/onboarding-assets/ceo/TOOLS.md +3 -0
- package/onboarding-assets/ceo/persona/body.md +16 -0
- package/onboarding-assets/ceo/persona/face.md +28 -0
- package/onboarding-assets/ceo/persona/identity.md +22 -0
- package/onboarding-assets/ceo/persona/personality.md +28 -0
- package/onboarding-assets/ceo/persona/style.md +28 -0
- package/onboarding-assets/default/AGENTS.md +3 -0
- package/package.json +3 -2
- package/dist/ui-dist/assets/channel-Kt9mAvNi.js +0 -1
- package/dist/ui-dist/assets/classDiagram-2ON5EDUG-CrzcGMuR.js +0 -1
- package/dist/ui-dist/assets/classDiagram-v2-WZHVMYZB-CrzcGMuR.js +0 -1
- package/dist/ui-dist/assets/clone-DhtyeTi3.js +0 -1
- package/dist/ui-dist/assets/index-DWLL5wfa.css +0 -1
- package/dist/ui-dist/assets/stateDiagram-v2-4FDKWEC3-DEbMyOKZ.js +0 -1
package/dist/index.js
CHANGED
|
@@ -3004,6 +3004,27 @@ var init_derive_account_handle_from_url = __esm({
|
|
|
3004
3004
|
}
|
|
3005
3005
|
});
|
|
3006
3006
|
|
|
3007
|
+
// ../packages/shared/dist/operator-timezone.js
|
|
3008
|
+
function resolveOperatorLocalNow(operatorTimezone) {
|
|
3009
|
+
const trimmed = typeof operatorTimezone === "string" ? operatorTimezone.trim() : "";
|
|
3010
|
+
const tz = trimmed.length > 0 ? trimmed : "UTC";
|
|
3011
|
+
try {
|
|
3012
|
+
const localNow = new Intl.DateTimeFormat("en-CA", {
|
|
3013
|
+
timeZone: tz,
|
|
3014
|
+
dateStyle: "full",
|
|
3015
|
+
timeStyle: "short"
|
|
3016
|
+
}).format(/* @__PURE__ */ new Date());
|
|
3017
|
+
return { tz, localNow };
|
|
3018
|
+
} catch {
|
|
3019
|
+
return { tz, localNow: (/* @__PURE__ */ new Date()).toISOString() };
|
|
3020
|
+
}
|
|
3021
|
+
}
|
|
3022
|
+
var init_operator_timezone = __esm({
|
|
3023
|
+
"../packages/shared/dist/operator-timezone.js"() {
|
|
3024
|
+
"use strict";
|
|
3025
|
+
}
|
|
3026
|
+
});
|
|
3027
|
+
|
|
3007
3028
|
// ../packages/shared/dist/config-schema.js
|
|
3008
3029
|
import { z as z26 } from "zod";
|
|
3009
3030
|
var configMetaSchema, llmConfigSchema, databaseBackupConfigSchema, databaseConfigSchema, loggingConfigSchema, serverConfigSchema, authConfigSchema, storageLocalDiskConfigSchema, storageS3ConfigSchema, storageConfigSchema, secretsLocalEncryptedConfigSchema, secretsConfigSchema, telemetryConfigSchema, paperclipConfigSchema;
|
|
@@ -3175,6 +3196,7 @@ var init_dist = __esm({
|
|
|
3175
3196
|
init_project_mentions();
|
|
3176
3197
|
init_routine_variables();
|
|
3177
3198
|
init_derive_account_handle_from_url();
|
|
3199
|
+
init_operator_timezone();
|
|
3178
3200
|
init_config_schema();
|
|
3179
3201
|
}
|
|
3180
3202
|
});
|
|
@@ -20188,6 +20210,11 @@ function nonEmpty4(value) {
|
|
|
20188
20210
|
const trimmed = value.trim();
|
|
20189
20211
|
return trimmed.length > 0 ? trimmed : void 0;
|
|
20190
20212
|
}
|
|
20213
|
+
function isTruthyEnvValue(value) {
|
|
20214
|
+
if (!value) return false;
|
|
20215
|
+
const normalized = value.trim().toLowerCase();
|
|
20216
|
+
return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on";
|
|
20217
|
+
}
|
|
20191
20218
|
function mergeWithDefaults(partial) {
|
|
20192
20219
|
return {
|
|
20193
20220
|
...DEFAULT_BRAND_PACK,
|
|
@@ -20220,7 +20247,8 @@ function getBrandPack(env = process.env) {
|
|
|
20220
20247
|
const hasEnvBrandOverrides = Boolean(
|
|
20221
20248
|
nonEmpty4(env.PAPERCLIP_BRAND_NAME) || nonEmpty4(env.PAPERCLIP_BRAND_LOGO_URL) || nonEmpty4(env.PAPERCLIP_BRAND_HOMEPAGE_URL) || nonEmpty4(env.PAPERCLIP_BRAND_UPDATE_URL) || nonEmpty4(env.PAPERCLIP_BRAND_SUPPORT_EMAIL) || nonEmpty4(env.PAPERCLIP_BRAND_DOCS_URL)
|
|
20222
20249
|
);
|
|
20223
|
-
const
|
|
20250
|
+
const base = hasEnvBrandOverrides ? loadBrandFromEnv(env) : DEFAULT_BRAND_PACK;
|
|
20251
|
+
const loaded = isTruthyEnvValue(env.PAPERCLIP_HIDE_BRAND) ? { ...base, hideBrand: true } : base;
|
|
20224
20252
|
if (env === process.env) cachedBrandPack = loaded;
|
|
20225
20253
|
return loaded;
|
|
20226
20254
|
}
|
|
@@ -39625,16 +39653,19 @@ function chatSessionService(db) {
|
|
|
39625
39653
|
return rows.length > 0;
|
|
39626
39654
|
},
|
|
39627
39655
|
async appendMessage(companyId, sessionId, input) {
|
|
39656
|
+
const createdAtOverride = input.createdAt ? input.createdAt instanceof Date ? input.createdAt : new Date(input.createdAt) : null;
|
|
39628
39657
|
const [row2] = await db.insert(chatMessages).values({
|
|
39629
39658
|
sessionId,
|
|
39630
39659
|
role: input.role,
|
|
39631
39660
|
content: input.content,
|
|
39632
39661
|
stopReason: input.stopReason ?? null,
|
|
39633
|
-
usage: input.usage ?? null
|
|
39662
|
+
usage: input.usage ?? null,
|
|
39663
|
+
...createdAtOverride ? { createdAt: createdAtOverride } : {}
|
|
39634
39664
|
}).returning();
|
|
39665
|
+
const lastMessageAt = createdAtOverride ?? /* @__PURE__ */ new Date();
|
|
39635
39666
|
const preview = extractTextPreview(input.content);
|
|
39636
39667
|
await db.update(chatSessions).set({
|
|
39637
|
-
lastMessageAt
|
|
39668
|
+
lastMessageAt,
|
|
39638
39669
|
lastMessagePreview: preview,
|
|
39639
39670
|
updatedAt: /* @__PURE__ */ new Date()
|
|
39640
39671
|
}).where(eq30(chatSessions.id, sessionId));
|
|
@@ -56529,34 +56560,34 @@ var init_agentmail2 = __esm({
|
|
|
56529
56560
|
const inboxId = (ctx.inboxId ?? ctx.credentials.AGENT_INBOX_ID ?? "").trim();
|
|
56530
56561
|
if (!apiKey) throw unprocessable("AgentMail API key is missing");
|
|
56531
56562
|
if (!inboxId) throw unprocessable("AgentMail inbox id is missing");
|
|
56532
|
-
const
|
|
56533
|
-
|
|
56534
|
-
|
|
56535
|
-
|
|
56536
|
-
|
|
56537
|
-
|
|
56538
|
-
|
|
56539
|
-
|
|
56540
|
-
|
|
56541
|
-
|
|
56542
|
-
|
|
56543
|
-
|
|
56544
|
-
|
|
56545
|
-
|
|
56546
|
-
|
|
56547
|
-
|
|
56548
|
-
|
|
56549
|
-
|
|
56550
|
-
|
|
56551
|
-
|
|
56552
|
-
);
|
|
56563
|
+
const parentMessageId = input.inReplyToExternalMessageId?.trim();
|
|
56564
|
+
const isReply = Boolean(parentMessageId);
|
|
56565
|
+
const url = isReply ? `https://api.agentmail.to/v0/inboxes/${encodeURIComponent(inboxId)}/messages/${encodeURIComponent(parentMessageId)}/reply` : `https://api.agentmail.to/v0/inboxes/${encodeURIComponent(inboxId)}/messages/send`;
|
|
56566
|
+
const body = {
|
|
56567
|
+
to: input.to,
|
|
56568
|
+
cc: input.cc ?? [],
|
|
56569
|
+
bcc: input.bcc ?? [],
|
|
56570
|
+
text: input.bodyText ?? void 0,
|
|
56571
|
+
html: input.bodyHtml ?? void 0
|
|
56572
|
+
};
|
|
56573
|
+
if (!isReply) {
|
|
56574
|
+
body.subject = input.subject;
|
|
56575
|
+
}
|
|
56576
|
+
const response = await fetch(url, {
|
|
56577
|
+
method: "POST",
|
|
56578
|
+
headers: {
|
|
56579
|
+
"Content-Type": "application/json",
|
|
56580
|
+
Authorization: `Bearer ${apiKey}`
|
|
56581
|
+
},
|
|
56582
|
+
body: JSON.stringify(body)
|
|
56583
|
+
});
|
|
56553
56584
|
if (!response.ok) {
|
|
56554
56585
|
throw unprocessable(`AgentMail send failed with status ${response.status}`, {
|
|
56555
56586
|
body: await response.text()
|
|
56556
56587
|
});
|
|
56557
56588
|
}
|
|
56558
56589
|
const payload = await response.json();
|
|
56559
|
-
const externalMessageId = asString13(payload.
|
|
56590
|
+
const externalMessageId = asString13(payload.message_id) ?? asString13(payload.messageId) ?? asString13(payload.id);
|
|
56560
56591
|
const externalThreadId = asString13(payload.thread_id) ?? asString13(payload.threadId) ?? input.threadExternalId;
|
|
56561
56592
|
if (!externalMessageId || !externalThreadId) {
|
|
56562
56593
|
throw unprocessable("AgentMail send response did not include message/thread identifiers");
|
|
@@ -56980,6 +57011,13 @@ function emailService(db) {
|
|
|
56980
57011
|
if (filters.archived === true) whereClauses.push(isNotNull4(emailThreads2.archivedAt));
|
|
56981
57012
|
if (filters.archived === false) whereClauses.push(isNull15(emailThreads2.archivedAt));
|
|
56982
57013
|
if (filters.unread !== void 0) whereClauses.push(eq43(emailThreads2.unread, filters.unread));
|
|
57014
|
+
if (filters.direction) {
|
|
57015
|
+
whereClauses.push(sql31`exists (
|
|
57016
|
+
select 1 from email_messages em
|
|
57017
|
+
where em.thread_id = ${emailThreads2.id}
|
|
57018
|
+
and em.direction = ${filters.direction}
|
|
57019
|
+
)`);
|
|
57020
|
+
}
|
|
56983
57021
|
if (filters.search?.trim()) {
|
|
56984
57022
|
const q = `%${filters.search.trim()}%`;
|
|
56985
57023
|
whereClauses.push(or10(ilike(emailThreads2.subject, q), sql31`exists (
|
|
@@ -57868,7 +57906,7 @@ var init_lead_product_api = __esm({
|
|
|
57868
57906
|
});
|
|
57869
57907
|
|
|
57870
57908
|
// ../server/src/services/leads.ts
|
|
57871
|
-
import { and as and44, asc as asc18, desc as desc28, eq as eq46, gte as gte8, inArray as inArray25, sql as sql34 } from "drizzle-orm";
|
|
57909
|
+
import { and as and44, asc as asc18, desc as desc28, eq as eq46, gte as gte8, inArray as inArray25, or as or12, sql as sql34 } from "drizzle-orm";
|
|
57872
57910
|
import crypto4 from "node:crypto";
|
|
57873
57911
|
function buildLeadsListOrderBy(sortKey, sortDirRaw) {
|
|
57874
57912
|
const ascDir = sortDirRaw === "asc";
|
|
@@ -57941,6 +57979,43 @@ function isRedditHostname(hostname) {
|
|
|
57941
57979
|
const h = hostname.replace(/^www\./i, "").toLowerCase();
|
|
57942
57980
|
return h === "reddit.com" || h.endsWith(".reddit.com");
|
|
57943
57981
|
}
|
|
57982
|
+
function normalizeContactMethods(tokens) {
|
|
57983
|
+
if (!tokens?.length) return [];
|
|
57984
|
+
const set = /* @__PURE__ */ new Set();
|
|
57985
|
+
const allowed = SUPPORTED_PLATFORMS2;
|
|
57986
|
+
for (const token of tokens) {
|
|
57987
|
+
const key = token.trim().toLowerCase();
|
|
57988
|
+
if (allowed.includes(key)) set.add(key);
|
|
57989
|
+
}
|
|
57990
|
+
return Array.from(set);
|
|
57991
|
+
}
|
|
57992
|
+
function buildContactMethodsWhere(methods) {
|
|
57993
|
+
const parts = [];
|
|
57994
|
+
for (const method of methods) {
|
|
57995
|
+
if (method === "email") {
|
|
57996
|
+
parts.push(sql34`trim(coalesce(${leads.email}, '')) <> ''`);
|
|
57997
|
+
continue;
|
|
57998
|
+
}
|
|
57999
|
+
if (method === "reddit") {
|
|
58000
|
+
const primaryReddit = sql34`(${leads.primaryPlatform} = 'reddit' and trim(coalesce(${leads.primaryHandle}, '')) <> '')`;
|
|
58001
|
+
const noEmail = sql34`trim(coalesce(${leads.email}, '')) = ''`;
|
|
58002
|
+
const noPrimaryPair = sql34`not (
|
|
58003
|
+
trim(coalesce(${leads.primaryPlatform}, '')) <> ''
|
|
58004
|
+
and trim(coalesce(${leads.primaryHandle}, '')) <> ''
|
|
58005
|
+
)`;
|
|
58006
|
+
const redditProfileUrl = sql34`${leads.sourceUrl} ~* '^https?://([^/?#]*\\.)?reddit\\.com/(user|u)/[^/?#]+'`;
|
|
58007
|
+
parts.push(sql34`(${primaryReddit} or (${noEmail} and ${noPrimaryPair} and ${redditProfileUrl}))`);
|
|
58008
|
+
continue;
|
|
58009
|
+
}
|
|
58010
|
+
parts.push(
|
|
58011
|
+
sql34`(${leads.primaryPlatform} = ${method} and trim(coalesce(${leads.primaryHandle}, '')) <> '')`
|
|
58012
|
+
);
|
|
58013
|
+
}
|
|
58014
|
+
if (parts.length === 1) return parts[0];
|
|
58015
|
+
const merged = or12(...parts);
|
|
58016
|
+
if (!merged) return sql34`false`;
|
|
58017
|
+
return merged;
|
|
58018
|
+
}
|
|
57944
58019
|
function parseRedditProfileUsernameFromSourceUrl(raw) {
|
|
57945
58020
|
const trimmed = cleanNullable2(raw);
|
|
57946
58021
|
if (!trimmed) return null;
|
|
@@ -58340,6 +58415,10 @@ function leadsService(db) {
|
|
|
58340
58415
|
if (filters.sourceIssueId?.trim()) {
|
|
58341
58416
|
whereClauses.push(eq46(leads.sourceIssueId, filters.sourceIssueId.trim()));
|
|
58342
58417
|
}
|
|
58418
|
+
const contactMethods = normalizeContactMethods(filters.contactMethods);
|
|
58419
|
+
if (contactMethods.length > 0) {
|
|
58420
|
+
whereClauses.push(buildContactMethodsWhere(contactMethods));
|
|
58421
|
+
}
|
|
58343
58422
|
if (filters.search?.trim()) {
|
|
58344
58423
|
const like2 = `%${filters.search.trim()}%`;
|
|
58345
58424
|
whereClauses.push(
|
|
@@ -58425,6 +58504,10 @@ function leadsService(db) {
|
|
|
58425
58504
|
)`
|
|
58426
58505
|
);
|
|
58427
58506
|
}
|
|
58507
|
+
const activityContactMethods = normalizeContactMethods(options2?.contactMethods);
|
|
58508
|
+
if (activityContactMethods.length > 0) {
|
|
58509
|
+
activityWhere.push(buildContactMethodsWhere(activityContactMethods));
|
|
58510
|
+
}
|
|
58428
58511
|
activityWhere.push(gte8(leadActivities.occurredAt, startDate));
|
|
58429
58512
|
const leadWhere = [eq46(leads.companyId, companyId)];
|
|
58430
58513
|
if (options2?.kind) leadWhere.push(eq46(leads.leadKind, options2.kind));
|
|
@@ -58442,6 +58525,10 @@ function leadsService(db) {
|
|
|
58442
58525
|
)`
|
|
58443
58526
|
);
|
|
58444
58527
|
}
|
|
58528
|
+
const leadTrendContactMethods = normalizeContactMethods(options2?.contactMethods);
|
|
58529
|
+
if (leadTrendContactMethods.length > 0) {
|
|
58530
|
+
leadWhere.push(buildContactMethodsWhere(leadTrendContactMethods));
|
|
58531
|
+
}
|
|
58445
58532
|
leadWhere.push(gte8(leads.createdAt, startDate));
|
|
58446
58533
|
const [recentActivityRows, recentLeadRows] = await Promise.all([
|
|
58447
58534
|
db.select({
|
|
@@ -58553,7 +58640,10 @@ function leadsService(db) {
|
|
|
58553
58640
|
};
|
|
58554
58641
|
},
|
|
58555
58642
|
stats: async (companyId, filters = {}) => {
|
|
58556
|
-
if (filters.kind)
|
|
58643
|
+
if (filters.kind) {
|
|
58644
|
+
assertLeadKindShape(filters.kind);
|
|
58645
|
+
await leadKindsSvc.assertKindAllowed(companyId, filters.kind);
|
|
58646
|
+
}
|
|
58557
58647
|
if (filters.status) assertLeadStatus(filters.status);
|
|
58558
58648
|
if (filters.organizationId) {
|
|
58559
58649
|
await assertOrganizationInCompany(companyId, filters.organizationId);
|
|
@@ -63546,7 +63636,277 @@ var init_creators3 = __esm({
|
|
|
63546
63636
|
}
|
|
63547
63637
|
});
|
|
63548
63638
|
|
|
63639
|
+
// ../server/src/services/email-suggestions.ts
|
|
63640
|
+
import { randomUUID as randomUUID13 } from "node:crypto";
|
|
63641
|
+
function buildPromptBody2(ctx) {
|
|
63642
|
+
const lines = [];
|
|
63643
|
+
lines.push(`Thread: ${ctx.subject}`);
|
|
63644
|
+
if (ctx.participants.length > 0) {
|
|
63645
|
+
lines.push(`Participants: ${ctx.participants.slice(0, 6).join(", ")}`);
|
|
63646
|
+
}
|
|
63647
|
+
if (ctx.lead) {
|
|
63648
|
+
const parts = [];
|
|
63649
|
+
if (ctx.lead.displayName) parts.push(`name=${ctx.lead.displayName}`);
|
|
63650
|
+
if (ctx.lead.email) parts.push(`email=${ctx.lead.email}`);
|
|
63651
|
+
if (ctx.lead.status) parts.push(`status=${ctx.lead.status}`);
|
|
63652
|
+
parts.push(`everReplied=${ctx.lead.everReplied}`);
|
|
63653
|
+
parts.push(`everSignedUp=${ctx.lead.everSignedUp}`);
|
|
63654
|
+
if (ctx.lead.lastContactedAt) parts.push(`lastContactedAt=${ctx.lead.lastContactedAt}`);
|
|
63655
|
+
if (ctx.lead.lastReplyAt) parts.push(`lastReplyAt=${ctx.lead.lastReplyAt}`);
|
|
63656
|
+
lines.push(`Lead: ${parts.join(" ")}`);
|
|
63657
|
+
if (ctx.lead.hungrySignal) lines.push(`HungrySignal: ${ctx.lead.hungrySignal.slice(0, 300)}`);
|
|
63658
|
+
if (ctx.lead.notes) lines.push(`Notes: ${ctx.lead.notes.slice(0, 500)}`);
|
|
63659
|
+
} else {
|
|
63660
|
+
lines.push("Lead: (none linked)");
|
|
63661
|
+
}
|
|
63662
|
+
if (ctx.lastMessages.length > 0) {
|
|
63663
|
+
lines.push("");
|
|
63664
|
+
lines.push("Last messages (oldest first):");
|
|
63665
|
+
for (const m of ctx.lastMessages) {
|
|
63666
|
+
lines.push(`- [${m.sentAt}] ${m.direction}: ${m.snippet}`);
|
|
63667
|
+
}
|
|
63668
|
+
}
|
|
63669
|
+
if (ctx.recentActivities.length > 0) {
|
|
63670
|
+
lines.push("");
|
|
63671
|
+
lines.push("Recent activity:");
|
|
63672
|
+
for (const a of ctx.recentActivities) {
|
|
63673
|
+
lines.push(`- ${a.occurredAt} ${a.kind}`);
|
|
63674
|
+
}
|
|
63675
|
+
}
|
|
63676
|
+
lines.push("");
|
|
63677
|
+
lines.push(`Kinds and params:
|
|
63678
|
+
- do_nothing: {} (use when no action is warranted yet)
|
|
63679
|
+
- send_follow_up: { reopen?: boolean } (MUST include draftBody with the suggested reply text)
|
|
63680
|
+
- archive_thread: {} (use when the thread is dead or resolved)
|
|
63681
|
+
- mark_lead_status: { status: drafted|contacted|replied|signed_up|dead }
|
|
63682
|
+
- create_handoff_issue: { title: string, description?: string, priority?: urgent|high|medium|low }
|
|
63683
|
+
Use when a human needs to do a manual action elsewhere (post a DM, call them, paste into a tool).`);
|
|
63684
|
+
lines.push("");
|
|
63685
|
+
lines.push(
|
|
63686
|
+
'Return JSON only: {"reasoning":"why these","actions":[{"kind":"...","label":"...","description":"...","params":{...},"draftBody":"..."}]}'
|
|
63687
|
+
);
|
|
63688
|
+
return lines.join("\n");
|
|
63689
|
+
}
|
|
63690
|
+
function snippetOf(message) {
|
|
63691
|
+
const text87 = message.bodyText ?? message.bodyHtml ?? "";
|
|
63692
|
+
const cleaned = text87.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
63693
|
+
return cleaned.slice(0, 240);
|
|
63694
|
+
}
|
|
63695
|
+
function validateActions2(raw) {
|
|
63696
|
+
if (!raw || typeof raw !== "object") return [];
|
|
63697
|
+
const arr = raw.actions;
|
|
63698
|
+
if (!Array.isArray(arr)) return [];
|
|
63699
|
+
const out = [];
|
|
63700
|
+
for (const entry of arr) {
|
|
63701
|
+
if (!entry || typeof entry !== "object") continue;
|
|
63702
|
+
const e = entry;
|
|
63703
|
+
const kind = e.kind;
|
|
63704
|
+
if (typeof kind !== "string" || !VALID_KINDS2.has(kind)) continue;
|
|
63705
|
+
const label = typeof e.label === "string" ? e.label.trim() : "";
|
|
63706
|
+
const description = typeof e.description === "string" ? e.description.trim() : "";
|
|
63707
|
+
if (!label || !description) continue;
|
|
63708
|
+
const params = e.params && typeof e.params === "object" ? e.params : {};
|
|
63709
|
+
const draftBody = typeof e.draftBody === "string" ? e.draftBody : void 0;
|
|
63710
|
+
const kindTyped = kind;
|
|
63711
|
+
let validated = null;
|
|
63712
|
+
switch (kindTyped) {
|
|
63713
|
+
case "do_nothing":
|
|
63714
|
+
case "archive_thread": {
|
|
63715
|
+
validated = {
|
|
63716
|
+
kind: kindTyped,
|
|
63717
|
+
label: label.slice(0, 40),
|
|
63718
|
+
description: description.slice(0, 120),
|
|
63719
|
+
params: {}
|
|
63720
|
+
};
|
|
63721
|
+
break;
|
|
63722
|
+
}
|
|
63723
|
+
case "send_follow_up": {
|
|
63724
|
+
if (!draftBody || draftBody.trim().length === 0) break;
|
|
63725
|
+
validated = {
|
|
63726
|
+
kind: kindTyped,
|
|
63727
|
+
label: label.slice(0, 40),
|
|
63728
|
+
description: description.slice(0, 120),
|
|
63729
|
+
params: {},
|
|
63730
|
+
draftBody: draftBody.replace(/—/g, "-").replace(/–/g, "-")
|
|
63731
|
+
};
|
|
63732
|
+
break;
|
|
63733
|
+
}
|
|
63734
|
+
case "mark_lead_status": {
|
|
63735
|
+
const status = typeof params.status === "string" ? params.status : null;
|
|
63736
|
+
if (!status || !VALID_LEAD_STATUSES.has(status)) break;
|
|
63737
|
+
validated = {
|
|
63738
|
+
kind: kindTyped,
|
|
63739
|
+
label: label.slice(0, 40),
|
|
63740
|
+
description: description.slice(0, 120),
|
|
63741
|
+
params: { status }
|
|
63742
|
+
};
|
|
63743
|
+
break;
|
|
63744
|
+
}
|
|
63745
|
+
case "create_handoff_issue": {
|
|
63746
|
+
const title = typeof params.title === "string" ? params.title.trim() : "";
|
|
63747
|
+
if (!title) break;
|
|
63748
|
+
const rawDesc = typeof params.description === "string" ? params.description.trim() : "";
|
|
63749
|
+
const descParam = rawDesc ? rawDesc.replace(/—/g, "-").slice(0, 1e3) : void 0;
|
|
63750
|
+
const priorityRaw = typeof params.priority === "string" ? params.priority : void 0;
|
|
63751
|
+
const priority = priorityRaw && VALID_PRIORITIES2.has(priorityRaw) ? priorityRaw : void 0;
|
|
63752
|
+
validated = {
|
|
63753
|
+
kind: kindTyped,
|
|
63754
|
+
label: label.slice(0, 40),
|
|
63755
|
+
description: description.slice(0, 120),
|
|
63756
|
+
params: {
|
|
63757
|
+
title: title.slice(0, 200),
|
|
63758
|
+
...descParam ? { description: descParam } : {},
|
|
63759
|
+
...priority ? { priority } : {}
|
|
63760
|
+
}
|
|
63761
|
+
};
|
|
63762
|
+
break;
|
|
63763
|
+
}
|
|
63764
|
+
}
|
|
63765
|
+
if (validated) {
|
|
63766
|
+
let cleanLabel = validated.label.replace(/—/g, "-").replace(/–/g, "-").trim();
|
|
63767
|
+
if (cleanLabel.length > 0) {
|
|
63768
|
+
cleanLabel = cleanLabel[0].toUpperCase() + cleanLabel.slice(1);
|
|
63769
|
+
}
|
|
63770
|
+
validated.label = cleanLabel;
|
|
63771
|
+
let cleanDescription = validated.description.replace(/—/g, "-").replace(/–/g, "-").trim();
|
|
63772
|
+
if (cleanDescription.length > 0) {
|
|
63773
|
+
cleanDescription = cleanDescription[0].toUpperCase() + cleanDescription.slice(1);
|
|
63774
|
+
}
|
|
63775
|
+
validated.description = cleanDescription;
|
|
63776
|
+
out.push(validated);
|
|
63777
|
+
if (out.length >= 3) break;
|
|
63778
|
+
}
|
|
63779
|
+
}
|
|
63780
|
+
return out;
|
|
63781
|
+
}
|
|
63782
|
+
function emailSuggestionsService(db) {
|
|
63783
|
+
const emails = emailService(db);
|
|
63784
|
+
const leadsSvc = leadsService(db);
|
|
63785
|
+
async function buildContext2(companyId, threadId) {
|
|
63786
|
+
const detail = await emails.getThreadDetail(companyId, threadId);
|
|
63787
|
+
const messages = detail.messages ?? [];
|
|
63788
|
+
const lastMessages = messages.slice(-3).map((m) => ({
|
|
63789
|
+
direction: m.direction,
|
|
63790
|
+
sentAt: m.sentAt instanceof Date ? m.sentAt.toISOString() : String(m.sentAt),
|
|
63791
|
+
snippet: snippetOf(m)
|
|
63792
|
+
}));
|
|
63793
|
+
let lead = null;
|
|
63794
|
+
let recentActivities = [];
|
|
63795
|
+
if (detail.leadId) {
|
|
63796
|
+
try {
|
|
63797
|
+
const leadDetail = await leadsSvc.get(companyId, detail.leadId);
|
|
63798
|
+
const isoOrNull = (v) => {
|
|
63799
|
+
if (!v) return null;
|
|
63800
|
+
if (v instanceof Date) return v.toISOString();
|
|
63801
|
+
if (typeof v === "string") return v;
|
|
63802
|
+
return null;
|
|
63803
|
+
};
|
|
63804
|
+
lead = {
|
|
63805
|
+
displayName: leadDetail.displayName ?? null,
|
|
63806
|
+
email: leadDetail.email ?? null,
|
|
63807
|
+
status: leadDetail.status ?? null,
|
|
63808
|
+
lastContactedAt: isoOrNull(leadDetail.lastContactedAt),
|
|
63809
|
+
lastReplyAt: isoOrNull(leadDetail.lastReplyAt),
|
|
63810
|
+
everReplied: Boolean(leadDetail.everReplied),
|
|
63811
|
+
everSignedUp: Boolean(leadDetail.everSignedUp),
|
|
63812
|
+
hungrySignal: leadDetail.hungrySignal ?? null,
|
|
63813
|
+
notes: leadDetail.notes ?? null
|
|
63814
|
+
};
|
|
63815
|
+
recentActivities = (leadDetail.activities ?? []).slice(0, 5).map((a) => ({
|
|
63816
|
+
kind: a.kind,
|
|
63817
|
+
occurredAt: isoOrNull(a.occurredAt) ?? ""
|
|
63818
|
+
}));
|
|
63819
|
+
} catch {
|
|
63820
|
+
}
|
|
63821
|
+
}
|
|
63822
|
+
return {
|
|
63823
|
+
threadId: detail.id,
|
|
63824
|
+
subject: detail.subject ?? "(no subject)",
|
|
63825
|
+
participants: (detail.participants ?? []).map(
|
|
63826
|
+
(p17) => typeof p17 === "string" ? p17 : p17.email ?? ""
|
|
63827
|
+
).filter((s) => s.length > 0),
|
|
63828
|
+
lastMessages,
|
|
63829
|
+
lead,
|
|
63830
|
+
recentActivities
|
|
63831
|
+
};
|
|
63832
|
+
}
|
|
63833
|
+
async function generate(companyId, threadId) {
|
|
63834
|
+
const ctx = await buildContext2(companyId, threadId);
|
|
63835
|
+
const prompt = `${SYSTEM_PROMPT2}
|
|
63836
|
+
|
|
63837
|
+
${buildPromptBody2(ctx)}`;
|
|
63838
|
+
const cliOutput = await runClaudeOneShot(prompt, {
|
|
63839
|
+
model: EMAIL_SUGGESTION_MODEL,
|
|
63840
|
+
timeoutMs: EMAIL_SUGGESTION_TIMEOUT_MS
|
|
63841
|
+
});
|
|
63842
|
+
const text87 = extractAssistantText(cliOutput);
|
|
63843
|
+
const raw = extractJsonObject(text87);
|
|
63844
|
+
const actions = validateActions2(raw);
|
|
63845
|
+
const rawArr = raw?.actions;
|
|
63846
|
+
const rawCount = Array.isArray(rawArr) ? rawArr.length : 0;
|
|
63847
|
+
console.log(
|
|
63848
|
+
"[email-suggestions] thread=%s rawCount=%d validCount=%d raw=%s",
|
|
63849
|
+
threadId,
|
|
63850
|
+
rawCount,
|
|
63851
|
+
actions.length,
|
|
63852
|
+
JSON.stringify(raw).slice(0, 1500)
|
|
63853
|
+
);
|
|
63854
|
+
const rawReasoning = raw?.reasoning;
|
|
63855
|
+
const reasoning = typeof rawReasoning === "string" && rawReasoning.trim().length > 0 ? rawReasoning.trim().replace(/—/g, "-").replace(/–/g, "-").slice(0, 400) : null;
|
|
63856
|
+
if (actions.length === 0) {
|
|
63857
|
+
throw new SuggestionsUnavailableError("no_valid_actions");
|
|
63858
|
+
}
|
|
63859
|
+
return {
|
|
63860
|
+
actions,
|
|
63861
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
63862
|
+
eventId: randomUUID13(),
|
|
63863
|
+
reasoning
|
|
63864
|
+
};
|
|
63865
|
+
}
|
|
63866
|
+
return { generate, buildContext: buildContext2 };
|
|
63867
|
+
}
|
|
63868
|
+
var EMAIL_SUGGESTION_MODEL, EMAIL_SUGGESTION_TIMEOUT_MS, VALID_KINDS2, VALID_LEAD_STATUSES, VALID_PRIORITIES2, SYSTEM_PROMPT2;
|
|
63869
|
+
var init_email_suggestions = __esm({
|
|
63870
|
+
"../server/src/services/email-suggestions.ts"() {
|
|
63871
|
+
"use strict";
|
|
63872
|
+
init_email_service();
|
|
63873
|
+
init_leads2();
|
|
63874
|
+
init_issue_suggestions();
|
|
63875
|
+
EMAIL_SUGGESTION_MODEL = process.env.PAPERCLIP_EMAIL_SUGGESTION_MODEL ?? process.env.PAPERCLIP_SUGGESTION_MODEL ?? "claude-haiku-4-5";
|
|
63876
|
+
EMAIL_SUGGESTION_TIMEOUT_MS = Number(
|
|
63877
|
+
process.env.PAPERCLIP_EMAIL_SUGGESTION_TIMEOUT_MS ?? "60000"
|
|
63878
|
+
);
|
|
63879
|
+
VALID_KINDS2 = /* @__PURE__ */ new Set([
|
|
63880
|
+
"do_nothing",
|
|
63881
|
+
"send_follow_up",
|
|
63882
|
+
"archive_thread",
|
|
63883
|
+
"mark_lead_status",
|
|
63884
|
+
"create_handoff_issue"
|
|
63885
|
+
]);
|
|
63886
|
+
VALID_LEAD_STATUSES = /* @__PURE__ */ new Set([
|
|
63887
|
+
"drafted",
|
|
63888
|
+
"contacted",
|
|
63889
|
+
"replied",
|
|
63890
|
+
"signed_up",
|
|
63891
|
+
"dead"
|
|
63892
|
+
]);
|
|
63893
|
+
VALID_PRIORITIES2 = /* @__PURE__ */ new Set(["urgent", "high", "medium", "low"]);
|
|
63894
|
+
SYSTEM_PROMPT2 = `Suggest 1-3 next actions for a human operator viewing an email thread in Paperclip.
|
|
63895
|
+
Be decisive and concrete. Pick the most useful single action when one is obvious, never pad.
|
|
63896
|
+
Heuristics:
|
|
63897
|
+
- If we contacted them less than 3 days ago and got no reply, "do_nothing" is usually correct.
|
|
63898
|
+
- If they replied recently, suggest "send_follow_up" with a tight draftBody.
|
|
63899
|
+
- If it has been more than 7 days since our last touch with no reply, suggest "send_follow_up".
|
|
63900
|
+
- If they explicitly declined, suggest "mark_lead_status" with status=dead.
|
|
63901
|
+
- If a human needs to take an external action (not just email), use "create_handoff_issue".
|
|
63902
|
+
|
|
63903
|
+
Style: no em dashes (use hyphens or commas). Labels under 40 chars. Descriptions under 120 chars.
|
|
63904
|
+
Respond with JSON ONLY, no prose before or after: {"reasoning":"...","actions":[...]}`;
|
|
63905
|
+
}
|
|
63906
|
+
});
|
|
63907
|
+
|
|
63549
63908
|
// ../server/src/routes/emails.ts
|
|
63909
|
+
import { randomUUID as randomUUID14 } from "node:crypto";
|
|
63550
63910
|
import { Router as Router14 } from "express";
|
|
63551
63911
|
import { z as z31 } from "zod";
|
|
63552
63912
|
function normalizeHeaders(headers) {
|
|
@@ -63563,6 +63923,7 @@ function normalizeHeaders(headers) {
|
|
|
63563
63923
|
function emailRoutes(db) {
|
|
63564
63924
|
const router = Router14();
|
|
63565
63925
|
const svc = emailService(db);
|
|
63926
|
+
const suggestionsSvc = emailSuggestionsService(db);
|
|
63566
63927
|
router.get("/companies/:companyId/email-threads", async (req, res) => {
|
|
63567
63928
|
const companyId = req.params.companyId;
|
|
63568
63929
|
assertCompanyAccess(req, companyId);
|
|
@@ -63645,6 +64006,60 @@ function emailRoutes(db) {
|
|
|
63645
64006
|
});
|
|
63646
64007
|
res.status(201).json(thread);
|
|
63647
64008
|
});
|
|
64009
|
+
router.post(
|
|
64010
|
+
"/companies/:companyId/email-threads/:threadId/suggested-actions",
|
|
64011
|
+
async (req, res) => {
|
|
64012
|
+
const { companyId, threadId } = req.params;
|
|
64013
|
+
assertCompanyAccess(req, companyId);
|
|
64014
|
+
try {
|
|
64015
|
+
const result = await suggestionsSvc.generate(companyId, threadId);
|
|
64016
|
+
const actor = getActorInfo(req);
|
|
64017
|
+
await logActivity(db, {
|
|
64018
|
+
companyId,
|
|
64019
|
+
actorType: actor.actorType,
|
|
64020
|
+
actorId: actor.actorId,
|
|
64021
|
+
agentId: actor.agentId,
|
|
64022
|
+
runId: actor.runId,
|
|
64023
|
+
action: "email.thread.suggestions_generated",
|
|
64024
|
+
entityType: "email_thread",
|
|
64025
|
+
entityId: threadId,
|
|
64026
|
+
details: {
|
|
64027
|
+
eventId: result.eventId,
|
|
64028
|
+
actionCount: result.actions.length,
|
|
64029
|
+
actions: result.actions.map((a) => ({
|
|
64030
|
+
kind: a.kind,
|
|
64031
|
+
label: a.label,
|
|
64032
|
+
description: a.description,
|
|
64033
|
+
params: a.params
|
|
64034
|
+
})),
|
|
64035
|
+
generatedAt: result.generatedAt
|
|
64036
|
+
}
|
|
64037
|
+
});
|
|
64038
|
+
res.json({
|
|
64039
|
+
actions: result.actions,
|
|
64040
|
+
generatedAt: result.generatedAt,
|
|
64041
|
+
eventId: result.eventId,
|
|
64042
|
+
...result.reasoning ? { reasoning: result.reasoning } : {}
|
|
64043
|
+
});
|
|
64044
|
+
} catch (err) {
|
|
64045
|
+
if (err instanceof SuggestionsUnavailableError) {
|
|
64046
|
+
const correlationId = randomUUID14();
|
|
64047
|
+
logger.warn(
|
|
64048
|
+
{ threadId, reason: err.reason, message: err.message, correlationId },
|
|
64049
|
+
"email_suggestions_unavailable"
|
|
64050
|
+
);
|
|
64051
|
+
res.status(503).json({
|
|
64052
|
+
error: "suggestions_unavailable",
|
|
64053
|
+
reason: err.reason,
|
|
64054
|
+
message: err.message,
|
|
64055
|
+
correlationId
|
|
64056
|
+
});
|
|
64057
|
+
return;
|
|
64058
|
+
}
|
|
64059
|
+
throw err;
|
|
64060
|
+
}
|
|
64061
|
+
}
|
|
64062
|
+
);
|
|
63648
64063
|
router.post("/webhooks/email/:provider", async (req, res) => {
|
|
63649
64064
|
const providerId = String(req.params.provider ?? "").trim();
|
|
63650
64065
|
const rawBody = req.rawBody;
|
|
@@ -63672,12 +64087,16 @@ var init_emails2 = __esm({
|
|
|
63672
64087
|
init_errors();
|
|
63673
64088
|
init_activity_log2();
|
|
63674
64089
|
init_email_service();
|
|
64090
|
+
init_email_suggestions();
|
|
64091
|
+
init_issue_suggestions();
|
|
64092
|
+
init_logger();
|
|
63675
64093
|
init_authz();
|
|
63676
64094
|
listThreadsQuerySchema = z31.object({
|
|
63677
64095
|
agentId: z31.string().uuid().optional(),
|
|
63678
64096
|
leadId: z31.string().uuid().optional(),
|
|
63679
64097
|
archived: z31.enum(["true", "false"]).optional().transform((value) => value === void 0 ? void 0 : value === "true"),
|
|
63680
64098
|
unread: z31.enum(["true", "false"]).optional().transform((value) => value === void 0 ? void 0 : value === "true"),
|
|
64099
|
+
direction: z31.enum(["inbound", "outbound"]).optional(),
|
|
63681
64100
|
search: z31.string().trim().min(1).optional(),
|
|
63682
64101
|
cursor: z31.string().trim().min(1).optional(),
|
|
63683
64102
|
limit: z31.coerce.number().int().min(1).max(200).optional()
|
|
@@ -63719,6 +64138,7 @@ function leadRoutes(db) {
|
|
|
63719
64138
|
const companyId = req.params.companyId;
|
|
63720
64139
|
assertCompanyAccess(req, companyId);
|
|
63721
64140
|
const labelIds = String(req.query.labelIds ?? "").split(",").map((value) => value.trim()).filter(Boolean);
|
|
64141
|
+
const contactMethods = String(req.query.contactMethods ?? "").split(",").map((value) => value.trim()).filter(Boolean);
|
|
63722
64142
|
const includeTotal = String(req.query.includeTotal ?? "") === "true";
|
|
63723
64143
|
const sortKey = req.query.sortKey?.trim() || void 0;
|
|
63724
64144
|
const sortDirRaw = req.query.sortDir;
|
|
@@ -63728,6 +64148,7 @@ function leadRoutes(db) {
|
|
|
63728
64148
|
status: req.query.status || void 0,
|
|
63729
64149
|
search: req.query.q || void 0,
|
|
63730
64150
|
labelIds,
|
|
64151
|
+
contactMethods,
|
|
63731
64152
|
organizationId: req.query.organizationId || void 0,
|
|
63732
64153
|
hideDead: String(req.query.hideDead ?? "") === "true",
|
|
63733
64154
|
platform: req.query.platform || void 0,
|
|
@@ -63763,12 +64184,14 @@ function leadRoutes(db) {
|
|
|
63763
64184
|
assertCompanyAccess(req, companyId);
|
|
63764
64185
|
const daysParam = Number(req.query.days ?? 30);
|
|
63765
64186
|
const labelIds = String(req.query.labelIds ?? "").split(",").map((value) => value.trim()).filter(Boolean);
|
|
64187
|
+
const contactMethods = String(req.query.contactMethods ?? "").split(",").map((value) => value.trim()).filter(Boolean);
|
|
63766
64188
|
const stats = await svc.activityStats(companyId, {
|
|
63767
64189
|
days: Number.isFinite(daysParam) ? daysParam : 30,
|
|
63768
64190
|
kind: req.query.kind || void 0,
|
|
63769
64191
|
status: req.query.status || void 0,
|
|
63770
64192
|
search: req.query.q || void 0,
|
|
63771
64193
|
labelIds,
|
|
64194
|
+
contactMethods,
|
|
63772
64195
|
hideDead: String(req.query.hideDead ?? "") === "true"
|
|
63773
64196
|
});
|
|
63774
64197
|
res.json(stats);
|
|
@@ -63920,7 +64343,7 @@ var init_lead_kinds3 = __esm({
|
|
|
63920
64343
|
});
|
|
63921
64344
|
|
|
63922
64345
|
// ../server/src/services/content.ts
|
|
63923
|
-
import { and as and47, desc as desc29, eq as eq50, gte as gte9, inArray as inArray26, lte as lte6, not as not3, or as
|
|
64346
|
+
import { and as and47, desc as desc29, eq as eq50, gte as gte9, inArray as inArray26, lte as lte6, not as not3, or as or13, sql as sql35 } from "drizzle-orm";
|
|
63924
64347
|
import { execFile as execFile13 } from "node:child_process";
|
|
63925
64348
|
function assertKind(value) {
|
|
63926
64349
|
if (!SUPPORTED_KINDS.includes(value)) {
|
|
@@ -63956,9 +64379,9 @@ function contentListMediaTypeWhere(mediaType) {
|
|
|
63956
64379
|
select 1 from jsonb_array_elements(${contents.media}) as elem
|
|
63957
64380
|
where coalesce(elem->>'mimeType', '') ilike 'audio/%'
|
|
63958
64381
|
)`;
|
|
63959
|
-
const imageSignal =
|
|
63960
|
-
const videoSignal =
|
|
63961
|
-
const audioSignal =
|
|
64382
|
+
const imageSignal = or13(inArray26(contents.kind, [...CONTENT_IMAGE_KINDS]), imageMime);
|
|
64383
|
+
const videoSignal = or13(inArray26(contents.kind, [...CONTENT_VIDEO_KINDS]), videoMime);
|
|
64384
|
+
const audioSignal = or13(inArray26(contents.kind, [...CONTENT_AUDIO_KINDS]), audioMime);
|
|
63962
64385
|
switch (mediaType) {
|
|
63963
64386
|
case "carousel":
|
|
63964
64387
|
return isCarouselKind;
|
|
@@ -66133,7 +66556,7 @@ var init_content2 = __esm({
|
|
|
66133
66556
|
});
|
|
66134
66557
|
|
|
66135
66558
|
// ../server/src/services/calendar.ts
|
|
66136
|
-
import { and as and50, asc as asc20, eq as eq53, gte as gte10, inArray as inArray28, lte as lte7, or as
|
|
66559
|
+
import { and as and50, asc as asc20, eq as eq53, gte as gte10, inArray as inArray28, lte as lte7, or as or14, sql as sql36 } from "drizzle-orm";
|
|
66137
66560
|
function cleanNullable4(value) {
|
|
66138
66561
|
if (value == null) return null;
|
|
66139
66562
|
const trimmed = value.trim();
|
|
@@ -66219,7 +66642,7 @@ function calendarService(db) {
|
|
|
66219
66642
|
];
|
|
66220
66643
|
if (options2.assignees && options2.assignees.length > 0) {
|
|
66221
66644
|
where.push(
|
|
66222
|
-
|
|
66645
|
+
or14(
|
|
66223
66646
|
inArray28(issues.assigneeAgentId, options2.assignees),
|
|
66224
66647
|
inArray28(issues.assigneeUserId, options2.assignees)
|
|
66225
66648
|
)
|
|
@@ -66260,7 +66683,7 @@ function calendarService(db) {
|
|
|
66260
66683
|
async function fetchContentItems(companyId, from, to, options2, limit) {
|
|
66261
66684
|
const where = [
|
|
66262
66685
|
eq53(contents.companyId, companyId),
|
|
66263
|
-
|
|
66686
|
+
or14(
|
|
66264
66687
|
and50(
|
|
66265
66688
|
sql36`${contents.scheduledAt} is not null`,
|
|
66266
66689
|
gte10(contents.scheduledAt, from),
|
|
@@ -66281,7 +66704,7 @@ function calendarService(db) {
|
|
|
66281
66704
|
}
|
|
66282
66705
|
if (options2.assignees && options2.assignees.length > 0) {
|
|
66283
66706
|
where.push(
|
|
66284
|
-
|
|
66707
|
+
or14(
|
|
66285
66708
|
inArray28(contents.assignedAgentId, options2.assignees),
|
|
66286
66709
|
inArray28(contents.assignedUserId, options2.assignees)
|
|
66287
66710
|
)
|
|
@@ -68089,7 +68512,7 @@ import { readFile as readFile4 } from "node:fs/promises";
|
|
|
68089
68512
|
import path59 from "node:path";
|
|
68090
68513
|
import { fileURLToPath as fileURLToPath16 } from "node:url";
|
|
68091
68514
|
import { and as and52, desc as desc32, eq as eq55, gte as gte11, inArray as inArray30, lte as lte8, ne as ne10, sql as sql38 } from "drizzle-orm";
|
|
68092
|
-
import { randomUUID as
|
|
68515
|
+
import { randomUUID as randomUUID15 } from "node:crypto";
|
|
68093
68516
|
function readNonEmptyConfigString2(value) {
|
|
68094
68517
|
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
68095
68518
|
}
|
|
@@ -68799,7 +69222,14 @@ function chatTriageService(db, storage) {
|
|
|
68799
69222
|
const validAgentIds = new Set(ctx.agents.map((a) => a.id));
|
|
68800
69223
|
const agentNameById = new Map(ctx.agents.map((a) => [a.id, a.name]));
|
|
68801
69224
|
const parts = [];
|
|
68802
|
-
parts.push(
|
|
69225
|
+
parts.push(SYSTEM_PROMPT3);
|
|
69226
|
+
{
|
|
69227
|
+
const { tz, localNow } = resolveOperatorLocalNow(options2?.operatorTimezone);
|
|
69228
|
+
parts.push("");
|
|
69229
|
+
parts.push(
|
|
69230
|
+
`Operator local timezone: ${tz}. Current local time: ${localNow}. When you state any time ("posted at", "scheduled for", "today", "yesterday", "X minutes ago"), convert from UTC ISO timestamps to this timezone before speaking. Never quote a raw UTC time to the operator.`
|
|
69231
|
+
);
|
|
69232
|
+
}
|
|
68803
69233
|
if (mode === "live") {
|
|
68804
69234
|
parts.push("");
|
|
68805
69235
|
parts.push(LIVE_MODE_SYSTEM_ADDENDUM);
|
|
@@ -68865,7 +69295,7 @@ ${message}`);
|
|
|
68865
69295
|
parts.push("Respond with JSON only.");
|
|
68866
69296
|
if (toolUseEnabled) {
|
|
68867
69297
|
const targetAgentId = options2?.targetAgentId ?? null;
|
|
68868
|
-
const jwt = targetAgentId ? createLocalAgentJwt(targetAgentId, companyId, "claude_local",
|
|
69298
|
+
const jwt = targetAgentId ? createLocalAgentJwt(targetAgentId, companyId, "claude_local", randomUUID15()) : null;
|
|
68869
69299
|
if (targetAgentId && !jwt) {
|
|
68870
69300
|
toolUseFallbackReason = "agent_auth_unavailable";
|
|
68871
69301
|
toolUseFallbackAdapterType = adapterType;
|
|
@@ -68920,7 +69350,7 @@ ${message}`);
|
|
|
68920
69350
|
const entries = parseClaudeStdoutLine(line, "");
|
|
68921
69351
|
for (const entry of entries) {
|
|
68922
69352
|
if (entry.kind === "tool_call") {
|
|
68923
|
-
const id = entry.toolUseId ?? `${entry.name}:${
|
|
69353
|
+
const id = entry.toolUseId ?? `${entry.name}:${randomUUID15()}`;
|
|
68924
69354
|
const input = entry.input && typeof entry.input === "object" && !Array.isArray(entry.input) ? entry.input : {};
|
|
68925
69355
|
onToolEvent({ kind: "tool_call", id, name: entry.name, input });
|
|
68926
69356
|
} else if (entry.kind === "tool_result" && entry.toolUseId) {
|
|
@@ -69009,12 +69439,13 @@ ${message}`);
|
|
|
69009
69439
|
}
|
|
69010
69440
|
return { triage };
|
|
69011
69441
|
}
|
|
69012
|
-
var CHAT_STYLE_MAX_BYTES, SKILL_PRIMER_MAX_BYTES, RECENT_COMMENT_LIMIT, COMMENT_BODY_PREVIEW_CHARS, CHAT_TRIAGE_MODEL, CHAT_TRIAGE_TIMEOUT_MS, PRIORITY_VALUES, GOAL_LEVEL_VALUES, LEAD_KIND_VALUES, LEAD_PLATFORM_VALUES, MAX_PROPOSALS_TOTAL, CONTEXT_CACHE_TTL_MS, CONTEXT_CACHE_MAX_SIZE, contextCache,
|
|
69442
|
+
var CHAT_STYLE_MAX_BYTES, SKILL_PRIMER_MAX_BYTES, RECENT_COMMENT_LIMIT, COMMENT_BODY_PREVIEW_CHARS, CHAT_TRIAGE_MODEL, CHAT_TRIAGE_TIMEOUT_MS, PRIORITY_VALUES, GOAL_LEVEL_VALUES, LEAD_KIND_VALUES, LEAD_PLATFORM_VALUES, MAX_PROPOSALS_TOTAL, CONTEXT_CACHE_TTL_MS, CONTEXT_CACHE_MAX_SIZE, contextCache, SYSTEM_PROMPT3, CHAT_PERSONA_MAX_CHARS, PERSONA_MODE_REPLY_HINT, MAX_ENV_KEYS_PER_AGENT, cachedChatStyleOverride, cachedSkillPrimer, VISION_IMAGE_MIME_TYPES, PDF_MIME_TYPE, ATTACHMENT_TOTAL_BYTE_CAP, LIVE_MODE_SYSTEM_ADDENDUM, VOICE_CALL_REPLY_ADDENDUM, POST_CALL_MODE_SYSTEM_ADDENDUM;
|
|
69013
69443
|
var init_chat_triage = __esm({
|
|
69014
69444
|
"../server/src/services/chat-triage.ts"() {
|
|
69015
69445
|
"use strict";
|
|
69016
69446
|
init_dist2();
|
|
69017
69447
|
init_issue_suggestions();
|
|
69448
|
+
init_dist();
|
|
69018
69449
|
init_agent_instructions();
|
|
69019
69450
|
init_dashboard();
|
|
69020
69451
|
init_acquisition();
|
|
@@ -69056,7 +69487,7 @@ var init_chat_triage = __esm({
|
|
|
69056
69487
|
CONTEXT_CACHE_TTL_MS = 6e4;
|
|
69057
69488
|
CONTEXT_CACHE_MAX_SIZE = 32;
|
|
69058
69489
|
contextCache = /* @__PURE__ */ new Map();
|
|
69059
|
-
|
|
69490
|
+
SYSTEM_PROMPT3 = `You are an assistant embedded inside Paperclip, the operator's control plane for a company of AI agents. The operator is talking to you to think out loud, ask questions about their company, and occasionally capture work.
|
|
69060
69491
|
|
|
69061
69492
|
Your primary job is to be a good conversational partner. Most turns you will just reply with text - answer questions, ask clarifying follow-ups, explore ideas together. Tables in the reply still count as conversational output when they are the clearest shape. Only when something sounds like a concrete artifact the operator wants captured should you propose creating one.
|
|
69062
69493
|
|
|
@@ -69340,12 +69771,13 @@ function chatRoutes(db, storageService, bridgeDeps) {
|
|
|
69340
69771
|
mode: parsed.data.mode,
|
|
69341
69772
|
previousProposalFingerprints: parsed.data.previousProposalFingerprints,
|
|
69342
69773
|
voiceCallActive,
|
|
69774
|
+
operatorTimezone: parsed.data.operatorTimezone,
|
|
69343
69775
|
onToolEvent: wantsStream ? (event) => writeNdjson({ type: "tool_event", event }) : void 0
|
|
69344
69776
|
}
|
|
69345
69777
|
);
|
|
69346
69778
|
let continuationRunId = null;
|
|
69347
69779
|
let continuationScheduled = false;
|
|
69348
|
-
if (result.continuation && parsed.data.sessionId && parsed.data.userMessageId) {
|
|
69780
|
+
if (result.continuation && parsed.data.sessionId && parsed.data.userMessageId && !voiceCallActive) {
|
|
69349
69781
|
const continuationSession = await sessionSvc.getSession(companyId, parsed.data.sessionId);
|
|
69350
69782
|
const preferredAgentId = result.continuation.preferredAgentId ?? continuationSession?.targetAgentId ?? await db.select({ id: agents.id }).from(agents).where(
|
|
69351
69783
|
and53(
|
|
@@ -69795,7 +70227,8 @@ var init_chat2 = __esm({
|
|
|
69795
70227
|
userMessageId: z34.string().uuid().optional(),
|
|
69796
70228
|
attachments: z34.array(triageAttachmentSchema).max(8).optional(),
|
|
69797
70229
|
mode: z34.enum(["full", "live"]).optional(),
|
|
69798
|
-
previousProposalFingerprints: z34.array(z34.string().min(1).max(200)).max(50).optional()
|
|
70230
|
+
previousProposalFingerprints: z34.array(z34.string().min(1).max(200)).max(50).optional(),
|
|
70231
|
+
operatorTimezone: z34.string().min(1).max(100).optional()
|
|
69799
70232
|
});
|
|
69800
70233
|
blockMetadataSchema = z34.record(z34.string(), z34.unknown()).optional();
|
|
69801
70234
|
blockSchema = z34.discriminatedUnion("type", [
|
|
@@ -75681,7 +76114,7 @@ var init_plugin_lifecycle = __esm({
|
|
|
75681
76114
|
});
|
|
75682
76115
|
|
|
75683
76116
|
// ../server/src/services/voice-intent-dispatcher.ts
|
|
75684
|
-
import { randomUUID as
|
|
76117
|
+
import { randomUUID as randomUUID16 } from "node:crypto";
|
|
75685
76118
|
import { and as and58, eq as eq61 } from "drizzle-orm";
|
|
75686
76119
|
function gcPendingResults(now) {
|
|
75687
76120
|
for (const [key, value] of pendingResults) {
|
|
@@ -75705,19 +76138,20 @@ async function loadAgentAdapterConfig(db, companyId, agentId) {
|
|
|
75705
76138
|
return row2.adapterConfig;
|
|
75706
76139
|
}
|
|
75707
76140
|
async function dispatchVoiceIntent(deps, input) {
|
|
75708
|
-
const { db, onComplete } = deps;
|
|
76141
|
+
const { db, onComplete, onToolEvent } = deps;
|
|
75709
76142
|
const intent = readNonEmpty(input.intent);
|
|
75710
76143
|
const companyId = readNonEmpty(input.companyId);
|
|
75711
76144
|
const agentId = readNonEmpty(input.agentId);
|
|
76145
|
+
const operatorTimezone = readNonEmpty(input.operatorTimezone);
|
|
75712
76146
|
const chatSessionId = readNonEmpty(input.chatSessionId);
|
|
75713
76147
|
const surface = input.surface ?? "voice";
|
|
75714
76148
|
const correlationId = readNonEmpty(input.correlationId) ?? null;
|
|
75715
76149
|
const sessions2 = chatSessionService(db);
|
|
75716
|
-
const append = async (text87) => {
|
|
76150
|
+
const append = async (text87, role = "system") => {
|
|
75717
76151
|
if (!chatSessionId || !companyId) return;
|
|
75718
76152
|
try {
|
|
75719
76153
|
await sessions2.appendMessage(companyId, chatSessionId, {
|
|
75720
|
-
role
|
|
76154
|
+
role,
|
|
75721
76155
|
content: [
|
|
75722
76156
|
{
|
|
75723
76157
|
type: "text",
|
|
@@ -75754,7 +76188,7 @@ async function dispatchVoiceIntent(deps, input) {
|
|
|
75754
76188
|
finish(text87, false);
|
|
75755
76189
|
return;
|
|
75756
76190
|
}
|
|
75757
|
-
const jwt = createLocalAgentJwt(agentId, companyId, "claude_local",
|
|
76191
|
+
const jwt = createLocalAgentJwt(agentId, companyId, "claude_local", randomUUID16());
|
|
75758
76192
|
if (!jwt) {
|
|
75759
76193
|
const text87 = `[${getBrandLogTag()}: agent auth unavailable, try again after the call]`;
|
|
75760
76194
|
await append(text87);
|
|
@@ -75772,13 +76206,52 @@ async function dispatchVoiceIntent(deps, input) {
|
|
|
75772
76206
|
finish(text87, false);
|
|
75773
76207
|
return;
|
|
75774
76208
|
}
|
|
76209
|
+
const { tz, localNow } = resolveOperatorLocalNow(operatorTimezone);
|
|
75775
76210
|
const prompt = [
|
|
75776
|
-
|
|
76211
|
+
SYSTEM_PROMPT4,
|
|
76212
|
+
"",
|
|
76213
|
+
`Operator local timezone: ${tz}. Current local time: ${localNow}. Convert every UTC ISO timestamp you encounter to this timezone before speaking it.`,
|
|
75777
76214
|
"",
|
|
75778
76215
|
`Operator intent: ${intent}`,
|
|
75779
76216
|
"",
|
|
75780
76217
|
"Reply with the final plain-text answer only."
|
|
75781
76218
|
].join("\n");
|
|
76219
|
+
const onStdoutLine = onToolEvent ? (line) => {
|
|
76220
|
+
const entries = parseClaudeStdoutLine(line, "");
|
|
76221
|
+
for (const entry of entries) {
|
|
76222
|
+
if (entry.kind === "tool_call") {
|
|
76223
|
+
const id = entry.toolUseId ?? `${entry.name}:${randomUUID16()}`;
|
|
76224
|
+
const inputObj = entry.input && typeof entry.input === "object" && !Array.isArray(entry.input) ? entry.input : {};
|
|
76225
|
+
try {
|
|
76226
|
+
onToolEvent({
|
|
76227
|
+
kind: "tool_call",
|
|
76228
|
+
companyId,
|
|
76229
|
+
agentId,
|
|
76230
|
+
chatSessionId,
|
|
76231
|
+
correlationId,
|
|
76232
|
+
id,
|
|
76233
|
+
name: entry.name,
|
|
76234
|
+
input: inputObj
|
|
76235
|
+
});
|
|
76236
|
+
} catch {
|
|
76237
|
+
}
|
|
76238
|
+
} else if (entry.kind === "tool_result" && entry.toolUseId) {
|
|
76239
|
+
try {
|
|
76240
|
+
onToolEvent({
|
|
76241
|
+
kind: "tool_result",
|
|
76242
|
+
companyId,
|
|
76243
|
+
agentId,
|
|
76244
|
+
chatSessionId,
|
|
76245
|
+
correlationId,
|
|
76246
|
+
id: entry.toolUseId,
|
|
76247
|
+
content: entry.content,
|
|
76248
|
+
isError: entry.isError
|
|
76249
|
+
});
|
|
76250
|
+
} catch {
|
|
76251
|
+
}
|
|
76252
|
+
}
|
|
76253
|
+
}
|
|
76254
|
+
} : void 0;
|
|
75782
76255
|
try {
|
|
75783
76256
|
const cliOutput = await runClaudeOneShot(prompt, {
|
|
75784
76257
|
model: DISPATCH_MODEL,
|
|
@@ -75793,13 +76266,18 @@ async function dispatchVoiceIntent(deps, input) {
|
|
|
75793
76266
|
PAPERCLIP_API_URL: process.env.PAPERCLIP_API_URL ?? "http://127.0.0.1:3100",
|
|
75794
76267
|
PAPERCLIP_COMPANY_ID: companyId,
|
|
75795
76268
|
PAPERCLIP_AGENT_ID: agentId
|
|
75796
|
-
}
|
|
76269
|
+
},
|
|
76270
|
+
onStdoutLine
|
|
75797
76271
|
});
|
|
75798
76272
|
const finalText = extractFinalText(cliOutput).trim();
|
|
75799
|
-
|
|
75800
|
-
|
|
75801
|
-
|
|
75802
|
-
|
|
76273
|
+
if (finalText.length > 0) {
|
|
76274
|
+
await append(finalText, "system");
|
|
76275
|
+
finish(finalText, true);
|
|
76276
|
+
} else {
|
|
76277
|
+
const text87 = `[${getBrandLogTag()}: no response]`;
|
|
76278
|
+
await append(text87);
|
|
76279
|
+
finish(text87, false);
|
|
76280
|
+
}
|
|
75803
76281
|
} catch (err) {
|
|
75804
76282
|
const text87 = `[${getBrandLogTag()}: ${err.message ?? "dispatch failed"} \u2014 try after the call]`;
|
|
75805
76283
|
await append(text87);
|
|
@@ -75842,12 +76320,14 @@ function extractFinalText(cliStdout) {
|
|
|
75842
76320
|
}
|
|
75843
76321
|
return trimmed;
|
|
75844
76322
|
}
|
|
75845
|
-
var DISPATCH_MAX_TURNS, DISPATCH_TIMEOUT_MS, DISPATCH_MODEL, PENDING_RESULT_TTL_MS, pendingResults,
|
|
76323
|
+
var DISPATCH_MAX_TURNS, DISPATCH_TIMEOUT_MS, DISPATCH_MODEL, PENDING_RESULT_TTL_MS, pendingResults, SYSTEM_PROMPT4;
|
|
75846
76324
|
var init_voice_intent_dispatcher = __esm({
|
|
75847
76325
|
"../server/src/services/voice-intent-dispatcher.ts"() {
|
|
75848
76326
|
"use strict";
|
|
75849
76327
|
init_dist2();
|
|
75850
76328
|
init_server2();
|
|
76329
|
+
init_ui();
|
|
76330
|
+
init_dist();
|
|
75851
76331
|
init_issue_suggestions();
|
|
75852
76332
|
init_agent_auth_jwt();
|
|
75853
76333
|
init_brand2();
|
|
@@ -75857,10 +76337,14 @@ var init_voice_intent_dispatcher = __esm({
|
|
|
75857
76337
|
DISPATCH_MODEL = "claude-haiku-4-5-20251001";
|
|
75858
76338
|
PENDING_RESULT_TTL_MS = 5 * 60 * 1e3;
|
|
75859
76339
|
pendingResults = /* @__PURE__ */ new Map();
|
|
75860
|
-
|
|
76340
|
+
SYSTEM_PROMPT4 = `You are speaking with a Paperclip operator on a live voice call. They just asked you to read or change something via the live-call paperclip tool. Run any reads/writes you need against the local API using the bundled paperclip skill, then return ONE final plain-text answer your voice can speak back conversationally \u2014 at most two short sentences, no markdown, no JSON, no list bullets.
|
|
75861
76341
|
|
|
75862
76342
|
If the intent has multiple parts (e.g. "cancel and add a comment", "update status and notify the assignee", "close the issue and leave a note"), you MUST do EVERY part. Each write is a separate API call \u2014 don't skip the second half just because the first one succeeded. If a part can't be done, say which part failed in your final answer.
|
|
75863
76343
|
|
|
76344
|
+
For content / posts / published-state questions ("what content did we post today?", "what's scheduled?", "did the X post go out?"), query GET /api/companies/{companyId}/content (see the paperclip-extras skill for filters and the postedAt/scheduledAt fields \u2014 list endpoint returns a bare JSON array, status enum is drafted/in_review/approved/scheduled/publishing/posted/failed/cancelled). NEVER answer content questions from issue data \u2014 content and issues are different entities.
|
|
76345
|
+
|
|
76346
|
+
Time handling: server timestamps are UTC ISO strings. The operator's local timezone is injected into your prompt below. ALWAYS convert UTC timestamps to that timezone before stating them. Never say "02:38" when the operator is in Bangkok and the local time was 09:38.
|
|
76347
|
+
|
|
75864
76348
|
Style: like a teammate giving a quick verbal answer. If you did a write, confirm it crisply and name what you changed. If you read state, name the top 1-3 items. Skip preamble like "I checked" or "Here's what I found" \u2014 just say it. If you couldn't do it, say so honestly in one sentence.
|
|
75865
76349
|
|
|
75866
76350
|
Writes attribute to you, the agent the operator is talking to.`;
|
|
@@ -76664,7 +77148,7 @@ var init_plugin_config_secret_normalizer = __esm({
|
|
|
76664
77148
|
// ../server/src/routes/plugins.ts
|
|
76665
77149
|
import { existsSync as existsSync10, readFileSync as readFileSync6 } from "node:fs";
|
|
76666
77150
|
import path64 from "node:path";
|
|
76667
|
-
import { randomUUID as
|
|
77151
|
+
import { randomUUID as randomUUID17 } from "node:crypto";
|
|
76668
77152
|
import { fileURLToPath as fileURLToPath19 } from "node:url";
|
|
76669
77153
|
import { Router as Router36 } from "express";
|
|
76670
77154
|
import { and as and60, desc as desc35, eq as eq63, gte as gte12 } from "drizzle-orm";
|
|
@@ -77862,7 +78346,7 @@ function pluginRoutes(db, loader, jobDeps, webhookDeps, toolDeps, bridgeDeps) {
|
|
|
77862
78346
|
});
|
|
77863
78347
|
return;
|
|
77864
78348
|
}
|
|
77865
|
-
const requestId =
|
|
78349
|
+
const requestId = randomUUID17();
|
|
77866
78350
|
const rawHeaders = {};
|
|
77867
78351
|
for (const [key, value] of Object.entries(req.headers)) {
|
|
77868
78352
|
if (typeof value === "string") {
|
|
@@ -78042,6 +78526,7 @@ function pluginRoutes(db, loader, jobDeps, webhookDeps, toolDeps, bridgeDeps) {
|
|
|
78042
78526
|
const intent = typeof body?.intent === "string" ? body.intent : "";
|
|
78043
78527
|
const surfaceFromBody = body?.surface === "runway" || body?.surface === "realtime-voice" || body?.surface === "voice" ? body.surface : void 0;
|
|
78044
78528
|
const correlationId = typeof body?.correlationId === "string" && body.correlationId.trim().length > 0 ? body.correlationId.trim() : void 0;
|
|
78529
|
+
const operatorTimezone = typeof body?.operatorTimezone === "string" && body.operatorTimezone.trim().length > 0 ? body.operatorTimezone.trim() : void 0;
|
|
78045
78530
|
if (!companyId || !agentId || !chatSessionId || !intent) {
|
|
78046
78531
|
res.status(400).json({ error: "companyId, agentId, chatSessionId, and intent are required" });
|
|
78047
78532
|
return;
|
|
@@ -78065,15 +78550,41 @@ function pluginRoutes(db, loader, jobDeps, webhookDeps, toolDeps, bridgeDeps) {
|
|
|
78065
78550
|
}
|
|
78066
78551
|
);
|
|
78067
78552
|
} : void 0;
|
|
78553
|
+
const onToolEvent = bridgeDeps?.streamBus && voicePluginRecord ? (event) => {
|
|
78554
|
+
bridgeDeps.streamBus.publish(
|
|
78555
|
+
voicePluginRecord.id,
|
|
78556
|
+
VOICE_INTENT_RESULT_CHANNEL2,
|
|
78557
|
+
event.companyId,
|
|
78558
|
+
{
|
|
78559
|
+
kind: "tool_event",
|
|
78560
|
+
chatSessionId: event.chatSessionId,
|
|
78561
|
+
agentId: event.agentId,
|
|
78562
|
+
correlationId: event.correlationId,
|
|
78563
|
+
event: event.kind === "tool_call" ? {
|
|
78564
|
+
kind: "tool_call",
|
|
78565
|
+
id: event.id,
|
|
78566
|
+
name: event.name,
|
|
78567
|
+
input: event.input
|
|
78568
|
+
} : {
|
|
78569
|
+
kind: "tool_result",
|
|
78570
|
+
id: event.id,
|
|
78571
|
+
content: event.content,
|
|
78572
|
+
isError: event.isError
|
|
78573
|
+
},
|
|
78574
|
+
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
78575
|
+
}
|
|
78576
|
+
);
|
|
78577
|
+
} : void 0;
|
|
78068
78578
|
void dispatchVoiceIntent(
|
|
78069
|
-
{ db, onComplete },
|
|
78579
|
+
{ db, onComplete, onToolEvent },
|
|
78070
78580
|
{
|
|
78071
78581
|
companyId,
|
|
78072
78582
|
agentId,
|
|
78073
78583
|
chatSessionId,
|
|
78074
78584
|
intent,
|
|
78075
78585
|
surface: surfaceFinal,
|
|
78076
|
-
correlationId
|
|
78586
|
+
correlationId,
|
|
78587
|
+
operatorTimezone
|
|
78077
78588
|
}
|
|
78078
78589
|
).catch(() => {
|
|
78079
78590
|
});
|
|
@@ -78809,7 +79320,7 @@ var init_plugin_ui_static = __esm({
|
|
|
78809
79320
|
});
|
|
78810
79321
|
|
|
78811
79322
|
// ../server/src/ui-branding.ts
|
|
78812
|
-
function
|
|
79323
|
+
function isTruthyEnvValue2(value) {
|
|
78813
79324
|
if (!value) return false;
|
|
78814
79325
|
const normalized = value.trim().toLowerCase();
|
|
78815
79326
|
return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on";
|
|
@@ -78906,6 +79417,15 @@ function createFaviconDataUrl(background, foreground) {
|
|
|
78906
79417
|
].join("");
|
|
78907
79418
|
return `data:image/svg+xml,${encodeURIComponent(svg)}`;
|
|
78908
79419
|
}
|
|
79420
|
+
function createNeutralFaviconDataUrl() {
|
|
79421
|
+
const svg = [
|
|
79422
|
+
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">',
|
|
79423
|
+
'<rect width="24" height="24" rx="6" fill="#1f2937"/>',
|
|
79424
|
+
'<circle cx="12" cy="12" r="3" fill="#e5e7eb"/>',
|
|
79425
|
+
"</svg>"
|
|
79426
|
+
].join("");
|
|
79427
|
+
return `data:image/svg+xml,${encodeURIComponent(svg)}`;
|
|
79428
|
+
}
|
|
78909
79429
|
function createInitialsFaviconDataUrl(brandName) {
|
|
78910
79430
|
const initials = brandName.trim().slice(0, 2).toUpperCase();
|
|
78911
79431
|
const bg = deriveColorFromSeed(brandName);
|
|
@@ -78919,7 +79439,7 @@ function createInitialsFaviconDataUrl(brandName) {
|
|
|
78919
79439
|
return `data:image/svg+xml,${encodeURIComponent(svg)}`;
|
|
78920
79440
|
}
|
|
78921
79441
|
function isWorktreeUiBrandingEnabled(env = process.env) {
|
|
78922
|
-
return
|
|
79442
|
+
return isTruthyEnvValue2(env.PAPERCLIP_IN_WORKTREE);
|
|
78923
79443
|
}
|
|
78924
79444
|
function getWorktreeUiBranding(env = process.env) {
|
|
78925
79445
|
if (!isWorktreeUiBrandingEnabled(env)) {
|
|
@@ -78952,15 +79472,17 @@ function renderFaviconLinksFromHref(faviconHref) {
|
|
|
78952
79472
|
}
|
|
78953
79473
|
function renderRuntimeBrandingMeta(branding, env = process.env) {
|
|
78954
79474
|
const brand2 = getBrandPack(env);
|
|
79475
|
+
const displayName = brand2.hideBrand ? HIDDEN_BRAND_TITLE : brand2.name;
|
|
78955
79476
|
const lines = [
|
|
78956
|
-
`<meta name="apple-mobile-web-app-title" content="${escapeHtmlAttribute(
|
|
78957
|
-
`<title>${escapeHtmlAttribute(
|
|
79477
|
+
`<meta name="apple-mobile-web-app-title" content="${escapeHtmlAttribute(displayName)}" />`,
|
|
79478
|
+
`<title>${escapeHtmlAttribute(displayName)}</title>`,
|
|
78958
79479
|
`<meta name="paperclip-brand-name" content="${escapeHtmlAttribute(brand2.name)}" />`,
|
|
78959
79480
|
`<meta name="paperclip-brand-logo-url" content="${escapeHtmlAttribute(brand2.logoUrl ?? "")}" />`,
|
|
78960
79481
|
`<meta name="paperclip-brand-homepage" content="${escapeHtmlAttribute(brand2.homepageUrl ?? "")}" />`,
|
|
78961
79482
|
`<meta name="paperclip-brand-update-url" content="${escapeHtmlAttribute(brand2.updateUrl ?? "")}" />`,
|
|
78962
79483
|
`<meta name="paperclip-brand-support-email" content="${escapeHtmlAttribute(brand2.supportEmail ?? "")}" />`,
|
|
78963
|
-
`<meta name="paperclip-brand-docs" content="${escapeHtmlAttribute(brand2.docsUrl ?? "")}"
|
|
79484
|
+
`<meta name="paperclip-brand-docs" content="${escapeHtmlAttribute(brand2.docsUrl ?? "")}" />`,
|
|
79485
|
+
`<meta name="paperclip-brand-hide" content="${brand2.hideBrand ? "true" : "false"}" />`
|
|
78964
79486
|
];
|
|
78965
79487
|
if (branding.enabled && branding.name && branding.color && branding.textColor) {
|
|
78966
79488
|
lines.push('<meta name="paperclip-worktree-enabled" content="true" />');
|
|
@@ -78984,10 +79506,15 @@ ${content.split("\n").map((line) => ` ${line}`).join("\n")}
|
|
|
78984
79506
|
function applyUiBranding(html, env = process.env) {
|
|
78985
79507
|
const branding = getWorktreeUiBranding(env);
|
|
78986
79508
|
const brand2 = getBrandPack(env);
|
|
78987
|
-
let faviconHref =
|
|
79509
|
+
let faviconHref = null;
|
|
79510
|
+
if (brand2.hideBrand) {
|
|
79511
|
+
faviconHref = createNeutralFaviconDataUrl();
|
|
79512
|
+
} else if (branding.enabled) {
|
|
79513
|
+
faviconHref = branding.faviconHref;
|
|
79514
|
+
}
|
|
78988
79515
|
if (!faviconHref && brand2.logoUrl) {
|
|
78989
79516
|
faviconHref = brand2.logoUrl;
|
|
78990
|
-
} else if (!faviconHref && brand2.name !== "Paperclip") {
|
|
79517
|
+
} else if (!faviconHref && !brand2.hideBrand && brand2.name !== "Paperclip") {
|
|
78991
79518
|
faviconHref = createInitialsFaviconDataUrl(brand2.name);
|
|
78992
79519
|
}
|
|
78993
79520
|
const withFavicon = replaceMarkedBlock(html, FAVICON_BLOCK_START, FAVICON_BLOCK_END, renderFaviconLinksFromHref(faviconHref));
|
|
@@ -78998,7 +79525,7 @@ function applyUiBranding(html, env = process.env) {
|
|
|
78998
79525
|
renderRuntimeBrandingMeta(branding, env)
|
|
78999
79526
|
);
|
|
79000
79527
|
}
|
|
79001
|
-
var FAVICON_BLOCK_START, FAVICON_BLOCK_END, RUNTIME_BRANDING_BLOCK_START, RUNTIME_BRANDING_BLOCK_END, DEFAULT_FAVICON_LINKS;
|
|
79528
|
+
var FAVICON_BLOCK_START, FAVICON_BLOCK_END, RUNTIME_BRANDING_BLOCK_START, RUNTIME_BRANDING_BLOCK_END, DEFAULT_FAVICON_LINKS, HIDDEN_BRAND_TITLE;
|
|
79002
79529
|
var init_ui_branding = __esm({
|
|
79003
79530
|
"../server/src/ui-branding.ts"() {
|
|
79004
79531
|
"use strict";
|
|
@@ -79013,6 +79540,7 @@ var init_ui_branding = __esm({
|
|
|
79013
79540
|
'<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />',
|
|
79014
79541
|
'<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />'
|
|
79015
79542
|
].join("\n");
|
|
79543
|
+
HIDDEN_BRAND_TITLE = "App";
|
|
79016
79544
|
}
|
|
79017
79545
|
});
|
|
79018
79546
|
|
|
@@ -79762,7 +80290,7 @@ var init_plugin_stream_bus = __esm({
|
|
|
79762
80290
|
});
|
|
79763
80291
|
|
|
79764
80292
|
// ../server/src/services/plugin-job-scheduler.ts
|
|
79765
|
-
import { and as and61, eq as eq64, lte as lte9, or as
|
|
80293
|
+
import { and as and61, eq as eq64, lte as lte9, or as or15 } from "drizzle-orm";
|
|
79766
80294
|
function createPluginJobScheduler(options2) {
|
|
79767
80295
|
const {
|
|
79768
80296
|
db,
|
|
@@ -80059,7 +80587,7 @@ function createPluginJobScheduler(options2) {
|
|
|
80059
80587
|
const runningRuns = await db.select().from(pluginJobRuns).where(
|
|
80060
80588
|
and61(
|
|
80061
80589
|
eq64(pluginJobRuns.pluginId, pluginId),
|
|
80062
|
-
|
|
80590
|
+
or15(
|
|
80063
80591
|
eq64(pluginJobRuns.status, "running"),
|
|
80064
80592
|
eq64(pluginJobRuns.status, "queued")
|
|
80065
80593
|
)
|
|
@@ -80944,7 +81472,8 @@ function pluginChatHostBridge(db) {
|
|
|
80944
81472
|
role: input.role,
|
|
80945
81473
|
content: input.content,
|
|
80946
81474
|
stopReason: input.stopReason ?? null,
|
|
80947
|
-
usage: input.usage ?? null
|
|
81475
|
+
usage: input.usage ?? null,
|
|
81476
|
+
createdAt: input.createdAt
|
|
80948
81477
|
});
|
|
80949
81478
|
return {
|
|
80950
81479
|
id: row2.id,
|
|
@@ -81260,7 +81789,7 @@ var init_plugin_state_store = __esm({
|
|
|
81260
81789
|
|
|
81261
81790
|
// ../server/src/services/plugin-host-services.ts
|
|
81262
81791
|
import { eq as eq68, and as and65, like, desc as desc37 } from "drizzle-orm";
|
|
81263
|
-
import { randomUUID as
|
|
81792
|
+
import { randomUUID as randomUUID18 } from "node:crypto";
|
|
81264
81793
|
import { lookup as dnsLookup } from "node:dns/promises";
|
|
81265
81794
|
import { request as httpRequest } from "node:http";
|
|
81266
81795
|
import { request as httpsRequest } from "node:https";
|
|
@@ -81951,7 +82480,8 @@ function buildHostServices(db, pluginId, pluginKey, eventBus, notifyWorker) {
|
|
|
81951
82480
|
role: params.role,
|
|
81952
82481
|
content,
|
|
81953
82482
|
stopReason: params.stopReason ?? null,
|
|
81954
|
-
usage: params.usage ?? null
|
|
82483
|
+
usage: params.usage ?? null,
|
|
82484
|
+
createdAt: params.createdAt
|
|
81955
82485
|
});
|
|
81956
82486
|
},
|
|
81957
82487
|
async listMessages(params) {
|
|
@@ -81987,7 +82517,7 @@ function buildHostServices(db, pluginId, pluginKey, eventBus, notifyWorker) {
|
|
|
81987
82517
|
await ensurePluginAvailableForCompany(companyId);
|
|
81988
82518
|
const agent = await agents2.getById(params.agentId);
|
|
81989
82519
|
requireInCompany("Agent", agent, companyId);
|
|
81990
|
-
const taskKey = params.taskKey ?? `plugin:${pluginKey}:session:${
|
|
82520
|
+
const taskKey = params.taskKey ?? `plugin:${pluginKey}:session:${randomUUID18()}`;
|
|
81991
82521
|
const row2 = await db.insert(agentTaskSessions).values({
|
|
81992
82522
|
companyId,
|
|
81993
82523
|
agentId: params.agentId,
|