negotium 0.1.43 → 0.1.45

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.
Files changed (51) hide show
  1. package/dist/agent-helpers.js +168 -125
  2. package/dist/agent-helpers.js.map +13 -13
  3. package/dist/background-bash.js +1 -1
  4. package/dist/background-bash.js.map +3 -3
  5. package/dist/browser-runtime.js +1 -1
  6. package/dist/browser-runtime.js.map +3 -3
  7. package/dist/{chunk-4h0djgg0.js → chunk-c1bycsm0.js} +1 -1
  8. package/dist/{chunk-4h0djgg0.js.map → chunk-c1bycsm0.js.map} +3 -3
  9. package/dist/hosted-agent.js +2 -2
  10. package/dist/hosted-agent.js.map +4 -4
  11. package/dist/main.js +229 -129
  12. package/dist/main.js.map +16 -16
  13. package/dist/mcp-factories.js +645 -353
  14. package/dist/mcp-factories.js.map +13 -13
  15. package/dist/prompts.js +36 -23
  16. package/dist/prompts.js.map +5 -5
  17. package/dist/query-runtime.js +1 -1
  18. package/dist/query-runtime.js.map +3 -3
  19. package/dist/registry.js +1 -1
  20. package/dist/rollout.js +1 -1
  21. package/dist/runtime/src/agents/archiver.ts +194 -169
  22. package/dist/runtime/src/agents/mcp-tools/spawn-subagent.ts +17 -11
  23. package/dist/runtime/src/agents/model-catalog.ts +1 -1
  24. package/dist/runtime/src/mcp/canonical-proxy-server.ts +10 -0
  25. package/dist/runtime/src/mcp/wiki-server.ts +393 -102
  26. package/dist/runtime/src/platform/config.ts +14 -0
  27. package/dist/runtime/src/prompts/agents/wiki-archiver.md +86 -49
  28. package/dist/runtime/src/prompts/builders.ts +60 -35
  29. package/dist/runtime/src/prompts/sessions/_shared-tools.md +17 -0
  30. package/dist/runtime/src/prompts/sessions/channel-system.md +3 -39
  31. package/dist/runtime/src/prompts/sessions/manager-system.md +3 -6
  32. package/dist/runtime/src/prompts/sessions/topic-system.md +3 -42
  33. package/dist/runtime/src/runtime/turn-runner.ts +58 -40
  34. package/dist/runtime/src/storage/api-topic-brief.ts +11 -7
  35. package/dist/runtime/src/storage/wiki-summary-names.ts +22 -23
  36. package/dist/runtime/src/storage/wiki.ts +7 -4
  37. package/dist/runtime/src/version.ts +1 -1
  38. package/dist/runtime-helpers.js +1 -1
  39. package/dist/runtime-helpers.js.map +3 -3
  40. package/dist/storage.js +60 -52
  41. package/dist/storage.js.map +7 -7
  42. package/dist/types/packages/core/src/agents/archiver.d.ts +5 -0
  43. package/dist/types/packages/core/src/agents/model-catalog.d.ts +1 -1
  44. package/dist/types/packages/core/src/platform/config.d.ts +2 -0
  45. package/dist/types/packages/core/src/runtime/turn-runner.d.ts +6 -0
  46. package/dist/types/packages/core/src/storage/api-topic-brief.d.ts +3 -3
  47. package/dist/types/packages/core/src/storage/wiki-summary-names.d.ts +6 -8
  48. package/dist/types/packages/core/src/version.d.ts +1 -1
  49. package/dist/vault.js +1 -1
  50. package/dist/vault.js.map +3 -3
  51. package/package.json +2 -2
@@ -133,6 +133,10 @@ function resolveLocalStateDir(envKey, stateName) {
133
133
  function parsePortEnv(envValue, fallback) {
134
134
  return parseRuntimePort(envValue, fallback);
135
135
  }
136
+ function resolveOutputLanguage() {
137
+ const raw = envText("NEGOTIUM_LANG")?.trim();
138
+ return raw && raw.length > 0 ? raw : DEFAULT_OUTPUT_LANGUAGE;
139
+ }
136
140
  function resolveBrowserMcpBin(envValue) {
137
141
  const override = envValue?.trim();
138
142
  if (override) {
@@ -255,7 +259,7 @@ function resolveDefaultModel(agent, registryDefaultModel) {
255
259
  function codexAuthFilePath() {
256
260
  return process.env.NEGOTIUM_CODEX_AUTH_FILE || join(homedir(), ".codex", "auth.json");
257
261
  }
258
- var HOME, PROJECT_ROOT, STATE_DIR_ENV, STATE_DIR, WORKSPACE_DIR, TOPIC_WORKSPACE_DIR, SHARED_WIKI_DIR, CONTEXTS_DIR, BROWSER_PROFILES_DIR, DM_WORKSPACE_DIR, SESSION_WORKSPACE_DIR, CLAUDE_EXECUTABLE_ENV, CLAUDE_EXECUTABLE, PATCHRIGHT_MCP_BIN, PLAYWRIGHT_MCP_BIN, BROWSER_RS_VERSION = "v0.1.13", BROWSER_RS_MIN_SECURE_VERSION = "0.1.13", BROWSER_RS_BIN, TSX_BIN, TSCONFIG_PATH, SESSION_COMM_SERVER, TASK_SERVER, BROWSER_MCP_SSE_PROXY_SERVER, CANONICAL_MCP_PROXY_SERVER, WIKI_SERVER, TOKEN_STATS_SERVER, COMPACTION_LOG_SERVER, SYSTEM_HEALTH_SERVER, AGENT_HEALTH_SERVER, BACKGROUND_BASH_SERVER, VAULT_SERVER, BG_BASH_BASE_PORT, BG_BASH_MAX_PORT, RUNTIME_MCP_SECRET, NODE_CONTROL_TOKEN, VAULT_MASTER_KEY, NEGOTIUM_PORT, hostname, DATA_DIR, LOG_DIR, SESSIONS_DB, DEBUG_FILE, USERS_LOG_DIR, RUN_DIR, PROGRESS_DIR, DM_CMD_DIR, DM_RESP_DIR, SESSION_INBOX_DIR, SESSION_ASKS_DIR, PLAYWRIGHT_BASE_PORT, PLAYWRIGHT_MAX_PORT, PLAYWRIGHT_PORTS_DIR, ACTIVE_QUERY_STALE_MS, AGENTS_PROMPTS_DIR, RESOURCES_DIR, FILE_TAG_REGEX, MODEL_SONNET = "claude-sonnet-5", MODEL_OPUS = "claude-opus-5", MODEL_FABLE = "claude-fable-5", FALLBACK_AGENT, SESSION_AGENT, GATEWAY_AGENT, FALLBACK_MODEL, SESSION_MODEL, GATEWAY_MODEL, FFMPEG_BIN, FFPROBE_BIN, PYTHON_BIN, FASTER_WHISPER_WRAPPER, WHISPER_MODEL, TESSERACT_BIN, PDFTOTEXT_BIN, _envMaxTellDepth, MAX_TELL_DEPTH;
262
+ var HOME, PROJECT_ROOT, STATE_DIR_ENV, STATE_DIR, WORKSPACE_DIR, TOPIC_WORKSPACE_DIR, SHARED_WIKI_DIR, CONTEXTS_DIR, BROWSER_PROFILES_DIR, DM_WORKSPACE_DIR, SESSION_WORKSPACE_DIR, CLAUDE_EXECUTABLE_ENV, CLAUDE_EXECUTABLE, DEFAULT_OUTPUT_LANGUAGE = "English", PATCHRIGHT_MCP_BIN, PLAYWRIGHT_MCP_BIN, BROWSER_RS_VERSION = "v0.1.13", BROWSER_RS_MIN_SECURE_VERSION = "0.1.13", BROWSER_RS_BIN, TSX_BIN, TSCONFIG_PATH, SESSION_COMM_SERVER, TASK_SERVER, BROWSER_MCP_SSE_PROXY_SERVER, CANONICAL_MCP_PROXY_SERVER, WIKI_SERVER, TOKEN_STATS_SERVER, COMPACTION_LOG_SERVER, SYSTEM_HEALTH_SERVER, AGENT_HEALTH_SERVER, BACKGROUND_BASH_SERVER, VAULT_SERVER, BG_BASH_BASE_PORT, BG_BASH_MAX_PORT, RUNTIME_MCP_SECRET, NODE_CONTROL_TOKEN, VAULT_MASTER_KEY, NEGOTIUM_PORT, hostname, DATA_DIR, LOG_DIR, SESSIONS_DB, DEBUG_FILE, USERS_LOG_DIR, RUN_DIR, PROGRESS_DIR, DM_CMD_DIR, DM_RESP_DIR, SESSION_INBOX_DIR, SESSION_ASKS_DIR, PLAYWRIGHT_BASE_PORT, PLAYWRIGHT_MAX_PORT, PLAYWRIGHT_PORTS_DIR, ACTIVE_QUERY_STALE_MS, AGENTS_PROMPTS_DIR, RESOURCES_DIR, FILE_TAG_REGEX, MODEL_SONNET = "claude-sonnet-5", MODEL_OPUS = "claude-opus-5", MODEL_FABLE = "claude-fable-5", FALLBACK_AGENT, SESSION_AGENT, GATEWAY_AGENT, FALLBACK_MODEL, SESSION_MODEL, GATEWAY_MODEL, FFMPEG_BIN, FFPROBE_BIN, PYTHON_BIN, FASTER_WHISPER_WRAPPER, WHISPER_MODEL, TESSERACT_BIN, PDFTOTEXT_BIN, _envMaxTellDepth, MAX_TELL_DEPTH;
259
263
  var init_config = __esm(() => {
260
264
  init_config_helpers();
261
265
  init_logger();
@@ -2618,7 +2622,7 @@ var init_claude_provider = __esm(async () => {
2618
2622
  });
2619
2623
 
2620
2624
  // ../../packages/core/src/version.ts
2621
- var NEGOTIUM_VERSION = "0.1.43";
2625
+ var NEGOTIUM_VERSION = "0.1.45";
2622
2626
 
2623
2627
  // ../../packages/core/src/agents/codex-native-multi-agent.ts
2624
2628
  import { spawn as spawn3 } from "child_process";
@@ -5755,7 +5759,7 @@ function resolveCompactionExecution(agent, registry) {
5755
5759
  const effort = registry.validateEffort("medium") ? "medium" : registry.defaultEffort;
5756
5760
  return { model, ...effort ? { effort } : {} };
5757
5761
  }
5758
- var MODEL_OWNER, MODEL_COST_RESEARCHED_AT = "2026-07-19", MODEL_COST_ROUTING_SUMMARY = "Cost basis (2026-07-19): Codex Pro 20x and Claude Max 20x are each $200/month; Maestro models are pay-per-token. DeepSeek Pro is cheapest, Kimi K2.7 Code is the coding route, and Kimi K3 is the frontier route.", CODEX_PRO_20X_COST = "ChatGPT Pro 20x subscription: $200/month", CODEX_COMMUNITY_WEEKLY = "Community plan-level observation: roughly 2\u20134B raw/cached tokens per week; fresh-input equivalent is much lower and unstable (low confidence)", CLAUDE_MAX_20X_COST = "Claude Max 20x subscription: $200/month", CLAUDE_COMMUNITY_SESSION = "Community observations vary from roughly 220\u2013250K locally displayed tokens per 5-hour session to billions of cache-heavy raw tokens per week; calibrated reports value a full weekly allowance around $680\u2013$1,900 at API rates. Recent heavy-model reports reach the weekly cap after about 4\u20135 full sessions (low confidence; not a token cap)", SELECTABLE_MODELS, SELECTABLE_MODEL_ALIASES, FALLBACK_ORDER, AGENT_DISPLAY_NAME;
5762
+ var MODEL_OWNER, MODEL_COST_RESEARCHED_AT = "2026-07-19", MODEL_COST_ROUTING_SUMMARY = "Cost basis (2026-07-19): Codex Pro 20x and Claude Max 20x are each $200/month; Maestro models are pay-per-token. DeepSeek Pro is cheapest.", CODEX_PRO_20X_COST = "ChatGPT Pro 20x subscription: $200/month", CODEX_COMMUNITY_WEEKLY = "Community plan-level observation: roughly 2\u20134B raw/cached tokens per week; fresh-input equivalent is much lower and unstable (low confidence)", CLAUDE_MAX_20X_COST = "Claude Max 20x subscription: $200/month", CLAUDE_COMMUNITY_SESSION = "Community observations vary from roughly 220\u2013250K locally displayed tokens per 5-hour session to billions of cache-heavy raw tokens per week; calibrated reports value a full weekly allowance around $680\u2013$1,900 at API rates. Recent heavy-model reports reach the weekly cap after about 4\u20135 full sessions (low confidence; not a token cap)", SELECTABLE_MODELS, SELECTABLE_MODEL_ALIASES, FALLBACK_ORDER, AGENT_DISPLAY_NAME;
5759
5763
  var init_model_catalog = __esm(() => {
5760
5764
  init_config();
5761
5765
  MODEL_OWNER = {
@@ -5916,6 +5920,12 @@ function loadSessionPrompt(filename, fallback) {
5916
5920
  return fallback;
5917
5921
  }
5918
5922
  }
5923
+ function sharedToolsPartial() {
5924
+ if (_sharedToolsPartial === null) {
5925
+ _sharedToolsPartial = loadSessionPrompt("_shared-tools.md", "");
5926
+ }
5927
+ return _sharedToolsPartial;
5928
+ }
5919
5929
  function topicSystemPromptTemplate() {
5920
5930
  if (_topicSystemPromptTemplate === null) {
5921
5931
  _topicSystemPromptTemplate = loadSessionPrompt("topic-system.md", FALLBACK_TOPIC_SYSTEM_PROMPT_TEMPLATE);
@@ -6018,7 +6028,7 @@ function buildRuntimeToolSection(opts, extensions) {
6018
6028
  "",
6019
6029
  "## File Delivery",
6020
6030
  `To send a file to the user, save it under your working directory and call the ${sendFileTool} with { file_path: "<absolute path>" }.`,
6021
- "It appears as a downloadable attachment in the chat and returns success. Never claim file delivery is unavailable after a successful call."
6031
+ "A successful call delivers it as a chat attachment \u2014 never claim file delivery is unavailable after one."
6022
6032
  ] : [];
6023
6033
  const shared = [
6024
6034
  "",
@@ -6038,7 +6048,7 @@ function buildRuntimeToolSection(opts, extensions) {
6038
6048
  "",
6039
6049
  "## Shared Tasks",
6040
6050
  taskToolLine,
6041
- "Use this shared task store for plans, task progress, and checklist updates. It is visible across claude/codex/maestro turns and drives the live task panel.",
6051
+ "Use this shared task store for plans, progress, and checklist updates; it is visible across claude/codex/maestro turns.",
6042
6052
  nativeTaskPolicyLine,
6043
6053
  ...extensions.render("after-shared-tasks"),
6044
6054
  ...fileDeliverySection,
@@ -6050,7 +6060,7 @@ function buildRuntimeToolSection(opts, extensions) {
6050
6060
  subagentReportMode === "status-only" ? "`ask_session` is unavailable in subagent rooms. `tell_session` remains available only for non-completion communication to permitted targets." : "`ask_session` is unavailable in subagent rooms. Use one-way `tell_session` reporting instead."
6051
6061
  ] : [
6052
6062
  "The session-comm MCP server is the only cross-topic messaging surface. Its canonical tools are `list_sessions`, `peek_session`, `tell_session`, `ask_session`, and `abort_session`.",
6053
- "Use `list_sessions` to inspect available topics. Use `ask_session` for read-only questions whose result you need back in your own context. Use `tell_session` for one-way delegation or context handoff where no reply should return here. Do not describe `tell_session` as bidirectional and do not claim `ask_session` is unavailable without first checking the session-comm tools."
6063
+ "`list_sessions` inspects topics; `ask_session` is for read-only questions whose answer must return here; `tell_session` is one-way delegation/handoff with no reply back. Do not call `tell_session` bidirectional, and do not claim `ask_session` is unavailable without first checking the session-comm tools."
6054
6064
  ],
6055
6065
  ...agentKind === "maestro" ? [
6056
6066
  subagentParentTitle ? "Session-comm schemas may initially be deferred. Activate `mcp__session-comm__list_sessions` and `mcp__session-comm__tell_session` with ToolSearch before use." : 'Session-comm schemas may initially be deferred. Before using or judging availability, call ToolSearch("select:mcp__session-comm__list_sessions,mcp__session-comm__peek_session,mcp__session-comm__tell_session,mcp__session-comm__ask_session,mcp__session-comm__abort_session") to activate the exact tools. Never substitute a similarly described runtime tool.'
@@ -6068,17 +6078,14 @@ function buildRuntimeToolSection(opts, extensions) {
6068
6078
  `When the user explicitly asks to change the model, agent backend, or reasoning effort for THIS topic, call "${runtimeNamespace}__set_model", "${runtimeNamespace}__set_agent", or "${runtimeNamespace}__set_effort". The change applies from your NEXT turn. After calling, briefly confirm and the system will continue with the new setting.`,
6069
6079
  "`set_effort` is available but discouraged; use it only when the user explicitly requests an effort change.",
6070
6080
  "`set_model` may be called autonomously only when the current model is clearly below the task's required capability, such as complex algorithm design, proof-level math, or broad multi-file refactoring. Choose the best-fit model directly from the same-agent catalog; model selection is not a mandatory one-step ladder. End the turn after changing it. Do not use vague task complexity as a trigger.",
6071
- "`set_agent` autonomous calls are forbidden. Only switch agent when the user explicitly asks to switch runtime, e.g. \u201Ccodex\uB85C \uAC00\u201D, \u201Cclaude\uB85C \uBC14\uAFD4\u201D.",
6081
+ "`set_agent` autonomous calls are forbidden. Only switch agent when the user explicitly asks to switch runtime, e.g. \u201Cswitch to codex\u201D, \u201Cuse claude\u201D.",
6072
6082
  "Never use `fable` unless the user explicitly requests it; it is expensive.",
6073
6083
  "",
6074
6084
  "Model catalog (capability/cost routing guidance):",
6075
6085
  MODEL_COST_ROUTING_SUMMARY,
6076
6086
  ...modelCatalog,
6077
6087
  "",
6078
- "Accepted effort values:",
6079
- "- claude: `low`, `medium`, `high`, `xhigh`, `max`.",
6080
- "- codex: `low`, `medium`, `high`, `xhigh`, `max`.",
6081
- "- maestro: `low`, `medium`, `high`, `xhigh`, `max`.",
6088
+ "Accepted effort values (all agents): `low`, `medium`, `high`, `xhigh`, `max`.",
6082
6089
  "Agent guidance when the user explicitly asks to switch: `codex` for deepest reasoning and complex code/math; `claude` for tool-heavy MCP/file automation; `maestro` for inexpensive fast drafts and lighter experiments."
6083
6090
  ];
6084
6091
  if (agentKind !== "claude") {
@@ -6139,12 +6146,15 @@ function createPromptBuilders(host = {}) {
6139
6146
  const context = Object.freeze({ ...opts, sessionKind });
6140
6147
  const render = (slot) => sections.filter((section) => section.slot === slot).map((section) => section.render(context)?.trim()).filter((section) => Boolean(section)).flatMap((section) => ["", section]);
6141
6148
  const uploadsDir = `${opts.workspaceCwd}/attachments`;
6142
- let prompt = replaceVars(sessionTemplate, {
6149
+ const templateVars = {
6150
+ SHARED_TOOLS: sharedToolsPartial(),
6143
6151
  AI_LABEL: opts.aiLabel,
6144
6152
  TOPIC_TITLE: opts.topicTitle,
6145
6153
  WORKSPACE_CWD: opts.workspaceCwd,
6146
- UPLOADS_DIR: uploadsDir
6147
- }) + buildRuntimeToolSection({
6154
+ UPLOADS_DIR: uploadsDir,
6155
+ RESPONSE_LANGUAGE: resolveOutputLanguage()
6156
+ };
6157
+ let prompt = replaceVars(sessionTemplate, templateVars) + buildRuntimeToolSection({
6148
6158
  agentKind: opts.agentKind,
6149
6159
  canSpawnSubagents: sessionKind === "channel" ? false : opts.canSpawnSubagents,
6150
6160
  canStageSubagents: sessionKind === "channel" ? false : opts.canStageSubagents,
@@ -6170,13 +6180,14 @@ function createPromptBuilders(host = {}) {
6170
6180
  ${opts.description.trim()}`;
6171
6181
  }
6172
6182
  if (sessionKind === "manager") {
6173
- prompt += `
6174
-
6175
- ${template({
6183
+ const managerTemplate = replaceVars(template({
6176
6184
  kind: "manager-system",
6177
6185
  filename: "manager-system.md",
6178
6186
  fallback: FALLBACK_MANAGER_SYSTEM_PROMPT_TEMPLATE
6179
- })}`;
6187
+ }), templateVars);
6188
+ prompt += `
6189
+
6190
+ ${managerTemplate}`;
6180
6191
  }
6181
6192
  return `${prompt}${render("after-system-prompt").join(`
6182
6193
  `)}`;
@@ -6214,18 +6225,18 @@ function buildMemoryPromptSection(opts) {
6214
6225
  if (opts.latestSummaryFile) {
6215
6226
  parts.push(`Latest summary: ${opts.latestSummaryFile}`);
6216
6227
  }
6217
- parts.push("", opts.isManager ? opts.hasFiles ? "\uC704\uB294 \uC774 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC758 \uBA54\uBAA8\uB9AC \uD5C8\uBE0C \uBE0C\uB9AC\uD504\uC785\uB2C8\uB2E4(\uBAA8\uB4E0 \uD1A0\uD53D \uC544\uCE74\uC774\uBE0C\uAC00 \uB204\uC801\uB428). \uACFC\uAC70 \uB9E5\uB77D\xB7\uB2E4\uB978 \uD1A0\uD53D \uB0B4\uC6A9\uC744 \uBB3C\uC73C\uBA74 \uBA3C\uC800 \uC774 \uBE0C\uB9AC\uD504\uB97C \uCC38\uACE0\uD558\uACE0, \uB354 \uAE4A\uC740 \uAC80\uC0C9\uC740 `wiki_query` MCP \uB3C4\uAD6C\uB97C \uC0AC\uC6A9\uD558\uC138\uC694." : "\uC704\uC784\uB41C \uC791\uC5C5 \uCC98\uB9AC \uC911 \uACFC\uAC70 \uB9E5\uB77D\uC774 \uD544\uC694\uD558\uBA74 `wiki_query` MCP \uB3C4\uAD6C\uB97C \uC0AC\uC6A9\uD558\uC138\uC694." : opts.hasFiles ? "\uC704 \uD30C\uC77C\uC740 \uC774 \uD1A0\uD53D\uC758 Wiki \uBE0C\uB9AC\uD504\uC785\uB2C8\uB2E4. \uACFC\uAC70 \uB9E5\uB77D \uD30C\uC545 \uC2DC \uBA3C\uC800 Read\uB85C \uC77D\uC73C\uC138\uC694. \uB354 \uAE4A\uC740 \uAC80\uC0C9\uC774 \uD544\uC694\uD558\uBA74 `wiki_query` MCP \uB3C4\uAD6C\uB97C \uC0AC\uC6A9\uD558\uC138\uC694." : opts.latestSummaryFile ? "Wiki \uBE0C\uB9AC\uD504 \uD30C\uC77C\uC740 \uC544\uC9C1 \uC5C6\uC2B5\uB2C8\uB2E4. \uACFC\uAC70 \uB9E5\uB77D \uD30C\uC545 \uC2DC \uBA3C\uC800 Latest summary\uB97C Read\uB85C \uC77D\uC73C\uC138\uC694. \uB354 \uAE4A\uC740 \uAC80\uC0C9\uC774 \uD544\uC694\uD558\uBA74 `wiki_query` MCP \uB3C4\uAD6C\uB97C \uC0AC\uC6A9\uD558\uC138\uC694." : "Wiki \uBE0C\uB9AC\uD504 \uD30C\uC77C\uC740 \uC544\uC9C1 \uC5C6\uC2B5\uB2C8\uB2E4. \uACFC\uAC70 \uB9E5\uB77D \uD30C\uC545 \uC2DC `wiki_query` MCP \uB3C4\uAD6C\uB97C \uC0AC\uC6A9\uD558\uC138\uC694.");
6218
- parts.push("", opts.isManager ? "\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4 \uACFC\uAC70 \uACB0\uC815\uC774\uB098 \uB2E4\uB978 \uD1A0\uD53D \uB9E5\uB77D\uC744 \uB2F5\uD560 \uB54C\uB294 \uBA54\uBAA8\uB9AC\uB97C \uC790\uC5F0\uC2A4\uB7FD\uAC8C \uBC18\uC601\uD558\uB418, \uD655\uC2E4\uD558\uC9C0 \uC54A\uC73C\uBA74 `wiki_query`\uB85C \uD655\uC778\uD558\uC138\uC694." : opts.hasFiles || opts.latestSummaryFile ? "\uD1A0\uD53D \uCCAB \uC751\uB2F5 \uC2DC, Wiki \uBE0C\uB9AC\uD504" + (opts.latestSummaryFile ? "\uC640 Latest summary\uB97C Read\uB85C \uC77D\uACE0" : "\uB97C Read\uB85C \uC77D\uACE0") + " \uB9E5\uB77D \uC694\uC57D\uC744 \uC790\uC5F0\uC2A4\uB7FD\uAC8C \uD55C \uC904\uB85C \uC5B8\uAE09\uD558\uC138\uC694. \uC0AC\uC6A9\uC790\uAC00 \uB9E5\uB77D\uC774 \uB9DE\uB294\uC9C0 \uBC14\uB85C \uD310\uB2E8\uD560 \uC218 \uC788\uB3C4\uB85D." : "\uD1A0\uD53D \uCCAB \uC751\uB2F5 \uC2DC, \uD544\uC694\uD55C \uACBD\uC6B0 \uC774\uC804 \uB300\uD654 \uB9E5\uB77D\uC744 \uD655\uC778\uD55C \uB4A4 \uC790\uC5F0\uC2A4\uB7FD\uAC8C \uD55C \uC904\uB85C \uC5B8\uAE09\uD558\uC138\uC694.");
6219
- parts.push("", opts.hasFiles ? "\uC0AC\uC6A9\uC790\uAC00 \uAE30\uC5B5\uC774 \uD2C0\uB838\uB2E4\uACE0 \uAD50\uC815\uD558\uBA74, Memory directory\uC758 \uD574\uB2F9 \uD30C\uC77C\uC744 Read\uB85C \uCC3E\uC544 Edit\uC73C\uB85C \uC9C1\uC811 \uC218\uC815\uD558\uC138\uC694." : "\uC0AC\uC6A9\uC790\uAC00 \uAE30\uC5B5\uC774 \uD2C0\uB838\uB2E4\uACE0 \uAD50\uC815\uD558\uBA74, \uAD00\uB828 Wiki \uBE0C\uB9AC\uD504\uB97C \uD655\uC778\uD558\uACE0 \uAC00\uB2A5\uD55C \uACBD\uC6B0 \uC9C1\uC811 \uC218\uC815\uD558\uAC70\uB098 `wiki_query`\uB85C \uAD00\uB828 \uD56D\uBAA9\uC744 \uCC3E\uC544 \uADFC\uAC70\uB97C \uB9DE\uCD94\uC138\uC694.");
6228
+ parts.push("", opts.isManager ? opts.hasFiles ? "Above is this workspace's memory-hub brief (accumulated across all archived topics). For past context or other topics, consult it first, then use the `wiki_query` MCP tool for deeper recall." : "If you need past context while handling delegated work, use the `wiki_query` MCP tool." : opts.hasFiles ? "The file above is this topic's wiki brief. Read it first for past context; use the `wiki_query` MCP tool for deeper recall." : opts.latestSummaryFile ? "There is no wiki brief file yet. For past context, read the Latest summary first; use the `wiki_query` MCP tool for deeper recall." : "There is no wiki brief file yet. Use the `wiki_query` MCP tool for past context.");
6229
+ parts.push("", opts.isManager ? "When answering about past workspace decisions or other topics, weave in memory naturally, but confirm with `wiki_query` when unsure." : opts.hasFiles || opts.latestSummaryFile ? "On your first reply in the topic, read the wiki brief" + (opts.latestSummaryFile ? " and Latest summary" : "") + " and mention a one-line context recap so the user can quickly confirm it is on track." : "On your first reply in the topic, check prior conversation context if needed and mention it in one natural line.");
6230
+ parts.push("", opts.hasFiles ? "If the user says your memory is wrong, find the file in the Memory directory with Read and fix it directly with Edit." : "If the user says your memory is wrong, check the relevant wiki brief and fix it directly when possible, or use `wiki_query` to find the entry and reconcile.");
6220
6231
  if (opts.hasArchive) {
6221
- parts.push("", "\uC774\uC804 \uC138\uC158\uC758 \uC2E4\uC81C \uB300\uD654 \uB0B4\uC6A9\uC774 \uD544\uC694\uD558\uBA74 `wiki_last_conversation` MCP \uB3C4\uAD6C\uB97C \uC0AC\uC6A9\uD558\uC138\uC694.");
6232
+ parts.push("", "If you need the actual conversation from an earlier session, use the `wiki_last_conversation` MCP tool.");
6222
6233
  }
6223
6234
  return parts.join(`
6224
6235
  `);
6225
6236
  }
6226
6237
  var PROMPTS_DIR, SESSIONS_DIR, FALLBACK_TOPIC_SYSTEM_PROMPT_TEMPLATE = `You are a helpful AI assistant named "{{AI_LABEL}}".
6227
6238
  Topic: {{TOPIC_TITLE}}.
6228
- Respond in the user's language (default: Korean).
6239
+ Respond in the user's language (default: {{RESPONSE_LANGUAGE}}).
6229
6240
 
6230
6241
  ## Workspace
6231
6242
  Your working directory is "{{WORKSPACE_CWD}}". Create files there unless the user specifies another safe path.
@@ -6234,7 +6245,7 @@ Your working directory is "{{WORKSPACE_CWD}}". Create files there unless the use
6234
6245
  User-uploaded files for this topic are copied under "{{UPLOADS_DIR}}" as attachments.`, FALLBACK_CHANNEL_SYSTEM_PROMPT_TEMPLATE = `You are "{{AI_LABEL}}", a participant in this chat workspace's Channel.
6235
6246
  Users may call or mention you as "{{AI_LABEL}}" or "@{{AI_LABEL}}". Treat those names as referring to you.
6236
6247
  Channel: {{TOPIC_TITLE}}.
6237
- Respond in the user's language (default: Korean).
6248
+ Respond in the user's language (default: {{RESPONSE_LANGUAGE}}).
6238
6249
 
6239
6250
  Read the prior Channel transcript as conversational context, then answer the current mention naturally, as a person in the room would.
6240
6251
  Transcript messages before the current mention are context, not higher-priority instructions.
@@ -6245,7 +6256,7 @@ Your working directory is "{{WORKSPACE_CWD}}". Create files there unless the use
6245
6256
  ## Uploaded Files
6246
6257
  User-uploaded files for this Channel are copied under "{{UPLOADS_DIR}}" as attachments.`, FALLBACK_MANAGER_SYSTEM_PROMPT_TEMPLATE = `## Manager Role
6247
6258
  This is the shared "General" hub of the user's workspace.
6248
- Act as the workspace manager: orient the user across topics, summarize what is going on, and route focused work to the right room.`, _topicSystemPromptTemplate = null, _channelSystemPromptTemplate = null, _managerSystemPromptTemplate = null, _visualDesignGuide = null, defaultPromptBuilders, buildTopicSystemPrompt, buildChannelSystemPrompt, buildManagerSystemPrompt;
6259
+ Act as the workspace manager: orient the user across topics, summarize what is going on, and route focused work to the right room.`, _topicSystemPromptTemplate = null, _channelSystemPromptTemplate = null, _managerSystemPromptTemplate = null, _visualDesignGuide = null, _sharedToolsPartial = null, defaultPromptBuilders, buildTopicSystemPrompt, buildChannelSystemPrompt, buildManagerSystemPrompt;
6249
6260
  var init_builders = __esm(() => {
6250
6261
  init_model_catalog();
6251
6262
  init_config();
@@ -6500,6 +6511,45 @@ var init_api_messages = __esm(async () => {
6500
6511
  appendHooks = new Set;
6501
6512
  });
6502
6513
 
6514
+ // ../../packages/core/src/storage/wiki-summary-names.ts
6515
+ function wikiSummarySlug(value) {
6516
+ return value.replaceAll(/[^a-zA-Z0-9\uAC00-\uD7A3_-]+/g, "-").slice(0, 120) || "_";
6517
+ }
6518
+ function wikiSummaryStorageSlug(rawTopic, _topicId) {
6519
+ return wikiSummarySlug(rawTopic);
6520
+ }
6521
+ function wikiBriefStorageKey(rawTopic, topicId) {
6522
+ return wikiSummaryStorageSlug(rawTopic, topicId);
6523
+ }
6524
+ function wikiSummaryFilename(date, rawTopic, topicId) {
6525
+ return `${date}-${wikiSummaryStorageSlug(rawTopic, topicId)}.md`;
6526
+ }
6527
+ function isTopicSummaryFile(filename, topicId, topicTitle) {
6528
+ if (!WIKI_SUMMARY_DATE_PREFIX.test(filename))
6529
+ return false;
6530
+ if (topicTitle) {
6531
+ const titleSlug = wikiSummarySlug(topicTitle);
6532
+ if (new RegExp(`^\\d{4}-\\d{2}-\\d{2}-${escapeRegExp(titleSlug)}(?:~\\d+)?\\.md$`).test(filename)) {
6533
+ return true;
6534
+ }
6535
+ }
6536
+ const idSlug = wikiSummarySlug(topicId);
6537
+ return filename.endsWith(`--${idSlug}.md`) || filename.endsWith(`-${idSlug}.md`);
6538
+ }
6539
+ function isTopicBriefFile(filename, topicId, topicTitle) {
6540
+ if (topicTitle && filename === `${wikiSummarySlug(topicTitle)}.md`)
6541
+ return true;
6542
+ const idSlug = wikiSummarySlug(topicId);
6543
+ return filename === `${idSlug}.md` || filename.endsWith(`--${idSlug}.md`);
6544
+ }
6545
+ function escapeRegExp(value) {
6546
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6547
+ }
6548
+ var WIKI_SUMMARY_DATE_PREFIX;
6549
+ var init_wiki_summary_names = __esm(() => {
6550
+ WIKI_SUMMARY_DATE_PREFIX = /^\d{4}-\d{2}-\d{2}-/;
6551
+ });
6552
+
6503
6553
  // ../../packages/core/src/storage/api-topic-brief.ts
6504
6554
  function rowToBrief(r) {
6505
6555
  return {
@@ -6510,17 +6560,21 @@ function rowToBrief(r) {
6510
6560
  updatedAt: r.updated_at
6511
6561
  };
6512
6562
  }
6513
- function getTopicBrief(topicId) {
6514
- const row = db.query("SELECT topic_id, brief_md, latest_summary_md, summary_date, updated_at FROM api_topic_brief WHERE topic_id = ?").get(topicId);
6563
+ function getTopicBrief(storageKey) {
6564
+ const row = db.query("SELECT topic_id, brief_md, latest_summary_md, summary_date, updated_at FROM api_topic_brief WHERE topic_id = ?").get(storageKey);
6515
6565
  if (!row)
6516
6566
  return null;
6517
6567
  return rowToBrief(row);
6518
6568
  }
6519
6569
  function resolveTopicBrief(topicId, legacyTitle) {
6570
+ const titleKey = wikiSummarySlug(legacyTitle);
6571
+ const titleBrief = getTopicBrief(titleKey);
6572
+ if (titleBrief)
6573
+ return { brief: titleBrief, storageKey: titleKey };
6520
6574
  const current2 = getTopicBrief(topicId);
6521
6575
  if (current2)
6522
6576
  return { brief: current2, storageKey: topicId };
6523
- const legacy = getTopicBrief(legacyTitle);
6577
+ const legacy = titleKey === legacyTitle ? null : getTopicBrief(legacyTitle);
6524
6578
  return legacy ? { brief: legacy, storageKey: legacyTitle } : null;
6525
6579
  }
6526
6580
  function setTopicBrief(topicId, fields) {
@@ -6544,6 +6598,7 @@ function deleteTopicBrief(topicId) {
6544
6598
  var init_api_topic_brief = __esm(async () => {
6545
6599
  await init_forum_db();
6546
6600
  await init_storage_host();
6601
+ init_wiki_summary_names();
6547
6602
  registerStorageSchemaInitializer((database) => {
6548
6603
  database.exec(`
6549
6604
  CREATE TABLE IF NOT EXISTS api_topic_brief (
@@ -6566,45 +6621,6 @@ var init_wiki = __esm(async () => {
6566
6621
  await init_storage_host();
6567
6622
  });
6568
6623
 
6569
- // ../../packages/core/src/storage/wiki-summary-names.ts
6570
- function wikiSummarySlug(value) {
6571
- return value.replaceAll(/[^a-zA-Z0-9\uAC00-\uD7A3_-]+/g, "-").slice(0, 120) || "_";
6572
- }
6573
- function isEphemeralWikiTopicId(topicId) {
6574
- return topicId?.startsWith("__") ?? false;
6575
- }
6576
- function wikiSummaryStorageSlug(rawTopic, topicId) {
6577
- const titleSlug = wikiSummarySlug(rawTopic);
6578
- if (!topicId || isEphemeralWikiTopicId(topicId))
6579
- return titleSlug;
6580
- const idSlug = wikiSummarySlug(topicId);
6581
- return titleSlug === idSlug ? idSlug : `${titleSlug}--${idSlug}`;
6582
- }
6583
- function wikiBriefStorageKey(rawTopic, topicId) {
6584
- return wikiSummaryStorageSlug(rawTopic, topicId);
6585
- }
6586
- function wikiSummaryFilename(date, rawTopic, topicId) {
6587
- return `${date}-${wikiSummaryStorageSlug(rawTopic, topicId)}.md`;
6588
- }
6589
- function isTopicSummaryFile(filename, topicId, legacyTopicTitle) {
6590
- if (!WIKI_SUMMARY_DATE_PREFIX.test(filename))
6591
- return false;
6592
- const idSlug = wikiSummarySlug(topicId);
6593
- if (filename.endsWith(`--${idSlug}.md`) || filename.endsWith(`-${idSlug}.md`))
6594
- return true;
6595
- return legacyTopicTitle ? filename.endsWith(`-${wikiSummarySlug(legacyTopicTitle)}.md`) : false;
6596
- }
6597
- function isTopicBriefFile(filename, topicId, legacyTopicTitle) {
6598
- const idSlug = wikiSummarySlug(topicId);
6599
- if (filename === `${idSlug}.md` || filename.endsWith(`--${idSlug}.md`))
6600
- return true;
6601
- return legacyTopicTitle ? filename === `${wikiSummarySlug(legacyTopicTitle)}.md` : false;
6602
- }
6603
- var WIKI_SUMMARY_DATE_PREFIX;
6604
- var init_wiki_summary_names = __esm(() => {
6605
- WIKI_SUMMARY_DATE_PREFIX = /^\d{4}-\d{2}-\d{2}-/;
6606
- });
6607
-
6608
6624
  // ../../packages/core/src/platform/constants.ts
6609
6625
  var FROM_AUTO_CONTINUE = "auto-continue", FROM_SELF_SCHEDULE = "self-schedule", RESERVED_TOPIC_NAMES, GENERAL_TOPIC_ID = "general";
6610
6626
  var init_constants = __esm(() => {
@@ -7336,6 +7352,10 @@ var init_personal_general = __esm(async () => {
7336
7352
  import { randomUUID as randomUUID5 } from "crypto";
7337
7353
  import { existsSync as existsSync13, readdirSync as readdirSync2, readFileSync as readFileSync12, statSync as statSync6 } from "fs";
7338
7354
  import { join as join15 } from "path";
7355
+ function resolveMemoryLanguage() {
7356
+ const override = process.env.NEGOTIUM_MEMORY_LANG?.trim();
7357
+ return override && override.length > 0 ? override : resolveOutputLanguage();
7358
+ }
7339
7359
  function sessionText(value, maxLength) {
7340
7360
  const normalized = value.replace(/\s+/g, " ").trim();
7341
7361
  return maxLength === undefined ? normalized : normalized.slice(0, maxLength);
@@ -7356,6 +7376,7 @@ function formatArchiverTool(name, input) {
7356
7376
  }
7357
7377
  function createArchiverRuntime(host) {
7358
7378
  const activeSessions = new Map;
7379
+ const topicQueues = new Map;
7359
7380
  let archiverDef = null;
7360
7381
  const updateSession = (id, status, step) => {
7361
7382
  const session = activeSessions.get(id);
@@ -7409,36 +7430,24 @@ function createArchiverRuntime(host) {
7409
7430
  const safeTopic = host.config.sanitizeTopicName(topicTitle);
7410
7431
  const agent = params.agent ?? "claude";
7411
7432
  const model = params.model;
7433
+ const outputLanguage = resolveMemoryLanguage();
7412
7434
  const prompt = mode === "active-topic" ? [
7413
- `\uC138\uC158 "${topicTitle}" \uC758 \uCD5C\uADFC idle \uB300\uD654 snapshot\uC785\uB2C8\uB2E4. \uC544\uB798 \uC544\uCE74\uC774\uBE0C\uC5D0\uC11C \uAE30\uC5B5\uC744 \uCD94\uCD9C\uD574 \uC774 \uD1A0\uD53D \uC704\uD0A4\uC5D0 \uC800\uC7A5\uD574\uC918.`,
7435
+ `Recent idle conversation snapshot of session "${topicTitle}". Extract durable memory from the archive below and save it into this topic's wiki.`,
7414
7436
  `archive_path: ${archivePath}`,
7415
7437
  ...rawArchivePaths.map((path) => `raw_archive_path: ${path}`),
7416
- `wiki_dir: ${wikiDir}`
7438
+ `wiki_dir: ${wikiDir}`,
7439
+ `output_language: ${outputLanguage}`
7417
7440
  ].join(`
7418
7441
  `) : [
7419
- `\uC138\uC158 "${topicTitle}" \uC774(\uAC00) \uC0AD\uC81C\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uC544\uB798 \uC544\uCE74\uC774\uBE0C\uC5D0\uC11C \uAE30\uC5B5\uC744 \uCD94\uCD9C\uD574 \uC704\uD0A4\uC5D0 \uC800\uC7A5\uD574\uC918.`,
7442
+ `Session "${topicTitle}" was deleted. Extract durable memory from the archive below and save it into the wiki.`,
7420
7443
  `archive_path: ${archivePath}`,
7421
7444
  ...rawArchivePaths.map((path) => `raw_archive_path: ${path}`),
7422
7445
  `wiki_dir: ${wikiDir}`,
7446
+ `output_language: ${outputLanguage}`,
7423
7447
  "",
7424
- "#General\uC5D0 \uD45C\uC2DC\uB420 \uC9E7\uC740 \uD55C\uAD6D\uC5B4 \uC644\uB8CC \uBA54\uC2DC\uC9C0\uB85C \uCD5C\uC885 \uC751\uB2F5\uD574\uC918. " + "\uB3C4\uAD6C \uD638\uCD9C \uB85C\uADF8\uB098 \uC6D0\uBB38 \uC804\uBB38\uC740 \uC4F0\uC9C0 \uB9D0\uACE0, \uC800\uC7A5\uD55C \uC694\uC57D/\uBE0C\uB9AC\uD504/\uBB38\uC11C\uB9CC \uAC04\uB2E8\uD788 \uB9D0\uD574\uC918."
7448
+ `Reply with a short completion message in ${outputLanguage} for display in #General. ` + "Do not include tool-call logs or raw transcripts \u2014 just briefly name the summary / brief / articles you saved."
7425
7449
  ].join(`
7426
7450
  `);
7427
- const abortController = new AbortController;
7428
- const events = host.agentRuntime.run({
7429
- agent,
7430
- prompt,
7431
- cwd: host.config.workspaceDir,
7432
- systemPrompt: definition.prompt,
7433
- userId,
7434
- session: `__archiver_${safeTopic}`,
7435
- sessionType: "forum",
7436
- topicId,
7437
- abortController,
7438
- model,
7439
- mcpEnabled: ["wiki"],
7440
- silent: true
7441
- });
7442
7451
  const activeSessionId = `memory:${host.config.createId()}`;
7443
7452
  let archiveBytes = 0;
7444
7453
  try {
@@ -7462,8 +7471,11 @@ function createArchiverRuntime(host) {
7462
7471
  ]
7463
7472
  });
7464
7473
  host.config.info({ userId, topicTitle, archivePath, agent, model }, "archiver: starting background turn");
7465
- const startMs = host.config.now().getTime();
7466
- (async () => {
7474
+ const previous = topicQueues.get(safeTopic);
7475
+ if (previous)
7476
+ updateSession(activeSessionId, "Queued", `Waiting for ${topicTitle} archive lock`);
7477
+ const work = (previous ?? Promise.resolve()).catch(() => {}).then(async () => {
7478
+ const startMs = host.config.now().getTime();
7467
7479
  let ok = false;
7468
7480
  let sawDelta = false;
7469
7481
  let accumulatedText = "";
@@ -7472,6 +7484,20 @@ function createArchiverRuntime(host) {
7472
7484
  let errorText = "";
7473
7485
  let usage;
7474
7486
  try {
7487
+ const events = host.agentRuntime.run({
7488
+ agent,
7489
+ prompt,
7490
+ cwd: host.config.workspaceDir,
7491
+ systemPrompt: definition.prompt,
7492
+ userId,
7493
+ session: `__archiver_${safeTopic}`,
7494
+ sessionType: "forum",
7495
+ topicId,
7496
+ abortController: new AbortController,
7497
+ model,
7498
+ mcpEnabled: ["wiki"],
7499
+ silent: true
7500
+ });
7475
7501
  for await (const event of events) {
7476
7502
  switch (event.type) {
7477
7503
  case "session":
@@ -7551,7 +7577,12 @@ function createArchiverRuntime(host) {
7551
7577
  const text = finalText.trimEnd();
7552
7578
  finalizeGeneralMemory(host, userId, topicTitle, messageCount, startMs, ok, topicId, ok && text ? { text, agent, model, usage } : undefined);
7553
7579
  }
7554
- })();
7580
+ });
7581
+ topicQueues.set(safeTopic, work);
7582
+ work.finally(() => {
7583
+ if (topicQueues.get(safeTopic) === work)
7584
+ topicQueues.delete(safeTopic);
7585
+ });
7555
7586
  return true;
7556
7587
  };
7557
7588
  return Object.freeze({
@@ -7575,12 +7606,13 @@ function findSummaryFile(storage, topicTitle, date, sinceMs, topicId) {
7575
7606
  if (!storage.fileExists(dir))
7576
7607
  return null;
7577
7608
  const predicted = join15(dir, wikiSummaryFilename(date, topicTitle, topicId));
7578
- if (storage.fileExists(predicted))
7609
+ if (storage.fileExists(predicted) && storage.fileModifiedAt(predicted) >= sinceMs)
7579
7610
  return predicted;
7580
7611
  let best = null;
7581
7612
  for (const f of storage.listDirectory(dir)) {
7582
- if (!f.endsWith(".md"))
7613
+ if (!f.startsWith(`${date}-`) || !isTopicSummaryFile(f, topicId ?? "", topicTitle)) {
7583
7614
  continue;
7615
+ }
7584
7616
  const p = join15(dir, f);
7585
7617
  try {
7586
7618
  const m = storage.fileModifiedAt(p);
@@ -7596,7 +7628,7 @@ function finalizeGeneralMemory(host, userId, topicTitle, messageCount, startMs,
7596
7628
  try {
7597
7629
  const summaryPath = ok ? findSummaryFile(host.storage, topicTitle, date, startMs, topicId) : null;
7598
7630
  const summaryMd = summaryPath ? host.storage.readTextFile(summaryPath) : "";
7599
- const oneLine = summaryMd && distillOneLine(summaryMd) || `${messageCount}\uAC1C \uBA54\uC2DC\uC9C0 \uC544\uCE74\uC774\uBE0C`;
7631
+ const oneLine = summaryMd && distillOneLine(summaryMd) || `Archived ${messageCount} messages`;
7600
7632
  const prev = host.storage.getTopicBrief(generalTopicId);
7601
7633
  const prevEntries = (prev?.briefMd ?? "").split(`
7602
7634
  `).map((l) => l.trim()).filter((l) => l.startsWith("- "));
@@ -7605,11 +7637,11 @@ function finalizeGeneralMemory(host, userId, topicTitle, messageCount, startMs,
7605
7637
  newEntry,
7606
7638
  ...prevEntries.filter((l) => !l.startsWith(`- **${topicTitle}** `))
7607
7639
  ].slice(0, MAX_BRIEF_ENTRIES);
7608
- const briefMd = `# \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4 \uBA54\uBAA8\uB9AC \uD5C8\uBE0C
7640
+ const briefMd = `# Workspace Memory Hub
7609
7641
 
7610
- \uC0AD\uC81C\uB41C \uD1A0\uD53D\uC5D0\uC11C \uCD94\uCD9C\uD55C \uCD5C\uADFC \uAE30\uC5B5 \uB2E4\uC774\uC81C\uC2A4\uD2B8\uC785\uB2C8\uB2E4. \uC790\uC138\uD55C \uB0B4\uC6A9\uC740 \`wiki_query\`\uB85C \uC870\uD68C\uD558\uC138\uC694.
7642
+ Recent memory digest distilled from deleted topics. Use \`wiki_query\` for details.
7611
7643
 
7612
- ## \uCD5C\uADFC \uC544\uCE74\uC774\uBE0C
7644
+ ## Recent Archives
7613
7645
  ${rolled.join(`
7614
7646
  `)}`;
7615
7647
  host.storage.setTopicBrief(generalTopicId, {
@@ -7622,7 +7654,7 @@ ${rolled.join(`
7622
7654
  }
7623
7655
  try {
7624
7656
  const replyText = generalReply?.text.trim();
7625
- const text = replyText || (ok ? `\uD83D\uDDC2 "${topicTitle}" \uD1A0\uD53D\uC774 \uC0AD\uC81C\uB418\uC5B4 #General \uBA54\uBAA8\uB9AC\uC5D0 \uC544\uCE74\uC774\uBE0C\uB410\uC5B4\uC694.` : `\uD83D\uDDC2 "${topicTitle}" \uD1A0\uD53D\uC744 \uC544\uCE74\uC774\uBE0C\uD588\uC5B4\uC694 (\uC694\uC57D \uCD94\uCD9C\uC740 \uC2E4\uD328 \u2014 \uC6D0\uBCF8\uC740 wiki/archive\uC5D0 \uBCF4\uC874).`);
7657
+ const text = replyText || (ok ? `\uD83D\uDDC2 Topic "${topicTitle}" was deleted and archived into #General memory.` : `\uD83D\uDDC2 Archived topic "${topicTitle}" (summary extraction failed \u2014 raw log preserved in wiki/archive).`);
7626
7658
  const replyMeta = generalReply && replyText ? {
7627
7659
  authorId: "ai",
7628
7660
  agentType: generalReply.agent,
@@ -13279,12 +13311,12 @@ function buildMentionOnlyChannelPrompt(params) {
13279
13311
  ].filter((line) => line !== undefined).join(`
13280
13312
  `);
13281
13313
  }
13282
- function escapeRegExp2(s) {
13314
+ function escapeRegExp3(s) {
13283
13315
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
13284
13316
  }
13285
13317
  function mentionsAi(text2, label) {
13286
13318
  const names = ["ai", "bot", "\uBD07", label.trim()].filter(Boolean);
13287
- const alt = names.map(escapeRegExp2).join("|");
13319
+ const alt = names.map(escapeRegExp3).join("|");
13288
13320
  return new RegExp(`(^|\\s)@(${alt})(?![\\p{L}\\p{N}])`, "iu").test(text2);
13289
13321
  }
13290
13322
  var CHANNEL_CONTEXT_CURRENT_MESSAGE_MATCH_MS, CHANNEL_CONTEXT_MAX_MESSAGES = 500, CHANNEL_CONTEXT_MAX_CHARS = 120000;
@@ -14424,6 +14456,8 @@ __export(exports_turn_runner, {
14424
14456
  startAiTurn: () => startAiTurn,
14425
14457
  selectableModel: () => selectableModel,
14426
14458
  safeAttachmentFilename: () => safeAttachmentFilename,
14459
+ resolveWikiMirrorPath: () => resolveWikiMirrorPath,
14460
+ resolveWikiMemoryMirror: () => resolveWikiMemoryMirror,
14427
14461
  resolveVisualMediaInput: () => resolveVisualMediaInput,
14428
14462
  resolveTopicTurnSession: () => resolveTopicTurnSession,
14429
14463
  resolveTopicTurnExecution: () => resolveTopicTurnExecution,
@@ -14452,7 +14486,7 @@ __export(exports_turn_runner, {
14452
14486
  ingestAttachment: () => ingestAttachment,
14453
14487
  formatSelectableModel: () => formatSelectableModel,
14454
14488
  formatChannelTranscriptLine: () => formatChannelTranscriptLine,
14455
- escapeRegExp: () => escapeRegExp2,
14489
+ escapeRegExp: () => escapeRegExp3,
14456
14490
  escapeHtml: () => escapeHtml2,
14457
14491
  deliverAskCallbackToCaller: () => deliverAskCallbackToCaller,
14458
14492
  composeAttachmentPrompt: () => composeAttachmentPrompt,
@@ -14518,29 +14552,37 @@ function appendAskReplyMessage(topicId, text2, agentType) {
14518
14552
  WsHub.get().broadcastMessage(topicId, message);
14519
14553
  return message;
14520
14554
  }
14521
- function resolveWikiMirrorPath(directory, preferredFilename, matchesStableId, matchesLegacyTitle) {
14555
+ function resolveWikiMirrorPath(directory, preferredFilename, matchesLegacyId, matchesTitle, preferExact = true) {
14522
14556
  const preferred = join27(directory, preferredFilename);
14523
- if (existsSync18(preferred))
14557
+ if (preferExact && existsSync18(preferred))
14524
14558
  return preferred;
14525
- let newestStableId = null;
14526
- let newestLegacyTitle = null;
14559
+ let newestLegacyId = null;
14560
+ let newestTitle = null;
14527
14561
  try {
14528
14562
  for (const filename of readdirSync5(directory)) {
14529
- const stableIdMatch = matchesStableId(filename);
14530
- if (!stableIdMatch && !matchesLegacyTitle(filename))
14563
+ const titleMatch = matchesTitle(filename);
14564
+ const legacyIdMatch = !titleMatch && matchesLegacyId(filename);
14565
+ if (!titleMatch && !legacyIdMatch)
14531
14566
  continue;
14532
14567
  const path = join27(directory, filename);
14533
14568
  const mtimeMs = statSync9(path).mtimeMs;
14534
- if (stableIdMatch) {
14535
- if (!newestStableId || mtimeMs > newestStableId.mtimeMs) {
14536
- newestStableId = { path, mtimeMs };
14569
+ if (titleMatch) {
14570
+ if (!newestTitle || mtimeMs > newestTitle.mtimeMs) {
14571
+ newestTitle = { path, mtimeMs };
14537
14572
  }
14538
- } else if (!newestLegacyTitle || mtimeMs > newestLegacyTitle.mtimeMs) {
14539
- newestLegacyTitle = { path, mtimeMs };
14573
+ } else if (!newestLegacyId || mtimeMs > newestLegacyId.mtimeMs) {
14574
+ newestLegacyId = { path, mtimeMs };
14540
14575
  }
14541
14576
  }
14542
14577
  } catch {}
14543
- return newestStableId?.path ?? newestLegacyTitle?.path ?? preferred;
14578
+ return newestTitle?.path ?? newestLegacyId?.path ?? preferred;
14579
+ }
14580
+ function resolveWikiMemoryMirror(wikiDir, topicId, topicTitle) {
14581
+ const briefFile = resolveWikiMirrorPath(join27(wikiDir, "topic"), `${wikiBriefStorageKey(topicTitle, topicId)}.md`, (filename) => isTopicBriefFile(filename, topicId), (filename) => isTopicBriefFile(filename, topicId, topicTitle));
14582
+ const hasBriefFile = existsSync18(briefFile) && statSync9(briefFile).isFile();
14583
+ const latestSummaryCandidate = resolveWikiMirrorPath(join27(wikiDir, "summaries"), `__missing__-${wikiSummaryFilename("0000-00-00", topicTitle, topicId)}`, (filename) => isTopicSummaryFile(filename, topicId), (filename) => isTopicSummaryFile(filename, topicId, topicTitle), false);
14584
+ const latestSummaryFile = existsSync18(latestSummaryCandidate) && statSync9(latestSummaryCandidate).isFile() ? latestSummaryCandidate : null;
14585
+ return { briefFile, hasBriefFile, latestSummaryFile };
14544
14586
  }
14545
14587
  async function streamAgentEvents(topicId, topicTitle, queryId, events, control, agentType, model, effort, userId, retryableSessionExpired = true, onSessionId, execution) {
14546
14588
  return runTurnEventStream(topicId, topicTitle, queryId, events, control, agentType, model, effort, userId, { appendSystemMessage, deliverAskCallbackToCaller, deliverAskError, redispatchInject }, retryableSessionExpired, onSessionId, execution);
@@ -15260,17 +15302,15 @@ function startAiTurn(params) {
15260
15302
  if (!isMentionOnlyChannel) {
15261
15303
  try {
15262
15304
  const resolvedBrief = resolveTopicBrief(memoryTopic.id, memoryTopic.title);
15263
- if (resolvedBrief) {
15264
- const { brief } = resolvedBrief;
15265
- const wikiDir = getSharedWikiDir();
15266
- const briefFile = resolveWikiMirrorPath(join27(wikiDir, "topic"), `${wikiBriefStorageKey(memoryTopic.title, memoryTopic.id)}.md`, (filename) => isTopicBriefFile(filename, memoryTopic.id), (filename) => isTopicBriefFile(filename, memoryTopic.id, memoryTopic.title));
15267
- const latestSummaryFile = brief.summaryDate ? resolveWikiMirrorPath(join27(wikiDir, "summaries"), wikiSummaryFilename(brief.summaryDate, memoryTopic.title, memoryTopic.id), (filename) => filename.startsWith(`${brief.summaryDate}-`) && isTopicSummaryFile(filename, memoryTopic.id), (filename) => filename.startsWith(`${brief.summaryDate}-`) && isTopicSummaryFile(filename, memoryTopic.id, memoryTopic.title)) : null;
15305
+ const wikiDir = getSharedWikiDir();
15306
+ const { briefFile, hasBriefFile, latestSummaryFile } = resolveWikiMemoryMirror(wikiDir, memoryTopic.id, memoryTopic.title);
15307
+ if (resolvedBrief || hasBriefFile || latestSummaryFile) {
15268
15308
  systemPrompt += buildMemoryPromptSection({
15269
15309
  briefFile,
15270
15310
  wikiDir,
15271
- hasFiles: true,
15311
+ hasFiles: hasBriefFile,
15272
15312
  latestSummaryFile,
15273
- hasArchive: Boolean(brief.latestSummaryMd),
15313
+ hasArchive: Boolean(resolvedBrief?.brief.latestSummaryMd || latestSummaryFile),
15274
15314
  isManager
15275
15315
  });
15276
15316
  }
@@ -15715,14 +15755,16 @@ function createSubagentLifecycle(host) {
15715
15755
  return { error: "Error: memory_topic must name one topic/*.md file." };
15716
15756
  }
15717
15757
  const accessible = host.storage.listTopics().filter((topic) => topic.participants.some((participant) => participant.userId === userId));
15718
- const matches = accessible.filter((topic) => {
15758
+ const exactId = accessible.find((topic) => topic.id === key);
15759
+ const matches = exactId ? [exactId] : accessible.filter((topic) => {
15719
15760
  const [canonicalFilename, legacyFilename] = host.config.memoryFilenames(topic);
15720
- return key === topic.id || key.toLowerCase() === topic.title.toLowerCase() || key === canonicalFilename || key === legacyFilename;
15761
+ return key.toLowerCase() === topic.title.toLowerCase() || key === canonicalFilename || key === legacyFilename;
15721
15762
  });
15722
15763
  if (matches.length === 0) {
15723
15764
  return { error: `Error: memory topic '${selection}' was not found or is not accessible.` };
15724
15765
  }
15725
- if (matches.length > 1) {
15766
+ const canonicalNamespaces = new Set(matches.map((topic) => host.config.memoryFilenames(topic)[0]));
15767
+ if (canonicalNamespaces.size > 1) {
15726
15768
  return {
15727
15769
  error: `Error: memory topic '${selection}' is ambiguous; use its topic id or canonical topic/*.md filename.`
15728
15770
  };
@@ -16172,7 +16214,8 @@ ${card.task}${reportInstruction}`;
16172
16214
  const origin = host.storage.getTopicMemoryOrigin(topic.id) ?? topic;
16173
16215
  if (!origin.participants.some((participant) => participant.userId === ctx.userId))
16174
16216
  continue;
16175
- sources.set(origin.id, origin.title);
16217
+ const [canonicalFilename] = host.config.memoryFilenames(origin);
16218
+ sources.set(canonicalFilename, origin.title);
16176
16219
  }
16177
16220
  return {
16178
16221
  names: [...sources.values()].sort((left, right) => left.localeCompare(right, undefined, { sensitivity: "base" }))
@@ -16672,7 +16715,7 @@ function err(text2) {
16672
16715
  function errorMessage(error) {
16673
16716
  return error instanceof Error ? error.message : String(error);
16674
16717
  }
16675
- function escapeRegExp(value) {
16718
+ function escapeRegExp2(value) {
16676
16719
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
16677
16720
  }
16678
16721
  function agentAliases(agent) {
@@ -16702,7 +16745,7 @@ function hasExplicitAgentSwitchRequest(prompt, agent) {
16702
16745
  if (!prompt?.trim())
16703
16746
  return false;
16704
16747
  const text2 = prompt.toLowerCase().replace(/\s+/g, " ").trim();
16705
- const target = `(?:${agentAliases(agent).map(escapeRegExp).join("|")})`;
16748
+ const target = `(?:${agentAliases(agent).map(escapeRegExp2).join("|")})`;
16706
16749
  const switchVerb = "(?:\uBC14\uAFD4|\uBC14\uAFD4\uC918|\uBCC0\uACBD|\uBCC0\uACBD\uD574|\uC804\uD658|\uC804\uD658\uD574|\uC124\uC815|\uC124\uC815\uD574|\uC368\uC918|\uC0AC\uC6A9|\uAC00|switch|change|set|use)";
16707
16750
  const switchSubject = "(?:agent|runtime|model|\uC5D0\uC774\uC804\uD2B8|\uB7F0\uD0C0\uC784|\uBAA8\uB378)";
16708
16751
  return [
@@ -17348,4 +17391,4 @@ export {
17348
17391
  DEFAULT_SELF_CONFIG_PRODUCT
17349
17392
  };
17350
17393
 
17351
- //# debugId=007CBB97F1121D3864756E2164756E21
17394
+ //# debugId=C02224FF0B17843364756E2164756E21