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/mcp-factories.js
CHANGED
|
@@ -29,6 +29,59 @@ var init_types = __esm(() => {
|
|
|
29
29
|
MAESTRO_EFFORT_VALUES = ["low", "medium", "high", "xhigh", "max"];
|
|
30
30
|
});
|
|
31
31
|
|
|
32
|
+
// ../../packages/core/src/storage/sqlite.ts
|
|
33
|
+
var isBun, Database;
|
|
34
|
+
var init_sqlite = __esm(async () => {
|
|
35
|
+
isBun = typeof process.versions.bun === "string";
|
|
36
|
+
if (isBun) {
|
|
37
|
+
({ Database } = await import("bun:sqlite"));
|
|
38
|
+
} else {
|
|
39
|
+
const nodeSqliteSpecifier = ["node", "sqlite"].join(":");
|
|
40
|
+
const { DatabaseSync } = await import(nodeSqliteSpecifier);
|
|
41
|
+
|
|
42
|
+
class NodeDatabase {
|
|
43
|
+
#db;
|
|
44
|
+
constructor(path, options = {}) {
|
|
45
|
+
this.#db = options.readonly ? new DatabaseSync(path, { readOnly: true }) : new DatabaseSync(path);
|
|
46
|
+
}
|
|
47
|
+
query(sql) {
|
|
48
|
+
return this.#db.prepare(sql);
|
|
49
|
+
}
|
|
50
|
+
prepare(sql) {
|
|
51
|
+
return this.#db.prepare(sql);
|
|
52
|
+
}
|
|
53
|
+
exec(sql) {
|
|
54
|
+
this.#db.exec(sql);
|
|
55
|
+
}
|
|
56
|
+
run(sql, ...params) {
|
|
57
|
+
return this.#db.prepare(sql).run(...params);
|
|
58
|
+
}
|
|
59
|
+
transaction(fn) {
|
|
60
|
+
const run = (begin) => (...args) => {
|
|
61
|
+
this.#db.exec(begin);
|
|
62
|
+
try {
|
|
63
|
+
const result = fn(...args);
|
|
64
|
+
this.#db.exec("COMMIT");
|
|
65
|
+
return result;
|
|
66
|
+
} catch (err) {
|
|
67
|
+
this.#db.exec("ROLLBACK");
|
|
68
|
+
throw err;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
const tx = run("BEGIN");
|
|
72
|
+
tx.deferred = run("BEGIN DEFERRED");
|
|
73
|
+
tx.immediate = run("BEGIN IMMEDIATE");
|
|
74
|
+
tx.exclusive = run("BEGIN EXCLUSIVE");
|
|
75
|
+
return tx;
|
|
76
|
+
}
|
|
77
|
+
close() {
|
|
78
|
+
this.#db.close();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
Database = NodeDatabase;
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
32
85
|
// ../../packages/core/src/platform/config-helpers.ts
|
|
33
86
|
function readEnvText(env, key) {
|
|
34
87
|
const value2 = env[key]?.trim();
|
|
@@ -72,11 +125,11 @@ import { execFileSync } from "child_process";
|
|
|
72
125
|
import { randomBytes } from "crypto";
|
|
73
126
|
import {
|
|
74
127
|
accessSync,
|
|
75
|
-
chmodSync,
|
|
128
|
+
chmodSync as chmodSync2,
|
|
76
129
|
constants,
|
|
77
|
-
existsSync,
|
|
130
|
+
existsSync as existsSync2,
|
|
78
131
|
mkdirSync,
|
|
79
|
-
readFileSync,
|
|
132
|
+
readFileSync as readFileSync2,
|
|
80
133
|
writeFileSync
|
|
81
134
|
} from "fs";
|
|
82
135
|
import { createRequire } from "module";
|
|
@@ -93,7 +146,7 @@ function resolveAgentEnv(envKey, fallback, legacyEnvKey) {
|
|
|
93
146
|
function resolveProjectRoot() {
|
|
94
147
|
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
95
148
|
const packagedRuntime = resolve(moduleDir, "runtime");
|
|
96
|
-
if (
|
|
149
|
+
if (existsSync2(resolve(packagedRuntime, "src")))
|
|
97
150
|
return packagedRuntime;
|
|
98
151
|
return resolve(moduleDir, "../..");
|
|
99
152
|
}
|
|
@@ -101,7 +154,7 @@ function resolveDependencyBin(name) {
|
|
|
101
154
|
let dir = PROJECT_ROOT;
|
|
102
155
|
while (true) {
|
|
103
156
|
const candidate = resolve(dir, "node_modules", ".bin", name);
|
|
104
|
-
if (
|
|
157
|
+
if (existsSync2(candidate))
|
|
105
158
|
return candidate;
|
|
106
159
|
const parent = dirname(dir);
|
|
107
160
|
if (parent === dir)
|
|
@@ -215,14 +268,14 @@ function loadOrCreateLocalSecret(envKey, filename, options = {}) {
|
|
|
215
268
|
if (options.persistEnvValue) {
|
|
216
269
|
writeFileSync(secretFile, `${envValue}
|
|
217
270
|
`, { mode: 384 });
|
|
218
|
-
|
|
271
|
+
chmodSync2(secretFile, 384);
|
|
219
272
|
}
|
|
220
273
|
return envValue;
|
|
221
274
|
}
|
|
222
|
-
if (
|
|
223
|
-
const stored =
|
|
275
|
+
if (existsSync2(secretFile)) {
|
|
276
|
+
const stored = readFileSync2(secretFile, "utf-8").trim();
|
|
224
277
|
if (stored) {
|
|
225
|
-
|
|
278
|
+
chmodSync2(secretFile, 384);
|
|
226
279
|
return stored;
|
|
227
280
|
}
|
|
228
281
|
}
|
|
@@ -234,10 +287,10 @@ function loadOrCreateLocalSecret(envKey, filename, options = {}) {
|
|
|
234
287
|
} catch (error) {
|
|
235
288
|
if (error.code !== "EEXIST")
|
|
236
289
|
throw error;
|
|
237
|
-
const stored =
|
|
290
|
+
const stored = readFileSync2(secretFile, "utf-8").trim();
|
|
238
291
|
if (!stored)
|
|
239
292
|
throw new Error(`Secret file exists but is empty: ${secretFile}`);
|
|
240
|
-
|
|
293
|
+
chmodSync2(secretFile, 384);
|
|
241
294
|
return stored;
|
|
242
295
|
}
|
|
243
296
|
}
|
|
@@ -569,7 +622,7 @@ var init_shared = __esm(() => {
|
|
|
569
622
|
});
|
|
570
623
|
|
|
571
624
|
// ../../packages/core/src/platform/file-utils.ts
|
|
572
|
-
import { unlinkSync as
|
|
625
|
+
import { unlinkSync as unlinkSync3 } from "fs";
|
|
573
626
|
function createSafeUnlink(host) {
|
|
574
627
|
return (path, warnLabel) => {
|
|
575
628
|
try {
|
|
@@ -589,7 +642,7 @@ var defaultSafeUnlink;
|
|
|
589
642
|
var init_file_utils = __esm(() => {
|
|
590
643
|
init_logger();
|
|
591
644
|
defaultSafeUnlink = createSafeUnlink({
|
|
592
|
-
unlink:
|
|
645
|
+
unlink: unlinkSync3,
|
|
593
646
|
warn: (context, message) => logger.warn(context, message)
|
|
594
647
|
});
|
|
595
648
|
});
|
|
@@ -601,15 +654,15 @@ import {
|
|
|
601
654
|
fsyncSync,
|
|
602
655
|
mkdirSync as mkdirSync4,
|
|
603
656
|
openSync as openSync2,
|
|
604
|
-
readFileSync as
|
|
657
|
+
readFileSync as readFileSync4,
|
|
605
658
|
renameSync as renameSync2,
|
|
606
659
|
statSync as statSync2,
|
|
607
|
-
unlinkSync as
|
|
660
|
+
unlinkSync as unlinkSync4,
|
|
608
661
|
writeFileSync as writeFileSync3
|
|
609
662
|
} from "fs";
|
|
610
663
|
import { dirname as dirname3 } from "path";
|
|
611
664
|
function readJsonlLines(filePath) {
|
|
612
|
-
return
|
|
665
|
+
return readFileSync4(filePath, "utf-8").trim().split(`
|
|
613
666
|
`).filter(Boolean);
|
|
614
667
|
}
|
|
615
668
|
function parseJsonlText(raw) {
|
|
@@ -618,7 +671,7 @@ function parseJsonlText(raw) {
|
|
|
618
671
|
}
|
|
619
672
|
function readJsonFile(filePath) {
|
|
620
673
|
try {
|
|
621
|
-
return JSON.parse(
|
|
674
|
+
return JSON.parse(readFileSync4(filePath, "utf-8"));
|
|
622
675
|
} catch {
|
|
623
676
|
return null;
|
|
624
677
|
}
|
|
@@ -708,7 +761,7 @@ function writeJsonlFile(filePath, entries) {
|
|
|
708
761
|
} catch {}
|
|
709
762
|
}
|
|
710
763
|
try {
|
|
711
|
-
|
|
764
|
+
unlinkSync4(tmpPath);
|
|
712
765
|
} catch {}
|
|
713
766
|
throw err;
|
|
714
767
|
}
|
|
@@ -744,13 +797,13 @@ var init_jsonl = __esm(() => {
|
|
|
744
797
|
|
|
745
798
|
// ../../packages/core/src/agents/rollout/claude.ts
|
|
746
799
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
747
|
-
import { existsSync as
|
|
800
|
+
import { existsSync as existsSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
748
801
|
import { homedir as homedir2 } from "os";
|
|
749
802
|
import { join as join3 } from "path";
|
|
750
803
|
function loadClaudeAttachments() {
|
|
751
804
|
if (_attachmentsCache)
|
|
752
805
|
return _attachmentsCache;
|
|
753
|
-
const raw =
|
|
806
|
+
const raw = readFileSync5(join3(FIXTURES_DIR, "claude-attachments.jsonl"), "utf8");
|
|
754
807
|
const lines = parseJsonlText(raw);
|
|
755
808
|
if (lines.length < 2) {
|
|
756
809
|
throw new Error(`loadClaudeAttachments: expected >=2 entries in claude-attachments.jsonl, got ${lines.length}`);
|
|
@@ -865,7 +918,7 @@ var init_claude = __esm(() => {
|
|
|
865
918
|
});
|
|
866
919
|
|
|
867
920
|
// ../../packages/core/src/agents/claude-registry.ts
|
|
868
|
-
import { existsSync as
|
|
921
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync5, readdirSync as readdirSync2, renameSync as renameSync3, unlinkSync as unlinkSync5 } from "fs";
|
|
869
922
|
import { homedir as homedir3 } from "os";
|
|
870
923
|
import { join as join4 } from "path";
|
|
871
924
|
var ALIAS_MAP, VALID_ALIASES, VALID_EFFORTS, claudeRegistry, claudeRegistryOperations;
|
|
@@ -917,8 +970,8 @@ var init_claude_registry = __esm(() => {
|
|
|
917
970
|
const projectsRoot = join4(homedir3(), ".claude", "projects");
|
|
918
971
|
const destDir = join4(projectsRoot, encodeClaudeCwd(cwd));
|
|
919
972
|
const destPath = join4(destDir, `${result.sessionId}.jsonl`);
|
|
920
|
-
if (!
|
|
921
|
-
const sourcePath = readdirSync2(projectsRoot).map((d) => join4(projectsRoot, d, `${result.sessionId}.jsonl`)).find((p) =>
|
|
973
|
+
if (!existsSync5(destPath)) {
|
|
974
|
+
const sourcePath = readdirSync2(projectsRoot).map((d) => join4(projectsRoot, d, `${result.sessionId}.jsonl`)).find((p) => existsSync5(p));
|
|
922
975
|
if (!sourcePath) {
|
|
923
976
|
throw new Error(`claude forkSession: fork rollout ${result.sessionId}.jsonl not found under ${projectsRoot}`);
|
|
924
977
|
}
|
|
@@ -936,7 +989,7 @@ var init_claude_registry = __esm(() => {
|
|
|
936
989
|
for (const sid of sessionIds) {
|
|
937
990
|
const path = join4(projectsDir, `${sid}.jsonl`);
|
|
938
991
|
try {
|
|
939
|
-
|
|
992
|
+
unlinkSync5(path);
|
|
940
993
|
} catch (e) {
|
|
941
994
|
if (e?.code !== "ENOENT") {
|
|
942
995
|
logger.warn({ err: e, path }, "claude cleanupRollouts: unlink failed");
|
|
@@ -952,27 +1005,27 @@ var init_claude_registry = __esm(() => {
|
|
|
952
1005
|
});
|
|
953
1006
|
|
|
954
1007
|
// ../../packages/core/src/version.ts
|
|
955
|
-
var NEGOTIUM_VERSION = "0.2.
|
|
1008
|
+
var NEGOTIUM_VERSION = "0.2.19";
|
|
956
1009
|
|
|
957
1010
|
// ../../packages/core/src/agents/codex-native-multi-agent.ts
|
|
958
1011
|
import { spawn as spawn2 } from "child_process";
|
|
959
1012
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
960
1013
|
import {
|
|
961
|
-
chmodSync as
|
|
1014
|
+
chmodSync as chmodSync3,
|
|
962
1015
|
copyFileSync,
|
|
963
|
-
existsSync as
|
|
1016
|
+
existsSync as existsSync6,
|
|
964
1017
|
mkdtempSync,
|
|
965
|
-
readFileSync as
|
|
1018
|
+
readFileSync as readFileSync6,
|
|
966
1019
|
renameSync as renameSync4,
|
|
967
1020
|
rmSync,
|
|
968
|
-
unlinkSync as
|
|
1021
|
+
unlinkSync as unlinkSync6,
|
|
969
1022
|
writeFileSync as writeFileSync5
|
|
970
1023
|
} from "fs";
|
|
971
1024
|
import { createRequire as createRequire2 } from "module";
|
|
972
1025
|
import { tmpdir } from "os";
|
|
973
1026
|
import { dirname as dirname4, join as join5 } from "path";
|
|
974
1027
|
function readPackageVersion(packageJsonPath) {
|
|
975
|
-
const parsed = JSON.parse(
|
|
1028
|
+
const parsed = JSON.parse(readFileSync6(packageJsonPath, "utf8"));
|
|
976
1029
|
if (typeof parsed.version !== "string" || !parsed.version.trim()) {
|
|
977
1030
|
throw new Error(`Codex package has no valid version: ${packageJsonPath}`);
|
|
978
1031
|
}
|
|
@@ -994,7 +1047,7 @@ function parseCodexModelCache(contents, sourcePath) {
|
|
|
994
1047
|
return parsed;
|
|
995
1048
|
}
|
|
996
1049
|
function readCodexModelCache(cachePath) {
|
|
997
|
-
const contents =
|
|
1050
|
+
const contents = readFileSync6(cachePath, "utf8");
|
|
998
1051
|
return { contents, parsed: parseCodexModelCache(contents, cachePath) };
|
|
999
1052
|
}
|
|
1000
1053
|
function readCompatibleCodexModelCache(cachePath) {
|
|
@@ -1006,16 +1059,16 @@ function readCompatibleCodexModelCache(cachePath) {
|
|
|
1006
1059
|
return cache;
|
|
1007
1060
|
}
|
|
1008
1061
|
function writePrivateFileAtomic(path, contents) {
|
|
1009
|
-
if (
|
|
1062
|
+
if (existsSync6(path) && readFileSync6(path, "utf8") === contents)
|
|
1010
1063
|
return;
|
|
1011
1064
|
const tempPath = `${path}.${process.pid}.${randomUUID3()}.tmp`;
|
|
1012
1065
|
try {
|
|
1013
1066
|
writeFileSync5(tempPath, contents, { encoding: "utf8", mode: 384 });
|
|
1014
1067
|
renameSync4(tempPath, path);
|
|
1015
|
-
|
|
1068
|
+
chmodSync3(path, 384);
|
|
1016
1069
|
} finally {
|
|
1017
1070
|
try {
|
|
1018
|
-
|
|
1071
|
+
unlinkSync6(tempPath);
|
|
1019
1072
|
} catch {}
|
|
1020
1073
|
}
|
|
1021
1074
|
}
|
|
@@ -1043,7 +1096,7 @@ async function bootstrapCodexModelCache(codexHome, cachePath) {
|
|
|
1043
1096
|
} catch {}
|
|
1044
1097
|
if (error)
|
|
1045
1098
|
reject(error);
|
|
1046
|
-
else if (!
|
|
1099
|
+
else if (!existsSync6(cachePath))
|
|
1047
1100
|
reject(new Error("Codex did not create its model cache"));
|
|
1048
1101
|
else
|
|
1049
1102
|
resolve4();
|
|
@@ -1111,12 +1164,12 @@ async function bootstrapIsolatedCodexModelCache(authFilePath, bootstrap) {
|
|
|
1111
1164
|
try {
|
|
1112
1165
|
const isolatedAuthPath = join5(isolatedHome, "auth.json");
|
|
1113
1166
|
copyFileSync(authFilePath, isolatedAuthPath);
|
|
1114
|
-
|
|
1167
|
+
chmodSync3(isolatedAuthPath, 384);
|
|
1115
1168
|
const sourceConfigPath = join5(sourceHome, "config.toml");
|
|
1116
|
-
if (
|
|
1169
|
+
if (existsSync6(sourceConfigPath)) {
|
|
1117
1170
|
const isolatedConfigPath = join5(isolatedHome, "config.toml");
|
|
1118
1171
|
copyFileSync(sourceConfigPath, isolatedConfigPath);
|
|
1119
|
-
|
|
1172
|
+
chmodSync3(isolatedConfigPath, 384);
|
|
1120
1173
|
}
|
|
1121
1174
|
await bootstrap(isolatedHome, isolatedCachePath);
|
|
1122
1175
|
return readCompatibleCodexModelCache(isolatedCachePath).contents;
|
|
@@ -1128,7 +1181,7 @@ async function ensureCodexModelCache(authFilePath, bootstrap = bootstrapCodexMod
|
|
|
1128
1181
|
const codexHome = dirname4(authFilePath);
|
|
1129
1182
|
const configuredCachePath = process.env.NEGOTIUM_CODEX_MODELS_CACHE_FILE;
|
|
1130
1183
|
if (configuredCachePath) {
|
|
1131
|
-
if (!
|
|
1184
|
+
if (!existsSync6(configuredCachePath)) {
|
|
1132
1185
|
throw new Error(`Configured Codex model cache does not exist: ${configuredCachePath}`);
|
|
1133
1186
|
}
|
|
1134
1187
|
readCompatibleCodexModelCache(configuredCachePath);
|
|
@@ -1136,14 +1189,14 @@ async function ensureCodexModelCache(authFilePath, bootstrap = bootstrapCodexMod
|
|
|
1136
1189
|
}
|
|
1137
1190
|
const bundledCachePath = bundledCodexModelCachePath(authFilePath);
|
|
1138
1191
|
const sharedCachePath = join5(codexHome, "models_cache.json");
|
|
1139
|
-
if (
|
|
1192
|
+
if (existsSync6(sharedCachePath)) {
|
|
1140
1193
|
try {
|
|
1141
1194
|
const shared = readCompatibleCodexModelCache(sharedCachePath);
|
|
1142
1195
|
writePrivateFileAtomic(bundledCachePath, shared.contents);
|
|
1143
1196
|
return bundledCachePath;
|
|
1144
1197
|
} catch {}
|
|
1145
1198
|
}
|
|
1146
|
-
if (
|
|
1199
|
+
if (existsSync6(bundledCachePath)) {
|
|
1147
1200
|
try {
|
|
1148
1201
|
readCompatibleCodexModelCache(bundledCachePath);
|
|
1149
1202
|
return bundledCachePath;
|
|
@@ -2128,6 +2181,8 @@ var init_mcp_config = __esm(() => {
|
|
|
2128
2181
|
return env ? buildStdioMcpServer(agent, CANONICAL_MCP_PROXY_SERVER, ["--surface=wiki"], env) : null;
|
|
2129
2182
|
}
|
|
2130
2183
|
const args = [`--user-id=${userId}`];
|
|
2184
|
+
if (topicId)
|
|
2185
|
+
args.push(`--current-topic-id=${topicId}`);
|
|
2131
2186
|
const resolvedWikiTopicId = wikiTopicId ?? topicId ?? (session !== "dm" ? session : undefined);
|
|
2132
2187
|
if (resolvedWikiTopicId)
|
|
2133
2188
|
args.push(`--topic-id=${resolvedWikiTopicId}`);
|
|
@@ -2186,59 +2241,6 @@ var init_mcp_config = __esm(() => {
|
|
|
2186
2241
|
nodeMcpEntries = [];
|
|
2187
2242
|
});
|
|
2188
2243
|
|
|
2189
|
-
// ../../packages/core/src/storage/sqlite.ts
|
|
2190
|
-
var isBun, Database;
|
|
2191
|
-
var init_sqlite = __esm(async () => {
|
|
2192
|
-
isBun = typeof process.versions.bun === "string";
|
|
2193
|
-
if (isBun) {
|
|
2194
|
-
({ Database } = await import("bun:sqlite"));
|
|
2195
|
-
} else {
|
|
2196
|
-
const nodeSqliteSpecifier = ["node", "sqlite"].join(":");
|
|
2197
|
-
const { DatabaseSync } = await import(nodeSqliteSpecifier);
|
|
2198
|
-
|
|
2199
|
-
class NodeDatabase {
|
|
2200
|
-
#db;
|
|
2201
|
-
constructor(path, options = {}) {
|
|
2202
|
-
this.#db = options.readonly ? new DatabaseSync(path, { readOnly: true }) : new DatabaseSync(path);
|
|
2203
|
-
}
|
|
2204
|
-
query(sql) {
|
|
2205
|
-
return this.#db.prepare(sql);
|
|
2206
|
-
}
|
|
2207
|
-
prepare(sql) {
|
|
2208
|
-
return this.#db.prepare(sql);
|
|
2209
|
-
}
|
|
2210
|
-
exec(sql) {
|
|
2211
|
-
this.#db.exec(sql);
|
|
2212
|
-
}
|
|
2213
|
-
run(sql, ...params) {
|
|
2214
|
-
return this.#db.prepare(sql).run(...params);
|
|
2215
|
-
}
|
|
2216
|
-
transaction(fn) {
|
|
2217
|
-
const run = (begin) => (...args) => {
|
|
2218
|
-
this.#db.exec(begin);
|
|
2219
|
-
try {
|
|
2220
|
-
const result = fn(...args);
|
|
2221
|
-
this.#db.exec("COMMIT");
|
|
2222
|
-
return result;
|
|
2223
|
-
} catch (err) {
|
|
2224
|
-
this.#db.exec("ROLLBACK");
|
|
2225
|
-
throw err;
|
|
2226
|
-
}
|
|
2227
|
-
};
|
|
2228
|
-
const tx = run("BEGIN");
|
|
2229
|
-
tx.deferred = run("BEGIN DEFERRED");
|
|
2230
|
-
tx.immediate = run("BEGIN IMMEDIATE");
|
|
2231
|
-
tx.exclusive = run("BEGIN EXCLUSIVE");
|
|
2232
|
-
return tx;
|
|
2233
|
-
}
|
|
2234
|
-
close() {
|
|
2235
|
-
this.#db.close();
|
|
2236
|
-
}
|
|
2237
|
-
}
|
|
2238
|
-
Database = NodeDatabase;
|
|
2239
|
-
}
|
|
2240
|
-
});
|
|
2241
|
-
|
|
2242
2244
|
// ../../packages/core/src/storage/vault-crypto-core.ts
|
|
2243
2245
|
import { createCipheriv, createDecipheriv, createHash as createHash2, randomBytes as randomBytes3 } from "crypto";
|
|
2244
2246
|
function encryptionKey(masterKey) {
|
|
@@ -2295,7 +2297,7 @@ var init_vault_crypto = __esm(() => {
|
|
|
2295
2297
|
});
|
|
2296
2298
|
|
|
2297
2299
|
// ../../packages/core/src/storage/vault.ts
|
|
2298
|
-
import { chmodSync as
|
|
2300
|
+
import { chmodSync as chmodSync4, mkdirSync as mkdirSync6 } from "fs";
|
|
2299
2301
|
import { join as join6 } from "path";
|
|
2300
2302
|
function initializeVaultDatabase(database) {
|
|
2301
2303
|
database.exec("PRAGMA journal_mode = WAL");
|
|
@@ -2335,7 +2337,7 @@ function openVaultDatabase(dataDir) {
|
|
|
2335
2337
|
const path = join6(vaultDir, "vault.db");
|
|
2336
2338
|
mkdirSync6(vaultDir, { recursive: true, mode: 448 });
|
|
2337
2339
|
const database = new Database(path, { create: true });
|
|
2338
|
-
|
|
2340
|
+
chmodSync4(path, 384);
|
|
2339
2341
|
initializeVaultDatabase(database);
|
|
2340
2342
|
return database;
|
|
2341
2343
|
}
|
|
@@ -2496,7 +2498,7 @@ var init_execution_host = __esm(async () => {
|
|
|
2496
2498
|
|
|
2497
2499
|
// ../../packages/core/src/agents/rollout/codex.ts
|
|
2498
2500
|
import { randomBytes as randomBytes4 } from "crypto";
|
|
2499
|
-
import { existsSync as
|
|
2501
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7, realpathSync as realpathSync2, statSync as statSync3, unlinkSync as unlinkSync7 } from "fs";
|
|
2500
2502
|
import { basename as basename2, dirname as dirname6, join as join7, resolve as resolve5 } from "path";
|
|
2501
2503
|
function codexSessionsDir() {
|
|
2502
2504
|
return join7(hostedCodexHomePath(), "sessions");
|
|
@@ -2504,7 +2506,7 @@ function codexSessionsDir() {
|
|
|
2504
2506
|
function loadCodexShell() {
|
|
2505
2507
|
if (_shellCache)
|
|
2506
2508
|
return _shellCache;
|
|
2507
|
-
const raw =
|
|
2509
|
+
const raw = readFileSync7(join7(FIXTURES_DIR, "codex-shell.jsonl"), "utf8");
|
|
2508
2510
|
const lines = parseJsonlText(raw);
|
|
2509
2511
|
if (lines.length < 5) {
|
|
2510
2512
|
throw new Error(`loadCodexShell: expected >=5 entries in codex-shell.jsonl, got ${lines.length}`);
|
|
@@ -2690,7 +2692,7 @@ function readCodexPatchCallIds(threadId) {
|
|
|
2690
2692
|
if (!path)
|
|
2691
2693
|
return [];
|
|
2692
2694
|
try {
|
|
2693
|
-
return extractCodexPatchCallIds(
|
|
2695
|
+
return extractCodexPatchCallIds(readFileSync7(path, "utf8"));
|
|
2694
2696
|
} catch (error) {
|
|
2695
2697
|
logger.debug({ error, threadId }, "codex patch ids: rollout read failed");
|
|
2696
2698
|
return [];
|
|
@@ -2701,7 +2703,7 @@ function readLatestCodexPatchPreview(threadId, expectedPaths, consumedCallIds =
|
|
|
2701
2703
|
if (!path)
|
|
2702
2704
|
return;
|
|
2703
2705
|
try {
|
|
2704
|
-
return extractLatestCodexPatchPreview(
|
|
2706
|
+
return extractLatestCodexPatchPreview(readFileSync7(path, "utf8"), expectedPaths, consumedCallIds, expectedCallId);
|
|
2705
2707
|
} catch (error) {
|
|
2706
2708
|
logger.debug({ error, threadId }, "codex patch preview: rollout read failed");
|
|
2707
2709
|
return;
|
|
@@ -2759,7 +2761,7 @@ function readLatestCodexContextUsage(threadId) {
|
|
|
2759
2761
|
if (!path)
|
|
2760
2762
|
return;
|
|
2761
2763
|
try {
|
|
2762
|
-
return extractLatestCodexContextUsage(
|
|
2764
|
+
return extractLatestCodexContextUsage(readFileSync7(path, "utf8"));
|
|
2763
2765
|
} catch (error) {
|
|
2764
2766
|
logger.debug({ error, threadId }, "codex context usage: rollout read failed");
|
|
2765
2767
|
return;
|
|
@@ -2770,7 +2772,7 @@ function readLatestCodexTokenTotals(threadId) {
|
|
|
2770
2772
|
if (!path)
|
|
2771
2773
|
return;
|
|
2772
2774
|
try {
|
|
2773
|
-
return extractLatestCodexTokenTotals(
|
|
2775
|
+
return extractLatestCodexTokenTotals(readFileSync7(path, "utf8"));
|
|
2774
2776
|
} catch (error) {
|
|
2775
2777
|
logger.debug({ error, threadId }, "codex token totals: rollout read failed");
|
|
2776
2778
|
return;
|
|
@@ -2781,7 +2783,7 @@ function migrateCodexRolloutNativeMultiAgentMetadata(threadId) {
|
|
|
2781
2783
|
if (!path)
|
|
2782
2784
|
return false;
|
|
2783
2785
|
try {
|
|
2784
|
-
const entries = parseJsonlText(
|
|
2786
|
+
const entries = parseJsonlText(readFileSync7(path, "utf8"));
|
|
2785
2787
|
let changed = false;
|
|
2786
2788
|
for (const entry of entries) {
|
|
2787
2789
|
if (entry.type !== "session_meta" && entry.type !== "turn_context")
|
|
@@ -2811,7 +2813,7 @@ function latestCodexRolloutPath(threadId) {
|
|
|
2811
2813
|
if (buckets) {
|
|
2812
2814
|
for (const bucket of buckets) {
|
|
2813
2815
|
const dir = join7(sessionsDir, bucket);
|
|
2814
|
-
if (!
|
|
2816
|
+
if (!existsSync7(dir))
|
|
2815
2817
|
continue;
|
|
2816
2818
|
const glob = new Bun.Glob(`rollout-*-${threadId}.jsonl`);
|
|
2817
2819
|
for (const rel of glob.scanSync({ cwd: dir, onlyFiles: true })) {
|
|
@@ -2944,14 +2946,14 @@ function sweepPriorRolloutsForThread(threadId) {
|
|
|
2944
2946
|
}
|
|
2945
2947
|
for (const bucket of buckets) {
|
|
2946
2948
|
const dir = join7(sessionsDir, bucket);
|
|
2947
|
-
if (!
|
|
2949
|
+
if (!existsSync7(dir))
|
|
2948
2950
|
continue;
|
|
2949
2951
|
try {
|
|
2950
2952
|
const glob = new Bun.Glob(`rollout-*-${threadId}.jsonl`);
|
|
2951
2953
|
for (const rel of glob.scanSync({ cwd: dir, onlyFiles: true })) {
|
|
2952
2954
|
const fullPath = join7(dir, rel);
|
|
2953
2955
|
try {
|
|
2954
|
-
|
|
2956
|
+
unlinkSync7(fullPath);
|
|
2955
2957
|
} catch (e) {
|
|
2956
2958
|
if (e?.code !== "ENOENT") {
|
|
2957
2959
|
logger.warn({ err: e, path: fullPath }, "writeCodexRollout: prior-rollout unlink failed (continuing)");
|
|
@@ -2969,7 +2971,7 @@ function sweepPriorRolloutsFullTree(threadId, sessionsDir) {
|
|
|
2969
2971
|
for (const rel of glob.scanSync({ cwd: sessionsDir, onlyFiles: true })) {
|
|
2970
2972
|
const fullPath = join7(sessionsDir, rel);
|
|
2971
2973
|
try {
|
|
2972
|
-
|
|
2974
|
+
unlinkSync7(fullPath);
|
|
2973
2975
|
} catch (e) {
|
|
2974
2976
|
if (e?.code !== "ENOENT") {
|
|
2975
2977
|
logger.warn({ err: e, path: fullPath }, "writeCodexRollout: prior-rollout unlink failed (continuing)");
|
|
@@ -3146,7 +3148,7 @@ var init_codex_app_server = __esm(async () => {
|
|
|
3146
3148
|
});
|
|
3147
3149
|
|
|
3148
3150
|
// ../../packages/core/src/agents/codex-registry.ts
|
|
3149
|
-
import { existsSync as
|
|
3151
|
+
import { existsSync as existsSync8, unlinkSync as unlinkSync8 } from "fs";
|
|
3150
3152
|
import { join as join8 } from "path";
|
|
3151
3153
|
var VALID_EFFORTS2, codexRegistry, codexRegistryOperations;
|
|
3152
3154
|
var init_codex_registry = __esm(async () => {
|
|
@@ -3193,7 +3195,7 @@ var init_codex_registry = __esm(async () => {
|
|
|
3193
3195
|
if (sessionIds.length === 0)
|
|
3194
3196
|
return;
|
|
3195
3197
|
const sessionsDir = join8(hostedCodexHomePath(), "sessions");
|
|
3196
|
-
if (!
|
|
3198
|
+
if (!existsSync8(sessionsDir))
|
|
3197
3199
|
return;
|
|
3198
3200
|
const failures = [];
|
|
3199
3201
|
for (const tid of sessionIds) {
|
|
@@ -3202,7 +3204,7 @@ var init_codex_registry = __esm(async () => {
|
|
|
3202
3204
|
for await (const rel of glob.scan({ cwd: sessionsDir, onlyFiles: true })) {
|
|
3203
3205
|
const path = join8(sessionsDir, rel);
|
|
3204
3206
|
try {
|
|
3205
|
-
|
|
3207
|
+
unlinkSync8(path);
|
|
3206
3208
|
} catch (e) {
|
|
3207
3209
|
if (e?.code !== "ENOENT") {
|
|
3208
3210
|
logger.warn({ err: e, path }, "codex cleanupRollouts: unlink failed");
|
|
@@ -3224,7 +3226,7 @@ var init_codex_registry = __esm(async () => {
|
|
|
3224
3226
|
|
|
3225
3227
|
// ../../packages/core/src/agents/maestro-registry.ts
|
|
3226
3228
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
3227
|
-
import { existsSync as
|
|
3229
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
|
|
3228
3230
|
import { homedir as homedir4 } from "os";
|
|
3229
3231
|
import { join as join9, resolve as resolve6 } from "path";
|
|
3230
3232
|
function maestroSessionsDir() {
|
|
@@ -3237,10 +3239,10 @@ function maestroActiveSessionPath(sessionId) {
|
|
|
3237
3239
|
return join9(maestroSessionsDir(), `${sessionId}.active.jsonl`);
|
|
3238
3240
|
}
|
|
3239
3241
|
function existingCreatedAt(path) {
|
|
3240
|
-
if (!
|
|
3242
|
+
if (!existsSync9(path))
|
|
3241
3243
|
return;
|
|
3242
3244
|
try {
|
|
3243
|
-
const firstLine =
|
|
3245
|
+
const firstLine = readFileSync8(path, "utf8").split(`
|
|
3244
3246
|
`, 1)[0];
|
|
3245
3247
|
const parsed = JSON.parse(firstLine);
|
|
3246
3248
|
return typeof parsed._meta?.createdAt === "string" ? parsed._meta.createdAt : undefined;
|
|
@@ -3321,7 +3323,7 @@ var init_maestro_registry = __esm(() => {
|
|
|
3321
3323
|
if (!parentMessages) {
|
|
3322
3324
|
throw new Error(`Maestro parent session not found: ${options.parentSessionId}`);
|
|
3323
3325
|
}
|
|
3324
|
-
const parentHasActiveProjection =
|
|
3326
|
+
const parentHasActiveProjection = existsSync9(maestroActiveSessionPath(options.parentSessionId));
|
|
3325
3327
|
const fork = forkSessionAt({
|
|
3326
3328
|
parentSessionId: options.parentSessionId,
|
|
3327
3329
|
messageIndex: parentMessages.length,
|
|
@@ -3336,7 +3338,7 @@ var init_maestro_registry = __esm(() => {
|
|
|
3336
3338
|
return {
|
|
3337
3339
|
forkId: fork.sessionId,
|
|
3338
3340
|
rolloutPath: fork.rolloutPath,
|
|
3339
|
-
...
|
|
3341
|
+
...existsSync9(activePath) ? { cleanupPaths: [activePath] } : {}
|
|
3340
3342
|
};
|
|
3341
3343
|
},
|
|
3342
3344
|
async cleanupRollouts(options) {
|
|
@@ -3348,7 +3350,7 @@ var init_maestro_registry = __esm(() => {
|
|
|
3348
3350
|
const remaining = [
|
|
3349
3351
|
maestroSessionPath(sessionId),
|
|
3350
3352
|
maestroActiveSessionPath(sessionId)
|
|
3351
|
-
].filter(
|
|
3353
|
+
].filter(existsSync9);
|
|
3352
3354
|
if (remaining.length > 0) {
|
|
3353
3355
|
throw new Error(`Maestro session files remain after cleanup: ${remaining.join(", ")}`);
|
|
3354
3356
|
}
|
|
@@ -4140,6 +4142,7 @@ function initializeApiTopicsSchema() {
|
|
|
4140
4142
|
last_message_at TEXT,
|
|
4141
4143
|
parent_topic_id TEXT,
|
|
4142
4144
|
memory_topic_id TEXT,
|
|
4145
|
+
memory_key TEXT,
|
|
4143
4146
|
is_fork INTEGER NOT NULL DEFAULT 0 CHECK (is_fork IN (0,1)),
|
|
4144
4147
|
is_subagent INTEGER NOT NULL DEFAULT 0 CHECK (is_subagent IN (0,1)),
|
|
4145
4148
|
visibility TEXT NOT NULL DEFAULT 'visible' CHECK (visibility IN ('visible','hidden')),
|
|
@@ -4189,8 +4192,8 @@ function initializeApiTopicsSchema() {
|
|
|
4189
4192
|
const legacyBaseEffort = row.base_effort ?? row.default_effort;
|
|
4190
4193
|
db.query(`INSERT INTO api_topics_next
|
|
4191
4194
|
(id,title,kind,description,agent,base_model,base_effort,response_policy,
|
|
4192
|
-
created_at,last_message_at,parent_topic_id,memory_topic_id,is_fork,is_subagent,visibility,access_mode,session_id)
|
|
4193
|
-
VALUES (
|
|
4195
|
+
created_at,last_message_at,parent_topic_id,memory_topic_id,memory_key,is_fork,is_subagent,visibility,access_mode,session_id)
|
|
4196
|
+
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`).run(String(row.id), String(row.title), normalized.kind, typeof row.description === "string" ? row.description : null, normalized.agent ?? null, typeof legacyBaseModel === "string" ? legacyBaseModel : null, typeof legacyBaseEffort === "string" ? legacyBaseEffort : null, normalized.aiMode, String(row.created_at), typeof row.last_message_at === "string" ? row.last_message_at : null, typeof row.parent_topic_id === "string" ? row.parent_topic_id : null, typeof row.memory_topic_id === "string" ? row.memory_topic_id : null, typeof row.memory_key === "string" ? row.memory_key : null, Number(row.is_fork ?? 0) !== 0 ? 1 : 0, Number(row.is_subagent ?? 0) !== 0 ? 1 : 0, row.visibility === "hidden" ? "hidden" : "visible", row.access_mode === "shared" ? "shared" : "private", typeof row.session_id === "string" ? row.session_id : null);
|
|
4194
4197
|
}
|
|
4195
4198
|
db.exec("DROP TABLE IF EXISTS topic_members");
|
|
4196
4199
|
db.exec("DROP TABLE api_topics");
|
|
@@ -4259,6 +4262,9 @@ function initializeApiTopicsSchema() {
|
|
|
4259
4262
|
if (!tableColumns("api_topics").has("memory_topic_id")) {
|
|
4260
4263
|
db.exec("ALTER TABLE api_topics ADD COLUMN memory_topic_id TEXT");
|
|
4261
4264
|
}
|
|
4265
|
+
if (!tableColumns("api_topics").has("memory_key")) {
|
|
4266
|
+
db.exec("ALTER TABLE api_topics ADD COLUMN memory_key TEXT");
|
|
4267
|
+
}
|
|
4262
4268
|
db.exec(`
|
|
4263
4269
|
UPDATE api_topics
|
|
4264
4270
|
SET browser_profile_owner = (
|
|
@@ -4319,6 +4325,7 @@ function rowToDto2(r, participants = getTopicParticipants(r.id), tellTargets) {
|
|
|
4319
4325
|
lastMessageAt: r.last_message_at ?? new Date().toISOString(),
|
|
4320
4326
|
parentTopicId: r.parent_topic_id ?? undefined,
|
|
4321
4327
|
memoryTopicId: r.memory_topic_id ?? undefined,
|
|
4328
|
+
memoryKey: r.memory_key ?? undefined,
|
|
4322
4329
|
isFork: r.is_fork !== 0,
|
|
4323
4330
|
...r.is_subagent !== 0 ? { isSubagent: true } : {},
|
|
4324
4331
|
...r.is_subagent !== 0 ? {
|
|
@@ -4390,9 +4397,9 @@ function upsertTopic(t) {
|
|
|
4390
4397
|
db.transaction(() => {
|
|
4391
4398
|
db.query(`INSERT INTO api_topics
|
|
4392
4399
|
(id,title,kind,description,agent,base_model,base_effort,response_policy,
|
|
4393
|
-
created_at,last_message_at,parent_topic_id,memory_topic_id,is_fork,is_subagent,visibility,access_mode,
|
|
4400
|
+
created_at,last_message_at,parent_topic_id,memory_topic_id,memory_key,is_fork,is_subagent,visibility,access_mode,
|
|
4394
4401
|
subagent_report_mode)
|
|
4395
|
-
VALUES (
|
|
4402
|
+
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
|
4396
4403
|
ON CONFLICT(id) DO UPDATE SET
|
|
4397
4404
|
title = excluded.title,
|
|
4398
4405
|
kind = excluded.kind,
|
|
@@ -4405,11 +4412,12 @@ function upsertTopic(t) {
|
|
|
4405
4412
|
last_message_at = excluded.last_message_at,
|
|
4406
4413
|
parent_topic_id = excluded.parent_topic_id,
|
|
4407
4414
|
memory_topic_id = excluded.memory_topic_id,
|
|
4415
|
+
memory_key = excluded.memory_key,
|
|
4408
4416
|
is_fork = excluded.is_fork,
|
|
4409
4417
|
is_subagent = excluded.is_subagent,
|
|
4410
4418
|
visibility = excluded.visibility,
|
|
4411
4419
|
access_mode = excluded.access_mode,
|
|
4412
|
-
subagent_report_mode = excluded.subagent_report_mode`).run(t.id, t.title, normalized.kind, t.description ?? null, normalized.agent ?? null, t.defaultModel ?? null, t.defaultEffort ?? null, normalized.aiMode, t.createdAt, t.lastMessageAt ?? null, t.parentTopicId ?? null, t.memoryTopicId ?? null, t.isFork ? 1 : 0, t.isSubagent ? 1 : 0, normalizeTopicVisibility(t.visibility), normalizeTopicAccessMode(t.accessMode), t.subagentReportMode ?? "auto");
|
|
4420
|
+
subagent_report_mode = excluded.subagent_report_mode`).run(t.id, t.title, normalized.kind, t.description ?? null, normalized.agent ?? null, t.defaultModel ?? null, t.defaultEffort ?? null, normalized.aiMode, t.createdAt, t.lastMessageAt ?? null, t.parentTopicId ?? null, t.memoryTopicId ?? null, t.memoryKey ?? null, t.isFork ? 1 : 0, t.isSubagent ? 1 : 0, normalizeTopicVisibility(t.visibility), normalizeTopicAccessMode(t.accessMode), t.subagentReportMode ?? "auto");
|
|
4413
4421
|
db.query("DELETE FROM topic_members WHERE topic_id = ?").run(t.id);
|
|
4414
4422
|
for (const participant of t.participants) {
|
|
4415
4423
|
db.query("INSERT INTO topic_members (topic_id,user_id,role) VALUES (?,?,?)").run(t.id, participant.userId, participant.role);
|
|
@@ -5096,7 +5104,7 @@ var init_registry = __esm(async () => {
|
|
|
5096
5104
|
});
|
|
5097
5105
|
|
|
5098
5106
|
// ../../packages/core/src/agents/fork.ts
|
|
5099
|
-
import { existsSync as
|
|
5107
|
+
import { existsSync as existsSync11, unlinkSync as unlinkSync9 } from "fs";
|
|
5100
5108
|
function createAgentForkHelpers(host) {
|
|
5101
5109
|
return {
|
|
5102
5110
|
async forkAgentSession(options) {
|
|
@@ -5133,8 +5141,8 @@ var init_fork = __esm(async () => {
|
|
|
5133
5141
|
await init_registry();
|
|
5134
5142
|
defaultForkHelpers = createAgentForkHelpers({
|
|
5135
5143
|
forkSession: (agent, options) => getRegistryOperations(agent).forkSession(options),
|
|
5136
|
-
exists:
|
|
5137
|
-
unlink:
|
|
5144
|
+
exists: existsSync11,
|
|
5145
|
+
unlink: unlinkSync9,
|
|
5138
5146
|
warn: (details, message) => logger.warn(details, message)
|
|
5139
5147
|
});
|
|
5140
5148
|
});
|
|
@@ -5533,7 +5541,7 @@ var init_runtime_process_leases = __esm(async () => {
|
|
|
5533
5541
|
|
|
5534
5542
|
// ../../packages/core/src/platform/playwright/browser-processes.ts
|
|
5535
5543
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
5536
|
-
import { readdirSync as readdirSync4, unlinkSync as
|
|
5544
|
+
import { readdirSync as readdirSync4, unlinkSync as unlinkSync10 } from "fs";
|
|
5537
5545
|
import { createServer } from "net";
|
|
5538
5546
|
import { resolve as resolve8, sep } from "path";
|
|
5539
5547
|
function isPortInUse(port) {
|
|
@@ -5662,7 +5670,7 @@ function cleanSingletonFiles(userDataDir) {
|
|
|
5662
5670
|
for (const f of files) {
|
|
5663
5671
|
if (f.startsWith("Singleton")) {
|
|
5664
5672
|
try {
|
|
5665
|
-
|
|
5673
|
+
unlinkSync10(resolve8(userDataDir, f));
|
|
5666
5674
|
logger.info({ file: f, userDataDir }, "Removed stale Singleton file");
|
|
5667
5675
|
} catch (e) {
|
|
5668
5676
|
logger.warn({ err: e, file: f }, "Failed to remove stale Chrome Singleton file");
|
|
@@ -5803,7 +5811,7 @@ var init_vault_public = __esm(async () => {
|
|
|
5803
5811
|
|
|
5804
5812
|
// ../../packages/core/src/platform/playwright/vault-broker.ts
|
|
5805
5813
|
import { randomBytes as randomBytes5, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
5806
|
-
import { chmodSync as
|
|
5814
|
+
import { chmodSync as chmodSync5 } from "fs";
|
|
5807
5815
|
import { createServer as createServer2 } from "net";
|
|
5808
5816
|
import { tmpdir as tmpdir2 } from "os";
|
|
5809
5817
|
import { join as join12 } from "path";
|
|
@@ -5999,7 +6007,7 @@ async function createBrowserVaultBroker(userId) {
|
|
|
5999
6007
|
server.once("error", reject);
|
|
6000
6008
|
server.listen(socketPath, resolveListen);
|
|
6001
6009
|
});
|
|
6002
|
-
|
|
6010
|
+
chmodSync5(socketPath, 384);
|
|
6003
6011
|
let closed = false;
|
|
6004
6012
|
return {
|
|
6005
6013
|
socketPath,
|
|
@@ -6107,11 +6115,11 @@ import { execFileSync as execFileSync4, spawn as spawn6 } from "child_process";
|
|
|
6107
6115
|
import { createHash as createHash3, randomBytes as randomBytes6, timingSafeEqual as timingSafeEqual3 } from "crypto";
|
|
6108
6116
|
import {
|
|
6109
6117
|
cpSync,
|
|
6110
|
-
existsSync as
|
|
6118
|
+
existsSync as existsSync12,
|
|
6111
6119
|
mkdirSync as mkdirSync9,
|
|
6112
|
-
readFileSync as
|
|
6120
|
+
readFileSync as readFileSync10,
|
|
6113
6121
|
rmSync as rmSync2,
|
|
6114
|
-
unlinkSync as
|
|
6122
|
+
unlinkSync as unlinkSync11,
|
|
6115
6123
|
writeFileSync as writeFileSync7
|
|
6116
6124
|
} from "fs";
|
|
6117
6125
|
import { dirname as dirname8, join as join13, resolve as resolve10 } from "path";
|
|
@@ -6121,7 +6129,7 @@ function removeDefaultProfileDataDir(userDataDir) {
|
|
|
6121
6129
|
if (dirname8(target) !== root) {
|
|
6122
6130
|
throw new Error(`Refusing to delete browser profile outside managed root: ${target}`);
|
|
6123
6131
|
}
|
|
6124
|
-
const existed =
|
|
6132
|
+
const existed = existsSync12(target);
|
|
6125
6133
|
rmSync2(target, { recursive: true, force: true });
|
|
6126
6134
|
return existed;
|
|
6127
6135
|
}
|
|
@@ -6211,7 +6219,7 @@ function writePortFile(instanceKey, port) {
|
|
|
6211
6219
|
}
|
|
6212
6220
|
function deletePortFile(instanceKey) {
|
|
6213
6221
|
try {
|
|
6214
|
-
|
|
6222
|
+
unlinkSync11(join13(managerHost.portsDir, portFileName(instanceKey)));
|
|
6215
6223
|
} catch (e) {
|
|
6216
6224
|
if (e.code === "ENOENT")
|
|
6217
6225
|
return;
|
|
@@ -6696,11 +6704,11 @@ async function cloneProfileForChild(opts) {
|
|
|
6696
6704
|
killInstance(dstKey);
|
|
6697
6705
|
await delay(500);
|
|
6698
6706
|
}
|
|
6699
|
-
if (!
|
|
6707
|
+
if (!existsSync12(srcDir)) {
|
|
6700
6708
|
return { copied: false, srcDir, dstDir, reason: "src-missing" };
|
|
6701
6709
|
}
|
|
6702
6710
|
try {
|
|
6703
|
-
if (
|
|
6711
|
+
if (existsSync12(dstDir)) {
|
|
6704
6712
|
rmSync2(dstDir, { recursive: true, force: true });
|
|
6705
6713
|
}
|
|
6706
6714
|
mkdirSync9(dirname8(dstDir), { recursive: true });
|
|
@@ -6721,7 +6729,7 @@ async function cloneProfileForChild(opts) {
|
|
|
6721
6729
|
cleanSingletonFiles(dstDir);
|
|
6722
6730
|
for (const f of ["DevToolsActivePort", "LOCK"]) {
|
|
6723
6731
|
try {
|
|
6724
|
-
|
|
6732
|
+
unlinkSync11(resolve10(dstDir, f));
|
|
6725
6733
|
} catch {}
|
|
6726
6734
|
}
|
|
6727
6735
|
logger.info({ srcKey, dstKey, srcDir, dstDir }, "Cloned Playwright profile for child topic");
|
|
@@ -7269,11 +7277,11 @@ var init_api_topic_config = __esm(async () => {
|
|
|
7269
7277
|
|
|
7270
7278
|
// ../../packages/core/src/storage/conversations.ts
|
|
7271
7279
|
import {
|
|
7272
|
-
existsSync as
|
|
7280
|
+
existsSync as existsSync13,
|
|
7273
7281
|
mkdirSync as mkdirSync10,
|
|
7274
|
-
readFileSync as
|
|
7282
|
+
readFileSync as readFileSync11,
|
|
7275
7283
|
renameSync as renameSync5,
|
|
7276
|
-
unlinkSync as
|
|
7284
|
+
unlinkSync as unlinkSync12,
|
|
7277
7285
|
writeFileSync as writeFileSync8
|
|
7278
7286
|
} from "fs";
|
|
7279
7287
|
import { dirname as dirname9, join as join14 } from "path";
|
|
@@ -7292,7 +7300,7 @@ function getActiveConversationPath(userId, topicName) {
|
|
|
7292
7300
|
return rawPath.endsWith(".jsonl") ? `${rawPath.slice(0, -".jsonl".length)}.active.jsonl` : `${rawPath}.active`;
|
|
7293
7301
|
}
|
|
7294
7302
|
function hasActiveConversation(userId, topicName) {
|
|
7295
|
-
return
|
|
7303
|
+
return existsSync13(getActiveConversationPath(userId, topicName));
|
|
7296
7304
|
}
|
|
7297
7305
|
function appendConversationEvent(userId, topicName, agent, event) {
|
|
7298
7306
|
try {
|
|
@@ -7314,7 +7322,7 @@ function appendConversationEventStrict(userId, topicName, agent, event) {
|
|
|
7314
7322
|
mkdirSync10(dirname9(path), { recursive: true });
|
|
7315
7323
|
appendJsonlLine(path, line);
|
|
7316
7324
|
const activePath = getActiveConversationPath(userId, topicName);
|
|
7317
|
-
if (
|
|
7325
|
+
if (existsSync13(activePath)) {
|
|
7318
7326
|
try {
|
|
7319
7327
|
appendJsonlLine(activePath, line);
|
|
7320
7328
|
} catch (cause) {
|
|
@@ -7324,11 +7332,11 @@ function appendConversationEventStrict(userId, topicName, agent, event) {
|
|
|
7324
7332
|
}
|
|
7325
7333
|
function readConversationPath(path) {
|
|
7326
7334
|
const out = [];
|
|
7327
|
-
if (!
|
|
7335
|
+
if (!existsSync13(path))
|
|
7328
7336
|
return out;
|
|
7329
7337
|
let raw;
|
|
7330
7338
|
try {
|
|
7331
|
-
raw =
|
|
7339
|
+
raw = readFileSync11(path, "utf8");
|
|
7332
7340
|
} catch (err) {
|
|
7333
7341
|
logger.warn({ err, path }, "readConversation: read failed");
|
|
7334
7342
|
return out;
|
|
@@ -7347,7 +7355,7 @@ function readConversationPath(path) {
|
|
|
7347
7355
|
}
|
|
7348
7356
|
function readConversation(userId, topicName) {
|
|
7349
7357
|
const activePath = getActiveConversationPath(userId, topicName);
|
|
7350
|
-
return readConversationPath(
|
|
7358
|
+
return readConversationPath(existsSync13(activePath) ? activePath : getConversationPath(userId, topicName));
|
|
7351
7359
|
}
|
|
7352
7360
|
function readRawConversation(userId, topicName) {
|
|
7353
7361
|
return readConversationPath(getConversationPath(userId, topicName));
|
|
@@ -7368,7 +7376,7 @@ function replaceConversationPathStrict(path, entries) {
|
|
|
7368
7376
|
renameSync5(tempPath, path);
|
|
7369
7377
|
} catch (error) {
|
|
7370
7378
|
try {
|
|
7371
|
-
|
|
7379
|
+
unlinkSync12(tempPath);
|
|
7372
7380
|
} catch {}
|
|
7373
7381
|
throw error;
|
|
7374
7382
|
}
|
|
@@ -7505,7 +7513,7 @@ RULES:
|
|
|
7505
7513
|
- Keep the entire summary under 1500 words.`, CHARS_PER_TEXT_TOKEN = 3.5, CHARS_PER_CJK_TOKEN = 0.9;
|
|
7506
7514
|
|
|
7507
7515
|
// ../../packages/core/src/storage/tasks.ts
|
|
7508
|
-
import { existsSync as
|
|
7516
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync11, readFileSync as readFileSync12, renameSync as renameSync6, statSync as statSync4, writeFileSync as writeFileSync9 } from "fs";
|
|
7509
7517
|
import { dirname as dirname10, join as join15 } from "path";
|
|
7510
7518
|
function safeTaskScopeKey(scopeKey) {
|
|
7511
7519
|
const safe = sanitizeFileName(scopeKey);
|
|
@@ -7522,10 +7530,10 @@ function getTaskFilePath(userId, scopeKey) {
|
|
|
7522
7530
|
}
|
|
7523
7531
|
function readTasks(userId, scopeKey) {
|
|
7524
7532
|
const path = getTaskFilePath(userId, scopeKey);
|
|
7525
|
-
if (!
|
|
7533
|
+
if (!existsSync14(path))
|
|
7526
7534
|
return [];
|
|
7527
7535
|
try {
|
|
7528
|
-
const parsed = JSON.parse(
|
|
7536
|
+
const parsed = JSON.parse(readFileSync12(path, "utf-8"));
|
|
7529
7537
|
return Array.isArray(parsed?.tasks) ? parsed.tasks : [];
|
|
7530
7538
|
} catch (e) {
|
|
7531
7539
|
logger.warn({ err: e, path }, "tasks: failed to read task store");
|
|
@@ -7780,7 +7788,7 @@ __export(exports_claude_provider, {
|
|
|
7780
7788
|
buildClaudeDisallowedTools: () => buildClaudeDisallowedTools
|
|
7781
7789
|
});
|
|
7782
7790
|
import { spawn as spawn7 } from "child_process";
|
|
7783
|
-
import { existsSync as
|
|
7791
|
+
import { existsSync as existsSync15, readFileSync as readFileSync13 } from "fs";
|
|
7784
7792
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
7785
7793
|
function claudeBuiltInTools(opts) {
|
|
7786
7794
|
return opts.toolPolicy ? [] : undefined;
|
|
@@ -7833,7 +7841,7 @@ function buildClaudePrompt(opts) {
|
|
|
7833
7841
|
source: {
|
|
7834
7842
|
type: "base64",
|
|
7835
7843
|
media_type: attachment.mimeType,
|
|
7836
|
-
data:
|
|
7844
|
+
data: readFileSync13(attachment.path).toString("base64")
|
|
7837
7845
|
}
|
|
7838
7846
|
});
|
|
7839
7847
|
} catch (err) {
|
|
@@ -7961,7 +7969,7 @@ function isAbortError(err) {
|
|
|
7961
7969
|
}
|
|
7962
7970
|
async function* claudeProvider(opts) {
|
|
7963
7971
|
const claudeExecutable = hostedClaudeCodeExecutablePath();
|
|
7964
|
-
if (claudeExecutable && !
|
|
7972
|
+
if (claudeExecutable && !existsSync15(claudeExecutable)) {
|
|
7965
7973
|
yield {
|
|
7966
7974
|
type: "error",
|
|
7967
7975
|
content: `Claude CLI not found at ${claudeExecutable}. Install Claude Code or configure the executable path.`
|
|
@@ -8481,7 +8489,7 @@ var init_codex_tree_kill = __esm(() => {
|
|
|
8481
8489
|
});
|
|
8482
8490
|
|
|
8483
8491
|
// ../../packages/core/src/agents/tool-format.ts
|
|
8484
|
-
import { readFileSync as
|
|
8492
|
+
import { readFileSync as readFileSync14, statSync as statSync5 } from "fs";
|
|
8485
8493
|
import { isAbsolute as isAbsolute2, resolve as resolve11 } from "path";
|
|
8486
8494
|
import { diffLines as computeLineDiff } from "diff";
|
|
8487
8495
|
function summarizeDisplayText(value2) {
|
|
@@ -8810,7 +8818,7 @@ function sourceStartLine(input, before, after, cwd) {
|
|
|
8810
8818
|
try {
|
|
8811
8819
|
if (statSync5(path).size > TOOL_DIFF_SOURCE_MAX_BYTES)
|
|
8812
8820
|
return 1;
|
|
8813
|
-
const source =
|
|
8821
|
+
const source = readFileSync14(path, "utf8").replace(/\r\n?/g, `
|
|
8814
8822
|
`);
|
|
8815
8823
|
for (const candidate of [before, after].filter(Boolean)) {
|
|
8816
8824
|
const index = source.indexOf(candidate);
|
|
@@ -9004,9 +9012,9 @@ __export(exports_codex_provider, {
|
|
|
9004
9012
|
codexProvider: () => codexProvider
|
|
9005
9013
|
});
|
|
9006
9014
|
import { execFileSync as execFileSync6 } from "child_process";
|
|
9007
|
-
import { existsSync as
|
|
9015
|
+
import { existsSync as existsSync16, readFileSync as readFileSync15, realpathSync as realpathSync4, statSync as statSync6 } from "fs";
|
|
9008
9016
|
import { homedir as homedir6 } from "os";
|
|
9009
|
-
import { dirname as dirname11, isAbsolute as isAbsolute3, join as join16, relative, resolve as resolve12 } from "path";
|
|
9017
|
+
import { dirname as dirname11, isAbsolute as isAbsolute3, join as join16, relative as relative2, resolve as resolve12 } from "path";
|
|
9010
9018
|
import { Codex } from "@openai/codex-sdk";
|
|
9011
9019
|
function sameCodexUsage(usage, total) {
|
|
9012
9020
|
return usage.input_tokens === total.inputTokens && usage.output_tokens === total.outputTokens && (usage.cached_input_tokens ?? 0) === total.cachedInputTokens && (usage.cache_write_input_tokens ?? 0) === total.cacheWriteInputTokens;
|
|
@@ -9035,11 +9043,11 @@ function codexMcpServerName(name) {
|
|
|
9035
9043
|
}
|
|
9036
9044
|
function globalCodexMcpServerNames(authFilePath) {
|
|
9037
9045
|
const configPath = join16(dirname11(authFilePath), "config.toml");
|
|
9038
|
-
if (!
|
|
9046
|
+
if (!existsSync16(configPath))
|
|
9039
9047
|
return [];
|
|
9040
9048
|
try {
|
|
9041
9049
|
const names = new Set;
|
|
9042
|
-
for (const line of
|
|
9050
|
+
for (const line of readFileSync15(configPath, "utf8").split(`
|
|
9043
9051
|
`)) {
|
|
9044
9052
|
const match = line.match(/^\s*\[\s*mcp_servers\.((?:"(?:\\.|[^"])*")|(?:'[^']*')|[^.\]\s]+)(?:[.\]])/);
|
|
9045
9053
|
const raw = match?.[1];
|
|
@@ -9136,13 +9144,13 @@ function canonicalPath(path) {
|
|
|
9136
9144
|
}
|
|
9137
9145
|
}
|
|
9138
9146
|
function textFile(path, maxBytes = CODEX_DIFF_FILE_LIMIT) {
|
|
9139
|
-
if (!
|
|
9147
|
+
if (!existsSync16(path))
|
|
9140
9148
|
return null;
|
|
9141
9149
|
try {
|
|
9142
9150
|
const byteLimit = Math.min(CODEX_DIFF_FILE_LIMIT, Math.max(0, maxBytes));
|
|
9143
9151
|
if (statSync6(path).size > byteLimit)
|
|
9144
9152
|
return;
|
|
9145
|
-
const content =
|
|
9153
|
+
const content = readFileSync15(path);
|
|
9146
9154
|
if (content.byteLength > byteLimit || content.includes(0))
|
|
9147
9155
|
return;
|
|
9148
9156
|
return content.toString("utf8");
|
|
@@ -9190,7 +9198,7 @@ class CodexFilePreviewTracker {
|
|
|
9190
9198
|
if (!this.#root || !this.#baselineAvailable || !succeeded)
|
|
9191
9199
|
return {};
|
|
9192
9200
|
const absolute = canonicalPath(isAbsolute3(path) ? path : resolve12(this.#cwd, path));
|
|
9193
|
-
const relativePath =
|
|
9201
|
+
const relativePath = relative2(this.#root, absolute);
|
|
9194
9202
|
if (!relativePath || relativePath.startsWith("..") || isAbsolute3(relativePath))
|
|
9195
9203
|
return {};
|
|
9196
9204
|
const before = this.#baseline.has(relativePath) ? this.#baseline.get(relativePath) : this.#headFile(relativePath);
|
|
@@ -9348,7 +9356,7 @@ async function* codexProvider(opts) {
|
|
|
9348
9356
|
const filePreviews = new CodexFilePreviewTracker(opts.cwd);
|
|
9349
9357
|
const consumedPatchCallIds = new Set(opts.sessionId ? readCodexPatchCallIds(opts.sessionId) : []);
|
|
9350
9358
|
const codexAuthPath = hostedCodexAuthFilePath();
|
|
9351
|
-
if (!
|
|
9359
|
+
if (!existsSync16(codexAuthPath)) {
|
|
9352
9360
|
yield {
|
|
9353
9361
|
type: "error",
|
|
9354
9362
|
content: `Codex auth file not found at ${codexAuthPath}. Run \`codex login\` to authenticate.`
|
|
@@ -9387,7 +9395,7 @@ async function* codexProvider(opts) {
|
|
|
9387
9395
|
model: opts.model ?? "(sdk default)",
|
|
9388
9396
|
effort: opts.effort ?? "(off)",
|
|
9389
9397
|
cwd: opts.cwd,
|
|
9390
|
-
cwdExists:
|
|
9398
|
+
cwdExists: existsSync16(opts.cwd),
|
|
9391
9399
|
resume: Boolean(opts.sessionId),
|
|
9392
9400
|
mcpServerCount: Object.keys(codexMcpServers).length
|
|
9393
9401
|
}, "codexProvider: starting turn");
|
|
@@ -9839,7 +9847,7 @@ var init_maestro_provider = __esm(async () => {
|
|
|
9839
9847
|
});
|
|
9840
9848
|
|
|
9841
9849
|
// ../../packages/core/src/agents/index.ts
|
|
9842
|
-
import { existsSync as
|
|
9850
|
+
import { existsSync as existsSync17 } from "fs";
|
|
9843
9851
|
import { homedir as homedir7 } from "os";
|
|
9844
9852
|
import { join as join17 } from "path";
|
|
9845
9853
|
async function* dispatchAgent(opts) {
|
|
@@ -9876,7 +9884,7 @@ async function resolveSessionFileMissing(agent, sessionId, cwd) {
|
|
|
9876
9884
|
case "claude": {
|
|
9877
9885
|
const encodedCwd = encodeClaudeCwd(cwd);
|
|
9878
9886
|
const path = join17(homedir7(), ".claude", "projects", encodedCwd, `${sessionId}.jsonl`);
|
|
9879
|
-
return !
|
|
9887
|
+
return !existsSync17(path);
|
|
9880
9888
|
}
|
|
9881
9889
|
case "codex": {
|
|
9882
9890
|
const sessionsDir = join17(hostedCodexHomePath(), "sessions");
|
|
@@ -9888,7 +9896,7 @@ async function resolveSessionFileMissing(agent, sessionId, cwd) {
|
|
|
9888
9896
|
}
|
|
9889
9897
|
case "maestro": {
|
|
9890
9898
|
const { hasActiveMaestroSession, maestroSessionPath: maestroSessionPath2 } = await import("maestro-agent-sdk");
|
|
9891
|
-
return !
|
|
9899
|
+
return !existsSync17(maestroSessionPath2(sessionId)) && !hasActiveMaestroSession(sessionId);
|
|
9892
9900
|
}
|
|
9893
9901
|
default: {
|
|
9894
9902
|
const _exhaustive = agent;
|
|
@@ -9961,10 +9969,10 @@ var init_agents = __esm(async () => {
|
|
|
9961
9969
|
});
|
|
9962
9970
|
|
|
9963
9971
|
// ../../packages/core/src/prompts/builders.ts
|
|
9964
|
-
import { readFileSync as
|
|
9972
|
+
import { readFileSync as readFileSync16 } from "fs";
|
|
9965
9973
|
import { resolve as resolve14 } from "path";
|
|
9966
9974
|
function loadPrompt(filename, dir = SESSIONS_DIR) {
|
|
9967
|
-
const raw =
|
|
9975
|
+
const raw = readFileSync16(resolve14(dir, filename), "utf-8");
|
|
9968
9976
|
return raw.replace(/\{\{RESOURCES_DIR\}\}/g, RESOURCES_DIR);
|
|
9969
9977
|
}
|
|
9970
9978
|
function replaceVars(template, vars) {
|
|
@@ -10013,7 +10021,7 @@ function visualDesignGuide() {
|
|
|
10013
10021
|
return _visualDesignGuide;
|
|
10014
10022
|
}
|
|
10015
10023
|
function loadAgentPrompt(filename) {
|
|
10016
|
-
const raw =
|
|
10024
|
+
const raw = readFileSync16(resolve14(AGENTS_PROMPTS_DIR, filename), "utf-8");
|
|
10017
10025
|
const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
10018
10026
|
if (!match)
|
|
10019
10027
|
throw new Error(`Agent prompt ${filename} is missing frontmatter`);
|
|
@@ -10282,14 +10290,14 @@ function buildMemoryPromptSection(opts) {
|
|
|
10282
10290
|
const parts = [`
|
|
10283
10291
|
|
|
10284
10292
|
## Memory`];
|
|
10285
|
-
|
|
10286
|
-
|
|
10287
|
-
if (opts.
|
|
10288
|
-
parts.push(`
|
|
10289
|
-
}
|
|
10290
|
-
|
|
10291
|
-
|
|
10292
|
-
parts.push("
|
|
10293
|
+
if (opts.isManager) {
|
|
10294
|
+
parts.push("Use `wiki_query` for past workspace decisions or cross-topic context, then `wiki_read` only for the relevant result.");
|
|
10295
|
+
} else if (opts.memoryKey) {
|
|
10296
|
+
parts.push(`This topic uses the canonical memory key \`${opts.memoryKey}\`. At the start of a new session, read it with \`wiki_read(kind: "topic", key: "${opts.memoryKey}")\`.`);
|
|
10297
|
+
} else {
|
|
10298
|
+
parts.push(`At the start of a new session, search topic memories with \`wiki_query(question: "${opts.topicTitle}", kind: "topic", limit: 5)\` and read the most relevant candidate with \`wiki_read\`.`, "If a candidate is clearly the same continuing topic/persona, read it with `adopt: true`; otherwise keep this topic's own name as its memory key. Do not merge weak or ambiguous matches.");
|
|
10299
|
+
}
|
|
10300
|
+
parts.push("Keep the lookup quiet. Mention prior context naturally in one short line only when it helps the user.", 'Use `wiki_query(kind: "article")` for reusable knowledge and `wiki_query(kind: "summary")` for historical session details.');
|
|
10293
10301
|
if (opts.hasArchive) {
|
|
10294
10302
|
parts.push("", "If you need the actual conversation from an earlier session, use the `wiki_last_conversation` MCP tool.");
|
|
10295
10303
|
}
|
|
@@ -10412,7 +10420,7 @@ var init_wiki = __esm(async () => {
|
|
|
10412
10420
|
|
|
10413
10421
|
// ../../packages/core/src/agents/archiver.ts
|
|
10414
10422
|
import { randomUUID as randomUUID9 } from "crypto";
|
|
10415
|
-
import { existsSync as
|
|
10423
|
+
import { existsSync as existsSync18, readdirSync as readdirSync5, readFileSync as readFileSync17, statSync as statSync7 } from "fs";
|
|
10416
10424
|
import { join as join19 } from "path";
|
|
10417
10425
|
function resolveMemoryLanguage() {
|
|
10418
10426
|
const override = process.env.NEGOTIUM_MEMORY_LANG?.trim();
|
|
@@ -10757,9 +10765,9 @@ var init_archiver = __esm(async () => {
|
|
|
10757
10765
|
defaultArchiverRuntime = createArchiverRuntime({
|
|
10758
10766
|
storage: {
|
|
10759
10767
|
getWikiDir: getSharedWikiDir,
|
|
10760
|
-
fileExists:
|
|
10768
|
+
fileExists: existsSync18,
|
|
10761
10769
|
listDirectory: readdirSync5,
|
|
10762
|
-
readTextFile: (path) =>
|
|
10770
|
+
readTextFile: (path) => readFileSync17(path, "utf-8"),
|
|
10763
10771
|
fileSize: (path) => statSync7(path).size,
|
|
10764
10772
|
fileModifiedAt: (path) => statSync7(path).mtimeMs,
|
|
10765
10773
|
getGeneralTopicId: (userId) => ensurePersonalGeneral(userId).id,
|
|
@@ -11245,11 +11253,12 @@ function archiveActiveTopicForMemory(topicId, userId, options) {
|
|
|
11245
11253
|
}, "topic-memory-archiver: raw snapshot preserved below exchange threshold");
|
|
11246
11254
|
return "below-threshold";
|
|
11247
11255
|
}
|
|
11248
|
-
const memoryTopic = getTopicMemoryOrigin(topicId) ?? topic;
|
|
11256
|
+
const memoryTopic = topic.memoryKey ? topic : getTopicMemoryOrigin(topicId) ?? topic;
|
|
11257
|
+
const memoryTitle = memoryTopic.memoryKey?.trim() || memoryTopic.title;
|
|
11249
11258
|
const launched = (options.launchArchiver ?? runArchiverTurn)({
|
|
11250
11259
|
userId,
|
|
11251
11260
|
topicId: memoryTopic.id,
|
|
11252
|
-
topicTitle:
|
|
11261
|
+
topicTitle: memoryTitle,
|
|
11253
11262
|
archivePath: job.archivePath,
|
|
11254
11263
|
...options.rawArchivePaths ? { rawArchivePaths: options.rawArchivePaths } : {},
|
|
11255
11264
|
messageCount: job.messageCount,
|
|
@@ -11295,7 +11304,7 @@ var init_idle_archiver = __esm(async () => {
|
|
|
11295
11304
|
});
|
|
11296
11305
|
|
|
11297
11306
|
// ../../packages/core/src/agents/topic-cleanup.ts
|
|
11298
|
-
import { mkdirSync as mkdirSync13, renameSync as renameSync7, unlinkSync as
|
|
11307
|
+
import { mkdirSync as mkdirSync13, renameSync as renameSync7, unlinkSync as unlinkSync13, writeFileSync as writeFileSync11 } from "fs";
|
|
11299
11308
|
import { dirname as dirname13 } from "path";
|
|
11300
11309
|
function collectSessionIdsByAgent(entries, extraSessions = []) {
|
|
11301
11310
|
const out = new Map;
|
|
@@ -11369,7 +11378,7 @@ function createTopicLogMaintenance(host) {
|
|
|
11369
11378
|
renameSync7(tempPath, path);
|
|
11370
11379
|
} catch (e) {
|
|
11371
11380
|
try {
|
|
11372
|
-
|
|
11381
|
+
unlinkSync13(tempPath);
|
|
11373
11382
|
} catch {}
|
|
11374
11383
|
runtimeHost.warn({ err: e, path }, "rotateTopicLogs: conversation replacement failed");
|
|
11375
11384
|
return {
|
|
@@ -11399,7 +11408,7 @@ function createTopicLogMaintenance(host) {
|
|
|
11399
11408
|
runtimeHost.rawConversationPath(userId, topicName)
|
|
11400
11409
|
]) {
|
|
11401
11410
|
try {
|
|
11402
|
-
|
|
11411
|
+
unlinkSync13(path);
|
|
11403
11412
|
} catch (e) {
|
|
11404
11413
|
if (e?.code !== "ENOENT") {
|
|
11405
11414
|
runtimeHost.warn({ err: e, path }, "purgeTopicLogs: conversation log unlink failed");
|
|
@@ -11951,7 +11960,7 @@ __export(exports_derive, {
|
|
|
11951
11960
|
TopicDeriveBusyError: () => TopicDeriveBusyError
|
|
11952
11961
|
});
|
|
11953
11962
|
import { createHash as createHash4, randomUUID as randomUUID11 } from "crypto";
|
|
11954
|
-
import { mkdirSync as mkdirSync14, rmSync as rmSync5, unlinkSync as
|
|
11963
|
+
import { mkdirSync as mkdirSync14, rmSync as rmSync5, unlinkSync as unlinkSync14 } from "fs";
|
|
11955
11964
|
function getTopics() {
|
|
11956
11965
|
return listTopics().filter((topic) => !isLegacySharedGeneral(topic.id));
|
|
11957
11966
|
}
|
|
@@ -12246,7 +12255,7 @@ async function createDerivedTopicImpl(topic, sourceTopicId, userId, copyHistory,
|
|
|
12246
12255
|
getConversationPath(userId, derived.title)
|
|
12247
12256
|
]) {
|
|
12248
12257
|
try {
|
|
12249
|
-
|
|
12258
|
+
unlinkSync14(path);
|
|
12250
12259
|
} catch {}
|
|
12251
12260
|
}
|
|
12252
12261
|
}
|
|
@@ -12318,7 +12327,7 @@ var init_session_inbox_path = __esm(() => {
|
|
|
12318
12327
|
});
|
|
12319
12328
|
|
|
12320
12329
|
// ../../packages/core/src/query/session-inbox-cleanup.ts
|
|
12321
|
-
import { unlinkSync as
|
|
12330
|
+
import { unlinkSync as unlinkSync15 } from "fs";
|
|
12322
12331
|
import { basename as basename4, join as join23 } from "path";
|
|
12323
12332
|
function cleanupSessionInboxFiles(userId, topicId, legacyTopicTitle) {
|
|
12324
12333
|
const live = sessionInboxPath(userId, topicId);
|
|
@@ -12333,7 +12342,7 @@ function cleanupSessionInboxFiles(userId, topicId, legacyTopicTitle) {
|
|
|
12333
12342
|
let deleted = 0;
|
|
12334
12343
|
for (const path of candidates) {
|
|
12335
12344
|
try {
|
|
12336
|
-
|
|
12345
|
+
unlinkSync15(path);
|
|
12337
12346
|
deleted++;
|
|
12338
12347
|
} catch (err) {
|
|
12339
12348
|
if (err?.code !== "ENOENT") {
|
|
@@ -12350,7 +12359,7 @@ var init_session_inbox_cleanup = __esm(() => {
|
|
|
12350
12359
|
});
|
|
12351
12360
|
|
|
12352
12361
|
// ../../packages/core/src/query/state.ts
|
|
12353
|
-
import { mkdirSync as mkdirSync15, renameSync as renameSync8, unlinkSync as
|
|
12362
|
+
import { mkdirSync as mkdirSync15, renameSync as renameSync8, unlinkSync as unlinkSync16, writeFileSync as writeFileSync13 } from "fs";
|
|
12354
12363
|
import { basename as basename5, join as join24 } from "path";
|
|
12355
12364
|
function createQueryStateStore(options) {
|
|
12356
12365
|
const sanitize = options.sanitizeTopicId ?? sanitizeId;
|
|
@@ -12381,7 +12390,7 @@ function createQueryStateStore(options) {
|
|
|
12381
12390
|
].filter((path) => Boolean(path));
|
|
12382
12391
|
for (const path of new Set(paths)) {
|
|
12383
12392
|
try {
|
|
12384
|
-
|
|
12393
|
+
unlinkSync16(path);
|
|
12385
12394
|
} catch (error) {
|
|
12386
12395
|
if (error?.code !== "ENOENT") {
|
|
12387
12396
|
options.logger?.warn({ err: error, userId, topicId, path }, "Failed to clear query state file");
|
|
@@ -12515,9 +12524,9 @@ import {
|
|
|
12515
12524
|
mkdirSync as mkdirSync16,
|
|
12516
12525
|
openSync as openSync3,
|
|
12517
12526
|
readdirSync as readdirSync6,
|
|
12518
|
-
readFileSync as
|
|
12527
|
+
readFileSync as readFileSync18,
|
|
12519
12528
|
statSync as statSync8,
|
|
12520
|
-
unlinkSync as
|
|
12529
|
+
unlinkSync as unlinkSync17,
|
|
12521
12530
|
writeFileSync as writeFileSync14
|
|
12522
12531
|
} from "fs";
|
|
12523
12532
|
import { dirname as dirname14, join as join25 } from "path";
|
|
@@ -12564,7 +12573,7 @@ function parsePendingAskFilename(fileName) {
|
|
|
12564
12573
|
}
|
|
12565
12574
|
function readPendingAskFile(path, fallback) {
|
|
12566
12575
|
try {
|
|
12567
|
-
const raw =
|
|
12576
|
+
const raw = readFileSync18(path, "utf8").trim();
|
|
12568
12577
|
if (!raw)
|
|
12569
12578
|
return null;
|
|
12570
12579
|
if (!raw.startsWith("{")) {
|
|
@@ -12653,7 +12662,7 @@ function readPendingAsk(key) {
|
|
|
12653
12662
|
const next = migrated ? legacy : readPendingAskFile(canonicalPath2, key);
|
|
12654
12663
|
if (next && next.from === key.from && next.to === key.to) {
|
|
12655
12664
|
try {
|
|
12656
|
-
|
|
12665
|
+
unlinkSync17(compatibilityPath);
|
|
12657
12666
|
} catch {}
|
|
12658
12667
|
return { path: canonicalPath2, record: next };
|
|
12659
12668
|
}
|
|
@@ -12668,7 +12677,7 @@ function createPendingAsk(args) {
|
|
|
12668
12677
|
}
|
|
12669
12678
|
if (migrated) {
|
|
12670
12679
|
try {
|
|
12671
|
-
|
|
12680
|
+
unlinkSync17(migrated.path);
|
|
12672
12681
|
} catch {
|
|
12673
12682
|
return { ok: false, existing: migrated.record, stale: true };
|
|
12674
12683
|
}
|
|
@@ -12700,7 +12709,7 @@ function createPendingAsk(args) {
|
|
|
12700
12709
|
if (!isStale(existing, path))
|
|
12701
12710
|
return { ok: false, existing, stale: false };
|
|
12702
12711
|
try {
|
|
12703
|
-
|
|
12712
|
+
unlinkSync17(path);
|
|
12704
12713
|
} catch {
|
|
12705
12714
|
return { ok: false, existing, stale: true };
|
|
12706
12715
|
}
|
|
@@ -12726,7 +12735,7 @@ function markPendingAskState(args) {
|
|
|
12726
12735
|
writePendingAsk(next);
|
|
12727
12736
|
if (pending.path !== pendingAskPath(next)) {
|
|
12728
12737
|
try {
|
|
12729
|
-
|
|
12738
|
+
unlinkSync17(pending.path);
|
|
12730
12739
|
} catch {}
|
|
12731
12740
|
}
|
|
12732
12741
|
return next;
|
|
@@ -12738,7 +12747,7 @@ function clearPendingAsk(args) {
|
|
|
12738
12747
|
if (args.requestId && record && record.requestId !== args.requestId)
|
|
12739
12748
|
return false;
|
|
12740
12749
|
try {
|
|
12741
|
-
|
|
12750
|
+
unlinkSync17(path);
|
|
12742
12751
|
return true;
|
|
12743
12752
|
} catch {
|
|
12744
12753
|
return false;
|
|
@@ -12793,7 +12802,7 @@ function listPendingAsksForCaller(args) {
|
|
|
12793
12802
|
continue;
|
|
12794
12803
|
if (isStale(record, path)) {
|
|
12795
12804
|
try {
|
|
12796
|
-
|
|
12805
|
+
unlinkSync17(path);
|
|
12797
12806
|
} catch {}
|
|
12798
12807
|
continue;
|
|
12799
12808
|
}
|
|
@@ -12802,12 +12811,12 @@ function listPendingAsksForCaller(args) {
|
|
|
12802
12811
|
continue;
|
|
12803
12812
|
if (path !== canonical.path) {
|
|
12804
12813
|
try {
|
|
12805
|
-
|
|
12814
|
+
unlinkSync17(path);
|
|
12806
12815
|
} catch {}
|
|
12807
12816
|
}
|
|
12808
12817
|
if (isStale(canonical.record, canonical.path)) {
|
|
12809
12818
|
try {
|
|
12810
|
-
|
|
12819
|
+
unlinkSync17(canonical.path);
|
|
12811
12820
|
} catch {}
|
|
12812
12821
|
continue;
|
|
12813
12822
|
}
|
|
@@ -12837,7 +12846,7 @@ function deletePendingAsksForTopic(args) {
|
|
|
12837
12846
|
if (from !== args.topicName && to !== args.topicName)
|
|
12838
12847
|
continue;
|
|
12839
12848
|
try {
|
|
12840
|
-
|
|
12849
|
+
unlinkSync17(path);
|
|
12841
12850
|
deleted++;
|
|
12842
12851
|
} catch {}
|
|
12843
12852
|
}
|
|
@@ -13357,11 +13366,12 @@ async function deleteTopicCascadeImpl(topic, userId, options, deletingAncestorId
|
|
|
13357
13366
|
}
|
|
13358
13367
|
if (archived && meetsMemoryArchiveExchangeThreshold(archived.exchangeCount, MIN_MEMORY_ARCHIVE_EXCHANGES, topic)) {
|
|
13359
13368
|
try {
|
|
13360
|
-
const memoryTopic = getTopicMemoryOrigin(topicId) ?? topic;
|
|
13369
|
+
const memoryTopic = topic.memoryKey ? topic : getTopicMemoryOrigin(topicId) ?? topic;
|
|
13370
|
+
const memoryTitle = memoryTopic.memoryKey?.trim() || memoryTopic.title;
|
|
13361
13371
|
runArchiverTurn({
|
|
13362
13372
|
userId,
|
|
13363
13373
|
...memoryTopic.id !== topicId && !deletingTopicIds.has(memoryTopic.id) ? { topicId: memoryTopic.id } : {},
|
|
13364
|
-
topicTitle:
|
|
13374
|
+
topicTitle: memoryTitle,
|
|
13365
13375
|
archivePath: archived.path,
|
|
13366
13376
|
...rawArchives.length > 0 ? { rawArchivePaths: rawArchives } : {},
|
|
13367
13377
|
messageCount: archived.messageCount
|
|
@@ -15496,7 +15506,7 @@ var init_turn_session = __esm(async () => {
|
|
|
15496
15506
|
});
|
|
15497
15507
|
|
|
15498
15508
|
// ../../packages/core/src/storage/app-settings.ts
|
|
15499
|
-
import { existsSync as
|
|
15509
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync19, readFileSync as readFileSync19, writeFileSync as writeFileSync16 } from "fs";
|
|
15500
15510
|
import { dirname as dirname15, join as join28 } from "path";
|
|
15501
15511
|
function settingsFile() {
|
|
15502
15512
|
return join28(resolveStorageDataDir(), "otium-settings.json");
|
|
@@ -15508,8 +15518,8 @@ function ensureSettingsLoaded() {
|
|
|
15508
15518
|
loadedSettingsFile = path;
|
|
15509
15519
|
aiName = DEFAULT_AI_NAME;
|
|
15510
15520
|
try {
|
|
15511
|
-
if (
|
|
15512
|
-
const data = JSON.parse(
|
|
15521
|
+
if (existsSync19(path)) {
|
|
15522
|
+
const data = JSON.parse(readFileSync19(path, "utf8"));
|
|
15513
15523
|
if (typeof data.aiName === "string" && data.aiName.trim()) {
|
|
15514
15524
|
aiName = data.aiName.trim();
|
|
15515
15525
|
}
|
|
@@ -15602,7 +15612,7 @@ __export(exports_turn_runner, {
|
|
|
15602
15612
|
AGENT_DISPLAY_NAME: () => AGENT_DISPLAY_NAME
|
|
15603
15613
|
});
|
|
15604
15614
|
import { randomUUID as randomUUID16 } from "crypto";
|
|
15605
|
-
import { existsSync as
|
|
15615
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync20, readdirSync as readdirSync7, statSync as statSync10 } from "fs";
|
|
15606
15616
|
import { join as join29 } from "path";
|
|
15607
15617
|
function withDefaultPlaywright(configuredMcp, isManager) {
|
|
15608
15618
|
if (isManager)
|
|
@@ -15659,7 +15669,7 @@ function appendAskReplyMessage(topicId, text2, agentType) {
|
|
|
15659
15669
|
}
|
|
15660
15670
|
function resolveWikiMirrorPath(directory, preferredFilename, matchesLegacyId, matchesTitle, preferExact = true) {
|
|
15661
15671
|
const preferred = join29(directory, preferredFilename);
|
|
15662
|
-
if (preferExact &&
|
|
15672
|
+
if (preferExact && existsSync20(preferred))
|
|
15663
15673
|
return preferred;
|
|
15664
15674
|
let newestLegacyId = null;
|
|
15665
15675
|
let newestTitle = null;
|
|
@@ -15684,9 +15694,9 @@ function resolveWikiMirrorPath(directory, preferredFilename, matchesLegacyId, ma
|
|
|
15684
15694
|
}
|
|
15685
15695
|
function resolveWikiMemoryMirror(wikiDir, topicId, topicTitle) {
|
|
15686
15696
|
const briefFile = resolveWikiMirrorPath(join29(wikiDir, "topic"), `${wikiBriefStorageKey(topicTitle, topicId)}.md`, (filename) => isTopicBriefFile(filename, topicId), (filename) => isTopicBriefFile(filename, topicId, topicTitle));
|
|
15687
|
-
const hasBriefFile =
|
|
15697
|
+
const hasBriefFile = existsSync20(briefFile) && statSync10(briefFile).isFile();
|
|
15688
15698
|
const latestSummaryCandidate = resolveWikiMirrorPath(join29(wikiDir, "summaries"), `__missing__-${wikiSummaryFilename("0000-00-00", topicTitle, topicId)}`, (filename) => isTopicSummaryFile(filename, topicId), (filename) => isTopicSummaryFile(filename, topicId, topicTitle), false);
|
|
15689
|
-
const latestSummaryFile =
|
|
15699
|
+
const latestSummaryFile = existsSync20(latestSummaryCandidate) && statSync10(latestSummaryCandidate).isFile() ? latestSummaryCandidate : null;
|
|
15690
15700
|
return { briefFile, hasBriefFile, latestSummaryFile };
|
|
15691
15701
|
}
|
|
15692
15702
|
async function streamAgentEvents(topicId, topicTitle, queryId, events, control, agentType, model, effort, userId, retryableSessionExpired = true, onSessionId, execution) {
|
|
@@ -16439,25 +16449,24 @@ function startAiTurn(params) {
|
|
|
16439
16449
|
const isManager = topicRecord?.kind === "manager";
|
|
16440
16450
|
const isMentionOnlyChannel = topic.aiMode === "mention" && !isManager;
|
|
16441
16451
|
let systemPrompt = isManager ? buildManagerSystemPrompt(systemPromptOpts) : isMentionOnlyChannel ? buildChannelSystemPrompt(systemPromptOpts) : buildTopicSystemPrompt(systemPromptOpts);
|
|
16442
|
-
const memoryTopic = isMentionOnlyChannel || isManager ? topic : getTopicMemoryOrigin(topicId) ?? topic;
|
|
16452
|
+
const memoryTopic = isMentionOnlyChannel || isManager || topic.memoryKey ? topic : getTopicMemoryOrigin(topicId) ?? topic;
|
|
16453
|
+
const memoryTitle = memoryTopic.memoryKey?.trim() || memoryTopic.title;
|
|
16443
16454
|
if (!isMentionOnlyChannel) {
|
|
16455
|
+
let hasMemoryArchive = false;
|
|
16444
16456
|
try {
|
|
16445
|
-
const resolvedBrief = resolveTopicBrief(memoryTopic.id,
|
|
16457
|
+
const resolvedBrief = resolveTopicBrief(memoryTopic.id, memoryTitle);
|
|
16446
16458
|
const wikiDir = getSharedWikiDir();
|
|
16447
|
-
const {
|
|
16448
|
-
|
|
16449
|
-
systemPrompt += buildMemoryPromptSection({
|
|
16450
|
-
briefFile,
|
|
16451
|
-
wikiDir,
|
|
16452
|
-
hasFiles: hasBriefFile,
|
|
16453
|
-
latestSummaryFile,
|
|
16454
|
-
hasArchive: Boolean(resolvedBrief?.brief.latestSummaryMd || latestSummaryFile),
|
|
16455
|
-
isManager
|
|
16456
|
-
});
|
|
16457
|
-
}
|
|
16459
|
+
const { latestSummaryFile } = resolveWikiMemoryMirror(wikiDir, memoryTopic.id, memoryTitle);
|
|
16460
|
+
hasMemoryArchive = Boolean(resolvedBrief?.brief.latestSummaryMd || latestSummaryFile);
|
|
16458
16461
|
} catch (err) {
|
|
16459
16462
|
logger.warn({ topicId, err }, "ai: failed to inject topic brief");
|
|
16460
16463
|
}
|
|
16464
|
+
systemPrompt += buildMemoryPromptSection({
|
|
16465
|
+
topicTitle: topic.title,
|
|
16466
|
+
...memoryTopic.memoryKey ? { memoryKey: memoryTopic.memoryKey } : {},
|
|
16467
|
+
hasArchive: hasMemoryArchive,
|
|
16468
|
+
isManager
|
|
16469
|
+
});
|
|
16461
16470
|
}
|
|
16462
16471
|
const activeVisual = visualTools ? getActiveVisualForPrompt(topicId, userId) : null;
|
|
16463
16472
|
if (activeVisual) {
|
|
@@ -16883,7 +16892,7 @@ __export(exports_auth_check, {
|
|
|
16883
16892
|
checkAgentAuth: () => checkAgentAuth
|
|
16884
16893
|
});
|
|
16885
16894
|
import { execFileSync as execFileSync7 } from "child_process";
|
|
16886
|
-
import { existsSync as
|
|
16895
|
+
import { existsSync as existsSync21 } from "fs";
|
|
16887
16896
|
import { homedir as homedir8, platform } from "os";
|
|
16888
16897
|
import { join as join30 } from "path";
|
|
16889
16898
|
function hasMaestroCredential(host, key, userId) {
|
|
@@ -16956,7 +16965,7 @@ var init_auth_check = __esm(async () => {
|
|
|
16956
16965
|
await init_vault();
|
|
16957
16966
|
defaultAgentAuthHost = {
|
|
16958
16967
|
codexAuthFilePath,
|
|
16959
|
-
exists:
|
|
16968
|
+
exists: existsSync21,
|
|
16960
16969
|
environment: process.env,
|
|
16961
16970
|
homeDirectory: homedir8,
|
|
16962
16971
|
operatingSystem: platform,
|
|
@@ -18187,25 +18196,238 @@ import { AsyncLocalStorage } from "async_hooks";
|
|
|
18187
18196
|
import { randomUUID } from "crypto";
|
|
18188
18197
|
import {
|
|
18189
18198
|
closeSync,
|
|
18190
|
-
existsSync as
|
|
18199
|
+
existsSync as existsSync3,
|
|
18191
18200
|
mkdirSync as mkdirSync2,
|
|
18192
18201
|
openSync,
|
|
18193
18202
|
readdirSync,
|
|
18194
|
-
readFileSync as
|
|
18203
|
+
readFileSync as readFileSync3,
|
|
18195
18204
|
renameSync,
|
|
18196
18205
|
statSync,
|
|
18197
|
-
unlinkSync,
|
|
18206
|
+
unlinkSync as unlinkSync2,
|
|
18198
18207
|
writeFileSync as writeFileSync2
|
|
18199
18208
|
} from "fs";
|
|
18200
|
-
import { basename, dirname as dirname2, join as join2, resolve as resolve2 } from "path";
|
|
18201
|
-
init_config();
|
|
18202
|
-
init_wiki_summary_names();
|
|
18209
|
+
import { basename, dirname as dirname2, join as join2, relative, resolve as resolve2 } from "path";
|
|
18203
18210
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
18204
18211
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
18205
18212
|
import {
|
|
18206
18213
|
CallToolRequestSchema,
|
|
18207
18214
|
ListToolsRequestSchema
|
|
18208
18215
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
18216
|
+
|
|
18217
|
+
// ../../packages/core/src/mcp/wiki-search-index.ts
|
|
18218
|
+
await init_sqlite();
|
|
18219
|
+
import { chmodSync, existsSync, readFileSync, unlinkSync } from "fs";
|
|
18220
|
+
var SEARCH_INDEX_SCHEMA_VERSION = 3;
|
|
18221
|
+
function documentId(source) {
|
|
18222
|
+
return `${source.kind}:${source.key}`;
|
|
18223
|
+
}
|
|
18224
|
+
function ensureSchema(database) {
|
|
18225
|
+
const version = database.query("PRAGMA user_version").get()?.user_version;
|
|
18226
|
+
if (version !== 0 && version !== SEARCH_INDEX_SCHEMA_VERSION) {
|
|
18227
|
+
throw new Error(`Unsupported wiki search index schema: ${version}`);
|
|
18228
|
+
}
|
|
18229
|
+
database.exec(`
|
|
18230
|
+
CREATE TABLE IF NOT EXISTS wiki_search_documents (
|
|
18231
|
+
id TEXT PRIMARY KEY,
|
|
18232
|
+
kind TEXT NOT NULL,
|
|
18233
|
+
key TEXT NOT NULL,
|
|
18234
|
+
path TEXT NOT NULL,
|
|
18235
|
+
mtime_ms REAL NOT NULL,
|
|
18236
|
+
size INTEGER NOT NULL,
|
|
18237
|
+
title TEXT NOT NULL,
|
|
18238
|
+
text TEXT NOT NULL,
|
|
18239
|
+
date TEXT,
|
|
18240
|
+
family TEXT
|
|
18241
|
+
);
|
|
18242
|
+
CREATE UNIQUE INDEX IF NOT EXISTS wiki_search_documents_kind_key
|
|
18243
|
+
ON wiki_search_documents(kind, key);
|
|
18244
|
+
CREATE INDEX IF NOT EXISTS wiki_search_documents_kind_date
|
|
18245
|
+
ON wiki_search_documents(kind, date);
|
|
18246
|
+
CREATE INDEX IF NOT EXISTS wiki_search_documents_kind_family_date
|
|
18247
|
+
ON wiki_search_documents(kind, family, date);
|
|
18248
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS wiki_search_fts USING fts5(
|
|
18249
|
+
document_id UNINDEXED,
|
|
18250
|
+
kind UNINDEXED,
|
|
18251
|
+
tokens,
|
|
18252
|
+
tokenize = 'unicode61'
|
|
18253
|
+
);
|
|
18254
|
+
PRAGMA user_version = ${SEARCH_INDEX_SCHEMA_VERSION};
|
|
18255
|
+
`);
|
|
18256
|
+
}
|
|
18257
|
+
|
|
18258
|
+
class WikiSearchIndex {
|
|
18259
|
+
path;
|
|
18260
|
+
#database;
|
|
18261
|
+
constructor(path) {
|
|
18262
|
+
this.path = path;
|
|
18263
|
+
this.#database = new Database(path, { create: true });
|
|
18264
|
+
try {
|
|
18265
|
+
this.#database.exec("PRAGMA foreign_keys = ON; PRAGMA journal_mode = DELETE;");
|
|
18266
|
+
ensureSchema(this.#database);
|
|
18267
|
+
chmodSync(path, 384);
|
|
18268
|
+
} catch (error) {
|
|
18269
|
+
this.#database.close();
|
|
18270
|
+
throw error;
|
|
18271
|
+
}
|
|
18272
|
+
}
|
|
18273
|
+
close() {
|
|
18274
|
+
this.#database.close();
|
|
18275
|
+
}
|
|
18276
|
+
upsert(source, document) {
|
|
18277
|
+
const id = documentId(source);
|
|
18278
|
+
const apply = this.#database.transaction(() => {
|
|
18279
|
+
this.#database.query("DELETE FROM wiki_search_fts WHERE document_id = ?").run(id);
|
|
18280
|
+
this.#database.query("DELETE FROM wiki_search_documents WHERE id = ?").run(id);
|
|
18281
|
+
this.#database.query(`
|
|
18282
|
+
INSERT INTO wiki_search_documents
|
|
18283
|
+
(id, kind, key, path, mtime_ms, size, title, text, date, family)
|
|
18284
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
18285
|
+
`).run(id, source.kind, source.key, source.path, source.mtimeMs, source.size, document.title, document.text, document.date ?? null, document.family ?? null);
|
|
18286
|
+
this.#database.query("INSERT INTO wiki_search_fts(document_id, kind, tokens) VALUES (?, ?, ?)").run(id, source.kind, document.tokens.join(" "));
|
|
18287
|
+
});
|
|
18288
|
+
apply.immediate();
|
|
18289
|
+
}
|
|
18290
|
+
sync(sources, prepare) {
|
|
18291
|
+
const result = { added: 0, updated: 0, removed: 0 };
|
|
18292
|
+
const existingRows = this.#database.query("SELECT id, path, mtime_ms, size FROM wiki_search_documents ORDER BY id").all();
|
|
18293
|
+
const existing = new Map(existingRows.map((row) => [row.id, row]));
|
|
18294
|
+
const present = new Set;
|
|
18295
|
+
const changed = [];
|
|
18296
|
+
for (const source of sources) {
|
|
18297
|
+
const id = documentId(source);
|
|
18298
|
+
present.add(id);
|
|
18299
|
+
const stored = existing.get(id);
|
|
18300
|
+
if (stored && stored.path === source.path && stored.mtime_ms === source.mtimeMs && stored.size === source.size) {
|
|
18301
|
+
continue;
|
|
18302
|
+
}
|
|
18303
|
+
try {
|
|
18304
|
+
const text = readFileSync(source.path, "utf-8");
|
|
18305
|
+
changed.push({ source, document: prepare(source, text), added: !stored });
|
|
18306
|
+
} catch {}
|
|
18307
|
+
}
|
|
18308
|
+
const removed = existingRows.filter((row) => !present.has(row.id));
|
|
18309
|
+
const apply = this.#database.transaction(() => {
|
|
18310
|
+
const deleteTerms = this.#database.query("DELETE FROM wiki_search_fts WHERE document_id = ?");
|
|
18311
|
+
const deleteDocument = this.#database.query("DELETE FROM wiki_search_documents WHERE id = ?");
|
|
18312
|
+
const insertDocument = this.#database.query(`
|
|
18313
|
+
INSERT INTO wiki_search_documents
|
|
18314
|
+
(id, kind, key, path, mtime_ms, size, title, text, date, family)
|
|
18315
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
18316
|
+
`);
|
|
18317
|
+
const insertTerms = this.#database.query(`
|
|
18318
|
+
INSERT INTO wiki_search_fts(document_id, kind, tokens)
|
|
18319
|
+
VALUES (?, ?, ?)
|
|
18320
|
+
`);
|
|
18321
|
+
for (const row of removed) {
|
|
18322
|
+
deleteTerms.run(row.id);
|
|
18323
|
+
deleteDocument.run(row.id);
|
|
18324
|
+
result.removed += 1;
|
|
18325
|
+
}
|
|
18326
|
+
for (const { source, document, added } of changed) {
|
|
18327
|
+
const id = documentId(source);
|
|
18328
|
+
deleteTerms.run(id);
|
|
18329
|
+
deleteDocument.run(id);
|
|
18330
|
+
insertDocument.run(id, source.kind, source.key, source.path, source.mtimeMs, source.size, document.title, document.text, document.date ?? null, document.family ?? null);
|
|
18331
|
+
insertTerms.run(id, source.kind, document.tokens.join(" "));
|
|
18332
|
+
if (added)
|
|
18333
|
+
result.added += 1;
|
|
18334
|
+
else
|
|
18335
|
+
result.updated += 1;
|
|
18336
|
+
}
|
|
18337
|
+
});
|
|
18338
|
+
apply.immediate();
|
|
18339
|
+
return result;
|
|
18340
|
+
}
|
|
18341
|
+
metadata(kinds) {
|
|
18342
|
+
if (kinds.length === 0)
|
|
18343
|
+
return [];
|
|
18344
|
+
const placeholders = kinds.map(() => "?").join(", ");
|
|
18345
|
+
const rows = this.#database.query(`SELECT id, kind, key, title, date
|
|
18346
|
+
FROM wiki_search_documents
|
|
18347
|
+
WHERE kind IN (${placeholders})`).all(...kinds);
|
|
18348
|
+
return rows.map((row) => ({
|
|
18349
|
+
id: row.id,
|
|
18350
|
+
kind: row.kind,
|
|
18351
|
+
key: row.key,
|
|
18352
|
+
title: row.title,
|
|
18353
|
+
...row.date ? { date: row.date } : {}
|
|
18354
|
+
}));
|
|
18355
|
+
}
|
|
18356
|
+
summaryMetadataByDate(query) {
|
|
18357
|
+
const conditions = ["kind = 'summary'", "date IS NOT NULL"];
|
|
18358
|
+
const parameters = [];
|
|
18359
|
+
if (query.year && query.month) {
|
|
18360
|
+
const month = Number(query.month);
|
|
18361
|
+
const nextYear = month === 12 ? String(Number(query.year) + 1) : query.year;
|
|
18362
|
+
const nextMonth = String(month === 12 ? 1 : month + 1).padStart(2, "0");
|
|
18363
|
+
conditions.push("date >= ?", "date < ?");
|
|
18364
|
+
parameters.push(`${query.year}-${query.month}-01`, `${nextYear}-${nextMonth}-01`);
|
|
18365
|
+
} else if (query.year) {
|
|
18366
|
+
conditions.push("date >= ?", "date < ?");
|
|
18367
|
+
parameters.push(`${query.year}-01-01`, `${Number(query.year) + 1}-01-01`);
|
|
18368
|
+
} else if (query.month) {
|
|
18369
|
+
conditions.push("substr(date, 6, 2) = ?");
|
|
18370
|
+
parameters.push(query.month);
|
|
18371
|
+
}
|
|
18372
|
+
parameters.push(query.limit);
|
|
18373
|
+
const rows = this.#database.query(`SELECT id, kind, key, title, date
|
|
18374
|
+
FROM wiki_search_documents
|
|
18375
|
+
WHERE ${conditions.join(" AND ")}
|
|
18376
|
+
ORDER BY date ${query.direction === "oldest" ? "ASC" : "DESC"}, key ASC
|
|
18377
|
+
LIMIT ?`).all(...parameters);
|
|
18378
|
+
return rows.map((row) => ({
|
|
18379
|
+
id: row.id,
|
|
18380
|
+
kind: row.kind,
|
|
18381
|
+
key: row.key,
|
|
18382
|
+
title: row.title,
|
|
18383
|
+
...row.date ? { date: row.date } : {}
|
|
18384
|
+
}));
|
|
18385
|
+
}
|
|
18386
|
+
matchingDocumentIds(kinds, terms) {
|
|
18387
|
+
if (kinds.length === 0 || terms.length === 0)
|
|
18388
|
+
return new Set;
|
|
18389
|
+
const kindPlaceholders = kinds.map(() => "?").join(", ");
|
|
18390
|
+
const match = terms.map((term) => `"${term.replaceAll('"', '""')}"`).join(" OR ");
|
|
18391
|
+
const rows = this.#database.query(`SELECT DISTINCT document_id
|
|
18392
|
+
FROM wiki_search_fts
|
|
18393
|
+
WHERE wiki_search_fts MATCH ? AND kind IN (${kindPlaceholders})`).all(match, ...kinds);
|
|
18394
|
+
return new Set(rows.map((row) => row.document_id));
|
|
18395
|
+
}
|
|
18396
|
+
documents(ids) {
|
|
18397
|
+
if (ids.size === 0)
|
|
18398
|
+
return [];
|
|
18399
|
+
const values = [...ids];
|
|
18400
|
+
const rows = [];
|
|
18401
|
+
for (let offset = 0;offset < values.length; offset += 500) {
|
|
18402
|
+
const chunk = values.slice(offset, offset + 500);
|
|
18403
|
+
const placeholders = chunk.map(() => "?").join(", ");
|
|
18404
|
+
rows.push(...this.#database.query(`SELECT id, kind, key, path, title, text, date
|
|
18405
|
+
FROM wiki_search_documents
|
|
18406
|
+
WHERE id IN (${placeholders})`).all(...chunk));
|
|
18407
|
+
}
|
|
18408
|
+
return rows.map((row) => ({
|
|
18409
|
+
id: row.id,
|
|
18410
|
+
kind: row.kind,
|
|
18411
|
+
key: row.key,
|
|
18412
|
+
path: row.path,
|
|
18413
|
+
title: row.title,
|
|
18414
|
+
text: row.text,
|
|
18415
|
+
tokens: [],
|
|
18416
|
+
...row.date ? { date: row.date } : {}
|
|
18417
|
+
}));
|
|
18418
|
+
}
|
|
18419
|
+
}
|
|
18420
|
+
function resetCorruptWikiSearchIndex(path) {
|
|
18421
|
+
for (const candidate of [path, `${path}-journal`, `${path}-wal`, `${path}-shm`]) {
|
|
18422
|
+
if (!existsSync(candidate))
|
|
18423
|
+
continue;
|
|
18424
|
+
unlinkSync(candidate);
|
|
18425
|
+
}
|
|
18426
|
+
}
|
|
18427
|
+
|
|
18428
|
+
// ../../packages/core/src/mcp/wiki-server.ts
|
|
18429
|
+
init_config();
|
|
18430
|
+
init_wiki_summary_names();
|
|
18209
18431
|
var wikiRuntime = new AsyncLocalStorage;
|
|
18210
18432
|
function runtime() {
|
|
18211
18433
|
const current = wikiRuntime.getStore();
|
|
@@ -18232,15 +18454,15 @@ function acquireRecoveryGuard(lockPath) {
|
|
|
18232
18454
|
} catch (error) {
|
|
18233
18455
|
closeSync(fd);
|
|
18234
18456
|
try {
|
|
18235
|
-
|
|
18457
|
+
unlinkSync2(guardPath);
|
|
18236
18458
|
} catch {}
|
|
18237
18459
|
throw error;
|
|
18238
18460
|
}
|
|
18239
18461
|
return () => {
|
|
18240
18462
|
closeSync(fd);
|
|
18241
18463
|
try {
|
|
18242
|
-
if (
|
|
18243
|
-
|
|
18464
|
+
if (readFileSync3(guardPath, "utf-8") === ownerToken)
|
|
18465
|
+
unlinkSync2(guardPath);
|
|
18244
18466
|
} catch {}
|
|
18245
18467
|
};
|
|
18246
18468
|
} catch (error) {
|
|
@@ -18265,7 +18487,7 @@ function acquireFileLock(path) {
|
|
|
18265
18487
|
} catch (error) {
|
|
18266
18488
|
closeSync(fd);
|
|
18267
18489
|
try {
|
|
18268
|
-
|
|
18490
|
+
unlinkSync2(lockPath);
|
|
18269
18491
|
} catch {}
|
|
18270
18492
|
throw error;
|
|
18271
18493
|
}
|
|
@@ -18273,8 +18495,8 @@ function acquireFileLock(path) {
|
|
|
18273
18495
|
closeSync(fd);
|
|
18274
18496
|
const releaseGuard = acquireRecoveryGuard(lockPath);
|
|
18275
18497
|
try {
|
|
18276
|
-
if (
|
|
18277
|
-
|
|
18498
|
+
if (readFileSync3(lockPath, "utf-8") === ownerToken)
|
|
18499
|
+
unlinkSync2(lockPath);
|
|
18278
18500
|
} catch {} finally {
|
|
18279
18501
|
releaseGuard();
|
|
18280
18502
|
}
|
|
@@ -18285,9 +18507,9 @@ function acquireFileLock(path) {
|
|
|
18285
18507
|
const releaseGuard = acquireRecoveryGuard(lockPath);
|
|
18286
18508
|
try {
|
|
18287
18509
|
try {
|
|
18288
|
-
const staleToken =
|
|
18289
|
-
if (Date.now() - statSync(lockPath).mtimeMs > FILE_LOCK_STALE_MS &&
|
|
18290
|
-
|
|
18510
|
+
const staleToken = readFileSync3(lockPath, "utf-8");
|
|
18511
|
+
if (Date.now() - statSync(lockPath).mtimeMs > FILE_LOCK_STALE_MS && readFileSync3(lockPath, "utf-8") === staleToken) {
|
|
18512
|
+
unlinkSync2(lockPath);
|
|
18291
18513
|
continue;
|
|
18292
18514
|
}
|
|
18293
18515
|
} catch {
|
|
@@ -18310,7 +18532,7 @@ function atomicWriteFile(path, content) {
|
|
|
18310
18532
|
renameSync(temporaryPath, path);
|
|
18311
18533
|
} catch (error) {
|
|
18312
18534
|
try {
|
|
18313
|
-
|
|
18535
|
+
unlinkSync2(temporaryPath);
|
|
18314
18536
|
} catch {}
|
|
18315
18537
|
throw error;
|
|
18316
18538
|
}
|
|
@@ -18331,9 +18553,395 @@ function topicNameFrom(topicIdStr) {
|
|
|
18331
18553
|
function slugify(topic) {
|
|
18332
18554
|
return wikiSummarySlug(topic);
|
|
18333
18555
|
}
|
|
18556
|
+
function normalizeSearchText(value2) {
|
|
18557
|
+
return value2.toLowerCase().replaceAll(/[^a-z0-9\uAC00-\uD7A3]+/g, " ").trim().replaceAll(/\s+/g, " ");
|
|
18558
|
+
}
|
|
18559
|
+
function compactSearchText(value2) {
|
|
18560
|
+
return normalizeSearchText(value2).replaceAll(" ", "");
|
|
18561
|
+
}
|
|
18562
|
+
function trigrams(value2) {
|
|
18563
|
+
const compact = compactSearchText(value2);
|
|
18564
|
+
if (compact.length < 3)
|
|
18565
|
+
return new Set(compact ? [compact] : []);
|
|
18566
|
+
const result = new Set;
|
|
18567
|
+
for (let i = 0;i <= compact.length - 3; i += 1)
|
|
18568
|
+
result.add(compact.slice(i, i + 3));
|
|
18569
|
+
return result;
|
|
18570
|
+
}
|
|
18571
|
+
function fuzzySearchScore(query, candidate, maximum) {
|
|
18572
|
+
const compactQuery = compactSearchText(query);
|
|
18573
|
+
const compactCandidate = compactSearchText(candidate);
|
|
18574
|
+
if (compactQuery.length < 3 || compactCandidate.length < 3)
|
|
18575
|
+
return 0;
|
|
18576
|
+
const similarity = trigramSimilarity(compactQuery, compactCandidate);
|
|
18577
|
+
const minimumSimilarity = compactQuery.length <= 4 ? 0.6 : 0.45;
|
|
18578
|
+
return similarity >= minimumSimilarity ? similarity * maximum : 0;
|
|
18579
|
+
}
|
|
18580
|
+
var SEARCH_STOP_WORDS = new Set([
|
|
18581
|
+
"a",
|
|
18582
|
+
"an",
|
|
18583
|
+
"and",
|
|
18584
|
+
"are",
|
|
18585
|
+
"as",
|
|
18586
|
+
"at",
|
|
18587
|
+
"be",
|
|
18588
|
+
"by",
|
|
18589
|
+
"for",
|
|
18590
|
+
"from",
|
|
18591
|
+
"in",
|
|
18592
|
+
"is",
|
|
18593
|
+
"it",
|
|
18594
|
+
"of",
|
|
18595
|
+
"on",
|
|
18596
|
+
"or",
|
|
18597
|
+
"that",
|
|
18598
|
+
"the",
|
|
18599
|
+
"to",
|
|
18600
|
+
"with"
|
|
18601
|
+
]);
|
|
18602
|
+
var SEARCH_MONTHS = new Map([
|
|
18603
|
+
["january", "01"],
|
|
18604
|
+
["jan", "01"],
|
|
18605
|
+
["1\uC6D4", "01"],
|
|
18606
|
+
["february", "02"],
|
|
18607
|
+
["feb", "02"],
|
|
18608
|
+
["2\uC6D4", "02"],
|
|
18609
|
+
["march", "03"],
|
|
18610
|
+
["mar", "03"],
|
|
18611
|
+
["3\uC6D4", "03"],
|
|
18612
|
+
["april", "04"],
|
|
18613
|
+
["apr", "04"],
|
|
18614
|
+
["4\uC6D4", "04"],
|
|
18615
|
+
["may", "05"],
|
|
18616
|
+
["5\uC6D4", "05"],
|
|
18617
|
+
["june", "06"],
|
|
18618
|
+
["jun", "06"],
|
|
18619
|
+
["6\uC6D4", "06"],
|
|
18620
|
+
["july", "07"],
|
|
18621
|
+
["jul", "07"],
|
|
18622
|
+
["7\uC6D4", "07"],
|
|
18623
|
+
["august", "08"],
|
|
18624
|
+
["aug", "08"],
|
|
18625
|
+
["8\uC6D4", "08"],
|
|
18626
|
+
["september", "09"],
|
|
18627
|
+
["sep", "09"],
|
|
18628
|
+
["sept", "09"],
|
|
18629
|
+
["9\uC6D4", "09"],
|
|
18630
|
+
["october", "10"],
|
|
18631
|
+
["oct", "10"],
|
|
18632
|
+
["10\uC6D4", "10"],
|
|
18633
|
+
["november", "11"],
|
|
18634
|
+
["nov", "11"],
|
|
18635
|
+
["11\uC6D4", "11"],
|
|
18636
|
+
["december", "12"],
|
|
18637
|
+
["dec", "12"],
|
|
18638
|
+
["12\uC6D4", "12"]
|
|
18639
|
+
]);
|
|
18640
|
+
var SEARCH_RECENCY_TERMS = new Set([
|
|
18641
|
+
"latest",
|
|
18642
|
+
"newest",
|
|
18643
|
+
"recent",
|
|
18644
|
+
"earliest",
|
|
18645
|
+
"oldest",
|
|
18646
|
+
"\uCD5C\uADFC",
|
|
18647
|
+
"\uCD5C\uC2E0",
|
|
18648
|
+
"\uCD5C\uCD08",
|
|
18649
|
+
"\uC624\uB798\uB41C"
|
|
18650
|
+
]);
|
|
18651
|
+
function meaningfulSearchTokens(value2) {
|
|
18652
|
+
return normalizeSearchText(value2).split(" ").filter((token) => (token.length >= 2 || /^\d+$/.test(token)) && !SEARCH_STOP_WORDS.has(token));
|
|
18653
|
+
}
|
|
18654
|
+
function canonicalSearchToken(token) {
|
|
18655
|
+
return token.length > 4 && token.endsWith("s") ? token.slice(0, -1) : token;
|
|
18656
|
+
}
|
|
18657
|
+
function canonicalSearchTokens(value2) {
|
|
18658
|
+
return meaningfulSearchTokens(value2).map(canonicalSearchToken);
|
|
18659
|
+
}
|
|
18660
|
+
function countToken(tokens, token) {
|
|
18661
|
+
let count = 0;
|
|
18662
|
+
for (const candidate of tokens)
|
|
18663
|
+
if (candidate === token)
|
|
18664
|
+
count += 1;
|
|
18665
|
+
return count;
|
|
18666
|
+
}
|
|
18667
|
+
function countPhrase(text, phrase) {
|
|
18668
|
+
if (!phrase)
|
|
18669
|
+
return 0;
|
|
18670
|
+
let count = 0;
|
|
18671
|
+
let offset = 0;
|
|
18672
|
+
let match = text.indexOf(phrase, offset);
|
|
18673
|
+
while (match >= 0) {
|
|
18674
|
+
count += 1;
|
|
18675
|
+
offset = match + phrase.length;
|
|
18676
|
+
match = text.indexOf(phrase, offset);
|
|
18677
|
+
}
|
|
18678
|
+
return count;
|
|
18679
|
+
}
|
|
18680
|
+
function summaryTopicIdentity(key) {
|
|
18681
|
+
return normalizeSearchText(key.replace(/^\d{4}-\d{2}-\d{2}-/, ""));
|
|
18682
|
+
}
|
|
18683
|
+
function scoreSummaryTime(query, key, title) {
|
|
18684
|
+
const date = key.match(/^(\d{4})-(\d{2})-(\d{2})/);
|
|
18685
|
+
if (!date)
|
|
18686
|
+
return 0;
|
|
18687
|
+
const tokens = meaningfulSearchTokens(query);
|
|
18688
|
+
const documentDate = `${date[1]}-${date[2]}-${date[3]}`;
|
|
18689
|
+
const requestedDateMatch = query.match(/\b(\d{4}) (\d{2}) (\d{2})\b/);
|
|
18690
|
+
const requestedDate = requestedDateMatch ? `${requestedDateMatch[1]}-${requestedDateMatch[2]}-${requestedDateMatch[3]}` : undefined;
|
|
18691
|
+
const beforeDate = /\bbefore\b/.test(query);
|
|
18692
|
+
const afterDate = /\bafter\b/.test(query);
|
|
18693
|
+
if (requestedDate && beforeDate && documentDate >= requestedDate)
|
|
18694
|
+
return null;
|
|
18695
|
+
if (requestedDate && afterDate && documentDate <= requestedDate)
|
|
18696
|
+
return null;
|
|
18697
|
+
const queryYear = tokens.find((token) => /^\d{4}(?:\uB144)?$/.test(token))?.replace(/\uB144$/, "");
|
|
18698
|
+
const queryMonth = tokens.map((token) => SEARCH_MONTHS.get(token)).find(Boolean);
|
|
18699
|
+
const hasRecencyTerm = tokens.some((token) => SEARCH_RECENCY_TERMS.has(token));
|
|
18700
|
+
const yearMatches = !queryYear || queryYear === date[1];
|
|
18701
|
+
const monthMatches = !queryMonth || queryMonth === date[2];
|
|
18702
|
+
if (!queryYear && !queryMonth && !hasRecencyTerm || !yearMatches || !monthMatches)
|
|
18703
|
+
return 0;
|
|
18704
|
+
const normalizedMetadata = `${normalizeSearchText(key)} ${normalizeSearchText(title)}`;
|
|
18705
|
+
const contentTokens = tokens.filter((token) => !/^\d{4}(?:\uB144)?$/.test(token) && !SEARCH_MONTHS.has(token) && !SEARCH_RECENCY_TERMS.has(token));
|
|
18706
|
+
const contentMatches = contentTokens.filter((token) => normalizedMetadata.includes(token)).length;
|
|
18707
|
+
const relationScore = requestedDate && (beforeDate || afterDate) ? 60 : 0;
|
|
18708
|
+
const exactDateScore = requestedDate === documentDate ? 90 : 0;
|
|
18709
|
+
return 30 + relationScore + exactDateScore + contentMatches * 15;
|
|
18710
|
+
}
|
|
18711
|
+
function trigramSimilarity(left, right) {
|
|
18712
|
+
const a = trigrams(left);
|
|
18713
|
+
const b = trigrams(right);
|
|
18714
|
+
if (a.size === 0 || b.size === 0)
|
|
18715
|
+
return 0;
|
|
18716
|
+
let overlap = 0;
|
|
18717
|
+
for (const gram of a)
|
|
18718
|
+
if (b.has(gram))
|
|
18719
|
+
overlap += 1;
|
|
18720
|
+
return 2 * overlap / (a.size + b.size);
|
|
18721
|
+
}
|
|
18722
|
+
var WIKI_SEARCH_INDEX_FILENAME = ".wiki-search-index.sqlite";
|
|
18723
|
+
function openWikiSearchIndex() {
|
|
18724
|
+
const path = resolve2(runtime().wikiDir, WIKI_SEARCH_INDEX_FILENAME);
|
|
18725
|
+
try {
|
|
18726
|
+
return new WikiSearchIndex(path);
|
|
18727
|
+
} catch (error) {
|
|
18728
|
+
if (!/malformed|not a database|schema/i.test(String(error)))
|
|
18729
|
+
return;
|
|
18730
|
+
try {
|
|
18731
|
+
resetCorruptWikiSearchIndex(path);
|
|
18732
|
+
return new WikiSearchIndex(path);
|
|
18733
|
+
} catch {
|
|
18734
|
+
return;
|
|
18735
|
+
}
|
|
18736
|
+
}
|
|
18737
|
+
}
|
|
18738
|
+
function prepareWikiSearchDocument(kind, key, text) {
|
|
18739
|
+
const titleLine = text.split(`
|
|
18740
|
+
`).find((line) => /^#+\s*/.test(line)) ?? "";
|
|
18741
|
+
const title = titleLine ? titleLine.replace(/^#+\s*/, "") : basename(key);
|
|
18742
|
+
return {
|
|
18743
|
+
title,
|
|
18744
|
+
text,
|
|
18745
|
+
tokens: canonicalSearchTokens(`${key} ${title} ${text}`),
|
|
18746
|
+
...kind === "summary" && /^\d{4}-\d{2}-\d{2}/.test(key) ? { date: key.slice(0, 10), family: summaryTopicIdentity(key) } : {}
|
|
18747
|
+
};
|
|
18748
|
+
}
|
|
18749
|
+
function indexWikiSearchDocument(kind, key, text) {
|
|
18750
|
+
const root = kind === "article" ? runtime().articlesDir : runtime().summariesDir;
|
|
18751
|
+
const path = resolve2(root, `${key}.md`);
|
|
18752
|
+
const index = openWikiSearchIndex();
|
|
18753
|
+
if (!index)
|
|
18754
|
+
return false;
|
|
18755
|
+
try {
|
|
18756
|
+
const stat = statSync(path);
|
|
18757
|
+
index.upsert({ kind, key, path, mtimeMs: stat.mtimeMs, size: stat.size }, prepareWikiSearchDocument(kind, key, text));
|
|
18758
|
+
return true;
|
|
18759
|
+
} catch {
|
|
18760
|
+
return false;
|
|
18761
|
+
} finally {
|
|
18762
|
+
index.close();
|
|
18763
|
+
}
|
|
18764
|
+
}
|
|
18765
|
+
var WIKI_INDEX_FILENAMES = {
|
|
18766
|
+
article: "article-index.md",
|
|
18767
|
+
summary: "summary-index.md",
|
|
18768
|
+
topic: "topic-index.md",
|
|
18769
|
+
skill: "skill-index.md"
|
|
18770
|
+
};
|
|
18771
|
+
var WIKI_INDEX_NAMESPACES = {
|
|
18772
|
+
article: "articles",
|
|
18773
|
+
summary: "summaries",
|
|
18774
|
+
topic: "topic",
|
|
18775
|
+
skill: "skills"
|
|
18776
|
+
};
|
|
18777
|
+
function wikiIndexPath(kind) {
|
|
18778
|
+
return resolve2(runtime().wikiDir, WIKI_INDEX_FILENAMES[kind]);
|
|
18779
|
+
}
|
|
18780
|
+
var MAX_INDEX_DESCRIPTION_LENGTH = 400;
|
|
18781
|
+
function normalizeIndexDescription(raw) {
|
|
18782
|
+
const description = raw.replaceAll(/\s+/g, " ").trim();
|
|
18783
|
+
if (!description)
|
|
18784
|
+
return { error: "A non-empty one-line description is required." };
|
|
18785
|
+
if (description.length > MAX_INDEX_DESCRIPTION_LENGTH) {
|
|
18786
|
+
return {
|
|
18787
|
+
error: `Description must be at most ${MAX_INDEX_DESCRIPTION_LENGTH} characters (got ${description.length}).`
|
|
18788
|
+
};
|
|
18789
|
+
}
|
|
18790
|
+
return { description };
|
|
18791
|
+
}
|
|
18792
|
+
function normalizeWikiSlug(raw) {
|
|
18793
|
+
const slug = raw.trim().replace(/\.md$/i, "").replace(/^\/+/, "");
|
|
18794
|
+
if (!slug)
|
|
18795
|
+
return { error: "A non-empty slug is required." };
|
|
18796
|
+
if (/[[\]|\\]/.test(slug))
|
|
18797
|
+
return { error: "Slug must not contain [ ] | or backslashes." };
|
|
18798
|
+
if (slug.split("/").some((segment) => segment === "" || segment === "." || segment === "..")) {
|
|
18799
|
+
return { error: "Slug must not contain empty or relative path segments." };
|
|
18800
|
+
}
|
|
18801
|
+
return { slug };
|
|
18802
|
+
}
|
|
18803
|
+
function parseWikiIndex(path) {
|
|
18804
|
+
let text;
|
|
18805
|
+
try {
|
|
18806
|
+
text = readFileSync3(path, "utf-8");
|
|
18807
|
+
} catch {
|
|
18808
|
+
return [];
|
|
18809
|
+
}
|
|
18810
|
+
const candidates = [];
|
|
18811
|
+
const entryPattern = /^\s*-\s*\[\[(topic|articles|summaries)\/([^\]]+)\]\]\s*(.*)$/;
|
|
18812
|
+
for (const line of text.split(`
|
|
18813
|
+
`)) {
|
|
18814
|
+
const match = line.match(entryPattern);
|
|
18815
|
+
if (!match)
|
|
18816
|
+
continue;
|
|
18817
|
+
const namespace = match[1];
|
|
18818
|
+
const key = match[2].replace(/\.md$/i, "");
|
|
18819
|
+
const tail = match[3].trim();
|
|
18820
|
+
const dateMatch = tail.match(/\((\d{4}-\d{2}-\d{2})(?:[^)]*)\)\s*$/);
|
|
18821
|
+
const description = dateMatch ? tail.slice(0, dateMatch.index).trim() : tail;
|
|
18822
|
+
candidates.push({
|
|
18823
|
+
kind: namespace === "topic" ? "topic" : namespace === "articles" ? "article" : "summary",
|
|
18824
|
+
key,
|
|
18825
|
+
description,
|
|
18826
|
+
...dateMatch ? { date: dateMatch[1] } : {}
|
|
18827
|
+
});
|
|
18828
|
+
}
|
|
18829
|
+
return candidates;
|
|
18830
|
+
}
|
|
18831
|
+
function indexCandidateExists(candidate) {
|
|
18832
|
+
if (candidate.kind === "topic")
|
|
18833
|
+
return true;
|
|
18834
|
+
const root = candidate.kind === "article" ? runtime().articlesDir : runtime().summariesDir;
|
|
18835
|
+
const filePath = resolve2(root, `${candidate.key}.md`);
|
|
18836
|
+
const relativePath = relative(root, filePath);
|
|
18837
|
+
if (relativePath.startsWith("..") || relativePath.startsWith("/"))
|
|
18838
|
+
return false;
|
|
18839
|
+
try {
|
|
18840
|
+
return statSync(filePath).isFile();
|
|
18841
|
+
} catch {
|
|
18842
|
+
return false;
|
|
18843
|
+
}
|
|
18844
|
+
}
|
|
18845
|
+
function scoreIndexKey(query, key) {
|
|
18846
|
+
const normalizedQuery = normalizeSearchText(query);
|
|
18847
|
+
const compactQuery = compactSearchText(normalizedQuery);
|
|
18848
|
+
const normalizedKey = normalizeSearchText(key);
|
|
18849
|
+
const compactKey = compactSearchText(normalizedKey);
|
|
18850
|
+
const tokens = meaningfulSearchTokens(normalizedQuery);
|
|
18851
|
+
let score = 0;
|
|
18852
|
+
if (normalizedKey === normalizedQuery)
|
|
18853
|
+
score += 120;
|
|
18854
|
+
else if (compactKey === compactQuery)
|
|
18855
|
+
score += 110;
|
|
18856
|
+
else {
|
|
18857
|
+
if (compactQuery.length >= 2 && (compactKey.startsWith(compactQuery) || compactQuery.startsWith(compactKey))) {
|
|
18858
|
+
score += 65;
|
|
18859
|
+
}
|
|
18860
|
+
if (tokens.length > 0 && compactQuery.length >= 3 && compactKey.includes(compactQuery)) {
|
|
18861
|
+
score += 45;
|
|
18862
|
+
}
|
|
18863
|
+
score += fuzzySearchScore(compactQuery, compactKey, 55);
|
|
18864
|
+
}
|
|
18865
|
+
for (const token of tokens) {
|
|
18866
|
+
if (normalizedKey.includes(token))
|
|
18867
|
+
score += 10;
|
|
18868
|
+
}
|
|
18869
|
+
return score;
|
|
18870
|
+
}
|
|
18871
|
+
function scoreIndexCandidate(query, candidate) {
|
|
18872
|
+
const normalizedQuery = normalizeSearchText(query);
|
|
18873
|
+
if (!normalizedQuery)
|
|
18874
|
+
return 0;
|
|
18875
|
+
const normalizedDescription = normalizeSearchText(candidate.description);
|
|
18876
|
+
const tokens = canonicalSearchTokens(normalizedQuery);
|
|
18877
|
+
const descriptionTokens = new Set(canonicalSearchTokens(normalizedDescription));
|
|
18878
|
+
let score = scoreIndexKey(normalizedQuery, candidate.key);
|
|
18879
|
+
if (tokens.length > 0 && normalizedDescription.includes(normalizedQuery))
|
|
18880
|
+
score += 30;
|
|
18881
|
+
let descriptionMatches = 0;
|
|
18882
|
+
for (const token of tokens) {
|
|
18883
|
+
if (descriptionTokens.has(token)) {
|
|
18884
|
+
score += 5;
|
|
18885
|
+
descriptionMatches += 1;
|
|
18886
|
+
}
|
|
18887
|
+
}
|
|
18888
|
+
if (tokens.length > 1 && descriptionMatches === tokens.length)
|
|
18889
|
+
score += 25;
|
|
18890
|
+
if (candidate.kind === "topic") {
|
|
18891
|
+
const requestsCurrent = /\b(?:current|active|ongoing|open)\b|\uD604\uC7AC|\uC9C4\uD589 \uC911/.test(normalizedQuery);
|
|
18892
|
+
const archivedCandidate = /\b(?:archive|archived|historical|closed)\b|\uBCF4\uAD00|\uC885\uB8CC/.test(`${normalizeSearchText(candidate.key)} ${normalizedDescription}`);
|
|
18893
|
+
if (requestsCurrent && archivedCandidate)
|
|
18894
|
+
score -= 40;
|
|
18895
|
+
}
|
|
18896
|
+
return score;
|
|
18897
|
+
}
|
|
18334
18898
|
function wikiQuery(args) {
|
|
18335
|
-
const question = safeExt(args, ["question", "query", "q", "text"], "");
|
|
18336
|
-
const
|
|
18899
|
+
const question = String(safeExt(args, ["question", "query", "q", "text"], "")).trim();
|
|
18900
|
+
const requestedKind = String(safeExt(args, ["kind", "type"], "all"));
|
|
18901
|
+
const kind = requestedKind === "topic" || requestedKind === "article" || requestedKind === "summary" ? requestedKind : "all";
|
|
18902
|
+
const requestedLimit = Number(safeExt(args, ["limit", "maxResults"], 8));
|
|
18903
|
+
const limit = Math.min(Math.max(Number.isFinite(requestedLimit) ? requestedLimit : 8, 1), 20);
|
|
18904
|
+
if (!question) {
|
|
18905
|
+
return {
|
|
18906
|
+
content: [{ type: "text", text: "A non-empty wiki query is required." }],
|
|
18907
|
+
isError: true
|
|
18908
|
+
};
|
|
18909
|
+
}
|
|
18910
|
+
const normalizedQuery = normalizeSearchText(question);
|
|
18911
|
+
if (!normalizedQuery) {
|
|
18912
|
+
return { content: [{ type: "text", text: "No matching wiki articles found." }] };
|
|
18913
|
+
}
|
|
18914
|
+
const canReadTopicMemory = runtime().host.canReadTopicMemory;
|
|
18915
|
+
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;
|
|
18916
|
+
const visibleIndexCandidates = [
|
|
18917
|
+
...parseWikiIndex(resolve2(runtime().wikiDir, "topic-index.md")),
|
|
18918
|
+
...parseWikiIndex(resolve2(runtime().wikiDir, "article-index.md"))
|
|
18919
|
+
].filter((candidate) => kind === "all" || candidate.kind === kind).filter((candidate) => candidate.kind !== "topic" || !canReadTopicMemory || canReadTopicMemory(candidate.key, runtime().userId));
|
|
18920
|
+
const hasStaleExactIndexMatch = visibleIndexCandidates.some((candidate) => !indexCandidateExists(candidate) && scoreIndexKey(question, candidate.key) >= 100);
|
|
18921
|
+
const indexCandidates = visibleIndexCandidates.filter(indexCandidateExists).map((candidate) => ({
|
|
18922
|
+
candidate,
|
|
18923
|
+
keyScore: scoreIndexKey(question, candidate.key),
|
|
18924
|
+
score: scoreIndexCandidate(question, candidate)
|
|
18925
|
+
})).filter(({ keyScore, score }) => keyScore >= 25 || score >= 40).sort((left, right) => right.score - left.score || left.candidate.key.localeCompare(right.candidate.key));
|
|
18926
|
+
const strongestIndex = indexCandidates[0];
|
|
18927
|
+
if (kind === "topic" && strongestIndex && (strongestIndex.keyScore >= 25 || strongestIndex.score >= 40)) {
|
|
18928
|
+
const runnerUp = indexCandidates[1];
|
|
18929
|
+
const ambiguous = strongestIndex.keyScore < 100 && runnerUp !== undefined && runnerUp.score >= strongestIndex.score * 0.9;
|
|
18930
|
+
if (ambiguous) {
|
|
18931
|
+
const lines2 = ["Found 2 ambiguous wiki candidates:", ""];
|
|
18932
|
+
for (const { candidate, score } of [strongestIndex, runnerUp]) {
|
|
18933
|
+
lines2.push(`- kind: ${candidate.kind}`, ` key: ${candidate.key}`, ` score: ${score.toFixed(2)}`, ` description: ${candidate.description || "(none)"}`, ...candidate.date ? [` date: ${candidate.date}`] : []);
|
|
18934
|
+
}
|
|
18935
|
+
return { content: [{ type: "text", text: lines2.join(`
|
|
18936
|
+
`) }] };
|
|
18937
|
+
}
|
|
18938
|
+
const lines = ["Found 1 wiki candidate:", ""];
|
|
18939
|
+
for (const { candidate, score } of [strongestIndex]) {
|
|
18940
|
+
lines.push(`- kind: ${candidate.kind}`, ` key: ${candidate.key}`, ` score: ${score.toFixed(2)}`, ` description: ${candidate.description || "(none)"}`, ...candidate.date ? [` date: ${candidate.date}`] : []);
|
|
18941
|
+
}
|
|
18942
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
18943
|
+
`) }] };
|
|
18944
|
+
}
|
|
18337
18945
|
const results = [];
|
|
18338
18946
|
const scored = [];
|
|
18339
18947
|
function scan(dir, label) {
|
|
@@ -18349,68 +18957,266 @@ function wikiQuery(args) {
|
|
|
18349
18957
|
continue;
|
|
18350
18958
|
const fp = join2(sub, f.name);
|
|
18351
18959
|
try {
|
|
18352
|
-
const text =
|
|
18960
|
+
const text = readFileSync3(fp, "utf-8");
|
|
18961
|
+
const key = `${entry.name}/${f.name.replace(/\.md$/i, "")}`;
|
|
18353
18962
|
scored.push({
|
|
18354
|
-
|
|
18963
|
+
kind: label === "articles" ? "article" : "skill",
|
|
18964
|
+
key,
|
|
18965
|
+
keyScore: scoreIndexKey(question, key),
|
|
18966
|
+
score: scoreMatch(text, key, label === "articles" ? "article" : "skill"),
|
|
18355
18967
|
path: `${label}/${entry.name}/${f.name}`,
|
|
18356
|
-
text
|
|
18968
|
+
text,
|
|
18969
|
+
title: titleFromDocument(text, key)
|
|
18357
18970
|
});
|
|
18358
18971
|
} catch {}
|
|
18359
18972
|
}
|
|
18360
18973
|
} catch {}
|
|
18361
18974
|
}
|
|
18362
18975
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
18976
|
+
if (label === "topic" && canReadTopicMemory && !canReadTopicMemory(entry.name.replace(/\.md$/i, ""), runtime().userId)) {
|
|
18977
|
+
continue;
|
|
18978
|
+
}
|
|
18363
18979
|
const fp = join2(dir, entry.name);
|
|
18364
18980
|
try {
|
|
18365
|
-
const text =
|
|
18981
|
+
const text = readFileSync3(fp, "utf-8");
|
|
18982
|
+
const key = entry.name.replace(/\.md$/i, "");
|
|
18983
|
+
const resultKind = label === "articles" ? "article" : label === "topic" ? "topic" : label === "summaries" ? "summary" : "skill";
|
|
18366
18984
|
scored.push({
|
|
18367
|
-
|
|
18985
|
+
kind: resultKind,
|
|
18986
|
+
key,
|
|
18987
|
+
keyScore: scoreIndexKey(question, key),
|
|
18988
|
+
score: scoreMatch(text, key, resultKind),
|
|
18368
18989
|
path: `${label}/${entry.name}`,
|
|
18369
|
-
text
|
|
18990
|
+
text,
|
|
18991
|
+
title: titleFromDocument(text, key),
|
|
18992
|
+
...label === "summaries" && /^\d{4}-\d{2}-\d{2}/.test(key) ? { date: key.slice(0, 10) } : {}
|
|
18370
18993
|
});
|
|
18371
18994
|
} catch {}
|
|
18372
18995
|
}
|
|
18373
18996
|
}
|
|
18374
18997
|
} catch {}
|
|
18375
18998
|
}
|
|
18376
|
-
function
|
|
18377
|
-
const
|
|
18999
|
+
function titleFromDocument(text, key) {
|
|
19000
|
+
const titleLine = text.split(`
|
|
19001
|
+
`).find((line) => /^#+\s*/.test(line));
|
|
19002
|
+
return titleLine ? titleLine.replace(/^#+\s*/, "") : basename(key);
|
|
19003
|
+
}
|
|
19004
|
+
function scoreMatch(text, key, resultKind) {
|
|
19005
|
+
const titleLine = text.split(`
|
|
19006
|
+
`).find((line) => /^#+\s*/.test(line)) ?? "";
|
|
19007
|
+
const title = titleLine.replace(/^#+\s*/, "");
|
|
19008
|
+
const normalizedKey = normalizeSearchText(key);
|
|
19009
|
+
const normalizedTitle = normalizeSearchText(title);
|
|
19010
|
+
const normalizedBody = normalizeSearchText(text.replace(titleLine, ""));
|
|
19011
|
+
const compactQuery = compactSearchText(normalizedQuery);
|
|
19012
|
+
const compactKey = compactSearchText(normalizedKey);
|
|
19013
|
+
const compactTitle = compactSearchText(normalizedTitle);
|
|
19014
|
+
const tokens = canonicalSearchTokens(normalizedQuery);
|
|
19015
|
+
const keyTokens = new Set(canonicalSearchTokens(normalizedKey));
|
|
19016
|
+
const titleTokens = new Set(canonicalSearchTokens(normalizedTitle));
|
|
19017
|
+
const bodyTokens = canonicalSearchTokens(normalizedBody);
|
|
18378
19018
|
let score = 0;
|
|
18379
|
-
|
|
18380
|
-
|
|
18381
|
-
|
|
18382
|
-
|
|
18383
|
-
|
|
18384
|
-
|
|
18385
|
-
|
|
18386
|
-
|
|
19019
|
+
let strongMetadataEvidence = false;
|
|
19020
|
+
if (normalizedKey === normalizedQuery) {
|
|
19021
|
+
score += 120;
|
|
19022
|
+
strongMetadataEvidence = true;
|
|
19023
|
+
} else if (compactKey === compactQuery) {
|
|
19024
|
+
score += 110;
|
|
19025
|
+
strongMetadataEvidence = true;
|
|
19026
|
+
} else if (compactQuery.length >= 2 && (compactKey.startsWith(compactQuery) || compactQuery.startsWith(compactKey))) {
|
|
19027
|
+
score += 65;
|
|
19028
|
+
strongMetadataEvidence = true;
|
|
19029
|
+
}
|
|
19030
|
+
if (tokens.length > 0 && compactQuery.length >= 3 && compactKey.includes(compactQuery)) {
|
|
19031
|
+
score += 45;
|
|
19032
|
+
strongMetadataEvidence = true;
|
|
19033
|
+
}
|
|
19034
|
+
const fuzzyKeyScore = fuzzySearchScore(compactQuery, compactKey, 55);
|
|
19035
|
+
score += fuzzyKeyScore;
|
|
19036
|
+
if (fuzzyKeyScore > 0)
|
|
19037
|
+
strongMetadataEvidence = true;
|
|
19038
|
+
if (normalizedTitle === normalizedQuery) {
|
|
19039
|
+
score += 115;
|
|
19040
|
+
strongMetadataEvidence = true;
|
|
19041
|
+
} else if (compactTitle === compactQuery) {
|
|
19042
|
+
score += 105;
|
|
19043
|
+
strongMetadataEvidence = true;
|
|
19044
|
+
} else if (compactQuery.length >= 2 && compactTitle.startsWith(compactQuery)) {
|
|
19045
|
+
score += 60;
|
|
19046
|
+
strongMetadataEvidence = true;
|
|
19047
|
+
}
|
|
19048
|
+
const fuzzyTitleScore = fuzzySearchScore(compactQuery, compactTitle, 50);
|
|
19049
|
+
score += fuzzyTitleScore;
|
|
19050
|
+
if (fuzzyTitleScore > 0)
|
|
19051
|
+
strongMetadataEvidence = true;
|
|
19052
|
+
if (resultKind === "summary") {
|
|
19053
|
+
const temporalScore = scoreSummaryTime(normalizedQuery, key, title);
|
|
19054
|
+
if (temporalScore === null)
|
|
19055
|
+
return 0;
|
|
19056
|
+
score += temporalScore;
|
|
19057
|
+
if (temporalScore > 0)
|
|
19058
|
+
strongMetadataEvidence = true;
|
|
19059
|
+
}
|
|
19060
|
+
const isMultiToken = tokens.length > 1;
|
|
19061
|
+
if (tokens.length > 0 && normalizedTitle.includes(normalizedQuery)) {
|
|
19062
|
+
score += 35;
|
|
19063
|
+
strongMetadataEvidence = true;
|
|
19064
|
+
}
|
|
19065
|
+
const phraseMatches = isMultiToken ? countPhrase(normalizedBody, normalizedQuery) : 0;
|
|
19066
|
+
if (phraseMatches > 0)
|
|
19067
|
+
score += phraseMatches * 12;
|
|
19068
|
+
else if (tokens.length === 1 && tokens[0].length >= 5 && normalizedBody.includes(tokens[0])) {
|
|
19069
|
+
score += 16;
|
|
19070
|
+
}
|
|
19071
|
+
let bodyMatches = 0;
|
|
19072
|
+
let minimumBodyFrequency = Number.POSITIVE_INFINITY;
|
|
19073
|
+
for (const token of tokens) {
|
|
19074
|
+
if (keyTokens.has(token))
|
|
19075
|
+
score += 10;
|
|
19076
|
+
if (titleTokens.has(token))
|
|
19077
|
+
score += 12;
|
|
19078
|
+
const bodyFrequency = countToken(bodyTokens, token);
|
|
19079
|
+
if (bodyFrequency > 0) {
|
|
19080
|
+
score += Math.min(bodyFrequency, 3) * 4;
|
|
19081
|
+
bodyMatches += 1;
|
|
19082
|
+
minimumBodyFrequency = Math.min(minimumBodyFrequency, bodyFrequency);
|
|
18387
19083
|
}
|
|
18388
19084
|
}
|
|
19085
|
+
if (isMultiToken && bodyMatches === tokens.length) {
|
|
19086
|
+
score += 8 * Math.min(minimumBodyFrequency, 3);
|
|
19087
|
+
}
|
|
19088
|
+
const minimumBodyTokens = hasStaleExactIndexMatch ? 3 : 2;
|
|
19089
|
+
const partialArticleEvidence = resultKind === "article" && bodyMatches >= 3 && bodyMatches / tokens.length >= 0.6;
|
|
19090
|
+
const strongBodyEvidence = tokens.length >= minimumBodyTokens && (bodyMatches === tokens.length && (phraseMatches > 0 || minimumBodyFrequency >= 1) || partialArticleEvidence);
|
|
19091
|
+
if (!strongMetadataEvidence && !strongBodyEvidence)
|
|
19092
|
+
return 0;
|
|
18389
19093
|
return score;
|
|
18390
19094
|
}
|
|
18391
|
-
|
|
18392
|
-
|
|
19095
|
+
const bodyKinds = [
|
|
19096
|
+
...kind === "all" || kind === "article" ? ["article"] : [],
|
|
19097
|
+
...kind === "all" || kind === "summary" ? ["summary"] : []
|
|
19098
|
+
];
|
|
19099
|
+
if (bodyKinds.length > 0) {
|
|
19100
|
+
const searchIndex = openWikiSearchIndex();
|
|
19101
|
+
if (searchIndex) {
|
|
19102
|
+
try {
|
|
19103
|
+
const terms = canonicalSearchTokens(normalizedQuery);
|
|
19104
|
+
const ids = searchIndex.matchingDocumentIds(bodyKinds, terms);
|
|
19105
|
+
if (temporalDirection && bodyKinds.includes("summary")) {
|
|
19106
|
+
for (const item of searchIndex.summaryMetadataByDate({
|
|
19107
|
+
direction: temporalDirection === "newest" ? "latest" : "oldest",
|
|
19108
|
+
limit
|
|
19109
|
+
})) {
|
|
19110
|
+
ids.add(item.id);
|
|
19111
|
+
}
|
|
19112
|
+
}
|
|
19113
|
+
for (const document of searchIndex.documents(ids)) {
|
|
19114
|
+
scored.push({
|
|
19115
|
+
kind: document.kind,
|
|
19116
|
+
key: document.key,
|
|
19117
|
+
keyScore: scoreIndexKey(question, document.key),
|
|
19118
|
+
score: scoreMatch(document.text, document.key, document.kind),
|
|
19119
|
+
path: `${document.kind === "article" ? "articles" : "summaries"}/${document.key}.md`,
|
|
19120
|
+
text: document.text,
|
|
19121
|
+
title: document.title,
|
|
19122
|
+
...document.date ? { date: document.date } : {}
|
|
19123
|
+
});
|
|
19124
|
+
}
|
|
19125
|
+
} catch {} finally {
|
|
19126
|
+
searchIndex.close();
|
|
19127
|
+
}
|
|
19128
|
+
}
|
|
19129
|
+
}
|
|
19130
|
+
if (kind === "all" && runtime().surface === "all")
|
|
18393
19131
|
scan(runtime().skillsDir, "skills");
|
|
18394
|
-
|
|
18395
|
-
|
|
18396
|
-
|
|
18397
|
-
const
|
|
18398
|
-
|
|
19132
|
+
if (kind === "all" || kind === "topic")
|
|
19133
|
+
scan(runtime().topicsDir, "topic");
|
|
19134
|
+
const merged = new Map;
|
|
19135
|
+
for (const { candidate, keyScore, score } of indexCandidates) {
|
|
19136
|
+
merged.set(`${candidate.kind}:${candidate.key}`, {
|
|
19137
|
+
kind: candidate.kind,
|
|
19138
|
+
key: candidate.key,
|
|
19139
|
+
keyScore,
|
|
19140
|
+
score,
|
|
19141
|
+
description: candidate.description,
|
|
19142
|
+
date: candidate.date
|
|
19143
|
+
});
|
|
19144
|
+
}
|
|
19145
|
+
const documentThreshold = kind === "topic" ? 24 : 16;
|
|
19146
|
+
for (const document of scored) {
|
|
19147
|
+
if (document.score < documentThreshold)
|
|
19148
|
+
continue;
|
|
19149
|
+
const id = `${document.kind}:${document.key}`;
|
|
19150
|
+
const indexed = merged.get(id);
|
|
19151
|
+
merged.set(id, indexed ? { ...indexed, ...document, score: Math.max(indexed.score, document.score) } : document);
|
|
19152
|
+
}
|
|
19153
|
+
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));
|
|
19154
|
+
if (kind === "summary") {
|
|
19155
|
+
const sameTopic = ranked.filter((result) => {
|
|
19156
|
+
const identity = summaryTopicIdentity(result.key);
|
|
19157
|
+
return identity.length >= 3 && normalizedQuery.includes(identity);
|
|
19158
|
+
});
|
|
19159
|
+
if (sameTopic.length > 0)
|
|
19160
|
+
ranked = sameTopic;
|
|
19161
|
+
const requestedDateMatch = normalizedQuery.match(/\b(\d{4}) (\d{2}) (\d{2})\b/);
|
|
19162
|
+
const requestedDate = requestedDateMatch ? `${requestedDateMatch[1]}-${requestedDateMatch[2]}-${requestedDateMatch[3]}` : undefined;
|
|
19163
|
+
const beforeDate = /\bbefore\b/.test(normalizedQuery);
|
|
19164
|
+
const afterDate = /\bafter\b/.test(normalizedQuery);
|
|
19165
|
+
if (requestedDate && beforeDate) {
|
|
19166
|
+
ranked = ranked.filter((result) => !result.date || result.date < requestedDate);
|
|
19167
|
+
} else if (requestedDate && afterDate) {
|
|
19168
|
+
ranked = ranked.filter((result) => !result.date || result.date > requestedDate);
|
|
19169
|
+
}
|
|
19170
|
+
ranked.sort((left, right) => {
|
|
19171
|
+
if (beforeDate) {
|
|
19172
|
+
return (right.date ?? "").localeCompare(left.date ?? "") || right.score - left.score;
|
|
19173
|
+
}
|
|
19174
|
+
if (afterDate) {
|
|
19175
|
+
return (left.date ?? "").localeCompare(right.date ?? "") || right.score - left.score;
|
|
19176
|
+
}
|
|
19177
|
+
if (requestedDate) {
|
|
19178
|
+
const dateDifference = Number(right.date === requestedDate) - Number(left.date === requestedDate);
|
|
19179
|
+
if (dateDifference !== 0)
|
|
19180
|
+
return dateDifference;
|
|
19181
|
+
}
|
|
19182
|
+
if (temporalDirection) {
|
|
19183
|
+
const dateDifference = temporalDirection === "oldest" ? (left.date ?? "").localeCompare(right.date ?? "") : (right.date ?? "").localeCompare(left.date ?? "");
|
|
19184
|
+
if (dateDifference !== 0)
|
|
19185
|
+
return dateDifference;
|
|
19186
|
+
}
|
|
19187
|
+
return right.score - left.score || (right.date ?? "").localeCompare(left.date ?? "") || left.key.localeCompare(right.key);
|
|
19188
|
+
});
|
|
19189
|
+
}
|
|
19190
|
+
if (kind === "topic" && ranked[0]) {
|
|
19191
|
+
const runnerUp = ranked[1];
|
|
19192
|
+
const ambiguous = ranked[0].keyScore < 100 && runnerUp !== undefined && runnerUp.score >= ranked[0].score * 0.9;
|
|
19193
|
+
if (ambiguous) {
|
|
19194
|
+
ranked = ranked.slice(0, 2);
|
|
19195
|
+
} else {
|
|
19196
|
+
ranked = ranked.slice(0, 1);
|
|
19197
|
+
}
|
|
19198
|
+
} else {
|
|
19199
|
+
ranked = ranked.slice(0, limit);
|
|
19200
|
+
}
|
|
19201
|
+
if (ranked.length === 0) {
|
|
18399
19202
|
results.push("No matching wiki articles found.");
|
|
18400
19203
|
} else {
|
|
18401
|
-
results.push(`Found ${
|
|
19204
|
+
results.push(`Found ${ranked.length} matching result(s):
|
|
18402
19205
|
`);
|
|
18403
|
-
for (const
|
|
18404
|
-
|
|
18405
|
-
|
|
18406
|
-
|
|
18407
|
-
|
|
18408
|
-
results.push(`## ${title}
|
|
18409
|
-
Path: ${path}
|
|
19206
|
+
for (const result of ranked) {
|
|
19207
|
+
if (result.kind === "skill") {
|
|
19208
|
+
const body = (result.text ?? "").trim().slice(0, 400);
|
|
19209
|
+
results.push(`## ${result.title ?? result.key}
|
|
19210
|
+
Path: ${result.path}
|
|
18410
19211
|
|
|
18411
|
-
${body}${text
|
|
19212
|
+
${body}${(result.text?.length ?? 0) > 400 ? `
|
|
18412
19213
|
...(truncated)` : ""}
|
|
18413
19214
|
`);
|
|
19215
|
+
continue;
|
|
19216
|
+
}
|
|
19217
|
+
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 ? [
|
|
19218
|
+
` excerpt: ${result.text.replace(/^---[\s\S]*?---\n?/, "").replace(/^#.*$/m, "").trim().slice(0, 400)}`
|
|
19219
|
+
] : []);
|
|
18414
19220
|
}
|
|
18415
19221
|
}
|
|
18416
19222
|
return { content: [{ type: "text", text: results.join(`
|
|
@@ -18429,36 +19235,94 @@ function wikiTopicBrief(args) {
|
|
|
18429
19235
|
};
|
|
18430
19236
|
}
|
|
18431
19237
|
const { getTopicBrief, resolveTopicBrief } = runtime().host;
|
|
18432
|
-
|
|
19238
|
+
const hasAuthorizedBridge = Boolean(runtime().topicId && (getTopicBrief || resolveTopicBrief));
|
|
19239
|
+
if (hasAuthorizedBridge) {
|
|
18433
19240
|
try {
|
|
18434
19241
|
const brief = resolveTopicBrief ? resolveTopicBrief(rawTopic, runtime().userId) : getTopicBrief(rawTopic);
|
|
18435
|
-
if (
|
|
18436
|
-
|
|
18437
|
-
|
|
18438
|
-
|
|
18439
|
-
|
|
18440
|
-
|
|
18441
|
-
|
|
18442
|
-
|
|
18443
|
-
|
|
18444
|
-
|
|
18445
|
-
|
|
18446
|
-
|
|
18447
|
-
|
|
18448
|
-
if (brief.summaryDate) {
|
|
18449
|
-
lines.push("", `Summary date: ${brief.summaryDate}`);
|
|
18450
|
-
}
|
|
18451
|
-
return { content: [{ type: "text", text: lines.join(`
|
|
19242
|
+
if (brief) {
|
|
19243
|
+
const lines = [
|
|
19244
|
+
`# Topic Brief: ${topicNameFrom(rawTopic)}`,
|
|
19245
|
+
"",
|
|
19246
|
+
brief.briefMd || "(empty brief)"
|
|
19247
|
+
];
|
|
19248
|
+
if (brief.latestSummaryMd) {
|
|
19249
|
+
lines.push("", "## Latest Summary", "", brief.latestSummaryMd);
|
|
19250
|
+
}
|
|
19251
|
+
if (brief.summaryDate) {
|
|
19252
|
+
lines.push("", `Summary date: ${brief.summaryDate}`);
|
|
19253
|
+
}
|
|
19254
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
18452
19255
|
`) }] };
|
|
18453
|
-
|
|
19256
|
+
}
|
|
19257
|
+
} catch {}
|
|
19258
|
+
return {
|
|
19259
|
+
content: [{ type: "text", text: `No brief found for topic: ${rawTopic}` }]
|
|
19260
|
+
};
|
|
19261
|
+
}
|
|
19262
|
+
const topicKey = wikiSummarySlug(rawTopic.replace(/^topic\//, "").replace(/\.md$/i, ""));
|
|
19263
|
+
const filePath = resolve2(runtime().topicsDir, `${topicKey}.md`);
|
|
19264
|
+
try {
|
|
19265
|
+
const content = readFileSync3(filePath, "utf-8");
|
|
19266
|
+
return {
|
|
19267
|
+
content: [{ type: "text", text: `# Topic Brief: ${topicKey}
|
|
19268
|
+
|
|
19269
|
+
${content}` }]
|
|
19270
|
+
};
|
|
19271
|
+
} catch {}
|
|
19272
|
+
return {
|
|
19273
|
+
content: [{ type: "text", text: `No brief found for topic: ${rawTopic}` }]
|
|
19274
|
+
};
|
|
19275
|
+
}
|
|
19276
|
+
function wikiRead(args) {
|
|
19277
|
+
const rawKind = String(safeExt(args, ["kind", "type"], "article"));
|
|
19278
|
+
const kind = rawKind === "topic" || rawKind === "summary" ? rawKind : "article";
|
|
19279
|
+
const rawKey = String(safeExt(args, ["key", "slug", "topic"], "")).trim();
|
|
19280
|
+
if (!rawKey) {
|
|
19281
|
+
return { content: [{ type: "text", text: "A wiki document key is required." }], isError: true };
|
|
19282
|
+
}
|
|
19283
|
+
if (kind === "topic") {
|
|
19284
|
+
const result = wikiTopicBrief({ topic: rawKey });
|
|
19285
|
+
const resultText = result.content.map((entry) => entry.type === "text" ? entry.text : "").join(`
|
|
19286
|
+
`);
|
|
19287
|
+
const adopt = safeExt(args, ["adopt", "use", "select"], false);
|
|
19288
|
+
if (!adopt || resultText.startsWith("No brief found for topic"))
|
|
19289
|
+
return result;
|
|
19290
|
+
const currentTopicId = runtime().currentTopicId;
|
|
19291
|
+
const memoryKey = wikiSummarySlug(rawKey.replace(/^topic\//, "").replace(/\.md$/i, ""));
|
|
19292
|
+
if (!currentTopicId || !runtime().host.adoptTopicMemory?.(currentTopicId, runtime().userId, memoryKey)) {
|
|
18454
19293
|
return {
|
|
18455
|
-
content: [
|
|
19294
|
+
content: [
|
|
19295
|
+
...result.content,
|
|
19296
|
+
{ type: "text", text: `
|
|
19297
|
+
Memory was read but could not be adopted by this topic.` }
|
|
19298
|
+
]
|
|
18456
19299
|
};
|
|
18457
19300
|
}
|
|
19301
|
+
return {
|
|
19302
|
+
content: [...result.content, { type: "text", text: `
|
|
19303
|
+
Adopted topic memory: ${memoryKey}` }]
|
|
19304
|
+
};
|
|
19305
|
+
}
|
|
19306
|
+
if (rawKey.includes("\\") || rawKey.split("/").some((part) => part === "..") || rawKey.startsWith("/")) {
|
|
19307
|
+
return { content: [{ type: "text", text: "Invalid wiki document key." }], isError: true };
|
|
19308
|
+
}
|
|
19309
|
+
const root = kind === "summary" ? runtime().summariesDir : runtime().articlesDir;
|
|
19310
|
+
const key = rawKey.replace(new RegExp(`^(?:${kind === "summary" ? "summaries" : "articles"})/`), "").replace(/\.md$/i, "");
|
|
19311
|
+
const filePath = resolve2(root, `${key}.md`);
|
|
19312
|
+
const relativePath = relative(root, filePath);
|
|
19313
|
+
if (relativePath.startsWith("..") || relativePath.startsWith("/")) {
|
|
19314
|
+
return { content: [{ type: "text", text: "Invalid wiki document key." }], isError: true };
|
|
19315
|
+
}
|
|
19316
|
+
try {
|
|
19317
|
+
const content = readFileSync3(filePath, "utf-8");
|
|
19318
|
+
return {
|
|
19319
|
+
content: [{ type: "text", text: `# Wiki ${kind}: ${key}
|
|
19320
|
+
|
|
19321
|
+
${content}` }]
|
|
19322
|
+
};
|
|
19323
|
+
} catch {
|
|
19324
|
+
return { content: [{ type: "text", text: `No ${kind} found for: ${rawKey}` }] };
|
|
18458
19325
|
}
|
|
18459
|
-
return {
|
|
18460
|
-
content: [{ type: "text", text: `No topic brief found for: ${rawTopic}` }]
|
|
18461
|
-
};
|
|
18462
19326
|
}
|
|
18463
19327
|
function wikiLastConversation(args) {
|
|
18464
19328
|
const rawTopic = safeExt(args, ["topic", "topicName", "topicId"], runtime().topicId);
|
|
@@ -18470,18 +19334,18 @@ function wikiLastConversation(args) {
|
|
|
18470
19334
|
const name = slugify(topicNameFrom(rawTopic));
|
|
18471
19335
|
const archiveDir = resolve2(runtime().archiveDir, name);
|
|
18472
19336
|
try {
|
|
18473
|
-
let files =
|
|
19337
|
+
let files = existsSync3(archiveDir) ? readdirSync(archiveDir).filter((f) => f.endsWith(".jsonl")).sort().reverse() : [];
|
|
18474
19338
|
if (files.length === 0) {
|
|
18475
|
-
files =
|
|
19339
|
+
files = existsSync3(runtime().archiveDir) ? readdirSync(runtime().archiveDir).filter((f) => f.startsWith(name) && f.endsWith(".jsonl")).sort().reverse() : [];
|
|
18476
19340
|
}
|
|
18477
19341
|
if (files.length === 0) {
|
|
18478
19342
|
return {
|
|
18479
19343
|
content: [{ type: "text", text: `No archive found for topic: ${rawTopic}` }]
|
|
18480
19344
|
};
|
|
18481
19345
|
}
|
|
18482
|
-
const actualPath = files[0].includes("/") ? files[0] : resolve2(
|
|
19346
|
+
const actualPath = files[0].includes("/") ? files[0] : resolve2(existsSync3(archiveDir) && readdirSync(archiveDir).includes(files[0]) ? archiveDir : runtime().archiveDir, files[0]);
|
|
18483
19347
|
try {
|
|
18484
|
-
const raw =
|
|
19348
|
+
const raw = readFileSync3(actualPath, "utf-8");
|
|
18485
19349
|
const lines = raw.trim().split(`
|
|
18486
19350
|
`).filter(Boolean);
|
|
18487
19351
|
const turns2 = [];
|
|
@@ -18554,7 +19418,7 @@ function skillQuery(args) {
|
|
|
18554
19418
|
continue;
|
|
18555
19419
|
const skillFile = resolve2(runtime().skillsDir, entry.name, "skill.md");
|
|
18556
19420
|
try {
|
|
18557
|
-
const text =
|
|
19421
|
+
const text = readFileSync3(skillFile, "utf-8");
|
|
18558
19422
|
const lower = text.toLowerCase();
|
|
18559
19423
|
let score = 0;
|
|
18560
19424
|
for (const w of query.split(/\s+/).filter(Boolean)) {
|
|
@@ -18625,7 +19489,7 @@ ${fresh.join(`
|
|
|
18625
19489
|
const skillPath = resolve2(skillDir, "skill.md");
|
|
18626
19490
|
let finalContent = content;
|
|
18627
19491
|
try {
|
|
18628
|
-
const existing =
|
|
19492
|
+
const existing = readFileSync3(skillPath, "utf-8");
|
|
18629
19493
|
finalContent = mergeGotchas(content, extractGotchas(existing));
|
|
18630
19494
|
} catch {}
|
|
18631
19495
|
writeFileSync2(skillPath, finalContent, "utf-8");
|
|
@@ -18638,15 +19502,7 @@ ${fresh.join(`
|
|
|
18638
19502
|
]
|
|
18639
19503
|
};
|
|
18640
19504
|
}
|
|
18641
|
-
function
|
|
18642
|
-
const rawTopic = safeExt(args, ["topic", "topicName"], "");
|
|
18643
|
-
const content = safeExt(args, ["content", "text", "body"], "");
|
|
18644
|
-
if (!rawTopic)
|
|
18645
|
-
return { content: [{ type: "text", text: "Missing topic." }] };
|
|
18646
|
-
if (!content)
|
|
18647
|
-
return { content: [{ type: "text", text: "Missing content." }] };
|
|
18648
|
-
const now = new Date;
|
|
18649
|
-
const dateStr = now.toISOString().slice(0, 10);
|
|
19505
|
+
function writeSummaryDocument(rawTopic, content, dateStr) {
|
|
18650
19506
|
const topicId = runtime().topicId;
|
|
18651
19507
|
const fileSlug = wikiBriefStorageKey(rawTopic, topicId);
|
|
18652
19508
|
ensureDir(runtime().summariesDir);
|
|
@@ -18673,7 +19529,7 @@ function saveWikiEntry(args) {
|
|
|
18673
19529
|
if (topicId && setTopicBrief) {
|
|
18674
19530
|
try {
|
|
18675
19531
|
const briefPath = resolve2(runtime().topicsDir, `${fileSlug}.md`);
|
|
18676
|
-
let briefMd =
|
|
19532
|
+
let briefMd = existsSync3(briefPath) ? readFileSync3(briefPath, "utf-8") : undefined;
|
|
18677
19533
|
if (briefMd === undefined && getTopicBrief && !getTopicBrief(fileSlug)?.briefMd) {
|
|
18678
19534
|
const legacy = getTopicBrief(topicId)?.briefMd;
|
|
18679
19535
|
if (legacy)
|
|
@@ -18687,28 +19543,14 @@ function saveWikiEntry(args) {
|
|
|
18687
19543
|
sqliteUpdated = true;
|
|
18688
19544
|
} catch {}
|
|
18689
19545
|
}
|
|
18690
|
-
return {
|
|
18691
|
-
content: [
|
|
18692
|
-
{
|
|
18693
|
-
type: "text",
|
|
18694
|
-
text: `Saved summary: summaries/${summaryName}` + (sqliteUpdated ? `
|
|
18695
|
-
SQLite latest-summary also updated.` : "")
|
|
18696
|
-
}
|
|
18697
|
-
]
|
|
18698
|
-
};
|
|
19546
|
+
return { slug: summaryName.replace(/\.md$/i, ""), sqliteUpdated };
|
|
18699
19547
|
}
|
|
18700
|
-
function
|
|
18701
|
-
const rawTopic = safeExt(args, ["topic", "topicName"], "");
|
|
18702
|
-
const content = safeExt(args, ["content", "brief", "text", "body"], "");
|
|
18703
|
-
if (!rawTopic)
|
|
18704
|
-
return { content: [{ type: "text", text: "Missing topic." }] };
|
|
18705
|
-
if (!content)
|
|
18706
|
-
return { content: [{ type: "text", text: "Missing content." }] };
|
|
19548
|
+
function writeTopicDocument(rawTopic, content) {
|
|
18707
19549
|
const topicId = runtime().topicId;
|
|
18708
19550
|
const fileSlug = wikiBriefStorageKey(rawTopic, topicId);
|
|
18709
19551
|
const briefPath = resolve2(runtime().topicsDir, `${fileSlug}.md`);
|
|
18710
19552
|
ensureDir(dirname2(briefPath));
|
|
18711
|
-
const existed =
|
|
19553
|
+
const existed = existsSync3(briefPath);
|
|
18712
19554
|
writeFileSync2(briefPath, content, "utf-8");
|
|
18713
19555
|
const setTopicBrief = runtime().host.setTopicBrief;
|
|
18714
19556
|
let sqliteUpdated = false;
|
|
@@ -18718,68 +19560,228 @@ function saveTopicBrief(args) {
|
|
|
18718
19560
|
sqliteUpdated = true;
|
|
18719
19561
|
} catch {}
|
|
18720
19562
|
}
|
|
19563
|
+
return { slug: fileSlug, existed, sqliteUpdated };
|
|
19564
|
+
}
|
|
19565
|
+
function writeArticleDocument(slug, content) {
|
|
19566
|
+
const articlePath = resolve2(runtime().articlesDir, `${slug}.md`);
|
|
19567
|
+
ensureDir(dirname2(articlePath));
|
|
19568
|
+
const existed = existsSync3(articlePath);
|
|
19569
|
+
atomicWriteFile(articlePath, content);
|
|
19570
|
+
return { existed };
|
|
19571
|
+
}
|
|
19572
|
+
function wikiWrite(args) {
|
|
19573
|
+
const rawKind = safeExt(args, ["kind", "type"], "");
|
|
19574
|
+
const content = safeExt(args, ["content", "text", "body", "brief"], "");
|
|
19575
|
+
const rawDescription = safeExt(args, ["description", "desc"], "");
|
|
19576
|
+
const rawTopic = safeExt(args, ["topic", "topicName"], "");
|
|
19577
|
+
const rawSlug = safeExt(args, ["slug", "id"], "");
|
|
19578
|
+
const section = safeExt(args, ["section", "category"], undefined);
|
|
19579
|
+
const rawDate = safeExt(args, ["date", "created"], undefined);
|
|
19580
|
+
if (rawKind !== "summary" && rawKind !== "article" && rawKind !== "topic") {
|
|
19581
|
+
return {
|
|
19582
|
+
content: [{ type: "text", text: 'kind must be one of "summary", "article", "topic".' }],
|
|
19583
|
+
isError: true
|
|
19584
|
+
};
|
|
19585
|
+
}
|
|
19586
|
+
if (!content.trim()) {
|
|
19587
|
+
return { content: [{ type: "text", text: "Missing content." }], isError: true };
|
|
19588
|
+
}
|
|
19589
|
+
const descriptionResult = normalizeIndexDescription(rawDescription);
|
|
19590
|
+
if ("error" in descriptionResult) {
|
|
19591
|
+
return { content: [{ type: "text", text: descriptionResult.error }], isError: true };
|
|
19592
|
+
}
|
|
19593
|
+
const { description } = descriptionResult;
|
|
19594
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
19595
|
+
if (rawKind === "article") {
|
|
19596
|
+
const slugResult = normalizeWikiSlug(rawSlug);
|
|
19597
|
+
if ("error" in slugResult) {
|
|
19598
|
+
return { content: [{ type: "text", text: slugResult.error }], isError: true };
|
|
19599
|
+
}
|
|
19600
|
+
if (!section?.trim()) {
|
|
19601
|
+
return {
|
|
19602
|
+
content: [
|
|
19603
|
+
{ type: "text", text: "An article requires a section so the catalog stays navigable." }
|
|
19604
|
+
],
|
|
19605
|
+
isError: true
|
|
19606
|
+
};
|
|
19607
|
+
}
|
|
19608
|
+
const { existed } = writeArticleDocument(slugResult.slug, content);
|
|
19609
|
+
const link2 = indexRowOrPartialWrite("article", {
|
|
19610
|
+
kind: "article",
|
|
19611
|
+
slug: slugResult.slug,
|
|
19612
|
+
description,
|
|
19613
|
+
date: rawDate ?? today,
|
|
19614
|
+
section: section.trim()
|
|
19615
|
+
});
|
|
19616
|
+
if (typeof link2 !== "string")
|
|
19617
|
+
return link2;
|
|
19618
|
+
const searched = indexWikiSearchDocument("article", slugResult.slug, content);
|
|
19619
|
+
return {
|
|
19620
|
+
content: [
|
|
19621
|
+
{
|
|
19622
|
+
type: "text",
|
|
19623
|
+
text: `Saved article: articles/${slugResult.slug}.md (${existed ? "updated" : "created"})
|
|
19624
|
+
Indexed: ${link2}` + (searched ? "" : `
|
|
19625
|
+
${SEARCH_CACHE_SKIPPED_NOTE}`)
|
|
19626
|
+
}
|
|
19627
|
+
]
|
|
19628
|
+
};
|
|
19629
|
+
}
|
|
19630
|
+
const topic = rawTopic.trim();
|
|
19631
|
+
if (!topic) {
|
|
19632
|
+
return { content: [{ type: "text", text: "Missing topic." }], isError: true };
|
|
19633
|
+
}
|
|
19634
|
+
if (rawKind === "summary") {
|
|
19635
|
+
const dateStr = rawDate ?? today;
|
|
19636
|
+
const written2 = writeSummaryDocument(topic, content, dateStr);
|
|
19637
|
+
const link2 = indexRowOrPartialWrite("summary", {
|
|
19638
|
+
kind: "summary",
|
|
19639
|
+
slug: written2.slug,
|
|
19640
|
+
description,
|
|
19641
|
+
date: dateStr
|
|
19642
|
+
});
|
|
19643
|
+
if (typeof link2 !== "string")
|
|
19644
|
+
return link2;
|
|
19645
|
+
const searched = indexWikiSearchDocument("summary", written2.slug, content);
|
|
19646
|
+
return {
|
|
19647
|
+
content: [
|
|
19648
|
+
{
|
|
19649
|
+
type: "text",
|
|
19650
|
+
text: `Saved summary: summaries/${written2.slug}.md
|
|
19651
|
+
Indexed: ${link2}` + (written2.sqliteUpdated ? `
|
|
19652
|
+
SQLite latest-summary also updated.` : "") + (searched ? "" : `
|
|
19653
|
+
${SEARCH_CACHE_SKIPPED_NOTE}`)
|
|
19654
|
+
}
|
|
19655
|
+
]
|
|
19656
|
+
};
|
|
19657
|
+
}
|
|
19658
|
+
const written = writeTopicDocument(topic, content);
|
|
19659
|
+
const link = indexRowOrPartialWrite("topic", {
|
|
19660
|
+
kind: "topic",
|
|
19661
|
+
slug: written.slug,
|
|
19662
|
+
description,
|
|
19663
|
+
date: rawDate ?? today
|
|
19664
|
+
});
|
|
19665
|
+
if (typeof link !== "string")
|
|
19666
|
+
return link;
|
|
18721
19667
|
return {
|
|
18722
19668
|
content: [
|
|
18723
19669
|
{
|
|
18724
19670
|
type: "text",
|
|
18725
|
-
text: `Saved brief: topic/${
|
|
19671
|
+
text: `Saved brief: topic/${written.slug}.md (${written.existed ? "updated" : "created"})
|
|
19672
|
+
Indexed: ${link}` + (written.sqliteUpdated ? `
|
|
18726
19673
|
SQLite brief also updated.` : "")
|
|
18727
19674
|
}
|
|
18728
19675
|
]
|
|
18729
19676
|
};
|
|
18730
19677
|
}
|
|
18731
|
-
function
|
|
18732
|
-
const
|
|
18733
|
-
|
|
18734
|
-
|
|
18735
|
-
|
|
18736
|
-
|
|
18737
|
-
|
|
18738
|
-
|
|
18739
|
-
|
|
18740
|
-
|
|
18741
|
-
const
|
|
18742
|
-
|
|
18743
|
-
|
|
18744
|
-
|
|
18745
|
-
|
|
18746
|
-
|
|
18747
|
-
|
|
19678
|
+
function collectIndexableDocuments() {
|
|
19679
|
+
const sources = [];
|
|
19680
|
+
function add(kind, key, path) {
|
|
19681
|
+
try {
|
|
19682
|
+
const stat = statSync(path);
|
|
19683
|
+
if (!stat.isFile())
|
|
19684
|
+
return;
|
|
19685
|
+
sources.push({ kind, key, path, mtimeMs: stat.mtimeMs, size: stat.size });
|
|
19686
|
+
} catch {}
|
|
19687
|
+
}
|
|
19688
|
+
for (const [root, kind, allowSubdirectories] of [
|
|
19689
|
+
[runtime().articlesDir, "article", true],
|
|
19690
|
+
[runtime().summariesDir, "summary", false]
|
|
19691
|
+
]) {
|
|
19692
|
+
let entries;
|
|
19693
|
+
try {
|
|
19694
|
+
entries = readdirSync(root, { withFileTypes: true });
|
|
19695
|
+
} catch {
|
|
19696
|
+
continue;
|
|
19697
|
+
}
|
|
19698
|
+
for (const entry of entries) {
|
|
19699
|
+
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
19700
|
+
add(kind, entry.name.replace(/\.md$/i, ""), join2(root, entry.name));
|
|
19701
|
+
continue;
|
|
19702
|
+
}
|
|
19703
|
+
if (!entry.isDirectory() || !allowSubdirectories)
|
|
19704
|
+
continue;
|
|
19705
|
+
let nested;
|
|
19706
|
+
try {
|
|
19707
|
+
nested = readdirSync(join2(root, entry.name), { withFileTypes: true });
|
|
19708
|
+
} catch {
|
|
19709
|
+
continue;
|
|
19710
|
+
}
|
|
19711
|
+
for (const file of nested) {
|
|
19712
|
+
if (!file.isFile() || !file.name.endsWith(".md"))
|
|
19713
|
+
continue;
|
|
19714
|
+
add(kind, `${entry.name}/${file.name.replace(/\.md$/i, "")}`, join2(root, entry.name, file.name));
|
|
19715
|
+
}
|
|
19716
|
+
}
|
|
19717
|
+
}
|
|
19718
|
+
return sources;
|
|
19719
|
+
}
|
|
19720
|
+
function wikiReindex() {
|
|
19721
|
+
const documents = collectIndexableDocuments();
|
|
19722
|
+
const index = openWikiSearchIndex();
|
|
19723
|
+
if (!index) {
|
|
19724
|
+
return {
|
|
19725
|
+
content: [{ type: "text", text: "Could not open the wiki search index." }],
|
|
19726
|
+
isError: true
|
|
19727
|
+
};
|
|
19728
|
+
}
|
|
19729
|
+
let synced;
|
|
19730
|
+
try {
|
|
19731
|
+
synced = index.sync(documents, (source, text) => prepareWikiSearchDocument(source.kind, source.key, text));
|
|
19732
|
+
} catch (error) {
|
|
19733
|
+
return {
|
|
19734
|
+
content: [{ type: "text", text: `Failed to rebuild the search index: ${String(error)}` }],
|
|
19735
|
+
isError: true
|
|
19736
|
+
};
|
|
19737
|
+
} finally {
|
|
19738
|
+
index.close();
|
|
19739
|
+
}
|
|
19740
|
+
const lines = [
|
|
19741
|
+
`Search index: ${synced.added} added, ${synced.updated} updated, ${synced.removed} removed (${documents.length} documents).`
|
|
19742
|
+
];
|
|
19743
|
+
for (const kind of ["article", "summary"]) {
|
|
19744
|
+
const rows = new Set(parseWikiIndex(wikiIndexPath(kind)).filter((candidate) => candidate.kind === kind).map((candidate) => candidate.key));
|
|
19745
|
+
const keys = documents.filter((source) => source.kind === kind).map((source) => source.key);
|
|
19746
|
+
const unindexed = keys.filter((key) => !rows.has(key));
|
|
19747
|
+
const tombstones = [...rows].filter((key) => !keys.includes(key));
|
|
19748
|
+
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(", ")}` : ""));
|
|
19749
|
+
}
|
|
19750
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
19751
|
+
`) }] };
|
|
19752
|
+
}
|
|
19753
|
+
var SEARCH_CACHE_SKIPPED_NOTE = "Body-search cache was not updated; catalog retrieval still works. Run wiki_reindex to refill it.";
|
|
19754
|
+
function indexRowOrPartialWrite(label, options) {
|
|
19755
|
+
try {
|
|
19756
|
+
return upsertIndexRow(options);
|
|
19757
|
+
} catch (error) {
|
|
19758
|
+
return {
|
|
19759
|
+
content: [
|
|
19760
|
+
{
|
|
19761
|
+
type: "text",
|
|
19762
|
+
text: `Wrote the ${label} document but failed to index it (${String(error)}). Retry the same wiki_write call: both writes are idempotent.`
|
|
19763
|
+
}
|
|
19764
|
+
],
|
|
19765
|
+
isError: true
|
|
19766
|
+
};
|
|
19767
|
+
}
|
|
19768
|
+
}
|
|
19769
|
+
function upsertIndexRow(options) {
|
|
19770
|
+
const { kind, slug, description, date, section } = options;
|
|
19771
|
+
const indexPath = wikiIndexPath(kind);
|
|
18748
19772
|
ensureDir(dirname2(indexPath));
|
|
18749
19773
|
const releaseLock = acquireFileLock(indexPath);
|
|
18750
19774
|
try {
|
|
18751
19775
|
let index;
|
|
18752
19776
|
try {
|
|
18753
|
-
index =
|
|
19777
|
+
index = readFileSync3(indexPath, "utf-8");
|
|
18754
19778
|
} catch {
|
|
18755
19779
|
index = "";
|
|
18756
19780
|
}
|
|
18757
19781
|
const lines = index.split(`
|
|
18758
19782
|
`);
|
|
18759
|
-
const
|
|
18760
|
-
|
|
18761
|
-
case "summary":
|
|
18762
|
-
return `- [[summaries/${slug}]] ${desc} (${dateStr})`;
|
|
18763
|
-
case "topic":
|
|
18764
|
-
return `- [[topic/${slug}]] ${desc} (${dateStr})`;
|
|
18765
|
-
case "skill":
|
|
18766
|
-
return `- [[skills/${slug}]] ${desc} (${dateStr})`;
|
|
18767
|
-
default:
|
|
18768
|
-
return `- [[articles/${slug}]] ${desc} (${dateStr})`;
|
|
18769
|
-
}
|
|
18770
|
-
})();
|
|
18771
|
-
const target = (() => {
|
|
18772
|
-
switch (kind) {
|
|
18773
|
-
case "summary":
|
|
18774
|
-
return `summaries/${slug}`;
|
|
18775
|
-
case "topic":
|
|
18776
|
-
return `topic/${slug}`;
|
|
18777
|
-
case "skill":
|
|
18778
|
-
return `skills/${slug}`;
|
|
18779
|
-
default:
|
|
18780
|
-
return `articles/${slug}`;
|
|
18781
|
-
}
|
|
18782
|
-
})();
|
|
19783
|
+
const target = `${WIKI_INDEX_NAMESPACES[kind]}/${slug}`;
|
|
19784
|
+
const link = `- [[${target}]] ${description} (${date})`;
|
|
18783
19785
|
const slugPattern = `[[${target}]]`;
|
|
18784
19786
|
let replaced = false;
|
|
18785
19787
|
for (let i = lines.length - 1;i >= 0; i--) {
|
|
@@ -18817,15 +19819,67 @@ function indexUpsert(args) {
|
|
|
18817
19819
|
}
|
|
18818
19820
|
atomicWriteFile(indexPath, lines.join(`
|
|
18819
19821
|
`));
|
|
18820
|
-
return
|
|
19822
|
+
return link;
|
|
18821
19823
|
} finally {
|
|
18822
19824
|
releaseLock();
|
|
18823
19825
|
}
|
|
18824
19826
|
}
|
|
19827
|
+
function wikiDocumentExists(kind, slug) {
|
|
19828
|
+
const root = kind === "article" ? runtime().articlesDir : kind === "summary" ? runtime().summariesDir : kind === "topic" ? runtime().topicsDir : runtime().skillsDir;
|
|
19829
|
+
const filePath = resolve2(root, `${slug}.md`);
|
|
19830
|
+
if (relative(root, filePath).startsWith(".."))
|
|
19831
|
+
return false;
|
|
19832
|
+
try {
|
|
19833
|
+
return statSync(filePath).isFile();
|
|
19834
|
+
} catch {
|
|
19835
|
+
return false;
|
|
19836
|
+
}
|
|
19837
|
+
}
|
|
19838
|
+
function indexUpsert(args) {
|
|
19839
|
+
const rawSlug = safeExt(args, ["slug", "id"], "");
|
|
19840
|
+
const rawDescription = safeExt(args, ["description", "desc", "text"], "");
|
|
19841
|
+
const rawKind = safeExt(args, ["kind", "type"], "article");
|
|
19842
|
+
const section = safeExt(args, ["section", "category"], undefined);
|
|
19843
|
+
const date = safeExt(args, ["date", "created"], undefined);
|
|
19844
|
+
if (!(rawKind in WIKI_INDEX_FILENAMES)) {
|
|
19845
|
+
return {
|
|
19846
|
+
content: [{ type: "text", text: `Unsupported kind: ${rawKind}` }],
|
|
19847
|
+
isError: true
|
|
19848
|
+
};
|
|
19849
|
+
}
|
|
19850
|
+
const kind = rawKind;
|
|
19851
|
+
const slugResult = normalizeWikiSlug(rawSlug);
|
|
19852
|
+
if ("error" in slugResult) {
|
|
19853
|
+
return { content: [{ type: "text", text: slugResult.error }], isError: true };
|
|
19854
|
+
}
|
|
19855
|
+
const descriptionResult = normalizeIndexDescription(rawDescription);
|
|
19856
|
+
if ("error" in descriptionResult) {
|
|
19857
|
+
return { content: [{ type: "text", text: descriptionResult.error }], isError: true };
|
|
19858
|
+
}
|
|
19859
|
+
if (kind !== "topic" && !wikiDocumentExists(kind, slugResult.slug)) {
|
|
19860
|
+
return {
|
|
19861
|
+
content: [
|
|
19862
|
+
{
|
|
19863
|
+
type: "text",
|
|
19864
|
+
text: `No ${kind} document named ${slugResult.slug}. Use wiki_write to create the document and its catalog row together.`
|
|
19865
|
+
}
|
|
19866
|
+
],
|
|
19867
|
+
isError: true
|
|
19868
|
+
};
|
|
19869
|
+
}
|
|
19870
|
+
const link = upsertIndexRow({
|
|
19871
|
+
kind,
|
|
19872
|
+
slug: slugResult.slug,
|
|
19873
|
+
description: descriptionResult.description,
|
|
19874
|
+
date: date ?? new Date().toISOString().slice(0, 10),
|
|
19875
|
+
section
|
|
19876
|
+
});
|
|
19877
|
+
return { content: [{ type: "text", text: `Index updated: ${link}` }] };
|
|
19878
|
+
}
|
|
18825
19879
|
var WIKI_TOOLS = [
|
|
18826
19880
|
{
|
|
18827
19881
|
name: "wiki_query",
|
|
18828
|
-
description: "Search
|
|
19882
|
+
description: "Search topic, article, or summary indexes and return relevant candidates.",
|
|
18829
19883
|
inputSchema: {
|
|
18830
19884
|
type: "object",
|
|
18831
19885
|
properties: {
|
|
@@ -18836,11 +19890,38 @@ var WIKI_TOOLS = [
|
|
|
18836
19890
|
topic: {
|
|
18837
19891
|
type: "string",
|
|
18838
19892
|
description: "Optional topic name to narrow the search"
|
|
19893
|
+
},
|
|
19894
|
+
kind: {
|
|
19895
|
+
type: "string",
|
|
19896
|
+
enum: ["all", "topic", "article", "summary"],
|
|
19897
|
+
description: "Document kind to search; defaults to all"
|
|
19898
|
+
},
|
|
19899
|
+
limit: {
|
|
19900
|
+
type: "number",
|
|
19901
|
+
minimum: 1,
|
|
19902
|
+
maximum: 20,
|
|
19903
|
+
description: "Maximum number of candidates; defaults to 8"
|
|
18839
19904
|
}
|
|
18840
19905
|
},
|
|
18841
19906
|
required: ["question"]
|
|
18842
19907
|
}
|
|
18843
19908
|
},
|
|
19909
|
+
{
|
|
19910
|
+
name: "wiki_read",
|
|
19911
|
+
description: "Read one topic, article, or summary returned by wiki_query. For a clearly matching continuing persona, set adopt=true when reading a topic.",
|
|
19912
|
+
inputSchema: {
|
|
19913
|
+
type: "object",
|
|
19914
|
+
properties: {
|
|
19915
|
+
kind: { type: "string", enum: ["topic", "article", "summary"] },
|
|
19916
|
+
key: { type: "string", description: "Canonical key returned by wiki_query" },
|
|
19917
|
+
adopt: {
|
|
19918
|
+
type: "boolean",
|
|
19919
|
+
description: "For kind=topic, persist this canonical memory key for the current room"
|
|
19920
|
+
}
|
|
19921
|
+
},
|
|
19922
|
+
required: ["kind", "key"]
|
|
19923
|
+
}
|
|
19924
|
+
},
|
|
18844
19925
|
{
|
|
18845
19926
|
name: "wiki_topic_brief",
|
|
18846
19927
|
description: "Get the lightweight topic brief for a specific topic.",
|
|
@@ -18876,35 +19957,46 @@ var WIKI_TOOLS = [
|
|
|
18876
19957
|
}
|
|
18877
19958
|
},
|
|
18878
19959
|
{
|
|
18879
|
-
name: "
|
|
18880
|
-
description: "
|
|
19960
|
+
name: "wiki_write",
|
|
19961
|
+
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.",
|
|
18881
19962
|
inputSchema: {
|
|
18882
19963
|
type: "object",
|
|
18883
19964
|
properties: {
|
|
18884
|
-
|
|
18885
|
-
|
|
19965
|
+
kind: {
|
|
19966
|
+
type: "string",
|
|
19967
|
+
enum: ["summary", "article", "topic"],
|
|
19968
|
+
description: "Which document to write"
|
|
19969
|
+
},
|
|
19970
|
+
content: { type: "string", description: "Document body in markdown" },
|
|
19971
|
+
description: {
|
|
19972
|
+
type: "string",
|
|
19973
|
+
description: "Single-line catalog description; required so the index stays useful"
|
|
19974
|
+
},
|
|
19975
|
+
topic: {
|
|
19976
|
+
type: "string",
|
|
19977
|
+
description: "Topic name; required for kind=summary and kind=topic"
|
|
19978
|
+
},
|
|
19979
|
+
slug: {
|
|
19980
|
+
type: "string",
|
|
19981
|
+
description: "Article slug in kebab-case; required for kind=article"
|
|
19982
|
+
},
|
|
19983
|
+
section: {
|
|
19984
|
+
type: "string",
|
|
19985
|
+
description: "H2 catalog section; required for kind=article"
|
|
19986
|
+
},
|
|
19987
|
+
date: { type: "string", description: "Override entry date YYYY-MM-DD (default: today)" }
|
|
18886
19988
|
},
|
|
18887
|
-
required: ["
|
|
19989
|
+
required: ["kind", "content", "description"]
|
|
18888
19990
|
}
|
|
18889
19991
|
},
|
|
18890
19992
|
{
|
|
18891
|
-
name: "
|
|
18892
|
-
description: "
|
|
18893
|
-
inputSchema: {
|
|
18894
|
-
type: "object",
|
|
18895
|
-
properties: {
|
|
18896
|
-
topic: { type: "string", description: "Topic name of the session" },
|
|
18897
|
-
content: {
|
|
18898
|
-
type: "string",
|
|
18899
|
-
description: "Full accumulated topic brief in markdown (persona layer + recent work)"
|
|
18900
|
-
}
|
|
18901
|
-
},
|
|
18902
|
-
required: ["topic", "content"]
|
|
18903
|
-
}
|
|
19993
|
+
name: "wiki_reindex",
|
|
19994
|
+
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.",
|
|
19995
|
+
inputSchema: { type: "object", properties: {} }
|
|
18904
19996
|
},
|
|
18905
19997
|
{
|
|
18906
19998
|
name: "index_upsert",
|
|
18907
|
-
description: "
|
|
19999
|
+
description: "Correct the description or section of an entry that already exists. Cannot create an entry \u2014 use wiki_write for that.",
|
|
18908
20000
|
inputSchema: {
|
|
18909
20001
|
type: "object",
|
|
18910
20002
|
properties: {
|
|
@@ -18973,6 +20065,7 @@ function createWikiMcpServer(context, host) {
|
|
|
18973
20065
|
const wikiDir = resolve2(host.wikiRoot);
|
|
18974
20066
|
const current = {
|
|
18975
20067
|
userId: context.userId,
|
|
20068
|
+
currentTopicId: context.currentTopicId ?? context.topicId,
|
|
18976
20069
|
topicId: context.topicId,
|
|
18977
20070
|
surface,
|
|
18978
20071
|
host,
|
|
@@ -19002,12 +20095,13 @@ function createWikiMcpServer(context, host) {
|
|
|
19002
20095
|
const { name, arguments: args } = request.params;
|
|
19003
20096
|
const handlers = {
|
|
19004
20097
|
wiki_query: wikiQuery,
|
|
20098
|
+
wiki_read: wikiRead,
|
|
19005
20099
|
wiki_topic_brief: wikiTopicBrief,
|
|
19006
20100
|
wiki_last_conversation: wikiLastConversation,
|
|
19007
20101
|
skill_query: skillQuery,
|
|
19008
20102
|
skill_save: skillSave,
|
|
19009
|
-
|
|
19010
|
-
|
|
20103
|
+
wiki_write: wikiWrite,
|
|
20104
|
+
wiki_reindex: wikiReindex,
|
|
19011
20105
|
index_upsert: indexUpsert
|
|
19012
20106
|
};
|
|
19013
20107
|
const handler = tools.some((tool) => tool.name === name) ? handlers[name] : undefined;
|
|
@@ -19025,7 +20119,7 @@ init_jsonl();
|
|
|
19025
20119
|
init_mcp_helpers();
|
|
19026
20120
|
await init_codex_registry();
|
|
19027
20121
|
import { spawn as spawn5 } from "child_process";
|
|
19028
|
-
import { existsSync as
|
|
20122
|
+
import { existsSync as existsSync10, readdirSync as readdirSync3 } from "fs";
|
|
19029
20123
|
import { join as join10 } from "path";
|
|
19030
20124
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
19031
20125
|
import { z } from "zod";
|
|
@@ -19280,7 +20374,7 @@ ${results.every((result) => result.ok) ? "All agents healthy" : "Some agents fai
|
|
|
19280
20374
|
if (!context.userId)
|
|
19281
20375
|
return mcpOk("\uC2E4\uD589 \uC911\uC778 \uCFFC\uB9AC \uC870\uD68C \uBD88\uAC00 (user context \uC5C6\uC74C)");
|
|
19282
20376
|
const stateDir = join10(USERS_LOG_DIR, context.userId, "active-queries");
|
|
19283
|
-
if (!
|
|
20377
|
+
if (!existsSync10(stateDir))
|
|
19284
20378
|
return mcpOk("\uC2E4\uD589 \uC911\uC778 \uCFFC\uB9AC \uC5C6\uC74C");
|
|
19285
20379
|
let files;
|
|
19286
20380
|
try {
|
|
@@ -19317,7 +20411,7 @@ ${lines.join(`
|
|
|
19317
20411
|
}
|
|
19318
20412
|
// ../../packages/core/src/mcp/factories/compaction-log.ts
|
|
19319
20413
|
init_mcp_helpers();
|
|
19320
|
-
import { readFileSync as
|
|
20414
|
+
import { readFileSync as readFileSync9, realpathSync as realpathSync3 } from "fs";
|
|
19321
20415
|
import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
19322
20416
|
import { z as z2 } from "zod";
|
|
19323
20417
|
var DEFAULT_MAX_CALLS = 12;
|
|
@@ -19350,7 +20444,7 @@ function createCompactionLogMcpServer(context) {
|
|
|
19350
20444
|
if (!context.filePath)
|
|
19351
20445
|
throw new Error("compaction-log MCP requires filePath");
|
|
19352
20446
|
const filePath = realpathSync3(context.filePath);
|
|
19353
|
-
const lines =
|
|
20447
|
+
const lines = readFileSync9(filePath, "utf8").split(`
|
|
19354
20448
|
`);
|
|
19355
20449
|
const maxCalls = positiveInt(context.maxCalls, DEFAULT_MAX_CALLS);
|
|
19356
20450
|
const maxTotalBytes = positiveInt(context.maxTotalBytes, DEFAULT_MAX_TOTAL_BYTES);
|
|
@@ -19943,4 +21037,4 @@ export {
|
|
|
19943
21037
|
createAgentHealthMcpServer
|
|
19944
21038
|
};
|
|
19945
21039
|
|
|
19946
|
-
//# debugId=
|
|
21040
|
+
//# debugId=1EA78260283E3CD164756E2164756E21
|