@askexenow/exe-os 0.9.263 → 0.9.265
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/deploy/stack-manifests/v0.9.json +1 -1
- package/dist/backfill-metadata-WM46YQZL.js +597 -0
- package/dist/bin/agentic-ontology-backfill.js +1 -1
- package/dist/bin/agentic-reflection-backfill.js +1 -1
- package/dist/bin/agentic-semantic-label.js +1 -1
- package/dist/bin/backfill-conversations.js +1 -1
- package/dist/bin/backfill-responses.js +1 -1
- package/dist/bin/backfill-vectors.js +2 -2
- package/dist/bin/bulk-sync-postgres.js +1 -1
- package/dist/bin/cleanup-stale-review-tasks.js +2 -2
- package/dist/bin/cli.js +5 -5
- package/dist/bin/exe-assign.js +1 -1
- package/dist/bin/exe-boot.js +3 -3
- package/dist/bin/exe-dispatch.js +2 -2
- package/dist/bin/exe-doctor.js +1 -1
- package/dist/bin/exe-export-behaviors.js +2 -2
- package/dist/bin/exe-forget.js +3 -3
- package/dist/bin/exe-gateway.js +5 -5
- package/dist/bin/exe-heartbeat.js +3 -3
- package/dist/bin/exe-kill.js +3 -3
- package/dist/bin/exe-launch-agent.js +3 -3
- package/dist/bin/exe-pending-messages.js +3 -3
- package/dist/bin/exe-pending-notifications.js +2 -2
- package/dist/bin/exe-pending-reviews.js +6 -4
- package/dist/bin/exe-review.js +3 -3
- package/dist/bin/exe-search.js +2 -2
- package/dist/bin/exe-session-cleanup.js +5 -5
- package/dist/bin/exe-start-codex.js +1 -1
- package/dist/bin/exe-start-opencode.js +1 -1
- package/dist/bin/exe-status.js +3 -3
- package/dist/bin/exe-support.js +1 -1
- package/dist/bin/exe-team.js +1 -1
- package/dist/bin/git-sweep.js +2 -2
- package/dist/bin/graph-backfill.js +1 -1
- package/dist/bin/graph-export.js +2 -2
- package/dist/bin/import-history.js +2 -2
- package/dist/bin/intercom-check.js +5 -4
- package/dist/bin/mcp-sessions.js +2 -2
- package/dist/bin/orchestration-metrics.js +1 -1
- package/dist/bin/scan-tasks.js +2 -2
- package/dist/bin/shard-migrate.js +1 -1
- package/dist/capacity-monitor-FZORNXTA.js +49 -0
- package/dist/catchup-brief-PRKHIRWW.js +151 -0
- package/dist/chunk-2GU3NYMB.js +813 -0
- package/dist/chunk-2VT7Z2E2.js +197 -0
- package/dist/chunk-3L6XLN4V.js +1090 -0
- package/dist/chunk-3T4PNG5O.js +447 -0
- package/dist/chunk-3W324KN7.js +13696 -0
- package/dist/{chunk-BJYXHSFN.js → chunk-3X555IGG.js} +1 -1
- package/dist/chunk-4IATAIAF.js +89 -0
- package/dist/chunk-5M4F2FVD.js +204 -0
- package/dist/chunk-5TANMPI4.js +377 -0
- package/dist/chunk-67E5WIMW.js +333 -0
- package/dist/chunk-6NRJIARA.js +346 -0
- package/dist/chunk-6O22GTSE.js +1148 -0
- package/dist/chunk-7O5CMDP6.js +1345 -0
- package/dist/chunk-7P2JKEO3.js +382 -0
- package/dist/chunk-A4UY44T7.js +486 -0
- package/dist/chunk-AZHPQGSI.js +159 -0
- package/dist/chunk-BQAC3GCO.js +127 -0
- package/dist/chunk-CIWJRYIC.js +244 -0
- package/dist/chunk-CZO2DGGF.js +214 -0
- package/dist/chunk-D55SXO3N.js +3951 -0
- package/dist/chunk-EPDSRI6O.js +284 -0
- package/dist/chunk-EU34R2A3.js +1073 -0
- package/dist/chunk-G4KEDLSM.js +488 -0
- package/dist/{chunk-235ZCOYB.js → chunk-IBGC6JFX.js} +2 -2
- package/dist/chunk-IODP4JNE.js +551 -0
- package/dist/chunk-IZ6LCET7.js +58 -0
- package/dist/chunk-LPG3U5UW.js +731 -0
- package/dist/chunk-NH6TPXZV.js +13696 -0
- package/dist/chunk-O5TZI7OZ.js +50 -0
- package/dist/chunk-Q3N4KHLM.js +330 -0
- package/dist/chunk-QHQTMWYH.js +54 -0
- package/dist/chunk-QNYOPM2L.js +1921 -0
- package/dist/chunk-SPOA7EOD.js +81 -0
- package/dist/chunk-TK23WXKB.js +128 -0
- package/dist/chunk-TOVXER6J.js +76 -0
- package/dist/chunk-UIPAZYP7.js +171 -0
- package/dist/chunk-WXW4GF6M.js +495 -0
- package/dist/{chunk-KQNVIDBP.js → chunk-XLYBSXWS.js} +4 -3
- package/dist/core-memory-QXMQ5I7S.js +110 -0
- package/dist/crm-webhook-CH5W633Y.js +10 -0
- package/dist/cto-delegation-gate-XY3NMGTE.js +206 -0
- package/dist/daemon-orchestration-LS62JMTI.js +135 -0
- package/dist/dreaming-ZBKE2GFX.js +32 -0
- package/dist/exe-export-JNSQRIWI.js +73 -0
- package/dist/exe-import-AVGWQZLU.js +76 -0
- package/dist/exe-key-WR6QEHYO.js +579 -0
- package/dist/exe-snapshot-U6K3J6BD.js +164 -0
- package/dist/fast-db-init-ZHRRYI7M.js +7 -0
- package/dist/gateway/index.js +6 -6
- package/dist/git-task-sweep-64KSWRUI.js +40 -0
- package/dist/hooks/bug-report-worker.js +4 -4
- package/dist/hooks/codex-stop-task-finalizer.js +4 -4
- package/dist/hooks/commit-complete.js +4 -4
- package/dist/hooks/error-recall.js +2 -2
- package/dist/hooks/ingest.js +2 -2
- package/dist/hooks/instructions-loaded.js +1 -1
- package/dist/hooks/manifest.json +18 -18
- package/dist/hooks/notification.js +1 -1
- package/dist/hooks/post-compact.js +2 -2
- package/dist/hooks/post-tool-combined.js +2 -2
- package/dist/hooks/pre-compact.js +3 -3
- package/dist/hooks/pre-tool-use.js +6 -6
- package/dist/hooks/prompt-submit.js +16 -11
- package/dist/hooks/session-end.js +5 -5
- package/dist/hooks/session-start.js +6 -6
- package/dist/hooks/stop.js +5 -5
- package/dist/hooks/subagent-stop.js +2 -2
- package/dist/hooks/summary-worker.js +5 -5
- package/dist/index.js +9 -9
- package/dist/lib/consolidation.js +2 -2
- package/dist/lib/exe-daemon.js +17 -17
- package/dist/lib/hybrid-search.js +2 -2
- package/dist/lib/messaging.js +2 -2
- package/dist/lib/schedules.js +2 -2
- package/dist/lib/store.js +1 -1
- package/dist/lib/tasks.js +3 -3
- package/dist/lib/tmux-routing.js +1 -1
- package/dist/mcp/register-tools.js +25 -25
- package/dist/mcp/server.js +26 -26
- package/dist/mcp/tools/create-task.js +4 -4
- package/dist/mcp/tools/list-tasks.js +4 -4
- package/dist/mcp/tools/send-message.js +3 -3
- package/dist/mcp/tools/update-task.js +4 -4
- package/dist/notifications-45QSHDFA.js +45 -0
- package/dist/orchestrator-7XBMFK7D.js +33 -0
- package/dist/pipeline-router-MQKRNCTR.js +13 -0
- package/dist/reranker-CJW3UYE2.js +19 -0
- package/dist/review-polling-RL75XLAY.js +124 -0
- package/dist/runtime/index.js +3 -3
- package/dist/session-events-ZULAN4XL.js +36 -0
- package/dist/session-scope-V2RSOTDU.js +86 -0
- package/dist/skill-refinement-BSX6Q6IN.js +157 -0
- package/dist/{support-outbox-SZVLHHZG.js → support-outbox-SO73Q5H2.js} +202 -7
- package/dist/task-enforcement-JRTAOYZT.js +333 -0
- package/dist/task-scope-GNCB2GAM.js +35 -0
- package/dist/tasks-crud-MZIOYF3R.js +77 -0
- package/dist/tasks-notify-7KNZ4ULO.js +38 -0
- package/dist/tasks-review-U5VEV4Y7.js +47 -0
- package/dist/telemetry-upload-BIB5TJA4.js +739 -0
- package/dist/tui/App.js +7 -7
- package/dist/tui-data-ZSB5DDEY.js +258 -0
- package/dist/worker-gate-TXLX33PX.js +21 -0
- package/dist/workflow-engine-3PIT3Y56.js +28 -0
- package/package.json +1 -1
- package/release-notes.json +27 -24
|
@@ -622,7 +622,7 @@ async function auditKeyHealth() {
|
|
|
622
622
|
}
|
|
623
623
|
async function auditOutbox() {
|
|
624
624
|
try {
|
|
625
|
-
const { getOutboxStatus } = await import("./support-outbox-
|
|
625
|
+
const { getOutboxStatus } = await import("./support-outbox-SO73Q5H2.js");
|
|
626
626
|
return await getOutboxStatus();
|
|
627
627
|
} catch {
|
|
628
628
|
return {
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getActiveAgent
|
|
3
|
+
} from "./chunk-YG7PIB2R.js";
|
|
4
|
+
import {
|
|
5
|
+
listTasks
|
|
6
|
+
} from "./chunk-D55SXO3N.js";
|
|
7
|
+
import {
|
|
8
|
+
getAgentContext
|
|
9
|
+
} from "./chunk-GJV3WDWM.js";
|
|
10
|
+
import {
|
|
11
|
+
isCoordinatorName
|
|
12
|
+
} from "./chunk-QQZMP6QL.js";
|
|
13
|
+
|
|
14
|
+
// src/mcp/tools/list-tasks.ts
|
|
15
|
+
import { z } from "zod";
|
|
16
|
+
function registerListTasks(server) {
|
|
17
|
+
server.registerTool(
|
|
18
|
+
"list_tasks",
|
|
19
|
+
{
|
|
20
|
+
title: "List Tasks",
|
|
21
|
+
description: "Query tasks by assignee, status, project, or priority. Defaults to current project. Pass project_name='all' for all projects. When querying your own tasks, project filter is skipped automatically.",
|
|
22
|
+
inputSchema: {
|
|
23
|
+
assigned_to: z.string().optional().describe("Filter by employee name"),
|
|
24
|
+
status: z.enum(["open", "in_progress", "done", "needs_review", "blocked", "cancelled", "closed"]).optional().describe("Filter by status. Default: active tasks only (excludes closed/cancelled)"),
|
|
25
|
+
project_name: z.string().optional().describe("Project name. Defaults to current project. Pass 'all' for all projects."),
|
|
26
|
+
priority: z.enum(["p0", "p1", "p2"]).optional().describe("Filter by priority"),
|
|
27
|
+
cross_session: z.boolean().optional().describe("When true, return tasks from ALL coordinator sessions (read-only). Default: false.")
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
async ({ assigned_to, status, project_name, priority, cross_session }) => {
|
|
31
|
+
try {
|
|
32
|
+
const { agentId } = getActiveAgent();
|
|
33
|
+
const isReviewQueue = status === "needs_review";
|
|
34
|
+
const shouldDefaultToOwnQueue = !assigned_to && !isReviewQueue && !isCoordinatorName(agentId);
|
|
35
|
+
const effectiveAssignedTo = assigned_to ?? (shouldDefaultToOwnQueue ? agentId : void 0);
|
|
36
|
+
const effectiveReviewer = !assigned_to && isReviewQueue ? agentId : void 0;
|
|
37
|
+
const isOwnQuery = effectiveAssignedTo === agentId;
|
|
38
|
+
const callerSession = getAgentContext()?.sessionHint || process.env.EXE_SESSION_NAME || process.env.EXE_SESSION || null;
|
|
39
|
+
const resolvedProject = project_name && project_name !== "all" ? project_name : null;
|
|
40
|
+
const effectiveCrossSession = cross_session === true;
|
|
41
|
+
const tasks = await listTasks({
|
|
42
|
+
assignedTo: effectiveAssignedTo,
|
|
43
|
+
reviewer: effectiveReviewer,
|
|
44
|
+
status,
|
|
45
|
+
projectName: resolvedProject,
|
|
46
|
+
priority,
|
|
47
|
+
crossSession: effectiveCrossSession,
|
|
48
|
+
isOwnQuery: !!isOwnQuery,
|
|
49
|
+
callerSession
|
|
50
|
+
});
|
|
51
|
+
if (tasks.length === 0) {
|
|
52
|
+
return {
|
|
53
|
+
content: [{ type: "text", text: "No tasks found." }]
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const lines = tasks.map((t) => {
|
|
57
|
+
const cpIndicator = t.checkpointCount && t.checkpointCount > 0 ? ` [cp:${t.checkpointCount}]` : "";
|
|
58
|
+
let budgetNote = "";
|
|
59
|
+
if (t.budgetTokens !== null) {
|
|
60
|
+
const pct = Math.round(t.tokensUsed / t.budgetTokens * 100);
|
|
61
|
+
budgetNote = ` [${t.tokensUsed}/${t.budgetTokens} tokens, ${pct}%]`;
|
|
62
|
+
}
|
|
63
|
+
const shortId = t.id.slice(0, 8);
|
|
64
|
+
const sessionNote = cross_session && t.sessionScope ? ` [session:${t.sessionScope}]` : "";
|
|
65
|
+
return `- [${t.priority.toUpperCase()}] ${t.title} (${t.projectName}) \u2014 ${t.status}${cpIndicator}${budgetNote}${sessionNote} \u2192 ${t.assignedTo} [id:${shortId}]`;
|
|
66
|
+
});
|
|
67
|
+
return {
|
|
68
|
+
content: [
|
|
69
|
+
{
|
|
70
|
+
type: "text",
|
|
71
|
+
text: `${tasks.length} task(s):
|
|
72
|
+
${lines.join("\n")}`
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
};
|
|
76
|
+
} catch (err) {
|
|
77
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
78
|
+
return {
|
|
79
|
+
content: [{ type: "text", text: `Failed to list tasks: ${msg}` }],
|
|
80
|
+
isError: true
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export {
|
|
88
|
+
registerListTasks
|
|
89
|
+
};
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getClient
|
|
3
|
+
} from "./chunk-QQZMP6QL.js";
|
|
4
|
+
|
|
5
|
+
// src/lib/pipeline-router.ts
|
|
6
|
+
import crypto from "crypto";
|
|
7
|
+
async function sinkConversationStore(msg, agentResponse, agentName) {
|
|
8
|
+
try {
|
|
9
|
+
const client = getClient();
|
|
10
|
+
const id = crypto.randomUUID();
|
|
11
|
+
const mediaJson = msg.media ? JSON.stringify(msg.media) : null;
|
|
12
|
+
await client.execute({
|
|
13
|
+
sql: `INSERT INTO conversations
|
|
14
|
+
(id, platform, external_id, sender_id, sender_name, sender_phone, sender_email,
|
|
15
|
+
recipient_id, channel_id, thread_id, reply_to_id,
|
|
16
|
+
content_text, content_media, agent_response, agent_name,
|
|
17
|
+
timestamp, ingested_at)
|
|
18
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
19
|
+
args: [
|
|
20
|
+
id,
|
|
21
|
+
msg.platform,
|
|
22
|
+
msg.messageId,
|
|
23
|
+
msg.senderId,
|
|
24
|
+
msg.senderName ?? null,
|
|
25
|
+
msg.senderPhone ?? null,
|
|
26
|
+
msg.senderEmail ?? null,
|
|
27
|
+
msg.accountId ?? null,
|
|
28
|
+
msg.channelId,
|
|
29
|
+
msg.threadId ?? `${msg.platform}:${msg.channelId}`,
|
|
30
|
+
msg.replyTo?.messageId ?? null,
|
|
31
|
+
msg.text ?? null,
|
|
32
|
+
mediaJson,
|
|
33
|
+
agentResponse ?? null,
|
|
34
|
+
agentName ?? null,
|
|
35
|
+
msg.timestamp,
|
|
36
|
+
(/* @__PURE__ */ new Date()).toISOString()
|
|
37
|
+
]
|
|
38
|
+
});
|
|
39
|
+
return true;
|
|
40
|
+
} catch (err) {
|
|
41
|
+
process.stderr.write(
|
|
42
|
+
`[pipeline] conversation-store error: ${err instanceof Error ? err.message : String(err)}
|
|
43
|
+
`
|
|
44
|
+
);
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async function sinkMemory(msg, agentResponse, agentName) {
|
|
49
|
+
try {
|
|
50
|
+
const { embed } = await import("./lib/embedder.js");
|
|
51
|
+
const { writeMemory, flushBatch } = await import("./lib/store.js");
|
|
52
|
+
const direction = agentResponse ? "conversation" : "inbound";
|
|
53
|
+
const rawText = [
|
|
54
|
+
`[${msg.platform}] ${direction} from ${msg.senderName ?? msg.senderId}`,
|
|
55
|
+
msg.text ? `Message: ${msg.text}` : null,
|
|
56
|
+
agentResponse ? `Agent (${agentName ?? "unknown"}): ${agentResponse}` : null
|
|
57
|
+
].filter(Boolean).join("\n");
|
|
58
|
+
const vector = await embed(rawText);
|
|
59
|
+
await writeMemory({
|
|
60
|
+
id: crypto.randomUUID(),
|
|
61
|
+
agent_id: agentName ?? "gateway",
|
|
62
|
+
agent_role: "gateway",
|
|
63
|
+
session_id: `gateway-${msg.platform}`,
|
|
64
|
+
timestamp: msg.timestamp,
|
|
65
|
+
tool_name: `gateway-${msg.platform}`,
|
|
66
|
+
project_name: "exe-os",
|
|
67
|
+
has_error: false,
|
|
68
|
+
raw_text: rawText,
|
|
69
|
+
vector,
|
|
70
|
+
importance: 4,
|
|
71
|
+
confidence: 0.8
|
|
72
|
+
});
|
|
73
|
+
await flushBatch();
|
|
74
|
+
return true;
|
|
75
|
+
} catch (err) {
|
|
76
|
+
process.stderr.write(
|
|
77
|
+
`[pipeline] memory-sink error: ${err instanceof Error ? err.message : String(err)}
|
|
78
|
+
`
|
|
79
|
+
);
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async function sinkCRM(msg, agentResponse, agentName) {
|
|
84
|
+
try {
|
|
85
|
+
const { isCRMBridgeEnabled, pushConversationToCRM, pushInboundMessageToCRM } = await import("./crm-bridge-BVTB6LZK.js");
|
|
86
|
+
if (!isCRMBridgeEnabled()) return false;
|
|
87
|
+
if (agentResponse) {
|
|
88
|
+
await pushConversationToCRM({
|
|
89
|
+
platform: msg.platform,
|
|
90
|
+
senderId: msg.senderId,
|
|
91
|
+
senderName: msg.senderName,
|
|
92
|
+
messageText: msg.text,
|
|
93
|
+
agentResponse,
|
|
94
|
+
agentName: agentName ?? "unknown",
|
|
95
|
+
timestamp: msg.timestamp,
|
|
96
|
+
accountId: msg.accountId
|
|
97
|
+
});
|
|
98
|
+
} else {
|
|
99
|
+
await pushInboundMessageToCRM({
|
|
100
|
+
platform: msg.platform,
|
|
101
|
+
senderId: msg.senderId,
|
|
102
|
+
senderName: msg.senderName,
|
|
103
|
+
messageText: msg.text,
|
|
104
|
+
timestamp: msg.timestamp,
|
|
105
|
+
accountId: msg.accountId
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
return true;
|
|
109
|
+
} catch (err) {
|
|
110
|
+
process.stderr.write(
|
|
111
|
+
`[pipeline] crm-sink error: ${err instanceof Error ? err.message : String(err)}
|
|
112
|
+
`
|
|
113
|
+
);
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async function sinkWiki(msg, agentResponse) {
|
|
118
|
+
try {
|
|
119
|
+
const { createWikiClient } = await import("./wiki-client-GBPR45BQ.js");
|
|
120
|
+
const client = await createWikiClient();
|
|
121
|
+
if (!client) return false;
|
|
122
|
+
const content = msg.text ?? "";
|
|
123
|
+
if (content.length < 50) return false;
|
|
124
|
+
const { chatInWorkspace } = await import("./wiki-client-GBPR45BQ.js");
|
|
125
|
+
const ingestText = [
|
|
126
|
+
`[${msg.platform}] Message from ${msg.senderName ?? msg.senderId} (${msg.timestamp}):`,
|
|
127
|
+
content,
|
|
128
|
+
agentResponse ? `
|
|
129
|
+
Agent response: ${agentResponse}` : ""
|
|
130
|
+
].join("\n");
|
|
131
|
+
await chatInWorkspace(client, "conversations", ingestText, "chat");
|
|
132
|
+
return true;
|
|
133
|
+
} catch (err) {
|
|
134
|
+
process.stderr.write(
|
|
135
|
+
`[pipeline] wiki-sink error: ${err instanceof Error ? err.message : String(err)}
|
|
136
|
+
`
|
|
137
|
+
);
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
async function sinkEntityExtraction(msg, agentResponse, agentName) {
|
|
142
|
+
try {
|
|
143
|
+
const { extractFromConversation } = await import("./conversation-entity-extractor-EYSI4DKM.js");
|
|
144
|
+
const { storeExtraction } = await import("./graph-rag-G3EG5Q6L.js");
|
|
145
|
+
if ((msg.text?.length ?? 0) < 20) return false;
|
|
146
|
+
if (msg.isHistorical) return false;
|
|
147
|
+
const extraction = await extractFromConversation(msg, agentResponse, agentName);
|
|
148
|
+
if (extraction.entities.length === 0) return false;
|
|
149
|
+
const client = getClient();
|
|
150
|
+
const memoryId = `conv:${msg.messageId}`;
|
|
151
|
+
await storeExtraction(client, extraction, memoryId, msg.timestamp);
|
|
152
|
+
import("./conversation-wiki-populator-L7O6F3BB.js").then(
|
|
153
|
+
({ populateWikiFromExtraction }) => populateWikiFromExtraction(extraction, msg, agentResponse, agentName)
|
|
154
|
+
).catch((err) => {
|
|
155
|
+
process.stderr.write(
|
|
156
|
+
`[pipeline] wiki-population error: ${err instanceof Error ? err.message : String(err)}
|
|
157
|
+
`
|
|
158
|
+
);
|
|
159
|
+
});
|
|
160
|
+
return true;
|
|
161
|
+
} catch (err) {
|
|
162
|
+
process.stderr.write(
|
|
163
|
+
`[pipeline] entity-extraction-sink error: ${err instanceof Error ? err.message : String(err)}
|
|
164
|
+
`
|
|
165
|
+
);
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
async function ingest(msg, agentResponse, agentName) {
|
|
170
|
+
const errors = [];
|
|
171
|
+
const [conversationStored, memorySunk, crmSunk, wikiSunk, entityExtracted] = await Promise.all([
|
|
172
|
+
sinkConversationStore(msg, agentResponse, agentName).catch((e) => {
|
|
173
|
+
errors.push(`conversation: ${e}`);
|
|
174
|
+
return false;
|
|
175
|
+
}),
|
|
176
|
+
sinkMemory(msg, agentResponse, agentName).catch((e) => {
|
|
177
|
+
errors.push(`memory: ${e}`);
|
|
178
|
+
return false;
|
|
179
|
+
}),
|
|
180
|
+
sinkCRM(msg, agentResponse, agentName).catch((e) => {
|
|
181
|
+
errors.push(`crm: ${e}`);
|
|
182
|
+
return false;
|
|
183
|
+
}),
|
|
184
|
+
sinkWiki(msg, agentResponse).catch((e) => {
|
|
185
|
+
errors.push(`wiki: ${e}`);
|
|
186
|
+
return false;
|
|
187
|
+
}),
|
|
188
|
+
sinkEntityExtraction(msg, agentResponse, agentName).catch((e) => {
|
|
189
|
+
errors.push(`entity-extraction: ${e}`);
|
|
190
|
+
return false;
|
|
191
|
+
})
|
|
192
|
+
]);
|
|
193
|
+
if (errors.length > 0) {
|
|
194
|
+
process.stderr.write(
|
|
195
|
+
`[pipeline] ${errors.length} sink error(s): ${errors.join("; ")}
|
|
196
|
+
`
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
return { conversationStored, memorySunk, crmSunk, wikiSunk, entityExtracted, errors };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export {
|
|
203
|
+
ingest
|
|
204
|
+
};
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
import {
|
|
2
|
+
extractMemoryGraph
|
|
3
|
+
} from "./chunk-PKJFMRUG.js";
|
|
4
|
+
import {
|
|
5
|
+
flushBatch,
|
|
6
|
+
writeMemory
|
|
7
|
+
} from "./chunk-2GU3NYMB.js";
|
|
8
|
+
import {
|
|
9
|
+
extractKeywords
|
|
10
|
+
} from "./chunk-CHCA3ZM2.js";
|
|
11
|
+
import {
|
|
12
|
+
getActiveAgent
|
|
13
|
+
} from "./chunk-YG7PIB2R.js";
|
|
14
|
+
|
|
15
|
+
// src/mcp/tools/import-conversations.ts
|
|
16
|
+
import { z } from "zod";
|
|
17
|
+
import crypto from "crypto";
|
|
18
|
+
import { readdir, readFile, stat } from "fs/promises";
|
|
19
|
+
import path, { resolve, normalize } from "path";
|
|
20
|
+
import os from "os";
|
|
21
|
+
|
|
22
|
+
// src/lib/conversation-parser.ts
|
|
23
|
+
function detectFormat(content, filePath) {
|
|
24
|
+
const lower = filePath.toLowerCase();
|
|
25
|
+
if (lower.endsWith(".md") || lower.endsWith(".markdown")) return "markdown";
|
|
26
|
+
const trimmed = content.trimStart();
|
|
27
|
+
if (!trimmed.startsWith("[") && !trimmed.startsWith("{")) return "markdown";
|
|
28
|
+
try {
|
|
29
|
+
const parsed = JSON.parse(trimmed);
|
|
30
|
+
if (Array.isArray(parsed) && parsed.length > 0) {
|
|
31
|
+
const first = parsed[0];
|
|
32
|
+
if (first && typeof first === "object") {
|
|
33
|
+
if ("mapping" in first && typeof first.mapping === "object") return "chatgpt";
|
|
34
|
+
if ("chat_messages" in first) return "claude";
|
|
35
|
+
if ("uuid" in first && "name" in first) return "claude";
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return "generic";
|
|
39
|
+
} catch {
|
|
40
|
+
return "markdown";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function extractChatGPTContent(node) {
|
|
44
|
+
const parts = node.message?.content?.parts;
|
|
45
|
+
if (!Array.isArray(parts)) return "";
|
|
46
|
+
return parts.filter((p) => typeof p === "string").join("\n").trim();
|
|
47
|
+
}
|
|
48
|
+
function parseChatGPT(content, sourceFile = "conversations.json") {
|
|
49
|
+
const data = JSON.parse(content);
|
|
50
|
+
if (!Array.isArray(data)) return [];
|
|
51
|
+
return data.map((conv) => {
|
|
52
|
+
const title = conv.title ?? "Untitled";
|
|
53
|
+
const messages = [];
|
|
54
|
+
if (conv.mapping) {
|
|
55
|
+
const nodes = Object.values(conv.mapping).filter((n) => n.message?.content?.parts && extractChatGPTContent(n).length > 0).sort((a, b) => (a.message?.create_time ?? 0) - (b.message?.create_time ?? 0));
|
|
56
|
+
for (const node of nodes) {
|
|
57
|
+
const role = node.message?.author?.role ?? "unknown";
|
|
58
|
+
const text = extractChatGPTContent(node);
|
|
59
|
+
if (!text) continue;
|
|
60
|
+
const createTime = node.message?.create_time;
|
|
61
|
+
messages.push({
|
|
62
|
+
role,
|
|
63
|
+
content: text,
|
|
64
|
+
timestamp: createTime ? new Date(createTime * 1e3).toISOString() : void 0
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return { title, messages, sourceFile };
|
|
69
|
+
}).filter((c) => c.messages.length > 0);
|
|
70
|
+
}
|
|
71
|
+
function parseClaude(content, sourceFile = "conversations.json") {
|
|
72
|
+
const data = JSON.parse(content);
|
|
73
|
+
if (!Array.isArray(data)) return [];
|
|
74
|
+
return data.map((conv) => {
|
|
75
|
+
const title = conv.name ?? conv.uuid ?? "Untitled";
|
|
76
|
+
const messages = [];
|
|
77
|
+
if (conv.chat_messages) {
|
|
78
|
+
for (const msg of conv.chat_messages) {
|
|
79
|
+
let text = msg.text ?? "";
|
|
80
|
+
if (!text && Array.isArray(msg.content)) {
|
|
81
|
+
text = msg.content.filter((c) => c.type === "text" && c.text).map((c) => c.text).join("\n");
|
|
82
|
+
}
|
|
83
|
+
if (!text.trim()) continue;
|
|
84
|
+
messages.push({
|
|
85
|
+
role: msg.sender ?? "unknown",
|
|
86
|
+
content: text.trim(),
|
|
87
|
+
timestamp: msg.created_at
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return { title, messages, sourceFile };
|
|
92
|
+
}).filter((c) => c.messages.length > 0);
|
|
93
|
+
}
|
|
94
|
+
function parseGeneric(content, sourceFile = "conversations.json") {
|
|
95
|
+
const trimmed = content.trimStart();
|
|
96
|
+
try {
|
|
97
|
+
const data = JSON.parse(trimmed);
|
|
98
|
+
if (Array.isArray(data)) {
|
|
99
|
+
if (data.length > 0 && typeof data[0] === "object" && ("role" in data[0] || "content" in data[0])) {
|
|
100
|
+
const messages = data.map((msg) => ({
|
|
101
|
+
role: msg.role ?? "unknown",
|
|
102
|
+
content: (msg.content ?? msg.text ?? "").trim(),
|
|
103
|
+
timestamp: msg.timestamp ?? msg.created_at
|
|
104
|
+
})).filter((m) => m.content.length > 0);
|
|
105
|
+
if (messages.length > 0) {
|
|
106
|
+
return [{ title: sourceFile, messages, sourceFile }];
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
} catch {
|
|
111
|
+
}
|
|
112
|
+
return parseMarkdown(content, sourceFile);
|
|
113
|
+
}
|
|
114
|
+
function parseMarkdown(content, sourceFile = "conversation.md") {
|
|
115
|
+
const lines = content.split("\n");
|
|
116
|
+
const conversations = [];
|
|
117
|
+
let currentTitle = sourceFile;
|
|
118
|
+
let currentMessages = [];
|
|
119
|
+
let currentRole = "user";
|
|
120
|
+
let currentContent = [];
|
|
121
|
+
function flushMessage() {
|
|
122
|
+
const text = currentContent.join("\n").trim();
|
|
123
|
+
if (text) {
|
|
124
|
+
currentMessages.push({ role: currentRole, content: text });
|
|
125
|
+
}
|
|
126
|
+
currentContent = [];
|
|
127
|
+
}
|
|
128
|
+
function flushConversation() {
|
|
129
|
+
flushMessage();
|
|
130
|
+
if (currentMessages.length > 0) {
|
|
131
|
+
conversations.push({ title: currentTitle, messages: [...currentMessages], sourceFile });
|
|
132
|
+
}
|
|
133
|
+
currentMessages = [];
|
|
134
|
+
}
|
|
135
|
+
for (const line of lines) {
|
|
136
|
+
const h1Match = line.match(/^# (.+)$/);
|
|
137
|
+
if (h1Match) {
|
|
138
|
+
flushConversation();
|
|
139
|
+
currentTitle = h1Match[1];
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
const roleMatch = line.match(/^(?:##\s+|\*\*)(user|assistant|human|system|ai|claude|gpt)(?:\*\*)?:?\s*/i);
|
|
143
|
+
if (roleMatch) {
|
|
144
|
+
flushMessage();
|
|
145
|
+
const raw = roleMatch[1].toLowerCase();
|
|
146
|
+
currentRole = raw === "human" || raw === "user" ? "user" : raw === "assistant" || raw === "ai" || raw === "claude" || raw === "gpt" ? "assistant" : raw;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
currentContent.push(line);
|
|
150
|
+
}
|
|
151
|
+
flushConversation();
|
|
152
|
+
return conversations;
|
|
153
|
+
}
|
|
154
|
+
function conversationsToMemories(convos, format = "generic") {
|
|
155
|
+
const memories = [];
|
|
156
|
+
for (const convo of convos) {
|
|
157
|
+
for (const msg of convo.messages) {
|
|
158
|
+
if (msg.content.length < 10) continue;
|
|
159
|
+
memories.push({
|
|
160
|
+
text: msg.content,
|
|
161
|
+
kind: "conversation",
|
|
162
|
+
metadata: {
|
|
163
|
+
source_type: "import",
|
|
164
|
+
source_format: format,
|
|
165
|
+
source_path: convo.sourceFile,
|
|
166
|
+
conversation_title: convo.title,
|
|
167
|
+
role: msg.role,
|
|
168
|
+
original_timestamp: msg.timestamp
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return memories;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// src/mcp/tools/import-conversations.ts
|
|
177
|
+
var DEFAULT_CAP = 5e4;
|
|
178
|
+
var YIELD_INTERVAL = 50;
|
|
179
|
+
async function readInputFiles(inputPath) {
|
|
180
|
+
const files = [];
|
|
181
|
+
const info = await stat(inputPath);
|
|
182
|
+
if (info.isDirectory()) {
|
|
183
|
+
const entries = await readdir(inputPath);
|
|
184
|
+
for (const entry of entries) {
|
|
185
|
+
const fullPath = path.join(inputPath, entry);
|
|
186
|
+
const entryStat = await stat(fullPath);
|
|
187
|
+
if (entryStat.isFile() && (entry.endsWith(".json") || entry.endsWith(".md") || entry.endsWith(".txt"))) {
|
|
188
|
+
files.push({ path: fullPath, content: await readFile(fullPath, "utf-8") });
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
} else {
|
|
192
|
+
files.push({ path: inputPath, content: await readFile(inputPath, "utf-8") });
|
|
193
|
+
}
|
|
194
|
+
return files;
|
|
195
|
+
}
|
|
196
|
+
function parseFile(content, filePath, requestedFormat) {
|
|
197
|
+
const format = requestedFormat && requestedFormat !== "auto" ? requestedFormat : detectFormat(content, filePath);
|
|
198
|
+
let conversations;
|
|
199
|
+
switch (format) {
|
|
200
|
+
case "chatgpt":
|
|
201
|
+
conversations = parseChatGPT(content, filePath);
|
|
202
|
+
break;
|
|
203
|
+
case "claude":
|
|
204
|
+
conversations = parseClaude(content, filePath);
|
|
205
|
+
break;
|
|
206
|
+
case "markdown":
|
|
207
|
+
conversations = parseMarkdown(content, filePath);
|
|
208
|
+
break;
|
|
209
|
+
case "generic":
|
|
210
|
+
default:
|
|
211
|
+
conversations = parseGeneric(content, filePath);
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
return { format, conversations };
|
|
215
|
+
}
|
|
216
|
+
function registerImportConversations(server) {
|
|
217
|
+
server.registerTool(
|
|
218
|
+
"import_conversations",
|
|
219
|
+
{
|
|
220
|
+
title: "Import Conversations",
|
|
221
|
+
description: "Import ChatGPT, Claude, or generic conversation exports into memory. Parses, extracts keywords + graph entities, stores with proper metadata. Deduplicates via content_hash. Supports JSON, markdown, and directory input.",
|
|
222
|
+
inputSchema: {
|
|
223
|
+
path: z.string().describe("File or directory path to import"),
|
|
224
|
+
format: z.enum(["chatgpt", "claude", "generic", "markdown", "auto"]).default("auto").describe("Source format (default: auto-detect)"),
|
|
225
|
+
project_name: z.string().optional().describe("Project name for imported memories"),
|
|
226
|
+
dry_run: z.boolean().default(false).describe("Parse and report counts without storing"),
|
|
227
|
+
force: z.boolean().default(false).describe("Override 50K memory cap"),
|
|
228
|
+
agent_id: z.string().optional().describe("Agent to attribute memories to (default: current agent)")
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
async ({ path: inputPath, format: requestedFormat, project_name, dry_run, force, agent_id: overrideAgentId }) => {
|
|
232
|
+
const { agentId: currentAgentId, agentRole } = getActiveAgent();
|
|
233
|
+
const agentId = overrideAgentId ?? currentAgentId;
|
|
234
|
+
const projectName = project_name ?? "imported";
|
|
235
|
+
const resolvedPath = resolve(normalize(inputPath));
|
|
236
|
+
const home = process.env.HOME || os.homedir();
|
|
237
|
+
const blocked = ["/etc", "/root", "/var", "/usr", "/sys", "/proc"];
|
|
238
|
+
const sensitiveHome = [".ssh", ".gnupg", ".aws", ".config/gcloud"];
|
|
239
|
+
for (const dir of blocked) {
|
|
240
|
+
if (resolvedPath.startsWith(dir + "/") || resolvedPath === dir) {
|
|
241
|
+
return {
|
|
242
|
+
content: [{ type: "text", text: `Access denied: cannot read files from ${dir}` }],
|
|
243
|
+
isError: true
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
for (const rel of sensitiveHome) {
|
|
248
|
+
const full = resolve(home, rel);
|
|
249
|
+
if (resolvedPath.startsWith(full + "/") || resolvedPath === full) {
|
|
250
|
+
return {
|
|
251
|
+
content: [{ type: "text", text: `Access denied: cannot read files from ~/${rel}` }],
|
|
252
|
+
isError: true
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
let files;
|
|
257
|
+
try {
|
|
258
|
+
files = await readInputFiles(inputPath);
|
|
259
|
+
} catch (err) {
|
|
260
|
+
return {
|
|
261
|
+
content: [{ type: "text", text: `Error reading ${inputPath}: ${err instanceof Error ? err.message : String(err)}` }],
|
|
262
|
+
isError: true
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
if (files.length === 0) {
|
|
266
|
+
return {
|
|
267
|
+
content: [{ type: "text", text: `No importable files found at ${inputPath}` }],
|
|
268
|
+
isError: true
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
const allMemories = [];
|
|
272
|
+
let conversationCount = 0;
|
|
273
|
+
const formatCounts = {};
|
|
274
|
+
for (const file of files) {
|
|
275
|
+
const { format, conversations } = parseFile(file.content, file.path, requestedFormat);
|
|
276
|
+
formatCounts[format] = (formatCounts[format] ?? 0) + conversations.length;
|
|
277
|
+
conversationCount += conversations.length;
|
|
278
|
+
const memories = conversationsToMemories(conversations, format);
|
|
279
|
+
allMemories.push(...memories);
|
|
280
|
+
}
|
|
281
|
+
if (allMemories.length > DEFAULT_CAP && !force) {
|
|
282
|
+
return {
|
|
283
|
+
content: [{
|
|
284
|
+
type: "text",
|
|
285
|
+
text: `Import would create ${allMemories.length} memories (cap: ${DEFAULT_CAP}). Use force=true to override. Files: ${files.length}, Conversations: ${conversationCount}`
|
|
286
|
+
}],
|
|
287
|
+
isError: true
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
if (dry_run) {
|
|
291
|
+
const formatSummary2 = Object.entries(formatCounts).map(([f, c]) => `${f}: ${c}`).join(", ");
|
|
292
|
+
return {
|
|
293
|
+
content: [{
|
|
294
|
+
type: "text",
|
|
295
|
+
text: `[DRY RUN] Would import:
|
|
296
|
+
Files: ${files.length}
|
|
297
|
+
Conversations: ${conversationCount} (${formatSummary2})
|
|
298
|
+
Memories: ${allMemories.length}
|
|
299
|
+
Agent: ${agentId}
|
|
300
|
+
Project: ${projectName}`
|
|
301
|
+
}]
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
let stored = 0;
|
|
305
|
+
let skipped = 0;
|
|
306
|
+
for (let i = 0; i < allMemories.length; i++) {
|
|
307
|
+
const memory = allMemories[i];
|
|
308
|
+
try {
|
|
309
|
+
const keywords = extractKeywords(memory.text);
|
|
310
|
+
const keywordsStr = keywords.length > 0 ? keywords.join(" ") : null;
|
|
311
|
+
let entities = null;
|
|
312
|
+
try {
|
|
313
|
+
const graph = extractMemoryGraph(memory.text, agentId, projectName, "import_conversations");
|
|
314
|
+
if (graph.entities.length > 0) {
|
|
315
|
+
entities = JSON.stringify(graph.entities.map((e) => e.name));
|
|
316
|
+
}
|
|
317
|
+
} catch {
|
|
318
|
+
}
|
|
319
|
+
const id = crypto.randomUUID();
|
|
320
|
+
await writeMemory({
|
|
321
|
+
id,
|
|
322
|
+
agent_id: agentId,
|
|
323
|
+
agent_role: agentRole,
|
|
324
|
+
session_id: process.env.SESSION_ID ?? "import",
|
|
325
|
+
timestamp: memory.metadata.original_timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
326
|
+
tool_name: "import_conversations",
|
|
327
|
+
project_name: projectName,
|
|
328
|
+
has_error: false,
|
|
329
|
+
raw_text: memory.text,
|
|
330
|
+
vector: null,
|
|
331
|
+
// Backfill later
|
|
332
|
+
source_path: memory.metadata.source_path,
|
|
333
|
+
source_type: "import",
|
|
334
|
+
memory_type: "conversation",
|
|
335
|
+
tier: 3,
|
|
336
|
+
// Passive ingestion
|
|
337
|
+
importance: memory.metadata.role === "assistant" ? 5 : 4,
|
|
338
|
+
intent: "observation",
|
|
339
|
+
domain: projectName,
|
|
340
|
+
referenced_entities: entities,
|
|
341
|
+
keywords: keywordsStr,
|
|
342
|
+
valid_from: memory.metadata.original_timestamp ?? void 0
|
|
343
|
+
});
|
|
344
|
+
stored++;
|
|
345
|
+
} catch {
|
|
346
|
+
skipped++;
|
|
347
|
+
}
|
|
348
|
+
if ((i + 1) % YIELD_INTERVAL === 0) {
|
|
349
|
+
await flushBatch();
|
|
350
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
351
|
+
if ((i + 1) % (YIELD_INTERVAL * 10) === 0) {
|
|
352
|
+
process.stderr.write(`[import] ${i + 1}/${allMemories.length} memories processed, ${stored} stored
|
|
353
|
+
`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
await flushBatch();
|
|
358
|
+
const formatSummary = Object.entries(formatCounts).map(([f, c]) => `${f}: ${c}`).join(", ");
|
|
359
|
+
return {
|
|
360
|
+
content: [{
|
|
361
|
+
type: "text",
|
|
362
|
+
text: `Import complete.
|
|
363
|
+
Files: ${files.length}
|
|
364
|
+
Conversations: ${conversationCount} (${formatSummary})
|
|
365
|
+
Memories stored: ${stored}
|
|
366
|
+
Duplicates skipped: ${skipped}
|
|
367
|
+
Agent: ${agentId}
|
|
368
|
+
Project: ${projectName}`
|
|
369
|
+
}]
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
export {
|
|
376
|
+
registerImportConversations
|
|
377
|
+
};
|