negotium 0.2.17 → 0.2.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-helpers.js +41 -32
- package/dist/agent-helpers.js.map +9 -9
- package/dist/{chunk-5cpdbbeh.js → chunk-fc58cqry.js} +3 -1
- package/dist/{chunk-5cpdbbeh.js.map → chunk-fc58cqry.js.map} +3 -3
- package/dist/hosted-agent.js +4 -2
- package/dist/hosted-agent.js.map +4 -4
- package/dist/main.js +662 -93
- package/dist/main.js.map +11 -11
- package/dist/mcp-factories.js +659 -91
- package/dist/mcp-factories.js.map +10 -10
- package/dist/prompts.js +8 -8
- package/dist/prompts.js.map +3 -3
- package/dist/registry.js +3 -3
- package/dist/registry.js.map +2 -2
- package/dist/rollout.js +1 -1
- package/dist/runtime/src/agents/idle-archiver.ts +3 -2
- package/dist/runtime/src/mcp/canonical-proxy-server.ts +18 -1
- package/dist/runtime/src/mcp/wiki-server.ts +801 -59
- package/dist/runtime/src/platform/mcp-config.ts +1 -0
- package/dist/runtime/src/prompts/agents/wiki-archiver.md +16 -6
- package/dist/runtime/src/prompts/builders.ts +17 -34
- package/dist/runtime/src/runtime/turn-runner.ts +14 -17
- package/dist/runtime/src/storage/api-topics.ts +13 -4
- package/dist/runtime/src/topics/lifecycle.ts +3 -2
- package/dist/runtime/src/types/api.ts +6 -0
- package/dist/runtime/src/version.ts +1 -1
- package/dist/storage.js +12 -6
- package/dist/storage.js.map +3 -3
- package/dist/types/packages/core/src/mcp/wiki-server.d.ts +4 -0
- package/dist/types/packages/core/src/prompts/builders.d.ts +2 -4
- package/dist/types/packages/core/src/storage/api-topics.d.ts +1 -0
- package/dist/types/packages/core/src/types/api.d.ts +6 -0
- package/dist/types/packages/core/src/version.d.ts +1 -1
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -1636,7 +1636,7 @@ var exports_version = {};
|
|
|
1636
1636
|
__export(exports_version, {
|
|
1637
1637
|
NEGOTIUM_VERSION: () => NEGOTIUM_VERSION
|
|
1638
1638
|
});
|
|
1639
|
-
var NEGOTIUM_VERSION = "0.2.
|
|
1639
|
+
var NEGOTIUM_VERSION = "0.2.18";
|
|
1640
1640
|
|
|
1641
1641
|
// ../../packages/core/src/agents/codex-native-multi-agent.ts
|
|
1642
1642
|
import { spawn } from "child_process";
|
|
@@ -2943,6 +2943,8 @@ var init_mcp_config = __esm(() => {
|
|
|
2943
2943
|
return env ? buildStdioMcpServer(agent, CANONICAL_MCP_PROXY_SERVER, ["--surface=wiki"], env) : null;
|
|
2944
2944
|
}
|
|
2945
2945
|
const args = [`--user-id=${userId}`];
|
|
2946
|
+
if (topicId)
|
|
2947
|
+
args.push(`--current-topic-id=${topicId}`);
|
|
2946
2948
|
const resolvedWikiTopicId = wikiTopicId ?? topicId ?? (session !== "dm" ? session : undefined);
|
|
2947
2949
|
if (resolvedWikiTopicId)
|
|
2948
2950
|
args.push(`--topic-id=${resolvedWikiTopicId}`);
|
|
@@ -7632,6 +7634,7 @@ function initializeApiTopicsSchema() {
|
|
|
7632
7634
|
last_message_at TEXT,
|
|
7633
7635
|
parent_topic_id TEXT,
|
|
7634
7636
|
memory_topic_id TEXT,
|
|
7637
|
+
memory_key TEXT,
|
|
7635
7638
|
is_fork INTEGER NOT NULL DEFAULT 0 CHECK (is_fork IN (0,1)),
|
|
7636
7639
|
is_subagent INTEGER NOT NULL DEFAULT 0 CHECK (is_subagent IN (0,1)),
|
|
7637
7640
|
visibility TEXT NOT NULL DEFAULT 'visible' CHECK (visibility IN ('visible','hidden')),
|
|
@@ -7681,8 +7684,8 @@ function initializeApiTopicsSchema() {
|
|
|
7681
7684
|
const legacyBaseEffort = row.base_effort ?? row.default_effort;
|
|
7682
7685
|
db.query(`INSERT INTO api_topics_next
|
|
7683
7686
|
(id,title,kind,description,agent,base_model,base_effort,response_policy,
|
|
7684
|
-
created_at,last_message_at,parent_topic_id,memory_topic_id,is_fork,is_subagent,visibility,access_mode,session_id)
|
|
7685
|
-
VALUES (
|
|
7687
|
+
created_at,last_message_at,parent_topic_id,memory_topic_id,memory_key,is_fork,is_subagent,visibility,access_mode,session_id)
|
|
7688
|
+
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`).run(String(row.id), String(row.title), normalized.kind, typeof row.description === "string" ? row.description : null, normalized.agent ?? null, typeof legacyBaseModel === "string" ? legacyBaseModel : null, typeof legacyBaseEffort === "string" ? legacyBaseEffort : null, normalized.aiMode, String(row.created_at), typeof row.last_message_at === "string" ? row.last_message_at : null, typeof row.parent_topic_id === "string" ? row.parent_topic_id : null, typeof row.memory_topic_id === "string" ? row.memory_topic_id : null, typeof row.memory_key === "string" ? row.memory_key : null, Number(row.is_fork ?? 0) !== 0 ? 1 : 0, Number(row.is_subagent ?? 0) !== 0 ? 1 : 0, row.visibility === "hidden" ? "hidden" : "visible", row.access_mode === "shared" ? "shared" : "private", typeof row.session_id === "string" ? row.session_id : null);
|
|
7686
7689
|
}
|
|
7687
7690
|
db.exec("DROP TABLE IF EXISTS topic_members");
|
|
7688
7691
|
db.exec("DROP TABLE api_topics");
|
|
@@ -7751,6 +7754,9 @@ function initializeApiTopicsSchema() {
|
|
|
7751
7754
|
if (!tableColumns2("api_topics").has("memory_topic_id")) {
|
|
7752
7755
|
db.exec("ALTER TABLE api_topics ADD COLUMN memory_topic_id TEXT");
|
|
7753
7756
|
}
|
|
7757
|
+
if (!tableColumns2("api_topics").has("memory_key")) {
|
|
7758
|
+
db.exec("ALTER TABLE api_topics ADD COLUMN memory_key TEXT");
|
|
7759
|
+
}
|
|
7754
7760
|
db.exec(`
|
|
7755
7761
|
UPDATE api_topics
|
|
7756
7762
|
SET browser_profile_owner = (
|
|
@@ -7811,6 +7817,7 @@ function rowToDto2(r, participants = getTopicParticipants(r.id), tellTargets) {
|
|
|
7811
7817
|
lastMessageAt: r.last_message_at ?? new Date().toISOString(),
|
|
7812
7818
|
parentTopicId: r.parent_topic_id ?? undefined,
|
|
7813
7819
|
memoryTopicId: r.memory_topic_id ?? undefined,
|
|
7820
|
+
memoryKey: r.memory_key ?? undefined,
|
|
7814
7821
|
isFork: r.is_fork !== 0,
|
|
7815
7822
|
...r.is_subagent !== 0 ? { isSubagent: true } : {},
|
|
7816
7823
|
...r.is_subagent !== 0 ? {
|
|
@@ -7905,9 +7912,9 @@ function upsertTopic(t) {
|
|
|
7905
7912
|
db.transaction(() => {
|
|
7906
7913
|
db.query(`INSERT INTO api_topics
|
|
7907
7914
|
(id,title,kind,description,agent,base_model,base_effort,response_policy,
|
|
7908
|
-
created_at,last_message_at,parent_topic_id,memory_topic_id,is_fork,is_subagent,visibility,access_mode,
|
|
7915
|
+
created_at,last_message_at,parent_topic_id,memory_topic_id,memory_key,is_fork,is_subagent,visibility,access_mode,
|
|
7909
7916
|
subagent_report_mode)
|
|
7910
|
-
VALUES (
|
|
7917
|
+
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
|
7911
7918
|
ON CONFLICT(id) DO UPDATE SET
|
|
7912
7919
|
title = excluded.title,
|
|
7913
7920
|
kind = excluded.kind,
|
|
@@ -7920,11 +7927,12 @@ function upsertTopic(t) {
|
|
|
7920
7927
|
last_message_at = excluded.last_message_at,
|
|
7921
7928
|
parent_topic_id = excluded.parent_topic_id,
|
|
7922
7929
|
memory_topic_id = excluded.memory_topic_id,
|
|
7930
|
+
memory_key = excluded.memory_key,
|
|
7923
7931
|
is_fork = excluded.is_fork,
|
|
7924
7932
|
is_subagent = excluded.is_subagent,
|
|
7925
7933
|
visibility = excluded.visibility,
|
|
7926
7934
|
access_mode = excluded.access_mode,
|
|
7927
|
-
subagent_report_mode = excluded.subagent_report_mode`).run(t.id, t.title, normalized.kind, t.description ?? null, normalized.agent ?? null, t.defaultModel ?? null, t.defaultEffort ?? null, normalized.aiMode, t.createdAt, t.lastMessageAt ?? null, t.parentTopicId ?? null, t.memoryTopicId ?? null, t.isFork ? 1 : 0, t.isSubagent ? 1 : 0, normalizeTopicVisibility(t.visibility), normalizeTopicAccessMode(t.accessMode), t.subagentReportMode ?? "auto");
|
|
7935
|
+
subagent_report_mode = excluded.subagent_report_mode`).run(t.id, t.title, normalized.kind, t.description ?? null, normalized.agent ?? null, t.defaultModel ?? null, t.defaultEffort ?? null, normalized.aiMode, t.createdAt, t.lastMessageAt ?? null, t.parentTopicId ?? null, t.memoryTopicId ?? null, t.memoryKey ?? null, t.isFork ? 1 : 0, t.isSubagent ? 1 : 0, normalizeTopicVisibility(t.visibility), normalizeTopicAccessMode(t.accessMode), t.subagentReportMode ?? "auto");
|
|
7928
7936
|
db.query("DELETE FROM topic_members WHERE topic_id = ?").run(t.id);
|
|
7929
7937
|
for (const participant of t.participants) {
|
|
7930
7938
|
db.query("INSERT INTO topic_members (topic_id,user_id,role) VALUES (?,?,?)").run(t.id, participant.userId, participant.role);
|
|
@@ -11708,14 +11716,14 @@ function buildMemoryPromptSection(opts) {
|
|
|
11708
11716
|
const parts = [`
|
|
11709
11717
|
|
|
11710
11718
|
## Memory`];
|
|
11711
|
-
|
|
11712
|
-
|
|
11713
|
-
if (opts.
|
|
11714
|
-
parts.push(`
|
|
11715
|
-
}
|
|
11716
|
-
|
|
11717
|
-
|
|
11718
|
-
parts.push("
|
|
11719
|
+
if (opts.isManager) {
|
|
11720
|
+
parts.push("Use `wiki_query` for past workspace decisions or cross-topic context, then `wiki_read` only for the relevant result.");
|
|
11721
|
+
} else if (opts.memoryKey) {
|
|
11722
|
+
parts.push(`This topic uses the canonical memory key \`${opts.memoryKey}\`. At the start of a new session, read it with \`wiki_read(kind: "topic", key: "${opts.memoryKey}")\`.`);
|
|
11723
|
+
} else {
|
|
11724
|
+
parts.push(`At the start of a new session, search topic memories with \`wiki_query(question: "${opts.topicTitle}", kind: "topic", limit: 5)\` and read the most relevant candidate with \`wiki_read\`.`, "If a candidate is clearly the same continuing topic/persona, read it with `adopt: true`; otherwise keep this topic's own name as its memory key. Do not merge weak or ambiguous matches.");
|
|
11725
|
+
}
|
|
11726
|
+
parts.push("Keep the lookup quiet. Mention prior context naturally in one short line only when it helps the user.", 'Use `wiki_query(kind: "article")` for reusable knowledge and `wiki_query(kind: "summary")` for historical session details.');
|
|
11719
11727
|
if (opts.hasArchive) {
|
|
11720
11728
|
parts.push("", "If you need the actual conversation from an earlier session, use the `wiki_last_conversation` MCP tool.");
|
|
11721
11729
|
}
|
|
@@ -12802,11 +12810,12 @@ function archiveActiveTopicForMemory(topicId, userId, options) {
|
|
|
12802
12810
|
}, "topic-memory-archiver: raw snapshot preserved below exchange threshold");
|
|
12803
12811
|
return "below-threshold";
|
|
12804
12812
|
}
|
|
12805
|
-
const memoryTopic = getTopicMemoryOrigin(topicId) ?? topic;
|
|
12813
|
+
const memoryTopic = topic.memoryKey ? topic : getTopicMemoryOrigin(topicId) ?? topic;
|
|
12814
|
+
const memoryTitle = memoryTopic.memoryKey?.trim() || memoryTopic.title;
|
|
12806
12815
|
const launched = (options.launchArchiver ?? runArchiverTurn)({
|
|
12807
12816
|
userId,
|
|
12808
12817
|
topicId: memoryTopic.id,
|
|
12809
|
-
topicTitle:
|
|
12818
|
+
topicTitle: memoryTitle,
|
|
12810
12819
|
archivePath: job.archivePath,
|
|
12811
12820
|
...options.rawArchivePaths ? { rawArchivePaths: options.rawArchivePaths } : {},
|
|
12812
12821
|
messageCount: job.messageCount,
|
|
@@ -16182,11 +16191,12 @@ async function deleteTopicCascadeImpl(topic, userId, options, deletingAncestorId
|
|
|
16182
16191
|
}
|
|
16183
16192
|
if (archived && meetsMemoryArchiveExchangeThreshold(archived.exchangeCount, MIN_MEMORY_ARCHIVE_EXCHANGES, topic)) {
|
|
16184
16193
|
try {
|
|
16185
|
-
const memoryTopic = getTopicMemoryOrigin(topicId) ?? topic;
|
|
16194
|
+
const memoryTopic = topic.memoryKey ? topic : getTopicMemoryOrigin(topicId) ?? topic;
|
|
16195
|
+
const memoryTitle = memoryTopic.memoryKey?.trim() || memoryTopic.title;
|
|
16186
16196
|
runArchiverTurn({
|
|
16187
16197
|
userId,
|
|
16188
16198
|
...memoryTopic.id !== topicId && !deletingTopicIds.has(memoryTopic.id) ? { topicId: memoryTopic.id } : {},
|
|
16189
|
-
topicTitle:
|
|
16199
|
+
topicTitle: memoryTitle,
|
|
16190
16200
|
archivePath: archived.path,
|
|
16191
16201
|
...rawArchives.length > 0 ? { rawArchivePaths: rawArchives } : {},
|
|
16192
16202
|
messageCount: archived.messageCount
|
|
@@ -18780,25 +18790,24 @@ function startAiTurn(params) {
|
|
|
18780
18790
|
const isManager = topicRecord?.kind === "manager";
|
|
18781
18791
|
const isMentionOnlyChannel = topic.aiMode === "mention" && !isManager;
|
|
18782
18792
|
let systemPrompt = isManager ? buildManagerSystemPrompt(systemPromptOpts) : isMentionOnlyChannel ? buildChannelSystemPrompt(systemPromptOpts) : buildTopicSystemPrompt(systemPromptOpts);
|
|
18783
|
-
const memoryTopic = isMentionOnlyChannel || isManager ? topic : getTopicMemoryOrigin(topicId) ?? topic;
|
|
18793
|
+
const memoryTopic = isMentionOnlyChannel || isManager || topic.memoryKey ? topic : getTopicMemoryOrigin(topicId) ?? topic;
|
|
18794
|
+
const memoryTitle = memoryTopic.memoryKey?.trim() || memoryTopic.title;
|
|
18784
18795
|
if (!isMentionOnlyChannel) {
|
|
18796
|
+
let hasMemoryArchive = false;
|
|
18785
18797
|
try {
|
|
18786
|
-
const resolvedBrief = resolveTopicBrief(memoryTopic.id,
|
|
18798
|
+
const resolvedBrief = resolveTopicBrief(memoryTopic.id, memoryTitle);
|
|
18787
18799
|
const wikiDir = getSharedWikiDir();
|
|
18788
|
-
const {
|
|
18789
|
-
|
|
18790
|
-
systemPrompt += buildMemoryPromptSection({
|
|
18791
|
-
briefFile,
|
|
18792
|
-
wikiDir,
|
|
18793
|
-
hasFiles: hasBriefFile,
|
|
18794
|
-
latestSummaryFile,
|
|
18795
|
-
hasArchive: Boolean(resolvedBrief?.brief.latestSummaryMd || latestSummaryFile),
|
|
18796
|
-
isManager
|
|
18797
|
-
});
|
|
18798
|
-
}
|
|
18800
|
+
const { latestSummaryFile } = resolveWikiMemoryMirror(wikiDir, memoryTopic.id, memoryTitle);
|
|
18801
|
+
hasMemoryArchive = Boolean(resolvedBrief?.brief.latestSummaryMd || latestSummaryFile);
|
|
18799
18802
|
} catch (err2) {
|
|
18800
18803
|
logger.warn({ topicId, err: err2 }, "ai: failed to inject topic brief");
|
|
18801
18804
|
}
|
|
18805
|
+
systemPrompt += buildMemoryPromptSection({
|
|
18806
|
+
topicTitle: topic.title,
|
|
18807
|
+
...memoryTopic.memoryKey ? { memoryKey: memoryTopic.memoryKey } : {},
|
|
18808
|
+
hasArchive: hasMemoryArchive,
|
|
18809
|
+
isManager
|
|
18810
|
+
});
|
|
18802
18811
|
}
|
|
18803
18812
|
const activeVisual = visualTools ? getActiveVisualForPrompt(topicId, userId) : null;
|
|
18804
18813
|
if (activeVisual) {
|
|
@@ -24931,7 +24940,7 @@ import {
|
|
|
24931
24940
|
unlinkSync as unlinkSync18,
|
|
24932
24941
|
writeFileSync as writeFileSync17
|
|
24933
24942
|
} from "fs";
|
|
24934
|
-
import { basename as basename7, dirname as dirname16, join as join36, resolve as resolve17 } from "path";
|
|
24943
|
+
import { basename as basename7, dirname as dirname16, join as join36, relative as relative2, resolve as resolve17 } from "path";
|
|
24935
24944
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
24936
24945
|
import { StdioServerTransport as StdioServerTransport2 } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
24937
24946
|
import {
|
|
@@ -25070,9 +25079,243 @@ function topicNameFrom(topicIdStr) {
|
|
|
25070
25079
|
function slugify(topic) {
|
|
25071
25080
|
return wikiSummarySlug(topic);
|
|
25072
25081
|
}
|
|
25082
|
+
function normalizeSearchText(value) {
|
|
25083
|
+
return value.toLowerCase().replaceAll(/[^a-z0-9\uAC00-\uD7A3]+/g, " ").trim().replaceAll(/\s+/g, " ");
|
|
25084
|
+
}
|
|
25085
|
+
function compactSearchText(value) {
|
|
25086
|
+
return normalizeSearchText(value).replaceAll(" ", "");
|
|
25087
|
+
}
|
|
25088
|
+
function trigrams(value) {
|
|
25089
|
+
const compact = compactSearchText(value);
|
|
25090
|
+
if (compact.length < 3)
|
|
25091
|
+
return new Set(compact ? [compact] : []);
|
|
25092
|
+
const result = new Set;
|
|
25093
|
+
for (let i = 0;i <= compact.length - 3; i += 1)
|
|
25094
|
+
result.add(compact.slice(i, i + 3));
|
|
25095
|
+
return result;
|
|
25096
|
+
}
|
|
25097
|
+
function fuzzySearchScore(query2, candidate, maximum) {
|
|
25098
|
+
const compactQuery = compactSearchText(query2);
|
|
25099
|
+
const compactCandidate = compactSearchText(candidate);
|
|
25100
|
+
if (compactQuery.length < 3 || compactCandidate.length < 3)
|
|
25101
|
+
return 0;
|
|
25102
|
+
const similarity = trigramSimilarity(compactQuery, compactCandidate);
|
|
25103
|
+
const minimumSimilarity = compactQuery.length <= 4 ? 0.6 : 0.45;
|
|
25104
|
+
return similarity >= minimumSimilarity ? similarity * maximum : 0;
|
|
25105
|
+
}
|
|
25106
|
+
function meaningfulSearchTokens(value) {
|
|
25107
|
+
return normalizeSearchText(value).split(" ").filter((token) => (token.length >= 2 || /^\d+$/.test(token)) && !SEARCH_STOP_WORDS.has(token));
|
|
25108
|
+
}
|
|
25109
|
+
function canonicalSearchToken(token) {
|
|
25110
|
+
return token.length > 4 && token.endsWith("s") ? token.slice(0, -1) : token;
|
|
25111
|
+
}
|
|
25112
|
+
function canonicalSearchTokens(value) {
|
|
25113
|
+
return meaningfulSearchTokens(value).map(canonicalSearchToken);
|
|
25114
|
+
}
|
|
25115
|
+
function countToken(tokens, token) {
|
|
25116
|
+
let count = 0;
|
|
25117
|
+
for (const candidate of tokens)
|
|
25118
|
+
if (candidate === token)
|
|
25119
|
+
count += 1;
|
|
25120
|
+
return count;
|
|
25121
|
+
}
|
|
25122
|
+
function countPhrase(text2, phrase) {
|
|
25123
|
+
if (!phrase)
|
|
25124
|
+
return 0;
|
|
25125
|
+
let count = 0;
|
|
25126
|
+
let offset = 0;
|
|
25127
|
+
let match = text2.indexOf(phrase, offset);
|
|
25128
|
+
while (match >= 0) {
|
|
25129
|
+
count += 1;
|
|
25130
|
+
offset = match + phrase.length;
|
|
25131
|
+
match = text2.indexOf(phrase, offset);
|
|
25132
|
+
}
|
|
25133
|
+
return count;
|
|
25134
|
+
}
|
|
25135
|
+
function summaryTopicIdentity(key) {
|
|
25136
|
+
return normalizeSearchText(key.replace(/^\d{4}-\d{2}-\d{2}-/, ""));
|
|
25137
|
+
}
|
|
25138
|
+
function scoreSummaryTime(query2, key, title) {
|
|
25139
|
+
const date = key.match(/^(\d{4})-(\d{2})-(\d{2})/);
|
|
25140
|
+
if (!date)
|
|
25141
|
+
return 0;
|
|
25142
|
+
const tokens = meaningfulSearchTokens(query2);
|
|
25143
|
+
const documentDate = `${date[1]}-${date[2]}-${date[3]}`;
|
|
25144
|
+
const requestedDateMatch = query2.match(/\b(\d{4}) (\d{2}) (\d{2})\b/);
|
|
25145
|
+
const requestedDate = requestedDateMatch ? `${requestedDateMatch[1]}-${requestedDateMatch[2]}-${requestedDateMatch[3]}` : undefined;
|
|
25146
|
+
const beforeDate = /\bbefore\b/.test(query2);
|
|
25147
|
+
const afterDate = /\bafter\b/.test(query2);
|
|
25148
|
+
if (requestedDate && beforeDate && documentDate >= requestedDate)
|
|
25149
|
+
return null;
|
|
25150
|
+
if (requestedDate && afterDate && documentDate <= requestedDate)
|
|
25151
|
+
return null;
|
|
25152
|
+
const queryYear = tokens.find((token) => /^\d{4}(?:\uB144)?$/.test(token))?.replace(/\uB144$/, "");
|
|
25153
|
+
const queryMonth = tokens.map((token) => SEARCH_MONTHS.get(token)).find(Boolean);
|
|
25154
|
+
const hasRecencyTerm = tokens.some((token) => SEARCH_RECENCY_TERMS.has(token));
|
|
25155
|
+
const yearMatches = !queryYear || queryYear === date[1];
|
|
25156
|
+
const monthMatches = !queryMonth || queryMonth === date[2];
|
|
25157
|
+
if (!queryYear && !queryMonth && !hasRecencyTerm || !yearMatches || !monthMatches)
|
|
25158
|
+
return 0;
|
|
25159
|
+
const normalizedMetadata = `${normalizeSearchText(key)} ${normalizeSearchText(title)}`;
|
|
25160
|
+
const contentTokens = tokens.filter((token) => !/^\d{4}(?:\uB144)?$/.test(token) && !SEARCH_MONTHS.has(token) && !SEARCH_RECENCY_TERMS.has(token));
|
|
25161
|
+
const contentMatches = contentTokens.filter((token) => normalizedMetadata.includes(token)).length;
|
|
25162
|
+
const relationScore = requestedDate && (beforeDate || afterDate) ? 60 : 0;
|
|
25163
|
+
const exactDateScore = requestedDate === documentDate ? 90 : 0;
|
|
25164
|
+
return 30 + relationScore + exactDateScore + contentMatches * 15;
|
|
25165
|
+
}
|
|
25166
|
+
function trigramSimilarity(left, right) {
|
|
25167
|
+
const a = trigrams(left);
|
|
25168
|
+
const b = trigrams(right);
|
|
25169
|
+
if (a.size === 0 || b.size === 0)
|
|
25170
|
+
return 0;
|
|
25171
|
+
let overlap = 0;
|
|
25172
|
+
for (const gram of a)
|
|
25173
|
+
if (b.has(gram))
|
|
25174
|
+
overlap += 1;
|
|
25175
|
+
return 2 * overlap / (a.size + b.size);
|
|
25176
|
+
}
|
|
25177
|
+
function parseWikiIndex(path) {
|
|
25178
|
+
let text2;
|
|
25179
|
+
try {
|
|
25180
|
+
text2 = readFileSync22(path, "utf-8");
|
|
25181
|
+
} catch {
|
|
25182
|
+
return [];
|
|
25183
|
+
}
|
|
25184
|
+
const candidates = [];
|
|
25185
|
+
const entryPattern = /^\s*-\s*\[\[(topic|articles|summaries)\/([^\]]+)\]\]\s*(.*)$/;
|
|
25186
|
+
for (const line of text2.split(`
|
|
25187
|
+
`)) {
|
|
25188
|
+
const match = line.match(entryPattern);
|
|
25189
|
+
if (!match)
|
|
25190
|
+
continue;
|
|
25191
|
+
const namespace = match[1];
|
|
25192
|
+
const key = match[2].replace(/\.md$/i, "");
|
|
25193
|
+
const tail = match[3].trim();
|
|
25194
|
+
const dateMatch = tail.match(/\((\d{4}-\d{2}-\d{2})(?:[^)]*)\)\s*$/);
|
|
25195
|
+
const description = dateMatch ? tail.slice(0, dateMatch.index).trim() : tail;
|
|
25196
|
+
candidates.push({
|
|
25197
|
+
kind: namespace === "topic" ? "topic" : namespace === "articles" ? "article" : "summary",
|
|
25198
|
+
key,
|
|
25199
|
+
description,
|
|
25200
|
+
...dateMatch ? { date: dateMatch[1] } : {}
|
|
25201
|
+
});
|
|
25202
|
+
}
|
|
25203
|
+
return candidates;
|
|
25204
|
+
}
|
|
25205
|
+
function indexCandidateExists(candidate) {
|
|
25206
|
+
if (candidate.kind === "topic")
|
|
25207
|
+
return true;
|
|
25208
|
+
const root = candidate.kind === "article" ? runtime().articlesDir : runtime().summariesDir;
|
|
25209
|
+
const filePath = resolve17(root, `${candidate.key}.md`);
|
|
25210
|
+
const relativePath = relative2(root, filePath);
|
|
25211
|
+
if (relativePath.startsWith("..") || relativePath.startsWith("/"))
|
|
25212
|
+
return false;
|
|
25213
|
+
try {
|
|
25214
|
+
return statSync15(filePath).isFile();
|
|
25215
|
+
} catch {
|
|
25216
|
+
return false;
|
|
25217
|
+
}
|
|
25218
|
+
}
|
|
25219
|
+
function scoreIndexKey(query2, key) {
|
|
25220
|
+
const normalizedQuery = normalizeSearchText(query2);
|
|
25221
|
+
const compactQuery = compactSearchText(normalizedQuery);
|
|
25222
|
+
const normalizedKey = normalizeSearchText(key);
|
|
25223
|
+
const compactKey = compactSearchText(normalizedKey);
|
|
25224
|
+
const tokens = meaningfulSearchTokens(normalizedQuery);
|
|
25225
|
+
let score = 0;
|
|
25226
|
+
if (normalizedKey === normalizedQuery)
|
|
25227
|
+
score += 120;
|
|
25228
|
+
else if (compactKey === compactQuery)
|
|
25229
|
+
score += 110;
|
|
25230
|
+
else {
|
|
25231
|
+
if (compactQuery.length >= 2 && (compactKey.startsWith(compactQuery) || compactQuery.startsWith(compactKey))) {
|
|
25232
|
+
score += 65;
|
|
25233
|
+
}
|
|
25234
|
+
if (tokens.length > 0 && compactQuery.length >= 3 && compactKey.includes(compactQuery)) {
|
|
25235
|
+
score += 45;
|
|
25236
|
+
}
|
|
25237
|
+
score += fuzzySearchScore(compactQuery, compactKey, 55);
|
|
25238
|
+
}
|
|
25239
|
+
for (const token of tokens) {
|
|
25240
|
+
if (normalizedKey.includes(token))
|
|
25241
|
+
score += 10;
|
|
25242
|
+
}
|
|
25243
|
+
return score;
|
|
25244
|
+
}
|
|
25245
|
+
function scoreIndexCandidate(query2, candidate) {
|
|
25246
|
+
const normalizedQuery = normalizeSearchText(query2);
|
|
25247
|
+
if (!normalizedQuery)
|
|
25248
|
+
return 0;
|
|
25249
|
+
const normalizedDescription = normalizeSearchText(candidate.description);
|
|
25250
|
+
const tokens = canonicalSearchTokens(normalizedQuery);
|
|
25251
|
+
const descriptionTokens = new Set(canonicalSearchTokens(normalizedDescription));
|
|
25252
|
+
let score = scoreIndexKey(normalizedQuery, candidate.key);
|
|
25253
|
+
if (tokens.length > 0 && normalizedDescription.includes(normalizedQuery))
|
|
25254
|
+
score += 30;
|
|
25255
|
+
let descriptionMatches = 0;
|
|
25256
|
+
for (const token of tokens) {
|
|
25257
|
+
if (descriptionTokens.has(token)) {
|
|
25258
|
+
score += 5;
|
|
25259
|
+
descriptionMatches += 1;
|
|
25260
|
+
}
|
|
25261
|
+
}
|
|
25262
|
+
if (tokens.length > 1 && descriptionMatches === tokens.length)
|
|
25263
|
+
score += 25;
|
|
25264
|
+
if (candidate.kind === "topic") {
|
|
25265
|
+
const requestsCurrent = /\b(?:current|active|ongoing|open)\b|\uD604\uC7AC|\uC9C4\uD589 \uC911/.test(normalizedQuery);
|
|
25266
|
+
const archivedCandidate = /\b(?:archive|archived|historical|closed)\b|\uBCF4\uAD00|\uC885\uB8CC/.test(`${normalizeSearchText(candidate.key)} ${normalizedDescription}`);
|
|
25267
|
+
if (requestsCurrent && archivedCandidate)
|
|
25268
|
+
score -= 40;
|
|
25269
|
+
}
|
|
25270
|
+
return score;
|
|
25271
|
+
}
|
|
25073
25272
|
function wikiQuery(args) {
|
|
25074
|
-
const question = safeExt(args, ["question", "query", "q", "text"], "");
|
|
25075
|
-
const
|
|
25273
|
+
const question = String(safeExt(args, ["question", "query", "q", "text"], "")).trim();
|
|
25274
|
+
const requestedKind = String(safeExt(args, ["kind", "type"], "all"));
|
|
25275
|
+
const kind = requestedKind === "topic" || requestedKind === "article" || requestedKind === "summary" ? requestedKind : "all";
|
|
25276
|
+
const requestedLimit = Number(safeExt(args, ["limit", "maxResults"], 8));
|
|
25277
|
+
const limit = Math.min(Math.max(Number.isFinite(requestedLimit) ? requestedLimit : 8, 1), 20);
|
|
25278
|
+
if (!question) {
|
|
25279
|
+
return {
|
|
25280
|
+
content: [{ type: "text", text: "A non-empty wiki query is required." }],
|
|
25281
|
+
isError: true
|
|
25282
|
+
};
|
|
25283
|
+
}
|
|
25284
|
+
const normalizedQuery = normalizeSearchText(question);
|
|
25285
|
+
if (!normalizedQuery) {
|
|
25286
|
+
return { content: [{ type: "text", text: "No matching wiki articles found." }] };
|
|
25287
|
+
}
|
|
25288
|
+
const canReadTopicMemory = runtime().host.canReadTopicMemory;
|
|
25289
|
+
const temporalDirection = /\b(?:latest|newest|recent)\b|\uCD5C\uADFC|\uCD5C\uC2E0/.test(normalizedQuery) ? "newest" : /\b(?:earliest|oldest)\b|\uAC00\uC7A5 \uC624\uB798\uB41C|\uCD5C\uCD08/.test(normalizedQuery) ? "oldest" : undefined;
|
|
25290
|
+
const visibleIndexCandidates = [
|
|
25291
|
+
...parseWikiIndex(resolve17(runtime().wikiDir, "topic-index.md")),
|
|
25292
|
+
...parseWikiIndex(resolve17(runtime().wikiDir, "article-index.md"))
|
|
25293
|
+
].filter((candidate) => kind === "all" || candidate.kind === kind).filter((candidate) => candidate.kind !== "topic" || !canReadTopicMemory || canReadTopicMemory(candidate.key, runtime().userId));
|
|
25294
|
+
const hasStaleExactIndexMatch = visibleIndexCandidates.some((candidate) => !indexCandidateExists(candidate) && scoreIndexKey(question, candidate.key) >= 100);
|
|
25295
|
+
const indexCandidates = visibleIndexCandidates.filter(indexCandidateExists).map((candidate) => ({
|
|
25296
|
+
candidate,
|
|
25297
|
+
keyScore: scoreIndexKey(question, candidate.key),
|
|
25298
|
+
score: scoreIndexCandidate(question, candidate)
|
|
25299
|
+
})).filter(({ keyScore, score }) => keyScore >= 25 || score >= 40).sort((left, right) => right.score - left.score || left.candidate.key.localeCompare(right.candidate.key));
|
|
25300
|
+
const strongestIndex = indexCandidates[0];
|
|
25301
|
+
if (kind === "topic" && strongestIndex && (strongestIndex.keyScore >= 25 || strongestIndex.score >= 40)) {
|
|
25302
|
+
const runnerUp = indexCandidates[1];
|
|
25303
|
+
const ambiguous = strongestIndex.keyScore < 100 && runnerUp !== undefined && runnerUp.score >= strongestIndex.score * 0.9;
|
|
25304
|
+
if (ambiguous) {
|
|
25305
|
+
const lines2 = ["Found 2 ambiguous wiki candidates:", ""];
|
|
25306
|
+
for (const { candidate, score } of [strongestIndex, runnerUp]) {
|
|
25307
|
+
lines2.push(`- kind: ${candidate.kind}`, ` key: ${candidate.key}`, ` score: ${score.toFixed(2)}`, ` description: ${candidate.description || "(none)"}`, ...candidate.date ? [` date: ${candidate.date}`] : []);
|
|
25308
|
+
}
|
|
25309
|
+
return { content: [{ type: "text", text: lines2.join(`
|
|
25310
|
+
`) }] };
|
|
25311
|
+
}
|
|
25312
|
+
const lines = ["Found 1 wiki candidate:", ""];
|
|
25313
|
+
for (const { candidate, score } of [strongestIndex]) {
|
|
25314
|
+
lines.push(`- kind: ${candidate.kind}`, ` key: ${candidate.key}`, ` score: ${score.toFixed(2)}`, ` description: ${candidate.description || "(none)"}`, ...candidate.date ? [` date: ${candidate.date}`] : []);
|
|
25315
|
+
}
|
|
25316
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
25317
|
+
`) }] };
|
|
25318
|
+
}
|
|
25076
25319
|
const results = [];
|
|
25077
25320
|
const scored = [];
|
|
25078
25321
|
function scan(dir, label) {
|
|
@@ -25089,67 +25332,234 @@ function wikiQuery(args) {
|
|
|
25089
25332
|
const fp = join36(sub, f.name);
|
|
25090
25333
|
try {
|
|
25091
25334
|
const text2 = readFileSync22(fp, "utf-8");
|
|
25335
|
+
const key = `${entry.name}/${f.name.replace(/\.md$/i, "")}`;
|
|
25092
25336
|
scored.push({
|
|
25093
|
-
|
|
25337
|
+
kind: label === "articles" ? "article" : "skill",
|
|
25338
|
+
key,
|
|
25339
|
+
keyScore: scoreIndexKey(question, key),
|
|
25340
|
+
score: scoreMatch(text2, key, label === "articles" ? "article" : "skill"),
|
|
25094
25341
|
path: `${label}/${entry.name}/${f.name}`,
|
|
25095
|
-
text: text2
|
|
25342
|
+
text: text2,
|
|
25343
|
+
title: titleFromDocument(text2, key)
|
|
25096
25344
|
});
|
|
25097
25345
|
} catch {}
|
|
25098
25346
|
}
|
|
25099
25347
|
} catch {}
|
|
25100
25348
|
}
|
|
25101
25349
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
25350
|
+
if (label === "topic" && canReadTopicMemory && !canReadTopicMemory(entry.name.replace(/\.md$/i, ""), runtime().userId)) {
|
|
25351
|
+
continue;
|
|
25352
|
+
}
|
|
25102
25353
|
const fp = join36(dir, entry.name);
|
|
25103
25354
|
try {
|
|
25104
25355
|
const text2 = readFileSync22(fp, "utf-8");
|
|
25356
|
+
const key = entry.name.replace(/\.md$/i, "");
|
|
25357
|
+
const resultKind = label === "articles" ? "article" : label === "topic" ? "topic" : label === "summaries" ? "summary" : "skill";
|
|
25105
25358
|
scored.push({
|
|
25106
|
-
|
|
25359
|
+
kind: resultKind,
|
|
25360
|
+
key,
|
|
25361
|
+
keyScore: scoreIndexKey(question, key),
|
|
25362
|
+
score: scoreMatch(text2, key, resultKind),
|
|
25107
25363
|
path: `${label}/${entry.name}`,
|
|
25108
|
-
text: text2
|
|
25364
|
+
text: text2,
|
|
25365
|
+
title: titleFromDocument(text2, key),
|
|
25366
|
+
...label === "summaries" && /^\d{4}-\d{2}-\d{2}/.test(key) ? { date: key.slice(0, 10) } : {}
|
|
25109
25367
|
});
|
|
25110
25368
|
} catch {}
|
|
25111
25369
|
}
|
|
25112
25370
|
}
|
|
25113
25371
|
} catch {}
|
|
25114
25372
|
}
|
|
25115
|
-
function
|
|
25116
|
-
const
|
|
25373
|
+
function titleFromDocument(text2, key) {
|
|
25374
|
+
const titleLine = text2.split(`
|
|
25375
|
+
`).find((line) => /^#+\s*/.test(line));
|
|
25376
|
+
return titleLine ? titleLine.replace(/^#+\s*/, "") : basename7(key);
|
|
25377
|
+
}
|
|
25378
|
+
function scoreMatch(text2, key, resultKind) {
|
|
25379
|
+
const titleLine = text2.split(`
|
|
25380
|
+
`).find((line) => /^#+\s*/.test(line)) ?? "";
|
|
25381
|
+
const title = titleLine.replace(/^#+\s*/, "");
|
|
25382
|
+
const normalizedKey = normalizeSearchText(key);
|
|
25383
|
+
const normalizedTitle2 = normalizeSearchText(title);
|
|
25384
|
+
const normalizedBody = normalizeSearchText(text2.replace(titleLine, ""));
|
|
25385
|
+
const compactQuery = compactSearchText(normalizedQuery);
|
|
25386
|
+
const compactKey = compactSearchText(normalizedKey);
|
|
25387
|
+
const compactTitle = compactSearchText(normalizedTitle2);
|
|
25388
|
+
const tokens = canonicalSearchTokens(normalizedQuery);
|
|
25389
|
+
const keyTokens = new Set(canonicalSearchTokens(normalizedKey));
|
|
25390
|
+
const titleTokens = new Set(canonicalSearchTokens(normalizedTitle2));
|
|
25391
|
+
const bodyTokens = canonicalSearchTokens(normalizedBody);
|
|
25117
25392
|
let score = 0;
|
|
25118
|
-
|
|
25119
|
-
|
|
25120
|
-
|
|
25121
|
-
|
|
25122
|
-
|
|
25123
|
-
|
|
25124
|
-
|
|
25125
|
-
|
|
25126
|
-
|
|
25127
|
-
|
|
25393
|
+
let strongMetadataEvidence = false;
|
|
25394
|
+
if (normalizedKey === normalizedQuery) {
|
|
25395
|
+
score += 120;
|
|
25396
|
+
strongMetadataEvidence = true;
|
|
25397
|
+
} else if (compactKey === compactQuery) {
|
|
25398
|
+
score += 110;
|
|
25399
|
+
strongMetadataEvidence = true;
|
|
25400
|
+
} else if (compactQuery.length >= 2 && (compactKey.startsWith(compactQuery) || compactQuery.startsWith(compactKey))) {
|
|
25401
|
+
score += 65;
|
|
25402
|
+
strongMetadataEvidence = true;
|
|
25403
|
+
}
|
|
25404
|
+
if (tokens.length > 0 && compactQuery.length >= 3 && compactKey.includes(compactQuery)) {
|
|
25405
|
+
score += 45;
|
|
25406
|
+
strongMetadataEvidence = true;
|
|
25407
|
+
}
|
|
25408
|
+
const fuzzyKeyScore = fuzzySearchScore(compactQuery, compactKey, 55);
|
|
25409
|
+
score += fuzzyKeyScore;
|
|
25410
|
+
if (fuzzyKeyScore > 0)
|
|
25411
|
+
strongMetadataEvidence = true;
|
|
25412
|
+
if (normalizedTitle2 === normalizedQuery) {
|
|
25413
|
+
score += 115;
|
|
25414
|
+
strongMetadataEvidence = true;
|
|
25415
|
+
} else if (compactTitle === compactQuery) {
|
|
25416
|
+
score += 105;
|
|
25417
|
+
strongMetadataEvidence = true;
|
|
25418
|
+
} else if (compactQuery.length >= 2 && compactTitle.startsWith(compactQuery)) {
|
|
25419
|
+
score += 60;
|
|
25420
|
+
strongMetadataEvidence = true;
|
|
25421
|
+
}
|
|
25422
|
+
const fuzzyTitleScore = fuzzySearchScore(compactQuery, compactTitle, 50);
|
|
25423
|
+
score += fuzzyTitleScore;
|
|
25424
|
+
if (fuzzyTitleScore > 0)
|
|
25425
|
+
strongMetadataEvidence = true;
|
|
25426
|
+
if (resultKind === "summary") {
|
|
25427
|
+
const temporalScore = scoreSummaryTime(normalizedQuery, key, title);
|
|
25428
|
+
if (temporalScore === null)
|
|
25429
|
+
return 0;
|
|
25430
|
+
score += temporalScore;
|
|
25431
|
+
if (temporalScore > 0)
|
|
25432
|
+
strongMetadataEvidence = true;
|
|
25433
|
+
}
|
|
25434
|
+
const isMultiToken = tokens.length > 1;
|
|
25435
|
+
if (tokens.length > 0 && normalizedTitle2.includes(normalizedQuery)) {
|
|
25436
|
+
score += 35;
|
|
25437
|
+
strongMetadataEvidence = true;
|
|
25438
|
+
}
|
|
25439
|
+
const phraseMatches = isMultiToken ? countPhrase(normalizedBody, normalizedQuery) : 0;
|
|
25440
|
+
if (phraseMatches > 0)
|
|
25441
|
+
score += phraseMatches * 12;
|
|
25442
|
+
else if (tokens.length === 1 && tokens[0].length >= 5 && normalizedBody.includes(tokens[0])) {
|
|
25443
|
+
score += 16;
|
|
25444
|
+
}
|
|
25445
|
+
let bodyMatches = 0;
|
|
25446
|
+
let minimumBodyFrequency = Number.POSITIVE_INFINITY;
|
|
25447
|
+
for (const token of tokens) {
|
|
25448
|
+
if (keyTokens.has(token))
|
|
25449
|
+
score += 10;
|
|
25450
|
+
if (titleTokens.has(token))
|
|
25451
|
+
score += 12;
|
|
25452
|
+
const bodyFrequency = countToken(bodyTokens, token);
|
|
25453
|
+
if (bodyFrequency > 0) {
|
|
25454
|
+
score += Math.min(bodyFrequency, 3) * 4;
|
|
25455
|
+
bodyMatches += 1;
|
|
25456
|
+
minimumBodyFrequency = Math.min(minimumBodyFrequency, bodyFrequency);
|
|
25457
|
+
}
|
|
25458
|
+
}
|
|
25459
|
+
if (isMultiToken && bodyMatches === tokens.length) {
|
|
25460
|
+
score += 8 * Math.min(minimumBodyFrequency, 3);
|
|
25461
|
+
}
|
|
25462
|
+
const minimumBodyTokens = hasStaleExactIndexMatch ? 3 : 2;
|
|
25463
|
+
const partialArticleEvidence = resultKind === "article" && bodyMatches >= 3 && bodyMatches / tokens.length >= 0.6;
|
|
25464
|
+
const strongBodyEvidence = tokens.length >= minimumBodyTokens && (bodyMatches === tokens.length && (phraseMatches > 0 || minimumBodyFrequency >= 1) || partialArticleEvidence);
|
|
25465
|
+
if (!strongMetadataEvidence && !strongBodyEvidence)
|
|
25466
|
+
return 0;
|
|
25128
25467
|
return score;
|
|
25129
25468
|
}
|
|
25130
|
-
|
|
25131
|
-
|
|
25469
|
+
if (kind === "all" || kind === "article")
|
|
25470
|
+
scan(runtime().articlesDir, "articles");
|
|
25471
|
+
if (kind === "all" && runtime().surface === "all")
|
|
25132
25472
|
scan(runtime().skillsDir, "skills");
|
|
25133
|
-
|
|
25134
|
-
|
|
25135
|
-
|
|
25136
|
-
|
|
25137
|
-
|
|
25473
|
+
if (kind === "all" || kind === "topic")
|
|
25474
|
+
scan(runtime().topicsDir, "topic");
|
|
25475
|
+
if (kind === "all" || kind === "summary")
|
|
25476
|
+
scan(runtime().summariesDir, "summaries");
|
|
25477
|
+
const merged = new Map;
|
|
25478
|
+
for (const { candidate, keyScore, score } of indexCandidates) {
|
|
25479
|
+
merged.set(`${candidate.kind}:${candidate.key}`, {
|
|
25480
|
+
kind: candidate.kind,
|
|
25481
|
+
key: candidate.key,
|
|
25482
|
+
keyScore,
|
|
25483
|
+
score,
|
|
25484
|
+
description: candidate.description,
|
|
25485
|
+
date: candidate.date
|
|
25486
|
+
});
|
|
25487
|
+
}
|
|
25488
|
+
const documentThreshold = kind === "topic" ? 24 : 16;
|
|
25489
|
+
for (const document of scored) {
|
|
25490
|
+
if (document.score < documentThreshold)
|
|
25491
|
+
continue;
|
|
25492
|
+
const id = `${document.kind}:${document.key}`;
|
|
25493
|
+
const indexed = merged.get(id);
|
|
25494
|
+
merged.set(id, indexed ? { ...indexed, ...document, score: Math.max(indexed.score, document.score) } : document);
|
|
25495
|
+
}
|
|
25496
|
+
let ranked = [...merged.values()].sort((left, right) => right.score - left.score || (left.kind === "summary" && right.kind === "summary" ? temporalDirection === "oldest" ? (left.date ?? "").localeCompare(right.date ?? "") : (right.date ?? "").localeCompare(left.date ?? "") : 0) || left.key.localeCompare(right.key));
|
|
25497
|
+
if (kind === "summary") {
|
|
25498
|
+
const sameTopic = ranked.filter((result) => {
|
|
25499
|
+
const identity = summaryTopicIdentity(result.key);
|
|
25500
|
+
return identity.length >= 3 && normalizedQuery.includes(identity);
|
|
25501
|
+
});
|
|
25502
|
+
if (sameTopic.length > 0)
|
|
25503
|
+
ranked = sameTopic;
|
|
25504
|
+
const requestedDateMatch = normalizedQuery.match(/\b(\d{4}) (\d{2}) (\d{2})\b/);
|
|
25505
|
+
const requestedDate = requestedDateMatch ? `${requestedDateMatch[1]}-${requestedDateMatch[2]}-${requestedDateMatch[3]}` : undefined;
|
|
25506
|
+
const beforeDate = /\bbefore\b/.test(normalizedQuery);
|
|
25507
|
+
const afterDate = /\bafter\b/.test(normalizedQuery);
|
|
25508
|
+
if (requestedDate && beforeDate) {
|
|
25509
|
+
ranked = ranked.filter((result) => !result.date || result.date < requestedDate);
|
|
25510
|
+
} else if (requestedDate && afterDate) {
|
|
25511
|
+
ranked = ranked.filter((result) => !result.date || result.date > requestedDate);
|
|
25512
|
+
}
|
|
25513
|
+
ranked.sort((left, right) => {
|
|
25514
|
+
if (beforeDate) {
|
|
25515
|
+
return (right.date ?? "").localeCompare(left.date ?? "") || right.score - left.score;
|
|
25516
|
+
}
|
|
25517
|
+
if (afterDate) {
|
|
25518
|
+
return (left.date ?? "").localeCompare(right.date ?? "") || right.score - left.score;
|
|
25519
|
+
}
|
|
25520
|
+
if (requestedDate) {
|
|
25521
|
+
const dateDifference = Number(right.date === requestedDate) - Number(left.date === requestedDate);
|
|
25522
|
+
if (dateDifference !== 0)
|
|
25523
|
+
return dateDifference;
|
|
25524
|
+
}
|
|
25525
|
+
if (temporalDirection) {
|
|
25526
|
+
const dateDifference = temporalDirection === "oldest" ? (left.date ?? "").localeCompare(right.date ?? "") : (right.date ?? "").localeCompare(left.date ?? "");
|
|
25527
|
+
if (dateDifference !== 0)
|
|
25528
|
+
return dateDifference;
|
|
25529
|
+
}
|
|
25530
|
+
return right.score - left.score || (right.date ?? "").localeCompare(left.date ?? "") || left.key.localeCompare(right.key);
|
|
25531
|
+
});
|
|
25532
|
+
}
|
|
25533
|
+
if (kind === "topic" && ranked[0]) {
|
|
25534
|
+
const runnerUp = ranked[1];
|
|
25535
|
+
const ambiguous = ranked[0].keyScore < 100 && runnerUp !== undefined && runnerUp.score >= ranked[0].score * 0.9;
|
|
25536
|
+
if (ambiguous) {
|
|
25537
|
+
ranked = ranked.slice(0, 2);
|
|
25538
|
+
} else {
|
|
25539
|
+
ranked = ranked.slice(0, 1);
|
|
25540
|
+
}
|
|
25541
|
+
} else {
|
|
25542
|
+
ranked = ranked.slice(0, limit);
|
|
25543
|
+
}
|
|
25544
|
+
if (ranked.length === 0) {
|
|
25138
25545
|
results.push("No matching wiki articles found.");
|
|
25139
25546
|
} else {
|
|
25140
|
-
results.push(`Found ${
|
|
25547
|
+
results.push(`Found ${ranked.length} matching result(s):
|
|
25141
25548
|
`);
|
|
25142
|
-
for (const
|
|
25143
|
-
|
|
25144
|
-
|
|
25145
|
-
|
|
25146
|
-
|
|
25147
|
-
|
|
25148
|
-
|
|
25149
|
-
|
|
25150
|
-
${body}${text2.length > 400 ? `
|
|
25549
|
+
for (const result of ranked) {
|
|
25550
|
+
if (result.kind === "skill") {
|
|
25551
|
+
const body = (result.text ?? "").trim().slice(0, 400);
|
|
25552
|
+
results.push(`## ${result.title ?? result.key}
|
|
25553
|
+
Path: ${result.path}
|
|
25554
|
+
|
|
25555
|
+
${body}${(result.text?.length ?? 0) > 400 ? `
|
|
25151
25556
|
...(truncated)` : ""}
|
|
25152
25557
|
`);
|
|
25558
|
+
continue;
|
|
25559
|
+
}
|
|
25560
|
+
results.push(`- kind: ${result.kind}`, ` key: ${result.key}`, ` score: ${result.score.toFixed(2)}`, ...result.title ? [` title: ${result.title}`] : [], ` description: ${result.description || "(none)"}`, ...result.date ? [` date: ${result.date}`] : [], ...result.path ? [` path: ${result.path}`, ` Path: ${result.path}`] : [], ...result.text ? [
|
|
25561
|
+
` excerpt: ${result.text.replace(/^---[\s\S]*?---\n?/, "").replace(/^#.*$/m, "").trim().slice(0, 400)}`
|
|
25562
|
+
] : []);
|
|
25153
25563
|
}
|
|
25154
25564
|
}
|
|
25155
25565
|
return { content: [{ type: "text", text: results.join(`
|
|
@@ -25168,36 +25578,94 @@ function wikiTopicBrief(args) {
|
|
|
25168
25578
|
};
|
|
25169
25579
|
}
|
|
25170
25580
|
const { getTopicBrief: getTopicBrief2, resolveTopicBrief: resolveTopicBrief2 } = runtime().host;
|
|
25171
|
-
|
|
25581
|
+
const hasAuthorizedBridge = Boolean(runtime().topicId && (getTopicBrief2 || resolveTopicBrief2));
|
|
25582
|
+
if (hasAuthorizedBridge) {
|
|
25172
25583
|
try {
|
|
25173
25584
|
const brief = resolveTopicBrief2 ? resolveTopicBrief2(rawTopic, runtime().userId) : getTopicBrief2(rawTopic);
|
|
25174
|
-
if (
|
|
25175
|
-
|
|
25176
|
-
|
|
25177
|
-
|
|
25178
|
-
|
|
25179
|
-
|
|
25180
|
-
|
|
25181
|
-
|
|
25182
|
-
|
|
25183
|
-
|
|
25184
|
-
|
|
25185
|
-
|
|
25186
|
-
|
|
25187
|
-
if (brief.summaryDate) {
|
|
25188
|
-
lines.push("", `Summary date: ${brief.summaryDate}`);
|
|
25189
|
-
}
|
|
25190
|
-
return { content: [{ type: "text", text: lines.join(`
|
|
25585
|
+
if (brief) {
|
|
25586
|
+
const lines = [
|
|
25587
|
+
`# Topic Brief: ${topicNameFrom(rawTopic)}`,
|
|
25588
|
+
"",
|
|
25589
|
+
brief.briefMd || "(empty brief)"
|
|
25590
|
+
];
|
|
25591
|
+
if (brief.latestSummaryMd) {
|
|
25592
|
+
lines.push("", "## Latest Summary", "", brief.latestSummaryMd);
|
|
25593
|
+
}
|
|
25594
|
+
if (brief.summaryDate) {
|
|
25595
|
+
lines.push("", `Summary date: ${brief.summaryDate}`);
|
|
25596
|
+
}
|
|
25597
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
25191
25598
|
`) }] };
|
|
25192
|
-
|
|
25599
|
+
}
|
|
25600
|
+
} catch {}
|
|
25601
|
+
return {
|
|
25602
|
+
content: [{ type: "text", text: `No brief found for topic: ${rawTopic}` }]
|
|
25603
|
+
};
|
|
25604
|
+
}
|
|
25605
|
+
const topicKey = wikiSummarySlug(rawTopic.replace(/^topic\//, "").replace(/\.md$/i, ""));
|
|
25606
|
+
const filePath = resolve17(runtime().topicsDir, `${topicKey}.md`);
|
|
25607
|
+
try {
|
|
25608
|
+
const content = readFileSync22(filePath, "utf-8");
|
|
25609
|
+
return {
|
|
25610
|
+
content: [{ type: "text", text: `# Topic Brief: ${topicKey}
|
|
25611
|
+
|
|
25612
|
+
${content}` }]
|
|
25613
|
+
};
|
|
25614
|
+
} catch {}
|
|
25615
|
+
return {
|
|
25616
|
+
content: [{ type: "text", text: `No brief found for topic: ${rawTopic}` }]
|
|
25617
|
+
};
|
|
25618
|
+
}
|
|
25619
|
+
function wikiRead(args) {
|
|
25620
|
+
const rawKind = String(safeExt(args, ["kind", "type"], "article"));
|
|
25621
|
+
const kind = rawKind === "topic" || rawKind === "summary" ? rawKind : "article";
|
|
25622
|
+
const rawKey = String(safeExt(args, ["key", "slug", "topic"], "")).trim();
|
|
25623
|
+
if (!rawKey) {
|
|
25624
|
+
return { content: [{ type: "text", text: "A wiki document key is required." }], isError: true };
|
|
25625
|
+
}
|
|
25626
|
+
if (kind === "topic") {
|
|
25627
|
+
const result = wikiTopicBrief({ topic: rawKey });
|
|
25628
|
+
const resultText = result.content.map((entry) => entry.type === "text" ? entry.text : "").join(`
|
|
25629
|
+
`);
|
|
25630
|
+
const adopt = safeExt(args, ["adopt", "use", "select"], false);
|
|
25631
|
+
if (!adopt || resultText.startsWith("No brief found for topic"))
|
|
25632
|
+
return result;
|
|
25633
|
+
const currentTopicId = runtime().currentTopicId;
|
|
25634
|
+
const memoryKey = wikiSummarySlug(rawKey.replace(/^topic\//, "").replace(/\.md$/i, ""));
|
|
25635
|
+
if (!currentTopicId || !runtime().host.adoptTopicMemory?.(currentTopicId, runtime().userId, memoryKey)) {
|
|
25193
25636
|
return {
|
|
25194
|
-
content: [
|
|
25637
|
+
content: [
|
|
25638
|
+
...result.content,
|
|
25639
|
+
{ type: "text", text: `
|
|
25640
|
+
Memory was read but could not be adopted by this topic.` }
|
|
25641
|
+
]
|
|
25195
25642
|
};
|
|
25196
25643
|
}
|
|
25644
|
+
return {
|
|
25645
|
+
content: [...result.content, { type: "text", text: `
|
|
25646
|
+
Adopted topic memory: ${memoryKey}` }]
|
|
25647
|
+
};
|
|
25648
|
+
}
|
|
25649
|
+
if (rawKey.includes("\\") || rawKey.split("/").some((part) => part === "..") || rawKey.startsWith("/")) {
|
|
25650
|
+
return { content: [{ type: "text", text: "Invalid wiki document key." }], isError: true };
|
|
25651
|
+
}
|
|
25652
|
+
const root = kind === "summary" ? runtime().summariesDir : runtime().articlesDir;
|
|
25653
|
+
const key = rawKey.replace(new RegExp(`^(?:${kind === "summary" ? "summaries" : "articles"})/`), "").replace(/\.md$/i, "");
|
|
25654
|
+
const filePath = resolve17(root, `${key}.md`);
|
|
25655
|
+
const relativePath = relative2(root, filePath);
|
|
25656
|
+
if (relativePath.startsWith("..") || relativePath.startsWith("/")) {
|
|
25657
|
+
return { content: [{ type: "text", text: "Invalid wiki document key." }], isError: true };
|
|
25658
|
+
}
|
|
25659
|
+
try {
|
|
25660
|
+
const content = readFileSync22(filePath, "utf-8");
|
|
25661
|
+
return {
|
|
25662
|
+
content: [{ type: "text", text: `# Wiki ${kind}: ${key}
|
|
25663
|
+
|
|
25664
|
+
${content}` }]
|
|
25665
|
+
};
|
|
25666
|
+
} catch {
|
|
25667
|
+
return { content: [{ type: "text", text: `No ${kind} found for: ${rawKey}` }] };
|
|
25197
25668
|
}
|
|
25198
|
-
return {
|
|
25199
|
-
content: [{ type: "text", text: `No topic brief found for: ${rawTopic}` }]
|
|
25200
|
-
};
|
|
25201
25669
|
}
|
|
25202
25670
|
function wikiLastConversation(args) {
|
|
25203
25671
|
const rawTopic = safeExt(args, ["topic", "topicName", "topicId"], runtime().topicId);
|
|
@@ -25566,6 +26034,7 @@ function createWikiMcpServer(context, host2) {
|
|
|
25566
26034
|
const wikiDir = resolve17(host2.wikiRoot);
|
|
25567
26035
|
const current3 = {
|
|
25568
26036
|
userId: context.userId,
|
|
26037
|
+
currentTopicId: context.currentTopicId ?? context.topicId,
|
|
25569
26038
|
topicId: context.topicId,
|
|
25570
26039
|
surface,
|
|
25571
26040
|
host: host2,
|
|
@@ -25595,6 +26064,7 @@ function createWikiMcpServer(context, host2) {
|
|
|
25595
26064
|
const { name, arguments: args } = request.params;
|
|
25596
26065
|
const handlers = {
|
|
25597
26066
|
wiki_query: wikiQuery,
|
|
26067
|
+
wiki_read: wikiRead,
|
|
25598
26068
|
wiki_topic_brief: wikiTopicBrief,
|
|
25599
26069
|
wiki_last_conversation: wikiLastConversation,
|
|
25600
26070
|
skill_query: skillQuery,
|
|
@@ -25609,16 +26079,87 @@ function createWikiMcpServer(context, host2) {
|
|
|
25609
26079
|
});
|
|
25610
26080
|
return server;
|
|
25611
26081
|
}
|
|
25612
|
-
var wikiRuntime, FILE_LOCK_WAIT_MS = 10, FILE_LOCK_TIMEOUT_MS = 5000, FILE_LOCK_STALE_MS = 30000, lockWaitArray, WIKI_TOOLS, SKILL_TOOLS;
|
|
26082
|
+
var wikiRuntime, FILE_LOCK_WAIT_MS = 10, FILE_LOCK_TIMEOUT_MS = 5000, FILE_LOCK_STALE_MS = 30000, lockWaitArray, SEARCH_STOP_WORDS, SEARCH_MONTHS, SEARCH_RECENCY_TERMS, WIKI_TOOLS, SKILL_TOOLS;
|
|
25613
26083
|
var init_wiki_server = __esm(() => {
|
|
25614
26084
|
init_config();
|
|
25615
26085
|
init_wiki_summary_names();
|
|
25616
26086
|
wikiRuntime = new AsyncLocalStorage3;
|
|
25617
26087
|
lockWaitArray = new Int32Array(new SharedArrayBuffer(4));
|
|
26088
|
+
SEARCH_STOP_WORDS = new Set([
|
|
26089
|
+
"a",
|
|
26090
|
+
"an",
|
|
26091
|
+
"and",
|
|
26092
|
+
"are",
|
|
26093
|
+
"as",
|
|
26094
|
+
"at",
|
|
26095
|
+
"be",
|
|
26096
|
+
"by",
|
|
26097
|
+
"for",
|
|
26098
|
+
"from",
|
|
26099
|
+
"in",
|
|
26100
|
+
"is",
|
|
26101
|
+
"it",
|
|
26102
|
+
"of",
|
|
26103
|
+
"on",
|
|
26104
|
+
"or",
|
|
26105
|
+
"that",
|
|
26106
|
+
"the",
|
|
26107
|
+
"to",
|
|
26108
|
+
"with"
|
|
26109
|
+
]);
|
|
26110
|
+
SEARCH_MONTHS = new Map([
|
|
26111
|
+
["january", "01"],
|
|
26112
|
+
["jan", "01"],
|
|
26113
|
+
["1\uC6D4", "01"],
|
|
26114
|
+
["february", "02"],
|
|
26115
|
+
["feb", "02"],
|
|
26116
|
+
["2\uC6D4", "02"],
|
|
26117
|
+
["march", "03"],
|
|
26118
|
+
["mar", "03"],
|
|
26119
|
+
["3\uC6D4", "03"],
|
|
26120
|
+
["april", "04"],
|
|
26121
|
+
["apr", "04"],
|
|
26122
|
+
["4\uC6D4", "04"],
|
|
26123
|
+
["may", "05"],
|
|
26124
|
+
["5\uC6D4", "05"],
|
|
26125
|
+
["june", "06"],
|
|
26126
|
+
["jun", "06"],
|
|
26127
|
+
["6\uC6D4", "06"],
|
|
26128
|
+
["july", "07"],
|
|
26129
|
+
["jul", "07"],
|
|
26130
|
+
["7\uC6D4", "07"],
|
|
26131
|
+
["august", "08"],
|
|
26132
|
+
["aug", "08"],
|
|
26133
|
+
["8\uC6D4", "08"],
|
|
26134
|
+
["september", "09"],
|
|
26135
|
+
["sep", "09"],
|
|
26136
|
+
["sept", "09"],
|
|
26137
|
+
["9\uC6D4", "09"],
|
|
26138
|
+
["october", "10"],
|
|
26139
|
+
["oct", "10"],
|
|
26140
|
+
["10\uC6D4", "10"],
|
|
26141
|
+
["november", "11"],
|
|
26142
|
+
["nov", "11"],
|
|
26143
|
+
["11\uC6D4", "11"],
|
|
26144
|
+
["december", "12"],
|
|
26145
|
+
["dec", "12"],
|
|
26146
|
+
["12\uC6D4", "12"]
|
|
26147
|
+
]);
|
|
26148
|
+
SEARCH_RECENCY_TERMS = new Set([
|
|
26149
|
+
"latest",
|
|
26150
|
+
"newest",
|
|
26151
|
+
"recent",
|
|
26152
|
+
"earliest",
|
|
26153
|
+
"oldest",
|
|
26154
|
+
"\uCD5C\uADFC",
|
|
26155
|
+
"\uCD5C\uC2E0",
|
|
26156
|
+
"\uCD5C\uCD08",
|
|
26157
|
+
"\uC624\uB798\uB41C"
|
|
26158
|
+
]);
|
|
25618
26159
|
WIKI_TOOLS = [
|
|
25619
26160
|
{
|
|
25620
26161
|
name: "wiki_query",
|
|
25621
|
-
description: "Search
|
|
26162
|
+
description: "Search topic, article, or summary indexes and return relevant candidates.",
|
|
25622
26163
|
inputSchema: {
|
|
25623
26164
|
type: "object",
|
|
25624
26165
|
properties: {
|
|
@@ -25629,11 +26170,38 @@ var init_wiki_server = __esm(() => {
|
|
|
25629
26170
|
topic: {
|
|
25630
26171
|
type: "string",
|
|
25631
26172
|
description: "Optional topic name to narrow the search"
|
|
26173
|
+
},
|
|
26174
|
+
kind: {
|
|
26175
|
+
type: "string",
|
|
26176
|
+
enum: ["all", "topic", "article", "summary"],
|
|
26177
|
+
description: "Document kind to search; defaults to all"
|
|
26178
|
+
},
|
|
26179
|
+
limit: {
|
|
26180
|
+
type: "number",
|
|
26181
|
+
minimum: 1,
|
|
26182
|
+
maximum: 20,
|
|
26183
|
+
description: "Maximum number of candidates; defaults to 8"
|
|
25632
26184
|
}
|
|
25633
26185
|
},
|
|
25634
26186
|
required: ["question"]
|
|
25635
26187
|
}
|
|
25636
26188
|
},
|
|
26189
|
+
{
|
|
26190
|
+
name: "wiki_read",
|
|
26191
|
+
description: "Read one topic, article, or summary returned by wiki_query. For a clearly matching continuing persona, set adopt=true when reading a topic.",
|
|
26192
|
+
inputSchema: {
|
|
26193
|
+
type: "object",
|
|
26194
|
+
properties: {
|
|
26195
|
+
kind: { type: "string", enum: ["topic", "article", "summary"] },
|
|
26196
|
+
key: { type: "string", description: "Canonical key returned by wiki_query" },
|
|
26197
|
+
adopt: {
|
|
26198
|
+
type: "boolean",
|
|
26199
|
+
description: "For kind=topic, persist this canonical memory key for the current room"
|
|
26200
|
+
}
|
|
26201
|
+
},
|
|
26202
|
+
required: ["kind", "key"]
|
|
26203
|
+
}
|
|
26204
|
+
},
|
|
25637
26205
|
{
|
|
25638
26206
|
name: "wiki_topic_brief",
|
|
25639
26207
|
description: "Get the lightweight topic brief for a specific topic.",
|
|
@@ -40957,6 +41525,7 @@ var init_canonical_mcp_bridge = __esm(async () => {
|
|
|
40957
41525
|
task: new Set(["task_create", "task_update", "task_list", "task_get", "task_delete"]),
|
|
40958
41526
|
wiki: new Set([
|
|
40959
41527
|
"wiki_query",
|
|
41528
|
+
"wiki_read",
|
|
40960
41529
|
"wiki_topic_brief",
|
|
40961
41530
|
"wiki_last_conversation",
|
|
40962
41531
|
"save_wiki_entry",
|
|
@@ -45823,4 +46392,4 @@ switch (command) {
|
|
|
45823
46392
|
}
|
|
45824
46393
|
}
|
|
45825
46394
|
|
|
45826
|
-
//# debugId=
|
|
46395
|
+
//# debugId=9061330203CC009264756E2164756E21
|