@automagik/omni 2.260429.4 → 2.260430.1
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/db/drizzle/0033_close_contact_logs.sql +47 -0
- package/db/drizzle/meta/_journal.json +7 -0
- package/dist/commands/messages.d.ts.map +1 -1
- package/dist/index.js +100 -4
- package/dist/server/index.js +1044 -602
- package/package.json +10 -10
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
-- Close-contact endpoint audit table.
|
|
2
|
+
--
|
|
3
|
+
-- Records every agent→close-contact event with full payload. Written
|
|
4
|
+
-- synchronously in the /messages/send/close-contact route so no data is
|
|
5
|
+
-- lost. The route also reads this table at close-time to count recent
|
|
6
|
+
-- rows for the same (chat_uuid, outcome) within the configured escalation
|
|
7
|
+
-- window — that count drives the auto-promotion of soft outcomes to hard
|
|
8
|
+
-- terminal (recorded back as `escalated = TRUE` on the new row). See the
|
|
9
|
+
-- design doc at .genie/wishes/559-close-contact/design.md §6 + §8.1 for
|
|
10
|
+
-- the loop-bound proof.
|
|
11
|
+
--
|
|
12
|
+
-- Hot path is the recent-count query, indexed on
|
|
13
|
+
-- (chat_uuid, outcome, sent_at). Per-instance BI feed is indexed on
|
|
14
|
+
-- (instance_id, sent_at). chat_id and agent_id get their own indexes for
|
|
15
|
+
-- support drilldowns.
|
|
16
|
+
--
|
|
17
|
+
-- The `outcome` column is a varchar(32) free string at the DB level; the
|
|
18
|
+
-- TypeScript layer constrains it to the `closeContactOutcomes` const array
|
|
19
|
+
-- in `packages/db/src/schema.ts` (kept in sync with `CloseContactOutcome`
|
|
20
|
+
-- in `@omni/core/events`). No CHECK constraint here so the BI/CRM tooling
|
|
21
|
+
-- can extend the taxonomy without a migration.
|
|
22
|
+
|
|
23
|
+
CREATE TABLE IF NOT EXISTS "close_contact_logs" (
|
|
24
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
|
25
|
+
"instance_id" uuid REFERENCES "instances"("id") ON DELETE SET NULL,
|
|
26
|
+
"chat_uuid" uuid REFERENCES "chats"("id") ON DELETE SET NULL,
|
|
27
|
+
"chat_id" varchar(255) NOT NULL,
|
|
28
|
+
"to_phone" varchar(100) NOT NULL,
|
|
29
|
+
"text" text NOT NULL,
|
|
30
|
+
"outcome" varchar(32) NOT NULL,
|
|
31
|
+
"reason" text,
|
|
32
|
+
"close_fields" jsonb,
|
|
33
|
+
"agent_id" uuid REFERENCES "agents"("id") ON DELETE SET NULL,
|
|
34
|
+
"external_message_id" varchar(255),
|
|
35
|
+
"escalated" boolean NOT NULL DEFAULT false,
|
|
36
|
+
"sent_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
37
|
+
"metadata" jsonb
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
CREATE INDEX IF NOT EXISTS "close_contact_logs_chat_outcome_sent_at_idx"
|
|
41
|
+
ON "close_contact_logs" ("chat_uuid", "outcome", "sent_at");
|
|
42
|
+
CREATE INDEX IF NOT EXISTS "close_contact_logs_instance_sent_at_idx"
|
|
43
|
+
ON "close_contact_logs" ("instance_id", "sent_at");
|
|
44
|
+
CREATE INDEX IF NOT EXISTS "close_contact_logs_chat_id_idx"
|
|
45
|
+
ON "close_contact_logs" ("chat_id");
|
|
46
|
+
CREATE INDEX IF NOT EXISTS "close_contact_logs_agent_idx"
|
|
47
|
+
ON "close_contact_logs" ("agent_id");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/commands/messages.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;AA8M5C,wBAAgB,qBAAqB,IAAI,OAAO,
|
|
1
|
+
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/commands/messages.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;AA8M5C,wBAAgB,qBAAqB,IAAI,OAAO,CA4T/C"}
|
package/dist/index.js
CHANGED
|
@@ -5755,6 +5755,7 @@ var init_types = __esm(() => {
|
|
|
5755
5755
|
"chat.idle_timeout",
|
|
5756
5756
|
"chat.handoff_activated",
|
|
5757
5757
|
"chat.archived",
|
|
5758
|
+
"chat.closed",
|
|
5758
5759
|
"follow_up.armed",
|
|
5759
5760
|
"follow_up.fired",
|
|
5760
5761
|
"follow_up.skipped",
|
|
@@ -27730,7 +27731,8 @@ var init_follow_up = __esm(() => {
|
|
|
27730
27731
|
"sequence_complete",
|
|
27731
27732
|
"agent_error",
|
|
27732
27733
|
"send_failed",
|
|
27733
|
-
"session_cleared"
|
|
27734
|
+
"session_cleared",
|
|
27735
|
+
"contact_closed"
|
|
27734
27736
|
]);
|
|
27735
27737
|
FixedScheduleSchema = exports_external.object({
|
|
27736
27738
|
kind: exports_external.literal("fixed"),
|
|
@@ -58589,6 +58591,8 @@ __export(exports_schema, {
|
|
|
58589
58591
|
contentTypes: () => contentTypes,
|
|
58590
58592
|
consumerOffsets: () => consumerOffsets,
|
|
58591
58593
|
conditionOperators: () => conditionOperators,
|
|
58594
|
+
closeContactOutcomes: () => closeContactOutcomes,
|
|
58595
|
+
closeContactLogs: () => closeContactLogs,
|
|
58592
58596
|
chatsRelations: () => chatsRelations,
|
|
58593
58597
|
chats: () => chats,
|
|
58594
58598
|
chatTypes: () => chatTypes,
|
|
@@ -58632,7 +58636,7 @@ __export(exports_schema, {
|
|
|
58632
58636
|
accessRules: () => accessRules,
|
|
58633
58637
|
accessModes: () => accessModes
|
|
58634
58638
|
});
|
|
58635
|
-
var channelTypes, agentTypes, agentSystems, agentEntityTypes, debounceMode, splitDelayMode, replyFilterMode, agentSessionStrategies, ruleTypes, accessModes, settingValueTypes, apiKeyStatuses, apiKeyProfiles, eventTypes, contentTypes, chatTypes, messageSources, messageTypes, messageStatuses, deliveryStatuses, jobStatuses, providerSchemas, agentProviders, agents, agentRoutes, agentSessions, apiKeys, apiKeyAuditLogs, apiKeysRelations, apiKeyAuditLogsRelations, instances, persons, platformIdentities, conversations, chats, chatParticipants, omniGroups, messages, omniEvents, handoffLogs, accessRules, globalSettings, settingChangeHistory, batchJobs, syncJobTypes, syncJobs, mediaContent, chatIdMappings, pluginStorage, agentProvidersRelations, agentsRelations, instancesRelations, syncJobsRelations, personsRelations, platformIdentitiesRelations, conversationsRelations, chatsRelations, chatParticipantsRelations, messagesRelations, omniEventsRelations, accessRulesRelations, globalSettingsRelations, settingChangeHistoryRelations, batchJobsRelations, mediaContentRelations, chatIdMappingsRelations, deadLetterStatuses, deadLetterEvents, payloadStorageConfig, payloadStages, eventPayloads, webhookSources, conditionOperators, actionTypes, automationDebounceModes, automations2, automationLogStatuses, automationLogs, consumerOffsets, automationsRelations, automationLogsRelations, triggerLogs, triggerLogsRelations, agentRoutesRelations, agentTaskStatuses, agentTasks, agentTasksRelations, turnStatuses, turnActions, turns, turnsRelations, followUpDisarmReasons, chatFollowUpState, chatFollowUpStateRelations, processedEvents, genieHosts;
|
|
58639
|
+
var channelTypes, agentTypes, agentSystems, agentEntityTypes, debounceMode, splitDelayMode, replyFilterMode, agentSessionStrategies, ruleTypes, accessModes, settingValueTypes, apiKeyStatuses, apiKeyProfiles, eventTypes, contentTypes, chatTypes, messageSources, messageTypes, messageStatuses, deliveryStatuses, jobStatuses, providerSchemas, agentProviders, agents, agentRoutes, agentSessions, apiKeys, apiKeyAuditLogs, apiKeysRelations, apiKeyAuditLogsRelations, instances, persons, platformIdentities, conversations, chats, chatParticipants, omniGroups, messages, omniEvents, handoffLogs, closeContactOutcomes, closeContactLogs, accessRules, globalSettings, settingChangeHistory, batchJobs, syncJobTypes, syncJobs, mediaContent, chatIdMappings, pluginStorage, agentProvidersRelations, agentsRelations, instancesRelations, syncJobsRelations, personsRelations, platformIdentitiesRelations, conversationsRelations, chatsRelations, chatParticipantsRelations, messagesRelations, omniEventsRelations, accessRulesRelations, globalSettingsRelations, settingChangeHistoryRelations, batchJobsRelations, mediaContentRelations, chatIdMappingsRelations, deadLetterStatuses, deadLetterEvents, payloadStorageConfig, payloadStages, eventPayloads, webhookSources, conditionOperators, actionTypes, automationDebounceModes, automations2, automationLogStatuses, automationLogs, consumerOffsets, automationsRelations, automationLogsRelations, triggerLogs, triggerLogsRelations, agentRoutesRelations, agentTaskStatuses, agentTasks, agentTasksRelations, turnStatuses, turnActions, turns, turnsRelations, followUpDisarmReasons, chatFollowUpState, chatFollowUpStateRelations, processedEvents, genieHosts;
|
|
58636
58640
|
var init_schema2 = __esm(() => {
|
|
58637
58641
|
init_events();
|
|
58638
58642
|
init_drizzle_orm();
|
|
@@ -59258,6 +59262,28 @@ var init_schema2 = __esm(() => {
|
|
|
59258
59262
|
sentAtIdx: index("handoff_logs_sent_at_idx").on(table3.sentAt),
|
|
59259
59263
|
agentIdx: index("handoff_logs_agent_idx").on(table3.agentId)
|
|
59260
59264
|
}));
|
|
59265
|
+
closeContactOutcomes = ["won", "lost", "redirected_sac", "unqualified", "no_response", "other"];
|
|
59266
|
+
closeContactLogs = pgTable("close_contact_logs", {
|
|
59267
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
59268
|
+
instanceId: uuid("instance_id").references(() => instances.id, { onDelete: "set null" }),
|
|
59269
|
+
chatUuid: uuid("chat_uuid").references(() => chats.id, { onDelete: "set null" }),
|
|
59270
|
+
chatId: varchar("chat_id", { length: 255 }).notNull(),
|
|
59271
|
+
toPhone: varchar("to_phone", { length: 100 }).notNull(),
|
|
59272
|
+
text: text("text").notNull(),
|
|
59273
|
+
outcome: varchar("outcome", { length: 32 }).notNull().$type(),
|
|
59274
|
+
reason: text("reason"),
|
|
59275
|
+
closeFields: jsonb("close_fields").$type(),
|
|
59276
|
+
agentId: uuid("agent_id").references(() => agents.id, { onDelete: "set null" }),
|
|
59277
|
+
externalMessageId: varchar("external_message_id", { length: 255 }),
|
|
59278
|
+
escalated: boolean("escalated").notNull().default(false),
|
|
59279
|
+
sentAt: timestamp("sent_at", { withTimezone: true }).notNull().defaultNow(),
|
|
59280
|
+
metadata: jsonb("metadata").$type()
|
|
59281
|
+
}, (table3) => ({
|
|
59282
|
+
chatOutcomeSentAtIdx: index("close_contact_logs_chat_outcome_sent_at_idx").on(table3.chatUuid, table3.outcome, table3.sentAt),
|
|
59283
|
+
instanceSentAtIdx: index("close_contact_logs_instance_sent_at_idx").on(table3.instanceId, table3.sentAt),
|
|
59284
|
+
chatIdIdx: index("close_contact_logs_chat_id_idx").on(table3.chatId),
|
|
59285
|
+
agentIdx: index("close_contact_logs_agent_idx").on(table3.agentId)
|
|
59286
|
+
}));
|
|
59261
59287
|
accessRules = pgTable("access_rules", {
|
|
59262
59288
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
59263
59289
|
instanceId: uuid("instance_id").references(() => instances.id, { onDelete: "cascade" }),
|
|
@@ -59892,7 +59918,8 @@ var init_schema2 = __esm(() => {
|
|
|
59892
59918
|
"sequence_complete",
|
|
59893
59919
|
"agent_error",
|
|
59894
59920
|
"send_failed",
|
|
59895
|
-
"session_cleared"
|
|
59921
|
+
"session_cleared",
|
|
59922
|
+
"contact_closed"
|
|
59896
59923
|
];
|
|
59897
59924
|
chatFollowUpState = pgTable("chat_follow_up_state", {
|
|
59898
59925
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
@@ -113953,7 +113980,7 @@ import { fileURLToPath } from "url";
|
|
|
113953
113980
|
// package.json
|
|
113954
113981
|
var package_default = {
|
|
113955
113982
|
name: "@automagik/omni",
|
|
113956
|
-
version: "2.
|
|
113983
|
+
version: "2.260430.1",
|
|
113957
113984
|
description: "LLM-optimized CLI for Omni",
|
|
113958
113985
|
type: "module",
|
|
113959
113986
|
bin: {
|
|
@@ -122464,8 +122491,77 @@ function createMessagesCommand() {
|
|
|
122464
122491
|
error(`Failed to edit message: ${message2}`);
|
|
122465
122492
|
}
|
|
122466
122493
|
});
|
|
122494
|
+
messages3.command("close-contact").description("Close a chat terminally (won/lost) or with a soft cooldown (redirected_sac/unqualified/no_response/other)").requiredOption("--instance <id>", "Instance ID").requiredOption("--chat <chatId>", "Chat DB UUID to close").requiredOption("--to <recipient>", "Recipient phone or platform ID").requiredOption("--text <text>", "Farewell message shown to the lead").requiredOption("--outcome <outcome>", "Outcome: won | lost | redirected_sac | unqualified | no_response | other").option("--reason <reason>", "Free-text rationale persisted in close_contact_logs").option("--close-fields <jsonOrPath>", "Structured BI/CRM payload \u2014 inline JSON or path to a JSON file").action(handleCloseContact);
|
|
122467
122495
|
return messages3;
|
|
122468
122496
|
}
|
|
122497
|
+
var VALID_CLOSE_OUTCOMES = ["won", "lost", "redirected_sac", "unqualified", "no_response", "other"];
|
|
122498
|
+
async function parseCloseFields(raw2) {
|
|
122499
|
+
const trimmed = raw2.trim();
|
|
122500
|
+
if (trimmed.startsWith("{")) {
|
|
122501
|
+
return JSON.parse(trimmed);
|
|
122502
|
+
}
|
|
122503
|
+
const fs2 = await import("fs/promises");
|
|
122504
|
+
const text3 = await fs2.readFile(trimmed, "utf-8");
|
|
122505
|
+
return JSON.parse(text3);
|
|
122506
|
+
}
|
|
122507
|
+
async function postCloseContact(body) {
|
|
122508
|
+
const _cfg = (await Promise.resolve().then(() => (init_config(), exports_config))).loadConfig();
|
|
122509
|
+
const baseUrl = _cfg.apiUrl ?? "http://localhost:8882";
|
|
122510
|
+
const apiKey = _cfg.apiKey ?? "";
|
|
122511
|
+
const resp = await fetch(`${baseUrl}/api/v2/messages/send/close-contact`, {
|
|
122512
|
+
method: "POST",
|
|
122513
|
+
headers: { "Content-Type": "application/json", "x-api-key": apiKey },
|
|
122514
|
+
body: JSON.stringify(body)
|
|
122515
|
+
});
|
|
122516
|
+
if (!resp.ok) {
|
|
122517
|
+
const err2 = await resp.json().catch(() => ({}));
|
|
122518
|
+
const errMsg = typeof err2.error === "string" ? err2.error : err2.error?.message ?? `API error: ${resp.status}`;
|
|
122519
|
+
throw new Error(errMsg);
|
|
122520
|
+
}
|
|
122521
|
+
const data2 = await resp.json();
|
|
122522
|
+
return data2.data ?? {};
|
|
122523
|
+
}
|
|
122524
|
+
async function buildCloseContactBody(options) {
|
|
122525
|
+
let closeFields;
|
|
122526
|
+
if (options.closeFields) {
|
|
122527
|
+
try {
|
|
122528
|
+
closeFields = await parseCloseFields(options.closeFields);
|
|
122529
|
+
} catch (err2) {
|
|
122530
|
+
error(`Failed to parse --close-fields: ${err2 instanceof Error ? err2.message : "Unknown error"}`);
|
|
122531
|
+
return null;
|
|
122532
|
+
}
|
|
122533
|
+
}
|
|
122534
|
+
const instanceId = await resolveInstanceId(options.instance);
|
|
122535
|
+
const resolvedChatId = await resolveChatId(options.chat);
|
|
122536
|
+
const body = {
|
|
122537
|
+
instanceId,
|
|
122538
|
+
chatId: resolvedChatId,
|
|
122539
|
+
to: options.to,
|
|
122540
|
+
text: options.text,
|
|
122541
|
+
outcome: options.outcome
|
|
122542
|
+
};
|
|
122543
|
+
if (options.reason)
|
|
122544
|
+
body.reason = options.reason;
|
|
122545
|
+
if (closeFields)
|
|
122546
|
+
body.closeFields = closeFields;
|
|
122547
|
+
return body;
|
|
122548
|
+
}
|
|
122549
|
+
async function handleCloseContact(options) {
|
|
122550
|
+
if (!VALID_CLOSE_OUTCOMES.includes(options.outcome)) {
|
|
122551
|
+
error(`Invalid --outcome '${options.outcome}'. Must be one of: ${VALID_CLOSE_OUTCOMES.join(", ")}`);
|
|
122552
|
+
return;
|
|
122553
|
+
}
|
|
122554
|
+
try {
|
|
122555
|
+
const body = await buildCloseContactBody(options);
|
|
122556
|
+
if (!body)
|
|
122557
|
+
return;
|
|
122558
|
+
const result = await postCloseContact(body);
|
|
122559
|
+
const cooldownPart = result.closeUntil ? `, closeUntil=${result.closeUntil}` : "";
|
|
122560
|
+
success(`Chat closed (${options.outcome}): terminal=${result.terminal ?? false}, escalated=${result.escalated ?? false}${cooldownPart}`);
|
|
122561
|
+
} catch (err2) {
|
|
122562
|
+
error(`Failed to close contact: ${err2 instanceof Error ? err2.message : "Unknown error"}`);
|
|
122563
|
+
}
|
|
122564
|
+
}
|
|
122469
122565
|
|
|
122470
122566
|
// src/commands/open.ts
|
|
122471
122567
|
init_output();
|