negotium 0.2.16 → 0.2.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-helpers.js +83 -38
- package/dist/agent-helpers.js.map +13 -13
- package/dist/background-bash.js.map +1 -1
- package/dist/browser-runtime.js.map +1 -1
- package/dist/{chunk-5cpdbbeh.js → chunk-fc58cqry.js} +3 -1
- package/dist/{chunk-5cpdbbeh.js.map → chunk-fc58cqry.js.map} +4 -4
- package/dist/hosted-agent.js +5 -2
- package/dist/hosted-agent.js.map +6 -6
- package/dist/main.js +1228 -486
- package/dist/main.js.map +23 -22
- package/dist/mcp-factories.js +701 -97
- package/dist/mcp-factories.js.map +14 -14
- package/dist/prompts.js +8 -8
- package/dist/prompts.js.map +4 -4
- package/dist/query-runtime.js.map +1 -1
- package/dist/registry.js +3 -3
- package/dist/registry.js.map +2 -2
- package/dist/rollout.js +1 -1
- package/dist/runtime/src/agents/compaction-support.ts +1 -1
- package/dist/runtime/src/agents/idle-archiver.ts +3 -2
- package/dist/runtime/src/agents/maestro-provider.ts +3 -0
- package/dist/runtime/src/index.ts +1 -0
- package/dist/runtime/src/mcp/canonical-proxy-server.ts +18 -1
- package/dist/runtime/src/mcp/wiki-server.ts +801 -59
- package/dist/runtime/src/platform/mcp-config.ts +1 -0
- package/dist/runtime/src/prompts/agents/wiki-archiver.md +16 -6
- package/dist/runtime/src/prompts/builders.ts +17 -34
- package/dist/runtime/src/runtime/turn-event-stream.ts +26 -0
- package/dist/runtime/src/runtime/turn-runner.ts +34 -26
- 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/types.ts +6 -0
- package/dist/runtime/src/version.ts +1 -1
- package/dist/runtime-helpers.js.map +1 -1
- package/dist/storage.js +12 -6
- package/dist/storage.js.map +4 -4
- package/dist/types/packages/core/src/agents/compaction-support.d.ts +1 -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/runtime/turn-runner.d.ts +4 -0
- 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/types.d.ts +6 -0
- package/dist/types/packages/core/src/version.d.ts +1 -1
- package/dist/vault.js.map +1 -1
- package/package.json +2 -2
package/dist/main.js
CHANGED
|
@@ -1010,6 +1010,70 @@ var init_codex_tree_kill = __esm(() => {
|
|
|
1010
1010
|
killOwnedCodexTreesForShutdown = manager.killOwnedTreesForShutdown;
|
|
1011
1011
|
});
|
|
1012
1012
|
|
|
1013
|
+
// ../../packages/core/src/agents/compaction-support.ts
|
|
1014
|
+
function isCjkCodePoint(code) {
|
|
1015
|
+
return code >= 12288 && code <= 19903 || code >= 19968 && code <= 40959 || code >= 4352 && code <= 4607 || code >= 44032 && code <= 55203 || code >= 63744 && code <= 64255 || code >= 65280 && code <= 65519 || code >= 131072 && code <= 262143;
|
|
1016
|
+
}
|
|
1017
|
+
function estimateTextTokens(text) {
|
|
1018
|
+
let cjkChars = 0;
|
|
1019
|
+
let cjkUnits = 0;
|
|
1020
|
+
for (let index = 0;index < text.length; index++) {
|
|
1021
|
+
const code = text.codePointAt(index) ?? 0;
|
|
1022
|
+
const wide = code > 65535;
|
|
1023
|
+
if (wide)
|
|
1024
|
+
index++;
|
|
1025
|
+
if (isCjkCodePoint(code)) {
|
|
1026
|
+
cjkChars++;
|
|
1027
|
+
cjkUnits += wide ? 2 : 1;
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
const latinUnits = text.length - cjkUnits;
|
|
1031
|
+
return Math.ceil(cjkChars / CHARS_PER_CJK_TOKEN + latinUnits / CHARS_PER_TEXT_TOKEN);
|
|
1032
|
+
}
|
|
1033
|
+
function estimateConversationTokens(messages) {
|
|
1034
|
+
return messages.reduce((sum, message) => sum + 4 + estimateTextTokens(message.content), 0);
|
|
1035
|
+
}
|
|
1036
|
+
var ACTIVE_TASK_TEMPLATE = `You are compressing a long agent conversation so the main agent can continue
|
|
1037
|
+
without losing context. Produce a single concise summary using EXACTLY these
|
|
1038
|
+
section headers, in this order:
|
|
1039
|
+
|
|
1040
|
+
## Active Task
|
|
1041
|
+
One sentence: what is the agent currently working on?
|
|
1042
|
+
|
|
1043
|
+
## Goal
|
|
1044
|
+
One or two sentences: the user's overall objective in this session.
|
|
1045
|
+
|
|
1046
|
+
## Constraints
|
|
1047
|
+
Bulleted list of durable requirements, user preferences, technical limits, or
|
|
1048
|
+
process rules that should continue to govern the work. Skip if none.
|
|
1049
|
+
|
|
1050
|
+
## Key Decisions
|
|
1051
|
+
Bulleted list of decisions already made that should not be reopened unless the
|
|
1052
|
+
user asks. Include the rationale when it is short and important. Skip if none.
|
|
1053
|
+
|
|
1054
|
+
## Pending
|
|
1055
|
+
Bulleted list of unresolved items, decisions to make, or work explicitly
|
|
1056
|
+
deferred. Use "(blocked: <reason>)" when applicable.
|
|
1057
|
+
|
|
1058
|
+
## Next Steps
|
|
1059
|
+
Bulleted list of the concrete next actions the main agent should take after
|
|
1060
|
+
compaction, in likely execution order. Skip if none.
|
|
1061
|
+
|
|
1062
|
+
## Files
|
|
1063
|
+
Bulleted list of \`absolute/paths\` touched or referenced (read, written,
|
|
1064
|
+
inspected). Skip if none.
|
|
1065
|
+
|
|
1066
|
+
## Recent context
|
|
1067
|
+
3-5 bullets capturing the most recent tool calls + their salient outputs.
|
|
1068
|
+
Prefer specifics (paths, line numbers, exit codes, key values) over generic
|
|
1069
|
+
recaps. Skip details that have no bearing on the next step.
|
|
1070
|
+
|
|
1071
|
+
RULES:
|
|
1072
|
+
- Output ONLY the eight sections above, with no preamble or postscript.
|
|
1073
|
+
- Do NOT echo the user's words verbatim - paraphrase tightly.
|
|
1074
|
+
- Do NOT invent file paths or facts not present in the transcript.
|
|
1075
|
+
- Keep the entire summary under 1500 words.`, CHARS_PER_TEXT_TOKEN = 3.5, CHARS_PER_CJK_TOKEN = 0.9;
|
|
1076
|
+
|
|
1013
1077
|
// ../../packages/core/src/runtime/user-turn-envelope.ts
|
|
1014
1078
|
function legacyUserTurnEnvelope(prompt, attachments) {
|
|
1015
1079
|
return attachments?.length ? { prompt, attachments } : { prompt };
|
|
@@ -1572,7 +1636,7 @@ var exports_version = {};
|
|
|
1572
1636
|
__export(exports_version, {
|
|
1573
1637
|
NEGOTIUM_VERSION: () => NEGOTIUM_VERSION
|
|
1574
1638
|
});
|
|
1575
|
-
var NEGOTIUM_VERSION = "0.2.
|
|
1639
|
+
var NEGOTIUM_VERSION = "0.2.18";
|
|
1576
1640
|
|
|
1577
1641
|
// ../../packages/core/src/agents/codex-native-multi-agent.ts
|
|
1578
1642
|
import { spawn } from "child_process";
|
|
@@ -2879,6 +2943,8 @@ var init_mcp_config = __esm(() => {
|
|
|
2879
2943
|
return env ? buildStdioMcpServer(agent, CANONICAL_MCP_PROXY_SERVER, ["--surface=wiki"], env) : null;
|
|
2880
2944
|
}
|
|
2881
2945
|
const args = [`--user-id=${userId}`];
|
|
2946
|
+
if (topicId)
|
|
2947
|
+
args.push(`--current-topic-id=${topicId}`);
|
|
2882
2948
|
const resolvedWikiTopicId = wikiTopicId ?? topicId ?? (session !== "dm" ? session : undefined);
|
|
2883
2949
|
if (resolvedWikiTopicId)
|
|
2884
2950
|
args.push(`--topic-id=${resolvedWikiTopicId}`);
|
|
@@ -6427,6 +6493,7 @@ function maestroProvider(opts) {
|
|
|
6427
6493
|
...opts,
|
|
6428
6494
|
enableToolSearch: !opts.toolPolicy && opts.enableToolSearch !== false,
|
|
6429
6495
|
toolResultTruncation: buildMaestroToolResultTruncation(opts),
|
|
6496
|
+
ephemeralSystemPrompt: opts.ephemeralSystemPrompt,
|
|
6430
6497
|
apiKeyOverrides: resolveMaestroApiKeyOverrides(userId),
|
|
6431
6498
|
agent: "maestro",
|
|
6432
6499
|
disallowedTools: buildMaestroDisallowedTools(callerDisallowedTools, opts.toolPolicy),
|
|
@@ -7567,6 +7634,7 @@ function initializeApiTopicsSchema() {
|
|
|
7567
7634
|
last_message_at TEXT,
|
|
7568
7635
|
parent_topic_id TEXT,
|
|
7569
7636
|
memory_topic_id TEXT,
|
|
7637
|
+
memory_key TEXT,
|
|
7570
7638
|
is_fork INTEGER NOT NULL DEFAULT 0 CHECK (is_fork IN (0,1)),
|
|
7571
7639
|
is_subagent INTEGER NOT NULL DEFAULT 0 CHECK (is_subagent IN (0,1)),
|
|
7572
7640
|
visibility TEXT NOT NULL DEFAULT 'visible' CHECK (visibility IN ('visible','hidden')),
|
|
@@ -7616,8 +7684,8 @@ function initializeApiTopicsSchema() {
|
|
|
7616
7684
|
const legacyBaseEffort = row.base_effort ?? row.default_effort;
|
|
7617
7685
|
db.query(`INSERT INTO api_topics_next
|
|
7618
7686
|
(id,title,kind,description,agent,base_model,base_effort,response_policy,
|
|
7619
|
-
created_at,last_message_at,parent_topic_id,memory_topic_id,is_fork,is_subagent,visibility,access_mode,session_id)
|
|
7620
|
-
VALUES (
|
|
7687
|
+
created_at,last_message_at,parent_topic_id,memory_topic_id,memory_key,is_fork,is_subagent,visibility,access_mode,session_id)
|
|
7688
|
+
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`).run(String(row.id), String(row.title), normalized.kind, typeof row.description === "string" ? row.description : null, normalized.agent ?? null, typeof legacyBaseModel === "string" ? legacyBaseModel : null, typeof legacyBaseEffort === "string" ? legacyBaseEffort : null, normalized.aiMode, String(row.created_at), typeof row.last_message_at === "string" ? row.last_message_at : null, typeof row.parent_topic_id === "string" ? row.parent_topic_id : null, typeof row.memory_topic_id === "string" ? row.memory_topic_id : null, typeof row.memory_key === "string" ? row.memory_key : null, Number(row.is_fork ?? 0) !== 0 ? 1 : 0, Number(row.is_subagent ?? 0) !== 0 ? 1 : 0, row.visibility === "hidden" ? "hidden" : "visible", row.access_mode === "shared" ? "shared" : "private", typeof row.session_id === "string" ? row.session_id : null);
|
|
7621
7689
|
}
|
|
7622
7690
|
db.exec("DROP TABLE IF EXISTS topic_members");
|
|
7623
7691
|
db.exec("DROP TABLE api_topics");
|
|
@@ -7686,6 +7754,9 @@ function initializeApiTopicsSchema() {
|
|
|
7686
7754
|
if (!tableColumns2("api_topics").has("memory_topic_id")) {
|
|
7687
7755
|
db.exec("ALTER TABLE api_topics ADD COLUMN memory_topic_id TEXT");
|
|
7688
7756
|
}
|
|
7757
|
+
if (!tableColumns2("api_topics").has("memory_key")) {
|
|
7758
|
+
db.exec("ALTER TABLE api_topics ADD COLUMN memory_key TEXT");
|
|
7759
|
+
}
|
|
7689
7760
|
db.exec(`
|
|
7690
7761
|
UPDATE api_topics
|
|
7691
7762
|
SET browser_profile_owner = (
|
|
@@ -7746,6 +7817,7 @@ function rowToDto2(r, participants = getTopicParticipants(r.id), tellTargets) {
|
|
|
7746
7817
|
lastMessageAt: r.last_message_at ?? new Date().toISOString(),
|
|
7747
7818
|
parentTopicId: r.parent_topic_id ?? undefined,
|
|
7748
7819
|
memoryTopicId: r.memory_topic_id ?? undefined,
|
|
7820
|
+
memoryKey: r.memory_key ?? undefined,
|
|
7749
7821
|
isFork: r.is_fork !== 0,
|
|
7750
7822
|
...r.is_subagent !== 0 ? { isSubagent: true } : {},
|
|
7751
7823
|
...r.is_subagent !== 0 ? {
|
|
@@ -7840,9 +7912,9 @@ function upsertTopic(t) {
|
|
|
7840
7912
|
db.transaction(() => {
|
|
7841
7913
|
db.query(`INSERT INTO api_topics
|
|
7842
7914
|
(id,title,kind,description,agent,base_model,base_effort,response_policy,
|
|
7843
|
-
created_at,last_message_at,parent_topic_id,memory_topic_id,is_fork,is_subagent,visibility,access_mode,
|
|
7915
|
+
created_at,last_message_at,parent_topic_id,memory_topic_id,memory_key,is_fork,is_subagent,visibility,access_mode,
|
|
7844
7916
|
subagent_report_mode)
|
|
7845
|
-
VALUES (
|
|
7917
|
+
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
|
7846
7918
|
ON CONFLICT(id) DO UPDATE SET
|
|
7847
7919
|
title = excluded.title,
|
|
7848
7920
|
kind = excluded.kind,
|
|
@@ -7855,11 +7927,12 @@ function upsertTopic(t) {
|
|
|
7855
7927
|
last_message_at = excluded.last_message_at,
|
|
7856
7928
|
parent_topic_id = excluded.parent_topic_id,
|
|
7857
7929
|
memory_topic_id = excluded.memory_topic_id,
|
|
7930
|
+
memory_key = excluded.memory_key,
|
|
7858
7931
|
is_fork = excluded.is_fork,
|
|
7859
7932
|
is_subagent = excluded.is_subagent,
|
|
7860
7933
|
visibility = excluded.visibility,
|
|
7861
7934
|
access_mode = excluded.access_mode,
|
|
7862
|
-
subagent_report_mode = excluded.subagent_report_mode`).run(t.id, t.title, normalized.kind, t.description ?? null, normalized.agent ?? null, t.defaultModel ?? null, t.defaultEffort ?? null, normalized.aiMode, t.createdAt, t.lastMessageAt ?? null, t.parentTopicId ?? null, t.memoryTopicId ?? null, t.isFork ? 1 : 0, t.isSubagent ? 1 : 0, normalizeTopicVisibility(t.visibility), normalizeTopicAccessMode(t.accessMode), t.subagentReportMode ?? "auto");
|
|
7935
|
+
subagent_report_mode = excluded.subagent_report_mode`).run(t.id, t.title, normalized.kind, t.description ?? null, normalized.agent ?? null, t.defaultModel ?? null, t.defaultEffort ?? null, normalized.aiMode, t.createdAt, t.lastMessageAt ?? null, t.parentTopicId ?? null, t.memoryTopicId ?? null, t.memoryKey ?? null, t.isFork ? 1 : 0, t.isSubagent ? 1 : 0, normalizeTopicVisibility(t.visibility), normalizeTopicAccessMode(t.accessMode), t.subagentReportMode ?? "auto");
|
|
7863
7936
|
db.query("DELETE FROM topic_members WHERE topic_id = ?").run(t.id);
|
|
7864
7937
|
for (const participant of t.participants) {
|
|
7865
7938
|
db.query("INSERT INTO topic_members (topic_id,user_id,role) VALUES (?,?,?)").run(t.id, participant.userId, participant.role);
|
|
@@ -11321,70 +11394,6 @@ var init_personal_general = __esm(async () => {
|
|
|
11321
11394
|
]);
|
|
11322
11395
|
});
|
|
11323
11396
|
|
|
11324
|
-
// ../../packages/core/src/agents/compaction-support.ts
|
|
11325
|
-
function isCjkCodePoint(code) {
|
|
11326
|
-
return code >= 12288 && code <= 19903 || code >= 19968 && code <= 40959 || code >= 4352 && code <= 4607 || code >= 44032 && code <= 55203 || code >= 63744 && code <= 64255 || code >= 65280 && code <= 65519 || code >= 131072 && code <= 262143;
|
|
11327
|
-
}
|
|
11328
|
-
function estimateTextTokens(text) {
|
|
11329
|
-
let cjkChars = 0;
|
|
11330
|
-
let cjkUnits = 0;
|
|
11331
|
-
for (let index = 0;index < text.length; index++) {
|
|
11332
|
-
const code = text.codePointAt(index) ?? 0;
|
|
11333
|
-
const wide = code > 65535;
|
|
11334
|
-
if (wide)
|
|
11335
|
-
index++;
|
|
11336
|
-
if (isCjkCodePoint(code)) {
|
|
11337
|
-
cjkChars++;
|
|
11338
|
-
cjkUnits += wide ? 2 : 1;
|
|
11339
|
-
}
|
|
11340
|
-
}
|
|
11341
|
-
const latinUnits = text.length - cjkUnits;
|
|
11342
|
-
return Math.ceil(cjkChars / CHARS_PER_CJK_TOKEN + latinUnits / CHARS_PER_TEXT_TOKEN);
|
|
11343
|
-
}
|
|
11344
|
-
function estimateConversationTokens(messages) {
|
|
11345
|
-
return messages.reduce((sum, message) => sum + 4 + estimateTextTokens(message.content), 0);
|
|
11346
|
-
}
|
|
11347
|
-
var ACTIVE_TASK_TEMPLATE = `You are compressing a long agent conversation so the main agent can continue
|
|
11348
|
-
without losing context. Produce a single concise summary using EXACTLY these
|
|
11349
|
-
section headers, in this order:
|
|
11350
|
-
|
|
11351
|
-
## Active Task
|
|
11352
|
-
One sentence: what is the agent currently working on?
|
|
11353
|
-
|
|
11354
|
-
## Goal
|
|
11355
|
-
One or two sentences: the user's overall objective in this session.
|
|
11356
|
-
|
|
11357
|
-
## Constraints
|
|
11358
|
-
Bulleted list of durable requirements, user preferences, technical limits, or
|
|
11359
|
-
process rules that should continue to govern the work. Skip if none.
|
|
11360
|
-
|
|
11361
|
-
## Key Decisions
|
|
11362
|
-
Bulleted list of decisions already made that should not be reopened unless the
|
|
11363
|
-
user asks. Include the rationale when it is short and important. Skip if none.
|
|
11364
|
-
|
|
11365
|
-
## Pending
|
|
11366
|
-
Bulleted list of unresolved items, decisions to make, or work explicitly
|
|
11367
|
-
deferred. Use "(blocked: <reason>)" when applicable.
|
|
11368
|
-
|
|
11369
|
-
## Next Steps
|
|
11370
|
-
Bulleted list of the concrete next actions the main agent should take after
|
|
11371
|
-
compaction, in likely execution order. Skip if none.
|
|
11372
|
-
|
|
11373
|
-
## Files
|
|
11374
|
-
Bulleted list of \`absolute/paths\` touched or referenced (read, written,
|
|
11375
|
-
inspected). Skip if none.
|
|
11376
|
-
|
|
11377
|
-
## Recent context
|
|
11378
|
-
3-5 bullets capturing the most recent tool calls + their salient outputs.
|
|
11379
|
-
Prefer specifics (paths, line numbers, exit codes, key values) over generic
|
|
11380
|
-
recaps. Skip details that have no bearing on the next step.
|
|
11381
|
-
|
|
11382
|
-
RULES:
|
|
11383
|
-
- Output ONLY the eight sections above, with no preamble or postscript.
|
|
11384
|
-
- Do NOT echo the user's words verbatim - paraphrase tightly.
|
|
11385
|
-
- Do NOT invent file paths or facts not present in the transcript.
|
|
11386
|
-
- Keep the entire summary under 1500 words.`, CHARS_PER_TEXT_TOKEN = 3.5, CHARS_PER_CJK_TOKEN = 0.9;
|
|
11387
|
-
|
|
11388
11397
|
// ../../packages/core/src/prompts/builders.ts
|
|
11389
11398
|
import { readFileSync as readFileSync13 } from "fs";
|
|
11390
11399
|
import { resolve as resolve13 } from "path";
|
|
@@ -11707,14 +11716,14 @@ function buildMemoryPromptSection(opts) {
|
|
|
11707
11716
|
const parts = [`
|
|
11708
11717
|
|
|
11709
11718
|
## Memory`];
|
|
11710
|
-
|
|
11711
|
-
|
|
11712
|
-
if (opts.
|
|
11713
|
-
parts.push(`
|
|
11714
|
-
}
|
|
11715
|
-
|
|
11716
|
-
|
|
11717
|
-
parts.push("
|
|
11719
|
+
if (opts.isManager) {
|
|
11720
|
+
parts.push("Use `wiki_query` for past workspace decisions or cross-topic context, then `wiki_read` only for the relevant result.");
|
|
11721
|
+
} else if (opts.memoryKey) {
|
|
11722
|
+
parts.push(`This topic uses the canonical memory key \`${opts.memoryKey}\`. At the start of a new session, read it with \`wiki_read(kind: "topic", key: "${opts.memoryKey}")\`.`);
|
|
11723
|
+
} else {
|
|
11724
|
+
parts.push(`At the start of a new session, search topic memories with \`wiki_query(question: "${opts.topicTitle}", kind: "topic", limit: 5)\` and read the most relevant candidate with \`wiki_read\`.`, "If a candidate is clearly the same continuing topic/persona, read it with `adopt: true`; otherwise keep this topic's own name as its memory key. Do not merge weak or ambiguous matches.");
|
|
11725
|
+
}
|
|
11726
|
+
parts.push("Keep the lookup quiet. Mention prior context naturally in one short line only when it helps the user.", 'Use `wiki_query(kind: "article")` for reusable knowledge and `wiki_query(kind: "summary")` for historical session details.');
|
|
11718
11727
|
if (opts.hasArchive) {
|
|
11719
11728
|
parts.push("", "If you need the actual conversation from an earlier session, use the `wiki_last_conversation` MCP tool.");
|
|
11720
11729
|
}
|
|
@@ -12801,11 +12810,12 @@ function archiveActiveTopicForMemory(topicId, userId, options) {
|
|
|
12801
12810
|
}, "topic-memory-archiver: raw snapshot preserved below exchange threshold");
|
|
12802
12811
|
return "below-threshold";
|
|
12803
12812
|
}
|
|
12804
|
-
const memoryTopic = getTopicMemoryOrigin(topicId) ?? topic;
|
|
12813
|
+
const memoryTopic = topic.memoryKey ? topic : getTopicMemoryOrigin(topicId) ?? topic;
|
|
12814
|
+
const memoryTitle = memoryTopic.memoryKey?.trim() || memoryTopic.title;
|
|
12805
12815
|
const launched = (options.launchArchiver ?? runArchiverTurn)({
|
|
12806
12816
|
userId,
|
|
12807
12817
|
topicId: memoryTopic.id,
|
|
12808
|
-
topicTitle:
|
|
12818
|
+
topicTitle: memoryTitle,
|
|
12809
12819
|
archivePath: job.archivePath,
|
|
12810
12820
|
...options.rawArchivePaths ? { rawArchivePaths: options.rawArchivePaths } : {},
|
|
12811
12821
|
messageCount: job.messageCount,
|
|
@@ -16181,11 +16191,12 @@ async function deleteTopicCascadeImpl(topic, userId, options, deletingAncestorId
|
|
|
16181
16191
|
}
|
|
16182
16192
|
if (archived && meetsMemoryArchiveExchangeThreshold(archived.exchangeCount, MIN_MEMORY_ARCHIVE_EXCHANGES, topic)) {
|
|
16183
16193
|
try {
|
|
16184
|
-
const memoryTopic = getTopicMemoryOrigin(topicId) ?? topic;
|
|
16194
|
+
const memoryTopic = topic.memoryKey ? topic : getTopicMemoryOrigin(topicId) ?? topic;
|
|
16195
|
+
const memoryTitle = memoryTopic.memoryKey?.trim() || memoryTopic.title;
|
|
16185
16196
|
runArchiverTurn({
|
|
16186
16197
|
userId,
|
|
16187
16198
|
...memoryTopic.id !== topicId && !deletingTopicIds.has(memoryTopic.id) ? { topicId: memoryTopic.id } : {},
|
|
16188
|
-
topicTitle:
|
|
16199
|
+
topicTitle: memoryTitle,
|
|
16189
16200
|
archivePath: archived.path,
|
|
16190
16201
|
...rawArchives.length > 0 ? { rawArchivePaths: rawArchives } : {},
|
|
16191
16202
|
messageCount: archived.messageCount
|
|
@@ -17209,6 +17220,9 @@ async function runTurnEventStream(topicId, topicTitle, queryId, events, control,
|
|
|
17209
17220
|
let pendingSawDelta = false;
|
|
17210
17221
|
let accumulatedText = "";
|
|
17211
17222
|
let pendingText = "";
|
|
17223
|
+
let assistantContextTokens = 0;
|
|
17224
|
+
let toolContextTokens = 0;
|
|
17225
|
+
let lastContextProgressAt = 0;
|
|
17212
17226
|
let lastVisibleMessageId = null;
|
|
17213
17227
|
const visibleMessageIds = [];
|
|
17214
17228
|
let lastTaskPanelText = null;
|
|
@@ -17227,6 +17241,20 @@ async function runTurnEventStream(topicId, topicTitle, queryId, events, control,
|
|
|
17227
17241
|
model
|
|
17228
17242
|
});
|
|
17229
17243
|
};
|
|
17244
|
+
const broadcastContextProgress = (force = false) => {
|
|
17245
|
+
if (silent)
|
|
17246
|
+
return;
|
|
17247
|
+
const now = Date.now();
|
|
17248
|
+
if (!force && lastContextProgressAt > 0 && now - lastContextProgressAt < 250)
|
|
17249
|
+
return;
|
|
17250
|
+
lastContextProgressAt = now;
|
|
17251
|
+
hub.broadcastAiStatus(topicId, {
|
|
17252
|
+
kind: "context_progress",
|
|
17253
|
+
queryId,
|
|
17254
|
+
assistantTokens: assistantContextTokens,
|
|
17255
|
+
toolTokens: toolContextTokens
|
|
17256
|
+
});
|
|
17257
|
+
};
|
|
17230
17258
|
const nextSyntheticToolUseId = () => `tool-${queryId}-${++syntheticToolCounter}`;
|
|
17231
17259
|
const bindToolUseId = (providerToolUseId, fallback) => {
|
|
17232
17260
|
const providerId = normalizeToolUseId(providerToolUseId);
|
|
@@ -17336,6 +17364,8 @@ async function runTurnEventStream(topicId, topicTitle, queryId, events, control,
|
|
|
17336
17364
|
const incoming = event.content;
|
|
17337
17365
|
accumulatedText += incoming;
|
|
17338
17366
|
pendingText += incoming;
|
|
17367
|
+
assistantContextTokens = estimateTextTokens(accumulatedText);
|
|
17368
|
+
broadcastContextProgress();
|
|
17339
17369
|
break;
|
|
17340
17370
|
}
|
|
17341
17371
|
case "text":
|
|
@@ -17343,10 +17373,15 @@ async function runTurnEventStream(topicId, topicTitle, queryId, events, control,
|
|
|
17343
17373
|
const incoming = event.content;
|
|
17344
17374
|
accumulatedText += incoming;
|
|
17345
17375
|
pendingText += incoming;
|
|
17376
|
+
assistantContextTokens = estimateTextTokens(accumulatedText);
|
|
17377
|
+
broadcastContextProgress();
|
|
17346
17378
|
}
|
|
17347
17379
|
break;
|
|
17348
17380
|
case "tool_use":
|
|
17349
17381
|
emitPendingAssistantMessage();
|
|
17382
|
+
toolContextTokens += estimateTextTokens(`${event.name}
|
|
17383
|
+
${JSON.stringify(event.input ?? {})}`);
|
|
17384
|
+
broadcastContextProgress(true);
|
|
17350
17385
|
if (isVisualsShowHtmlTool(event.name)) {
|
|
17351
17386
|
const input = event.input;
|
|
17352
17387
|
if (typeof input.html !== "string" || input.html.trim().length === 0) {
|
|
@@ -17500,6 +17535,8 @@ async function runTurnEventStream(topicId, topicTitle, queryId, events, control,
|
|
|
17500
17535
|
}
|
|
17501
17536
|
break;
|
|
17502
17537
|
case "tool_result":
|
|
17538
|
+
toolContextTokens += estimateTextTokens(event.content);
|
|
17539
|
+
broadcastContextProgress(true);
|
|
17503
17540
|
if (isVisualToolResultHandled(event.toolUseId))
|
|
17504
17541
|
break;
|
|
17505
17542
|
if (!silent) {
|
|
@@ -17900,6 +17937,7 @@ __export(exports_turn_runner, {
|
|
|
17900
17937
|
channelTranscriptSpeaker: () => channelTranscriptSpeaker,
|
|
17901
17938
|
canonicalModelId: () => canonicalModelId,
|
|
17902
17939
|
buildVideoHtml: () => buildVideoHtml,
|
|
17940
|
+
buildTurnReminderQuery: () => buildTurnReminderQuery,
|
|
17903
17941
|
buildMermaidHtml: () => buildMermaidHtml,
|
|
17904
17942
|
buildMentionOnlyChannelPrompt: () => buildMentionOnlyChannelPrompt,
|
|
17905
17943
|
buildImageHtml: () => buildImageHtml,
|
|
@@ -17925,6 +17963,18 @@ function withDefaultPlaywright(configuredMcp, isManager) {
|
|
|
17925
17963
|
enabled.add("background-bash");
|
|
17926
17964
|
return [...enabled];
|
|
17927
17965
|
}
|
|
17966
|
+
function buildTurnReminderQuery(agent, prompt, reminders) {
|
|
17967
|
+
const reminderPrompt = reminders.join(`
|
|
17968
|
+
|
|
17969
|
+
`);
|
|
17970
|
+
if (!reminderPrompt)
|
|
17971
|
+
return { prompt };
|
|
17972
|
+
if (agent === "maestro")
|
|
17973
|
+
return { prompt, ephemeralSystemPrompt: reminderPrompt };
|
|
17974
|
+
return { prompt: `${prompt}
|
|
17975
|
+
|
|
17976
|
+
${reminderPrompt}` };
|
|
17977
|
+
}
|
|
17928
17978
|
function appendSystemMessage(topicId, text2) {
|
|
17929
17979
|
const message = {
|
|
17930
17980
|
id: randomUUID16(),
|
|
@@ -18740,25 +18790,24 @@ function startAiTurn(params) {
|
|
|
18740
18790
|
const isManager = topicRecord?.kind === "manager";
|
|
18741
18791
|
const isMentionOnlyChannel = topic.aiMode === "mention" && !isManager;
|
|
18742
18792
|
let systemPrompt = isManager ? buildManagerSystemPrompt(systemPromptOpts) : isMentionOnlyChannel ? buildChannelSystemPrompt(systemPromptOpts) : buildTopicSystemPrompt(systemPromptOpts);
|
|
18743
|
-
const memoryTopic = isMentionOnlyChannel || isManager ? topic : getTopicMemoryOrigin(topicId) ?? topic;
|
|
18793
|
+
const memoryTopic = isMentionOnlyChannel || isManager || topic.memoryKey ? topic : getTopicMemoryOrigin(topicId) ?? topic;
|
|
18794
|
+
const memoryTitle = memoryTopic.memoryKey?.trim() || memoryTopic.title;
|
|
18744
18795
|
if (!isMentionOnlyChannel) {
|
|
18796
|
+
let hasMemoryArchive = false;
|
|
18745
18797
|
try {
|
|
18746
|
-
const resolvedBrief = resolveTopicBrief(memoryTopic.id,
|
|
18798
|
+
const resolvedBrief = resolveTopicBrief(memoryTopic.id, memoryTitle);
|
|
18747
18799
|
const wikiDir = getSharedWikiDir();
|
|
18748
|
-
const {
|
|
18749
|
-
|
|
18750
|
-
systemPrompt += buildMemoryPromptSection({
|
|
18751
|
-
briefFile,
|
|
18752
|
-
wikiDir,
|
|
18753
|
-
hasFiles: hasBriefFile,
|
|
18754
|
-
latestSummaryFile,
|
|
18755
|
-
hasArchive: Boolean(resolvedBrief?.brief.latestSummaryMd || latestSummaryFile),
|
|
18756
|
-
isManager
|
|
18757
|
-
});
|
|
18758
|
-
}
|
|
18800
|
+
const { latestSummaryFile } = resolveWikiMemoryMirror(wikiDir, memoryTopic.id, memoryTitle);
|
|
18801
|
+
hasMemoryArchive = Boolean(resolvedBrief?.brief.latestSummaryMd || latestSummaryFile);
|
|
18759
18802
|
} catch (err2) {
|
|
18760
18803
|
logger.warn({ topicId, err: err2 }, "ai: failed to inject topic brief");
|
|
18761
18804
|
}
|
|
18805
|
+
systemPrompt += buildMemoryPromptSection({
|
|
18806
|
+
topicTitle: topic.title,
|
|
18807
|
+
...memoryTopic.memoryKey ? { memoryKey: memoryTopic.memoryKey } : {},
|
|
18808
|
+
hasArchive: hasMemoryArchive,
|
|
18809
|
+
isManager
|
|
18810
|
+
});
|
|
18762
18811
|
}
|
|
18763
18812
|
const activeVisual = visualTools ? getActiveVisualForPrompt(topicId, userId) : null;
|
|
18764
18813
|
if (activeVisual) {
|
|
@@ -18872,15 +18921,11 @@ function startAiTurn(params) {
|
|
|
18872
18921
|
if (consumePlaywrightUnavailable(userId, topic.title)) {
|
|
18873
18922
|
turnReminders.push("<system-reminder>Playwright browser tools are UNAVAILABLE this turn. The `mcp__playwright__*` tools have been removed from this turn's catalog because the long-lived browser MCP could not be prepared. Do not attempt to call browser tools. If browser interaction is required, ask the user to retry shortly or use a non-browser alternative.</system-reminder>");
|
|
18874
18923
|
}
|
|
18875
|
-
const
|
|
18876
|
-
|
|
18877
|
-
${turnReminders.join(`
|
|
18878
|
-
|
|
18879
|
-
`)}` : agentPrompt;
|
|
18924
|
+
const reminderQuery = buildTurnReminderQuery(agentKind, agentPrompt, turnReminders);
|
|
18880
18925
|
try {
|
|
18881
18926
|
yield* runAgent({
|
|
18882
18927
|
agent: agentKind,
|
|
18883
|
-
|
|
18928
|
+
...reminderQuery,
|
|
18884
18929
|
attachments: promptAttachments,
|
|
18885
18930
|
cwd: workspaceCwd,
|
|
18886
18931
|
systemPrompt,
|
|
@@ -22461,6 +22506,7 @@ __export(exports_src, {
|
|
|
22461
22506
|
extractFileEvents: () => extractFileEvents,
|
|
22462
22507
|
executeVaultCommand: () => executeVaultCommand,
|
|
22463
22508
|
executeExternalUserTurn: () => executeExternalUserTurn,
|
|
22509
|
+
estimateTextTokens: () => estimateTextTokens,
|
|
22464
22510
|
errorResult: () => errorResult,
|
|
22465
22511
|
errMsg: () => errMsg,
|
|
22466
22512
|
ensurePersonalGeneral: () => ensurePersonalGeneral,
|
|
@@ -24894,7 +24940,7 @@ import {
|
|
|
24894
24940
|
unlinkSync as unlinkSync18,
|
|
24895
24941
|
writeFileSync as writeFileSync17
|
|
24896
24942
|
} from "fs";
|
|
24897
|
-
import { basename as basename7, dirname as dirname16, join as join36, resolve as resolve17 } from "path";
|
|
24943
|
+
import { basename as basename7, dirname as dirname16, join as join36, relative as relative2, resolve as resolve17 } from "path";
|
|
24898
24944
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
24899
24945
|
import { StdioServerTransport as StdioServerTransport2 } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
24900
24946
|
import {
|
|
@@ -25033,9 +25079,243 @@ function topicNameFrom(topicIdStr) {
|
|
|
25033
25079
|
function slugify(topic) {
|
|
25034
25080
|
return wikiSummarySlug(topic);
|
|
25035
25081
|
}
|
|
25082
|
+
function normalizeSearchText(value) {
|
|
25083
|
+
return value.toLowerCase().replaceAll(/[^a-z0-9\uAC00-\uD7A3]+/g, " ").trim().replaceAll(/\s+/g, " ");
|
|
25084
|
+
}
|
|
25085
|
+
function compactSearchText(value) {
|
|
25086
|
+
return normalizeSearchText(value).replaceAll(" ", "");
|
|
25087
|
+
}
|
|
25088
|
+
function trigrams(value) {
|
|
25089
|
+
const compact = compactSearchText(value);
|
|
25090
|
+
if (compact.length < 3)
|
|
25091
|
+
return new Set(compact ? [compact] : []);
|
|
25092
|
+
const result = new Set;
|
|
25093
|
+
for (let i = 0;i <= compact.length - 3; i += 1)
|
|
25094
|
+
result.add(compact.slice(i, i + 3));
|
|
25095
|
+
return result;
|
|
25096
|
+
}
|
|
25097
|
+
function fuzzySearchScore(query2, candidate, maximum) {
|
|
25098
|
+
const compactQuery = compactSearchText(query2);
|
|
25099
|
+
const compactCandidate = compactSearchText(candidate);
|
|
25100
|
+
if (compactQuery.length < 3 || compactCandidate.length < 3)
|
|
25101
|
+
return 0;
|
|
25102
|
+
const similarity = trigramSimilarity(compactQuery, compactCandidate);
|
|
25103
|
+
const minimumSimilarity = compactQuery.length <= 4 ? 0.6 : 0.45;
|
|
25104
|
+
return similarity >= minimumSimilarity ? similarity * maximum : 0;
|
|
25105
|
+
}
|
|
25106
|
+
function meaningfulSearchTokens(value) {
|
|
25107
|
+
return normalizeSearchText(value).split(" ").filter((token) => (token.length >= 2 || /^\d+$/.test(token)) && !SEARCH_STOP_WORDS.has(token));
|
|
25108
|
+
}
|
|
25109
|
+
function canonicalSearchToken(token) {
|
|
25110
|
+
return token.length > 4 && token.endsWith("s") ? token.slice(0, -1) : token;
|
|
25111
|
+
}
|
|
25112
|
+
function canonicalSearchTokens(value) {
|
|
25113
|
+
return meaningfulSearchTokens(value).map(canonicalSearchToken);
|
|
25114
|
+
}
|
|
25115
|
+
function countToken(tokens, token) {
|
|
25116
|
+
let count = 0;
|
|
25117
|
+
for (const candidate of tokens)
|
|
25118
|
+
if (candidate === token)
|
|
25119
|
+
count += 1;
|
|
25120
|
+
return count;
|
|
25121
|
+
}
|
|
25122
|
+
function countPhrase(text2, phrase) {
|
|
25123
|
+
if (!phrase)
|
|
25124
|
+
return 0;
|
|
25125
|
+
let count = 0;
|
|
25126
|
+
let offset = 0;
|
|
25127
|
+
let match = text2.indexOf(phrase, offset);
|
|
25128
|
+
while (match >= 0) {
|
|
25129
|
+
count += 1;
|
|
25130
|
+
offset = match + phrase.length;
|
|
25131
|
+
match = text2.indexOf(phrase, offset);
|
|
25132
|
+
}
|
|
25133
|
+
return count;
|
|
25134
|
+
}
|
|
25135
|
+
function summaryTopicIdentity(key) {
|
|
25136
|
+
return normalizeSearchText(key.replace(/^\d{4}-\d{2}-\d{2}-/, ""));
|
|
25137
|
+
}
|
|
25138
|
+
function scoreSummaryTime(query2, key, title) {
|
|
25139
|
+
const date = key.match(/^(\d{4})-(\d{2})-(\d{2})/);
|
|
25140
|
+
if (!date)
|
|
25141
|
+
return 0;
|
|
25142
|
+
const tokens = meaningfulSearchTokens(query2);
|
|
25143
|
+
const documentDate = `${date[1]}-${date[2]}-${date[3]}`;
|
|
25144
|
+
const requestedDateMatch = query2.match(/\b(\d{4}) (\d{2}) (\d{2})\b/);
|
|
25145
|
+
const requestedDate = requestedDateMatch ? `${requestedDateMatch[1]}-${requestedDateMatch[2]}-${requestedDateMatch[3]}` : undefined;
|
|
25146
|
+
const beforeDate = /\bbefore\b/.test(query2);
|
|
25147
|
+
const afterDate = /\bafter\b/.test(query2);
|
|
25148
|
+
if (requestedDate && beforeDate && documentDate >= requestedDate)
|
|
25149
|
+
return null;
|
|
25150
|
+
if (requestedDate && afterDate && documentDate <= requestedDate)
|
|
25151
|
+
return null;
|
|
25152
|
+
const queryYear = tokens.find((token) => /^\d{4}(?:\uB144)?$/.test(token))?.replace(/\uB144$/, "");
|
|
25153
|
+
const queryMonth = tokens.map((token) => SEARCH_MONTHS.get(token)).find(Boolean);
|
|
25154
|
+
const hasRecencyTerm = tokens.some((token) => SEARCH_RECENCY_TERMS.has(token));
|
|
25155
|
+
const yearMatches = !queryYear || queryYear === date[1];
|
|
25156
|
+
const monthMatches = !queryMonth || queryMonth === date[2];
|
|
25157
|
+
if (!queryYear && !queryMonth && !hasRecencyTerm || !yearMatches || !monthMatches)
|
|
25158
|
+
return 0;
|
|
25159
|
+
const normalizedMetadata = `${normalizeSearchText(key)} ${normalizeSearchText(title)}`;
|
|
25160
|
+
const contentTokens = tokens.filter((token) => !/^\d{4}(?:\uB144)?$/.test(token) && !SEARCH_MONTHS.has(token) && !SEARCH_RECENCY_TERMS.has(token));
|
|
25161
|
+
const contentMatches = contentTokens.filter((token) => normalizedMetadata.includes(token)).length;
|
|
25162
|
+
const relationScore = requestedDate && (beforeDate || afterDate) ? 60 : 0;
|
|
25163
|
+
const exactDateScore = requestedDate === documentDate ? 90 : 0;
|
|
25164
|
+
return 30 + relationScore + exactDateScore + contentMatches * 15;
|
|
25165
|
+
}
|
|
25166
|
+
function trigramSimilarity(left, right) {
|
|
25167
|
+
const a = trigrams(left);
|
|
25168
|
+
const b = trigrams(right);
|
|
25169
|
+
if (a.size === 0 || b.size === 0)
|
|
25170
|
+
return 0;
|
|
25171
|
+
let overlap = 0;
|
|
25172
|
+
for (const gram of a)
|
|
25173
|
+
if (b.has(gram))
|
|
25174
|
+
overlap += 1;
|
|
25175
|
+
return 2 * overlap / (a.size + b.size);
|
|
25176
|
+
}
|
|
25177
|
+
function parseWikiIndex(path) {
|
|
25178
|
+
let text2;
|
|
25179
|
+
try {
|
|
25180
|
+
text2 = readFileSync22(path, "utf-8");
|
|
25181
|
+
} catch {
|
|
25182
|
+
return [];
|
|
25183
|
+
}
|
|
25184
|
+
const candidates = [];
|
|
25185
|
+
const entryPattern = /^\s*-\s*\[\[(topic|articles|summaries)\/([^\]]+)\]\]\s*(.*)$/;
|
|
25186
|
+
for (const line of text2.split(`
|
|
25187
|
+
`)) {
|
|
25188
|
+
const match = line.match(entryPattern);
|
|
25189
|
+
if (!match)
|
|
25190
|
+
continue;
|
|
25191
|
+
const namespace = match[1];
|
|
25192
|
+
const key = match[2].replace(/\.md$/i, "");
|
|
25193
|
+
const tail = match[3].trim();
|
|
25194
|
+
const dateMatch = tail.match(/\((\d{4}-\d{2}-\d{2})(?:[^)]*)\)\s*$/);
|
|
25195
|
+
const description = dateMatch ? tail.slice(0, dateMatch.index).trim() : tail;
|
|
25196
|
+
candidates.push({
|
|
25197
|
+
kind: namespace === "topic" ? "topic" : namespace === "articles" ? "article" : "summary",
|
|
25198
|
+
key,
|
|
25199
|
+
description,
|
|
25200
|
+
...dateMatch ? { date: dateMatch[1] } : {}
|
|
25201
|
+
});
|
|
25202
|
+
}
|
|
25203
|
+
return candidates;
|
|
25204
|
+
}
|
|
25205
|
+
function indexCandidateExists(candidate) {
|
|
25206
|
+
if (candidate.kind === "topic")
|
|
25207
|
+
return true;
|
|
25208
|
+
const root = candidate.kind === "article" ? runtime().articlesDir : runtime().summariesDir;
|
|
25209
|
+
const filePath = resolve17(root, `${candidate.key}.md`);
|
|
25210
|
+
const relativePath = relative2(root, filePath);
|
|
25211
|
+
if (relativePath.startsWith("..") || relativePath.startsWith("/"))
|
|
25212
|
+
return false;
|
|
25213
|
+
try {
|
|
25214
|
+
return statSync15(filePath).isFile();
|
|
25215
|
+
} catch {
|
|
25216
|
+
return false;
|
|
25217
|
+
}
|
|
25218
|
+
}
|
|
25219
|
+
function scoreIndexKey(query2, key) {
|
|
25220
|
+
const normalizedQuery = normalizeSearchText(query2);
|
|
25221
|
+
const compactQuery = compactSearchText(normalizedQuery);
|
|
25222
|
+
const normalizedKey = normalizeSearchText(key);
|
|
25223
|
+
const compactKey = compactSearchText(normalizedKey);
|
|
25224
|
+
const tokens = meaningfulSearchTokens(normalizedQuery);
|
|
25225
|
+
let score = 0;
|
|
25226
|
+
if (normalizedKey === normalizedQuery)
|
|
25227
|
+
score += 120;
|
|
25228
|
+
else if (compactKey === compactQuery)
|
|
25229
|
+
score += 110;
|
|
25230
|
+
else {
|
|
25231
|
+
if (compactQuery.length >= 2 && (compactKey.startsWith(compactQuery) || compactQuery.startsWith(compactKey))) {
|
|
25232
|
+
score += 65;
|
|
25233
|
+
}
|
|
25234
|
+
if (tokens.length > 0 && compactQuery.length >= 3 && compactKey.includes(compactQuery)) {
|
|
25235
|
+
score += 45;
|
|
25236
|
+
}
|
|
25237
|
+
score += fuzzySearchScore(compactQuery, compactKey, 55);
|
|
25238
|
+
}
|
|
25239
|
+
for (const token of tokens) {
|
|
25240
|
+
if (normalizedKey.includes(token))
|
|
25241
|
+
score += 10;
|
|
25242
|
+
}
|
|
25243
|
+
return score;
|
|
25244
|
+
}
|
|
25245
|
+
function scoreIndexCandidate(query2, candidate) {
|
|
25246
|
+
const normalizedQuery = normalizeSearchText(query2);
|
|
25247
|
+
if (!normalizedQuery)
|
|
25248
|
+
return 0;
|
|
25249
|
+
const normalizedDescription = normalizeSearchText(candidate.description);
|
|
25250
|
+
const tokens = canonicalSearchTokens(normalizedQuery);
|
|
25251
|
+
const descriptionTokens = new Set(canonicalSearchTokens(normalizedDescription));
|
|
25252
|
+
let score = scoreIndexKey(normalizedQuery, candidate.key);
|
|
25253
|
+
if (tokens.length > 0 && normalizedDescription.includes(normalizedQuery))
|
|
25254
|
+
score += 30;
|
|
25255
|
+
let descriptionMatches = 0;
|
|
25256
|
+
for (const token of tokens) {
|
|
25257
|
+
if (descriptionTokens.has(token)) {
|
|
25258
|
+
score += 5;
|
|
25259
|
+
descriptionMatches += 1;
|
|
25260
|
+
}
|
|
25261
|
+
}
|
|
25262
|
+
if (tokens.length > 1 && descriptionMatches === tokens.length)
|
|
25263
|
+
score += 25;
|
|
25264
|
+
if (candidate.kind === "topic") {
|
|
25265
|
+
const requestsCurrent = /\b(?:current|active|ongoing|open)\b|\uD604\uC7AC|\uC9C4\uD589 \uC911/.test(normalizedQuery);
|
|
25266
|
+
const archivedCandidate = /\b(?:archive|archived|historical|closed)\b|\uBCF4\uAD00|\uC885\uB8CC/.test(`${normalizeSearchText(candidate.key)} ${normalizedDescription}`);
|
|
25267
|
+
if (requestsCurrent && archivedCandidate)
|
|
25268
|
+
score -= 40;
|
|
25269
|
+
}
|
|
25270
|
+
return score;
|
|
25271
|
+
}
|
|
25036
25272
|
function wikiQuery(args) {
|
|
25037
|
-
const question = safeExt(args, ["question", "query", "q", "text"], "");
|
|
25038
|
-
const
|
|
25273
|
+
const question = String(safeExt(args, ["question", "query", "q", "text"], "")).trim();
|
|
25274
|
+
const requestedKind = String(safeExt(args, ["kind", "type"], "all"));
|
|
25275
|
+
const kind = requestedKind === "topic" || requestedKind === "article" || requestedKind === "summary" ? requestedKind : "all";
|
|
25276
|
+
const requestedLimit = Number(safeExt(args, ["limit", "maxResults"], 8));
|
|
25277
|
+
const limit = Math.min(Math.max(Number.isFinite(requestedLimit) ? requestedLimit : 8, 1), 20);
|
|
25278
|
+
if (!question) {
|
|
25279
|
+
return {
|
|
25280
|
+
content: [{ type: "text", text: "A non-empty wiki query is required." }],
|
|
25281
|
+
isError: true
|
|
25282
|
+
};
|
|
25283
|
+
}
|
|
25284
|
+
const normalizedQuery = normalizeSearchText(question);
|
|
25285
|
+
if (!normalizedQuery) {
|
|
25286
|
+
return { content: [{ type: "text", text: "No matching wiki articles found." }] };
|
|
25287
|
+
}
|
|
25288
|
+
const canReadTopicMemory = runtime().host.canReadTopicMemory;
|
|
25289
|
+
const temporalDirection = /\b(?:latest|newest|recent)\b|\uCD5C\uADFC|\uCD5C\uC2E0/.test(normalizedQuery) ? "newest" : /\b(?:earliest|oldest)\b|\uAC00\uC7A5 \uC624\uB798\uB41C|\uCD5C\uCD08/.test(normalizedQuery) ? "oldest" : undefined;
|
|
25290
|
+
const visibleIndexCandidates = [
|
|
25291
|
+
...parseWikiIndex(resolve17(runtime().wikiDir, "topic-index.md")),
|
|
25292
|
+
...parseWikiIndex(resolve17(runtime().wikiDir, "article-index.md"))
|
|
25293
|
+
].filter((candidate) => kind === "all" || candidate.kind === kind).filter((candidate) => candidate.kind !== "topic" || !canReadTopicMemory || canReadTopicMemory(candidate.key, runtime().userId));
|
|
25294
|
+
const hasStaleExactIndexMatch = visibleIndexCandidates.some((candidate) => !indexCandidateExists(candidate) && scoreIndexKey(question, candidate.key) >= 100);
|
|
25295
|
+
const indexCandidates = visibleIndexCandidates.filter(indexCandidateExists).map((candidate) => ({
|
|
25296
|
+
candidate,
|
|
25297
|
+
keyScore: scoreIndexKey(question, candidate.key),
|
|
25298
|
+
score: scoreIndexCandidate(question, candidate)
|
|
25299
|
+
})).filter(({ keyScore, score }) => keyScore >= 25 || score >= 40).sort((left, right) => right.score - left.score || left.candidate.key.localeCompare(right.candidate.key));
|
|
25300
|
+
const strongestIndex = indexCandidates[0];
|
|
25301
|
+
if (kind === "topic" && strongestIndex && (strongestIndex.keyScore >= 25 || strongestIndex.score >= 40)) {
|
|
25302
|
+
const runnerUp = indexCandidates[1];
|
|
25303
|
+
const ambiguous = strongestIndex.keyScore < 100 && runnerUp !== undefined && runnerUp.score >= strongestIndex.score * 0.9;
|
|
25304
|
+
if (ambiguous) {
|
|
25305
|
+
const lines2 = ["Found 2 ambiguous wiki candidates:", ""];
|
|
25306
|
+
for (const { candidate, score } of [strongestIndex, runnerUp]) {
|
|
25307
|
+
lines2.push(`- kind: ${candidate.kind}`, ` key: ${candidate.key}`, ` score: ${score.toFixed(2)}`, ` description: ${candidate.description || "(none)"}`, ...candidate.date ? [` date: ${candidate.date}`] : []);
|
|
25308
|
+
}
|
|
25309
|
+
return { content: [{ type: "text", text: lines2.join(`
|
|
25310
|
+
`) }] };
|
|
25311
|
+
}
|
|
25312
|
+
const lines = ["Found 1 wiki candidate:", ""];
|
|
25313
|
+
for (const { candidate, score } of [strongestIndex]) {
|
|
25314
|
+
lines.push(`- kind: ${candidate.kind}`, ` key: ${candidate.key}`, ` score: ${score.toFixed(2)}`, ` description: ${candidate.description || "(none)"}`, ...candidate.date ? [` date: ${candidate.date}`] : []);
|
|
25315
|
+
}
|
|
25316
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
25317
|
+
`) }] };
|
|
25318
|
+
}
|
|
25039
25319
|
const results = [];
|
|
25040
25320
|
const scored = [];
|
|
25041
25321
|
function scan(dir, label) {
|
|
@@ -25052,67 +25332,234 @@ function wikiQuery(args) {
|
|
|
25052
25332
|
const fp = join36(sub, f.name);
|
|
25053
25333
|
try {
|
|
25054
25334
|
const text2 = readFileSync22(fp, "utf-8");
|
|
25335
|
+
const key = `${entry.name}/${f.name.replace(/\.md$/i, "")}`;
|
|
25055
25336
|
scored.push({
|
|
25056
|
-
|
|
25337
|
+
kind: label === "articles" ? "article" : "skill",
|
|
25338
|
+
key,
|
|
25339
|
+
keyScore: scoreIndexKey(question, key),
|
|
25340
|
+
score: scoreMatch(text2, key, label === "articles" ? "article" : "skill"),
|
|
25057
25341
|
path: `${label}/${entry.name}/${f.name}`,
|
|
25058
|
-
text: text2
|
|
25342
|
+
text: text2,
|
|
25343
|
+
title: titleFromDocument(text2, key)
|
|
25059
25344
|
});
|
|
25060
25345
|
} catch {}
|
|
25061
25346
|
}
|
|
25062
25347
|
} catch {}
|
|
25063
25348
|
}
|
|
25064
25349
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
25350
|
+
if (label === "topic" && canReadTopicMemory && !canReadTopicMemory(entry.name.replace(/\.md$/i, ""), runtime().userId)) {
|
|
25351
|
+
continue;
|
|
25352
|
+
}
|
|
25065
25353
|
const fp = join36(dir, entry.name);
|
|
25066
25354
|
try {
|
|
25067
25355
|
const text2 = readFileSync22(fp, "utf-8");
|
|
25356
|
+
const key = entry.name.replace(/\.md$/i, "");
|
|
25357
|
+
const resultKind = label === "articles" ? "article" : label === "topic" ? "topic" : label === "summaries" ? "summary" : "skill";
|
|
25068
25358
|
scored.push({
|
|
25069
|
-
|
|
25359
|
+
kind: resultKind,
|
|
25360
|
+
key,
|
|
25361
|
+
keyScore: scoreIndexKey(question, key),
|
|
25362
|
+
score: scoreMatch(text2, key, resultKind),
|
|
25070
25363
|
path: `${label}/${entry.name}`,
|
|
25071
|
-
text: text2
|
|
25364
|
+
text: text2,
|
|
25365
|
+
title: titleFromDocument(text2, key),
|
|
25366
|
+
...label === "summaries" && /^\d{4}-\d{2}-\d{2}/.test(key) ? { date: key.slice(0, 10) } : {}
|
|
25072
25367
|
});
|
|
25073
25368
|
} catch {}
|
|
25074
25369
|
}
|
|
25075
25370
|
}
|
|
25076
25371
|
} catch {}
|
|
25077
25372
|
}
|
|
25078
|
-
function
|
|
25079
|
-
const
|
|
25373
|
+
function titleFromDocument(text2, key) {
|
|
25374
|
+
const titleLine = text2.split(`
|
|
25375
|
+
`).find((line) => /^#+\s*/.test(line));
|
|
25376
|
+
return titleLine ? titleLine.replace(/^#+\s*/, "") : basename7(key);
|
|
25377
|
+
}
|
|
25378
|
+
function scoreMatch(text2, key, resultKind) {
|
|
25379
|
+
const titleLine = text2.split(`
|
|
25380
|
+
`).find((line) => /^#+\s*/.test(line)) ?? "";
|
|
25381
|
+
const title = titleLine.replace(/^#+\s*/, "");
|
|
25382
|
+
const normalizedKey = normalizeSearchText(key);
|
|
25383
|
+
const normalizedTitle2 = normalizeSearchText(title);
|
|
25384
|
+
const normalizedBody = normalizeSearchText(text2.replace(titleLine, ""));
|
|
25385
|
+
const compactQuery = compactSearchText(normalizedQuery);
|
|
25386
|
+
const compactKey = compactSearchText(normalizedKey);
|
|
25387
|
+
const compactTitle = compactSearchText(normalizedTitle2);
|
|
25388
|
+
const tokens = canonicalSearchTokens(normalizedQuery);
|
|
25389
|
+
const keyTokens = new Set(canonicalSearchTokens(normalizedKey));
|
|
25390
|
+
const titleTokens = new Set(canonicalSearchTokens(normalizedTitle2));
|
|
25391
|
+
const bodyTokens = canonicalSearchTokens(normalizedBody);
|
|
25080
25392
|
let score = 0;
|
|
25081
|
-
|
|
25082
|
-
|
|
25083
|
-
|
|
25084
|
-
|
|
25085
|
-
|
|
25086
|
-
|
|
25087
|
-
|
|
25088
|
-
|
|
25089
|
-
|
|
25090
|
-
|
|
25393
|
+
let strongMetadataEvidence = false;
|
|
25394
|
+
if (normalizedKey === normalizedQuery) {
|
|
25395
|
+
score += 120;
|
|
25396
|
+
strongMetadataEvidence = true;
|
|
25397
|
+
} else if (compactKey === compactQuery) {
|
|
25398
|
+
score += 110;
|
|
25399
|
+
strongMetadataEvidence = true;
|
|
25400
|
+
} else if (compactQuery.length >= 2 && (compactKey.startsWith(compactQuery) || compactQuery.startsWith(compactKey))) {
|
|
25401
|
+
score += 65;
|
|
25402
|
+
strongMetadataEvidence = true;
|
|
25403
|
+
}
|
|
25404
|
+
if (tokens.length > 0 && compactQuery.length >= 3 && compactKey.includes(compactQuery)) {
|
|
25405
|
+
score += 45;
|
|
25406
|
+
strongMetadataEvidence = true;
|
|
25407
|
+
}
|
|
25408
|
+
const fuzzyKeyScore = fuzzySearchScore(compactQuery, compactKey, 55);
|
|
25409
|
+
score += fuzzyKeyScore;
|
|
25410
|
+
if (fuzzyKeyScore > 0)
|
|
25411
|
+
strongMetadataEvidence = true;
|
|
25412
|
+
if (normalizedTitle2 === normalizedQuery) {
|
|
25413
|
+
score += 115;
|
|
25414
|
+
strongMetadataEvidence = true;
|
|
25415
|
+
} else if (compactTitle === compactQuery) {
|
|
25416
|
+
score += 105;
|
|
25417
|
+
strongMetadataEvidence = true;
|
|
25418
|
+
} else if (compactQuery.length >= 2 && compactTitle.startsWith(compactQuery)) {
|
|
25419
|
+
score += 60;
|
|
25420
|
+
strongMetadataEvidence = true;
|
|
25421
|
+
}
|
|
25422
|
+
const fuzzyTitleScore = fuzzySearchScore(compactQuery, compactTitle, 50);
|
|
25423
|
+
score += fuzzyTitleScore;
|
|
25424
|
+
if (fuzzyTitleScore > 0)
|
|
25425
|
+
strongMetadataEvidence = true;
|
|
25426
|
+
if (resultKind === "summary") {
|
|
25427
|
+
const temporalScore = scoreSummaryTime(normalizedQuery, key, title);
|
|
25428
|
+
if (temporalScore === null)
|
|
25429
|
+
return 0;
|
|
25430
|
+
score += temporalScore;
|
|
25431
|
+
if (temporalScore > 0)
|
|
25432
|
+
strongMetadataEvidence = true;
|
|
25433
|
+
}
|
|
25434
|
+
const isMultiToken = tokens.length > 1;
|
|
25435
|
+
if (tokens.length > 0 && normalizedTitle2.includes(normalizedQuery)) {
|
|
25436
|
+
score += 35;
|
|
25437
|
+
strongMetadataEvidence = true;
|
|
25438
|
+
}
|
|
25439
|
+
const phraseMatches = isMultiToken ? countPhrase(normalizedBody, normalizedQuery) : 0;
|
|
25440
|
+
if (phraseMatches > 0)
|
|
25441
|
+
score += phraseMatches * 12;
|
|
25442
|
+
else if (tokens.length === 1 && tokens[0].length >= 5 && normalizedBody.includes(tokens[0])) {
|
|
25443
|
+
score += 16;
|
|
25444
|
+
}
|
|
25445
|
+
let bodyMatches = 0;
|
|
25446
|
+
let minimumBodyFrequency = Number.POSITIVE_INFINITY;
|
|
25447
|
+
for (const token of tokens) {
|
|
25448
|
+
if (keyTokens.has(token))
|
|
25449
|
+
score += 10;
|
|
25450
|
+
if (titleTokens.has(token))
|
|
25451
|
+
score += 12;
|
|
25452
|
+
const bodyFrequency = countToken(bodyTokens, token);
|
|
25453
|
+
if (bodyFrequency > 0) {
|
|
25454
|
+
score += Math.min(bodyFrequency, 3) * 4;
|
|
25455
|
+
bodyMatches += 1;
|
|
25456
|
+
minimumBodyFrequency = Math.min(minimumBodyFrequency, bodyFrequency);
|
|
25457
|
+
}
|
|
25458
|
+
}
|
|
25459
|
+
if (isMultiToken && bodyMatches === tokens.length) {
|
|
25460
|
+
score += 8 * Math.min(minimumBodyFrequency, 3);
|
|
25461
|
+
}
|
|
25462
|
+
const minimumBodyTokens = hasStaleExactIndexMatch ? 3 : 2;
|
|
25463
|
+
const partialArticleEvidence = resultKind === "article" && bodyMatches >= 3 && bodyMatches / tokens.length >= 0.6;
|
|
25464
|
+
const strongBodyEvidence = tokens.length >= minimumBodyTokens && (bodyMatches === tokens.length && (phraseMatches > 0 || minimumBodyFrequency >= 1) || partialArticleEvidence);
|
|
25465
|
+
if (!strongMetadataEvidence && !strongBodyEvidence)
|
|
25466
|
+
return 0;
|
|
25091
25467
|
return score;
|
|
25092
25468
|
}
|
|
25093
|
-
|
|
25094
|
-
|
|
25469
|
+
if (kind === "all" || kind === "article")
|
|
25470
|
+
scan(runtime().articlesDir, "articles");
|
|
25471
|
+
if (kind === "all" && runtime().surface === "all")
|
|
25095
25472
|
scan(runtime().skillsDir, "skills");
|
|
25096
|
-
|
|
25097
|
-
|
|
25098
|
-
|
|
25099
|
-
|
|
25100
|
-
|
|
25473
|
+
if (kind === "all" || kind === "topic")
|
|
25474
|
+
scan(runtime().topicsDir, "topic");
|
|
25475
|
+
if (kind === "all" || kind === "summary")
|
|
25476
|
+
scan(runtime().summariesDir, "summaries");
|
|
25477
|
+
const merged = new Map;
|
|
25478
|
+
for (const { candidate, keyScore, score } of indexCandidates) {
|
|
25479
|
+
merged.set(`${candidate.kind}:${candidate.key}`, {
|
|
25480
|
+
kind: candidate.kind,
|
|
25481
|
+
key: candidate.key,
|
|
25482
|
+
keyScore,
|
|
25483
|
+
score,
|
|
25484
|
+
description: candidate.description,
|
|
25485
|
+
date: candidate.date
|
|
25486
|
+
});
|
|
25487
|
+
}
|
|
25488
|
+
const documentThreshold = kind === "topic" ? 24 : 16;
|
|
25489
|
+
for (const document of scored) {
|
|
25490
|
+
if (document.score < documentThreshold)
|
|
25491
|
+
continue;
|
|
25492
|
+
const id = `${document.kind}:${document.key}`;
|
|
25493
|
+
const indexed = merged.get(id);
|
|
25494
|
+
merged.set(id, indexed ? { ...indexed, ...document, score: Math.max(indexed.score, document.score) } : document);
|
|
25495
|
+
}
|
|
25496
|
+
let ranked = [...merged.values()].sort((left, right) => right.score - left.score || (left.kind === "summary" && right.kind === "summary" ? temporalDirection === "oldest" ? (left.date ?? "").localeCompare(right.date ?? "") : (right.date ?? "").localeCompare(left.date ?? "") : 0) || left.key.localeCompare(right.key));
|
|
25497
|
+
if (kind === "summary") {
|
|
25498
|
+
const sameTopic = ranked.filter((result) => {
|
|
25499
|
+
const identity = summaryTopicIdentity(result.key);
|
|
25500
|
+
return identity.length >= 3 && normalizedQuery.includes(identity);
|
|
25501
|
+
});
|
|
25502
|
+
if (sameTopic.length > 0)
|
|
25503
|
+
ranked = sameTopic;
|
|
25504
|
+
const requestedDateMatch = normalizedQuery.match(/\b(\d{4}) (\d{2}) (\d{2})\b/);
|
|
25505
|
+
const requestedDate = requestedDateMatch ? `${requestedDateMatch[1]}-${requestedDateMatch[2]}-${requestedDateMatch[3]}` : undefined;
|
|
25506
|
+
const beforeDate = /\bbefore\b/.test(normalizedQuery);
|
|
25507
|
+
const afterDate = /\bafter\b/.test(normalizedQuery);
|
|
25508
|
+
if (requestedDate && beforeDate) {
|
|
25509
|
+
ranked = ranked.filter((result) => !result.date || result.date < requestedDate);
|
|
25510
|
+
} else if (requestedDate && afterDate) {
|
|
25511
|
+
ranked = ranked.filter((result) => !result.date || result.date > requestedDate);
|
|
25512
|
+
}
|
|
25513
|
+
ranked.sort((left, right) => {
|
|
25514
|
+
if (beforeDate) {
|
|
25515
|
+
return (right.date ?? "").localeCompare(left.date ?? "") || right.score - left.score;
|
|
25516
|
+
}
|
|
25517
|
+
if (afterDate) {
|
|
25518
|
+
return (left.date ?? "").localeCompare(right.date ?? "") || right.score - left.score;
|
|
25519
|
+
}
|
|
25520
|
+
if (requestedDate) {
|
|
25521
|
+
const dateDifference = Number(right.date === requestedDate) - Number(left.date === requestedDate);
|
|
25522
|
+
if (dateDifference !== 0)
|
|
25523
|
+
return dateDifference;
|
|
25524
|
+
}
|
|
25525
|
+
if (temporalDirection) {
|
|
25526
|
+
const dateDifference = temporalDirection === "oldest" ? (left.date ?? "").localeCompare(right.date ?? "") : (right.date ?? "").localeCompare(left.date ?? "");
|
|
25527
|
+
if (dateDifference !== 0)
|
|
25528
|
+
return dateDifference;
|
|
25529
|
+
}
|
|
25530
|
+
return right.score - left.score || (right.date ?? "").localeCompare(left.date ?? "") || left.key.localeCompare(right.key);
|
|
25531
|
+
});
|
|
25532
|
+
}
|
|
25533
|
+
if (kind === "topic" && ranked[0]) {
|
|
25534
|
+
const runnerUp = ranked[1];
|
|
25535
|
+
const ambiguous = ranked[0].keyScore < 100 && runnerUp !== undefined && runnerUp.score >= ranked[0].score * 0.9;
|
|
25536
|
+
if (ambiguous) {
|
|
25537
|
+
ranked = ranked.slice(0, 2);
|
|
25538
|
+
} else {
|
|
25539
|
+
ranked = ranked.slice(0, 1);
|
|
25540
|
+
}
|
|
25541
|
+
} else {
|
|
25542
|
+
ranked = ranked.slice(0, limit);
|
|
25543
|
+
}
|
|
25544
|
+
if (ranked.length === 0) {
|
|
25101
25545
|
results.push("No matching wiki articles found.");
|
|
25102
25546
|
} else {
|
|
25103
|
-
results.push(`Found ${
|
|
25547
|
+
results.push(`Found ${ranked.length} matching result(s):
|
|
25104
25548
|
`);
|
|
25105
|
-
for (const
|
|
25106
|
-
|
|
25107
|
-
|
|
25108
|
-
|
|
25109
|
-
|
|
25110
|
-
|
|
25111
|
-
|
|
25112
|
-
|
|
25113
|
-
${body}${text2.length > 400 ? `
|
|
25549
|
+
for (const result of ranked) {
|
|
25550
|
+
if (result.kind === "skill") {
|
|
25551
|
+
const body = (result.text ?? "").trim().slice(0, 400);
|
|
25552
|
+
results.push(`## ${result.title ?? result.key}
|
|
25553
|
+
Path: ${result.path}
|
|
25554
|
+
|
|
25555
|
+
${body}${(result.text?.length ?? 0) > 400 ? `
|
|
25114
25556
|
...(truncated)` : ""}
|
|
25115
25557
|
`);
|
|
25558
|
+
continue;
|
|
25559
|
+
}
|
|
25560
|
+
results.push(`- kind: ${result.kind}`, ` key: ${result.key}`, ` score: ${result.score.toFixed(2)}`, ...result.title ? [` title: ${result.title}`] : [], ` description: ${result.description || "(none)"}`, ...result.date ? [` date: ${result.date}`] : [], ...result.path ? [` path: ${result.path}`, ` Path: ${result.path}`] : [], ...result.text ? [
|
|
25561
|
+
` excerpt: ${result.text.replace(/^---[\s\S]*?---\n?/, "").replace(/^#.*$/m, "").trim().slice(0, 400)}`
|
|
25562
|
+
] : []);
|
|
25116
25563
|
}
|
|
25117
25564
|
}
|
|
25118
25565
|
return { content: [{ type: "text", text: results.join(`
|
|
@@ -25131,36 +25578,94 @@ function wikiTopicBrief(args) {
|
|
|
25131
25578
|
};
|
|
25132
25579
|
}
|
|
25133
25580
|
const { getTopicBrief: getTopicBrief2, resolveTopicBrief: resolveTopicBrief2 } = runtime().host;
|
|
25134
|
-
|
|
25581
|
+
const hasAuthorizedBridge = Boolean(runtime().topicId && (getTopicBrief2 || resolveTopicBrief2));
|
|
25582
|
+
if (hasAuthorizedBridge) {
|
|
25135
25583
|
try {
|
|
25136
25584
|
const brief = resolveTopicBrief2 ? resolveTopicBrief2(rawTopic, runtime().userId) : getTopicBrief2(rawTopic);
|
|
25137
|
-
if (
|
|
25138
|
-
|
|
25139
|
-
|
|
25140
|
-
|
|
25141
|
-
|
|
25142
|
-
|
|
25143
|
-
|
|
25144
|
-
|
|
25145
|
-
|
|
25146
|
-
|
|
25147
|
-
|
|
25148
|
-
|
|
25149
|
-
|
|
25150
|
-
if (brief.summaryDate) {
|
|
25151
|
-
lines.push("", `Summary date: ${brief.summaryDate}`);
|
|
25152
|
-
}
|
|
25153
|
-
return { content: [{ type: "text", text: lines.join(`
|
|
25585
|
+
if (brief) {
|
|
25586
|
+
const lines = [
|
|
25587
|
+
`# Topic Brief: ${topicNameFrom(rawTopic)}`,
|
|
25588
|
+
"",
|
|
25589
|
+
brief.briefMd || "(empty brief)"
|
|
25590
|
+
];
|
|
25591
|
+
if (brief.latestSummaryMd) {
|
|
25592
|
+
lines.push("", "## Latest Summary", "", brief.latestSummaryMd);
|
|
25593
|
+
}
|
|
25594
|
+
if (brief.summaryDate) {
|
|
25595
|
+
lines.push("", `Summary date: ${brief.summaryDate}`);
|
|
25596
|
+
}
|
|
25597
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
25154
25598
|
`) }] };
|
|
25155
|
-
|
|
25599
|
+
}
|
|
25600
|
+
} catch {}
|
|
25601
|
+
return {
|
|
25602
|
+
content: [{ type: "text", text: `No brief found for topic: ${rawTopic}` }]
|
|
25603
|
+
};
|
|
25604
|
+
}
|
|
25605
|
+
const topicKey = wikiSummarySlug(rawTopic.replace(/^topic\//, "").replace(/\.md$/i, ""));
|
|
25606
|
+
const filePath = resolve17(runtime().topicsDir, `${topicKey}.md`);
|
|
25607
|
+
try {
|
|
25608
|
+
const content = readFileSync22(filePath, "utf-8");
|
|
25609
|
+
return {
|
|
25610
|
+
content: [{ type: "text", text: `# Topic Brief: ${topicKey}
|
|
25611
|
+
|
|
25612
|
+
${content}` }]
|
|
25613
|
+
};
|
|
25614
|
+
} catch {}
|
|
25615
|
+
return {
|
|
25616
|
+
content: [{ type: "text", text: `No brief found for topic: ${rawTopic}` }]
|
|
25617
|
+
};
|
|
25618
|
+
}
|
|
25619
|
+
function wikiRead(args) {
|
|
25620
|
+
const rawKind = String(safeExt(args, ["kind", "type"], "article"));
|
|
25621
|
+
const kind = rawKind === "topic" || rawKind === "summary" ? rawKind : "article";
|
|
25622
|
+
const rawKey = String(safeExt(args, ["key", "slug", "topic"], "")).trim();
|
|
25623
|
+
if (!rawKey) {
|
|
25624
|
+
return { content: [{ type: "text", text: "A wiki document key is required." }], isError: true };
|
|
25625
|
+
}
|
|
25626
|
+
if (kind === "topic") {
|
|
25627
|
+
const result = wikiTopicBrief({ topic: rawKey });
|
|
25628
|
+
const resultText = result.content.map((entry) => entry.type === "text" ? entry.text : "").join(`
|
|
25629
|
+
`);
|
|
25630
|
+
const adopt = safeExt(args, ["adopt", "use", "select"], false);
|
|
25631
|
+
if (!adopt || resultText.startsWith("No brief found for topic"))
|
|
25632
|
+
return result;
|
|
25633
|
+
const currentTopicId = runtime().currentTopicId;
|
|
25634
|
+
const memoryKey = wikiSummarySlug(rawKey.replace(/^topic\//, "").replace(/\.md$/i, ""));
|
|
25635
|
+
if (!currentTopicId || !runtime().host.adoptTopicMemory?.(currentTopicId, runtime().userId, memoryKey)) {
|
|
25156
25636
|
return {
|
|
25157
|
-
content: [
|
|
25637
|
+
content: [
|
|
25638
|
+
...result.content,
|
|
25639
|
+
{ type: "text", text: `
|
|
25640
|
+
Memory was read but could not be adopted by this topic.` }
|
|
25641
|
+
]
|
|
25158
25642
|
};
|
|
25159
25643
|
}
|
|
25644
|
+
return {
|
|
25645
|
+
content: [...result.content, { type: "text", text: `
|
|
25646
|
+
Adopted topic memory: ${memoryKey}` }]
|
|
25647
|
+
};
|
|
25648
|
+
}
|
|
25649
|
+
if (rawKey.includes("\\") || rawKey.split("/").some((part) => part === "..") || rawKey.startsWith("/")) {
|
|
25650
|
+
return { content: [{ type: "text", text: "Invalid wiki document key." }], isError: true };
|
|
25651
|
+
}
|
|
25652
|
+
const root = kind === "summary" ? runtime().summariesDir : runtime().articlesDir;
|
|
25653
|
+
const key = rawKey.replace(new RegExp(`^(?:${kind === "summary" ? "summaries" : "articles"})/`), "").replace(/\.md$/i, "");
|
|
25654
|
+
const filePath = resolve17(root, `${key}.md`);
|
|
25655
|
+
const relativePath = relative2(root, filePath);
|
|
25656
|
+
if (relativePath.startsWith("..") || relativePath.startsWith("/")) {
|
|
25657
|
+
return { content: [{ type: "text", text: "Invalid wiki document key." }], isError: true };
|
|
25658
|
+
}
|
|
25659
|
+
try {
|
|
25660
|
+
const content = readFileSync22(filePath, "utf-8");
|
|
25661
|
+
return {
|
|
25662
|
+
content: [{ type: "text", text: `# Wiki ${kind}: ${key}
|
|
25663
|
+
|
|
25664
|
+
${content}` }]
|
|
25665
|
+
};
|
|
25666
|
+
} catch {
|
|
25667
|
+
return { content: [{ type: "text", text: `No ${kind} found for: ${rawKey}` }] };
|
|
25160
25668
|
}
|
|
25161
|
-
return {
|
|
25162
|
-
content: [{ type: "text", text: `No topic brief found for: ${rawTopic}` }]
|
|
25163
|
-
};
|
|
25164
25669
|
}
|
|
25165
25670
|
function wikiLastConversation(args) {
|
|
25166
25671
|
const rawTopic = safeExt(args, ["topic", "topicName", "topicId"], runtime().topicId);
|
|
@@ -25529,6 +26034,7 @@ function createWikiMcpServer(context, host2) {
|
|
|
25529
26034
|
const wikiDir = resolve17(host2.wikiRoot);
|
|
25530
26035
|
const current3 = {
|
|
25531
26036
|
userId: context.userId,
|
|
26037
|
+
currentTopicId: context.currentTopicId ?? context.topicId,
|
|
25532
26038
|
topicId: context.topicId,
|
|
25533
26039
|
surface,
|
|
25534
26040
|
host: host2,
|
|
@@ -25558,6 +26064,7 @@ function createWikiMcpServer(context, host2) {
|
|
|
25558
26064
|
const { name, arguments: args } = request.params;
|
|
25559
26065
|
const handlers = {
|
|
25560
26066
|
wiki_query: wikiQuery,
|
|
26067
|
+
wiki_read: wikiRead,
|
|
25561
26068
|
wiki_topic_brief: wikiTopicBrief,
|
|
25562
26069
|
wiki_last_conversation: wikiLastConversation,
|
|
25563
26070
|
skill_query: skillQuery,
|
|
@@ -25572,16 +26079,87 @@ function createWikiMcpServer(context, host2) {
|
|
|
25572
26079
|
});
|
|
25573
26080
|
return server;
|
|
25574
26081
|
}
|
|
25575
|
-
var wikiRuntime, FILE_LOCK_WAIT_MS = 10, FILE_LOCK_TIMEOUT_MS = 5000, FILE_LOCK_STALE_MS = 30000, lockWaitArray, WIKI_TOOLS, SKILL_TOOLS;
|
|
26082
|
+
var wikiRuntime, FILE_LOCK_WAIT_MS = 10, FILE_LOCK_TIMEOUT_MS = 5000, FILE_LOCK_STALE_MS = 30000, lockWaitArray, SEARCH_STOP_WORDS, SEARCH_MONTHS, SEARCH_RECENCY_TERMS, WIKI_TOOLS, SKILL_TOOLS;
|
|
25576
26083
|
var init_wiki_server = __esm(() => {
|
|
25577
26084
|
init_config();
|
|
25578
26085
|
init_wiki_summary_names();
|
|
25579
26086
|
wikiRuntime = new AsyncLocalStorage3;
|
|
25580
26087
|
lockWaitArray = new Int32Array(new SharedArrayBuffer(4));
|
|
26088
|
+
SEARCH_STOP_WORDS = new Set([
|
|
26089
|
+
"a",
|
|
26090
|
+
"an",
|
|
26091
|
+
"and",
|
|
26092
|
+
"are",
|
|
26093
|
+
"as",
|
|
26094
|
+
"at",
|
|
26095
|
+
"be",
|
|
26096
|
+
"by",
|
|
26097
|
+
"for",
|
|
26098
|
+
"from",
|
|
26099
|
+
"in",
|
|
26100
|
+
"is",
|
|
26101
|
+
"it",
|
|
26102
|
+
"of",
|
|
26103
|
+
"on",
|
|
26104
|
+
"or",
|
|
26105
|
+
"that",
|
|
26106
|
+
"the",
|
|
26107
|
+
"to",
|
|
26108
|
+
"with"
|
|
26109
|
+
]);
|
|
26110
|
+
SEARCH_MONTHS = new Map([
|
|
26111
|
+
["january", "01"],
|
|
26112
|
+
["jan", "01"],
|
|
26113
|
+
["1\uC6D4", "01"],
|
|
26114
|
+
["february", "02"],
|
|
26115
|
+
["feb", "02"],
|
|
26116
|
+
["2\uC6D4", "02"],
|
|
26117
|
+
["march", "03"],
|
|
26118
|
+
["mar", "03"],
|
|
26119
|
+
["3\uC6D4", "03"],
|
|
26120
|
+
["april", "04"],
|
|
26121
|
+
["apr", "04"],
|
|
26122
|
+
["4\uC6D4", "04"],
|
|
26123
|
+
["may", "05"],
|
|
26124
|
+
["5\uC6D4", "05"],
|
|
26125
|
+
["june", "06"],
|
|
26126
|
+
["jun", "06"],
|
|
26127
|
+
["6\uC6D4", "06"],
|
|
26128
|
+
["july", "07"],
|
|
26129
|
+
["jul", "07"],
|
|
26130
|
+
["7\uC6D4", "07"],
|
|
26131
|
+
["august", "08"],
|
|
26132
|
+
["aug", "08"],
|
|
26133
|
+
["8\uC6D4", "08"],
|
|
26134
|
+
["september", "09"],
|
|
26135
|
+
["sep", "09"],
|
|
26136
|
+
["sept", "09"],
|
|
26137
|
+
["9\uC6D4", "09"],
|
|
26138
|
+
["october", "10"],
|
|
26139
|
+
["oct", "10"],
|
|
26140
|
+
["10\uC6D4", "10"],
|
|
26141
|
+
["november", "11"],
|
|
26142
|
+
["nov", "11"],
|
|
26143
|
+
["11\uC6D4", "11"],
|
|
26144
|
+
["december", "12"],
|
|
26145
|
+
["dec", "12"],
|
|
26146
|
+
["12\uC6D4", "12"]
|
|
26147
|
+
]);
|
|
26148
|
+
SEARCH_RECENCY_TERMS = new Set([
|
|
26149
|
+
"latest",
|
|
26150
|
+
"newest",
|
|
26151
|
+
"recent",
|
|
26152
|
+
"earliest",
|
|
26153
|
+
"oldest",
|
|
26154
|
+
"\uCD5C\uADFC",
|
|
26155
|
+
"\uCD5C\uC2E0",
|
|
26156
|
+
"\uCD5C\uCD08",
|
|
26157
|
+
"\uC624\uB798\uB41C"
|
|
26158
|
+
]);
|
|
25581
26159
|
WIKI_TOOLS = [
|
|
25582
26160
|
{
|
|
25583
26161
|
name: "wiki_query",
|
|
25584
|
-
description: "Search
|
|
26162
|
+
description: "Search topic, article, or summary indexes and return relevant candidates.",
|
|
25585
26163
|
inputSchema: {
|
|
25586
26164
|
type: "object",
|
|
25587
26165
|
properties: {
|
|
@@ -25592,11 +26170,38 @@ var init_wiki_server = __esm(() => {
|
|
|
25592
26170
|
topic: {
|
|
25593
26171
|
type: "string",
|
|
25594
26172
|
description: "Optional topic name to narrow the search"
|
|
26173
|
+
},
|
|
26174
|
+
kind: {
|
|
26175
|
+
type: "string",
|
|
26176
|
+
enum: ["all", "topic", "article", "summary"],
|
|
26177
|
+
description: "Document kind to search; defaults to all"
|
|
26178
|
+
},
|
|
26179
|
+
limit: {
|
|
26180
|
+
type: "number",
|
|
26181
|
+
minimum: 1,
|
|
26182
|
+
maximum: 20,
|
|
26183
|
+
description: "Maximum number of candidates; defaults to 8"
|
|
25595
26184
|
}
|
|
25596
26185
|
},
|
|
25597
26186
|
required: ["question"]
|
|
25598
26187
|
}
|
|
25599
26188
|
},
|
|
26189
|
+
{
|
|
26190
|
+
name: "wiki_read",
|
|
26191
|
+
description: "Read one topic, article, or summary returned by wiki_query. For a clearly matching continuing persona, set adopt=true when reading a topic.",
|
|
26192
|
+
inputSchema: {
|
|
26193
|
+
type: "object",
|
|
26194
|
+
properties: {
|
|
26195
|
+
kind: { type: "string", enum: ["topic", "article", "summary"] },
|
|
26196
|
+
key: { type: "string", description: "Canonical key returned by wiki_query" },
|
|
26197
|
+
adopt: {
|
|
26198
|
+
type: "boolean",
|
|
26199
|
+
description: "For kind=topic, persist this canonical memory key for the current room"
|
|
26200
|
+
}
|
|
26201
|
+
},
|
|
26202
|
+
required: ["kind", "key"]
|
|
26203
|
+
}
|
|
26204
|
+
},
|
|
25600
26205
|
{
|
|
25601
26206
|
name: "wiki_topic_brief",
|
|
25602
26207
|
description: "Get the lightweight topic brief for a specific topic.",
|
|
@@ -30914,6 +31519,7 @@ var init_commands = __esm(() => {
|
|
|
30914
31519
|
{ name: "new", usage: "/new", description: "reset the current session" },
|
|
30915
31520
|
{ name: "compact", usage: "/compact", description: "summarize and shrink context" },
|
|
30916
31521
|
{ name: "status", usage: "/status", description: "show model and token usage" },
|
|
31522
|
+
{ name: "context", usage: "/context", description: "show live context breakdown" },
|
|
30917
31523
|
{ name: "model", usage: "/model", description: "choose the model" },
|
|
30918
31524
|
{ name: "effort", usage: "/effort", description: "choose reasoning effort" },
|
|
30919
31525
|
{ name: "topics", usage: "/topics", description: "open topic picker" },
|
|
@@ -30942,297 +31548,6 @@ var init_commands = __esm(() => {
|
|
|
30942
31548
|
];
|
|
30943
31549
|
});
|
|
30944
31550
|
|
|
30945
|
-
// ../../adapters/terminal/src/path-suggest.ts
|
|
30946
|
-
import { execFile as execFile3 } from "child_process";
|
|
30947
|
-
import { existsSync as existsSync33, readdirSync as readdirSync14, statSync as statSync19 } from "fs";
|
|
30948
|
-
import { homedir as homedir10 } from "os";
|
|
30949
|
-
import { basename as basename11, dirname as dirname19, join as join40, sep as sep3 } from "path";
|
|
30950
|
-
import { promisify as promisify3 } from "util";
|
|
30951
|
-
function indexRecursivePaths(files) {
|
|
30952
|
-
const paths = [];
|
|
30953
|
-
const directories = new Set;
|
|
30954
|
-
for (const relPath of files) {
|
|
30955
|
-
const segments = relPath.split("/");
|
|
30956
|
-
if (segments.some((segment) => segment.startsWith(".")))
|
|
30957
|
-
continue;
|
|
30958
|
-
for (let index = 0;index < segments.length - 1; index += 1) {
|
|
30959
|
-
const directory = segments.slice(0, index + 1).join("/");
|
|
30960
|
-
if (directories.has(directory))
|
|
30961
|
-
continue;
|
|
30962
|
-
directories.add(directory);
|
|
30963
|
-
paths.push({
|
|
30964
|
-
relPath: directory,
|
|
30965
|
-
isDir: true,
|
|
30966
|
-
lowerName: (segments[index] ?? "").toLowerCase()
|
|
30967
|
-
});
|
|
30968
|
-
}
|
|
30969
|
-
paths.push({ relPath, isDir: false, lowerName: (segments.at(-1) ?? "").toLowerCase() });
|
|
30970
|
-
}
|
|
30971
|
-
return paths;
|
|
30972
|
-
}
|
|
30973
|
-
async function listFilesWithRipgrep(dir) {
|
|
30974
|
-
const args = ["--files", "--max-depth", String(RECURSIVE_MAX_DEPTH)];
|
|
30975
|
-
for (const excluded of RECURSIVE_EXCLUDES)
|
|
30976
|
-
args.push("-g", `!${excluded}`);
|
|
30977
|
-
args.push(".");
|
|
30978
|
-
let out = "";
|
|
30979
|
-
try {
|
|
30980
|
-
const result = await execFileAsync3("rg", args, {
|
|
30981
|
-
cwd: dir,
|
|
30982
|
-
encoding: "utf8",
|
|
30983
|
-
timeout: RECURSIVE_TIMEOUT_MS,
|
|
30984
|
-
maxBuffer: 32 * 1024 * 1024
|
|
30985
|
-
});
|
|
30986
|
-
out = result.stdout;
|
|
30987
|
-
} catch (err2) {
|
|
30988
|
-
const stdout = err2?.stdout;
|
|
30989
|
-
out = typeof stdout === "string" ? stdout : "";
|
|
30990
|
-
}
|
|
30991
|
-
return out.split(`
|
|
30992
|
-
`).filter(Boolean).map((path) => path.replaceAll(sep3, "/").replace(/^\.\//, ""));
|
|
30993
|
-
}
|
|
30994
|
-
function cacheRecursivePaths(dir, files) {
|
|
30995
|
-
const now = Date.now();
|
|
30996
|
-
for (const [key, entry] of recursiveCache) {
|
|
30997
|
-
if (now - entry.loadedAt >= RECURSIVE_CACHE_TTL_MS)
|
|
30998
|
-
recursiveCache.delete(key);
|
|
30999
|
-
}
|
|
31000
|
-
recursiveCache.delete(dir);
|
|
31001
|
-
while (recursiveCache.size >= MAX_RECURSIVE_CACHE_ENTRIES) {
|
|
31002
|
-
const oldest = recursiveCache.keys().next().value;
|
|
31003
|
-
if (oldest === undefined)
|
|
31004
|
-
break;
|
|
31005
|
-
recursiveCache.delete(oldest);
|
|
31006
|
-
}
|
|
31007
|
-
recursiveCache.set(dir, {
|
|
31008
|
-
loadedAt: now,
|
|
31009
|
-
paths: indexRecursivePaths(files),
|
|
31010
|
-
matches: new Map
|
|
31011
|
-
});
|
|
31012
|
-
}
|
|
31013
|
-
function loadFilesRecursive(dir, loader) {
|
|
31014
|
-
const cached = recursiveCache.get(dir);
|
|
31015
|
-
if (cached && Date.now() - cached.loadedAt < RECURSIVE_CACHE_TTL_MS) {
|
|
31016
|
-
recursiveCache.delete(dir);
|
|
31017
|
-
recursiveCache.set(dir, cached);
|
|
31018
|
-
return Promise.resolve(false);
|
|
31019
|
-
}
|
|
31020
|
-
const active2 = recursiveSearches.get(dir);
|
|
31021
|
-
if (active2)
|
|
31022
|
-
return active2;
|
|
31023
|
-
const search = (async () => {
|
|
31024
|
-
try {
|
|
31025
|
-
cacheRecursivePaths(dir, await loader(dir));
|
|
31026
|
-
} catch {
|
|
31027
|
-
cacheRecursivePaths(dir, []);
|
|
31028
|
-
}
|
|
31029
|
-
return true;
|
|
31030
|
-
})().finally(() => recursiveSearches.delete(dir));
|
|
31031
|
-
recursiveSearches.set(dir, search);
|
|
31032
|
-
return search;
|
|
31033
|
-
}
|
|
31034
|
-
function activeAtToken(lineText, col) {
|
|
31035
|
-
const upto = lineText.slice(0, col);
|
|
31036
|
-
const match = /(?:^|\s)@([^\s]*)$/.exec(upto);
|
|
31037
|
-
if (!match)
|
|
31038
|
-
return null;
|
|
31039
|
-
const frag = match[1] ?? "";
|
|
31040
|
-
return { start: col - frag.length - 1, frag };
|
|
31041
|
-
}
|
|
31042
|
-
function resolveFragment(frag) {
|
|
31043
|
-
const home = homedir10();
|
|
31044
|
-
let path;
|
|
31045
|
-
if (frag === "" || frag === "~")
|
|
31046
|
-
path = home;
|
|
31047
|
-
else if (frag === "~/")
|
|
31048
|
-
path = home;
|
|
31049
|
-
else if (frag.startsWith("~/"))
|
|
31050
|
-
path = join40(home, frag.slice(2));
|
|
31051
|
-
else if (frag.startsWith("/"))
|
|
31052
|
-
path = frag;
|
|
31053
|
-
else
|
|
31054
|
-
path = join40(home, frag);
|
|
31055
|
-
if (frag.endsWith("/") || frag === "" || frag === "~") {
|
|
31056
|
-
return { dir: path, prefix: "" };
|
|
31057
|
-
}
|
|
31058
|
-
return { dir: dirname19(path), prefix: basename11(path) };
|
|
31059
|
-
}
|
|
31060
|
-
function toToken(fullPath, isDir) {
|
|
31061
|
-
const home = homedir10();
|
|
31062
|
-
let shown = fullPath;
|
|
31063
|
-
if (fullPath === home)
|
|
31064
|
-
shown = "~";
|
|
31065
|
-
else if (fullPath.startsWith(`${home}/`))
|
|
31066
|
-
shown = `~/${fullPath.slice(home.length + 1)}`;
|
|
31067
|
-
return `@${shown}${isDir ? "/" : ""}`;
|
|
31068
|
-
}
|
|
31069
|
-
function rankAndSlice(dir, candidates) {
|
|
31070
|
-
candidates.sort((a, b) => {
|
|
31071
|
-
if (a.isPrefixMatch !== b.isPrefixMatch)
|
|
31072
|
-
return a.isPrefixMatch ? -1 : 1;
|
|
31073
|
-
if (a.isDir !== b.isDir)
|
|
31074
|
-
return a.isDir ? -1 : 1;
|
|
31075
|
-
const depthA = a.relPath.split("/").length;
|
|
31076
|
-
const depthB = b.relPath.split("/").length;
|
|
31077
|
-
if (depthA !== depthB)
|
|
31078
|
-
return depthA - depthB;
|
|
31079
|
-
return a.relPath.localeCompare(b.relPath);
|
|
31080
|
-
});
|
|
31081
|
-
return candidates.slice(0, MAX_SUGGESTIONS).map(({ relPath, isDir }) => ({
|
|
31082
|
-
label: `${relPath}${isDir ? "/" : ""}`,
|
|
31083
|
-
value: toToken(join40(dir, relPath), isDir),
|
|
31084
|
-
isDir
|
|
31085
|
-
}));
|
|
31086
|
-
}
|
|
31087
|
-
function recursiveCandidates(dir, lowerPrefix, seen) {
|
|
31088
|
-
const entry = recursiveCache.get(dir);
|
|
31089
|
-
if (!entry)
|
|
31090
|
-
return [];
|
|
31091
|
-
let matches = entry.matches.get(lowerPrefix);
|
|
31092
|
-
if (!matches) {
|
|
31093
|
-
matches = entry.paths.filter((path) => path.lowerName.includes(lowerPrefix)).map((path) => ({
|
|
31094
|
-
relPath: path.relPath,
|
|
31095
|
-
isDir: path.isDir,
|
|
31096
|
-
isPrefixMatch: path.lowerName.startsWith(lowerPrefix)
|
|
31097
|
-
}));
|
|
31098
|
-
if (entry.matches.size >= MAX_MATCH_CACHE_ENTRIES) {
|
|
31099
|
-
const oldest = entry.matches.keys().next().value;
|
|
31100
|
-
if (oldest !== undefined)
|
|
31101
|
-
entry.matches.delete(oldest);
|
|
31102
|
-
}
|
|
31103
|
-
entry.matches.set(lowerPrefix, matches);
|
|
31104
|
-
} else {
|
|
31105
|
-
entry.matches.delete(lowerPrefix);
|
|
31106
|
-
entry.matches.set(lowerPrefix, matches);
|
|
31107
|
-
}
|
|
31108
|
-
return matches.filter((candidate) => {
|
|
31109
|
-
if (seen.has(candidate.relPath))
|
|
31110
|
-
return false;
|
|
31111
|
-
seen.add(candidate.relPath);
|
|
31112
|
-
return true;
|
|
31113
|
-
});
|
|
31114
|
-
}
|
|
31115
|
-
function isRecursivePathQuery(lineText, col) {
|
|
31116
|
-
const token = activeAtToken(lineText, col);
|
|
31117
|
-
if (!token)
|
|
31118
|
-
return false;
|
|
31119
|
-
const { prefix } = resolveFragment(token.frag);
|
|
31120
|
-
return !prefix.startsWith(".") && prefix.length >= RECURSIVE_MIN_PREFIX_LEN;
|
|
31121
|
-
}
|
|
31122
|
-
async function warmPathSuggestions(lineText, col, loader = listFilesWithRipgrep) {
|
|
31123
|
-
if (!isRecursivePathQuery(lineText, col))
|
|
31124
|
-
return false;
|
|
31125
|
-
const token = activeAtToken(lineText, col);
|
|
31126
|
-
if (!token)
|
|
31127
|
-
return false;
|
|
31128
|
-
const { dir } = resolveFragment(token.frag);
|
|
31129
|
-
try {
|
|
31130
|
-
if (!statSync19(dir).isDirectory())
|
|
31131
|
-
return false;
|
|
31132
|
-
} catch {
|
|
31133
|
-
return false;
|
|
31134
|
-
}
|
|
31135
|
-
return loadFilesRecursive(dir, loader);
|
|
31136
|
-
}
|
|
31137
|
-
function pathSuggestions(lineText, col) {
|
|
31138
|
-
const token = activeAtToken(lineText, col);
|
|
31139
|
-
if (!token)
|
|
31140
|
-
return null;
|
|
31141
|
-
const { dir, prefix } = resolveFragment(token.frag);
|
|
31142
|
-
let entries;
|
|
31143
|
-
try {
|
|
31144
|
-
entries = readdirSync14(dir, { withFileTypes: true });
|
|
31145
|
-
} catch {
|
|
31146
|
-
return { start: token.start, end: col, items: [], truncated: 0, searching: false };
|
|
31147
|
-
}
|
|
31148
|
-
const lowerPrefix = prefix.toLowerCase();
|
|
31149
|
-
const showHidden = prefix.startsWith(".");
|
|
31150
|
-
const candidates = entries.filter((entry) => {
|
|
31151
|
-
const name = entry.name;
|
|
31152
|
-
if (!showHidden && name.startsWith("."))
|
|
31153
|
-
return false;
|
|
31154
|
-
if (lowerPrefix === "")
|
|
31155
|
-
return true;
|
|
31156
|
-
const lowerName = name.toLowerCase();
|
|
31157
|
-
return showHidden ? lowerName.startsWith(lowerPrefix) : lowerName.includes(lowerPrefix);
|
|
31158
|
-
}).map((entry) => {
|
|
31159
|
-
let isDir = entry.isDirectory();
|
|
31160
|
-
if (!isDir && entry.isSymbolicLink()) {
|
|
31161
|
-
try {
|
|
31162
|
-
isDir = statSync19(join40(dir, entry.name)).isDirectory();
|
|
31163
|
-
} catch {}
|
|
31164
|
-
}
|
|
31165
|
-
return {
|
|
31166
|
-
relPath: entry.name,
|
|
31167
|
-
isDir,
|
|
31168
|
-
isPrefixMatch: entry.name.toLowerCase().startsWith(lowerPrefix)
|
|
31169
|
-
};
|
|
31170
|
-
});
|
|
31171
|
-
if (!showHidden && lowerPrefix.length >= RECURSIVE_MIN_PREFIX_LEN) {
|
|
31172
|
-
const seen = new Set(candidates.map((c) => c.relPath));
|
|
31173
|
-
candidates.push(...recursiveCandidates(dir, lowerPrefix, seen));
|
|
31174
|
-
}
|
|
31175
|
-
const items = rankAndSlice(dir, candidates);
|
|
31176
|
-
return {
|
|
31177
|
-
start: token.start,
|
|
31178
|
-
end: col,
|
|
31179
|
-
items,
|
|
31180
|
-
truncated: Math.max(0, candidates.length - items.length),
|
|
31181
|
-
searching: recursiveSearches.has(dir)
|
|
31182
|
-
};
|
|
31183
|
-
}
|
|
31184
|
-
function fragmentToAbsolutePath(frag) {
|
|
31185
|
-
const home = homedir10();
|
|
31186
|
-
if (frag === "~" || frag === "~/")
|
|
31187
|
-
return home;
|
|
31188
|
-
if (frag.startsWith("~/"))
|
|
31189
|
-
return join40(home, frag.slice(2));
|
|
31190
|
-
if (frag.startsWith("/"))
|
|
31191
|
-
return frag;
|
|
31192
|
-
return join40(home, frag);
|
|
31193
|
-
}
|
|
31194
|
-
function fragmentResolves(frag) {
|
|
31195
|
-
try {
|
|
31196
|
-
return existsSync33(fragmentToAbsolutePath(frag));
|
|
31197
|
-
} catch {
|
|
31198
|
-
return false;
|
|
31199
|
-
}
|
|
31200
|
-
}
|
|
31201
|
-
function stripResolvedPathTriggers(text2) {
|
|
31202
|
-
return text2.replaceAll(AT_PATH_TOKEN, (whole, lead, frag) => {
|
|
31203
|
-
if (fragmentResolves(frag))
|
|
31204
|
-
return `${lead}${frag}`;
|
|
31205
|
-
const pathPart = frag.replace(TRAILING_PUNCT, "");
|
|
31206
|
-
if (pathPart && pathPart !== frag && fragmentResolves(pathPart)) {
|
|
31207
|
-
return `${lead}${pathPart}${frag.slice(pathPart.length)}`;
|
|
31208
|
-
}
|
|
31209
|
-
return whole;
|
|
31210
|
-
});
|
|
31211
|
-
}
|
|
31212
|
-
function completePathToken(lineText, col, suggestion, options = {}) {
|
|
31213
|
-
const token = activeAtToken(lineText, col);
|
|
31214
|
-
if (!token)
|
|
31215
|
-
return null;
|
|
31216
|
-
let tokenEnd = col;
|
|
31217
|
-
while (tokenEnd < lineText.length && !/\s/.test(lineText[tokenEnd] ?? " "))
|
|
31218
|
-
tokenEnd += 1;
|
|
31219
|
-
const trailingText = lineText.slice(col, tokenEnd);
|
|
31220
|
-
const pathTail = trailingText.replace(TRAILING_PUNCT, "");
|
|
31221
|
-
const end = col + pathTail.length;
|
|
31222
|
-
const replacement = options.keepTrigger ? suggestion.value : suggestion.value.slice(1);
|
|
31223
|
-
const line = lineText.slice(0, token.start) + replacement + lineText.slice(end);
|
|
31224
|
-
return { line, col: token.start + replacement.length };
|
|
31225
|
-
}
|
|
31226
|
-
var MAX_SUGGESTIONS = 8, RECURSIVE_MIN_PREFIX_LEN = 4, RECURSIVE_MAX_DEPTH = 10, RECURSIVE_TIMEOUT_MS = 2000, RECURSIVE_CACHE_TTL_MS = 30000, MAX_RECURSIVE_CACHE_ENTRIES = 3, MAX_MATCH_CACHE_ENTRIES = 24, RECURSIVE_EXCLUDES, recursiveCache, recursiveSearches, execFileAsync3, AT_PATH_TOKEN, TRAILING_PUNCT;
|
|
31227
|
-
var init_path_suggest = __esm(() => {
|
|
31228
|
-
RECURSIVE_EXCLUDES = ["node_modules", ".git", ".cache", "dist", "build", "Library", ".Trash"];
|
|
31229
|
-
recursiveCache = new Map;
|
|
31230
|
-
recursiveSearches = new Map;
|
|
31231
|
-
execFileAsync3 = promisify3(execFile3);
|
|
31232
|
-
AT_PATH_TOKEN = /(^|[\s([{<])@([^\s]+)/g;
|
|
31233
|
-
TRAILING_PUNCT = /[.,;:!?)\]}>"'`]+$/;
|
|
31234
|
-
});
|
|
31235
|
-
|
|
31236
31551
|
// ../../adapters/terminal/src/subagent-graph.ts
|
|
31237
31552
|
import {
|
|
31238
31553
|
layoutTerminalGraph,
|
|
@@ -31867,6 +32182,19 @@ function applyAiStatus(state, topicId, raw, createdAt) {
|
|
|
31867
32182
|
tools: []
|
|
31868
32183
|
});
|
|
31869
32184
|
}
|
|
32185
|
+
if (kind === "context_progress") {
|
|
32186
|
+
if (isStaleTerminalStatus(current3, status))
|
|
32187
|
+
return state;
|
|
32188
|
+
return setActivity(state, topicId, {
|
|
32189
|
+
...liveCurrent,
|
|
32190
|
+
running: true,
|
|
32191
|
+
queryId: typeof status.queryId === "string" ? status.queryId : current3.queryId,
|
|
32192
|
+
contextProgress: {
|
|
32193
|
+
assistantTokens: Math.max(0, Number(status.assistantTokens) || 0),
|
|
32194
|
+
toolTokens: Math.max(0, Number(status.toolTokens) || 0)
|
|
32195
|
+
}
|
|
32196
|
+
});
|
|
32197
|
+
}
|
|
31870
32198
|
if (kind === "ai_done") {
|
|
31871
32199
|
if (isStaleTerminalStatus(current3, status))
|
|
31872
32200
|
return state;
|
|
@@ -31980,6 +32308,356 @@ var init_state2 = __esm(async () => {
|
|
|
31980
32308
|
await init_src();
|
|
31981
32309
|
});
|
|
31982
32310
|
|
|
32311
|
+
// ../../adapters/terminal/src/context-usage.ts
|
|
32312
|
+
function latestUsageMessage(messages) {
|
|
32313
|
+
for (let index = messages.length - 1;index >= 0; index--) {
|
|
32314
|
+
const usage = messages[index]?.authorId === "ai" ? messages[index]?.usage : undefined;
|
|
32315
|
+
if (usage?.context !== undefined && usage.contextWindow)
|
|
32316
|
+
return { index, usage };
|
|
32317
|
+
}
|
|
32318
|
+
return;
|
|
32319
|
+
}
|
|
32320
|
+
function estimateMessages(messages, predicate) {
|
|
32321
|
+
return messages.reduce((sum, message) => predicate(message) ? sum + 4 + estimateTextTokens(message.text) : sum, 0);
|
|
32322
|
+
}
|
|
32323
|
+
function activeContextBreakdown(state) {
|
|
32324
|
+
const topic = activeTopic(state);
|
|
32325
|
+
if (!topic)
|
|
32326
|
+
return;
|
|
32327
|
+
const messages = activeMessages(state);
|
|
32328
|
+
const latest = latestUsageMessage(messages);
|
|
32329
|
+
const stored = state.topicUsage[topic.id]?.currentSession;
|
|
32330
|
+
const confirmed = latest?.usage.context ?? stored?.contextTokens;
|
|
32331
|
+
const contextWindow = latest?.usage.contextWindow ?? stored?.contextWindow;
|
|
32332
|
+
if (confirmed === undefined || !contextWindow)
|
|
32333
|
+
return;
|
|
32334
|
+
const activity = state.activity[topic.id];
|
|
32335
|
+
if (!activity?.running) {
|
|
32336
|
+
return {
|
|
32337
|
+
context: confirmed,
|
|
32338
|
+
contextWindow,
|
|
32339
|
+
confirmed,
|
|
32340
|
+
user: 0,
|
|
32341
|
+
assistant: 0,
|
|
32342
|
+
tools: 0,
|
|
32343
|
+
free: Math.max(0, contextWindow - confirmed),
|
|
32344
|
+
estimated: false
|
|
32345
|
+
};
|
|
32346
|
+
}
|
|
32347
|
+
const afterBaseline = latest ? messages.slice(latest.index + 1) : stored?.timestamp ? messages.filter((message) => message.createdAt > stored.timestamp) : messages;
|
|
32348
|
+
const user = estimateMessages(afterBaseline, (message) => message.authorId !== "ai" && message.kind !== "system");
|
|
32349
|
+
const assistant = activity.contextProgress?.assistantTokens ?? estimateMessages(afterBaseline, (message) => message.authorId === "ai" && message.kind !== "tool");
|
|
32350
|
+
const tools = activity.contextProgress?.toolTokens ?? estimateMessages(afterBaseline, (message) => message.kind === "tool");
|
|
32351
|
+
const context = confirmed + user + assistant + tools;
|
|
32352
|
+
return {
|
|
32353
|
+
context,
|
|
32354
|
+
contextWindow,
|
|
32355
|
+
confirmed,
|
|
32356
|
+
user,
|
|
32357
|
+
assistant,
|
|
32358
|
+
tools,
|
|
32359
|
+
free: Math.max(0, contextWindow - context),
|
|
32360
|
+
estimated: true
|
|
32361
|
+
};
|
|
32362
|
+
}
|
|
32363
|
+
var init_context_usage = __esm(async () => {
|
|
32364
|
+
await __promiseAll([
|
|
32365
|
+
init_src(),
|
|
32366
|
+
init_state2()
|
|
32367
|
+
]);
|
|
32368
|
+
});
|
|
32369
|
+
|
|
32370
|
+
// ../../adapters/terminal/src/path-suggest.ts
|
|
32371
|
+
import { execFile as execFile3 } from "child_process";
|
|
32372
|
+
import { existsSync as existsSync33, readdirSync as readdirSync14, statSync as statSync19 } from "fs";
|
|
32373
|
+
import { homedir as homedir10 } from "os";
|
|
32374
|
+
import { basename as basename11, dirname as dirname19, join as join40, sep as sep3 } from "path";
|
|
32375
|
+
import { promisify as promisify3 } from "util";
|
|
32376
|
+
function indexRecursivePaths(files) {
|
|
32377
|
+
const paths = [];
|
|
32378
|
+
const directories = new Set;
|
|
32379
|
+
for (const relPath of files) {
|
|
32380
|
+
const segments = relPath.split("/");
|
|
32381
|
+
if (segments.some((segment) => segment.startsWith(".")))
|
|
32382
|
+
continue;
|
|
32383
|
+
for (let index = 0;index < segments.length - 1; index += 1) {
|
|
32384
|
+
const directory = segments.slice(0, index + 1).join("/");
|
|
32385
|
+
if (directories.has(directory))
|
|
32386
|
+
continue;
|
|
32387
|
+
directories.add(directory);
|
|
32388
|
+
paths.push({
|
|
32389
|
+
relPath: directory,
|
|
32390
|
+
isDir: true,
|
|
32391
|
+
lowerName: (segments[index] ?? "").toLowerCase()
|
|
32392
|
+
});
|
|
32393
|
+
}
|
|
32394
|
+
paths.push({ relPath, isDir: false, lowerName: (segments.at(-1) ?? "").toLowerCase() });
|
|
32395
|
+
}
|
|
32396
|
+
return paths;
|
|
32397
|
+
}
|
|
32398
|
+
async function listFilesWithRipgrep(dir) {
|
|
32399
|
+
const args = ["--files", "--max-depth", String(RECURSIVE_MAX_DEPTH)];
|
|
32400
|
+
for (const excluded of RECURSIVE_EXCLUDES)
|
|
32401
|
+
args.push("-g", `!${excluded}`);
|
|
32402
|
+
args.push(".");
|
|
32403
|
+
let out = "";
|
|
32404
|
+
try {
|
|
32405
|
+
const result = await execFileAsync3("rg", args, {
|
|
32406
|
+
cwd: dir,
|
|
32407
|
+
encoding: "utf8",
|
|
32408
|
+
timeout: RECURSIVE_TIMEOUT_MS,
|
|
32409
|
+
maxBuffer: 32 * 1024 * 1024
|
|
32410
|
+
});
|
|
32411
|
+
out = result.stdout;
|
|
32412
|
+
} catch (err2) {
|
|
32413
|
+
const stdout = err2?.stdout;
|
|
32414
|
+
out = typeof stdout === "string" ? stdout : "";
|
|
32415
|
+
}
|
|
32416
|
+
return out.split(`
|
|
32417
|
+
`).filter(Boolean).map((path) => path.replaceAll(sep3, "/").replace(/^\.\//, ""));
|
|
32418
|
+
}
|
|
32419
|
+
function cacheRecursivePaths(dir, files) {
|
|
32420
|
+
const now = Date.now();
|
|
32421
|
+
for (const [key, entry] of recursiveCache) {
|
|
32422
|
+
if (now - entry.loadedAt >= RECURSIVE_CACHE_TTL_MS)
|
|
32423
|
+
recursiveCache.delete(key);
|
|
32424
|
+
}
|
|
32425
|
+
recursiveCache.delete(dir);
|
|
32426
|
+
while (recursiveCache.size >= MAX_RECURSIVE_CACHE_ENTRIES) {
|
|
32427
|
+
const oldest = recursiveCache.keys().next().value;
|
|
32428
|
+
if (oldest === undefined)
|
|
32429
|
+
break;
|
|
32430
|
+
recursiveCache.delete(oldest);
|
|
32431
|
+
}
|
|
32432
|
+
recursiveCache.set(dir, {
|
|
32433
|
+
loadedAt: now,
|
|
32434
|
+
paths: indexRecursivePaths(files),
|
|
32435
|
+
matches: new Map
|
|
32436
|
+
});
|
|
32437
|
+
}
|
|
32438
|
+
function loadFilesRecursive(dir, loader) {
|
|
32439
|
+
const cached = recursiveCache.get(dir);
|
|
32440
|
+
if (cached && Date.now() - cached.loadedAt < RECURSIVE_CACHE_TTL_MS) {
|
|
32441
|
+
recursiveCache.delete(dir);
|
|
32442
|
+
recursiveCache.set(dir, cached);
|
|
32443
|
+
return Promise.resolve(false);
|
|
32444
|
+
}
|
|
32445
|
+
const active2 = recursiveSearches.get(dir);
|
|
32446
|
+
if (active2)
|
|
32447
|
+
return active2;
|
|
32448
|
+
const search = (async () => {
|
|
32449
|
+
try {
|
|
32450
|
+
cacheRecursivePaths(dir, await loader(dir));
|
|
32451
|
+
} catch {
|
|
32452
|
+
cacheRecursivePaths(dir, []);
|
|
32453
|
+
}
|
|
32454
|
+
return true;
|
|
32455
|
+
})().finally(() => recursiveSearches.delete(dir));
|
|
32456
|
+
recursiveSearches.set(dir, search);
|
|
32457
|
+
return search;
|
|
32458
|
+
}
|
|
32459
|
+
function activeAtToken(lineText, col) {
|
|
32460
|
+
const upto = lineText.slice(0, col);
|
|
32461
|
+
const match = /(?:^|\s)@([^\s]*)$/.exec(upto);
|
|
32462
|
+
if (!match)
|
|
32463
|
+
return null;
|
|
32464
|
+
const frag = match[1] ?? "";
|
|
32465
|
+
return { start: col - frag.length - 1, frag };
|
|
32466
|
+
}
|
|
32467
|
+
function resolveFragment(frag) {
|
|
32468
|
+
const home = homedir10();
|
|
32469
|
+
let path;
|
|
32470
|
+
if (frag === "" || frag === "~")
|
|
32471
|
+
path = home;
|
|
32472
|
+
else if (frag === "~/")
|
|
32473
|
+
path = home;
|
|
32474
|
+
else if (frag.startsWith("~/"))
|
|
32475
|
+
path = join40(home, frag.slice(2));
|
|
32476
|
+
else if (frag.startsWith("/"))
|
|
32477
|
+
path = frag;
|
|
32478
|
+
else
|
|
32479
|
+
path = join40(home, frag);
|
|
32480
|
+
if (frag.endsWith("/") || frag === "" || frag === "~") {
|
|
32481
|
+
return { dir: path, prefix: "" };
|
|
32482
|
+
}
|
|
32483
|
+
return { dir: dirname19(path), prefix: basename11(path) };
|
|
32484
|
+
}
|
|
32485
|
+
function toToken(fullPath, isDir) {
|
|
32486
|
+
const home = homedir10();
|
|
32487
|
+
let shown = fullPath;
|
|
32488
|
+
if (fullPath === home)
|
|
32489
|
+
shown = "~";
|
|
32490
|
+
else if (fullPath.startsWith(`${home}/`))
|
|
32491
|
+
shown = `~/${fullPath.slice(home.length + 1)}`;
|
|
32492
|
+
return `@${shown}${isDir ? "/" : ""}`;
|
|
32493
|
+
}
|
|
32494
|
+
function rankAndSlice(dir, candidates) {
|
|
32495
|
+
candidates.sort((a, b) => {
|
|
32496
|
+
if (a.isPrefixMatch !== b.isPrefixMatch)
|
|
32497
|
+
return a.isPrefixMatch ? -1 : 1;
|
|
32498
|
+
if (a.isDir !== b.isDir)
|
|
32499
|
+
return a.isDir ? -1 : 1;
|
|
32500
|
+
const depthA = a.relPath.split("/").length;
|
|
32501
|
+
const depthB = b.relPath.split("/").length;
|
|
32502
|
+
if (depthA !== depthB)
|
|
32503
|
+
return depthA - depthB;
|
|
32504
|
+
return a.relPath.localeCompare(b.relPath);
|
|
32505
|
+
});
|
|
32506
|
+
return candidates.slice(0, MAX_SUGGESTIONS).map(({ relPath, isDir }) => ({
|
|
32507
|
+
label: `${relPath}${isDir ? "/" : ""}`,
|
|
32508
|
+
value: toToken(join40(dir, relPath), isDir),
|
|
32509
|
+
isDir
|
|
32510
|
+
}));
|
|
32511
|
+
}
|
|
32512
|
+
function recursiveCandidates(dir, lowerPrefix, seen) {
|
|
32513
|
+
const entry = recursiveCache.get(dir);
|
|
32514
|
+
if (!entry)
|
|
32515
|
+
return [];
|
|
32516
|
+
let matches = entry.matches.get(lowerPrefix);
|
|
32517
|
+
if (!matches) {
|
|
32518
|
+
matches = entry.paths.filter((path) => path.lowerName.includes(lowerPrefix)).map((path) => ({
|
|
32519
|
+
relPath: path.relPath,
|
|
32520
|
+
isDir: path.isDir,
|
|
32521
|
+
isPrefixMatch: path.lowerName.startsWith(lowerPrefix)
|
|
32522
|
+
}));
|
|
32523
|
+
if (entry.matches.size >= MAX_MATCH_CACHE_ENTRIES) {
|
|
32524
|
+
const oldest = entry.matches.keys().next().value;
|
|
32525
|
+
if (oldest !== undefined)
|
|
32526
|
+
entry.matches.delete(oldest);
|
|
32527
|
+
}
|
|
32528
|
+
entry.matches.set(lowerPrefix, matches);
|
|
32529
|
+
} else {
|
|
32530
|
+
entry.matches.delete(lowerPrefix);
|
|
32531
|
+
entry.matches.set(lowerPrefix, matches);
|
|
32532
|
+
}
|
|
32533
|
+
return matches.filter((candidate) => {
|
|
32534
|
+
if (seen.has(candidate.relPath))
|
|
32535
|
+
return false;
|
|
32536
|
+
seen.add(candidate.relPath);
|
|
32537
|
+
return true;
|
|
32538
|
+
});
|
|
32539
|
+
}
|
|
32540
|
+
function isRecursivePathQuery(lineText, col) {
|
|
32541
|
+
const token = activeAtToken(lineText, col);
|
|
32542
|
+
if (!token)
|
|
32543
|
+
return false;
|
|
32544
|
+
const { prefix } = resolveFragment(token.frag);
|
|
32545
|
+
return !prefix.startsWith(".") && prefix.length >= RECURSIVE_MIN_PREFIX_LEN;
|
|
32546
|
+
}
|
|
32547
|
+
async function warmPathSuggestions(lineText, col, loader = listFilesWithRipgrep) {
|
|
32548
|
+
if (!isRecursivePathQuery(lineText, col))
|
|
32549
|
+
return false;
|
|
32550
|
+
const token = activeAtToken(lineText, col);
|
|
32551
|
+
if (!token)
|
|
32552
|
+
return false;
|
|
32553
|
+
const { dir } = resolveFragment(token.frag);
|
|
32554
|
+
try {
|
|
32555
|
+
if (!statSync19(dir).isDirectory())
|
|
32556
|
+
return false;
|
|
32557
|
+
} catch {
|
|
32558
|
+
return false;
|
|
32559
|
+
}
|
|
32560
|
+
return loadFilesRecursive(dir, loader);
|
|
32561
|
+
}
|
|
32562
|
+
function pathSuggestions(lineText, col) {
|
|
32563
|
+
const token = activeAtToken(lineText, col);
|
|
32564
|
+
if (!token)
|
|
32565
|
+
return null;
|
|
32566
|
+
const { dir, prefix } = resolveFragment(token.frag);
|
|
32567
|
+
let entries;
|
|
32568
|
+
try {
|
|
32569
|
+
entries = readdirSync14(dir, { withFileTypes: true });
|
|
32570
|
+
} catch {
|
|
32571
|
+
return { start: token.start, end: col, items: [], truncated: 0, searching: false };
|
|
32572
|
+
}
|
|
32573
|
+
const lowerPrefix = prefix.toLowerCase();
|
|
32574
|
+
const showHidden = prefix.startsWith(".");
|
|
32575
|
+
const candidates = entries.filter((entry) => {
|
|
32576
|
+
const name = entry.name;
|
|
32577
|
+
if (!showHidden && name.startsWith("."))
|
|
32578
|
+
return false;
|
|
32579
|
+
if (lowerPrefix === "")
|
|
32580
|
+
return true;
|
|
32581
|
+
const lowerName = name.toLowerCase();
|
|
32582
|
+
return showHidden ? lowerName.startsWith(lowerPrefix) : lowerName.includes(lowerPrefix);
|
|
32583
|
+
}).map((entry) => {
|
|
32584
|
+
let isDir = entry.isDirectory();
|
|
32585
|
+
if (!isDir && entry.isSymbolicLink()) {
|
|
32586
|
+
try {
|
|
32587
|
+
isDir = statSync19(join40(dir, entry.name)).isDirectory();
|
|
32588
|
+
} catch {}
|
|
32589
|
+
}
|
|
32590
|
+
return {
|
|
32591
|
+
relPath: entry.name,
|
|
32592
|
+
isDir,
|
|
32593
|
+
isPrefixMatch: entry.name.toLowerCase().startsWith(lowerPrefix)
|
|
32594
|
+
};
|
|
32595
|
+
});
|
|
32596
|
+
if (!showHidden && lowerPrefix.length >= RECURSIVE_MIN_PREFIX_LEN) {
|
|
32597
|
+
const seen = new Set(candidates.map((c) => c.relPath));
|
|
32598
|
+
candidates.push(...recursiveCandidates(dir, lowerPrefix, seen));
|
|
32599
|
+
}
|
|
32600
|
+
const items = rankAndSlice(dir, candidates);
|
|
32601
|
+
return {
|
|
32602
|
+
start: token.start,
|
|
32603
|
+
end: col,
|
|
32604
|
+
items,
|
|
32605
|
+
truncated: Math.max(0, candidates.length - items.length),
|
|
32606
|
+
searching: recursiveSearches.has(dir)
|
|
32607
|
+
};
|
|
32608
|
+
}
|
|
32609
|
+
function fragmentToAbsolutePath(frag) {
|
|
32610
|
+
const home = homedir10();
|
|
32611
|
+
if (frag === "~" || frag === "~/")
|
|
32612
|
+
return home;
|
|
32613
|
+
if (frag.startsWith("~/"))
|
|
32614
|
+
return join40(home, frag.slice(2));
|
|
32615
|
+
if (frag.startsWith("/"))
|
|
32616
|
+
return frag;
|
|
32617
|
+
return join40(home, frag);
|
|
32618
|
+
}
|
|
32619
|
+
function fragmentResolves(frag) {
|
|
32620
|
+
try {
|
|
32621
|
+
return existsSync33(fragmentToAbsolutePath(frag));
|
|
32622
|
+
} catch {
|
|
32623
|
+
return false;
|
|
32624
|
+
}
|
|
32625
|
+
}
|
|
32626
|
+
function stripResolvedPathTriggers(text2) {
|
|
32627
|
+
return text2.replaceAll(AT_PATH_TOKEN, (whole, lead, frag) => {
|
|
32628
|
+
if (fragmentResolves(frag))
|
|
32629
|
+
return `${lead}${frag}`;
|
|
32630
|
+
const pathPart = frag.replace(TRAILING_PUNCT, "");
|
|
32631
|
+
if (pathPart && pathPart !== frag && fragmentResolves(pathPart)) {
|
|
32632
|
+
return `${lead}${pathPart}${frag.slice(pathPart.length)}`;
|
|
32633
|
+
}
|
|
32634
|
+
return whole;
|
|
32635
|
+
});
|
|
32636
|
+
}
|
|
32637
|
+
function completePathToken(lineText, col, suggestion, options = {}) {
|
|
32638
|
+
const token = activeAtToken(lineText, col);
|
|
32639
|
+
if (!token)
|
|
32640
|
+
return null;
|
|
32641
|
+
let tokenEnd = col;
|
|
32642
|
+
while (tokenEnd < lineText.length && !/\s/.test(lineText[tokenEnd] ?? " "))
|
|
32643
|
+
tokenEnd += 1;
|
|
32644
|
+
const trailingText = lineText.slice(col, tokenEnd);
|
|
32645
|
+
const pathTail = trailingText.replace(TRAILING_PUNCT, "");
|
|
32646
|
+
const end = col + pathTail.length;
|
|
32647
|
+
const replacement = options.keepTrigger ? suggestion.value : suggestion.value.slice(1);
|
|
32648
|
+
const line = lineText.slice(0, token.start) + replacement + lineText.slice(end);
|
|
32649
|
+
return { line, col: token.start + replacement.length };
|
|
32650
|
+
}
|
|
32651
|
+
var MAX_SUGGESTIONS = 8, RECURSIVE_MIN_PREFIX_LEN = 4, RECURSIVE_MAX_DEPTH = 10, RECURSIVE_TIMEOUT_MS = 2000, RECURSIVE_CACHE_TTL_MS = 30000, MAX_RECURSIVE_CACHE_ENTRIES = 3, MAX_MATCH_CACHE_ENTRIES = 24, RECURSIVE_EXCLUDES, recursiveCache, recursiveSearches, execFileAsync3, AT_PATH_TOKEN, TRAILING_PUNCT;
|
|
32652
|
+
var init_path_suggest = __esm(() => {
|
|
32653
|
+
RECURSIVE_EXCLUDES = ["node_modules", ".git", ".cache", "dist", "build", "Library", ".Trash"];
|
|
32654
|
+
recursiveCache = new Map;
|
|
32655
|
+
recursiveSearches = new Map;
|
|
32656
|
+
execFileAsync3 = promisify3(execFile3);
|
|
32657
|
+
AT_PATH_TOKEN = /(^|[\s([{<])@([^\s]+)/g;
|
|
32658
|
+
TRAILING_PUNCT = /[.,;:!?)\]}>"'`]+$/;
|
|
32659
|
+
});
|
|
32660
|
+
|
|
31983
32661
|
// ../../adapters/terminal/src/terminal-width.ts
|
|
31984
32662
|
function stripAnsi(value) {
|
|
31985
32663
|
return value.replace(new RegExp("\\u001b\\]8;[^\\u0007\\u001b]*(?:\\u0007|\\u001b\\\\)", "g"), "").replace(new RegExp("\\u001b\\[[0-?]*[ -/]*[@-~]", "g"), "");
|
|
@@ -32811,9 +33489,9 @@ function helpLines() {
|
|
|
32811
33489
|
line(" Esc/Ctrl-C stop active turn \xB7 Ctrl-C twice when idle to quit"),
|
|
32812
33490
|
line(""),
|
|
32813
33491
|
line(" Commands", { fg: theme.cyan, bold: true }),
|
|
32814
|
-
line(" /new /model /effort /
|
|
32815
|
-
line(" /
|
|
32816
|
-
line(" /abort /help /quit", { fg: theme.muted })
|
|
33492
|
+
line(" /new /model /effort /status /context"),
|
|
33493
|
+
line(" /topics /public /private /fork /spawn"),
|
|
33494
|
+
line(" /del /copy /abort /help /quit", { fg: theme.muted })
|
|
32817
33495
|
];
|
|
32818
33496
|
}
|
|
32819
33497
|
function tokenCount(value) {
|
|
@@ -32828,19 +33506,17 @@ function tokenCount(value) {
|
|
|
32828
33506
|
function latestActiveUsage(state) {
|
|
32829
33507
|
return activeMessages(state).slice().reverse().find((message) => message.authorId === "ai" && message.usage)?.usage;
|
|
32830
33508
|
}
|
|
32831
|
-
function
|
|
32832
|
-
|
|
32833
|
-
|
|
32834
|
-
|
|
32835
|
-
|
|
32836
|
-
|
|
32837
|
-
const current3 = topic ? state.topicUsage[topic.id]?.currentSession : undefined;
|
|
32838
|
-
return current3 ? { context: current3.contextTokens, contextWindow: current3.contextWindow } : undefined;
|
|
33509
|
+
function contextUsageColor(ratio) {
|
|
33510
|
+
if (ratio !== undefined && ratio >= 90)
|
|
33511
|
+
return theme.red;
|
|
33512
|
+
if (ratio !== undefined && ratio >= 80)
|
|
33513
|
+
return theme.amber;
|
|
33514
|
+
return theme.text;
|
|
32839
33515
|
}
|
|
32840
33516
|
function statusLines(state) {
|
|
32841
33517
|
const topic = activeTopic(state);
|
|
32842
33518
|
const usage = latestActiveUsage(state);
|
|
32843
|
-
const contextUsage =
|
|
33519
|
+
const contextUsage = activeContextBreakdown(state);
|
|
32844
33520
|
const total = topic ? state.topicUsage[topic.id] : undefined;
|
|
32845
33521
|
const hasTopicUsage = total !== undefined && total.queries > 0;
|
|
32846
33522
|
const ratio = contextUsage?.context !== undefined && contextUsage.contextWindow ? Math.round(contextUsage.context / contextUsage.contextWindow * 100) : undefined;
|
|
@@ -32852,8 +33528,8 @@ function statusLines(state) {
|
|
|
32852
33528
|
line(` Model ${effectiveTopicModel(topic)}`),
|
|
32853
33529
|
line(` Effort ${topic?.effectiveEffort ?? topic?.defaultEffort ?? "-"}`),
|
|
32854
33530
|
line(""),
|
|
32855
|
-
line(` Context ${tokenCount(contextUsage?.context)} / ${tokenCount(contextUsage?.contextWindow)}${ratio === undefined ? "" : ` (${ratio}%)`}`, { fg: ratio
|
|
32856
|
-
line(" Measured on the latest model request", { fg: theme.muted, dim: true }),
|
|
33531
|
+
line(` Context ${contextUsage?.estimated ? "~" : ""}${tokenCount(contextUsage?.context)} / ${tokenCount(contextUsage?.contextWindow)}${ratio === undefined ? "" : ` (${ratio}%)`}`, { fg: contextUsageColor(ratio) }),
|
|
33532
|
+
line(contextUsage?.estimated ? " Live estimate; replaced by provider usage when the turn finishes" : " Measured on the latest model request", { fg: theme.muted, dim: true }),
|
|
32857
33533
|
line(""),
|
|
32858
33534
|
line(` Last turn input ${tokenCount(usage?.input)} \xB7 output ${tokenCount(usage?.output)}`),
|
|
32859
33535
|
line(` Cache read ${tokenCount(usage?.cachedInput)}`),
|
|
@@ -32873,6 +33549,31 @@ function statusLines(state) {
|
|
|
32873
33549
|
line(" Esc close", { fg: theme.muted })
|
|
32874
33550
|
];
|
|
32875
33551
|
}
|
|
33552
|
+
function contextLines(state) {
|
|
33553
|
+
const usage = activeContextBreakdown(state);
|
|
33554
|
+
const ratio = usage ? Math.round(usage.context / usage.contextWindow * 100) : undefined;
|
|
33555
|
+
const value = (tokens) => tokenCount(tokens);
|
|
33556
|
+
return [
|
|
33557
|
+
line(" Context", { fg: theme.accent, bold: true }),
|
|
33558
|
+
line(""),
|
|
33559
|
+
line(` ${usage?.estimated ? "Estimated" : "Current"} ${usage?.estimated ? "~" : ""}${value(usage?.context)} / ${value(usage?.contextWindow)}${ratio === undefined ? "" : ` (${ratio}%)`}`, { fg: contextUsageColor(ratio) }),
|
|
33560
|
+
line(""),
|
|
33561
|
+
line(" Breakdown", { fg: theme.cyan, bold: true }),
|
|
33562
|
+
line(` Confirmed ${value(usage?.confirmed)}`),
|
|
33563
|
+
line(` New user ${value(usage?.user)}`),
|
|
33564
|
+
line(` Assistant ${value(usage?.assistant)}`),
|
|
33565
|
+
line(` Tools ${value(usage?.tools)}`),
|
|
33566
|
+
line(` Free ${value(usage?.free)}`),
|
|
33567
|
+
line(""),
|
|
33568
|
+
line(usage?.estimated ? " Live estimate; final provider usage replaces it" : " Provider-confirmed latest request; live categories appear during a turn", { fg: theme.muted, dim: true }),
|
|
33569
|
+
line(" System prompt and tool schemas are included in Confirmed", {
|
|
33570
|
+
fg: theme.muted,
|
|
33571
|
+
dim: true
|
|
33572
|
+
}),
|
|
33573
|
+
line(""),
|
|
33574
|
+
line(" Esc close", { fg: theme.muted })
|
|
33575
|
+
];
|
|
33576
|
+
}
|
|
32876
33577
|
function subagentTreePrefix(topic, topics, rootTopicId) {
|
|
32877
33578
|
if (!topic.isSubagent || topic.id === rootTopicId)
|
|
32878
33579
|
return "";
|
|
@@ -33241,6 +33942,8 @@ function conversationLines(state, width, height, animationFrame = 0, nowMs = ter
|
|
|
33241
33942
|
return helpLines().slice(0, height);
|
|
33242
33943
|
if (state.overlay === "status")
|
|
33243
33944
|
return statusLines(state).slice(0, height);
|
|
33945
|
+
if (state.overlay === "context")
|
|
33946
|
+
return contextLines(state).slice(0, height);
|
|
33244
33947
|
if (state.overlay === "topics")
|
|
33245
33948
|
return topicOverlayLines(state, width, height, animationFrame).slice(0, height);
|
|
33246
33949
|
if (state.overlay === "subagents")
|
|
@@ -33461,35 +34164,71 @@ function footerStatusText(state) {
|
|
|
33461
34164
|
function footerUsageText(state) {
|
|
33462
34165
|
const topic = activeTopic(state);
|
|
33463
34166
|
if (!topic)
|
|
33464
|
-
return [""];
|
|
33465
|
-
const contextUsage =
|
|
34167
|
+
return [{ text: "", spans: [] }];
|
|
34168
|
+
const contextUsage = activeContextBreakdown(state);
|
|
33466
34169
|
const total = state.topicUsage[topic.id];
|
|
33467
34170
|
const ratio = contextUsage?.context !== undefined && contextUsage.contextWindow ? Math.round(contextUsage.context / contextUsage.contextWindow * 100) : undefined;
|
|
33468
|
-
const context = contextUsage?.context !== undefined && contextUsage.contextWindow ? `${tokenCount(contextUsage.context)}/${tokenCount(contextUsage.contextWindow)} ${ratio}%` : "";
|
|
34171
|
+
const context = contextUsage?.context !== undefined && contextUsage.contextWindow ? `${contextUsage.estimated ? "~" : ""}${tokenCount(contextUsage.context)}/${tokenCount(contextUsage.contextWindow)} ${ratio}%` : "";
|
|
33469
34172
|
const hasTopicUsage = total !== undefined && total.queries > 0;
|
|
33470
34173
|
const cumulative = hasTopicUsage ? `\u03A3 ${tokenCount(total.inputTokens)} in/${tokenCount(total.outputTokens)} out` : "";
|
|
33471
34174
|
const cache = hasTopicUsage && total.cacheReadInputTokens ? `cache ${tokenCount(total.cacheReadInputTokens)}` : "";
|
|
33472
34175
|
const cost = hasTopicUsage ? `est $${total.estimatedCostUsd.toFixed(2)}` : "";
|
|
33473
|
-
const
|
|
34176
|
+
const variant = (values2) => {
|
|
34177
|
+
const present = values2.filter((value) => value.text);
|
|
34178
|
+
const spans = present.flatMap((value, index) => [
|
|
34179
|
+
...index > 0 ? [{ text: " \xB7 ", fg: theme.subtle }] : [],
|
|
34180
|
+
{ text: value.text, fg: value.fg }
|
|
34181
|
+
]);
|
|
34182
|
+
if (spans.length > 0)
|
|
34183
|
+
spans.push({ text: " ", fg: theme.muted });
|
|
34184
|
+
return {
|
|
34185
|
+
text: `${present.map((value) => value.text).join(" \xB7 ")}${present.length ? " " : ""}`,
|
|
34186
|
+
spans
|
|
34187
|
+
};
|
|
34188
|
+
};
|
|
34189
|
+
const values = {
|
|
34190
|
+
context: { text: context, fg: contextUsageColor(ratio) },
|
|
34191
|
+
cumulative: { text: cumulative, fg: theme.muted },
|
|
34192
|
+
cache: { text: cache, fg: theme.muted },
|
|
34193
|
+
cost: { text: cost, fg: theme.muted }
|
|
34194
|
+
};
|
|
33474
34195
|
return [
|
|
33475
|
-
|
|
33476
|
-
|
|
33477
|
-
|
|
33478
|
-
context
|
|
33479
|
-
|
|
34196
|
+
variant([values.context, values.cumulative, values.cache, values.cost]),
|
|
34197
|
+
variant([values.context, values.cumulative, values.cache]),
|
|
34198
|
+
variant([values.context, values.cumulative]),
|
|
34199
|
+
variant([values.context]),
|
|
34200
|
+
variant([])
|
|
33480
34201
|
];
|
|
33481
34202
|
}
|
|
33482
34203
|
function footerStatusLine(state, width) {
|
|
33483
34204
|
const leftCandidates = footerStatusText(state);
|
|
33484
34205
|
const rightCandidates = footerUsageText(state);
|
|
33485
34206
|
for (const right of rightCandidates) {
|
|
33486
|
-
for (const
|
|
33487
|
-
|
|
33488
|
-
|
|
34207
|
+
for (const left2 of leftCandidates) {
|
|
34208
|
+
const rightWidth = displayWidth(right.text);
|
|
34209
|
+
if (displayWidth(left2) + (rightWidth ? 1 + rightWidth : 0) <= width) {
|
|
34210
|
+
const gap = width - displayWidth(left2) - rightWidth;
|
|
34211
|
+
return [
|
|
34212
|
+
{ text: left2, fg: theme.accent, bold: true },
|
|
34213
|
+
{ text: " ".repeat(gap), bg: theme.surfaceRaised },
|
|
34214
|
+
...right.spans
|
|
34215
|
+
];
|
|
33489
34216
|
}
|
|
33490
34217
|
}
|
|
33491
34218
|
}
|
|
33492
|
-
|
|
34219
|
+
const left = sliceWidth(leftCandidates[leftCandidates.length - 1] ?? "", width);
|
|
34220
|
+
return [
|
|
34221
|
+
{ text: left, fg: theme.accent, bold: true },
|
|
34222
|
+
{ text: " ".repeat(Math.max(0, width - displayWidth(left))), bg: theme.surfaceRaised }
|
|
34223
|
+
];
|
|
34224
|
+
}
|
|
34225
|
+
function paintFooterSpans(spans) {
|
|
34226
|
+
return spans.map((span) => paint(span.text, {
|
|
34227
|
+
fg: span.fg ?? theme.text,
|
|
34228
|
+
bg: span.bg ?? theme.surfaceRaised,
|
|
34229
|
+
bold: span.bold,
|
|
34230
|
+
dim: span.dim
|
|
34231
|
+
})).join("");
|
|
33493
34232
|
}
|
|
33494
34233
|
function noticeStyle(state) {
|
|
33495
34234
|
return NOTICE_STYLE[state.noticeLevel ?? "warn"];
|
|
@@ -33540,11 +34279,7 @@ function footerLines(state, width) {
|
|
|
33540
34279
|
];
|
|
33541
34280
|
}
|
|
33542
34281
|
return [
|
|
33543
|
-
|
|
33544
|
-
fg: theme.accent,
|
|
33545
|
-
bg: theme.surfaceRaised,
|
|
33546
|
-
bold: true
|
|
33547
|
-
}),
|
|
34282
|
+
paintFooterSpans(footerStatusLine(state, width)),
|
|
33548
34283
|
paint(joinSides("", pickFitting(footerHintText(state), width), width), {
|
|
33549
34284
|
fg: state.notice ? noticeStyle(state).fg : theme.muted,
|
|
33550
34285
|
bg: theme.canvas
|
|
@@ -33679,6 +34414,7 @@ var init_render = __esm(async () => {
|
|
|
33679
34414
|
init_terminal_width();
|
|
33680
34415
|
await __promiseAll([
|
|
33681
34416
|
init_src(),
|
|
34417
|
+
init_context_usage(),
|
|
33682
34418
|
init_state2()
|
|
33683
34419
|
]);
|
|
33684
34420
|
theme = {
|
|
@@ -34821,6 +35557,11 @@ async function runTerminalCommand(commandLine, context) {
|
|
|
34821
35557
|
context.queueRender();
|
|
34822
35558
|
return;
|
|
34823
35559
|
}
|
|
35560
|
+
if (command === "context") {
|
|
35561
|
+
context.state = { ...context.state, overlay: "context" };
|
|
35562
|
+
context.queueRender();
|
|
35563
|
+
return;
|
|
35564
|
+
}
|
|
34824
35565
|
if (command === "model") {
|
|
34825
35566
|
if (args.length > 0) {
|
|
34826
35567
|
context.state = { ...context.state, notice: "Usage: /model", noticeLevel: "warn" };
|
|
@@ -40784,6 +41525,7 @@ var init_canonical_mcp_bridge = __esm(async () => {
|
|
|
40784
41525
|
task: new Set(["task_create", "task_update", "task_list", "task_get", "task_delete"]),
|
|
40785
41526
|
wiki: new Set([
|
|
40786
41527
|
"wiki_query",
|
|
41528
|
+
"wiki_read",
|
|
40787
41529
|
"wiki_topic_brief",
|
|
40788
41530
|
"wiki_last_conversation",
|
|
40789
41531
|
"save_wiki_entry",
|
|
@@ -45650,4 +46392,4 @@ switch (command) {
|
|
|
45650
46392
|
}
|
|
45651
46393
|
}
|
|
45652
46394
|
|
|
45653
|
-
//# debugId=
|
|
46395
|
+
//# debugId=9061330203CC009264756E2164756E21
|