negotium 0.2.17 → 0.2.19
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 +10 -10
- 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 +1377 -285
- package/dist/main.js.map +14 -13
- package/dist/mcp-factories.js +1497 -403
- package/dist/mcp-factories.js.map +14 -13
- 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/archiver.ts +1 -1
- package/dist/runtime/src/agents/idle-archiver.ts +3 -2
- package/dist/runtime/src/mcp/canonical-proxy-server.ts +31 -15
- package/dist/runtime/src/mcp/wiki-search-index.ts +359 -0
- package/dist/runtime/src/mcp/wiki-server.ts +1358 -155
- package/dist/runtime/src/platform/mcp-config.ts +2 -1
- package/dist/runtime/src/prompts/agents/wiki-archiver.md +73 -48
- 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/agents/archiver.d.ts +1 -1
- package/dist/types/packages/core/src/mcp/wiki-search-index.d.ts +59 -0
- 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.19";
|
|
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) {
|
|
@@ -24911,6 +24920,219 @@ var init_vault2 = __esm(() => {
|
|
|
24911
24920
|
init_mcp_helpers();
|
|
24912
24921
|
});
|
|
24913
24922
|
|
|
24923
|
+
// ../../packages/core/src/mcp/wiki-search-index.ts
|
|
24924
|
+
import { chmodSync as chmodSync5, existsSync as existsSync26, readFileSync as readFileSync22, unlinkSync as unlinkSync18 } from "fs";
|
|
24925
|
+
function documentId(source) {
|
|
24926
|
+
return `${source.kind}:${source.key}`;
|
|
24927
|
+
}
|
|
24928
|
+
function ensureSchema(database) {
|
|
24929
|
+
const version = database.query("PRAGMA user_version").get()?.user_version;
|
|
24930
|
+
if (version !== 0 && version !== SEARCH_INDEX_SCHEMA_VERSION) {
|
|
24931
|
+
throw new Error(`Unsupported wiki search index schema: ${version}`);
|
|
24932
|
+
}
|
|
24933
|
+
database.exec(`
|
|
24934
|
+
CREATE TABLE IF NOT EXISTS wiki_search_documents (
|
|
24935
|
+
id TEXT PRIMARY KEY,
|
|
24936
|
+
kind TEXT NOT NULL,
|
|
24937
|
+
key TEXT NOT NULL,
|
|
24938
|
+
path TEXT NOT NULL,
|
|
24939
|
+
mtime_ms REAL NOT NULL,
|
|
24940
|
+
size INTEGER NOT NULL,
|
|
24941
|
+
title TEXT NOT NULL,
|
|
24942
|
+
text TEXT NOT NULL,
|
|
24943
|
+
date TEXT,
|
|
24944
|
+
family TEXT
|
|
24945
|
+
);
|
|
24946
|
+
CREATE UNIQUE INDEX IF NOT EXISTS wiki_search_documents_kind_key
|
|
24947
|
+
ON wiki_search_documents(kind, key);
|
|
24948
|
+
CREATE INDEX IF NOT EXISTS wiki_search_documents_kind_date
|
|
24949
|
+
ON wiki_search_documents(kind, date);
|
|
24950
|
+
CREATE INDEX IF NOT EXISTS wiki_search_documents_kind_family_date
|
|
24951
|
+
ON wiki_search_documents(kind, family, date);
|
|
24952
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS wiki_search_fts USING fts5(
|
|
24953
|
+
document_id UNINDEXED,
|
|
24954
|
+
kind UNINDEXED,
|
|
24955
|
+
tokens,
|
|
24956
|
+
tokenize = 'unicode61'
|
|
24957
|
+
);
|
|
24958
|
+
PRAGMA user_version = ${SEARCH_INDEX_SCHEMA_VERSION};
|
|
24959
|
+
`);
|
|
24960
|
+
}
|
|
24961
|
+
|
|
24962
|
+
class WikiSearchIndex {
|
|
24963
|
+
path;
|
|
24964
|
+
#database;
|
|
24965
|
+
constructor(path) {
|
|
24966
|
+
this.path = path;
|
|
24967
|
+
this.#database = new Database(path, { create: true });
|
|
24968
|
+
try {
|
|
24969
|
+
this.#database.exec("PRAGMA foreign_keys = ON; PRAGMA journal_mode = DELETE;");
|
|
24970
|
+
ensureSchema(this.#database);
|
|
24971
|
+
chmodSync5(path, 384);
|
|
24972
|
+
} catch (error2) {
|
|
24973
|
+
this.#database.close();
|
|
24974
|
+
throw error2;
|
|
24975
|
+
}
|
|
24976
|
+
}
|
|
24977
|
+
close() {
|
|
24978
|
+
this.#database.close();
|
|
24979
|
+
}
|
|
24980
|
+
upsert(source, document) {
|
|
24981
|
+
const id = documentId(source);
|
|
24982
|
+
const apply = this.#database.transaction(() => {
|
|
24983
|
+
this.#database.query("DELETE FROM wiki_search_fts WHERE document_id = ?").run(id);
|
|
24984
|
+
this.#database.query("DELETE FROM wiki_search_documents WHERE id = ?").run(id);
|
|
24985
|
+
this.#database.query(`
|
|
24986
|
+
INSERT INTO wiki_search_documents
|
|
24987
|
+
(id, kind, key, path, mtime_ms, size, title, text, date, family)
|
|
24988
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
24989
|
+
`).run(id, source.kind, source.key, source.path, source.mtimeMs, source.size, document.title, document.text, document.date ?? null, document.family ?? null);
|
|
24990
|
+
this.#database.query("INSERT INTO wiki_search_fts(document_id, kind, tokens) VALUES (?, ?, ?)").run(id, source.kind, document.tokens.join(" "));
|
|
24991
|
+
});
|
|
24992
|
+
apply.immediate();
|
|
24993
|
+
}
|
|
24994
|
+
sync(sources, prepare) {
|
|
24995
|
+
const result = { added: 0, updated: 0, removed: 0 };
|
|
24996
|
+
const existingRows = this.#database.query("SELECT id, path, mtime_ms, size FROM wiki_search_documents ORDER BY id").all();
|
|
24997
|
+
const existing = new Map(existingRows.map((row) => [row.id, row]));
|
|
24998
|
+
const present = new Set;
|
|
24999
|
+
const changed = [];
|
|
25000
|
+
for (const source of sources) {
|
|
25001
|
+
const id = documentId(source);
|
|
25002
|
+
present.add(id);
|
|
25003
|
+
const stored = existing.get(id);
|
|
25004
|
+
if (stored && stored.path === source.path && stored.mtime_ms === source.mtimeMs && stored.size === source.size) {
|
|
25005
|
+
continue;
|
|
25006
|
+
}
|
|
25007
|
+
try {
|
|
25008
|
+
const text2 = readFileSync22(source.path, "utf-8");
|
|
25009
|
+
changed.push({ source, document: prepare(source, text2), added: !stored });
|
|
25010
|
+
} catch {}
|
|
25011
|
+
}
|
|
25012
|
+
const removed = existingRows.filter((row) => !present.has(row.id));
|
|
25013
|
+
const apply = this.#database.transaction(() => {
|
|
25014
|
+
const deleteTerms = this.#database.query("DELETE FROM wiki_search_fts WHERE document_id = ?");
|
|
25015
|
+
const deleteDocument = this.#database.query("DELETE FROM wiki_search_documents WHERE id = ?");
|
|
25016
|
+
const insertDocument = this.#database.query(`
|
|
25017
|
+
INSERT INTO wiki_search_documents
|
|
25018
|
+
(id, kind, key, path, mtime_ms, size, title, text, date, family)
|
|
25019
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
25020
|
+
`);
|
|
25021
|
+
const insertTerms = this.#database.query(`
|
|
25022
|
+
INSERT INTO wiki_search_fts(document_id, kind, tokens)
|
|
25023
|
+
VALUES (?, ?, ?)
|
|
25024
|
+
`);
|
|
25025
|
+
for (const row of removed) {
|
|
25026
|
+
deleteTerms.run(row.id);
|
|
25027
|
+
deleteDocument.run(row.id);
|
|
25028
|
+
result.removed += 1;
|
|
25029
|
+
}
|
|
25030
|
+
for (const { source, document, added } of changed) {
|
|
25031
|
+
const id = documentId(source);
|
|
25032
|
+
deleteTerms.run(id);
|
|
25033
|
+
deleteDocument.run(id);
|
|
25034
|
+
insertDocument.run(id, source.kind, source.key, source.path, source.mtimeMs, source.size, document.title, document.text, document.date ?? null, document.family ?? null);
|
|
25035
|
+
insertTerms.run(id, source.kind, document.tokens.join(" "));
|
|
25036
|
+
if (added)
|
|
25037
|
+
result.added += 1;
|
|
25038
|
+
else
|
|
25039
|
+
result.updated += 1;
|
|
25040
|
+
}
|
|
25041
|
+
});
|
|
25042
|
+
apply.immediate();
|
|
25043
|
+
return result;
|
|
25044
|
+
}
|
|
25045
|
+
metadata(kinds) {
|
|
25046
|
+
if (kinds.length === 0)
|
|
25047
|
+
return [];
|
|
25048
|
+
const placeholders = kinds.map(() => "?").join(", ");
|
|
25049
|
+
const rows = this.#database.query(`SELECT id, kind, key, title, date
|
|
25050
|
+
FROM wiki_search_documents
|
|
25051
|
+
WHERE kind IN (${placeholders})`).all(...kinds);
|
|
25052
|
+
return rows.map((row) => ({
|
|
25053
|
+
id: row.id,
|
|
25054
|
+
kind: row.kind,
|
|
25055
|
+
key: row.key,
|
|
25056
|
+
title: row.title,
|
|
25057
|
+
...row.date ? { date: row.date } : {}
|
|
25058
|
+
}));
|
|
25059
|
+
}
|
|
25060
|
+
summaryMetadataByDate(query2) {
|
|
25061
|
+
const conditions = ["kind = 'summary'", "date IS NOT NULL"];
|
|
25062
|
+
const parameters = [];
|
|
25063
|
+
if (query2.year && query2.month) {
|
|
25064
|
+
const month = Number(query2.month);
|
|
25065
|
+
const nextYear = month === 12 ? String(Number(query2.year) + 1) : query2.year;
|
|
25066
|
+
const nextMonth = String(month === 12 ? 1 : month + 1).padStart(2, "0");
|
|
25067
|
+
conditions.push("date >= ?", "date < ?");
|
|
25068
|
+
parameters.push(`${query2.year}-${query2.month}-01`, `${nextYear}-${nextMonth}-01`);
|
|
25069
|
+
} else if (query2.year) {
|
|
25070
|
+
conditions.push("date >= ?", "date < ?");
|
|
25071
|
+
parameters.push(`${query2.year}-01-01`, `${Number(query2.year) + 1}-01-01`);
|
|
25072
|
+
} else if (query2.month) {
|
|
25073
|
+
conditions.push("substr(date, 6, 2) = ?");
|
|
25074
|
+
parameters.push(query2.month);
|
|
25075
|
+
}
|
|
25076
|
+
parameters.push(query2.limit);
|
|
25077
|
+
const rows = this.#database.query(`SELECT id, kind, key, title, date
|
|
25078
|
+
FROM wiki_search_documents
|
|
25079
|
+
WHERE ${conditions.join(" AND ")}
|
|
25080
|
+
ORDER BY date ${query2.direction === "oldest" ? "ASC" : "DESC"}, key ASC
|
|
25081
|
+
LIMIT ?`).all(...parameters);
|
|
25082
|
+
return rows.map((row) => ({
|
|
25083
|
+
id: row.id,
|
|
25084
|
+
kind: row.kind,
|
|
25085
|
+
key: row.key,
|
|
25086
|
+
title: row.title,
|
|
25087
|
+
...row.date ? { date: row.date } : {}
|
|
25088
|
+
}));
|
|
25089
|
+
}
|
|
25090
|
+
matchingDocumentIds(kinds, terms) {
|
|
25091
|
+
if (kinds.length === 0 || terms.length === 0)
|
|
25092
|
+
return new Set;
|
|
25093
|
+
const kindPlaceholders = kinds.map(() => "?").join(", ");
|
|
25094
|
+
const match = terms.map((term) => `"${term.replaceAll('"', '""')}"`).join(" OR ");
|
|
25095
|
+
const rows = this.#database.query(`SELECT DISTINCT document_id
|
|
25096
|
+
FROM wiki_search_fts
|
|
25097
|
+
WHERE wiki_search_fts MATCH ? AND kind IN (${kindPlaceholders})`).all(match, ...kinds);
|
|
25098
|
+
return new Set(rows.map((row) => row.document_id));
|
|
25099
|
+
}
|
|
25100
|
+
documents(ids) {
|
|
25101
|
+
if (ids.size === 0)
|
|
25102
|
+
return [];
|
|
25103
|
+
const values = [...ids];
|
|
25104
|
+
const rows = [];
|
|
25105
|
+
for (let offset = 0;offset < values.length; offset += 500) {
|
|
25106
|
+
const chunk = values.slice(offset, offset + 500);
|
|
25107
|
+
const placeholders = chunk.map(() => "?").join(", ");
|
|
25108
|
+
rows.push(...this.#database.query(`SELECT id, kind, key, path, title, text, date
|
|
25109
|
+
FROM wiki_search_documents
|
|
25110
|
+
WHERE id IN (${placeholders})`).all(...chunk));
|
|
25111
|
+
}
|
|
25112
|
+
return rows.map((row) => ({
|
|
25113
|
+
id: row.id,
|
|
25114
|
+
kind: row.kind,
|
|
25115
|
+
key: row.key,
|
|
25116
|
+
path: row.path,
|
|
25117
|
+
title: row.title,
|
|
25118
|
+
text: row.text,
|
|
25119
|
+
tokens: [],
|
|
25120
|
+
...row.date ? { date: row.date } : {}
|
|
25121
|
+
}));
|
|
25122
|
+
}
|
|
25123
|
+
}
|
|
25124
|
+
function resetCorruptWikiSearchIndex(path) {
|
|
25125
|
+
for (const candidate of [path, `${path}-journal`, `${path}-wal`, `${path}-shm`]) {
|
|
25126
|
+
if (!existsSync26(candidate))
|
|
25127
|
+
continue;
|
|
25128
|
+
unlinkSync18(candidate);
|
|
25129
|
+
}
|
|
25130
|
+
}
|
|
25131
|
+
var SEARCH_INDEX_SCHEMA_VERSION = 3;
|
|
25132
|
+
var init_wiki_search_index = __esm(async () => {
|
|
25133
|
+
await init_sqlite();
|
|
25134
|
+
});
|
|
25135
|
+
|
|
24914
25136
|
// ../../packages/core/src/mcp/wiki-server.ts
|
|
24915
25137
|
var exports_wiki_server = {};
|
|
24916
25138
|
__export(exports_wiki_server, {
|
|
@@ -24921,17 +25143,17 @@ import { AsyncLocalStorage as AsyncLocalStorage3 } from "async_hooks";
|
|
|
24921
25143
|
import { randomUUID as randomUUID21 } from "crypto";
|
|
24922
25144
|
import {
|
|
24923
25145
|
closeSync as closeSync4,
|
|
24924
|
-
existsSync as
|
|
25146
|
+
existsSync as existsSync27,
|
|
24925
25147
|
mkdirSync as mkdirSync24,
|
|
24926
25148
|
openSync as openSync4,
|
|
24927
25149
|
readdirSync as readdirSync10,
|
|
24928
|
-
readFileSync as
|
|
25150
|
+
readFileSync as readFileSync23,
|
|
24929
25151
|
renameSync as renameSync11,
|
|
24930
25152
|
statSync as statSync15,
|
|
24931
|
-
unlinkSync as
|
|
25153
|
+
unlinkSync as unlinkSync19,
|
|
24932
25154
|
writeFileSync as writeFileSync17
|
|
24933
25155
|
} from "fs";
|
|
24934
|
-
import { basename as basename7, dirname as dirname16, join as join36, resolve as resolve17 } from "path";
|
|
25156
|
+
import { basename as basename7, dirname as dirname16, join as join36, relative as relative2, resolve as resolve17 } from "path";
|
|
24935
25157
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
24936
25158
|
import { StdioServerTransport as StdioServerTransport2 } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
24937
25159
|
import {
|
|
@@ -24971,15 +25193,15 @@ function acquireRecoveryGuard(lockPath) {
|
|
|
24971
25193
|
} catch (error2) {
|
|
24972
25194
|
closeSync4(fd);
|
|
24973
25195
|
try {
|
|
24974
|
-
|
|
25196
|
+
unlinkSync19(guardPath);
|
|
24975
25197
|
} catch {}
|
|
24976
25198
|
throw error2;
|
|
24977
25199
|
}
|
|
24978
25200
|
return () => {
|
|
24979
25201
|
closeSync4(fd);
|
|
24980
25202
|
try {
|
|
24981
|
-
if (
|
|
24982
|
-
|
|
25203
|
+
if (readFileSync23(guardPath, "utf-8") === ownerToken)
|
|
25204
|
+
unlinkSync19(guardPath);
|
|
24983
25205
|
} catch {}
|
|
24984
25206
|
};
|
|
24985
25207
|
} catch (error2) {
|
|
@@ -25004,7 +25226,7 @@ function acquireFileLock(path) {
|
|
|
25004
25226
|
} catch (error2) {
|
|
25005
25227
|
closeSync4(fd);
|
|
25006
25228
|
try {
|
|
25007
|
-
|
|
25229
|
+
unlinkSync19(lockPath);
|
|
25008
25230
|
} catch {}
|
|
25009
25231
|
throw error2;
|
|
25010
25232
|
}
|
|
@@ -25012,8 +25234,8 @@ function acquireFileLock(path) {
|
|
|
25012
25234
|
closeSync4(fd);
|
|
25013
25235
|
const releaseGuard = acquireRecoveryGuard(lockPath);
|
|
25014
25236
|
try {
|
|
25015
|
-
if (
|
|
25016
|
-
|
|
25237
|
+
if (readFileSync23(lockPath, "utf-8") === ownerToken)
|
|
25238
|
+
unlinkSync19(lockPath);
|
|
25017
25239
|
} catch {} finally {
|
|
25018
25240
|
releaseGuard();
|
|
25019
25241
|
}
|
|
@@ -25024,9 +25246,9 @@ function acquireFileLock(path) {
|
|
|
25024
25246
|
const releaseGuard = acquireRecoveryGuard(lockPath);
|
|
25025
25247
|
try {
|
|
25026
25248
|
try {
|
|
25027
|
-
const staleToken =
|
|
25028
|
-
if (Date.now() - statSync15(lockPath).mtimeMs > FILE_LOCK_STALE_MS &&
|
|
25029
|
-
|
|
25249
|
+
const staleToken = readFileSync23(lockPath, "utf-8");
|
|
25250
|
+
if (Date.now() - statSync15(lockPath).mtimeMs > FILE_LOCK_STALE_MS && readFileSync23(lockPath, "utf-8") === staleToken) {
|
|
25251
|
+
unlinkSync19(lockPath);
|
|
25030
25252
|
continue;
|
|
25031
25253
|
}
|
|
25032
25254
|
} catch {
|
|
@@ -25049,7 +25271,7 @@ function atomicWriteFile(path, content) {
|
|
|
25049
25271
|
renameSync11(temporaryPath, path);
|
|
25050
25272
|
} catch (error2) {
|
|
25051
25273
|
try {
|
|
25052
|
-
|
|
25274
|
+
unlinkSync19(temporaryPath);
|
|
25053
25275
|
} catch {}
|
|
25054
25276
|
throw error2;
|
|
25055
25277
|
}
|
|
@@ -25070,9 +25292,310 @@ function topicNameFrom(topicIdStr) {
|
|
|
25070
25292
|
function slugify(topic) {
|
|
25071
25293
|
return wikiSummarySlug(topic);
|
|
25072
25294
|
}
|
|
25295
|
+
function normalizeSearchText(value) {
|
|
25296
|
+
return value.toLowerCase().replaceAll(/[^a-z0-9\uAC00-\uD7A3]+/g, " ").trim().replaceAll(/\s+/g, " ");
|
|
25297
|
+
}
|
|
25298
|
+
function compactSearchText(value) {
|
|
25299
|
+
return normalizeSearchText(value).replaceAll(" ", "");
|
|
25300
|
+
}
|
|
25301
|
+
function trigrams(value) {
|
|
25302
|
+
const compact = compactSearchText(value);
|
|
25303
|
+
if (compact.length < 3)
|
|
25304
|
+
return new Set(compact ? [compact] : []);
|
|
25305
|
+
const result = new Set;
|
|
25306
|
+
for (let i = 0;i <= compact.length - 3; i += 1)
|
|
25307
|
+
result.add(compact.slice(i, i + 3));
|
|
25308
|
+
return result;
|
|
25309
|
+
}
|
|
25310
|
+
function fuzzySearchScore(query2, candidate, maximum) {
|
|
25311
|
+
const compactQuery = compactSearchText(query2);
|
|
25312
|
+
const compactCandidate = compactSearchText(candidate);
|
|
25313
|
+
if (compactQuery.length < 3 || compactCandidate.length < 3)
|
|
25314
|
+
return 0;
|
|
25315
|
+
const similarity = trigramSimilarity(compactQuery, compactCandidate);
|
|
25316
|
+
const minimumSimilarity = compactQuery.length <= 4 ? 0.6 : 0.45;
|
|
25317
|
+
return similarity >= minimumSimilarity ? similarity * maximum : 0;
|
|
25318
|
+
}
|
|
25319
|
+
function meaningfulSearchTokens(value) {
|
|
25320
|
+
return normalizeSearchText(value).split(" ").filter((token) => (token.length >= 2 || /^\d+$/.test(token)) && !SEARCH_STOP_WORDS.has(token));
|
|
25321
|
+
}
|
|
25322
|
+
function canonicalSearchToken(token) {
|
|
25323
|
+
return token.length > 4 && token.endsWith("s") ? token.slice(0, -1) : token;
|
|
25324
|
+
}
|
|
25325
|
+
function canonicalSearchTokens(value) {
|
|
25326
|
+
return meaningfulSearchTokens(value).map(canonicalSearchToken);
|
|
25327
|
+
}
|
|
25328
|
+
function countToken(tokens, token) {
|
|
25329
|
+
let count = 0;
|
|
25330
|
+
for (const candidate of tokens)
|
|
25331
|
+
if (candidate === token)
|
|
25332
|
+
count += 1;
|
|
25333
|
+
return count;
|
|
25334
|
+
}
|
|
25335
|
+
function countPhrase(text2, phrase) {
|
|
25336
|
+
if (!phrase)
|
|
25337
|
+
return 0;
|
|
25338
|
+
let count = 0;
|
|
25339
|
+
let offset = 0;
|
|
25340
|
+
let match = text2.indexOf(phrase, offset);
|
|
25341
|
+
while (match >= 0) {
|
|
25342
|
+
count += 1;
|
|
25343
|
+
offset = match + phrase.length;
|
|
25344
|
+
match = text2.indexOf(phrase, offset);
|
|
25345
|
+
}
|
|
25346
|
+
return count;
|
|
25347
|
+
}
|
|
25348
|
+
function summaryTopicIdentity(key) {
|
|
25349
|
+
return normalizeSearchText(key.replace(/^\d{4}-\d{2}-\d{2}-/, ""));
|
|
25350
|
+
}
|
|
25351
|
+
function scoreSummaryTime(query2, key, title) {
|
|
25352
|
+
const date = key.match(/^(\d{4})-(\d{2})-(\d{2})/);
|
|
25353
|
+
if (!date)
|
|
25354
|
+
return 0;
|
|
25355
|
+
const tokens = meaningfulSearchTokens(query2);
|
|
25356
|
+
const documentDate = `${date[1]}-${date[2]}-${date[3]}`;
|
|
25357
|
+
const requestedDateMatch = query2.match(/\b(\d{4}) (\d{2}) (\d{2})\b/);
|
|
25358
|
+
const requestedDate = requestedDateMatch ? `${requestedDateMatch[1]}-${requestedDateMatch[2]}-${requestedDateMatch[3]}` : undefined;
|
|
25359
|
+
const beforeDate = /\bbefore\b/.test(query2);
|
|
25360
|
+
const afterDate = /\bafter\b/.test(query2);
|
|
25361
|
+
if (requestedDate && beforeDate && documentDate >= requestedDate)
|
|
25362
|
+
return null;
|
|
25363
|
+
if (requestedDate && afterDate && documentDate <= requestedDate)
|
|
25364
|
+
return null;
|
|
25365
|
+
const queryYear = tokens.find((token) => /^\d{4}(?:\uB144)?$/.test(token))?.replace(/\uB144$/, "");
|
|
25366
|
+
const queryMonth = tokens.map((token) => SEARCH_MONTHS.get(token)).find(Boolean);
|
|
25367
|
+
const hasRecencyTerm = tokens.some((token) => SEARCH_RECENCY_TERMS.has(token));
|
|
25368
|
+
const yearMatches = !queryYear || queryYear === date[1];
|
|
25369
|
+
const monthMatches = !queryMonth || queryMonth === date[2];
|
|
25370
|
+
if (!queryYear && !queryMonth && !hasRecencyTerm || !yearMatches || !monthMatches)
|
|
25371
|
+
return 0;
|
|
25372
|
+
const normalizedMetadata = `${normalizeSearchText(key)} ${normalizeSearchText(title)}`;
|
|
25373
|
+
const contentTokens = tokens.filter((token) => !/^\d{4}(?:\uB144)?$/.test(token) && !SEARCH_MONTHS.has(token) && !SEARCH_RECENCY_TERMS.has(token));
|
|
25374
|
+
const contentMatches = contentTokens.filter((token) => normalizedMetadata.includes(token)).length;
|
|
25375
|
+
const relationScore = requestedDate && (beforeDate || afterDate) ? 60 : 0;
|
|
25376
|
+
const exactDateScore = requestedDate === documentDate ? 90 : 0;
|
|
25377
|
+
return 30 + relationScore + exactDateScore + contentMatches * 15;
|
|
25378
|
+
}
|
|
25379
|
+
function trigramSimilarity(left, right) {
|
|
25380
|
+
const a = trigrams(left);
|
|
25381
|
+
const b = trigrams(right);
|
|
25382
|
+
if (a.size === 0 || b.size === 0)
|
|
25383
|
+
return 0;
|
|
25384
|
+
let overlap = 0;
|
|
25385
|
+
for (const gram of a)
|
|
25386
|
+
if (b.has(gram))
|
|
25387
|
+
overlap += 1;
|
|
25388
|
+
return 2 * overlap / (a.size + b.size);
|
|
25389
|
+
}
|
|
25390
|
+
function openWikiSearchIndex() {
|
|
25391
|
+
const path = resolve17(runtime().wikiDir, WIKI_SEARCH_INDEX_FILENAME);
|
|
25392
|
+
try {
|
|
25393
|
+
return new WikiSearchIndex(path);
|
|
25394
|
+
} catch (error2) {
|
|
25395
|
+
if (!/malformed|not a database|schema/i.test(String(error2)))
|
|
25396
|
+
return;
|
|
25397
|
+
try {
|
|
25398
|
+
resetCorruptWikiSearchIndex(path);
|
|
25399
|
+
return new WikiSearchIndex(path);
|
|
25400
|
+
} catch {
|
|
25401
|
+
return;
|
|
25402
|
+
}
|
|
25403
|
+
}
|
|
25404
|
+
}
|
|
25405
|
+
function prepareWikiSearchDocument(kind, key, text2) {
|
|
25406
|
+
const titleLine = text2.split(`
|
|
25407
|
+
`).find((line) => /^#+\s*/.test(line)) ?? "";
|
|
25408
|
+
const title = titleLine ? titleLine.replace(/^#+\s*/, "") : basename7(key);
|
|
25409
|
+
return {
|
|
25410
|
+
title,
|
|
25411
|
+
text: text2,
|
|
25412
|
+
tokens: canonicalSearchTokens(`${key} ${title} ${text2}`),
|
|
25413
|
+
...kind === "summary" && /^\d{4}-\d{2}-\d{2}/.test(key) ? { date: key.slice(0, 10), family: summaryTopicIdentity(key) } : {}
|
|
25414
|
+
};
|
|
25415
|
+
}
|
|
25416
|
+
function indexWikiSearchDocument(kind, key, text2) {
|
|
25417
|
+
const root = kind === "article" ? runtime().articlesDir : runtime().summariesDir;
|
|
25418
|
+
const path = resolve17(root, `${key}.md`);
|
|
25419
|
+
const index = openWikiSearchIndex();
|
|
25420
|
+
if (!index)
|
|
25421
|
+
return false;
|
|
25422
|
+
try {
|
|
25423
|
+
const stat = statSync15(path);
|
|
25424
|
+
index.upsert({ kind, key, path, mtimeMs: stat.mtimeMs, size: stat.size }, prepareWikiSearchDocument(kind, key, text2));
|
|
25425
|
+
return true;
|
|
25426
|
+
} catch {
|
|
25427
|
+
return false;
|
|
25428
|
+
} finally {
|
|
25429
|
+
index.close();
|
|
25430
|
+
}
|
|
25431
|
+
}
|
|
25432
|
+
function wikiIndexPath(kind) {
|
|
25433
|
+
return resolve17(runtime().wikiDir, WIKI_INDEX_FILENAMES[kind]);
|
|
25434
|
+
}
|
|
25435
|
+
function normalizeIndexDescription(raw) {
|
|
25436
|
+
const description = raw.replaceAll(/\s+/g, " ").trim();
|
|
25437
|
+
if (!description)
|
|
25438
|
+
return { error: "A non-empty one-line description is required." };
|
|
25439
|
+
if (description.length > MAX_INDEX_DESCRIPTION_LENGTH) {
|
|
25440
|
+
return {
|
|
25441
|
+
error: `Description must be at most ${MAX_INDEX_DESCRIPTION_LENGTH} characters (got ${description.length}).`
|
|
25442
|
+
};
|
|
25443
|
+
}
|
|
25444
|
+
return { description };
|
|
25445
|
+
}
|
|
25446
|
+
function normalizeWikiSlug(raw) {
|
|
25447
|
+
const slug = raw.trim().replace(/\.md$/i, "").replace(/^\/+/, "");
|
|
25448
|
+
if (!slug)
|
|
25449
|
+
return { error: "A non-empty slug is required." };
|
|
25450
|
+
if (/[[\]|\\]/.test(slug))
|
|
25451
|
+
return { error: "Slug must not contain [ ] | or backslashes." };
|
|
25452
|
+
if (slug.split("/").some((segment) => segment === "" || segment === "." || segment === "..")) {
|
|
25453
|
+
return { error: "Slug must not contain empty or relative path segments." };
|
|
25454
|
+
}
|
|
25455
|
+
return { slug };
|
|
25456
|
+
}
|
|
25457
|
+
function parseWikiIndex(path) {
|
|
25458
|
+
let text2;
|
|
25459
|
+
try {
|
|
25460
|
+
text2 = readFileSync23(path, "utf-8");
|
|
25461
|
+
} catch {
|
|
25462
|
+
return [];
|
|
25463
|
+
}
|
|
25464
|
+
const candidates = [];
|
|
25465
|
+
const entryPattern = /^\s*-\s*\[\[(topic|articles|summaries)\/([^\]]+)\]\]\s*(.*)$/;
|
|
25466
|
+
for (const line of text2.split(`
|
|
25467
|
+
`)) {
|
|
25468
|
+
const match = line.match(entryPattern);
|
|
25469
|
+
if (!match)
|
|
25470
|
+
continue;
|
|
25471
|
+
const namespace = match[1];
|
|
25472
|
+
const key = match[2].replace(/\.md$/i, "");
|
|
25473
|
+
const tail = match[3].trim();
|
|
25474
|
+
const dateMatch = tail.match(/\((\d{4}-\d{2}-\d{2})(?:[^)]*)\)\s*$/);
|
|
25475
|
+
const description = dateMatch ? tail.slice(0, dateMatch.index).trim() : tail;
|
|
25476
|
+
candidates.push({
|
|
25477
|
+
kind: namespace === "topic" ? "topic" : namespace === "articles" ? "article" : "summary",
|
|
25478
|
+
key,
|
|
25479
|
+
description,
|
|
25480
|
+
...dateMatch ? { date: dateMatch[1] } : {}
|
|
25481
|
+
});
|
|
25482
|
+
}
|
|
25483
|
+
return candidates;
|
|
25484
|
+
}
|
|
25485
|
+
function indexCandidateExists(candidate) {
|
|
25486
|
+
if (candidate.kind === "topic")
|
|
25487
|
+
return true;
|
|
25488
|
+
const root = candidate.kind === "article" ? runtime().articlesDir : runtime().summariesDir;
|
|
25489
|
+
const filePath = resolve17(root, `${candidate.key}.md`);
|
|
25490
|
+
const relativePath = relative2(root, filePath);
|
|
25491
|
+
if (relativePath.startsWith("..") || relativePath.startsWith("/"))
|
|
25492
|
+
return false;
|
|
25493
|
+
try {
|
|
25494
|
+
return statSync15(filePath).isFile();
|
|
25495
|
+
} catch {
|
|
25496
|
+
return false;
|
|
25497
|
+
}
|
|
25498
|
+
}
|
|
25499
|
+
function scoreIndexKey(query2, key) {
|
|
25500
|
+
const normalizedQuery = normalizeSearchText(query2);
|
|
25501
|
+
const compactQuery = compactSearchText(normalizedQuery);
|
|
25502
|
+
const normalizedKey = normalizeSearchText(key);
|
|
25503
|
+
const compactKey = compactSearchText(normalizedKey);
|
|
25504
|
+
const tokens = meaningfulSearchTokens(normalizedQuery);
|
|
25505
|
+
let score = 0;
|
|
25506
|
+
if (normalizedKey === normalizedQuery)
|
|
25507
|
+
score += 120;
|
|
25508
|
+
else if (compactKey === compactQuery)
|
|
25509
|
+
score += 110;
|
|
25510
|
+
else {
|
|
25511
|
+
if (compactQuery.length >= 2 && (compactKey.startsWith(compactQuery) || compactQuery.startsWith(compactKey))) {
|
|
25512
|
+
score += 65;
|
|
25513
|
+
}
|
|
25514
|
+
if (tokens.length > 0 && compactQuery.length >= 3 && compactKey.includes(compactQuery)) {
|
|
25515
|
+
score += 45;
|
|
25516
|
+
}
|
|
25517
|
+
score += fuzzySearchScore(compactQuery, compactKey, 55);
|
|
25518
|
+
}
|
|
25519
|
+
for (const token of tokens) {
|
|
25520
|
+
if (normalizedKey.includes(token))
|
|
25521
|
+
score += 10;
|
|
25522
|
+
}
|
|
25523
|
+
return score;
|
|
25524
|
+
}
|
|
25525
|
+
function scoreIndexCandidate(query2, candidate) {
|
|
25526
|
+
const normalizedQuery = normalizeSearchText(query2);
|
|
25527
|
+
if (!normalizedQuery)
|
|
25528
|
+
return 0;
|
|
25529
|
+
const normalizedDescription = normalizeSearchText(candidate.description);
|
|
25530
|
+
const tokens = canonicalSearchTokens(normalizedQuery);
|
|
25531
|
+
const descriptionTokens = new Set(canonicalSearchTokens(normalizedDescription));
|
|
25532
|
+
let score = scoreIndexKey(normalizedQuery, candidate.key);
|
|
25533
|
+
if (tokens.length > 0 && normalizedDescription.includes(normalizedQuery))
|
|
25534
|
+
score += 30;
|
|
25535
|
+
let descriptionMatches = 0;
|
|
25536
|
+
for (const token of tokens) {
|
|
25537
|
+
if (descriptionTokens.has(token)) {
|
|
25538
|
+
score += 5;
|
|
25539
|
+
descriptionMatches += 1;
|
|
25540
|
+
}
|
|
25541
|
+
}
|
|
25542
|
+
if (tokens.length > 1 && descriptionMatches === tokens.length)
|
|
25543
|
+
score += 25;
|
|
25544
|
+
if (candidate.kind === "topic") {
|
|
25545
|
+
const requestsCurrent = /\b(?:current|active|ongoing|open)\b|\uD604\uC7AC|\uC9C4\uD589 \uC911/.test(normalizedQuery);
|
|
25546
|
+
const archivedCandidate = /\b(?:archive|archived|historical|closed)\b|\uBCF4\uAD00|\uC885\uB8CC/.test(`${normalizeSearchText(candidate.key)} ${normalizedDescription}`);
|
|
25547
|
+
if (requestsCurrent && archivedCandidate)
|
|
25548
|
+
score -= 40;
|
|
25549
|
+
}
|
|
25550
|
+
return score;
|
|
25551
|
+
}
|
|
25073
25552
|
function wikiQuery(args) {
|
|
25074
|
-
const question = safeExt(args, ["question", "query", "q", "text"], "");
|
|
25075
|
-
const
|
|
25553
|
+
const question = String(safeExt(args, ["question", "query", "q", "text"], "")).trim();
|
|
25554
|
+
const requestedKind = String(safeExt(args, ["kind", "type"], "all"));
|
|
25555
|
+
const kind = requestedKind === "topic" || requestedKind === "article" || requestedKind === "summary" ? requestedKind : "all";
|
|
25556
|
+
const requestedLimit = Number(safeExt(args, ["limit", "maxResults"], 8));
|
|
25557
|
+
const limit = Math.min(Math.max(Number.isFinite(requestedLimit) ? requestedLimit : 8, 1), 20);
|
|
25558
|
+
if (!question) {
|
|
25559
|
+
return {
|
|
25560
|
+
content: [{ type: "text", text: "A non-empty wiki query is required." }],
|
|
25561
|
+
isError: true
|
|
25562
|
+
};
|
|
25563
|
+
}
|
|
25564
|
+
const normalizedQuery = normalizeSearchText(question);
|
|
25565
|
+
if (!normalizedQuery) {
|
|
25566
|
+
return { content: [{ type: "text", text: "No matching wiki articles found." }] };
|
|
25567
|
+
}
|
|
25568
|
+
const canReadTopicMemory = runtime().host.canReadTopicMemory;
|
|
25569
|
+
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;
|
|
25570
|
+
const visibleIndexCandidates = [
|
|
25571
|
+
...parseWikiIndex(resolve17(runtime().wikiDir, "topic-index.md")),
|
|
25572
|
+
...parseWikiIndex(resolve17(runtime().wikiDir, "article-index.md"))
|
|
25573
|
+
].filter((candidate) => kind === "all" || candidate.kind === kind).filter((candidate) => candidate.kind !== "topic" || !canReadTopicMemory || canReadTopicMemory(candidate.key, runtime().userId));
|
|
25574
|
+
const hasStaleExactIndexMatch = visibleIndexCandidates.some((candidate) => !indexCandidateExists(candidate) && scoreIndexKey(question, candidate.key) >= 100);
|
|
25575
|
+
const indexCandidates = visibleIndexCandidates.filter(indexCandidateExists).map((candidate) => ({
|
|
25576
|
+
candidate,
|
|
25577
|
+
keyScore: scoreIndexKey(question, candidate.key),
|
|
25578
|
+
score: scoreIndexCandidate(question, candidate)
|
|
25579
|
+
})).filter(({ keyScore, score }) => keyScore >= 25 || score >= 40).sort((left, right) => right.score - left.score || left.candidate.key.localeCompare(right.candidate.key));
|
|
25580
|
+
const strongestIndex = indexCandidates[0];
|
|
25581
|
+
if (kind === "topic" && strongestIndex && (strongestIndex.keyScore >= 25 || strongestIndex.score >= 40)) {
|
|
25582
|
+
const runnerUp = indexCandidates[1];
|
|
25583
|
+
const ambiguous = strongestIndex.keyScore < 100 && runnerUp !== undefined && runnerUp.score >= strongestIndex.score * 0.9;
|
|
25584
|
+
if (ambiguous) {
|
|
25585
|
+
const lines2 = ["Found 2 ambiguous wiki candidates:", ""];
|
|
25586
|
+
for (const { candidate, score } of [strongestIndex, runnerUp]) {
|
|
25587
|
+
lines2.push(`- kind: ${candidate.kind}`, ` key: ${candidate.key}`, ` score: ${score.toFixed(2)}`, ` description: ${candidate.description || "(none)"}`, ...candidate.date ? [` date: ${candidate.date}`] : []);
|
|
25588
|
+
}
|
|
25589
|
+
return { content: [{ type: "text", text: lines2.join(`
|
|
25590
|
+
`) }] };
|
|
25591
|
+
}
|
|
25592
|
+
const lines = ["Found 1 wiki candidate:", ""];
|
|
25593
|
+
for (const { candidate, score } of [strongestIndex]) {
|
|
25594
|
+
lines.push(`- kind: ${candidate.kind}`, ` key: ${candidate.key}`, ` score: ${score.toFixed(2)}`, ` description: ${candidate.description || "(none)"}`, ...candidate.date ? [` date: ${candidate.date}`] : []);
|
|
25595
|
+
}
|
|
25596
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
25597
|
+
`) }] };
|
|
25598
|
+
}
|
|
25076
25599
|
const results = [];
|
|
25077
25600
|
const scored = [];
|
|
25078
25601
|
function scan(dir, label) {
|
|
@@ -25088,68 +25611,266 @@ function wikiQuery(args) {
|
|
|
25088
25611
|
continue;
|
|
25089
25612
|
const fp = join36(sub, f.name);
|
|
25090
25613
|
try {
|
|
25091
|
-
const text2 =
|
|
25614
|
+
const text2 = readFileSync23(fp, "utf-8");
|
|
25615
|
+
const key = `${entry.name}/${f.name.replace(/\.md$/i, "")}`;
|
|
25092
25616
|
scored.push({
|
|
25093
|
-
|
|
25617
|
+
kind: label === "articles" ? "article" : "skill",
|
|
25618
|
+
key,
|
|
25619
|
+
keyScore: scoreIndexKey(question, key),
|
|
25620
|
+
score: scoreMatch(text2, key, label === "articles" ? "article" : "skill"),
|
|
25094
25621
|
path: `${label}/${entry.name}/${f.name}`,
|
|
25095
|
-
text: text2
|
|
25622
|
+
text: text2,
|
|
25623
|
+
title: titleFromDocument(text2, key)
|
|
25096
25624
|
});
|
|
25097
25625
|
} catch {}
|
|
25098
25626
|
}
|
|
25099
25627
|
} catch {}
|
|
25100
25628
|
}
|
|
25101
25629
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
25630
|
+
if (label === "topic" && canReadTopicMemory && !canReadTopicMemory(entry.name.replace(/\.md$/i, ""), runtime().userId)) {
|
|
25631
|
+
continue;
|
|
25632
|
+
}
|
|
25102
25633
|
const fp = join36(dir, entry.name);
|
|
25103
25634
|
try {
|
|
25104
|
-
const text2 =
|
|
25635
|
+
const text2 = readFileSync23(fp, "utf-8");
|
|
25636
|
+
const key = entry.name.replace(/\.md$/i, "");
|
|
25637
|
+
const resultKind = label === "articles" ? "article" : label === "topic" ? "topic" : label === "summaries" ? "summary" : "skill";
|
|
25105
25638
|
scored.push({
|
|
25106
|
-
|
|
25639
|
+
kind: resultKind,
|
|
25640
|
+
key,
|
|
25641
|
+
keyScore: scoreIndexKey(question, key),
|
|
25642
|
+
score: scoreMatch(text2, key, resultKind),
|
|
25107
25643
|
path: `${label}/${entry.name}`,
|
|
25108
|
-
text: text2
|
|
25644
|
+
text: text2,
|
|
25645
|
+
title: titleFromDocument(text2, key),
|
|
25646
|
+
...label === "summaries" && /^\d{4}-\d{2}-\d{2}/.test(key) ? { date: key.slice(0, 10) } : {}
|
|
25109
25647
|
});
|
|
25110
25648
|
} catch {}
|
|
25111
25649
|
}
|
|
25112
25650
|
}
|
|
25113
25651
|
} catch {}
|
|
25114
25652
|
}
|
|
25115
|
-
function
|
|
25116
|
-
const
|
|
25653
|
+
function titleFromDocument(text2, key) {
|
|
25654
|
+
const titleLine = text2.split(`
|
|
25655
|
+
`).find((line) => /^#+\s*/.test(line));
|
|
25656
|
+
return titleLine ? titleLine.replace(/^#+\s*/, "") : basename7(key);
|
|
25657
|
+
}
|
|
25658
|
+
function scoreMatch(text2, key, resultKind) {
|
|
25659
|
+
const titleLine = text2.split(`
|
|
25660
|
+
`).find((line) => /^#+\s*/.test(line)) ?? "";
|
|
25661
|
+
const title = titleLine.replace(/^#+\s*/, "");
|
|
25662
|
+
const normalizedKey = normalizeSearchText(key);
|
|
25663
|
+
const normalizedTitle2 = normalizeSearchText(title);
|
|
25664
|
+
const normalizedBody = normalizeSearchText(text2.replace(titleLine, ""));
|
|
25665
|
+
const compactQuery = compactSearchText(normalizedQuery);
|
|
25666
|
+
const compactKey = compactSearchText(normalizedKey);
|
|
25667
|
+
const compactTitle = compactSearchText(normalizedTitle2);
|
|
25668
|
+
const tokens = canonicalSearchTokens(normalizedQuery);
|
|
25669
|
+
const keyTokens = new Set(canonicalSearchTokens(normalizedKey));
|
|
25670
|
+
const titleTokens = new Set(canonicalSearchTokens(normalizedTitle2));
|
|
25671
|
+
const bodyTokens = canonicalSearchTokens(normalizedBody);
|
|
25117
25672
|
let score = 0;
|
|
25118
|
-
|
|
25119
|
-
|
|
25120
|
-
|
|
25121
|
-
|
|
25122
|
-
|
|
25123
|
-
|
|
25124
|
-
|
|
25125
|
-
|
|
25673
|
+
let strongMetadataEvidence = false;
|
|
25674
|
+
if (normalizedKey === normalizedQuery) {
|
|
25675
|
+
score += 120;
|
|
25676
|
+
strongMetadataEvidence = true;
|
|
25677
|
+
} else if (compactKey === compactQuery) {
|
|
25678
|
+
score += 110;
|
|
25679
|
+
strongMetadataEvidence = true;
|
|
25680
|
+
} else if (compactQuery.length >= 2 && (compactKey.startsWith(compactQuery) || compactQuery.startsWith(compactKey))) {
|
|
25681
|
+
score += 65;
|
|
25682
|
+
strongMetadataEvidence = true;
|
|
25683
|
+
}
|
|
25684
|
+
if (tokens.length > 0 && compactQuery.length >= 3 && compactKey.includes(compactQuery)) {
|
|
25685
|
+
score += 45;
|
|
25686
|
+
strongMetadataEvidence = true;
|
|
25687
|
+
}
|
|
25688
|
+
const fuzzyKeyScore = fuzzySearchScore(compactQuery, compactKey, 55);
|
|
25689
|
+
score += fuzzyKeyScore;
|
|
25690
|
+
if (fuzzyKeyScore > 0)
|
|
25691
|
+
strongMetadataEvidence = true;
|
|
25692
|
+
if (normalizedTitle2 === normalizedQuery) {
|
|
25693
|
+
score += 115;
|
|
25694
|
+
strongMetadataEvidence = true;
|
|
25695
|
+
} else if (compactTitle === compactQuery) {
|
|
25696
|
+
score += 105;
|
|
25697
|
+
strongMetadataEvidence = true;
|
|
25698
|
+
} else if (compactQuery.length >= 2 && compactTitle.startsWith(compactQuery)) {
|
|
25699
|
+
score += 60;
|
|
25700
|
+
strongMetadataEvidence = true;
|
|
25701
|
+
}
|
|
25702
|
+
const fuzzyTitleScore = fuzzySearchScore(compactQuery, compactTitle, 50);
|
|
25703
|
+
score += fuzzyTitleScore;
|
|
25704
|
+
if (fuzzyTitleScore > 0)
|
|
25705
|
+
strongMetadataEvidence = true;
|
|
25706
|
+
if (resultKind === "summary") {
|
|
25707
|
+
const temporalScore = scoreSummaryTime(normalizedQuery, key, title);
|
|
25708
|
+
if (temporalScore === null)
|
|
25709
|
+
return 0;
|
|
25710
|
+
score += temporalScore;
|
|
25711
|
+
if (temporalScore > 0)
|
|
25712
|
+
strongMetadataEvidence = true;
|
|
25713
|
+
}
|
|
25714
|
+
const isMultiToken = tokens.length > 1;
|
|
25715
|
+
if (tokens.length > 0 && normalizedTitle2.includes(normalizedQuery)) {
|
|
25716
|
+
score += 35;
|
|
25717
|
+
strongMetadataEvidence = true;
|
|
25718
|
+
}
|
|
25719
|
+
const phraseMatches = isMultiToken ? countPhrase(normalizedBody, normalizedQuery) : 0;
|
|
25720
|
+
if (phraseMatches > 0)
|
|
25721
|
+
score += phraseMatches * 12;
|
|
25722
|
+
else if (tokens.length === 1 && tokens[0].length >= 5 && normalizedBody.includes(tokens[0])) {
|
|
25723
|
+
score += 16;
|
|
25724
|
+
}
|
|
25725
|
+
let bodyMatches = 0;
|
|
25726
|
+
let minimumBodyFrequency = Number.POSITIVE_INFINITY;
|
|
25727
|
+
for (const token of tokens) {
|
|
25728
|
+
if (keyTokens.has(token))
|
|
25729
|
+
score += 10;
|
|
25730
|
+
if (titleTokens.has(token))
|
|
25731
|
+
score += 12;
|
|
25732
|
+
const bodyFrequency = countToken(bodyTokens, token);
|
|
25733
|
+
if (bodyFrequency > 0) {
|
|
25734
|
+
score += Math.min(bodyFrequency, 3) * 4;
|
|
25735
|
+
bodyMatches += 1;
|
|
25736
|
+
minimumBodyFrequency = Math.min(minimumBodyFrequency, bodyFrequency);
|
|
25737
|
+
}
|
|
25738
|
+
}
|
|
25739
|
+
if (isMultiToken && bodyMatches === tokens.length) {
|
|
25740
|
+
score += 8 * Math.min(minimumBodyFrequency, 3);
|
|
25741
|
+
}
|
|
25742
|
+
const minimumBodyTokens = hasStaleExactIndexMatch ? 3 : 2;
|
|
25743
|
+
const partialArticleEvidence = resultKind === "article" && bodyMatches >= 3 && bodyMatches / tokens.length >= 0.6;
|
|
25744
|
+
const strongBodyEvidence = tokens.length >= minimumBodyTokens && (bodyMatches === tokens.length && (phraseMatches > 0 || minimumBodyFrequency >= 1) || partialArticleEvidence);
|
|
25745
|
+
if (!strongMetadataEvidence && !strongBodyEvidence)
|
|
25746
|
+
return 0;
|
|
25747
|
+
return score;
|
|
25748
|
+
}
|
|
25749
|
+
const bodyKinds = [
|
|
25750
|
+
...kind === "all" || kind === "article" ? ["article"] : [],
|
|
25751
|
+
...kind === "all" || kind === "summary" ? ["summary"] : []
|
|
25752
|
+
];
|
|
25753
|
+
if (bodyKinds.length > 0) {
|
|
25754
|
+
const searchIndex = openWikiSearchIndex();
|
|
25755
|
+
if (searchIndex) {
|
|
25756
|
+
try {
|
|
25757
|
+
const terms = canonicalSearchTokens(normalizedQuery);
|
|
25758
|
+
const ids = searchIndex.matchingDocumentIds(bodyKinds, terms);
|
|
25759
|
+
if (temporalDirection && bodyKinds.includes("summary")) {
|
|
25760
|
+
for (const item of searchIndex.summaryMetadataByDate({
|
|
25761
|
+
direction: temporalDirection === "newest" ? "latest" : "oldest",
|
|
25762
|
+
limit
|
|
25763
|
+
})) {
|
|
25764
|
+
ids.add(item.id);
|
|
25765
|
+
}
|
|
25766
|
+
}
|
|
25767
|
+
for (const document of searchIndex.documents(ids)) {
|
|
25768
|
+
scored.push({
|
|
25769
|
+
kind: document.kind,
|
|
25770
|
+
key: document.key,
|
|
25771
|
+
keyScore: scoreIndexKey(question, document.key),
|
|
25772
|
+
score: scoreMatch(document.text, document.key, document.kind),
|
|
25773
|
+
path: `${document.kind === "article" ? "articles" : "summaries"}/${document.key}.md`,
|
|
25774
|
+
text: document.text,
|
|
25775
|
+
title: document.title,
|
|
25776
|
+
...document.date ? { date: document.date } : {}
|
|
25777
|
+
});
|
|
25778
|
+
}
|
|
25779
|
+
} catch {} finally {
|
|
25780
|
+
searchIndex.close();
|
|
25126
25781
|
}
|
|
25127
25782
|
}
|
|
25128
|
-
return score;
|
|
25129
25783
|
}
|
|
25130
|
-
|
|
25131
|
-
if (runtime().surface === "all")
|
|
25784
|
+
if (kind === "all" && runtime().surface === "all")
|
|
25132
25785
|
scan(runtime().skillsDir, "skills");
|
|
25133
|
-
|
|
25134
|
-
|
|
25135
|
-
|
|
25136
|
-
const
|
|
25137
|
-
|
|
25786
|
+
if (kind === "all" || kind === "topic")
|
|
25787
|
+
scan(runtime().topicsDir, "topic");
|
|
25788
|
+
const merged = new Map;
|
|
25789
|
+
for (const { candidate, keyScore, score } of indexCandidates) {
|
|
25790
|
+
merged.set(`${candidate.kind}:${candidate.key}`, {
|
|
25791
|
+
kind: candidate.kind,
|
|
25792
|
+
key: candidate.key,
|
|
25793
|
+
keyScore,
|
|
25794
|
+
score,
|
|
25795
|
+
description: candidate.description,
|
|
25796
|
+
date: candidate.date
|
|
25797
|
+
});
|
|
25798
|
+
}
|
|
25799
|
+
const documentThreshold = kind === "topic" ? 24 : 16;
|
|
25800
|
+
for (const document of scored) {
|
|
25801
|
+
if (document.score < documentThreshold)
|
|
25802
|
+
continue;
|
|
25803
|
+
const id = `${document.kind}:${document.key}`;
|
|
25804
|
+
const indexed = merged.get(id);
|
|
25805
|
+
merged.set(id, indexed ? { ...indexed, ...document, score: Math.max(indexed.score, document.score) } : document);
|
|
25806
|
+
}
|
|
25807
|
+
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));
|
|
25808
|
+
if (kind === "summary") {
|
|
25809
|
+
const sameTopic = ranked.filter((result) => {
|
|
25810
|
+
const identity = summaryTopicIdentity(result.key);
|
|
25811
|
+
return identity.length >= 3 && normalizedQuery.includes(identity);
|
|
25812
|
+
});
|
|
25813
|
+
if (sameTopic.length > 0)
|
|
25814
|
+
ranked = sameTopic;
|
|
25815
|
+
const requestedDateMatch = normalizedQuery.match(/\b(\d{4}) (\d{2}) (\d{2})\b/);
|
|
25816
|
+
const requestedDate = requestedDateMatch ? `${requestedDateMatch[1]}-${requestedDateMatch[2]}-${requestedDateMatch[3]}` : undefined;
|
|
25817
|
+
const beforeDate = /\bbefore\b/.test(normalizedQuery);
|
|
25818
|
+
const afterDate = /\bafter\b/.test(normalizedQuery);
|
|
25819
|
+
if (requestedDate && beforeDate) {
|
|
25820
|
+
ranked = ranked.filter((result) => !result.date || result.date < requestedDate);
|
|
25821
|
+
} else if (requestedDate && afterDate) {
|
|
25822
|
+
ranked = ranked.filter((result) => !result.date || result.date > requestedDate);
|
|
25823
|
+
}
|
|
25824
|
+
ranked.sort((left, right) => {
|
|
25825
|
+
if (beforeDate) {
|
|
25826
|
+
return (right.date ?? "").localeCompare(left.date ?? "") || right.score - left.score;
|
|
25827
|
+
}
|
|
25828
|
+
if (afterDate) {
|
|
25829
|
+
return (left.date ?? "").localeCompare(right.date ?? "") || right.score - left.score;
|
|
25830
|
+
}
|
|
25831
|
+
if (requestedDate) {
|
|
25832
|
+
const dateDifference = Number(right.date === requestedDate) - Number(left.date === requestedDate);
|
|
25833
|
+
if (dateDifference !== 0)
|
|
25834
|
+
return dateDifference;
|
|
25835
|
+
}
|
|
25836
|
+
if (temporalDirection) {
|
|
25837
|
+
const dateDifference = temporalDirection === "oldest" ? (left.date ?? "").localeCompare(right.date ?? "") : (right.date ?? "").localeCompare(left.date ?? "");
|
|
25838
|
+
if (dateDifference !== 0)
|
|
25839
|
+
return dateDifference;
|
|
25840
|
+
}
|
|
25841
|
+
return right.score - left.score || (right.date ?? "").localeCompare(left.date ?? "") || left.key.localeCompare(right.key);
|
|
25842
|
+
});
|
|
25843
|
+
}
|
|
25844
|
+
if (kind === "topic" && ranked[0]) {
|
|
25845
|
+
const runnerUp = ranked[1];
|
|
25846
|
+
const ambiguous = ranked[0].keyScore < 100 && runnerUp !== undefined && runnerUp.score >= ranked[0].score * 0.9;
|
|
25847
|
+
if (ambiguous) {
|
|
25848
|
+
ranked = ranked.slice(0, 2);
|
|
25849
|
+
} else {
|
|
25850
|
+
ranked = ranked.slice(0, 1);
|
|
25851
|
+
}
|
|
25852
|
+
} else {
|
|
25853
|
+
ranked = ranked.slice(0, limit);
|
|
25854
|
+
}
|
|
25855
|
+
if (ranked.length === 0) {
|
|
25138
25856
|
results.push("No matching wiki articles found.");
|
|
25139
25857
|
} else {
|
|
25140
|
-
results.push(`Found ${
|
|
25858
|
+
results.push(`Found ${ranked.length} matching result(s):
|
|
25141
25859
|
`);
|
|
25142
|
-
for (const
|
|
25143
|
-
|
|
25144
|
-
|
|
25145
|
-
|
|
25146
|
-
|
|
25147
|
-
|
|
25148
|
-
|
|
25149
|
-
|
|
25150
|
-
${body}${text2.length > 400 ? `
|
|
25860
|
+
for (const result of ranked) {
|
|
25861
|
+
if (result.kind === "skill") {
|
|
25862
|
+
const body = (result.text ?? "").trim().slice(0, 400);
|
|
25863
|
+
results.push(`## ${result.title ?? result.key}
|
|
25864
|
+
Path: ${result.path}
|
|
25865
|
+
|
|
25866
|
+
${body}${(result.text?.length ?? 0) > 400 ? `
|
|
25151
25867
|
...(truncated)` : ""}
|
|
25152
25868
|
`);
|
|
25869
|
+
continue;
|
|
25870
|
+
}
|
|
25871
|
+
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 ? [
|
|
25872
|
+
` excerpt: ${result.text.replace(/^---[\s\S]*?---\n?/, "").replace(/^#.*$/m, "").trim().slice(0, 400)}`
|
|
25873
|
+
] : []);
|
|
25153
25874
|
}
|
|
25154
25875
|
}
|
|
25155
25876
|
return { content: [{ type: "text", text: results.join(`
|
|
@@ -25168,36 +25889,94 @@ function wikiTopicBrief(args) {
|
|
|
25168
25889
|
};
|
|
25169
25890
|
}
|
|
25170
25891
|
const { getTopicBrief: getTopicBrief2, resolveTopicBrief: resolveTopicBrief2 } = runtime().host;
|
|
25171
|
-
|
|
25892
|
+
const hasAuthorizedBridge = Boolean(runtime().topicId && (getTopicBrief2 || resolveTopicBrief2));
|
|
25893
|
+
if (hasAuthorizedBridge) {
|
|
25172
25894
|
try {
|
|
25173
25895
|
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(`
|
|
25896
|
+
if (brief) {
|
|
25897
|
+
const lines = [
|
|
25898
|
+
`# Topic Brief: ${topicNameFrom(rawTopic)}`,
|
|
25899
|
+
"",
|
|
25900
|
+
brief.briefMd || "(empty brief)"
|
|
25901
|
+
];
|
|
25902
|
+
if (brief.latestSummaryMd) {
|
|
25903
|
+
lines.push("", "## Latest Summary", "", brief.latestSummaryMd);
|
|
25904
|
+
}
|
|
25905
|
+
if (brief.summaryDate) {
|
|
25906
|
+
lines.push("", `Summary date: ${brief.summaryDate}`);
|
|
25907
|
+
}
|
|
25908
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
25191
25909
|
`) }] };
|
|
25192
|
-
|
|
25910
|
+
}
|
|
25911
|
+
} catch {}
|
|
25912
|
+
return {
|
|
25913
|
+
content: [{ type: "text", text: `No brief found for topic: ${rawTopic}` }]
|
|
25914
|
+
};
|
|
25915
|
+
}
|
|
25916
|
+
const topicKey = wikiSummarySlug(rawTopic.replace(/^topic\//, "").replace(/\.md$/i, ""));
|
|
25917
|
+
const filePath = resolve17(runtime().topicsDir, `${topicKey}.md`);
|
|
25918
|
+
try {
|
|
25919
|
+
const content = readFileSync23(filePath, "utf-8");
|
|
25920
|
+
return {
|
|
25921
|
+
content: [{ type: "text", text: `# Topic Brief: ${topicKey}
|
|
25922
|
+
|
|
25923
|
+
${content}` }]
|
|
25924
|
+
};
|
|
25925
|
+
} catch {}
|
|
25926
|
+
return {
|
|
25927
|
+
content: [{ type: "text", text: `No brief found for topic: ${rawTopic}` }]
|
|
25928
|
+
};
|
|
25929
|
+
}
|
|
25930
|
+
function wikiRead(args) {
|
|
25931
|
+
const rawKind = String(safeExt(args, ["kind", "type"], "article"));
|
|
25932
|
+
const kind = rawKind === "topic" || rawKind === "summary" ? rawKind : "article";
|
|
25933
|
+
const rawKey = String(safeExt(args, ["key", "slug", "topic"], "")).trim();
|
|
25934
|
+
if (!rawKey) {
|
|
25935
|
+
return { content: [{ type: "text", text: "A wiki document key is required." }], isError: true };
|
|
25936
|
+
}
|
|
25937
|
+
if (kind === "topic") {
|
|
25938
|
+
const result = wikiTopicBrief({ topic: rawKey });
|
|
25939
|
+
const resultText = result.content.map((entry) => entry.type === "text" ? entry.text : "").join(`
|
|
25940
|
+
`);
|
|
25941
|
+
const adopt = safeExt(args, ["adopt", "use", "select"], false);
|
|
25942
|
+
if (!adopt || resultText.startsWith("No brief found for topic"))
|
|
25943
|
+
return result;
|
|
25944
|
+
const currentTopicId = runtime().currentTopicId;
|
|
25945
|
+
const memoryKey = wikiSummarySlug(rawKey.replace(/^topic\//, "").replace(/\.md$/i, ""));
|
|
25946
|
+
if (!currentTopicId || !runtime().host.adoptTopicMemory?.(currentTopicId, runtime().userId, memoryKey)) {
|
|
25193
25947
|
return {
|
|
25194
|
-
content: [
|
|
25948
|
+
content: [
|
|
25949
|
+
...result.content,
|
|
25950
|
+
{ type: "text", text: `
|
|
25951
|
+
Memory was read but could not be adopted by this topic.` }
|
|
25952
|
+
]
|
|
25195
25953
|
};
|
|
25196
25954
|
}
|
|
25955
|
+
return {
|
|
25956
|
+
content: [...result.content, { type: "text", text: `
|
|
25957
|
+
Adopted topic memory: ${memoryKey}` }]
|
|
25958
|
+
};
|
|
25959
|
+
}
|
|
25960
|
+
if (rawKey.includes("\\") || rawKey.split("/").some((part) => part === "..") || rawKey.startsWith("/")) {
|
|
25961
|
+
return { content: [{ type: "text", text: "Invalid wiki document key." }], isError: true };
|
|
25962
|
+
}
|
|
25963
|
+
const root = kind === "summary" ? runtime().summariesDir : runtime().articlesDir;
|
|
25964
|
+
const key = rawKey.replace(new RegExp(`^(?:${kind === "summary" ? "summaries" : "articles"})/`), "").replace(/\.md$/i, "");
|
|
25965
|
+
const filePath = resolve17(root, `${key}.md`);
|
|
25966
|
+
const relativePath = relative2(root, filePath);
|
|
25967
|
+
if (relativePath.startsWith("..") || relativePath.startsWith("/")) {
|
|
25968
|
+
return { content: [{ type: "text", text: "Invalid wiki document key." }], isError: true };
|
|
25969
|
+
}
|
|
25970
|
+
try {
|
|
25971
|
+
const content = readFileSync23(filePath, "utf-8");
|
|
25972
|
+
return {
|
|
25973
|
+
content: [{ type: "text", text: `# Wiki ${kind}: ${key}
|
|
25974
|
+
|
|
25975
|
+
${content}` }]
|
|
25976
|
+
};
|
|
25977
|
+
} catch {
|
|
25978
|
+
return { content: [{ type: "text", text: `No ${kind} found for: ${rawKey}` }] };
|
|
25197
25979
|
}
|
|
25198
|
-
return {
|
|
25199
|
-
content: [{ type: "text", text: `No topic brief found for: ${rawTopic}` }]
|
|
25200
|
-
};
|
|
25201
25980
|
}
|
|
25202
25981
|
function wikiLastConversation(args) {
|
|
25203
25982
|
const rawTopic = safeExt(args, ["topic", "topicName", "topicId"], runtime().topicId);
|
|
@@ -25209,18 +25988,18 @@ function wikiLastConversation(args) {
|
|
|
25209
25988
|
const name = slugify(topicNameFrom(rawTopic));
|
|
25210
25989
|
const archiveDir = resolve17(runtime().archiveDir, name);
|
|
25211
25990
|
try {
|
|
25212
|
-
let files =
|
|
25991
|
+
let files = existsSync27(archiveDir) ? readdirSync10(archiveDir).filter((f) => f.endsWith(".jsonl")).sort().reverse() : [];
|
|
25213
25992
|
if (files.length === 0) {
|
|
25214
|
-
files =
|
|
25993
|
+
files = existsSync27(runtime().archiveDir) ? readdirSync10(runtime().archiveDir).filter((f) => f.startsWith(name) && f.endsWith(".jsonl")).sort().reverse() : [];
|
|
25215
25994
|
}
|
|
25216
25995
|
if (files.length === 0) {
|
|
25217
25996
|
return {
|
|
25218
25997
|
content: [{ type: "text", text: `No archive found for topic: ${rawTopic}` }]
|
|
25219
25998
|
};
|
|
25220
25999
|
}
|
|
25221
|
-
const actualPath = files[0].includes("/") ? files[0] : resolve17(
|
|
26000
|
+
const actualPath = files[0].includes("/") ? files[0] : resolve17(existsSync27(archiveDir) && readdirSync10(archiveDir).includes(files[0]) ? archiveDir : runtime().archiveDir, files[0]);
|
|
25222
26001
|
try {
|
|
25223
|
-
const raw =
|
|
26002
|
+
const raw = readFileSync23(actualPath, "utf-8");
|
|
25224
26003
|
const lines = raw.trim().split(`
|
|
25225
26004
|
`).filter(Boolean);
|
|
25226
26005
|
const turns2 = [];
|
|
@@ -25293,7 +26072,7 @@ function skillQuery(args) {
|
|
|
25293
26072
|
continue;
|
|
25294
26073
|
const skillFile = resolve17(runtime().skillsDir, entry.name, "skill.md");
|
|
25295
26074
|
try {
|
|
25296
|
-
const text2 =
|
|
26075
|
+
const text2 = readFileSync23(skillFile, "utf-8");
|
|
25297
26076
|
const lower = text2.toLowerCase();
|
|
25298
26077
|
let score = 0;
|
|
25299
26078
|
for (const w of query2.split(/\s+/).filter(Boolean)) {
|
|
@@ -25364,7 +26143,7 @@ ${fresh.join(`
|
|
|
25364
26143
|
const skillPath = resolve17(skillDir, "skill.md");
|
|
25365
26144
|
let finalContent = content;
|
|
25366
26145
|
try {
|
|
25367
|
-
const existing =
|
|
26146
|
+
const existing = readFileSync23(skillPath, "utf-8");
|
|
25368
26147
|
finalContent = mergeGotchas(content, extractGotchas(existing));
|
|
25369
26148
|
} catch {}
|
|
25370
26149
|
writeFileSync17(skillPath, finalContent, "utf-8");
|
|
@@ -25377,15 +26156,7 @@ ${fresh.join(`
|
|
|
25377
26156
|
]
|
|
25378
26157
|
};
|
|
25379
26158
|
}
|
|
25380
|
-
function
|
|
25381
|
-
const rawTopic = safeExt(args, ["topic", "topicName"], "");
|
|
25382
|
-
const content = safeExt(args, ["content", "text", "body"], "");
|
|
25383
|
-
if (!rawTopic)
|
|
25384
|
-
return { content: [{ type: "text", text: "Missing topic." }] };
|
|
25385
|
-
if (!content)
|
|
25386
|
-
return { content: [{ type: "text", text: "Missing content." }] };
|
|
25387
|
-
const now = new Date;
|
|
25388
|
-
const dateStr = now.toISOString().slice(0, 10);
|
|
26159
|
+
function writeSummaryDocument(rawTopic, content, dateStr) {
|
|
25389
26160
|
const topicId = runtime().topicId;
|
|
25390
26161
|
const fileSlug = wikiBriefStorageKey(rawTopic, topicId);
|
|
25391
26162
|
ensureDir(runtime().summariesDir);
|
|
@@ -25412,7 +26183,7 @@ function saveWikiEntry(args) {
|
|
|
25412
26183
|
if (topicId && setTopicBrief2) {
|
|
25413
26184
|
try {
|
|
25414
26185
|
const briefPath = resolve17(runtime().topicsDir, `${fileSlug}.md`);
|
|
25415
|
-
let briefMd =
|
|
26186
|
+
let briefMd = existsSync27(briefPath) ? readFileSync23(briefPath, "utf-8") : undefined;
|
|
25416
26187
|
if (briefMd === undefined && getTopicBrief2 && !getTopicBrief2(fileSlug)?.briefMd) {
|
|
25417
26188
|
const legacy = getTopicBrief2(topicId)?.briefMd;
|
|
25418
26189
|
if (legacy)
|
|
@@ -25426,28 +26197,14 @@ function saveWikiEntry(args) {
|
|
|
25426
26197
|
sqliteUpdated = true;
|
|
25427
26198
|
} catch {}
|
|
25428
26199
|
}
|
|
25429
|
-
return {
|
|
25430
|
-
content: [
|
|
25431
|
-
{
|
|
25432
|
-
type: "text",
|
|
25433
|
-
text: `Saved summary: summaries/${summaryName}` + (sqliteUpdated ? `
|
|
25434
|
-
SQLite latest-summary also updated.` : "")
|
|
25435
|
-
}
|
|
25436
|
-
]
|
|
25437
|
-
};
|
|
26200
|
+
return { slug: summaryName.replace(/\.md$/i, ""), sqliteUpdated };
|
|
25438
26201
|
}
|
|
25439
|
-
function
|
|
25440
|
-
const rawTopic = safeExt(args, ["topic", "topicName"], "");
|
|
25441
|
-
const content = safeExt(args, ["content", "brief", "text", "body"], "");
|
|
25442
|
-
if (!rawTopic)
|
|
25443
|
-
return { content: [{ type: "text", text: "Missing topic." }] };
|
|
25444
|
-
if (!content)
|
|
25445
|
-
return { content: [{ type: "text", text: "Missing content." }] };
|
|
26202
|
+
function writeTopicDocument(rawTopic, content) {
|
|
25446
26203
|
const topicId = runtime().topicId;
|
|
25447
26204
|
const fileSlug = wikiBriefStorageKey(rawTopic, topicId);
|
|
25448
26205
|
const briefPath = resolve17(runtime().topicsDir, `${fileSlug}.md`);
|
|
25449
26206
|
ensureDir(dirname16(briefPath));
|
|
25450
|
-
const existed =
|
|
26207
|
+
const existed = existsSync27(briefPath);
|
|
25451
26208
|
writeFileSync17(briefPath, content, "utf-8");
|
|
25452
26209
|
const setTopicBrief2 = runtime().host.setTopicBrief;
|
|
25453
26210
|
let sqliteUpdated = false;
|
|
@@ -25457,68 +26214,227 @@ function saveTopicBrief(args) {
|
|
|
25457
26214
|
sqliteUpdated = true;
|
|
25458
26215
|
} catch {}
|
|
25459
26216
|
}
|
|
26217
|
+
return { slug: fileSlug, existed, sqliteUpdated };
|
|
26218
|
+
}
|
|
26219
|
+
function writeArticleDocument(slug, content) {
|
|
26220
|
+
const articlePath = resolve17(runtime().articlesDir, `${slug}.md`);
|
|
26221
|
+
ensureDir(dirname16(articlePath));
|
|
26222
|
+
const existed = existsSync27(articlePath);
|
|
26223
|
+
atomicWriteFile(articlePath, content);
|
|
26224
|
+
return { existed };
|
|
26225
|
+
}
|
|
26226
|
+
function wikiWrite(args) {
|
|
26227
|
+
const rawKind = safeExt(args, ["kind", "type"], "");
|
|
26228
|
+
const content = safeExt(args, ["content", "text", "body", "brief"], "");
|
|
26229
|
+
const rawDescription = safeExt(args, ["description", "desc"], "");
|
|
26230
|
+
const rawTopic = safeExt(args, ["topic", "topicName"], "");
|
|
26231
|
+
const rawSlug = safeExt(args, ["slug", "id"], "");
|
|
26232
|
+
const section = safeExt(args, ["section", "category"], undefined);
|
|
26233
|
+
const rawDate = safeExt(args, ["date", "created"], undefined);
|
|
26234
|
+
if (rawKind !== "summary" && rawKind !== "article" && rawKind !== "topic") {
|
|
26235
|
+
return {
|
|
26236
|
+
content: [{ type: "text", text: 'kind must be one of "summary", "article", "topic".' }],
|
|
26237
|
+
isError: true
|
|
26238
|
+
};
|
|
26239
|
+
}
|
|
26240
|
+
if (!content.trim()) {
|
|
26241
|
+
return { content: [{ type: "text", text: "Missing content." }], isError: true };
|
|
26242
|
+
}
|
|
26243
|
+
const descriptionResult = normalizeIndexDescription(rawDescription);
|
|
26244
|
+
if ("error" in descriptionResult) {
|
|
26245
|
+
return { content: [{ type: "text", text: descriptionResult.error }], isError: true };
|
|
26246
|
+
}
|
|
26247
|
+
const { description } = descriptionResult;
|
|
26248
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
26249
|
+
if (rawKind === "article") {
|
|
26250
|
+
const slugResult = normalizeWikiSlug(rawSlug);
|
|
26251
|
+
if ("error" in slugResult) {
|
|
26252
|
+
return { content: [{ type: "text", text: slugResult.error }], isError: true };
|
|
26253
|
+
}
|
|
26254
|
+
if (!section?.trim()) {
|
|
26255
|
+
return {
|
|
26256
|
+
content: [
|
|
26257
|
+
{ type: "text", text: "An article requires a section so the catalog stays navigable." }
|
|
26258
|
+
],
|
|
26259
|
+
isError: true
|
|
26260
|
+
};
|
|
26261
|
+
}
|
|
26262
|
+
const { existed } = writeArticleDocument(slugResult.slug, content);
|
|
26263
|
+
const link2 = indexRowOrPartialWrite("article", {
|
|
26264
|
+
kind: "article",
|
|
26265
|
+
slug: slugResult.slug,
|
|
26266
|
+
description,
|
|
26267
|
+
date: rawDate ?? today,
|
|
26268
|
+
section: section.trim()
|
|
26269
|
+
});
|
|
26270
|
+
if (typeof link2 !== "string")
|
|
26271
|
+
return link2;
|
|
26272
|
+
const searched = indexWikiSearchDocument("article", slugResult.slug, content);
|
|
26273
|
+
return {
|
|
26274
|
+
content: [
|
|
26275
|
+
{
|
|
26276
|
+
type: "text",
|
|
26277
|
+
text: `Saved article: articles/${slugResult.slug}.md (${existed ? "updated" : "created"})
|
|
26278
|
+
Indexed: ${link2}` + (searched ? "" : `
|
|
26279
|
+
${SEARCH_CACHE_SKIPPED_NOTE}`)
|
|
26280
|
+
}
|
|
26281
|
+
]
|
|
26282
|
+
};
|
|
26283
|
+
}
|
|
26284
|
+
const topic = rawTopic.trim();
|
|
26285
|
+
if (!topic) {
|
|
26286
|
+
return { content: [{ type: "text", text: "Missing topic." }], isError: true };
|
|
26287
|
+
}
|
|
26288
|
+
if (rawKind === "summary") {
|
|
26289
|
+
const dateStr = rawDate ?? today;
|
|
26290
|
+
const written2 = writeSummaryDocument(topic, content, dateStr);
|
|
26291
|
+
const link2 = indexRowOrPartialWrite("summary", {
|
|
26292
|
+
kind: "summary",
|
|
26293
|
+
slug: written2.slug,
|
|
26294
|
+
description,
|
|
26295
|
+
date: dateStr
|
|
26296
|
+
});
|
|
26297
|
+
if (typeof link2 !== "string")
|
|
26298
|
+
return link2;
|
|
26299
|
+
const searched = indexWikiSearchDocument("summary", written2.slug, content);
|
|
26300
|
+
return {
|
|
26301
|
+
content: [
|
|
26302
|
+
{
|
|
26303
|
+
type: "text",
|
|
26304
|
+
text: `Saved summary: summaries/${written2.slug}.md
|
|
26305
|
+
Indexed: ${link2}` + (written2.sqliteUpdated ? `
|
|
26306
|
+
SQLite latest-summary also updated.` : "") + (searched ? "" : `
|
|
26307
|
+
${SEARCH_CACHE_SKIPPED_NOTE}`)
|
|
26308
|
+
}
|
|
26309
|
+
]
|
|
26310
|
+
};
|
|
26311
|
+
}
|
|
26312
|
+
const written = writeTopicDocument(topic, content);
|
|
26313
|
+
const link = indexRowOrPartialWrite("topic", {
|
|
26314
|
+
kind: "topic",
|
|
26315
|
+
slug: written.slug,
|
|
26316
|
+
description,
|
|
26317
|
+
date: rawDate ?? today
|
|
26318
|
+
});
|
|
26319
|
+
if (typeof link !== "string")
|
|
26320
|
+
return link;
|
|
25460
26321
|
return {
|
|
25461
26322
|
content: [
|
|
25462
26323
|
{
|
|
25463
26324
|
type: "text",
|
|
25464
|
-
text: `Saved brief: topic/${
|
|
26325
|
+
text: `Saved brief: topic/${written.slug}.md (${written.existed ? "updated" : "created"})
|
|
26326
|
+
Indexed: ${link}` + (written.sqliteUpdated ? `
|
|
25465
26327
|
SQLite brief also updated.` : "")
|
|
25466
26328
|
}
|
|
25467
26329
|
]
|
|
25468
26330
|
};
|
|
25469
26331
|
}
|
|
25470
|
-
function
|
|
25471
|
-
const
|
|
25472
|
-
|
|
25473
|
-
|
|
25474
|
-
|
|
25475
|
-
|
|
25476
|
-
|
|
25477
|
-
|
|
25478
|
-
|
|
25479
|
-
|
|
25480
|
-
const
|
|
25481
|
-
|
|
25482
|
-
|
|
25483
|
-
|
|
25484
|
-
|
|
25485
|
-
|
|
25486
|
-
|
|
26332
|
+
function collectIndexableDocuments() {
|
|
26333
|
+
const sources = [];
|
|
26334
|
+
function add(kind, key, path) {
|
|
26335
|
+
try {
|
|
26336
|
+
const stat = statSync15(path);
|
|
26337
|
+
if (!stat.isFile())
|
|
26338
|
+
return;
|
|
26339
|
+
sources.push({ kind, key, path, mtimeMs: stat.mtimeMs, size: stat.size });
|
|
26340
|
+
} catch {}
|
|
26341
|
+
}
|
|
26342
|
+
for (const [root, kind, allowSubdirectories] of [
|
|
26343
|
+
[runtime().articlesDir, "article", true],
|
|
26344
|
+
[runtime().summariesDir, "summary", false]
|
|
26345
|
+
]) {
|
|
26346
|
+
let entries;
|
|
26347
|
+
try {
|
|
26348
|
+
entries = readdirSync10(root, { withFileTypes: true });
|
|
26349
|
+
} catch {
|
|
26350
|
+
continue;
|
|
26351
|
+
}
|
|
26352
|
+
for (const entry of entries) {
|
|
26353
|
+
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
26354
|
+
add(kind, entry.name.replace(/\.md$/i, ""), join36(root, entry.name));
|
|
26355
|
+
continue;
|
|
26356
|
+
}
|
|
26357
|
+
if (!entry.isDirectory() || !allowSubdirectories)
|
|
26358
|
+
continue;
|
|
26359
|
+
let nested;
|
|
26360
|
+
try {
|
|
26361
|
+
nested = readdirSync10(join36(root, entry.name), { withFileTypes: true });
|
|
26362
|
+
} catch {
|
|
26363
|
+
continue;
|
|
26364
|
+
}
|
|
26365
|
+
for (const file of nested) {
|
|
26366
|
+
if (!file.isFile() || !file.name.endsWith(".md"))
|
|
26367
|
+
continue;
|
|
26368
|
+
add(kind, `${entry.name}/${file.name.replace(/\.md$/i, "")}`, join36(root, entry.name, file.name));
|
|
26369
|
+
}
|
|
26370
|
+
}
|
|
26371
|
+
}
|
|
26372
|
+
return sources;
|
|
26373
|
+
}
|
|
26374
|
+
function wikiReindex() {
|
|
26375
|
+
const documents = collectIndexableDocuments();
|
|
26376
|
+
const index = openWikiSearchIndex();
|
|
26377
|
+
if (!index) {
|
|
26378
|
+
return {
|
|
26379
|
+
content: [{ type: "text", text: "Could not open the wiki search index." }],
|
|
26380
|
+
isError: true
|
|
26381
|
+
};
|
|
26382
|
+
}
|
|
26383
|
+
let synced;
|
|
26384
|
+
try {
|
|
26385
|
+
synced = index.sync(documents, (source, text2) => prepareWikiSearchDocument(source.kind, source.key, text2));
|
|
26386
|
+
} catch (error2) {
|
|
26387
|
+
return {
|
|
26388
|
+
content: [{ type: "text", text: `Failed to rebuild the search index: ${String(error2)}` }],
|
|
26389
|
+
isError: true
|
|
26390
|
+
};
|
|
26391
|
+
} finally {
|
|
26392
|
+
index.close();
|
|
26393
|
+
}
|
|
26394
|
+
const lines = [
|
|
26395
|
+
`Search index: ${synced.added} added, ${synced.updated} updated, ${synced.removed} removed (${documents.length} documents).`
|
|
26396
|
+
];
|
|
26397
|
+
for (const kind of ["article", "summary"]) {
|
|
26398
|
+
const rows = new Set(parseWikiIndex(wikiIndexPath(kind)).filter((candidate) => candidate.kind === kind).map((candidate) => candidate.key));
|
|
26399
|
+
const keys = documents.filter((source) => source.kind === kind).map((source) => source.key);
|
|
26400
|
+
const unindexed = keys.filter((key) => !rows.has(key));
|
|
26401
|
+
const tombstones = [...rows].filter((key) => !keys.includes(key));
|
|
26402
|
+
lines.push(`${WIKI_INDEX_FILENAMES[kind]}: ${rows.size} rows for ${keys.length} documents` + (unindexed.length > 0 ? `; missing rows: ${unindexed.join(", ")}` : "") + (tombstones.length > 0 ? `; tombstones: ${tombstones.join(", ")}` : ""));
|
|
26403
|
+
}
|
|
26404
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
26405
|
+
`) }] };
|
|
26406
|
+
}
|
|
26407
|
+
function indexRowOrPartialWrite(label, options) {
|
|
26408
|
+
try {
|
|
26409
|
+
return upsertIndexRow(options);
|
|
26410
|
+
} catch (error2) {
|
|
26411
|
+
return {
|
|
26412
|
+
content: [
|
|
26413
|
+
{
|
|
26414
|
+
type: "text",
|
|
26415
|
+
text: `Wrote the ${label} document but failed to index it (${String(error2)}). Retry the same wiki_write call: both writes are idempotent.`
|
|
26416
|
+
}
|
|
26417
|
+
],
|
|
26418
|
+
isError: true
|
|
26419
|
+
};
|
|
26420
|
+
}
|
|
26421
|
+
}
|
|
26422
|
+
function upsertIndexRow(options) {
|
|
26423
|
+
const { kind, slug, description, date, section } = options;
|
|
26424
|
+
const indexPath = wikiIndexPath(kind);
|
|
25487
26425
|
ensureDir(dirname16(indexPath));
|
|
25488
26426
|
const releaseLock = acquireFileLock(indexPath);
|
|
25489
26427
|
try {
|
|
25490
26428
|
let index;
|
|
25491
26429
|
try {
|
|
25492
|
-
index =
|
|
26430
|
+
index = readFileSync23(indexPath, "utf-8");
|
|
25493
26431
|
} catch {
|
|
25494
26432
|
index = "";
|
|
25495
26433
|
}
|
|
25496
26434
|
const lines = index.split(`
|
|
25497
26435
|
`);
|
|
25498
|
-
const
|
|
25499
|
-
|
|
25500
|
-
case "summary":
|
|
25501
|
-
return `- [[summaries/${slug}]] ${desc} (${dateStr})`;
|
|
25502
|
-
case "topic":
|
|
25503
|
-
return `- [[topic/${slug}]] ${desc} (${dateStr})`;
|
|
25504
|
-
case "skill":
|
|
25505
|
-
return `- [[skills/${slug}]] ${desc} (${dateStr})`;
|
|
25506
|
-
default:
|
|
25507
|
-
return `- [[articles/${slug}]] ${desc} (${dateStr})`;
|
|
25508
|
-
}
|
|
25509
|
-
})();
|
|
25510
|
-
const target = (() => {
|
|
25511
|
-
switch (kind) {
|
|
25512
|
-
case "summary":
|
|
25513
|
-
return `summaries/${slug}`;
|
|
25514
|
-
case "topic":
|
|
25515
|
-
return `topic/${slug}`;
|
|
25516
|
-
case "skill":
|
|
25517
|
-
return `skills/${slug}`;
|
|
25518
|
-
default:
|
|
25519
|
-
return `articles/${slug}`;
|
|
25520
|
-
}
|
|
25521
|
-
})();
|
|
26436
|
+
const target = `${WIKI_INDEX_NAMESPACES[kind]}/${slug}`;
|
|
26437
|
+
const link = `- [[${target}]] ${description} (${date})`;
|
|
25522
26438
|
const slugPattern = `[[${target}]]`;
|
|
25523
26439
|
let replaced = false;
|
|
25524
26440
|
for (let i = lines.length - 1;i >= 0; i--) {
|
|
@@ -25556,16 +26472,69 @@ function indexUpsert(args) {
|
|
|
25556
26472
|
}
|
|
25557
26473
|
atomicWriteFile(indexPath, lines.join(`
|
|
25558
26474
|
`));
|
|
25559
|
-
return
|
|
26475
|
+
return link;
|
|
25560
26476
|
} finally {
|
|
25561
26477
|
releaseLock();
|
|
25562
26478
|
}
|
|
25563
26479
|
}
|
|
26480
|
+
function wikiDocumentExists(kind, slug) {
|
|
26481
|
+
const root = kind === "article" ? runtime().articlesDir : kind === "summary" ? runtime().summariesDir : kind === "topic" ? runtime().topicsDir : runtime().skillsDir;
|
|
26482
|
+
const filePath = resolve17(root, `${slug}.md`);
|
|
26483
|
+
if (relative2(root, filePath).startsWith(".."))
|
|
26484
|
+
return false;
|
|
26485
|
+
try {
|
|
26486
|
+
return statSync15(filePath).isFile();
|
|
26487
|
+
} catch {
|
|
26488
|
+
return false;
|
|
26489
|
+
}
|
|
26490
|
+
}
|
|
26491
|
+
function indexUpsert(args) {
|
|
26492
|
+
const rawSlug = safeExt(args, ["slug", "id"], "");
|
|
26493
|
+
const rawDescription = safeExt(args, ["description", "desc", "text"], "");
|
|
26494
|
+
const rawKind = safeExt(args, ["kind", "type"], "article");
|
|
26495
|
+
const section = safeExt(args, ["section", "category"], undefined);
|
|
26496
|
+
const date = safeExt(args, ["date", "created"], undefined);
|
|
26497
|
+
if (!(rawKind in WIKI_INDEX_FILENAMES)) {
|
|
26498
|
+
return {
|
|
26499
|
+
content: [{ type: "text", text: `Unsupported kind: ${rawKind}` }],
|
|
26500
|
+
isError: true
|
|
26501
|
+
};
|
|
26502
|
+
}
|
|
26503
|
+
const kind = rawKind;
|
|
26504
|
+
const slugResult = normalizeWikiSlug(rawSlug);
|
|
26505
|
+
if ("error" in slugResult) {
|
|
26506
|
+
return { content: [{ type: "text", text: slugResult.error }], isError: true };
|
|
26507
|
+
}
|
|
26508
|
+
const descriptionResult = normalizeIndexDescription(rawDescription);
|
|
26509
|
+
if ("error" in descriptionResult) {
|
|
26510
|
+
return { content: [{ type: "text", text: descriptionResult.error }], isError: true };
|
|
26511
|
+
}
|
|
26512
|
+
if (kind !== "topic" && !wikiDocumentExists(kind, slugResult.slug)) {
|
|
26513
|
+
return {
|
|
26514
|
+
content: [
|
|
26515
|
+
{
|
|
26516
|
+
type: "text",
|
|
26517
|
+
text: `No ${kind} document named ${slugResult.slug}. Use wiki_write to create the document and its catalog row together.`
|
|
26518
|
+
}
|
|
26519
|
+
],
|
|
26520
|
+
isError: true
|
|
26521
|
+
};
|
|
26522
|
+
}
|
|
26523
|
+
const link = upsertIndexRow({
|
|
26524
|
+
kind,
|
|
26525
|
+
slug: slugResult.slug,
|
|
26526
|
+
description: descriptionResult.description,
|
|
26527
|
+
date: date ?? new Date().toISOString().slice(0, 10),
|
|
26528
|
+
section
|
|
26529
|
+
});
|
|
26530
|
+
return { content: [{ type: "text", text: `Index updated: ${link}` }] };
|
|
26531
|
+
}
|
|
25564
26532
|
function createWikiMcpServer(context, host2) {
|
|
25565
26533
|
const surface = context.surface ?? "all";
|
|
25566
26534
|
const wikiDir = resolve17(host2.wikiRoot);
|
|
25567
26535
|
const current3 = {
|
|
25568
26536
|
userId: context.userId,
|
|
26537
|
+
currentTopicId: context.currentTopicId ?? context.topicId,
|
|
25569
26538
|
topicId: context.topicId,
|
|
25570
26539
|
surface,
|
|
25571
26540
|
host: host2,
|
|
@@ -25595,12 +26564,13 @@ function createWikiMcpServer(context, host2) {
|
|
|
25595
26564
|
const { name, arguments: args } = request.params;
|
|
25596
26565
|
const handlers = {
|
|
25597
26566
|
wiki_query: wikiQuery,
|
|
26567
|
+
wiki_read: wikiRead,
|
|
25598
26568
|
wiki_topic_brief: wikiTopicBrief,
|
|
25599
26569
|
wiki_last_conversation: wikiLastConversation,
|
|
25600
26570
|
skill_query: skillQuery,
|
|
25601
26571
|
skill_save: skillSave,
|
|
25602
|
-
|
|
25603
|
-
|
|
26572
|
+
wiki_write: wikiWrite,
|
|
26573
|
+
wiki_reindex: wikiReindex,
|
|
25604
26574
|
index_upsert: indexUpsert
|
|
25605
26575
|
};
|
|
25606
26576
|
const handler = tools.some((tool) => tool.name === name) ? handlers[name] : undefined;
|
|
@@ -25609,16 +26579,100 @@ function createWikiMcpServer(context, host2) {
|
|
|
25609
26579
|
});
|
|
25610
26580
|
return server;
|
|
25611
26581
|
}
|
|
25612
|
-
var wikiRuntime, FILE_LOCK_WAIT_MS = 10, FILE_LOCK_TIMEOUT_MS = 5000, FILE_LOCK_STALE_MS = 30000, lockWaitArray, WIKI_TOOLS, SKILL_TOOLS;
|
|
25613
|
-
var init_wiki_server = __esm(() => {
|
|
26582
|
+
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_SEARCH_INDEX_FILENAME = ".wiki-search-index.sqlite", WIKI_INDEX_FILENAMES, WIKI_INDEX_NAMESPACES, MAX_INDEX_DESCRIPTION_LENGTH = 400, SEARCH_CACHE_SKIPPED_NOTE = "Body-search cache was not updated; catalog retrieval still works. Run wiki_reindex to refill it.", WIKI_TOOLS, SKILL_TOOLS;
|
|
26583
|
+
var init_wiki_server = __esm(async () => {
|
|
25614
26584
|
init_config();
|
|
25615
26585
|
init_wiki_summary_names();
|
|
26586
|
+
await init_wiki_search_index();
|
|
25616
26587
|
wikiRuntime = new AsyncLocalStorage3;
|
|
25617
26588
|
lockWaitArray = new Int32Array(new SharedArrayBuffer(4));
|
|
26589
|
+
SEARCH_STOP_WORDS = new Set([
|
|
26590
|
+
"a",
|
|
26591
|
+
"an",
|
|
26592
|
+
"and",
|
|
26593
|
+
"are",
|
|
26594
|
+
"as",
|
|
26595
|
+
"at",
|
|
26596
|
+
"be",
|
|
26597
|
+
"by",
|
|
26598
|
+
"for",
|
|
26599
|
+
"from",
|
|
26600
|
+
"in",
|
|
26601
|
+
"is",
|
|
26602
|
+
"it",
|
|
26603
|
+
"of",
|
|
26604
|
+
"on",
|
|
26605
|
+
"or",
|
|
26606
|
+
"that",
|
|
26607
|
+
"the",
|
|
26608
|
+
"to",
|
|
26609
|
+
"with"
|
|
26610
|
+
]);
|
|
26611
|
+
SEARCH_MONTHS = new Map([
|
|
26612
|
+
["january", "01"],
|
|
26613
|
+
["jan", "01"],
|
|
26614
|
+
["1\uC6D4", "01"],
|
|
26615
|
+
["february", "02"],
|
|
26616
|
+
["feb", "02"],
|
|
26617
|
+
["2\uC6D4", "02"],
|
|
26618
|
+
["march", "03"],
|
|
26619
|
+
["mar", "03"],
|
|
26620
|
+
["3\uC6D4", "03"],
|
|
26621
|
+
["april", "04"],
|
|
26622
|
+
["apr", "04"],
|
|
26623
|
+
["4\uC6D4", "04"],
|
|
26624
|
+
["may", "05"],
|
|
26625
|
+
["5\uC6D4", "05"],
|
|
26626
|
+
["june", "06"],
|
|
26627
|
+
["jun", "06"],
|
|
26628
|
+
["6\uC6D4", "06"],
|
|
26629
|
+
["july", "07"],
|
|
26630
|
+
["jul", "07"],
|
|
26631
|
+
["7\uC6D4", "07"],
|
|
26632
|
+
["august", "08"],
|
|
26633
|
+
["aug", "08"],
|
|
26634
|
+
["8\uC6D4", "08"],
|
|
26635
|
+
["september", "09"],
|
|
26636
|
+
["sep", "09"],
|
|
26637
|
+
["sept", "09"],
|
|
26638
|
+
["9\uC6D4", "09"],
|
|
26639
|
+
["october", "10"],
|
|
26640
|
+
["oct", "10"],
|
|
26641
|
+
["10\uC6D4", "10"],
|
|
26642
|
+
["november", "11"],
|
|
26643
|
+
["nov", "11"],
|
|
26644
|
+
["11\uC6D4", "11"],
|
|
26645
|
+
["december", "12"],
|
|
26646
|
+
["dec", "12"],
|
|
26647
|
+
["12\uC6D4", "12"]
|
|
26648
|
+
]);
|
|
26649
|
+
SEARCH_RECENCY_TERMS = new Set([
|
|
26650
|
+
"latest",
|
|
26651
|
+
"newest",
|
|
26652
|
+
"recent",
|
|
26653
|
+
"earliest",
|
|
26654
|
+
"oldest",
|
|
26655
|
+
"\uCD5C\uADFC",
|
|
26656
|
+
"\uCD5C\uC2E0",
|
|
26657
|
+
"\uCD5C\uCD08",
|
|
26658
|
+
"\uC624\uB798\uB41C"
|
|
26659
|
+
]);
|
|
26660
|
+
WIKI_INDEX_FILENAMES = {
|
|
26661
|
+
article: "article-index.md",
|
|
26662
|
+
summary: "summary-index.md",
|
|
26663
|
+
topic: "topic-index.md",
|
|
26664
|
+
skill: "skill-index.md"
|
|
26665
|
+
};
|
|
26666
|
+
WIKI_INDEX_NAMESPACES = {
|
|
26667
|
+
article: "articles",
|
|
26668
|
+
summary: "summaries",
|
|
26669
|
+
topic: "topic",
|
|
26670
|
+
skill: "skills"
|
|
26671
|
+
};
|
|
25618
26672
|
WIKI_TOOLS = [
|
|
25619
26673
|
{
|
|
25620
26674
|
name: "wiki_query",
|
|
25621
|
-
description: "Search
|
|
26675
|
+
description: "Search topic, article, or summary indexes and return relevant candidates.",
|
|
25622
26676
|
inputSchema: {
|
|
25623
26677
|
type: "object",
|
|
25624
26678
|
properties: {
|
|
@@ -25629,11 +26683,38 @@ var init_wiki_server = __esm(() => {
|
|
|
25629
26683
|
topic: {
|
|
25630
26684
|
type: "string",
|
|
25631
26685
|
description: "Optional topic name to narrow the search"
|
|
26686
|
+
},
|
|
26687
|
+
kind: {
|
|
26688
|
+
type: "string",
|
|
26689
|
+
enum: ["all", "topic", "article", "summary"],
|
|
26690
|
+
description: "Document kind to search; defaults to all"
|
|
26691
|
+
},
|
|
26692
|
+
limit: {
|
|
26693
|
+
type: "number",
|
|
26694
|
+
minimum: 1,
|
|
26695
|
+
maximum: 20,
|
|
26696
|
+
description: "Maximum number of candidates; defaults to 8"
|
|
25632
26697
|
}
|
|
25633
26698
|
},
|
|
25634
26699
|
required: ["question"]
|
|
25635
26700
|
}
|
|
25636
26701
|
},
|
|
26702
|
+
{
|
|
26703
|
+
name: "wiki_read",
|
|
26704
|
+
description: "Read one topic, article, or summary returned by wiki_query. For a clearly matching continuing persona, set adopt=true when reading a topic.",
|
|
26705
|
+
inputSchema: {
|
|
26706
|
+
type: "object",
|
|
26707
|
+
properties: {
|
|
26708
|
+
kind: { type: "string", enum: ["topic", "article", "summary"] },
|
|
26709
|
+
key: { type: "string", description: "Canonical key returned by wiki_query" },
|
|
26710
|
+
adopt: {
|
|
26711
|
+
type: "boolean",
|
|
26712
|
+
description: "For kind=topic, persist this canonical memory key for the current room"
|
|
26713
|
+
}
|
|
26714
|
+
},
|
|
26715
|
+
required: ["kind", "key"]
|
|
26716
|
+
}
|
|
26717
|
+
},
|
|
25637
26718
|
{
|
|
25638
26719
|
name: "wiki_topic_brief",
|
|
25639
26720
|
description: "Get the lightweight topic brief for a specific topic.",
|
|
@@ -25669,35 +26750,46 @@ var init_wiki_server = __esm(() => {
|
|
|
25669
26750
|
}
|
|
25670
26751
|
},
|
|
25671
26752
|
{
|
|
25672
|
-
name: "
|
|
25673
|
-
description: "
|
|
26753
|
+
name: "wiki_write",
|
|
26754
|
+
description: "Write a wiki document and its catalog row together. The only way to create a summary, article, or topic brief: summaries go to wiki/summaries/ + summary-index.md, articles to wiki/articles/ + article-index.md, briefs to wiki/topic/ + topic-index.md (also mirrored into SQLite for next-session injection). Never write wiki files by any other means.",
|
|
25674
26755
|
inputSchema: {
|
|
25675
26756
|
type: "object",
|
|
25676
26757
|
properties: {
|
|
25677
|
-
|
|
25678
|
-
|
|
26758
|
+
kind: {
|
|
26759
|
+
type: "string",
|
|
26760
|
+
enum: ["summary", "article", "topic"],
|
|
26761
|
+
description: "Which document to write"
|
|
26762
|
+
},
|
|
26763
|
+
content: { type: "string", description: "Document body in markdown" },
|
|
26764
|
+
description: {
|
|
26765
|
+
type: "string",
|
|
26766
|
+
description: "Single-line catalog description; required so the index stays useful"
|
|
26767
|
+
},
|
|
26768
|
+
topic: {
|
|
26769
|
+
type: "string",
|
|
26770
|
+
description: "Topic name; required for kind=summary and kind=topic"
|
|
26771
|
+
},
|
|
26772
|
+
slug: {
|
|
26773
|
+
type: "string",
|
|
26774
|
+
description: "Article slug in kebab-case; required for kind=article"
|
|
26775
|
+
},
|
|
26776
|
+
section: {
|
|
26777
|
+
type: "string",
|
|
26778
|
+
description: "H2 catalog section; required for kind=article"
|
|
26779
|
+
},
|
|
26780
|
+
date: { type: "string", description: "Override entry date YYYY-MM-DD (default: today)" }
|
|
25679
26781
|
},
|
|
25680
|
-
required: ["
|
|
26782
|
+
required: ["kind", "content", "description"]
|
|
25681
26783
|
}
|
|
25682
26784
|
},
|
|
25683
26785
|
{
|
|
25684
|
-
name: "
|
|
25685
|
-
description: "
|
|
25686
|
-
inputSchema: {
|
|
25687
|
-
type: "object",
|
|
25688
|
-
properties: {
|
|
25689
|
-
topic: { type: "string", description: "Topic name of the session" },
|
|
25690
|
-
content: {
|
|
25691
|
-
type: "string",
|
|
25692
|
-
description: "Full accumulated topic brief in markdown (persona layer + recent work)"
|
|
25693
|
-
}
|
|
25694
|
-
},
|
|
25695
|
-
required: ["topic", "content"]
|
|
25696
|
-
}
|
|
26786
|
+
name: "wiki_reindex",
|
|
26787
|
+
description: "Rebuild the derived body-search cache from the documents on disk and report catalog gaps. Run this once after adding documents outside wiki_write; retrieval itself never scans the wiki.",
|
|
26788
|
+
inputSchema: { type: "object", properties: {} }
|
|
25697
26789
|
},
|
|
25698
26790
|
{
|
|
25699
26791
|
name: "index_upsert",
|
|
25700
|
-
description: "
|
|
26792
|
+
description: "Correct the description or section of an entry that already exists. Cannot create an entry \u2014 use wiki_write for that.",
|
|
25701
26793
|
inputSchema: {
|
|
25702
26794
|
type: "object",
|
|
25703
26795
|
properties: {
|
|
@@ -25774,12 +26866,12 @@ __export(exports_activity_log, {
|
|
|
25774
26866
|
readSentFilesForTopic: () => readSentFilesForTopic
|
|
25775
26867
|
});
|
|
25776
26868
|
import {
|
|
25777
|
-
existsSync as
|
|
26869
|
+
existsSync as existsSync28,
|
|
25778
26870
|
mkdirSync as mkdirSync25,
|
|
25779
26871
|
readdirSync as readdirSync11,
|
|
25780
26872
|
renameSync as renameSync12,
|
|
25781
26873
|
statSync as statSync16,
|
|
25782
|
-
unlinkSync as
|
|
26874
|
+
unlinkSync as unlinkSync20,
|
|
25783
26875
|
writeFileSync as writeFileSync18
|
|
25784
26876
|
} from "fs";
|
|
25785
26877
|
import { basename as basename8, join as join37 } from "path";
|
|
@@ -25832,7 +26924,7 @@ function rotateOldLogs() {
|
|
|
25832
26924
|
if (freed >= toFree)
|
|
25833
26925
|
break;
|
|
25834
26926
|
try {
|
|
25835
|
-
|
|
26927
|
+
unlinkSync20(join37(logDir, file.name));
|
|
25836
26928
|
freed += file.size;
|
|
25837
26929
|
logger.info({ file: file.name, sizeKB: (file.size / 1024).toFixed(0) }, "Rotated out old log");
|
|
25838
26930
|
} catch (e) {
|
|
@@ -25858,7 +26950,7 @@ function entryMatchesTopic(e, topicName) {
|
|
|
25858
26950
|
}
|
|
25859
26951
|
function readSentFilesForTopic(userId, topicName) {
|
|
25860
26952
|
const file = join37(resolveStorageUsersLogDir(), String(userId), "sent-files.jsonl");
|
|
25861
|
-
if (!
|
|
26953
|
+
if (!existsSync28(file))
|
|
25862
26954
|
return [];
|
|
25863
26955
|
try {
|
|
25864
26956
|
return readJsonlLines(file).map((line) => JSON.parse(line)).filter((e) => entryMatchesTopic(e, topicName));
|
|
@@ -25869,7 +26961,7 @@ function readSentFilesForTopic(userId, topicName) {
|
|
|
25869
26961
|
}
|
|
25870
26962
|
function removeSentFilesForTopic(userId, topicName) {
|
|
25871
26963
|
const file = join37(resolveStorageUsersLogDir(), String(userId), "sent-files.jsonl");
|
|
25872
|
-
if (!
|
|
26964
|
+
if (!existsSync28(file))
|
|
25873
26965
|
return;
|
|
25874
26966
|
try {
|
|
25875
26967
|
const remaining = readJsonlLines(file).filter((line) => {
|
|
@@ -25884,7 +26976,7 @@ function removeSentFilesForTopic(userId, topicName) {
|
|
|
25884
26976
|
`)}
|
|
25885
26977
|
`);
|
|
25886
26978
|
} else {
|
|
25887
|
-
|
|
26979
|
+
unlinkSync20(file);
|
|
25888
26980
|
}
|
|
25889
26981
|
} catch (e) {
|
|
25890
26982
|
logger.warn({ err: e, userId, topicName }, "Failed to remove sent-files for topic");
|
|
@@ -26682,7 +27774,7 @@ async function buildHostedSurfaceServer(surface, context) {
|
|
|
26682
27774
|
case "wiki":
|
|
26683
27775
|
case "skills": {
|
|
26684
27776
|
const [{ createWikiMcpServer: createWikiMcpServer2 }, storage, { WORKSPACE_DIR: WORKSPACE_DIR2 }] = await Promise.all([
|
|
26685
|
-
|
|
27777
|
+
init_wiki_server().then(() => exports_wiki_server),
|
|
26686
27778
|
init_storage_public().then(() => exports_storage_public),
|
|
26687
27779
|
init_mcp_runtime_host().then(() => exports_mcp_runtime_host)
|
|
26688
27780
|
]);
|
|
@@ -27393,7 +28485,7 @@ var init_spec = __esm(() => {
|
|
|
27393
28485
|
});
|
|
27394
28486
|
|
|
27395
28487
|
// ../../packages/mcp-host/src/manifest.ts
|
|
27396
|
-
import { existsSync as
|
|
28488
|
+
import { existsSync as existsSync29, mkdirSync as mkdirSync26, readFileSync as readFileSync24, renameSync as renameSync13, writeFileSync as writeFileSync19 } from "fs";
|
|
27397
28489
|
import { dirname as dirname17 } from "path";
|
|
27398
28490
|
import { z as z14 } from "zod";
|
|
27399
28491
|
|
|
@@ -27438,11 +28530,11 @@ class McpManifest {
|
|
|
27438
28530
|
return this.entries.get(key)?.enabled ?? false;
|
|
27439
28531
|
}
|
|
27440
28532
|
load() {
|
|
27441
|
-
if (!
|
|
28533
|
+
if (!existsSync29(this.file))
|
|
27442
28534
|
return;
|
|
27443
28535
|
let parsed;
|
|
27444
28536
|
try {
|
|
27445
|
-
parsed = manifestFileSchema.parse(JSON.parse(
|
|
28537
|
+
parsed = manifestFileSchema.parse(JSON.parse(readFileSync24(this.file, "utf8")));
|
|
27446
28538
|
} catch (e) {
|
|
27447
28539
|
throw new Error(`Invalid MCP manifest at ${this.file}: ${e instanceof Error ? e.message : String(e)}`);
|
|
27448
28540
|
}
|
|
@@ -27492,9 +28584,9 @@ import { execFileSync as execFileSync8, spawn as spawn8 } from "child_process";
|
|
|
27492
28584
|
import {
|
|
27493
28585
|
mkdirSync as mkdirSync27,
|
|
27494
28586
|
readdirSync as readdirSync12,
|
|
27495
|
-
readFileSync as
|
|
28587
|
+
readFileSync as readFileSync25,
|
|
27496
28588
|
renameSync as renameSync14,
|
|
27497
|
-
unlinkSync as
|
|
28589
|
+
unlinkSync as unlinkSync21,
|
|
27498
28590
|
writeFileSync as writeFileSync20
|
|
27499
28591
|
} from "fs";
|
|
27500
28592
|
import { connect, createServer as createServer3 } from "net";
|
|
@@ -27800,7 +28892,7 @@ class McpHost {
|
|
|
27800
28892
|
if (file.includes(".tmp-"))
|
|
27801
28893
|
continue;
|
|
27802
28894
|
try {
|
|
27803
|
-
const port = Number.parseInt(
|
|
28895
|
+
const port = Number.parseInt(readFileSync25(join38(this.portsDir, file), "utf8").trim(), 10);
|
|
27804
28896
|
if (!Number.isNaN(port))
|
|
27805
28897
|
claims.set(file, port);
|
|
27806
28898
|
} catch {}
|
|
@@ -27820,7 +28912,7 @@ class McpHost {
|
|
|
27820
28912
|
}
|
|
27821
28913
|
deletePortFile(fileName) {
|
|
27822
28914
|
try {
|
|
27823
|
-
|
|
28915
|
+
unlinkSync21(join38(this.portsDir, fileName));
|
|
27824
28916
|
} catch {}
|
|
27825
28917
|
}
|
|
27826
28918
|
cleanupRunning(rkey, inst) {
|
|
@@ -27925,9 +29017,9 @@ var init_src3 = __esm(() => {
|
|
|
27925
29017
|
import { randomUUID as randomUUID24 } from "crypto";
|
|
27926
29018
|
import {
|
|
27927
29019
|
copyFileSync as copyFileSync4,
|
|
27928
|
-
existsSync as
|
|
29020
|
+
existsSync as existsSync30,
|
|
27929
29021
|
mkdirSync as mkdirSync28,
|
|
27930
|
-
readFileSync as
|
|
29022
|
+
readFileSync as readFileSync26,
|
|
27931
29023
|
rmSync as rmSync9,
|
|
27932
29024
|
statSync as statSync18,
|
|
27933
29025
|
writeFileSync as writeFileSync21
|
|
@@ -27963,7 +29055,7 @@ class NodeFileStore {
|
|
|
27963
29055
|
if (!FILE_ID_RE.test(fileId))
|
|
27964
29056
|
return null;
|
|
27965
29057
|
try {
|
|
27966
|
-
const parsed = JSON.parse(
|
|
29058
|
+
const parsed = JSON.parse(readFileSync26(this.#metadataPath(fileId), "utf8"));
|
|
27967
29059
|
if (typeof parsed.filename !== "string" || typeof parsed.mimeType !== "string" || typeof parsed.sizeBytes !== "number" || typeof parsed.savedName !== "string") {
|
|
27968
29060
|
return null;
|
|
27969
29061
|
}
|
|
@@ -27986,7 +29078,7 @@ class NodeFileStore {
|
|
|
27986
29078
|
hooks = {
|
|
27987
29079
|
resolveAttachmentByFileId: (fileId) => {
|
|
27988
29080
|
const metadata = this.#metadata(fileId);
|
|
27989
|
-
if (!metadata || !
|
|
29081
|
+
if (!metadata || !existsSync30(join39(this.uploadDir, metadata.savedName)))
|
|
27990
29082
|
return null;
|
|
27991
29083
|
return this.#attachment(fileId, metadata);
|
|
27992
29084
|
},
|
|
@@ -27995,7 +29087,7 @@ class NodeFileStore {
|
|
|
27995
29087
|
if (!metadata)
|
|
27996
29088
|
return null;
|
|
27997
29089
|
const path = join39(this.uploadDir, metadata.savedName);
|
|
27998
|
-
return
|
|
29090
|
+
return existsSync30(path) ? path : null;
|
|
27999
29091
|
},
|
|
28000
29092
|
storeLocalFileAsUpload: (absPath, access = {}) => this.store(absPath, access),
|
|
28001
29093
|
deleteFilesForTopic: (topicId) => this.deleteForTopic(topicId)
|
|
@@ -28038,7 +29130,7 @@ class NodeFileStore {
|
|
|
28038
29130
|
if (!allowed)
|
|
28039
29131
|
return null;
|
|
28040
29132
|
const path = join39(this.uploadDir, metadata.savedName);
|
|
28041
|
-
if (!
|
|
29133
|
+
if (!existsSync30(path))
|
|
28042
29134
|
return null;
|
|
28043
29135
|
return new Response(Bun.file(path), {
|
|
28044
29136
|
headers: {
|
|
@@ -28207,12 +29299,12 @@ function createPollingSseStream(req, options) {
|
|
|
28207
29299
|
// ../../packages/node/src/control.ts
|
|
28208
29300
|
import { randomUUID as randomUUID25, timingSafeEqual as timingSafeEqual4 } from "crypto";
|
|
28209
29301
|
import {
|
|
28210
|
-
chmodSync as
|
|
28211
|
-
existsSync as
|
|
29302
|
+
chmodSync as chmodSync6,
|
|
29303
|
+
existsSync as existsSync31,
|
|
28212
29304
|
mkdirSync as mkdirSync29,
|
|
28213
|
-
readFileSync as
|
|
29305
|
+
readFileSync as readFileSync27,
|
|
28214
29306
|
renameSync as renameSync15,
|
|
28215
|
-
unlinkSync as
|
|
29307
|
+
unlinkSync as unlinkSync22,
|
|
28216
29308
|
writeFileSync as writeFileSync22
|
|
28217
29309
|
} from "fs";
|
|
28218
29310
|
import { dirname as dirname18, resolve as resolve21 } from "path";
|
|
@@ -28719,16 +29811,16 @@ function writeNodeDaemonInfo(port, startedAt) {
|
|
|
28719
29811
|
const temporary = `${NODE_DAEMON_INFO_PATH}.${process.pid}.${randomUUID25()}.tmp`;
|
|
28720
29812
|
writeFileSync22(temporary, `${JSON.stringify(info, null, 2)}
|
|
28721
29813
|
`, { mode: 384 });
|
|
28722
|
-
|
|
29814
|
+
chmodSync6(temporary, 384);
|
|
28723
29815
|
renameSync15(temporary, NODE_DAEMON_INFO_PATH);
|
|
28724
|
-
|
|
29816
|
+
chmodSync6(NODE_DAEMON_INFO_PATH, 384);
|
|
28725
29817
|
return info;
|
|
28726
29818
|
}
|
|
28727
29819
|
function readNodeDaemonInfo() {
|
|
28728
|
-
if (!
|
|
29820
|
+
if (!existsSync31(NODE_DAEMON_INFO_PATH))
|
|
28729
29821
|
return null;
|
|
28730
29822
|
try {
|
|
28731
|
-
const value = JSON.parse(
|
|
29823
|
+
const value = JSON.parse(readFileSync27(NODE_DAEMON_INFO_PATH, "utf8"));
|
|
28732
29824
|
if (value?.schemaVersion !== 1 || value.stateDir !== STATE_DIR || !Number.isInteger(value.protocolVersion) || !Number.isInteger(value.pid) || !Number.isInteger(value.port) || value.port <= 0) {
|
|
28733
29825
|
return null;
|
|
28734
29826
|
}
|
|
@@ -28742,7 +29834,7 @@ function removeNodeDaemonInfo(expected) {
|
|
|
28742
29834
|
if (!current3 || current3.pid !== expected.pid || current3.port !== expected.port)
|
|
28743
29835
|
return;
|
|
28744
29836
|
try {
|
|
28745
|
-
|
|
29837
|
+
unlinkSync22(NODE_DAEMON_INFO_PATH);
|
|
28746
29838
|
} catch {}
|
|
28747
29839
|
}
|
|
28748
29840
|
async function fetchWithTimeout(url, init, timeoutMs) {
|
|
@@ -29016,7 +30108,7 @@ var init_schedule = __esm(() => {
|
|
|
29016
30108
|
|
|
29017
30109
|
// ../../packages/module-cron/src/scripts.ts
|
|
29018
30110
|
import { spawn as spawn9 } from "child_process";
|
|
29019
|
-
import { existsSync as
|
|
30111
|
+
import { existsSync as existsSync32, mkdirSync as mkdirSync30, readdirSync as readdirSync13 } from "fs";
|
|
29020
30112
|
import { resolve as resolve22, sep as sep2 } from "path";
|
|
29021
30113
|
function validateCronScriptName(script) {
|
|
29022
30114
|
if (!/^[A-Za-z0-9_][A-Za-z0-9_.-]*\.py$/.test(script) || script.includes("..") || script.includes("/") || script.includes("\\")) {
|
|
@@ -29039,7 +30131,7 @@ function listCronScripts() {
|
|
|
29039
30131
|
}
|
|
29040
30132
|
function cronScriptExists(script) {
|
|
29041
30133
|
try {
|
|
29042
|
-
return
|
|
30134
|
+
return existsSync32(resolveCronScriptPath(script));
|
|
29043
30135
|
} catch {
|
|
29044
30136
|
return false;
|
|
29045
30137
|
}
|
|
@@ -29071,7 +30163,7 @@ async function runCronPromptScript(options) {
|
|
|
29071
30163
|
if (options.signal?.aborted)
|
|
29072
30164
|
throw new CronScriptError("script run aborted");
|
|
29073
30165
|
const scriptPath = resolveCronScriptPath(options.script);
|
|
29074
|
-
if (!
|
|
30166
|
+
if (!existsSync32(scriptPath))
|
|
29075
30167
|
throw new CronScriptError(`cron script not found: ${options.script}`);
|
|
29076
30168
|
const timeoutMs = Math.max(5000, options.timeoutMs ?? DEFAULT_SCRIPT_TIMEOUT_MS);
|
|
29077
30169
|
const outputLimitBytes = Math.max(1024, options.outputLimitBytes ?? DEFAULT_OUTPUT_LIMIT_BYTES);
|
|
@@ -30272,7 +31364,7 @@ var init_scheduler = __esm(async () => {
|
|
|
30272
31364
|
});
|
|
30273
31365
|
|
|
30274
31366
|
// ../../packages/module-cron/src/module.ts
|
|
30275
|
-
import { existsSync as
|
|
31367
|
+
import { existsSync as existsSync33 } from "fs";
|
|
30276
31368
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
30277
31369
|
function envMs(name, fallback) {
|
|
30278
31370
|
const parsed = Number.parseInt(process.env[name] ?? "", 10);
|
|
@@ -30303,7 +31395,7 @@ function createCronModule(options = {}) {
|
|
|
30303
31395
|
if (event.type === "topic-deleted")
|
|
30304
31396
|
trackCleanup(resetCronTopicContext(event.topicId));
|
|
30305
31397
|
});
|
|
30306
|
-
const mcpServerFile = options.mcpServerFile ?? (
|
|
31398
|
+
const mcpServerFile = options.mcpServerFile ?? (existsSync33(SOURCE_MCP_SERVER_FILE) ? SOURCE_MCP_SERVER_FILE : PACKAGED_MCP_SERVER_FILE);
|
|
30307
31399
|
const unregisterMcp = host2.registerRuntimeMcpServer("cron-manager", {
|
|
30308
31400
|
scopes: ["forum", "manager"],
|
|
30309
31401
|
forumRequired: true,
|
|
@@ -31801,7 +32893,7 @@ var init_context_usage = __esm(async () => {
|
|
|
31801
32893
|
|
|
31802
32894
|
// ../../adapters/terminal/src/path-suggest.ts
|
|
31803
32895
|
import { execFile as execFile3 } from "child_process";
|
|
31804
|
-
import { existsSync as
|
|
32896
|
+
import { existsSync as existsSync34, readdirSync as readdirSync14, statSync as statSync19 } from "fs";
|
|
31805
32897
|
import { homedir as homedir10 } from "os";
|
|
31806
32898
|
import { basename as basename11, dirname as dirname19, join as join40, sep as sep3 } from "path";
|
|
31807
32899
|
import { promisify as promisify3 } from "util";
|
|
@@ -32050,7 +33142,7 @@ function fragmentToAbsolutePath(frag) {
|
|
|
32050
33142
|
}
|
|
32051
33143
|
function fragmentResolves(frag) {
|
|
32052
33144
|
try {
|
|
32053
|
-
return
|
|
33145
|
+
return existsSync34(fragmentToAbsolutePath(frag));
|
|
32054
33146
|
} catch {
|
|
32055
33147
|
return false;
|
|
32056
33148
|
}
|
|
@@ -38744,7 +39836,7 @@ function renderOutbound(text2, maxLen = 4096) {
|
|
|
38744
39836
|
}
|
|
38745
39837
|
|
|
38746
39838
|
// ../../adapters/telegram/src/adapter.ts
|
|
38747
|
-
import { existsSync as
|
|
39839
|
+
import { existsSync as existsSync35 } from "fs";
|
|
38748
39840
|
import { basename as basename12, extname as extname5 } from "path";
|
|
38749
39841
|
function startTelegramAdapter(opts) {
|
|
38750
39842
|
const { client, forumChatId } = opts;
|
|
@@ -39232,7 +40324,7 @@ function startTelegramAdapter(opts) {
|
|
|
39232
40324
|
logger.warn({ path, chatId }, "telegram adapter: blocked sensitive file path");
|
|
39233
40325
|
return { ref: null, delivered: false, error: "path matches the sensitive-file blacklist" };
|
|
39234
40326
|
}
|
|
39235
|
-
if (!
|
|
40327
|
+
if (!existsSync35(path)) {
|
|
39236
40328
|
const sent = await client.sendMessage(chatId, `File: ${path}`, base).catch(() => null);
|
|
39237
40329
|
const messageId = sentMessageId(sent);
|
|
39238
40330
|
return {
|
|
@@ -40414,19 +41506,19 @@ __export(exports_join, {
|
|
|
40414
41506
|
});
|
|
40415
41507
|
import { createHash as createHash11, randomUUID as randomUUID27 } from "crypto";
|
|
40416
41508
|
import {
|
|
40417
|
-
chmodSync as
|
|
41509
|
+
chmodSync as chmodSync7,
|
|
40418
41510
|
closeSync as closeSync5,
|
|
40419
|
-
existsSync as
|
|
41511
|
+
existsSync as existsSync36,
|
|
40420
41512
|
fsyncSync as fsyncSync2,
|
|
40421
41513
|
linkSync,
|
|
40422
41514
|
lstatSync,
|
|
40423
41515
|
mkdirSync as mkdirSync32,
|
|
40424
41516
|
openSync as openSync5,
|
|
40425
|
-
readFileSync as
|
|
41517
|
+
readFileSync as readFileSync28,
|
|
40426
41518
|
renameSync as renameSync16,
|
|
40427
41519
|
rmSync as rmSync10,
|
|
40428
41520
|
statSync as statSync20,
|
|
40429
|
-
unlinkSync as
|
|
41521
|
+
unlinkSync as unlinkSync23,
|
|
40430
41522
|
writeFileSync as writeFileSync23
|
|
40431
41523
|
} from "fs";
|
|
40432
41524
|
import { dirname as dirname21, resolve as resolve23 } from "path";
|
|
@@ -40526,7 +41618,7 @@ function withJoinCredentialLock(operation) {
|
|
|
40526
41618
|
throw error2;
|
|
40527
41619
|
let current3 = null;
|
|
40528
41620
|
try {
|
|
40529
|
-
current3 = JSON.parse(
|
|
41621
|
+
current3 = JSON.parse(readFileSync28(ownerPath, "utf8"));
|
|
40530
41622
|
} catch {}
|
|
40531
41623
|
let ageMs;
|
|
40532
41624
|
try {
|
|
@@ -40556,7 +41648,7 @@ function withJoinCredentialLock(operation) {
|
|
|
40556
41648
|
return operation();
|
|
40557
41649
|
} finally {
|
|
40558
41650
|
try {
|
|
40559
|
-
const current3 = JSON.parse(
|
|
41651
|
+
const current3 = JSON.parse(readFileSync28(ownerPath, "utf8"));
|
|
40560
41652
|
if (current3.pid === owner.pid && current3.token === owner.token) {
|
|
40561
41653
|
rmSync10(lockPath, { recursive: true, force: true });
|
|
40562
41654
|
}
|
|
@@ -40579,9 +41671,9 @@ function joinCredentialDigest(join42) {
|
|
|
40579
41671
|
return createHash11("sha256").update(JSON.stringify(normalizedJoin(join42))).digest("base64url");
|
|
40580
41672
|
}
|
|
40581
41673
|
function readPersistedJoin(path = joinFilePath()) {
|
|
40582
|
-
if (!
|
|
41674
|
+
if (!existsSync36(path))
|
|
40583
41675
|
return null;
|
|
40584
|
-
const parsed = JSON.parse(
|
|
41676
|
+
const parsed = JSON.parse(readFileSync28(path, "utf-8"));
|
|
40585
41677
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
40586
41678
|
throw new Error("persisted join credentials are not a JSON object");
|
|
40587
41679
|
}
|
|
@@ -40600,7 +41692,7 @@ function saveJoinWhileLocked(join42, options = {}) {
|
|
|
40600
41692
|
const directory = dirname21(path);
|
|
40601
41693
|
const normalized = normalizedJoin(join42);
|
|
40602
41694
|
mkdirSync32(directory, { recursive: true });
|
|
40603
|
-
if (
|
|
41695
|
+
if (existsSync36(path)) {
|
|
40604
41696
|
if (lstatSync(path).isSymbolicLink()) {
|
|
40605
41697
|
throw new Error(`refusing to replace symlinked Otium join file at ${path}`);
|
|
40606
41698
|
}
|
|
@@ -40613,7 +41705,7 @@ function saveJoinWhileLocked(join42, options = {}) {
|
|
|
40613
41705
|
}
|
|
40614
41706
|
}
|
|
40615
41707
|
if (existing && joinsEqual(existing, normalized)) {
|
|
40616
|
-
|
|
41708
|
+
chmodSync7(path, 384);
|
|
40617
41709
|
const fileFd = openSync5(path, "r");
|
|
40618
41710
|
try {
|
|
40619
41711
|
fsyncSync2(fileFd);
|
|
@@ -40645,9 +41737,9 @@ function saveJoinWhileLocked(join42, options = {}) {
|
|
|
40645
41737
|
renameSync16(temporaryPath, path);
|
|
40646
41738
|
} else {
|
|
40647
41739
|
linkSync(temporaryPath, path);
|
|
40648
|
-
|
|
41740
|
+
unlinkSync23(temporaryPath);
|
|
40649
41741
|
}
|
|
40650
|
-
|
|
41742
|
+
chmodSync7(path, 384);
|
|
40651
41743
|
const directoryFd = openSync5(directory, "r");
|
|
40652
41744
|
try {
|
|
40653
41745
|
fsyncSync2(directoryFd);
|
|
@@ -40657,8 +41749,8 @@ function saveJoinWhileLocked(join42, options = {}) {
|
|
|
40657
41749
|
} catch (error2) {
|
|
40658
41750
|
if (fd !== undefined)
|
|
40659
41751
|
closeSync5(fd);
|
|
40660
|
-
if (
|
|
40661
|
-
|
|
41752
|
+
if (existsSync36(temporaryPath))
|
|
41753
|
+
unlinkSync23(temporaryPath);
|
|
40662
41754
|
throw error2;
|
|
40663
41755
|
}
|
|
40664
41756
|
return path;
|
|
@@ -40669,12 +41761,12 @@ function saveJoin(join42, options = {}) {
|
|
|
40669
41761
|
function removeJoin() {
|
|
40670
41762
|
return withJoinCredentialLock(() => {
|
|
40671
41763
|
const path = joinFilePath();
|
|
40672
|
-
if (!
|
|
41764
|
+
if (!existsSync36(path))
|
|
40673
41765
|
return false;
|
|
40674
41766
|
if (lstatSync(path).isSymbolicLink()) {
|
|
40675
41767
|
throw new Error(`refusing to remove symlinked Otium join file at ${path}`);
|
|
40676
41768
|
}
|
|
40677
|
-
|
|
41769
|
+
unlinkSync23(path);
|
|
40678
41770
|
const directoryFd = openSync5(dirname21(path), "r");
|
|
40679
41771
|
try {
|
|
40680
41772
|
fsyncSync2(directoryFd);
|
|
@@ -40701,10 +41793,10 @@ function loadJoin() {
|
|
|
40701
41793
|
logger.warn("otium: OTIUM_CENTRAL_URL, OTIUM_CELL_ID, OTIUM_CELL_SECRET must be set together \u2014 ignoring partial env");
|
|
40702
41794
|
}
|
|
40703
41795
|
const path = joinFilePath();
|
|
40704
|
-
if (!
|
|
41796
|
+
if (!existsSync36(path))
|
|
40705
41797
|
return null;
|
|
40706
41798
|
try {
|
|
40707
|
-
const parsed = JSON.parse(
|
|
41799
|
+
const parsed = JSON.parse(readFileSync28(path, "utf-8"));
|
|
40708
41800
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
40709
41801
|
return null;
|
|
40710
41802
|
return normalizeJoin(parsed);
|
|
@@ -40728,11 +41820,11 @@ var exports_join_status = {};
|
|
|
40728
41820
|
__export(exports_join_status, {
|
|
40729
41821
|
hasConfiguredOtiumJoin: () => hasConfiguredOtiumJoin
|
|
40730
41822
|
});
|
|
40731
|
-
import { existsSync as
|
|
41823
|
+
import { existsSync as existsSync37 } from "fs";
|
|
40732
41824
|
import { resolve as resolve24 } from "path";
|
|
40733
41825
|
function hasConfiguredOtiumJoin() {
|
|
40734
41826
|
const envJoin = Boolean(process.env.OTIUM_CENTRAL_URL?.trim() && process.env.OTIUM_CELL_ID?.trim() && process.env.OTIUM_CELL_SECRET?.trim());
|
|
40735
|
-
return envJoin ||
|
|
41827
|
+
return envJoin || existsSync37(resolve24(DATA_DIR, "otium-join.json"));
|
|
40736
41828
|
}
|
|
40737
41829
|
var init_join_status = __esm(() => {
|
|
40738
41830
|
init_config_public();
|
|
@@ -40957,10 +42049,10 @@ var init_canonical_mcp_bridge = __esm(async () => {
|
|
|
40957
42049
|
task: new Set(["task_create", "task_update", "task_list", "task_get", "task_delete"]),
|
|
40958
42050
|
wiki: new Set([
|
|
40959
42051
|
"wiki_query",
|
|
42052
|
+
"wiki_read",
|
|
40960
42053
|
"wiki_topic_brief",
|
|
40961
42054
|
"wiki_last_conversation",
|
|
40962
|
-
"
|
|
40963
|
-
"save_topic_brief",
|
|
42055
|
+
"wiki_write",
|
|
40964
42056
|
"index_upsert"
|
|
40965
42057
|
])
|
|
40966
42058
|
};
|
|
@@ -43420,16 +44512,16 @@ import {
|
|
|
43420
44512
|
randomUUID as randomUUID31
|
|
43421
44513
|
} from "crypto";
|
|
43422
44514
|
import {
|
|
43423
|
-
chmodSync as
|
|
44515
|
+
chmodSync as chmodSync8,
|
|
43424
44516
|
closeSync as closeSync6,
|
|
43425
|
-
existsSync as
|
|
44517
|
+
existsSync as existsSync38,
|
|
43426
44518
|
fsyncSync as fsyncSync3,
|
|
43427
44519
|
linkSync as linkSync2,
|
|
43428
44520
|
mkdirSync as mkdirSync34,
|
|
43429
44521
|
openSync as openSync6,
|
|
43430
|
-
readFileSync as
|
|
44522
|
+
readFileSync as readFileSync29,
|
|
43431
44523
|
renameSync as renameSync17,
|
|
43432
|
-
unlinkSync as
|
|
44524
|
+
unlinkSync as unlinkSync24,
|
|
43433
44525
|
writeFileSync as writeFileSync24
|
|
43434
44526
|
} from "fs";
|
|
43435
44527
|
import { dirname as dirname22, resolve as resolve25 } from "path";
|
|
@@ -43457,10 +44549,10 @@ function pendingEnrollmentPath() {
|
|
|
43457
44549
|
}
|
|
43458
44550
|
function isEnrollmentPending(invite) {
|
|
43459
44551
|
const path = pendingEnrollmentPath();
|
|
43460
|
-
if (!
|
|
44552
|
+
if (!existsSync38(path))
|
|
43461
44553
|
return false;
|
|
43462
44554
|
try {
|
|
43463
|
-
const saved = JSON.parse(
|
|
44555
|
+
const saved = JSON.parse(readFileSync29(path, "utf8"));
|
|
43464
44556
|
return saved.central === invite.central && saved.token === invite.token;
|
|
43465
44557
|
} catch {
|
|
43466
44558
|
return false;
|
|
@@ -43468,8 +44560,8 @@ function isEnrollmentPending(invite) {
|
|
|
43468
44560
|
}
|
|
43469
44561
|
function loadOrCreatePending(invite, nodeName) {
|
|
43470
44562
|
const path = pendingEnrollmentPath();
|
|
43471
|
-
if (
|
|
43472
|
-
const saved = JSON.parse(
|
|
44563
|
+
if (existsSync38(path)) {
|
|
44564
|
+
const saved = JSON.parse(readFileSync29(path, "utf8"));
|
|
43473
44565
|
if (saved.central === invite.central && saved.token === invite.token)
|
|
43474
44566
|
return saved;
|
|
43475
44567
|
throw new Error(`another Otium enrollment is pending at ${path}`);
|
|
@@ -43493,8 +44585,8 @@ function loadOrCreatePending(invite, nodeName) {
|
|
|
43493
44585
|
closeSync6(fd);
|
|
43494
44586
|
fd = undefined;
|
|
43495
44587
|
linkSync2(temporaryPath, path);
|
|
43496
|
-
|
|
43497
|
-
|
|
44588
|
+
unlinkSync24(temporaryPath);
|
|
44589
|
+
chmodSync8(path, 384);
|
|
43498
44590
|
const directoryFd = openSync6(dirname22(path), "r");
|
|
43499
44591
|
try {
|
|
43500
44592
|
fsyncSync3(directoryFd);
|
|
@@ -43504,10 +44596,10 @@ function loadOrCreatePending(invite, nodeName) {
|
|
|
43504
44596
|
} catch (error2) {
|
|
43505
44597
|
if (fd !== undefined)
|
|
43506
44598
|
closeSync6(fd);
|
|
43507
|
-
if (
|
|
43508
|
-
|
|
43509
|
-
if (error2.code === "EEXIST" &&
|
|
43510
|
-
const saved = JSON.parse(
|
|
44599
|
+
if (existsSync38(temporaryPath))
|
|
44600
|
+
unlinkSync24(temporaryPath);
|
|
44601
|
+
if (error2.code === "EEXIST" && existsSync38(path)) {
|
|
44602
|
+
const saved = JSON.parse(readFileSync29(path, "utf8"));
|
|
43511
44603
|
if (saved.central === invite.central && saved.token === invite.token)
|
|
43512
44604
|
return saved;
|
|
43513
44605
|
}
|
|
@@ -43537,14 +44629,14 @@ function replacePendingEnrollment(pending2) {
|
|
|
43537
44629
|
} catch (error2) {
|
|
43538
44630
|
if (fd !== undefined)
|
|
43539
44631
|
closeSync6(fd);
|
|
43540
|
-
if (
|
|
43541
|
-
|
|
44632
|
+
if (existsSync38(temporaryPath))
|
|
44633
|
+
unlinkSync24(temporaryPath);
|
|
43542
44634
|
throw error2;
|
|
43543
44635
|
}
|
|
43544
44636
|
}
|
|
43545
44637
|
function recordClaimedCredential(pending2, join43) {
|
|
43546
44638
|
withJoinCredentialLock(() => {
|
|
43547
|
-
const current3 = JSON.parse(
|
|
44639
|
+
const current3 = JSON.parse(readFileSync29(pendingEnrollmentPath(), "utf8"));
|
|
43548
44640
|
if (current3.central !== pending2.central || current3.token !== pending2.token || current3.idempotencyKey !== pending2.idempotencyKey || current3.publicKey !== pending2.publicKey) {
|
|
43549
44641
|
throw new Error("pending Otium enrollment changed while its claim was in flight");
|
|
43550
44642
|
}
|
|
@@ -43623,7 +44715,7 @@ async function claimEnrollment(invite, nodeName) {
|
|
|
43623
44715
|
function commitEnrollment(join43, options = {}) {
|
|
43624
44716
|
return withJoinCredentialLock(() => {
|
|
43625
44717
|
const pendingPath = pendingEnrollmentPath();
|
|
43626
|
-
const pending2 =
|
|
44718
|
+
const pending2 = existsSync38(pendingPath) ? JSON.parse(readFileSync29(pendingPath, "utf8")) : null;
|
|
43627
44719
|
const digest = joinCredentialDigest(join43);
|
|
43628
44720
|
if (pending2 && (!pending2.claimed || pending2.claimed.digest !== digest || pending2.claimed.cellId !== join43.cellId)) {
|
|
43629
44721
|
throw new Error(`pending Otium enrollment at ${pendingPath} does not match these credentials`);
|
|
@@ -43634,7 +44726,7 @@ function commitEnrollment(join43, options = {}) {
|
|
|
43634
44726
|
}
|
|
43635
44727
|
if (!pending2)
|
|
43636
44728
|
return path;
|
|
43637
|
-
|
|
44729
|
+
unlinkSync24(pendingPath);
|
|
43638
44730
|
const directoryFd = openSync6(dirname22(pendingPath), "r");
|
|
43639
44731
|
try {
|
|
43640
44732
|
fsyncSync3(directoryFd);
|
|
@@ -45823,4 +46915,4 @@ switch (command) {
|
|
|
45823
46915
|
}
|
|
45824
46916
|
}
|
|
45825
46917
|
|
|
45826
|
-
//# debugId=
|
|
46918
|
+
//# debugId=1F2F931DB2BD421464756E2164756E21
|