clay-server 3.8.1 → 4.0.0-beta.10
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/lib/ask-user-mcp-server.js +5 -119
- package/lib/background-task-timing.js +105 -0
- package/lib/builtin-mates.js +6 -6
- package/lib/claude-hook-installer.js +15 -0
- package/lib/clay-history-mcp-server.js +62 -297
- package/lib/daemon.js +23 -1
- package/lib/debate-model-selection.js +119 -0
- package/lib/home-debate-tool-policy.js +164 -0
- package/lib/home-response-notifications.js +26 -0
- package/lib/knowledge-import.js +436 -0
- package/lib/knowledge-record-store.js +188 -0
- package/lib/knowledge-search.js +224 -0
- package/lib/mate-creation-mcp-server.js +55 -0
- package/lib/mate-knowledge-mcp-server.js +151 -0
- package/lib/mate-knowledge-migration.js +283 -0
- package/lib/mate-knowledge-service.js +488 -0
- package/lib/mate-knowledge-sync.js +482 -0
- package/lib/mate-ready-creation.js +95 -0
- package/lib/mates.js +2 -0
- package/lib/notes.js +2 -1
- package/lib/project-ask-user.js +64 -0
- package/lib/project-capsule-catalog.js +101 -0
- package/lib/project-connection.js +17 -0
- package/lib/project-debate-proposal.js +133 -17
- package/lib/project-debate.js +214 -110
- package/lib/project-delegated-follow-up.js +84 -0
- package/lib/project-delegated-session.js +53 -0
- package/lib/project-filesystem.js +7 -9
- package/lib/project-knowledge.js +23 -0
- package/lib/project-logs-comments.js +137 -0
- package/lib/project-logs-mcp-server.js +290 -0
- package/lib/project-logs-query.js +167 -0
- package/lib/project-logs-root.js +67 -0
- package/lib/project-logs-schema.js +259 -0
- package/lib/project-logs-service.js +357 -0
- package/lib/project-logs-snapshot.js +202 -0
- package/lib/project-logs-store.js +498 -0
- package/lib/project-logs-versioning.js +128 -0
- package/lib/project-logs.js +347 -0
- package/lib/project-mate-creation-proposal.js +120 -0
- package/lib/project-mate-interaction.js +14 -0
- package/lib/project-mate-knowledge.js +110 -0
- package/lib/project-memory.js +36 -7
- package/lib/project-models.js +157 -32
- package/lib/project-session-notes.js +2 -2
- package/lib/project-session-pair.js +8 -8
- package/lib/project-sessions.js +66 -51
- package/lib/project-vendor-login.js +414 -0
- package/lib/project-worker-proposal.js +25 -25
- package/lib/project-workspace-query.js +144 -0
- package/lib/project.js +433 -102
- package/lib/public/app.js +109 -48
- package/lib/public/css/avatar-imprints.css +0 -1
- package/lib/public/css/base.css +4 -0
- package/lib/public/css/capsule-ui.css +389 -0
- package/lib/public/css/command-palette.css +306 -6
- package/lib/public/css/home-capsule-library.css +243 -0
- package/lib/public/css/home-capsule-source.css +192 -0
- package/lib/public/css/home-debate-live.css +146 -0
- package/lib/public/css/home-debate-planning.css +125 -0
- package/lib/public/css/home-debates-archive.css +281 -0
- package/lib/public/css/home-hub.css +896 -405
- package/lib/public/css/home-mate-creation.css +34 -0
- package/lib/public/css/home-mate-model-picker.css +131 -0
- package/lib/public/css/home-mate-settings.css +344 -0
- package/lib/public/css/home-session-actions.css +265 -0
- package/lib/public/css/home-sidebar.css +667 -0
- package/lib/public/css/icon-strip.css +1 -2
- package/lib/public/css/input.css +79 -25
- package/lib/public/css/mates.css +7 -34
- package/lib/public/css/pane.css +1 -1
- package/lib/public/css/playbook.css +0 -80
- package/lib/public/css/project-logs.css +234 -0
- package/lib/public/css/scheduler.css +1 -1
- package/lib/public/css/sticky-notes.css +53 -117
- package/lib/public/css/tui-attention.css +0 -44
- package/lib/public/css/workspace-assignment.css +45 -0
- package/lib/public/fonts/source-serif-4/LICENSE.md +93 -0
- package/lib/public/fonts/source-serif-4/SourceSerif4Caption-Semibold.ttf.woff2 +0 -0
- package/lib/public/index.html +96 -74
- package/lib/public/modules/app-connection.js +100 -5
- package/lib/public/modules/app-dm.js +43 -160
- package/lib/public/modules/app-home-hub.js +328 -585
- package/lib/public/modules/app-message-cards.js +242 -0
- package/lib/public/modules/app-message-router.js +84 -0
- package/lib/public/modules/app-messages.js +124 -38
- package/lib/public/modules/app-notifications.js +8 -119
- package/lib/public/modules/app-panels.js +43 -2
- package/lib/public/modules/app-projects.js +9 -1
- package/lib/public/modules/app-rendering.js +21 -602
- package/lib/public/modules/avatar-imprint.js +11 -8
- package/lib/public/modules/background-tasks-ui.js +139 -16
- package/lib/public/modules/chat-bubble-renderer.js +152 -0
- package/lib/public/modules/chat-render-runtime.js +198 -0
- package/lib/public/modules/command-palette.js +225 -436
- package/lib/public/modules/dm-render.js +82 -0
- package/lib/public/modules/home-capsule-creation-intent.js +31 -0
- package/lib/public/modules/home-capsule-library.js +146 -0
- package/lib/public/modules/home-capsule-source.js +383 -0
- package/lib/public/modules/home-chat-identity.js +35 -0
- package/lib/public/modules/home-chat-scroll.js +77 -0
- package/lib/public/modules/home-chat-stream-state.js +34 -0
- package/lib/public/modules/home-composer-focus.js +25 -0
- package/lib/public/modules/home-conversations-sheet.js +179 -0
- package/lib/public/modules/home-debate-activity.js +48 -0
- package/lib/public/modules/home-debate-controls.js +295 -0
- package/lib/public/modules/home-debate-launch.js +41 -0
- package/lib/public/modules/home-debate-live.js +284 -0
- package/lib/public/modules/home-debate-models.js +73 -0
- package/lib/public/modules/home-debate-planning.js +335 -0
- package/lib/public/modules/home-debates-archive.js +279 -0
- package/lib/public/modules/home-dock-resize.js +74 -0
- package/lib/public/modules/home-dock.js +446 -0
- package/lib/public/modules/home-mate-chat.js +496 -0
- package/lib/public/modules/home-mate-creation.js +109 -0
- package/lib/public/modules/home-mate-model-picker.js +281 -0
- package/lib/public/modules/home-mate-selection.js +38 -0
- package/lib/public/modules/home-mate-settings-menu.js +131 -0
- package/lib/public/modules/home-mate-settings.js +383 -0
- package/lib/public/modules/home-session-actions.js +373 -0
- package/lib/public/modules/home-session-model-confirmation.js +13 -0
- package/lib/public/modules/home-shell.js +9 -0
- package/lib/public/modules/home-sidebar-chat-list.js +63 -0
- package/lib/public/modules/home-sidebar.js +165 -0
- package/lib/public/modules/home-sub-surface.js +19 -0
- package/lib/public/modules/home-surface-boot.js +68 -0
- package/lib/public/modules/home-surface.js +141 -0
- package/lib/public/modules/home-tools.js +271 -0
- package/lib/public/modules/markdown.js +50 -2
- package/lib/public/modules/mate-management.js +86 -0
- package/lib/public/modules/mate-sidebar.js +0 -8
- package/lib/public/modules/mate-wizard.js +0 -7
- package/lib/public/modules/mention.js +17 -19
- package/lib/public/modules/pane-bridge.js +9 -0
- package/lib/public/modules/profile.js +4 -5
- package/lib/public/modules/project-logs-ambient.js +238 -0
- package/lib/public/modules/project-logs-render.js +359 -0
- package/lib/public/modules/project-logs.js +328 -0
- package/lib/public/modules/project-settings.js +34 -28
- package/lib/public/modules/project-switcher.js +11 -5
- package/lib/public/modules/project-workspace-assignment.js +23 -0
- package/lib/public/modules/scheduler.js +17 -3
- package/lib/public/modules/search-clay-chat.js +402 -0
- package/lib/public/modules/server-settings.js +14 -12
- package/lib/public/modules/session-actions.js +1 -1
- package/lib/public/modules/sidebar-mates.js +56 -552
- package/lib/public/modules/sidebar-mobile.js +12 -73
- package/lib/public/modules/sidebar-presence.js +37 -0
- package/lib/public/modules/sidebar-sessions.js +25 -1
- package/lib/public/modules/split-pair-ui.js +13 -13
- package/lib/public/modules/split-view.js +7 -0
- package/lib/public/modules/sticky-notes.js +106 -53
- package/lib/public/modules/tool-input-composition.js +48 -0
- package/lib/public/modules/tool-llm-status.js +167 -0
- package/lib/public/modules/tool-palette.js +76 -4
- package/lib/public/modules/tool-renderer-advanced.js +285 -0
- package/lib/public/modules/tool-renderer-chart.js +158 -0
- package/lib/public/modules/tool-renderer-semantics.js +35 -0
- package/lib/public/modules/tool-renderer.js +464 -0
- package/lib/public/modules/tool-runtime.js +309 -0
- package/lib/public/modules/tool-ui-evaluator.js +97 -0
- package/lib/public/modules/tool-ui-tree.js +21 -0
- package/lib/public/modules/tui-attention.js +10 -0
- package/lib/public/modules/user-settings.js +16 -23
- package/lib/public/modules/vendor-login.js +287 -0
- package/lib/public/modules/worker-proposal.js +2 -2
- package/lib/public/modules/workspace-assignment-card.js +149 -0
- package/lib/public/modules/workspace-assignment-routing.js +4 -0
- package/lib/public/style.css +14 -2
- package/lib/runtime-env.js +65 -0
- package/lib/sdk-bridge.js +120 -32
- package/lib/sdk-message-processor.js +34 -13
- package/lib/server-home-capsule-creation.js +36 -0
- package/lib/server-home-chat-events.js +287 -0
- package/lib/server-home-chat.js +518 -0
- package/lib/server-home-clay-entry.js +65 -0
- package/lib/server-home-clay-session-links.js +26 -0
- package/lib/server-home-debate-planning.js +214 -0
- package/lib/server-home-debates.js +137 -0
- package/lib/server-home-mate-creation.js +261 -0
- package/lib/server-home-models.js +241 -0
- package/lib/server-home-preferences.js +61 -0
- package/lib/server-mates.js +37 -0
- package/lib/server-tools.js +377 -0
- package/lib/server.js +166 -59
- package/lib/session-pair-mcp-server.js +6 -6
- package/lib/sessions.js +22 -4
- package/lib/tool-capsule-source.js +142 -0
- package/lib/tool-control-mcp-server.js +126 -0
- package/lib/tool-llm.js +48 -0
- package/lib/tool-storage.js +77 -0
- package/lib/tool-ui-spec-advanced.js +82 -0
- package/lib/tool-ui-spec.js +261 -0
- package/lib/tools-registry.js +286 -0
- package/lib/users-home-dock-preferences.js +77 -0
- package/lib/users-home-surface-preferences.js +99 -0
- package/lib/users-preferences.js +4 -7
- package/lib/users.js +12 -0
- package/lib/whats-new-content.js +3 -3
- package/lib/workspace-assignment-service.js +420 -0
- package/lib/workspace-query-mcp-server.js +154 -0
- package/lib/workspace-query-service.js +492 -0
- package/lib/ws-schema.js +107 -12
- package/lib/yoke/adapters/acp.js +2 -1
- package/lib/yoke/adapters/antigravity.js +2 -1
- package/lib/yoke/adapters/claude-worker.js +11 -4
- package/lib/yoke/adapters/claude.js +141 -46
- package/lib/yoke/adapters/codex.js +79 -11
- package/lib/yoke/adapters/kiro.js +2 -1
- package/lib/yoke/claude-user-input.js +38 -0
- package/lib/yoke/codex-app-server.js +20 -2
- package/lib/yoke/codex-background-tasks.js +8 -2
- package/lib/yoke/codex-user-input.js +64 -0
- package/lib/yoke/complete-once.js +164 -0
- package/lib/yoke/index.js +5 -1
- package/lib/yoke/interface.js +19 -0
- package/lib/yoke/mcp-bridge-server.js +2 -6
- package/lib/yoke/user-input.js +254 -0
- package/package.json +4 -3
- package/lib/public/css/home-chat.css +0 -380
- package/lib/public/modules/home-chat.js +0 -438
- package/lib/server-clay-home.js +0 -245
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
var splitShellSegments = require("./sdk-skill-discovery").splitShellSegments;
|
|
2
|
+
var isSafeBashSegment = require("./safe-bash-commands").isSafeBashSegment;
|
|
3
|
+
|
|
4
|
+
function hasTopicAnswer(session) {
|
|
5
|
+
if (session && typeof session.homeDebateInitialTopic === "string" && session.homeDebateInitialTopic.trim()) return true;
|
|
6
|
+
var history = session && Array.isArray(session.history) ? session.history : [];
|
|
7
|
+
var firstQuestionId = null;
|
|
8
|
+
for (var i = 0; i < history.length; i++) {
|
|
9
|
+
var event = history[i];
|
|
10
|
+
if (!firstQuestionId && event && event.type === "tool_executing" && event.name === "AskUserQuestion") firstQuestionId = event.id || null;
|
|
11
|
+
if (firstQuestionId && event && event.type === "ask_user_answered" && event.toolId === firstQuestionId) return true;
|
|
12
|
+
}
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function hasProviderMateQuestion(session) {
|
|
17
|
+
var history = session && Array.isArray(session.history) ? session.history : [];
|
|
18
|
+
for (var i = 0; i < history.length; i++) {
|
|
19
|
+
var event = history[i];
|
|
20
|
+
if (event && event.type === "tool_executing" && event.name === "AskUserQuestion" && typeof event.id === "string" && event.id.indexOf("mate_creation_intent_") !== 0) return true;
|
|
21
|
+
}
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function initialToolDecision(session, toolName, input) {
|
|
26
|
+
var debatePlanning = session && session.homeDebatePlanning === true;
|
|
27
|
+
var mateCreation = session && session.mateCreationMode === true;
|
|
28
|
+
if (!debatePlanning && !mateCreation) return null;
|
|
29
|
+
if (mateCreation && toolName === "Skill") return { behavior: "deny", message: "The complete Mate interview contract is already provided by Clay." };
|
|
30
|
+
var isSessionQuestion = toolName === "ask_user_questions" || toolName === "mcp__clay-ask-user__ask_user_questions";
|
|
31
|
+
if (mateCreation && hasTopicAnswer(session) && !hasProviderMateQuestion(session)) {
|
|
32
|
+
if (isSessionQuestion) return { behavior: "allow", updatedInput: input };
|
|
33
|
+
if (toolName === "AskUserQuestion") return { behavior: "deny", message: "Use the session-bound ask_user_questions tool for the next Mate interview question." };
|
|
34
|
+
return { behavior: "deny", message: "Present the next Mate interview question before inspecting context or using other tools." };
|
|
35
|
+
}
|
|
36
|
+
if (hasTopicAnswer(session)) return null;
|
|
37
|
+
if (isSessionQuestion || toolName === "Skill") return { behavior: "allow", updatedInput: input };
|
|
38
|
+
if (toolName === "AskUserQuestion") return { behavior: "deny", message: mateCreation ? "Use the session-bound ask_user_questions tool for the initial freeform Mate question." : "Use the session-bound ask_user_questions tool for the initial freeform topic question." };
|
|
39
|
+
return { behavior: "deny", message: mateCreation ? "Ask what kind of Mate the user wants before inspecting context or using other tools." : "Ask for the debate topic before inspecting context or using other tools." };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
var NON_MUTATING_TOOLS = {
|
|
43
|
+
Read: true,
|
|
44
|
+
Glob: true,
|
|
45
|
+
Grep: true,
|
|
46
|
+
WebFetch: true,
|
|
47
|
+
WebSearch: true,
|
|
48
|
+
Skill: true,
|
|
49
|
+
TodoRead: true,
|
|
50
|
+
TaskOutput: true,
|
|
51
|
+
ListMcpResources: true,
|
|
52
|
+
ReadMcpResource: true,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
var NON_MUTATING_MCP_TOOLS = {
|
|
56
|
+
"mcp__clay-workspace__list_projects": true,
|
|
57
|
+
"mcp__clay-workspace__list_project_sessions": true,
|
|
58
|
+
"mcp__clay-workspace__search_project_history": true,
|
|
59
|
+
"mcp__clay-workspace__read_project_session": true,
|
|
60
|
+
"mcp__clay-workspace__search_workspace_history": true,
|
|
61
|
+
"mcp__clay-workspace__list_workspace_activity": true,
|
|
62
|
+
"mcp__clay-workspace__get_assignment_status": true,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
function safeBash(input) {
|
|
66
|
+
if (!input || typeof input.command !== "string" || !input.command.trim()) return false;
|
|
67
|
+
var command = input.command.trim();
|
|
68
|
+
if (/[<>`]/.test(command) || /\$\(/.test(command) || /(^|\s)sudo(\s|$)/.test(command)) return false;
|
|
69
|
+
var segments = splitShellSegments(command);
|
|
70
|
+
if (!segments.length) return false;
|
|
71
|
+
for (var i = 0; i < segments.length; i++) {
|
|
72
|
+
if (!isSafeBashSegment(segments[i])) return false;
|
|
73
|
+
var segment = segments[i].trim();
|
|
74
|
+
var commandName = segment.replace(/^(?:\w+=\S*\s+)*/, "").split(/\s+/)[0];
|
|
75
|
+
if ({ env: true, command: true, time: true, tee: true, xargs: true, wget: true, http: true }[commandName]) return false;
|
|
76
|
+
if (commandName === "find" && /\s-(?:delete|exec|execdir|ok|okdir)(?:\s|$)/.test(segment)) return false;
|
|
77
|
+
if (commandName === "sed" && /\s-(?:[^\s]*i[^\s]*|file)(?:\s|$)/.test(segment)) return false;
|
|
78
|
+
if (commandName === "awk" && /\bsystem\s*\(/.test(segment)) return false;
|
|
79
|
+
if (commandName === "curl" && /\s(?:-o|-O|-T|-d|-F|--output|--remote-name|--upload-file|--request|--data|--form|-X)(?:\s|=|$)/.test(segment)) return false;
|
|
80
|
+
}
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
var DISPLAYABLE_COMMANDS = {
|
|
85
|
+
rg: true, grep: true, git: true, find: true, ls: true, pwd: true, wc: true, head: true, tail: true,
|
|
86
|
+
sed: true, awk: true, sort: true, uniq: true, diff: true, stat: true, file: true, tree: true, curl: true,
|
|
87
|
+
rm: true, mv: true, cp: true, mkdir: true, touch: true, chmod: true, chown: true, npm: true, node: true,
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
function shellCommandNames(input) {
|
|
91
|
+
if (!input || typeof input.command !== "string") return [];
|
|
92
|
+
var segments = splitShellSegments(input.command.trim());
|
|
93
|
+
var names = [];
|
|
94
|
+
for (var i = 0; i < segments.length && names.length < 3; i++) {
|
|
95
|
+
var name = segments[i].trim().replace(/^(?:\w+=\S*\s+)*/, "").split(/\s+/)[0] || "";
|
|
96
|
+
name = name.split("/").pop().replace(/[^a-zA-Z0-9._-]/g, "").slice(0, 24);
|
|
97
|
+
if (DISPLAYABLE_COMMANDS[name] && names.indexOf(name) === -1) names.push(name);
|
|
98
|
+
}
|
|
99
|
+
return names;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function toolAction(toolName, input, allowed) {
|
|
103
|
+
if (toolName === "Bash") {
|
|
104
|
+
var names = shellCommandNames(input);
|
|
105
|
+
return (allowed ? "Run read-only shell commands" : "Run shell commands") + (names.length ? " (" + names.join(", ") + ")" : "");
|
|
106
|
+
}
|
|
107
|
+
if (toolName === "Read") return "Read project files";
|
|
108
|
+
if (toolName === "Glob") return "Find project files";
|
|
109
|
+
if (toolName === "Grep") return "Search project text";
|
|
110
|
+
if (toolName === "WebSearch") return "Search the web";
|
|
111
|
+
if (toolName === "WebFetch") return "Read a web page";
|
|
112
|
+
if (toolName === "Edit" || toolName === "NotebookEdit") return "Edit a project file";
|
|
113
|
+
if (toolName === "Write") return "Write a project file";
|
|
114
|
+
if (toolName.indexOf("search_workspace_history") !== -1) return "Search workspace history";
|
|
115
|
+
if (toolName.indexOf("read_project_session") !== -1) return "Read a project session";
|
|
116
|
+
if (toolName.indexOf("list_project_sessions") !== -1) return "List project sessions";
|
|
117
|
+
if (toolName.indexOf("list_projects") !== -1) return "List projects";
|
|
118
|
+
return "Use " + displayToolName(toolName);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function decisionReason(toolName, allowed) {
|
|
122
|
+
if (allowed) return "Read-only investigation";
|
|
123
|
+
if (toolName === "Edit" || toolName === "Write" || toolName === "NotebookEdit") return "Would modify project files";
|
|
124
|
+
if (toolName === "Bash") return "Command may change the system";
|
|
125
|
+
return "Not verified as read-only";
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function auditCommand(toolName, input) {
|
|
129
|
+
if (toolName !== "Bash" || !input || typeof input.command !== "string") return { command: "", truncated: false };
|
|
130
|
+
var clean = input.command.replace(/\r/g, "").replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, "").trim();
|
|
131
|
+
return { command: clean.slice(0, 4000), truncated: clean.length > 4000 };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function debateToolDecision(toolName, input) {
|
|
135
|
+
toolName = typeof toolName === "string" ? toolName : "";
|
|
136
|
+
var allowed = NON_MUTATING_TOOLS[toolName] === true;
|
|
137
|
+
if (toolName === "Bash") allowed = safeBash(input);
|
|
138
|
+
if (NON_MUTATING_MCP_TOOLS[toolName] === true || toolName.indexOf("mcp__clay-history__") === 0) allowed = true;
|
|
139
|
+
var audit = auditCommand(toolName, input);
|
|
140
|
+
return {
|
|
141
|
+
allowed: allowed,
|
|
142
|
+
behavior: allowed ? "allow" : "deny",
|
|
143
|
+
updatedInput: allowed ? input : undefined,
|
|
144
|
+
message: allowed ? undefined : "The moderator blocked this tool because debate participants may not change the system.",
|
|
145
|
+
action: toolAction(toolName, input, allowed),
|
|
146
|
+
reason: decisionReason(toolName, allowed),
|
|
147
|
+
command: audit.command,
|
|
148
|
+
commandTruncated: audit.truncated,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function displayToolName(toolName) {
|
|
153
|
+
var name = typeof toolName === "string" ? toolName : "Tool";
|
|
154
|
+
if (name.indexOf("mcp__") === 0) name = name.substring(name.lastIndexOf("__") + 2);
|
|
155
|
+
name = name.replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim();
|
|
156
|
+
if (!name) return "Tool";
|
|
157
|
+
return name.length > 48 ? name.slice(0, 45).trim() + "..." : name;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
module.exports = {
|
|
161
|
+
initialToolDecision: initialToolDecision,
|
|
162
|
+
debateToolDecision: debateToolDecision,
|
|
163
|
+
displayToolName: displayToolName,
|
|
164
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Exact Home presentation checks for Mate response notifications.
|
|
2
|
+
|
|
3
|
+
function sessionReference(session) {
|
|
4
|
+
return session && (session.cliSessionId || (session.localId != null ? "local:" + session.localId : null));
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function shouldSuppressHomeResponse(projects, slug, session) {
|
|
8
|
+
if (!projects || !session || typeof slug !== "string" || slug.indexOf("mate-") !== 0) return false;
|
|
9
|
+
var reference = sessionReference(session);
|
|
10
|
+
var matched = false;
|
|
11
|
+
projects.forEach(function (ctx) {
|
|
12
|
+
if (matched || !ctx || typeof ctx.forEachClient !== "function") return;
|
|
13
|
+
ctx.forEachClient(function (ws) {
|
|
14
|
+
if (matched || !ws || !ws._homeChatPresented) return;
|
|
15
|
+
if (session.ownerId && (!ws._clayUser || ws._clayUser.id !== session.ownerId)) return;
|
|
16
|
+
var tap = ws._homeChatTap;
|
|
17
|
+
if (!tap || tap.mateSlug !== slug) return;
|
|
18
|
+
var localMatch = tap.sessionId === session.localId;
|
|
19
|
+
var referenceMatch = !!reference && tap.sessionReference === reference;
|
|
20
|
+
if (localMatch && referenceMatch) matched = true;
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
return matched;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
module.exports = { shouldSuppressHomeResponse: shouldSuppressHomeResponse };
|
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
// Import-keyed projection over the Clay-wide knowledge record backend.
|
|
2
|
+
//
|
|
3
|
+
// A migration and a live write-through bridge both copy legacy content in
|
|
4
|
+
// repeatedly: on every restart, after a partial run, after every mutation, and
|
|
5
|
+
// potentially from two daemons at once. This layer makes that safe by
|
|
6
|
+
// addressing every imported record with a stable import key derived from the
|
|
7
|
+
// source, so re-importing the same source is a no-op rather than a duplicate.
|
|
8
|
+
//
|
|
9
|
+
// Change rule, applied by the caller through the key it chooses:
|
|
10
|
+
// - File-addressed sources use a key derived from the file name. Re-importing
|
|
11
|
+
// changed content revises the existing record, keeping the prior revision
|
|
12
|
+
// and its provenance.
|
|
13
|
+
// - Line-addressed sources (append-only journals) fold the line's content
|
|
14
|
+
// hash into the key. An edited line is therefore a new, deterministic
|
|
15
|
+
// import rather than a revision, which matches how those journals are
|
|
16
|
+
// actually written.
|
|
17
|
+
//
|
|
18
|
+
// Content is never truncated. A source larger than one record is stored as
|
|
19
|
+
// content-addressed chunks and reassembled exactly, with the whole-source hash
|
|
20
|
+
// verified on read. A caller that cannot read a source back completely is told
|
|
21
|
+
// so rather than handed a silently partial string.
|
|
22
|
+
|
|
23
|
+
var crypto = require("crypto");
|
|
24
|
+
var recordStore = require("./knowledge-record-store");
|
|
25
|
+
|
|
26
|
+
var IMPORT_VERSION = 2;
|
|
27
|
+
// Worst-case JSON/UTF-8 expansion of an 8000 character slice stays well inside
|
|
28
|
+
// the record store's 64KB single-record limit.
|
|
29
|
+
var CHUNK_CHARS = 8000;
|
|
30
|
+
var MAX_CONTENT_CHARS = CHUNK_CHARS;
|
|
31
|
+
var MAX_NAME_CHARS = 200;
|
|
32
|
+
var MAX_CHUNKS = 8192;
|
|
33
|
+
var NUL = String.fromCharCode(0);
|
|
34
|
+
var CONTROL_PATTERN = new RegExp("[\\u0000-\\u001f\\u007f]+", "g");
|
|
35
|
+
|
|
36
|
+
function contentHash(value) {
|
|
37
|
+
return crypto.createHash("sha256").update(String(value == null ? "" : value), "utf8").digest("hex").substring(0, 32);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function cleanLine(value, max) {
|
|
41
|
+
if (typeof value !== "string") return "";
|
|
42
|
+
var text = value.replace(CONTROL_PATTERN, " ").replace(/\s+/g, " ").trim();
|
|
43
|
+
return text.length > max ? text.substring(0, max) : text;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Content is stored verbatim. Every character survives a JSONL round trip,
|
|
47
|
+
// including NUL, which JSON.stringify escapes as a six-character sequence and JSON.parse restores
|
|
48
|
+
// exactly; it is not a newline, so it cannot split a record either. Removing
|
|
49
|
+
// any character here would break reassembly against the source hash.
|
|
50
|
+
function exactContent(value) {
|
|
51
|
+
return typeof value === "string" ? value : "";
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function splitChunks(content) {
|
|
55
|
+
var chunks = [];
|
|
56
|
+
for (var offset = 0; offset < content.length; offset += CHUNK_CHARS) {
|
|
57
|
+
chunks.push(content.substring(offset, offset + CHUNK_CHARS));
|
|
58
|
+
}
|
|
59
|
+
return chunks;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function chunkKey(importKey, position) {
|
|
63
|
+
return importKey + "#c" + position;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function createKnowledgeImporter(opts) {
|
|
67
|
+
var options = opts || {};
|
|
68
|
+
var scopeId = options.scopeId;
|
|
69
|
+
var store = recordStore.createRecordStore({ scopeId: scopeId, baseDir: options.baseDir });
|
|
70
|
+
|
|
71
|
+
function refFor(rootId) {
|
|
72
|
+
var digest = crypto.createHash("sha256").update(scopeId + NUL + String(rootId)).digest("base64url");
|
|
73
|
+
return "know:" + digest.substring(0, 24);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Incrementally maintained so importing N sources stays O(N) rather than
|
|
77
|
+
// re-folding the whole scope once per source. The bridge runs on every
|
|
78
|
+
// mutation and the migration on every startup, so this must stay cheap.
|
|
79
|
+
var indexed = null;
|
|
80
|
+
|
|
81
|
+
function resetIndex() {
|
|
82
|
+
indexed = { byKey: new Map(), byRoot: new Map(), duplicates: 0, consumed: 0 };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function foldRecord(record) {
|
|
86
|
+
if (record.op === "create" && record.importKey) {
|
|
87
|
+
// First create for an import key wins. A duplicate that slipped past the
|
|
88
|
+
// run lock is folded away rather than double-counted, so the projection
|
|
89
|
+
// stays correct even if the lock ever fails.
|
|
90
|
+
if (indexed.byKey.has(record.importKey)) {
|
|
91
|
+
indexed.duplicates++;
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
var state = {
|
|
95
|
+
importKey: record.importKey,
|
|
96
|
+
rootId: record.id,
|
|
97
|
+
ref: refFor(record.id),
|
|
98
|
+
sourceHash: record.sourceHash || null,
|
|
99
|
+
contentHash: record.contentHash || record.sourceHash || null,
|
|
100
|
+
kind: record.kind || null,
|
|
101
|
+
name: record.name || null,
|
|
102
|
+
chunked: record.chunked === true,
|
|
103
|
+
chunkCount: record.chunkCount || 0,
|
|
104
|
+
deleted: false,
|
|
105
|
+
revisions: 1,
|
|
106
|
+
importedAt: record.at || 0,
|
|
107
|
+
updatedAt: record.at || 0,
|
|
108
|
+
};
|
|
109
|
+
indexed.byKey.set(record.importKey, state);
|
|
110
|
+
indexed.byRoot.set(record.id, state);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (!record.rootId) return;
|
|
114
|
+
var target = indexed.byRoot.get(record.rootId);
|
|
115
|
+
if (!target) return;
|
|
116
|
+
if (record.op === "update") {
|
|
117
|
+
if (record.sourceHash) target.sourceHash = record.sourceHash;
|
|
118
|
+
if (record.contentHash) target.contentHash = record.contentHash;
|
|
119
|
+
if (record.kind) target.kind = record.kind;
|
|
120
|
+
if (record.name) target.name = record.name;
|
|
121
|
+
target.chunked = record.chunked === true;
|
|
122
|
+
target.chunkCount = record.chunkCount || 0;
|
|
123
|
+
// An update always asserts that this content is current, so it revives a
|
|
124
|
+
// record whose source came back after a tombstone.
|
|
125
|
+
target.deleted = false;
|
|
126
|
+
} else if (record.op === "delete") {
|
|
127
|
+
target.deleted = true;
|
|
128
|
+
} else {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
target.revisions++;
|
|
132
|
+
target.updatedAt = record.at || target.updatedAt;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function index() {
|
|
136
|
+
var records = store.all();
|
|
137
|
+
if (!indexed || records.length < indexed.consumed) resetIndex();
|
|
138
|
+
for (var i = indexed.consumed; i < records.length; i++) foldRecord(records[i]);
|
|
139
|
+
indexed.consumed = records.length;
|
|
140
|
+
return { byKey: indexed.byKey, duplicates: indexed.duplicates };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Folded content for every live import key. Used only for reassembling a
|
|
144
|
+
// chunked source, which is rare, so it is computed on demand.
|
|
145
|
+
function foldContents() {
|
|
146
|
+
var records = store.all();
|
|
147
|
+
var byRoot = new Map();
|
|
148
|
+
var byKey = new Map();
|
|
149
|
+
for (var i = 0; i < records.length; i++) {
|
|
150
|
+
var record = records[i];
|
|
151
|
+
if (record.op === "create" && record.importKey) {
|
|
152
|
+
if (byKey.has(record.importKey)) continue;
|
|
153
|
+
var state = { content: record.content || "", deleted: false };
|
|
154
|
+
byKey.set(record.importKey, state);
|
|
155
|
+
byRoot.set(record.id, state);
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (!record.rootId) continue;
|
|
159
|
+
var target = byRoot.get(record.rootId);
|
|
160
|
+
if (!target) continue;
|
|
161
|
+
if (record.op === "update") {
|
|
162
|
+
if (typeof record.content === "string") target.content = record.content;
|
|
163
|
+
target.deleted = false;
|
|
164
|
+
} else if (record.op === "delete") {
|
|
165
|
+
target.deleted = true;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return byKey;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Exact reassembly. Returns complete:false rather than a partial string when
|
|
172
|
+
// a chunk is missing, tombstoned, or the whole-source hash does not verify.
|
|
173
|
+
function readContent(entry) {
|
|
174
|
+
if (!entry) return { complete: false, content: "", reason: "not-found" };
|
|
175
|
+
if (!entry.chunked) {
|
|
176
|
+
// Verified on the inline path too, so "complete" means the same thing
|
|
177
|
+
// whether or not a source happened to need chunking.
|
|
178
|
+
var inline = entry.content || "";
|
|
179
|
+
if (entry.contentHash && contentHash(inline) !== entry.contentHash) {
|
|
180
|
+
return { complete: false, content: "", reason: "hash-mismatch" };
|
|
181
|
+
}
|
|
182
|
+
return { complete: true, content: inline };
|
|
183
|
+
}
|
|
184
|
+
var contents = foldContents();
|
|
185
|
+
var parts = [];
|
|
186
|
+
for (var i = 0; i < entry.chunkCount; i++) {
|
|
187
|
+
var part = contents.get(chunkKey(entry.importKey, i));
|
|
188
|
+
if (!part || part.deleted) return { complete: false, content: "", reason: "missing-chunk", chunkIndex: i };
|
|
189
|
+
parts.push(part.content);
|
|
190
|
+
}
|
|
191
|
+
var joined = parts.join("");
|
|
192
|
+
if (entry.contentHash && contentHash(joined) !== entry.contentHash) {
|
|
193
|
+
return { complete: false, content: "", reason: "hash-mismatch" };
|
|
194
|
+
}
|
|
195
|
+
return { complete: true, content: joined };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function append(payload) {
|
|
199
|
+
return store.append(payload);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Chunks are written before the parent record. A failure partway therefore
|
|
203
|
+
// leaves orphan chunks and no parent, so the source is not reported as
|
|
204
|
+
// imported and the next run retries it. Chunks are content-addressed, so the
|
|
205
|
+
// retry re-uses whatever already landed.
|
|
206
|
+
function writeChunks(importKey, chunks, entry, at) {
|
|
207
|
+
var existingIndex = index().byKey;
|
|
208
|
+
for (var i = 0; i < chunks.length; i++) {
|
|
209
|
+
var key = chunkKey(importKey, i);
|
|
210
|
+
var hash = contentHash(chunks[i]);
|
|
211
|
+
var current = existingIndex.get(key);
|
|
212
|
+
if (current && !current.deleted && current.sourceHash === hash) continue;
|
|
213
|
+
var base = {
|
|
214
|
+
op: current ? "update" : "create",
|
|
215
|
+
scope: scopeId,
|
|
216
|
+
importKey: key,
|
|
217
|
+
sourceHash: hash,
|
|
218
|
+
contentHash: hash,
|
|
219
|
+
kind: "chunk",
|
|
220
|
+
name: entry.name || "",
|
|
221
|
+
chunkOf: importKey,
|
|
222
|
+
chunkIndex: i,
|
|
223
|
+
content: chunks[i],
|
|
224
|
+
importVersion: IMPORT_VERSION,
|
|
225
|
+
at: at,
|
|
226
|
+
};
|
|
227
|
+
if (current) {
|
|
228
|
+
base.rootId = current.rootId;
|
|
229
|
+
} else {
|
|
230
|
+
var id = recordStore.newRecordId();
|
|
231
|
+
base.id = id;
|
|
232
|
+
base.rootId = id;
|
|
233
|
+
}
|
|
234
|
+
append(base);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Import one source unit. Returns what actually happened so a migration or a
|
|
239
|
+
// bridge can report truthfully instead of assuming.
|
|
240
|
+
function importRecord(input) {
|
|
241
|
+
var entry = input || {};
|
|
242
|
+
if (!entry.importKey || typeof entry.importKey !== "string") throw new Error("An import key is required.");
|
|
243
|
+
var content = exactContent(entry.content);
|
|
244
|
+
// Two distinct hashes, deliberately.
|
|
245
|
+
// sourceHash - the caller's fingerprint of the legacy source, used for
|
|
246
|
+
// change detection and for line-addressed import keys.
|
|
247
|
+
// storedHash - always derived here from the exact bytes being written,
|
|
248
|
+
// so reassembly verifies against what was actually stored
|
|
249
|
+
// rather than against something the caller asserted.
|
|
250
|
+
var storedHash = contentHash(content);
|
|
251
|
+
var hash = entry.sourceHash || storedHash;
|
|
252
|
+
var existing = index().byKey.get(entry.importKey) || null;
|
|
253
|
+
|
|
254
|
+
if (existing && !existing.deleted && existing.sourceHash === hash) {
|
|
255
|
+
return { action: "unchanged", ref: existing.ref, importKey: entry.importKey };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
var at = Number.isFinite(entry.at) ? entry.at : Date.now();
|
|
259
|
+
var chunks = content.length > MAX_CONTENT_CHARS ? splitChunks(content) : null;
|
|
260
|
+
if (chunks) {
|
|
261
|
+
if (chunks.length > MAX_CHUNKS) {
|
|
262
|
+
throw new Error("Source exceeds the maximum representable size: " + entry.importKey);
|
|
263
|
+
}
|
|
264
|
+
writeChunks(entry.importKey, chunks, entry, at);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
var payload = {
|
|
268
|
+
op: existing ? "update" : "create",
|
|
269
|
+
scope: scopeId,
|
|
270
|
+
importKey: entry.importKey,
|
|
271
|
+
sourceHash: hash,
|
|
272
|
+
contentHash: storedHash,
|
|
273
|
+
kind: entry.kind || "knowledge",
|
|
274
|
+
name: cleanLine(entry.name || (existing && existing.name) || "", MAX_NAME_CHARS),
|
|
275
|
+
chunked: !!chunks,
|
|
276
|
+
chunkCount: chunks ? chunks.length : 0,
|
|
277
|
+
content: chunks ? "" : content,
|
|
278
|
+
source: entry.source || null,
|
|
279
|
+
actor: entry.actor || null,
|
|
280
|
+
importVersion: IMPORT_VERSION,
|
|
281
|
+
at: at,
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
if (existing) {
|
|
285
|
+
payload.rootId = existing.rootId;
|
|
286
|
+
append(payload);
|
|
287
|
+
return { action: existing.deleted ? "revived" : "revised", ref: existing.ref, importKey: entry.importKey };
|
|
288
|
+
}
|
|
289
|
+
var newId = recordStore.newRecordId();
|
|
290
|
+
payload.id = newId;
|
|
291
|
+
payload.rootId = newId;
|
|
292
|
+
append(payload);
|
|
293
|
+
return { action: "created", ref: refFor(newId), importKey: entry.importKey };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Logical removal. The record and its history stay; the projection stops
|
|
297
|
+
// returning it, so a memory deleted in the legacy store is not left active
|
|
298
|
+
// in the new backend.
|
|
299
|
+
function removeRecord(importKey, options2) {
|
|
300
|
+
var settings = options2 || {};
|
|
301
|
+
var existing = index().byKey.get(importKey) || null;
|
|
302
|
+
if (!existing) return { action: "absent", importKey: importKey };
|
|
303
|
+
if (existing.deleted) return { action: "already-deleted", ref: existing.ref, importKey: importKey };
|
|
304
|
+
append({
|
|
305
|
+
rootId: existing.rootId,
|
|
306
|
+
op: "delete",
|
|
307
|
+
scope: scopeId,
|
|
308
|
+
importKey: importKey,
|
|
309
|
+
kind: existing.kind,
|
|
310
|
+
name: existing.name,
|
|
311
|
+
source: settings.source || null,
|
|
312
|
+
actor: settings.actor || null,
|
|
313
|
+
importVersion: IMPORT_VERSION,
|
|
314
|
+
at: Number.isFinite(settings.at) ? settings.at : Date.now(),
|
|
315
|
+
});
|
|
316
|
+
return { action: "deleted", ref: existing.ref, importKey: importKey };
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Read projection. Chunk records are internal and never surface here; use
|
|
320
|
+
// readContent to reassemble a chunked entry.
|
|
321
|
+
function entries(options2) {
|
|
322
|
+
var settings = options2 || {};
|
|
323
|
+
var records = store.all();
|
|
324
|
+
var byRoot = new Map();
|
|
325
|
+
var byKey = new Map();
|
|
326
|
+
var order = [];
|
|
327
|
+
var i;
|
|
328
|
+
for (i = 0; i < records.length; i++) {
|
|
329
|
+
var record = records[i];
|
|
330
|
+
if (record.op !== "create" || !record.importKey) continue;
|
|
331
|
+
if (byKey.has(record.importKey)) continue;
|
|
332
|
+
var entry = {
|
|
333
|
+
ref: refFor(record.id),
|
|
334
|
+
importKey: record.importKey,
|
|
335
|
+
kind: record.kind || null,
|
|
336
|
+
name: record.name || null,
|
|
337
|
+
content: record.content || "",
|
|
338
|
+
chunked: record.chunked === true,
|
|
339
|
+
chunkCount: record.chunkCount || 0,
|
|
340
|
+
contentHash: record.contentHash || record.sourceHash || null,
|
|
341
|
+
source: record.source || null,
|
|
342
|
+
actor: record.actor || null,
|
|
343
|
+
deleted: false,
|
|
344
|
+
deletedAt: null,
|
|
345
|
+
deletedBy: null,
|
|
346
|
+
importedAt: record.at || 0,
|
|
347
|
+
updatedAt: record.at || 0,
|
|
348
|
+
revisions: 1,
|
|
349
|
+
};
|
|
350
|
+
byKey.set(record.importKey, entry);
|
|
351
|
+
byRoot.set(record.id, entry);
|
|
352
|
+
order.push(entry);
|
|
353
|
+
}
|
|
354
|
+
for (i = 0; i < records.length; i++) {
|
|
355
|
+
var revision = records[i];
|
|
356
|
+
if (revision.op === "create" || !revision.rootId) continue;
|
|
357
|
+
var target = byRoot.get(revision.rootId);
|
|
358
|
+
if (!target) continue;
|
|
359
|
+
if (revision.op === "delete") {
|
|
360
|
+
// Original authorship is preserved on `actor`; who removed it is
|
|
361
|
+
// recorded separately so a tombstone is traceable too.
|
|
362
|
+
target.deleted = true;
|
|
363
|
+
target.deletedAt = revision.at || 0;
|
|
364
|
+
target.deletedBy = revision.actor || null;
|
|
365
|
+
} else if (revision.op === "update") {
|
|
366
|
+
if (typeof revision.content === "string") target.content = revision.content;
|
|
367
|
+
if (revision.name) target.name = revision.name;
|
|
368
|
+
if (revision.kind) target.kind = revision.kind;
|
|
369
|
+
if (revision.source) target.source = revision.source;
|
|
370
|
+
if (revision.actor) target.actor = revision.actor;
|
|
371
|
+
if (revision.contentHash) target.contentHash = revision.contentHash;
|
|
372
|
+
target.chunked = revision.chunked === true;
|
|
373
|
+
target.chunkCount = revision.chunkCount || 0;
|
|
374
|
+
target.deleted = false;
|
|
375
|
+
target.deletedAt = null;
|
|
376
|
+
target.deletedBy = null;
|
|
377
|
+
} else {
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
target.updatedAt = revision.at || target.updatedAt;
|
|
381
|
+
target.revisions++;
|
|
382
|
+
}
|
|
383
|
+
var out = [];
|
|
384
|
+
for (i = 0; i < order.length; i++) {
|
|
385
|
+
if (order[i].kind === "chunk" && settings.includeChunks !== true) continue;
|
|
386
|
+
if (order[i].deleted && settings.includeDeleted !== true) continue;
|
|
387
|
+
out.push(order[i]);
|
|
388
|
+
}
|
|
389
|
+
return out;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function stats() {
|
|
393
|
+
var indexedNow = index();
|
|
394
|
+
var base = store.stats();
|
|
395
|
+
var live = 0;
|
|
396
|
+
var deleted = 0;
|
|
397
|
+
var chunks = 0;
|
|
398
|
+
indexedNow.byKey.forEach(function (state) {
|
|
399
|
+
if (state.kind === "chunk") { chunks++; return; }
|
|
400
|
+
if (state.deleted) deleted++;
|
|
401
|
+
else live++;
|
|
402
|
+
});
|
|
403
|
+
return {
|
|
404
|
+
scopeId: scopeId,
|
|
405
|
+
filePath: base.filePath,
|
|
406
|
+
records: base.records,
|
|
407
|
+
skippedRecords: base.skipped,
|
|
408
|
+
imported: live,
|
|
409
|
+
deleted: deleted,
|
|
410
|
+
chunks: chunks,
|
|
411
|
+
duplicates: indexedNow.duplicates,
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return {
|
|
416
|
+
scopeId: scopeId,
|
|
417
|
+
filePath: store.filePath,
|
|
418
|
+
index: index,
|
|
419
|
+
importRecord: importRecord,
|
|
420
|
+
removeRecord: removeRecord,
|
|
421
|
+
readContent: readContent,
|
|
422
|
+
entries: entries,
|
|
423
|
+
stats: stats,
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
module.exports = {
|
|
428
|
+
IMPORT_VERSION: IMPORT_VERSION,
|
|
429
|
+
CHUNK_CHARS: CHUNK_CHARS,
|
|
430
|
+
MAX_CONTENT_CHARS: MAX_CONTENT_CHARS,
|
|
431
|
+
MAX_CHUNKS: MAX_CHUNKS,
|
|
432
|
+
contentHash: contentHash,
|
|
433
|
+
chunkKey: chunkKey,
|
|
434
|
+
splitChunks: splitChunks,
|
|
435
|
+
createKnowledgeImporter: createKnowledgeImporter,
|
|
436
|
+
};
|