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/mcp-factories.js
CHANGED
|
@@ -952,7 +952,7 @@ var init_claude_registry = __esm(() => {
|
|
|
952
952
|
});
|
|
953
953
|
|
|
954
954
|
// ../../packages/core/src/version.ts
|
|
955
|
-
var NEGOTIUM_VERSION = "0.2.
|
|
955
|
+
var NEGOTIUM_VERSION = "0.2.18";
|
|
956
956
|
|
|
957
957
|
// ../../packages/core/src/agents/codex-native-multi-agent.ts
|
|
958
958
|
import { spawn as spawn2 } from "child_process";
|
|
@@ -2128,6 +2128,8 @@ var init_mcp_config = __esm(() => {
|
|
|
2128
2128
|
return env ? buildStdioMcpServer(agent, CANONICAL_MCP_PROXY_SERVER, ["--surface=wiki"], env) : null;
|
|
2129
2129
|
}
|
|
2130
2130
|
const args = [`--user-id=${userId}`];
|
|
2131
|
+
if (topicId)
|
|
2132
|
+
args.push(`--current-topic-id=${topicId}`);
|
|
2131
2133
|
const resolvedWikiTopicId = wikiTopicId ?? topicId ?? (session !== "dm" ? session : undefined);
|
|
2132
2134
|
if (resolvedWikiTopicId)
|
|
2133
2135
|
args.push(`--topic-id=${resolvedWikiTopicId}`);
|
|
@@ -4140,6 +4142,7 @@ function initializeApiTopicsSchema() {
|
|
|
4140
4142
|
last_message_at TEXT,
|
|
4141
4143
|
parent_topic_id TEXT,
|
|
4142
4144
|
memory_topic_id TEXT,
|
|
4145
|
+
memory_key TEXT,
|
|
4143
4146
|
is_fork INTEGER NOT NULL DEFAULT 0 CHECK (is_fork IN (0,1)),
|
|
4144
4147
|
is_subagent INTEGER NOT NULL DEFAULT 0 CHECK (is_subagent IN (0,1)),
|
|
4145
4148
|
visibility TEXT NOT NULL DEFAULT 'visible' CHECK (visibility IN ('visible','hidden')),
|
|
@@ -4189,8 +4192,8 @@ function initializeApiTopicsSchema() {
|
|
|
4189
4192
|
const legacyBaseEffort = row.base_effort ?? row.default_effort;
|
|
4190
4193
|
db.query(`INSERT INTO api_topics_next
|
|
4191
4194
|
(id,title,kind,description,agent,base_model,base_effort,response_policy,
|
|
4192
|
-
created_at,last_message_at,parent_topic_id,memory_topic_id,is_fork,is_subagent,visibility,access_mode,session_id)
|
|
4193
|
-
VALUES (
|
|
4195
|
+
created_at,last_message_at,parent_topic_id,memory_topic_id,memory_key,is_fork,is_subagent,visibility,access_mode,session_id)
|
|
4196
|
+
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);
|
|
4194
4197
|
}
|
|
4195
4198
|
db.exec("DROP TABLE IF EXISTS topic_members");
|
|
4196
4199
|
db.exec("DROP TABLE api_topics");
|
|
@@ -4259,6 +4262,9 @@ function initializeApiTopicsSchema() {
|
|
|
4259
4262
|
if (!tableColumns("api_topics").has("memory_topic_id")) {
|
|
4260
4263
|
db.exec("ALTER TABLE api_topics ADD COLUMN memory_topic_id TEXT");
|
|
4261
4264
|
}
|
|
4265
|
+
if (!tableColumns("api_topics").has("memory_key")) {
|
|
4266
|
+
db.exec("ALTER TABLE api_topics ADD COLUMN memory_key TEXT");
|
|
4267
|
+
}
|
|
4262
4268
|
db.exec(`
|
|
4263
4269
|
UPDATE api_topics
|
|
4264
4270
|
SET browser_profile_owner = (
|
|
@@ -4319,6 +4325,7 @@ function rowToDto2(r, participants = getTopicParticipants(r.id), tellTargets) {
|
|
|
4319
4325
|
lastMessageAt: r.last_message_at ?? new Date().toISOString(),
|
|
4320
4326
|
parentTopicId: r.parent_topic_id ?? undefined,
|
|
4321
4327
|
memoryTopicId: r.memory_topic_id ?? undefined,
|
|
4328
|
+
memoryKey: r.memory_key ?? undefined,
|
|
4322
4329
|
isFork: r.is_fork !== 0,
|
|
4323
4330
|
...r.is_subagent !== 0 ? { isSubagent: true } : {},
|
|
4324
4331
|
...r.is_subagent !== 0 ? {
|
|
@@ -4390,9 +4397,9 @@ function upsertTopic(t) {
|
|
|
4390
4397
|
db.transaction(() => {
|
|
4391
4398
|
db.query(`INSERT INTO api_topics
|
|
4392
4399
|
(id,title,kind,description,agent,base_model,base_effort,response_policy,
|
|
4393
|
-
created_at,last_message_at,parent_topic_id,memory_topic_id,is_fork,is_subagent,visibility,access_mode,
|
|
4400
|
+
created_at,last_message_at,parent_topic_id,memory_topic_id,memory_key,is_fork,is_subagent,visibility,access_mode,
|
|
4394
4401
|
subagent_report_mode)
|
|
4395
|
-
VALUES (
|
|
4402
|
+
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
|
4396
4403
|
ON CONFLICT(id) DO UPDATE SET
|
|
4397
4404
|
title = excluded.title,
|
|
4398
4405
|
kind = excluded.kind,
|
|
@@ -4405,11 +4412,12 @@ function upsertTopic(t) {
|
|
|
4405
4412
|
last_message_at = excluded.last_message_at,
|
|
4406
4413
|
parent_topic_id = excluded.parent_topic_id,
|
|
4407
4414
|
memory_topic_id = excluded.memory_topic_id,
|
|
4415
|
+
memory_key = excluded.memory_key,
|
|
4408
4416
|
is_fork = excluded.is_fork,
|
|
4409
4417
|
is_subagent = excluded.is_subagent,
|
|
4410
4418
|
visibility = excluded.visibility,
|
|
4411
4419
|
access_mode = excluded.access_mode,
|
|
4412
|
-
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");
|
|
4420
|
+
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");
|
|
4413
4421
|
db.query("DELETE FROM topic_members WHERE topic_id = ?").run(t.id);
|
|
4414
4422
|
for (const participant of t.participants) {
|
|
4415
4423
|
db.query("INSERT INTO topic_members (topic_id,user_id,role) VALUES (?,?,?)").run(t.id, participant.userId, participant.role);
|
|
@@ -9006,7 +9014,7 @@ __export(exports_codex_provider, {
|
|
|
9006
9014
|
import { execFileSync as execFileSync6 } from "child_process";
|
|
9007
9015
|
import { existsSync as existsSync15, readFileSync as readFileSync14, realpathSync as realpathSync4, statSync as statSync6 } from "fs";
|
|
9008
9016
|
import { homedir as homedir6 } from "os";
|
|
9009
|
-
import { dirname as dirname11, isAbsolute as isAbsolute3, join as join16, relative, resolve as resolve12 } from "path";
|
|
9017
|
+
import { dirname as dirname11, isAbsolute as isAbsolute3, join as join16, relative as relative2, resolve as resolve12 } from "path";
|
|
9010
9018
|
import { Codex } from "@openai/codex-sdk";
|
|
9011
9019
|
function sameCodexUsage(usage, total) {
|
|
9012
9020
|
return usage.input_tokens === total.inputTokens && usage.output_tokens === total.outputTokens && (usage.cached_input_tokens ?? 0) === total.cachedInputTokens && (usage.cache_write_input_tokens ?? 0) === total.cacheWriteInputTokens;
|
|
@@ -9190,7 +9198,7 @@ class CodexFilePreviewTracker {
|
|
|
9190
9198
|
if (!this.#root || !this.#baselineAvailable || !succeeded)
|
|
9191
9199
|
return {};
|
|
9192
9200
|
const absolute = canonicalPath(isAbsolute3(path) ? path : resolve12(this.#cwd, path));
|
|
9193
|
-
const relativePath =
|
|
9201
|
+
const relativePath = relative2(this.#root, absolute);
|
|
9194
9202
|
if (!relativePath || relativePath.startsWith("..") || isAbsolute3(relativePath))
|
|
9195
9203
|
return {};
|
|
9196
9204
|
const before = this.#baseline.has(relativePath) ? this.#baseline.get(relativePath) : this.#headFile(relativePath);
|
|
@@ -10282,14 +10290,14 @@ function buildMemoryPromptSection(opts) {
|
|
|
10282
10290
|
const parts = [`
|
|
10283
10291
|
|
|
10284
10292
|
## Memory`];
|
|
10285
|
-
|
|
10286
|
-
|
|
10287
|
-
if (opts.
|
|
10288
|
-
parts.push(`
|
|
10289
|
-
}
|
|
10290
|
-
|
|
10291
|
-
|
|
10292
|
-
parts.push("
|
|
10293
|
+
if (opts.isManager) {
|
|
10294
|
+
parts.push("Use `wiki_query` for past workspace decisions or cross-topic context, then `wiki_read` only for the relevant result.");
|
|
10295
|
+
} else if (opts.memoryKey) {
|
|
10296
|
+
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}")\`.`);
|
|
10297
|
+
} else {
|
|
10298
|
+
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.");
|
|
10299
|
+
}
|
|
10300
|
+
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.');
|
|
10293
10301
|
if (opts.hasArchive) {
|
|
10294
10302
|
parts.push("", "If you need the actual conversation from an earlier session, use the `wiki_last_conversation` MCP tool.");
|
|
10295
10303
|
}
|
|
@@ -11245,11 +11253,12 @@ function archiveActiveTopicForMemory(topicId, userId, options) {
|
|
|
11245
11253
|
}, "topic-memory-archiver: raw snapshot preserved below exchange threshold");
|
|
11246
11254
|
return "below-threshold";
|
|
11247
11255
|
}
|
|
11248
|
-
const memoryTopic = getTopicMemoryOrigin(topicId) ?? topic;
|
|
11256
|
+
const memoryTopic = topic.memoryKey ? topic : getTopicMemoryOrigin(topicId) ?? topic;
|
|
11257
|
+
const memoryTitle = memoryTopic.memoryKey?.trim() || memoryTopic.title;
|
|
11249
11258
|
const launched = (options.launchArchiver ?? runArchiverTurn)({
|
|
11250
11259
|
userId,
|
|
11251
11260
|
topicId: memoryTopic.id,
|
|
11252
|
-
topicTitle:
|
|
11261
|
+
topicTitle: memoryTitle,
|
|
11253
11262
|
archivePath: job.archivePath,
|
|
11254
11263
|
...options.rawArchivePaths ? { rawArchivePaths: options.rawArchivePaths } : {},
|
|
11255
11264
|
messageCount: job.messageCount,
|
|
@@ -13357,11 +13366,12 @@ async function deleteTopicCascadeImpl(topic, userId, options, deletingAncestorId
|
|
|
13357
13366
|
}
|
|
13358
13367
|
if (archived && meetsMemoryArchiveExchangeThreshold(archived.exchangeCount, MIN_MEMORY_ARCHIVE_EXCHANGES, topic)) {
|
|
13359
13368
|
try {
|
|
13360
|
-
const memoryTopic = getTopicMemoryOrigin(topicId) ?? topic;
|
|
13369
|
+
const memoryTopic = topic.memoryKey ? topic : getTopicMemoryOrigin(topicId) ?? topic;
|
|
13370
|
+
const memoryTitle = memoryTopic.memoryKey?.trim() || memoryTopic.title;
|
|
13361
13371
|
runArchiverTurn({
|
|
13362
13372
|
userId,
|
|
13363
13373
|
...memoryTopic.id !== topicId && !deletingTopicIds.has(memoryTopic.id) ? { topicId: memoryTopic.id } : {},
|
|
13364
|
-
topicTitle:
|
|
13374
|
+
topicTitle: memoryTitle,
|
|
13365
13375
|
archivePath: archived.path,
|
|
13366
13376
|
...rawArchives.length > 0 ? { rawArchivePaths: rawArchives } : {},
|
|
13367
13377
|
messageCount: archived.messageCount
|
|
@@ -16439,25 +16449,24 @@ function startAiTurn(params) {
|
|
|
16439
16449
|
const isManager = topicRecord?.kind === "manager";
|
|
16440
16450
|
const isMentionOnlyChannel = topic.aiMode === "mention" && !isManager;
|
|
16441
16451
|
let systemPrompt = isManager ? buildManagerSystemPrompt(systemPromptOpts) : isMentionOnlyChannel ? buildChannelSystemPrompt(systemPromptOpts) : buildTopicSystemPrompt(systemPromptOpts);
|
|
16442
|
-
const memoryTopic = isMentionOnlyChannel || isManager ? topic : getTopicMemoryOrigin(topicId) ?? topic;
|
|
16452
|
+
const memoryTopic = isMentionOnlyChannel || isManager || topic.memoryKey ? topic : getTopicMemoryOrigin(topicId) ?? topic;
|
|
16453
|
+
const memoryTitle = memoryTopic.memoryKey?.trim() || memoryTopic.title;
|
|
16443
16454
|
if (!isMentionOnlyChannel) {
|
|
16455
|
+
let hasMemoryArchive = false;
|
|
16444
16456
|
try {
|
|
16445
|
-
const resolvedBrief = resolveTopicBrief(memoryTopic.id,
|
|
16457
|
+
const resolvedBrief = resolveTopicBrief(memoryTopic.id, memoryTitle);
|
|
16446
16458
|
const wikiDir = getSharedWikiDir();
|
|
16447
|
-
const {
|
|
16448
|
-
|
|
16449
|
-
systemPrompt += buildMemoryPromptSection({
|
|
16450
|
-
briefFile,
|
|
16451
|
-
wikiDir,
|
|
16452
|
-
hasFiles: hasBriefFile,
|
|
16453
|
-
latestSummaryFile,
|
|
16454
|
-
hasArchive: Boolean(resolvedBrief?.brief.latestSummaryMd || latestSummaryFile),
|
|
16455
|
-
isManager
|
|
16456
|
-
});
|
|
16457
|
-
}
|
|
16459
|
+
const { latestSummaryFile } = resolveWikiMemoryMirror(wikiDir, memoryTopic.id, memoryTitle);
|
|
16460
|
+
hasMemoryArchive = Boolean(resolvedBrief?.brief.latestSummaryMd || latestSummaryFile);
|
|
16458
16461
|
} catch (err) {
|
|
16459
16462
|
logger.warn({ topicId, err }, "ai: failed to inject topic brief");
|
|
16460
16463
|
}
|
|
16464
|
+
systemPrompt += buildMemoryPromptSection({
|
|
16465
|
+
topicTitle: topic.title,
|
|
16466
|
+
...memoryTopic.memoryKey ? { memoryKey: memoryTopic.memoryKey } : {},
|
|
16467
|
+
hasArchive: hasMemoryArchive,
|
|
16468
|
+
isManager
|
|
16469
|
+
});
|
|
16461
16470
|
}
|
|
16462
16471
|
const activeVisual = visualTools ? getActiveVisualForPrompt(topicId, userId) : null;
|
|
16463
16472
|
if (activeVisual) {
|
|
@@ -18197,7 +18206,7 @@ import {
|
|
|
18197
18206
|
unlinkSync,
|
|
18198
18207
|
writeFileSync as writeFileSync2
|
|
18199
18208
|
} from "fs";
|
|
18200
|
-
import { basename, dirname as dirname2, join as join2, resolve as resolve2 } from "path";
|
|
18209
|
+
import { basename, dirname as dirname2, join as join2, relative, resolve as resolve2 } from "path";
|
|
18201
18210
|
init_config();
|
|
18202
18211
|
init_wiki_summary_names();
|
|
18203
18212
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -18331,9 +18340,314 @@ function topicNameFrom(topicIdStr) {
|
|
|
18331
18340
|
function slugify(topic) {
|
|
18332
18341
|
return wikiSummarySlug(topic);
|
|
18333
18342
|
}
|
|
18343
|
+
function normalizeSearchText(value2) {
|
|
18344
|
+
return value2.toLowerCase().replaceAll(/[^a-z0-9\uAC00-\uD7A3]+/g, " ").trim().replaceAll(/\s+/g, " ");
|
|
18345
|
+
}
|
|
18346
|
+
function compactSearchText(value2) {
|
|
18347
|
+
return normalizeSearchText(value2).replaceAll(" ", "");
|
|
18348
|
+
}
|
|
18349
|
+
function trigrams(value2) {
|
|
18350
|
+
const compact = compactSearchText(value2);
|
|
18351
|
+
if (compact.length < 3)
|
|
18352
|
+
return new Set(compact ? [compact] : []);
|
|
18353
|
+
const result = new Set;
|
|
18354
|
+
for (let i = 0;i <= compact.length - 3; i += 1)
|
|
18355
|
+
result.add(compact.slice(i, i + 3));
|
|
18356
|
+
return result;
|
|
18357
|
+
}
|
|
18358
|
+
function fuzzySearchScore(query, candidate, maximum) {
|
|
18359
|
+
const compactQuery = compactSearchText(query);
|
|
18360
|
+
const compactCandidate = compactSearchText(candidate);
|
|
18361
|
+
if (compactQuery.length < 3 || compactCandidate.length < 3)
|
|
18362
|
+
return 0;
|
|
18363
|
+
const similarity = trigramSimilarity(compactQuery, compactCandidate);
|
|
18364
|
+
const minimumSimilarity = compactQuery.length <= 4 ? 0.6 : 0.45;
|
|
18365
|
+
return similarity >= minimumSimilarity ? similarity * maximum : 0;
|
|
18366
|
+
}
|
|
18367
|
+
var SEARCH_STOP_WORDS = new Set([
|
|
18368
|
+
"a",
|
|
18369
|
+
"an",
|
|
18370
|
+
"and",
|
|
18371
|
+
"are",
|
|
18372
|
+
"as",
|
|
18373
|
+
"at",
|
|
18374
|
+
"be",
|
|
18375
|
+
"by",
|
|
18376
|
+
"for",
|
|
18377
|
+
"from",
|
|
18378
|
+
"in",
|
|
18379
|
+
"is",
|
|
18380
|
+
"it",
|
|
18381
|
+
"of",
|
|
18382
|
+
"on",
|
|
18383
|
+
"or",
|
|
18384
|
+
"that",
|
|
18385
|
+
"the",
|
|
18386
|
+
"to",
|
|
18387
|
+
"with"
|
|
18388
|
+
]);
|
|
18389
|
+
var SEARCH_MONTHS = new Map([
|
|
18390
|
+
["january", "01"],
|
|
18391
|
+
["jan", "01"],
|
|
18392
|
+
["1\uC6D4", "01"],
|
|
18393
|
+
["february", "02"],
|
|
18394
|
+
["feb", "02"],
|
|
18395
|
+
["2\uC6D4", "02"],
|
|
18396
|
+
["march", "03"],
|
|
18397
|
+
["mar", "03"],
|
|
18398
|
+
["3\uC6D4", "03"],
|
|
18399
|
+
["april", "04"],
|
|
18400
|
+
["apr", "04"],
|
|
18401
|
+
["4\uC6D4", "04"],
|
|
18402
|
+
["may", "05"],
|
|
18403
|
+
["5\uC6D4", "05"],
|
|
18404
|
+
["june", "06"],
|
|
18405
|
+
["jun", "06"],
|
|
18406
|
+
["6\uC6D4", "06"],
|
|
18407
|
+
["july", "07"],
|
|
18408
|
+
["jul", "07"],
|
|
18409
|
+
["7\uC6D4", "07"],
|
|
18410
|
+
["august", "08"],
|
|
18411
|
+
["aug", "08"],
|
|
18412
|
+
["8\uC6D4", "08"],
|
|
18413
|
+
["september", "09"],
|
|
18414
|
+
["sep", "09"],
|
|
18415
|
+
["sept", "09"],
|
|
18416
|
+
["9\uC6D4", "09"],
|
|
18417
|
+
["october", "10"],
|
|
18418
|
+
["oct", "10"],
|
|
18419
|
+
["10\uC6D4", "10"],
|
|
18420
|
+
["november", "11"],
|
|
18421
|
+
["nov", "11"],
|
|
18422
|
+
["11\uC6D4", "11"],
|
|
18423
|
+
["december", "12"],
|
|
18424
|
+
["dec", "12"],
|
|
18425
|
+
["12\uC6D4", "12"]
|
|
18426
|
+
]);
|
|
18427
|
+
var SEARCH_RECENCY_TERMS = new Set([
|
|
18428
|
+
"latest",
|
|
18429
|
+
"newest",
|
|
18430
|
+
"recent",
|
|
18431
|
+
"earliest",
|
|
18432
|
+
"oldest",
|
|
18433
|
+
"\uCD5C\uADFC",
|
|
18434
|
+
"\uCD5C\uC2E0",
|
|
18435
|
+
"\uCD5C\uCD08",
|
|
18436
|
+
"\uC624\uB798\uB41C"
|
|
18437
|
+
]);
|
|
18438
|
+
function meaningfulSearchTokens(value2) {
|
|
18439
|
+
return normalizeSearchText(value2).split(" ").filter((token) => (token.length >= 2 || /^\d+$/.test(token)) && !SEARCH_STOP_WORDS.has(token));
|
|
18440
|
+
}
|
|
18441
|
+
function canonicalSearchToken(token) {
|
|
18442
|
+
return token.length > 4 && token.endsWith("s") ? token.slice(0, -1) : token;
|
|
18443
|
+
}
|
|
18444
|
+
function canonicalSearchTokens(value2) {
|
|
18445
|
+
return meaningfulSearchTokens(value2).map(canonicalSearchToken);
|
|
18446
|
+
}
|
|
18447
|
+
function countToken(tokens, token) {
|
|
18448
|
+
let count = 0;
|
|
18449
|
+
for (const candidate of tokens)
|
|
18450
|
+
if (candidate === token)
|
|
18451
|
+
count += 1;
|
|
18452
|
+
return count;
|
|
18453
|
+
}
|
|
18454
|
+
function countPhrase(text, phrase) {
|
|
18455
|
+
if (!phrase)
|
|
18456
|
+
return 0;
|
|
18457
|
+
let count = 0;
|
|
18458
|
+
let offset = 0;
|
|
18459
|
+
let match = text.indexOf(phrase, offset);
|
|
18460
|
+
while (match >= 0) {
|
|
18461
|
+
count += 1;
|
|
18462
|
+
offset = match + phrase.length;
|
|
18463
|
+
match = text.indexOf(phrase, offset);
|
|
18464
|
+
}
|
|
18465
|
+
return count;
|
|
18466
|
+
}
|
|
18467
|
+
function summaryTopicIdentity(key) {
|
|
18468
|
+
return normalizeSearchText(key.replace(/^\d{4}-\d{2}-\d{2}-/, ""));
|
|
18469
|
+
}
|
|
18470
|
+
function scoreSummaryTime(query, key, title) {
|
|
18471
|
+
const date = key.match(/^(\d{4})-(\d{2})-(\d{2})/);
|
|
18472
|
+
if (!date)
|
|
18473
|
+
return 0;
|
|
18474
|
+
const tokens = meaningfulSearchTokens(query);
|
|
18475
|
+
const documentDate = `${date[1]}-${date[2]}-${date[3]}`;
|
|
18476
|
+
const requestedDateMatch = query.match(/\b(\d{4}) (\d{2}) (\d{2})\b/);
|
|
18477
|
+
const requestedDate = requestedDateMatch ? `${requestedDateMatch[1]}-${requestedDateMatch[2]}-${requestedDateMatch[3]}` : undefined;
|
|
18478
|
+
const beforeDate = /\bbefore\b/.test(query);
|
|
18479
|
+
const afterDate = /\bafter\b/.test(query);
|
|
18480
|
+
if (requestedDate && beforeDate && documentDate >= requestedDate)
|
|
18481
|
+
return null;
|
|
18482
|
+
if (requestedDate && afterDate && documentDate <= requestedDate)
|
|
18483
|
+
return null;
|
|
18484
|
+
const queryYear = tokens.find((token) => /^\d{4}(?:\uB144)?$/.test(token))?.replace(/\uB144$/, "");
|
|
18485
|
+
const queryMonth = tokens.map((token) => SEARCH_MONTHS.get(token)).find(Boolean);
|
|
18486
|
+
const hasRecencyTerm = tokens.some((token) => SEARCH_RECENCY_TERMS.has(token));
|
|
18487
|
+
const yearMatches = !queryYear || queryYear === date[1];
|
|
18488
|
+
const monthMatches = !queryMonth || queryMonth === date[2];
|
|
18489
|
+
if (!queryYear && !queryMonth && !hasRecencyTerm || !yearMatches || !monthMatches)
|
|
18490
|
+
return 0;
|
|
18491
|
+
const normalizedMetadata = `${normalizeSearchText(key)} ${normalizeSearchText(title)}`;
|
|
18492
|
+
const contentTokens = tokens.filter((token) => !/^\d{4}(?:\uB144)?$/.test(token) && !SEARCH_MONTHS.has(token) && !SEARCH_RECENCY_TERMS.has(token));
|
|
18493
|
+
const contentMatches = contentTokens.filter((token) => normalizedMetadata.includes(token)).length;
|
|
18494
|
+
const relationScore = requestedDate && (beforeDate || afterDate) ? 60 : 0;
|
|
18495
|
+
const exactDateScore = requestedDate === documentDate ? 90 : 0;
|
|
18496
|
+
return 30 + relationScore + exactDateScore + contentMatches * 15;
|
|
18497
|
+
}
|
|
18498
|
+
function trigramSimilarity(left, right) {
|
|
18499
|
+
const a = trigrams(left);
|
|
18500
|
+
const b = trigrams(right);
|
|
18501
|
+
if (a.size === 0 || b.size === 0)
|
|
18502
|
+
return 0;
|
|
18503
|
+
let overlap = 0;
|
|
18504
|
+
for (const gram of a)
|
|
18505
|
+
if (b.has(gram))
|
|
18506
|
+
overlap += 1;
|
|
18507
|
+
return 2 * overlap / (a.size + b.size);
|
|
18508
|
+
}
|
|
18509
|
+
function parseWikiIndex(path) {
|
|
18510
|
+
let text;
|
|
18511
|
+
try {
|
|
18512
|
+
text = readFileSync2(path, "utf-8");
|
|
18513
|
+
} catch {
|
|
18514
|
+
return [];
|
|
18515
|
+
}
|
|
18516
|
+
const candidates = [];
|
|
18517
|
+
const entryPattern = /^\s*-\s*\[\[(topic|articles|summaries)\/([^\]]+)\]\]\s*(.*)$/;
|
|
18518
|
+
for (const line of text.split(`
|
|
18519
|
+
`)) {
|
|
18520
|
+
const match = line.match(entryPattern);
|
|
18521
|
+
if (!match)
|
|
18522
|
+
continue;
|
|
18523
|
+
const namespace = match[1];
|
|
18524
|
+
const key = match[2].replace(/\.md$/i, "");
|
|
18525
|
+
const tail = match[3].trim();
|
|
18526
|
+
const dateMatch = tail.match(/\((\d{4}-\d{2}-\d{2})(?:[^)]*)\)\s*$/);
|
|
18527
|
+
const description = dateMatch ? tail.slice(0, dateMatch.index).trim() : tail;
|
|
18528
|
+
candidates.push({
|
|
18529
|
+
kind: namespace === "topic" ? "topic" : namespace === "articles" ? "article" : "summary",
|
|
18530
|
+
key,
|
|
18531
|
+
description,
|
|
18532
|
+
...dateMatch ? { date: dateMatch[1] } : {}
|
|
18533
|
+
});
|
|
18534
|
+
}
|
|
18535
|
+
return candidates;
|
|
18536
|
+
}
|
|
18537
|
+
function indexCandidateExists(candidate) {
|
|
18538
|
+
if (candidate.kind === "topic")
|
|
18539
|
+
return true;
|
|
18540
|
+
const root = candidate.kind === "article" ? runtime().articlesDir : runtime().summariesDir;
|
|
18541
|
+
const filePath = resolve2(root, `${candidate.key}.md`);
|
|
18542
|
+
const relativePath = relative(root, filePath);
|
|
18543
|
+
if (relativePath.startsWith("..") || relativePath.startsWith("/"))
|
|
18544
|
+
return false;
|
|
18545
|
+
try {
|
|
18546
|
+
return statSync(filePath).isFile();
|
|
18547
|
+
} catch {
|
|
18548
|
+
return false;
|
|
18549
|
+
}
|
|
18550
|
+
}
|
|
18551
|
+
function scoreIndexKey(query, key) {
|
|
18552
|
+
const normalizedQuery = normalizeSearchText(query);
|
|
18553
|
+
const compactQuery = compactSearchText(normalizedQuery);
|
|
18554
|
+
const normalizedKey = normalizeSearchText(key);
|
|
18555
|
+
const compactKey = compactSearchText(normalizedKey);
|
|
18556
|
+
const tokens = meaningfulSearchTokens(normalizedQuery);
|
|
18557
|
+
let score = 0;
|
|
18558
|
+
if (normalizedKey === normalizedQuery)
|
|
18559
|
+
score += 120;
|
|
18560
|
+
else if (compactKey === compactQuery)
|
|
18561
|
+
score += 110;
|
|
18562
|
+
else {
|
|
18563
|
+
if (compactQuery.length >= 2 && (compactKey.startsWith(compactQuery) || compactQuery.startsWith(compactKey))) {
|
|
18564
|
+
score += 65;
|
|
18565
|
+
}
|
|
18566
|
+
if (tokens.length > 0 && compactQuery.length >= 3 && compactKey.includes(compactQuery)) {
|
|
18567
|
+
score += 45;
|
|
18568
|
+
}
|
|
18569
|
+
score += fuzzySearchScore(compactQuery, compactKey, 55);
|
|
18570
|
+
}
|
|
18571
|
+
for (const token of tokens) {
|
|
18572
|
+
if (normalizedKey.includes(token))
|
|
18573
|
+
score += 10;
|
|
18574
|
+
}
|
|
18575
|
+
return score;
|
|
18576
|
+
}
|
|
18577
|
+
function scoreIndexCandidate(query, candidate) {
|
|
18578
|
+
const normalizedQuery = normalizeSearchText(query);
|
|
18579
|
+
if (!normalizedQuery)
|
|
18580
|
+
return 0;
|
|
18581
|
+
const normalizedDescription = normalizeSearchText(candidate.description);
|
|
18582
|
+
const tokens = canonicalSearchTokens(normalizedQuery);
|
|
18583
|
+
const descriptionTokens = new Set(canonicalSearchTokens(normalizedDescription));
|
|
18584
|
+
let score = scoreIndexKey(normalizedQuery, candidate.key);
|
|
18585
|
+
if (tokens.length > 0 && normalizedDescription.includes(normalizedQuery))
|
|
18586
|
+
score += 30;
|
|
18587
|
+
let descriptionMatches = 0;
|
|
18588
|
+
for (const token of tokens) {
|
|
18589
|
+
if (descriptionTokens.has(token)) {
|
|
18590
|
+
score += 5;
|
|
18591
|
+
descriptionMatches += 1;
|
|
18592
|
+
}
|
|
18593
|
+
}
|
|
18594
|
+
if (tokens.length > 1 && descriptionMatches === tokens.length)
|
|
18595
|
+
score += 25;
|
|
18596
|
+
if (candidate.kind === "topic") {
|
|
18597
|
+
const requestsCurrent = /\b(?:current|active|ongoing|open)\b|\uD604\uC7AC|\uC9C4\uD589 \uC911/.test(normalizedQuery);
|
|
18598
|
+
const archivedCandidate = /\b(?:archive|archived|historical|closed)\b|\uBCF4\uAD00|\uC885\uB8CC/.test(`${normalizeSearchText(candidate.key)} ${normalizedDescription}`);
|
|
18599
|
+
if (requestsCurrent && archivedCandidate)
|
|
18600
|
+
score -= 40;
|
|
18601
|
+
}
|
|
18602
|
+
return score;
|
|
18603
|
+
}
|
|
18334
18604
|
function wikiQuery(args) {
|
|
18335
|
-
const question = safeExt(args, ["question", "query", "q", "text"], "");
|
|
18336
|
-
const
|
|
18605
|
+
const question = String(safeExt(args, ["question", "query", "q", "text"], "")).trim();
|
|
18606
|
+
const requestedKind = String(safeExt(args, ["kind", "type"], "all"));
|
|
18607
|
+
const kind = requestedKind === "topic" || requestedKind === "article" || requestedKind === "summary" ? requestedKind : "all";
|
|
18608
|
+
const requestedLimit = Number(safeExt(args, ["limit", "maxResults"], 8));
|
|
18609
|
+
const limit = Math.min(Math.max(Number.isFinite(requestedLimit) ? requestedLimit : 8, 1), 20);
|
|
18610
|
+
if (!question) {
|
|
18611
|
+
return {
|
|
18612
|
+
content: [{ type: "text", text: "A non-empty wiki query is required." }],
|
|
18613
|
+
isError: true
|
|
18614
|
+
};
|
|
18615
|
+
}
|
|
18616
|
+
const normalizedQuery = normalizeSearchText(question);
|
|
18617
|
+
if (!normalizedQuery) {
|
|
18618
|
+
return { content: [{ type: "text", text: "No matching wiki articles found." }] };
|
|
18619
|
+
}
|
|
18620
|
+
const canReadTopicMemory = runtime().host.canReadTopicMemory;
|
|
18621
|
+
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;
|
|
18622
|
+
const visibleIndexCandidates = [
|
|
18623
|
+
...parseWikiIndex(resolve2(runtime().wikiDir, "topic-index.md")),
|
|
18624
|
+
...parseWikiIndex(resolve2(runtime().wikiDir, "article-index.md"))
|
|
18625
|
+
].filter((candidate) => kind === "all" || candidate.kind === kind).filter((candidate) => candidate.kind !== "topic" || !canReadTopicMemory || canReadTopicMemory(candidate.key, runtime().userId));
|
|
18626
|
+
const hasStaleExactIndexMatch = visibleIndexCandidates.some((candidate) => !indexCandidateExists(candidate) && scoreIndexKey(question, candidate.key) >= 100);
|
|
18627
|
+
const indexCandidates = visibleIndexCandidates.filter(indexCandidateExists).map((candidate) => ({
|
|
18628
|
+
candidate,
|
|
18629
|
+
keyScore: scoreIndexKey(question, candidate.key),
|
|
18630
|
+
score: scoreIndexCandidate(question, candidate)
|
|
18631
|
+
})).filter(({ keyScore, score }) => keyScore >= 25 || score >= 40).sort((left, right) => right.score - left.score || left.candidate.key.localeCompare(right.candidate.key));
|
|
18632
|
+
const strongestIndex = indexCandidates[0];
|
|
18633
|
+
if (kind === "topic" && strongestIndex && (strongestIndex.keyScore >= 25 || strongestIndex.score >= 40)) {
|
|
18634
|
+
const runnerUp = indexCandidates[1];
|
|
18635
|
+
const ambiguous = strongestIndex.keyScore < 100 && runnerUp !== undefined && runnerUp.score >= strongestIndex.score * 0.9;
|
|
18636
|
+
if (ambiguous) {
|
|
18637
|
+
const lines2 = ["Found 2 ambiguous wiki candidates:", ""];
|
|
18638
|
+
for (const { candidate, score } of [strongestIndex, runnerUp]) {
|
|
18639
|
+
lines2.push(`- kind: ${candidate.kind}`, ` key: ${candidate.key}`, ` score: ${score.toFixed(2)}`, ` description: ${candidate.description || "(none)"}`, ...candidate.date ? [` date: ${candidate.date}`] : []);
|
|
18640
|
+
}
|
|
18641
|
+
return { content: [{ type: "text", text: lines2.join(`
|
|
18642
|
+
`) }] };
|
|
18643
|
+
}
|
|
18644
|
+
const lines = ["Found 1 wiki candidate:", ""];
|
|
18645
|
+
for (const { candidate, score } of [strongestIndex]) {
|
|
18646
|
+
lines.push(`- kind: ${candidate.kind}`, ` key: ${candidate.key}`, ` score: ${score.toFixed(2)}`, ` description: ${candidate.description || "(none)"}`, ...candidate.date ? [` date: ${candidate.date}`] : []);
|
|
18647
|
+
}
|
|
18648
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
18649
|
+
`) }] };
|
|
18650
|
+
}
|
|
18337
18651
|
const results = [];
|
|
18338
18652
|
const scored = [];
|
|
18339
18653
|
function scan(dir, label) {
|
|
@@ -18350,67 +18664,234 @@ function wikiQuery(args) {
|
|
|
18350
18664
|
const fp = join2(sub, f.name);
|
|
18351
18665
|
try {
|
|
18352
18666
|
const text = readFileSync2(fp, "utf-8");
|
|
18667
|
+
const key = `${entry.name}/${f.name.replace(/\.md$/i, "")}`;
|
|
18353
18668
|
scored.push({
|
|
18354
|
-
|
|
18669
|
+
kind: label === "articles" ? "article" : "skill",
|
|
18670
|
+
key,
|
|
18671
|
+
keyScore: scoreIndexKey(question, key),
|
|
18672
|
+
score: scoreMatch(text, key, label === "articles" ? "article" : "skill"),
|
|
18355
18673
|
path: `${label}/${entry.name}/${f.name}`,
|
|
18356
|
-
text
|
|
18674
|
+
text,
|
|
18675
|
+
title: titleFromDocument(text, key)
|
|
18357
18676
|
});
|
|
18358
18677
|
} catch {}
|
|
18359
18678
|
}
|
|
18360
18679
|
} catch {}
|
|
18361
18680
|
}
|
|
18362
18681
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
18682
|
+
if (label === "topic" && canReadTopicMemory && !canReadTopicMemory(entry.name.replace(/\.md$/i, ""), runtime().userId)) {
|
|
18683
|
+
continue;
|
|
18684
|
+
}
|
|
18363
18685
|
const fp = join2(dir, entry.name);
|
|
18364
18686
|
try {
|
|
18365
18687
|
const text = readFileSync2(fp, "utf-8");
|
|
18688
|
+
const key = entry.name.replace(/\.md$/i, "");
|
|
18689
|
+
const resultKind = label === "articles" ? "article" : label === "topic" ? "topic" : label === "summaries" ? "summary" : "skill";
|
|
18366
18690
|
scored.push({
|
|
18367
|
-
|
|
18691
|
+
kind: resultKind,
|
|
18692
|
+
key,
|
|
18693
|
+
keyScore: scoreIndexKey(question, key),
|
|
18694
|
+
score: scoreMatch(text, key, resultKind),
|
|
18368
18695
|
path: `${label}/${entry.name}`,
|
|
18369
|
-
text
|
|
18696
|
+
text,
|
|
18697
|
+
title: titleFromDocument(text, key),
|
|
18698
|
+
...label === "summaries" && /^\d{4}-\d{2}-\d{2}/.test(key) ? { date: key.slice(0, 10) } : {}
|
|
18370
18699
|
});
|
|
18371
18700
|
} catch {}
|
|
18372
18701
|
}
|
|
18373
18702
|
}
|
|
18374
18703
|
} catch {}
|
|
18375
18704
|
}
|
|
18376
|
-
function
|
|
18377
|
-
const
|
|
18705
|
+
function titleFromDocument(text, key) {
|
|
18706
|
+
const titleLine = text.split(`
|
|
18707
|
+
`).find((line) => /^#+\s*/.test(line));
|
|
18708
|
+
return titleLine ? titleLine.replace(/^#+\s*/, "") : basename(key);
|
|
18709
|
+
}
|
|
18710
|
+
function scoreMatch(text, key, resultKind) {
|
|
18711
|
+
const titleLine = text.split(`
|
|
18712
|
+
`).find((line) => /^#+\s*/.test(line)) ?? "";
|
|
18713
|
+
const title = titleLine.replace(/^#+\s*/, "");
|
|
18714
|
+
const normalizedKey = normalizeSearchText(key);
|
|
18715
|
+
const normalizedTitle = normalizeSearchText(title);
|
|
18716
|
+
const normalizedBody = normalizeSearchText(text.replace(titleLine, ""));
|
|
18717
|
+
const compactQuery = compactSearchText(normalizedQuery);
|
|
18718
|
+
const compactKey = compactSearchText(normalizedKey);
|
|
18719
|
+
const compactTitle = compactSearchText(normalizedTitle);
|
|
18720
|
+
const tokens = canonicalSearchTokens(normalizedQuery);
|
|
18721
|
+
const keyTokens = new Set(canonicalSearchTokens(normalizedKey));
|
|
18722
|
+
const titleTokens = new Set(canonicalSearchTokens(normalizedTitle));
|
|
18723
|
+
const bodyTokens = canonicalSearchTokens(normalizedBody);
|
|
18378
18724
|
let score = 0;
|
|
18379
|
-
|
|
18380
|
-
|
|
18381
|
-
|
|
18382
|
-
|
|
18383
|
-
|
|
18384
|
-
|
|
18385
|
-
|
|
18386
|
-
|
|
18725
|
+
let strongMetadataEvidence = false;
|
|
18726
|
+
if (normalizedKey === normalizedQuery) {
|
|
18727
|
+
score += 120;
|
|
18728
|
+
strongMetadataEvidence = true;
|
|
18729
|
+
} else if (compactKey === compactQuery) {
|
|
18730
|
+
score += 110;
|
|
18731
|
+
strongMetadataEvidence = true;
|
|
18732
|
+
} else if (compactQuery.length >= 2 && (compactKey.startsWith(compactQuery) || compactQuery.startsWith(compactKey))) {
|
|
18733
|
+
score += 65;
|
|
18734
|
+
strongMetadataEvidence = true;
|
|
18735
|
+
}
|
|
18736
|
+
if (tokens.length > 0 && compactQuery.length >= 3 && compactKey.includes(compactQuery)) {
|
|
18737
|
+
score += 45;
|
|
18738
|
+
strongMetadataEvidence = true;
|
|
18739
|
+
}
|
|
18740
|
+
const fuzzyKeyScore = fuzzySearchScore(compactQuery, compactKey, 55);
|
|
18741
|
+
score += fuzzyKeyScore;
|
|
18742
|
+
if (fuzzyKeyScore > 0)
|
|
18743
|
+
strongMetadataEvidence = true;
|
|
18744
|
+
if (normalizedTitle === normalizedQuery) {
|
|
18745
|
+
score += 115;
|
|
18746
|
+
strongMetadataEvidence = true;
|
|
18747
|
+
} else if (compactTitle === compactQuery) {
|
|
18748
|
+
score += 105;
|
|
18749
|
+
strongMetadataEvidence = true;
|
|
18750
|
+
} else if (compactQuery.length >= 2 && compactTitle.startsWith(compactQuery)) {
|
|
18751
|
+
score += 60;
|
|
18752
|
+
strongMetadataEvidence = true;
|
|
18753
|
+
}
|
|
18754
|
+
const fuzzyTitleScore = fuzzySearchScore(compactQuery, compactTitle, 50);
|
|
18755
|
+
score += fuzzyTitleScore;
|
|
18756
|
+
if (fuzzyTitleScore > 0)
|
|
18757
|
+
strongMetadataEvidence = true;
|
|
18758
|
+
if (resultKind === "summary") {
|
|
18759
|
+
const temporalScore = scoreSummaryTime(normalizedQuery, key, title);
|
|
18760
|
+
if (temporalScore === null)
|
|
18761
|
+
return 0;
|
|
18762
|
+
score += temporalScore;
|
|
18763
|
+
if (temporalScore > 0)
|
|
18764
|
+
strongMetadataEvidence = true;
|
|
18765
|
+
}
|
|
18766
|
+
const isMultiToken = tokens.length > 1;
|
|
18767
|
+
if (tokens.length > 0 && normalizedTitle.includes(normalizedQuery)) {
|
|
18768
|
+
score += 35;
|
|
18769
|
+
strongMetadataEvidence = true;
|
|
18770
|
+
}
|
|
18771
|
+
const phraseMatches = isMultiToken ? countPhrase(normalizedBody, normalizedQuery) : 0;
|
|
18772
|
+
if (phraseMatches > 0)
|
|
18773
|
+
score += phraseMatches * 12;
|
|
18774
|
+
else if (tokens.length === 1 && tokens[0].length >= 5 && normalizedBody.includes(tokens[0])) {
|
|
18775
|
+
score += 16;
|
|
18776
|
+
}
|
|
18777
|
+
let bodyMatches = 0;
|
|
18778
|
+
let minimumBodyFrequency = Number.POSITIVE_INFINITY;
|
|
18779
|
+
for (const token of tokens) {
|
|
18780
|
+
if (keyTokens.has(token))
|
|
18781
|
+
score += 10;
|
|
18782
|
+
if (titleTokens.has(token))
|
|
18783
|
+
score += 12;
|
|
18784
|
+
const bodyFrequency = countToken(bodyTokens, token);
|
|
18785
|
+
if (bodyFrequency > 0) {
|
|
18786
|
+
score += Math.min(bodyFrequency, 3) * 4;
|
|
18787
|
+
bodyMatches += 1;
|
|
18788
|
+
minimumBodyFrequency = Math.min(minimumBodyFrequency, bodyFrequency);
|
|
18387
18789
|
}
|
|
18388
18790
|
}
|
|
18791
|
+
if (isMultiToken && bodyMatches === tokens.length) {
|
|
18792
|
+
score += 8 * Math.min(minimumBodyFrequency, 3);
|
|
18793
|
+
}
|
|
18794
|
+
const minimumBodyTokens = hasStaleExactIndexMatch ? 3 : 2;
|
|
18795
|
+
const partialArticleEvidence = resultKind === "article" && bodyMatches >= 3 && bodyMatches / tokens.length >= 0.6;
|
|
18796
|
+
const strongBodyEvidence = tokens.length >= minimumBodyTokens && (bodyMatches === tokens.length && (phraseMatches > 0 || minimumBodyFrequency >= 1) || partialArticleEvidence);
|
|
18797
|
+
if (!strongMetadataEvidence && !strongBodyEvidence)
|
|
18798
|
+
return 0;
|
|
18389
18799
|
return score;
|
|
18390
18800
|
}
|
|
18391
|
-
|
|
18392
|
-
|
|
18801
|
+
if (kind === "all" || kind === "article")
|
|
18802
|
+
scan(runtime().articlesDir, "articles");
|
|
18803
|
+
if (kind === "all" && runtime().surface === "all")
|
|
18393
18804
|
scan(runtime().skillsDir, "skills");
|
|
18394
|
-
|
|
18395
|
-
|
|
18396
|
-
|
|
18397
|
-
|
|
18398
|
-
|
|
18805
|
+
if (kind === "all" || kind === "topic")
|
|
18806
|
+
scan(runtime().topicsDir, "topic");
|
|
18807
|
+
if (kind === "all" || kind === "summary")
|
|
18808
|
+
scan(runtime().summariesDir, "summaries");
|
|
18809
|
+
const merged = new Map;
|
|
18810
|
+
for (const { candidate, keyScore, score } of indexCandidates) {
|
|
18811
|
+
merged.set(`${candidate.kind}:${candidate.key}`, {
|
|
18812
|
+
kind: candidate.kind,
|
|
18813
|
+
key: candidate.key,
|
|
18814
|
+
keyScore,
|
|
18815
|
+
score,
|
|
18816
|
+
description: candidate.description,
|
|
18817
|
+
date: candidate.date
|
|
18818
|
+
});
|
|
18819
|
+
}
|
|
18820
|
+
const documentThreshold = kind === "topic" ? 24 : 16;
|
|
18821
|
+
for (const document of scored) {
|
|
18822
|
+
if (document.score < documentThreshold)
|
|
18823
|
+
continue;
|
|
18824
|
+
const id = `${document.kind}:${document.key}`;
|
|
18825
|
+
const indexed = merged.get(id);
|
|
18826
|
+
merged.set(id, indexed ? { ...indexed, ...document, score: Math.max(indexed.score, document.score) } : document);
|
|
18827
|
+
}
|
|
18828
|
+
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));
|
|
18829
|
+
if (kind === "summary") {
|
|
18830
|
+
const sameTopic = ranked.filter((result) => {
|
|
18831
|
+
const identity = summaryTopicIdentity(result.key);
|
|
18832
|
+
return identity.length >= 3 && normalizedQuery.includes(identity);
|
|
18833
|
+
});
|
|
18834
|
+
if (sameTopic.length > 0)
|
|
18835
|
+
ranked = sameTopic;
|
|
18836
|
+
const requestedDateMatch = normalizedQuery.match(/\b(\d{4}) (\d{2}) (\d{2})\b/);
|
|
18837
|
+
const requestedDate = requestedDateMatch ? `${requestedDateMatch[1]}-${requestedDateMatch[2]}-${requestedDateMatch[3]}` : undefined;
|
|
18838
|
+
const beforeDate = /\bbefore\b/.test(normalizedQuery);
|
|
18839
|
+
const afterDate = /\bafter\b/.test(normalizedQuery);
|
|
18840
|
+
if (requestedDate && beforeDate) {
|
|
18841
|
+
ranked = ranked.filter((result) => !result.date || result.date < requestedDate);
|
|
18842
|
+
} else if (requestedDate && afterDate) {
|
|
18843
|
+
ranked = ranked.filter((result) => !result.date || result.date > requestedDate);
|
|
18844
|
+
}
|
|
18845
|
+
ranked.sort((left, right) => {
|
|
18846
|
+
if (beforeDate) {
|
|
18847
|
+
return (right.date ?? "").localeCompare(left.date ?? "") || right.score - left.score;
|
|
18848
|
+
}
|
|
18849
|
+
if (afterDate) {
|
|
18850
|
+
return (left.date ?? "").localeCompare(right.date ?? "") || right.score - left.score;
|
|
18851
|
+
}
|
|
18852
|
+
if (requestedDate) {
|
|
18853
|
+
const dateDifference = Number(right.date === requestedDate) - Number(left.date === requestedDate);
|
|
18854
|
+
if (dateDifference !== 0)
|
|
18855
|
+
return dateDifference;
|
|
18856
|
+
}
|
|
18857
|
+
if (temporalDirection) {
|
|
18858
|
+
const dateDifference = temporalDirection === "oldest" ? (left.date ?? "").localeCompare(right.date ?? "") : (right.date ?? "").localeCompare(left.date ?? "");
|
|
18859
|
+
if (dateDifference !== 0)
|
|
18860
|
+
return dateDifference;
|
|
18861
|
+
}
|
|
18862
|
+
return right.score - left.score || (right.date ?? "").localeCompare(left.date ?? "") || left.key.localeCompare(right.key);
|
|
18863
|
+
});
|
|
18864
|
+
}
|
|
18865
|
+
if (kind === "topic" && ranked[0]) {
|
|
18866
|
+
const runnerUp = ranked[1];
|
|
18867
|
+
const ambiguous = ranked[0].keyScore < 100 && runnerUp !== undefined && runnerUp.score >= ranked[0].score * 0.9;
|
|
18868
|
+
if (ambiguous) {
|
|
18869
|
+
ranked = ranked.slice(0, 2);
|
|
18870
|
+
} else {
|
|
18871
|
+
ranked = ranked.slice(0, 1);
|
|
18872
|
+
}
|
|
18873
|
+
} else {
|
|
18874
|
+
ranked = ranked.slice(0, limit);
|
|
18875
|
+
}
|
|
18876
|
+
if (ranked.length === 0) {
|
|
18399
18877
|
results.push("No matching wiki articles found.");
|
|
18400
18878
|
} else {
|
|
18401
|
-
results.push(`Found ${
|
|
18879
|
+
results.push(`Found ${ranked.length} matching result(s):
|
|
18402
18880
|
`);
|
|
18403
|
-
for (const
|
|
18404
|
-
|
|
18405
|
-
|
|
18406
|
-
|
|
18407
|
-
|
|
18408
|
-
results.push(`## ${title}
|
|
18409
|
-
Path: ${path}
|
|
18881
|
+
for (const result of ranked) {
|
|
18882
|
+
if (result.kind === "skill") {
|
|
18883
|
+
const body = (result.text ?? "").trim().slice(0, 400);
|
|
18884
|
+
results.push(`## ${result.title ?? result.key}
|
|
18885
|
+
Path: ${result.path}
|
|
18410
18886
|
|
|
18411
|
-
${body}${text
|
|
18887
|
+
${body}${(result.text?.length ?? 0) > 400 ? `
|
|
18412
18888
|
...(truncated)` : ""}
|
|
18413
18889
|
`);
|
|
18890
|
+
continue;
|
|
18891
|
+
}
|
|
18892
|
+
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 ? [
|
|
18893
|
+
` excerpt: ${result.text.replace(/^---[\s\S]*?---\n?/, "").replace(/^#.*$/m, "").trim().slice(0, 400)}`
|
|
18894
|
+
] : []);
|
|
18414
18895
|
}
|
|
18415
18896
|
}
|
|
18416
18897
|
return { content: [{ type: "text", text: results.join(`
|
|
@@ -18429,36 +18910,94 @@ function wikiTopicBrief(args) {
|
|
|
18429
18910
|
};
|
|
18430
18911
|
}
|
|
18431
18912
|
const { getTopicBrief, resolveTopicBrief } = runtime().host;
|
|
18432
|
-
|
|
18913
|
+
const hasAuthorizedBridge = Boolean(runtime().topicId && (getTopicBrief || resolveTopicBrief));
|
|
18914
|
+
if (hasAuthorizedBridge) {
|
|
18433
18915
|
try {
|
|
18434
18916
|
const brief = resolveTopicBrief ? resolveTopicBrief(rawTopic, runtime().userId) : getTopicBrief(rawTopic);
|
|
18435
|
-
if (
|
|
18436
|
-
|
|
18437
|
-
|
|
18438
|
-
|
|
18439
|
-
|
|
18440
|
-
|
|
18441
|
-
|
|
18442
|
-
|
|
18443
|
-
|
|
18444
|
-
|
|
18445
|
-
|
|
18446
|
-
|
|
18447
|
-
|
|
18448
|
-
if (brief.summaryDate) {
|
|
18449
|
-
lines.push("", `Summary date: ${brief.summaryDate}`);
|
|
18450
|
-
}
|
|
18451
|
-
return { content: [{ type: "text", text: lines.join(`
|
|
18917
|
+
if (brief) {
|
|
18918
|
+
const lines = [
|
|
18919
|
+
`# Topic Brief: ${topicNameFrom(rawTopic)}`,
|
|
18920
|
+
"",
|
|
18921
|
+
brief.briefMd || "(empty brief)"
|
|
18922
|
+
];
|
|
18923
|
+
if (brief.latestSummaryMd) {
|
|
18924
|
+
lines.push("", "## Latest Summary", "", brief.latestSummaryMd);
|
|
18925
|
+
}
|
|
18926
|
+
if (brief.summaryDate) {
|
|
18927
|
+
lines.push("", `Summary date: ${brief.summaryDate}`);
|
|
18928
|
+
}
|
|
18929
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
18452
18930
|
`) }] };
|
|
18453
|
-
|
|
18931
|
+
}
|
|
18932
|
+
} catch {}
|
|
18933
|
+
return {
|
|
18934
|
+
content: [{ type: "text", text: `No brief found for topic: ${rawTopic}` }]
|
|
18935
|
+
};
|
|
18936
|
+
}
|
|
18937
|
+
const topicKey = wikiSummarySlug(rawTopic.replace(/^topic\//, "").replace(/\.md$/i, ""));
|
|
18938
|
+
const filePath = resolve2(runtime().topicsDir, `${topicKey}.md`);
|
|
18939
|
+
try {
|
|
18940
|
+
const content = readFileSync2(filePath, "utf-8");
|
|
18941
|
+
return {
|
|
18942
|
+
content: [{ type: "text", text: `# Topic Brief: ${topicKey}
|
|
18943
|
+
|
|
18944
|
+
${content}` }]
|
|
18945
|
+
};
|
|
18946
|
+
} catch {}
|
|
18947
|
+
return {
|
|
18948
|
+
content: [{ type: "text", text: `No brief found for topic: ${rawTopic}` }]
|
|
18949
|
+
};
|
|
18950
|
+
}
|
|
18951
|
+
function wikiRead(args) {
|
|
18952
|
+
const rawKind = String(safeExt(args, ["kind", "type"], "article"));
|
|
18953
|
+
const kind = rawKind === "topic" || rawKind === "summary" ? rawKind : "article";
|
|
18954
|
+
const rawKey = String(safeExt(args, ["key", "slug", "topic"], "")).trim();
|
|
18955
|
+
if (!rawKey) {
|
|
18956
|
+
return { content: [{ type: "text", text: "A wiki document key is required." }], isError: true };
|
|
18957
|
+
}
|
|
18958
|
+
if (kind === "topic") {
|
|
18959
|
+
const result = wikiTopicBrief({ topic: rawKey });
|
|
18960
|
+
const resultText = result.content.map((entry) => entry.type === "text" ? entry.text : "").join(`
|
|
18961
|
+
`);
|
|
18962
|
+
const adopt = safeExt(args, ["adopt", "use", "select"], false);
|
|
18963
|
+
if (!adopt || resultText.startsWith("No brief found for topic"))
|
|
18964
|
+
return result;
|
|
18965
|
+
const currentTopicId = runtime().currentTopicId;
|
|
18966
|
+
const memoryKey = wikiSummarySlug(rawKey.replace(/^topic\//, "").replace(/\.md$/i, ""));
|
|
18967
|
+
if (!currentTopicId || !runtime().host.adoptTopicMemory?.(currentTopicId, runtime().userId, memoryKey)) {
|
|
18454
18968
|
return {
|
|
18455
|
-
content: [
|
|
18969
|
+
content: [
|
|
18970
|
+
...result.content,
|
|
18971
|
+
{ type: "text", text: `
|
|
18972
|
+
Memory was read but could not be adopted by this topic.` }
|
|
18973
|
+
]
|
|
18456
18974
|
};
|
|
18457
18975
|
}
|
|
18976
|
+
return {
|
|
18977
|
+
content: [...result.content, { type: "text", text: `
|
|
18978
|
+
Adopted topic memory: ${memoryKey}` }]
|
|
18979
|
+
};
|
|
18980
|
+
}
|
|
18981
|
+
if (rawKey.includes("\\") || rawKey.split("/").some((part) => part === "..") || rawKey.startsWith("/")) {
|
|
18982
|
+
return { content: [{ type: "text", text: "Invalid wiki document key." }], isError: true };
|
|
18983
|
+
}
|
|
18984
|
+
const root = kind === "summary" ? runtime().summariesDir : runtime().articlesDir;
|
|
18985
|
+
const key = rawKey.replace(new RegExp(`^(?:${kind === "summary" ? "summaries" : "articles"})/`), "").replace(/\.md$/i, "");
|
|
18986
|
+
const filePath = resolve2(root, `${key}.md`);
|
|
18987
|
+
const relativePath = relative(root, filePath);
|
|
18988
|
+
if (relativePath.startsWith("..") || relativePath.startsWith("/")) {
|
|
18989
|
+
return { content: [{ type: "text", text: "Invalid wiki document key." }], isError: true };
|
|
18990
|
+
}
|
|
18991
|
+
try {
|
|
18992
|
+
const content = readFileSync2(filePath, "utf-8");
|
|
18993
|
+
return {
|
|
18994
|
+
content: [{ type: "text", text: `# Wiki ${kind}: ${key}
|
|
18995
|
+
|
|
18996
|
+
${content}` }]
|
|
18997
|
+
};
|
|
18998
|
+
} catch {
|
|
18999
|
+
return { content: [{ type: "text", text: `No ${kind} found for: ${rawKey}` }] };
|
|
18458
19000
|
}
|
|
18459
|
-
return {
|
|
18460
|
-
content: [{ type: "text", text: `No topic brief found for: ${rawTopic}` }]
|
|
18461
|
-
};
|
|
18462
19001
|
}
|
|
18463
19002
|
function wikiLastConversation(args) {
|
|
18464
19003
|
const rawTopic = safeExt(args, ["topic", "topicName", "topicId"], runtime().topicId);
|
|
@@ -18825,7 +19364,7 @@ function indexUpsert(args) {
|
|
|
18825
19364
|
var WIKI_TOOLS = [
|
|
18826
19365
|
{
|
|
18827
19366
|
name: "wiki_query",
|
|
18828
|
-
description: "Search
|
|
19367
|
+
description: "Search topic, article, or summary indexes and return relevant candidates.",
|
|
18829
19368
|
inputSchema: {
|
|
18830
19369
|
type: "object",
|
|
18831
19370
|
properties: {
|
|
@@ -18836,11 +19375,38 @@ var WIKI_TOOLS = [
|
|
|
18836
19375
|
topic: {
|
|
18837
19376
|
type: "string",
|
|
18838
19377
|
description: "Optional topic name to narrow the search"
|
|
19378
|
+
},
|
|
19379
|
+
kind: {
|
|
19380
|
+
type: "string",
|
|
19381
|
+
enum: ["all", "topic", "article", "summary"],
|
|
19382
|
+
description: "Document kind to search; defaults to all"
|
|
19383
|
+
},
|
|
19384
|
+
limit: {
|
|
19385
|
+
type: "number",
|
|
19386
|
+
minimum: 1,
|
|
19387
|
+
maximum: 20,
|
|
19388
|
+
description: "Maximum number of candidates; defaults to 8"
|
|
18839
19389
|
}
|
|
18840
19390
|
},
|
|
18841
19391
|
required: ["question"]
|
|
18842
19392
|
}
|
|
18843
19393
|
},
|
|
19394
|
+
{
|
|
19395
|
+
name: "wiki_read",
|
|
19396
|
+
description: "Read one topic, article, or summary returned by wiki_query. For a clearly matching continuing persona, set adopt=true when reading a topic.",
|
|
19397
|
+
inputSchema: {
|
|
19398
|
+
type: "object",
|
|
19399
|
+
properties: {
|
|
19400
|
+
kind: { type: "string", enum: ["topic", "article", "summary"] },
|
|
19401
|
+
key: { type: "string", description: "Canonical key returned by wiki_query" },
|
|
19402
|
+
adopt: {
|
|
19403
|
+
type: "boolean",
|
|
19404
|
+
description: "For kind=topic, persist this canonical memory key for the current room"
|
|
19405
|
+
}
|
|
19406
|
+
},
|
|
19407
|
+
required: ["kind", "key"]
|
|
19408
|
+
}
|
|
19409
|
+
},
|
|
18844
19410
|
{
|
|
18845
19411
|
name: "wiki_topic_brief",
|
|
18846
19412
|
description: "Get the lightweight topic brief for a specific topic.",
|
|
@@ -18973,6 +19539,7 @@ function createWikiMcpServer(context, host) {
|
|
|
18973
19539
|
const wikiDir = resolve2(host.wikiRoot);
|
|
18974
19540
|
const current = {
|
|
18975
19541
|
userId: context.userId,
|
|
19542
|
+
currentTopicId: context.currentTopicId ?? context.topicId,
|
|
18976
19543
|
topicId: context.topicId,
|
|
18977
19544
|
surface,
|
|
18978
19545
|
host,
|
|
@@ -19002,6 +19569,7 @@ function createWikiMcpServer(context, host) {
|
|
|
19002
19569
|
const { name, arguments: args } = request.params;
|
|
19003
19570
|
const handlers = {
|
|
19004
19571
|
wiki_query: wikiQuery,
|
|
19572
|
+
wiki_read: wikiRead,
|
|
19005
19573
|
wiki_topic_brief: wikiTopicBrief,
|
|
19006
19574
|
wiki_last_conversation: wikiLastConversation,
|
|
19007
19575
|
skill_query: skillQuery,
|
|
@@ -19943,4 +20511,4 @@ export {
|
|
|
19943
20511
|
createAgentHealthMcpServer
|
|
19944
20512
|
};
|
|
19945
20513
|
|
|
19946
|
-
//# debugId=
|
|
20514
|
+
//# debugId=AE60D4AF576C043E64756E2164756E21
|