negotium 0.1.19 → 0.1.21

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 (46) hide show
  1. package/dist/agent-helpers.js +146 -23
  2. package/dist/agent-helpers.js.map +14 -12
  3. package/dist/cron.js +137 -52
  4. package/dist/cron.js.map +20 -19
  5. package/dist/hosted-agent.js +29 -17
  6. package/dist/hosted-agent.js.map +7 -7
  7. package/dist/main.js +145 -55
  8. package/dist/main.js.map +20 -19
  9. package/dist/mcp-factories.js +23 -22
  10. package/dist/mcp-factories.js.map +3 -3
  11. package/dist/prompts.js +98 -9
  12. package/dist/prompts.js.map +6 -5
  13. package/dist/runtime/scripts/browser-passkey-policy.mjs +36 -0
  14. package/dist/runtime/scripts/browser-vault-transform.mjs +33 -0
  15. package/dist/runtime/scripts/mcp-patchright-http.mjs +22 -4
  16. package/dist/runtime/src/agents/archiver.ts +0 -14
  17. package/dist/runtime/src/agents/claude-provider.ts +11 -7
  18. package/dist/runtime/src/agents/execution-host.ts +10 -1
  19. package/dist/runtime/src/agents/idle-archiver.ts +21 -0
  20. package/dist/runtime/src/agents/maestro-provider.ts +9 -14
  21. package/dist/runtime/src/agents/mcp-tools/self-config.ts +1 -1
  22. package/dist/runtime/src/agents/mcp-tools/spawn-subagent.ts +6 -1
  23. package/dist/runtime/src/agents/memory-archive-policy.ts +34 -0
  24. package/dist/runtime/src/agents/model-catalog.ts +84 -18
  25. package/dist/runtime/src/mcp/factories/vault.ts +36 -34
  26. package/dist/runtime/src/mcp/vault-server.ts +1 -0
  27. package/dist/runtime/src/platform/mcp-config.ts +4 -8
  28. package/dist/runtime/src/platform/playwright/manager.ts +5 -2
  29. package/dist/runtime/src/prompts/builders.ts +36 -7
  30. package/dist/runtime/src/runtime/turn-runner.ts +2 -0
  31. package/dist/runtime/src/storage/topic-archive.ts +5 -2
  32. package/dist/runtime/src/topics/lifecycle.ts +2 -1
  33. package/dist/runtime/src/topics/session.ts +2 -0
  34. package/dist/runtime/src/version.ts +1 -1
  35. package/dist/storage.js +23 -3
  36. package/dist/storage.js.map +5 -4
  37. package/dist/types/packages/core/src/agents/claude-provider.d.ts +1 -0
  38. package/dist/types/packages/core/src/agents/execution-host.d.ts +2 -0
  39. package/dist/types/packages/core/src/agents/idle-archiver.d.ts +2 -0
  40. package/dist/types/packages/core/src/agents/memory-archive-policy.d.ts +14 -0
  41. package/dist/types/packages/core/src/agents/model-catalog.d.ts +77 -0
  42. package/dist/types/packages/core/src/mcp/factories/vault.d.ts +1 -0
  43. package/dist/types/packages/core/src/prompts/builders.d.ts +5 -1
  44. package/dist/types/packages/core/src/storage/topic-archive.d.ts +1 -0
  45. package/dist/types/packages/core/src/version.d.ts +1 -1
  46. package/package.json +2 -2
package/dist/main.js CHANGED
@@ -2091,6 +2091,19 @@ function vaultDel(userId, key) {
2091
2091
  function vaultList(userId) {
2092
2092
  return activeVaultDatabase().prepare("SELECT key, description FROM vault WHERE user_id = ? ORDER BY key").all(userId);
2093
2093
  }
2094
+ function vaultSubstituteDetailed(userId, text) {
2095
+ const entries = new Map(vaultListWithValues(userId).map((entry) => [entry.key, entry.value]));
2096
+ const usedKeys = new Set;
2097
+ const substituted = text.replace(/\{\{([^}]+)\}\}/g, (match, rawKey) => {
2098
+ const key = normalizeVaultKey(rawKey);
2099
+ const value = entries.get(key);
2100
+ if (value === undefined)
2101
+ return match;
2102
+ usedKeys.add(key);
2103
+ return value;
2104
+ });
2105
+ return { text: substituted, usedKeys: [...usedKeys] };
2106
+ }
2094
2107
  function valueReferencesVaultKey(userId, value) {
2095
2108
  const keys = new Set(vaultList(userId).map((entry) => entry.key));
2096
2109
  const visit = (candidate) => {
@@ -2182,7 +2195,7 @@ function createVaultToolPolicy(host) {
2182
2195
  }
2183
2196
  return { isVaultBrokerTool, referencesRuntimeSecretStorage, shouldRedirectVaultTool };
2184
2197
  }
2185
- var SENSITIVE_RUNTIME_NAMES, VAULT_BROKER_REDIRECT_ERROR = "Vault placeholders must be executed through mcp__vault__vault_run (shell/CLI) or mcp__vault__vault_http_request (HTTP) so secret values cannot enter the model transcript.", defaultVaultToolPolicy, isVaultBrokerTool, referencesRuntimeSecretStorage, shouldRedirectVaultTool;
2198
+ var SENSITIVE_RUNTIME_NAMES, defaultVaultToolPolicy, isVaultBrokerTool, referencesRuntimeSecretStorage, shouldRedirectVaultTool;
2186
2199
  var init_vault_tool_policy = __esm(async () => {
2187
2200
  init_sensitive_path();
2188
2201
  await init_vault();
@@ -3061,9 +3074,7 @@ var init_mcp_config = __esm(() => {
3061
3074
  vault: {
3062
3075
  ...commonRuntimeMcpPolicy("vault"),
3063
3076
  build({ userId, agent }) {
3064
- const args = [`--user-id=${userId}`];
3065
- if (agent === "codex")
3066
- args.push("--http-only=true");
3077
+ const args = [`--user-id=${userId}`, "--list-only=true"];
3067
3078
  return buildStdioMcpServer(agent, VAULT_SERVER, args);
3068
3079
  }
3069
3080
  }
@@ -3094,12 +3105,12 @@ function hostedMcpServers(opts) {
3094
3105
  function redactHostedSecrets(userId, value) {
3095
3106
  return activeHost().redactVaultSecrets(userId, value);
3096
3107
  }
3108
+ function substituteHostedSecrets(userId, value) {
3109
+ return activeHost().substituteVaultSecrets(userId, value);
3110
+ }
3097
3111
  function referencesHostedSecretStorage(value) {
3098
3112
  return activeHost().referencesRuntimeSecretStorage(value);
3099
3113
  }
3100
- function shouldRedirectHostedVaultTool(userId, toolName, input) {
3101
- return activeHost().shouldRedirectVaultTool(userId, toolName, input);
3102
- }
3103
3114
  function hostedClaudeCodeExecutablePath() {
3104
3115
  return activeHost().claudeCodeExecutablePath();
3105
3116
  }
@@ -3115,6 +3126,7 @@ var init_execution_host = __esm(async () => {
3115
3126
  defaultHost = {
3116
3127
  getMcpServersForQuery,
3117
3128
  redactVaultSecrets,
3129
+ substituteVaultSecrets: (userId, value) => vaultSubstituteDetailed(userId, value).text,
3118
3130
  referencesRuntimeSecretStorage,
3119
3131
  shouldRedirectVaultTool,
3120
3132
  claudeCodeExecutablePath: () => CLAUDE_EXECUTABLE,
@@ -3223,6 +3235,9 @@ var DEFAULT_CONTEXT_WARNING_RATIO = 0.8;
3223
3235
  import { spawn as spawn2 } from "child_process";
3224
3236
  import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
3225
3237
  import { query } from "@anthropic-ai/claude-agent-sdk";
3238
+ function substituteClaudeToolInput(userId, input) {
3239
+ return deepMapStrings(input, (value) => substituteHostedSecrets(userId, value));
3240
+ }
3226
3241
  function buildClaudeDisallowedTools(extra = undefined) {
3227
3242
  return [
3228
3243
  ...new Set([
@@ -3453,14 +3468,14 @@ async function* claudeProvider(opts) {
3453
3468
  };
3454
3469
  }
3455
3470
  const userId = opts.userId ?? "";
3456
- if (!shouldRedirectHostedVaultTool(userId, tool_name, tool_input)) {
3471
+ const withVault = substituteClaudeToolInput(userId, tool_input);
3472
+ if (JSON.stringify(withVault) === JSON.stringify(tool_input)) {
3457
3473
  return { continue: true };
3458
3474
  }
3459
3475
  return {
3460
3476
  hookSpecificOutput: {
3461
3477
  hookEventName: "PreToolUse",
3462
- permissionDecision: "deny",
3463
- permissionDecisionReason: VAULT_BROKER_REDIRECT_ERROR
3478
+ updatedInput: withVault
3464
3479
  }
3465
3480
  };
3466
3481
  }
@@ -3670,7 +3685,6 @@ var CLAUDE_DEFAULT_DISALLOWED_TOOLS, CLAUDE_NATIVE_AGENT_TOOLS, CLAUDE_IMAGE_MAX
3670
3685
  var init_claude_provider = __esm(async () => {
3671
3686
  init_claude_registry();
3672
3687
  await init_execution_host();
3673
- await init_vault_tool_policy();
3674
3688
  init_file_events();
3675
3689
  init_logger();
3676
3690
  CLAUDE_DEFAULT_DISALLOWED_TOOLS = [
@@ -3693,7 +3707,7 @@ var init_claude_provider = __esm(async () => {
3693
3707
  });
3694
3708
 
3695
3709
  // ../../packages/core/src/version.ts
3696
- var NEGOTIUM_VERSION = "0.1.19";
3710
+ var NEGOTIUM_VERSION = "0.1.21";
3697
3711
 
3698
3712
  // ../../packages/core/src/agents/codex-native-multi-agent.ts
3699
3713
  import { spawn as spawn3 } from "child_process";
@@ -4331,14 +4345,12 @@ function buildMaestroDisallowedTools(callerDisallowedTools = []) {
4331
4345
  function buildVaultHook(userId) {
4332
4346
  return {
4333
4347
  name: "vault-guard",
4334
- pre({ toolName, input }) {
4348
+ pre({ input }) {
4335
4349
  if (referencesHostedSecretStorage(input)) {
4336
4350
  return { decision: "block", error: "Runtime secret storage access is not permitted" };
4337
4351
  }
4338
- if (shouldRedirectHostedVaultTool(userId, toolName, input)) {
4339
- return { decision: "block", error: VAULT_BROKER_REDIRECT_ERROR };
4340
- }
4341
- return { decision: "allow" };
4352
+ const substituted = deepMapStrings(input, (value) => substituteHostedSecrets(userId, value));
4353
+ return JSON.stringify(substituted) === JSON.stringify(input) ? { decision: "allow" } : { decision: "modify", input: substituted };
4342
4354
  },
4343
4355
  post({ output }) {
4344
4356
  const redacted = redactHostedSecrets(userId, output);
@@ -4394,7 +4406,6 @@ var MAESTRO_DEFAULT_MAX_TOKENS = 32768, PROVIDER_ASK_USER_TOOL = "AskUserQuestio
4394
4406
  var init_maestro_provider = __esm(async () => {
4395
4407
  init_maestro_bootstrap_env();
4396
4408
  await init_execution_host();
4397
- await init_vault_tool_policy();
4398
4409
  MAESTRO_NATIVE_TASK_TOOLS = [
4399
4410
  "TaskCreate",
4400
4411
  "TaskUpdate",
@@ -6068,11 +6079,18 @@ __export(exports_model_catalog, {
6068
6079
  selectableModel: () => selectableModel,
6069
6080
  resolveModelForAgent: () => resolveModelForAgent,
6070
6081
  modelOwner: () => modelOwner,
6082
+ formatSelectableModel: () => formatSelectableModel,
6071
6083
  SELECTABLE_MODELS: () => SELECTABLE_MODELS,
6072
6084
  MODEL_OWNER: () => MODEL_OWNER,
6085
+ MODEL_COST_ROUTING_SUMMARY: () => MODEL_COST_ROUTING_SUMMARY,
6086
+ MODEL_COST_RESEARCHED_AT: () => MODEL_COST_RESEARCHED_AT,
6073
6087
  FALLBACK_ORDER: () => FALLBACK_ORDER,
6074
6088
  AGENT_DISPLAY_NAME: () => AGENT_DISPLAY_NAME
6075
6089
  });
6090
+ function formatSelectableModel(candidate) {
6091
+ const tier = `${candidate.intelligenceTier[0].toUpperCase()}${candidate.intelligenceTier.slice(1)}`;
6092
+ return `${candidate.agent} / \`${candidate.model}\` [${tier}-level]: ${candidate.routingSummary}`;
6093
+ }
6076
6094
  function selectableModel(value) {
6077
6095
  const normalized = value.trim().toLowerCase();
6078
6096
  return SELECTABLE_MODELS.find((candidate) => candidate.model === normalized);
@@ -6095,7 +6113,7 @@ function resolveModelForAgent(agent, requested, registry) {
6095
6113
  return defaultModel;
6096
6114
  return registry.validateModel(requested) ? requested : defaultModel;
6097
6115
  }
6098
- var MODEL_OWNER, SELECTABLE_MODELS, FALLBACK_ORDER, AGENT_DISPLAY_NAME;
6116
+ 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; DeepSeek Pro is pay-per-token. Relative marginal token cost: DeepSeek Pro << Codex < Claude.", 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, FALLBACK_ORDER, AGENT_DISPLAY_NAME;
6099
6117
  var init_model_catalog = __esm(() => {
6100
6118
  init_config();
6101
6119
  MODEL_OWNER = {
@@ -6115,47 +6133,72 @@ var init_model_catalog = __esm(() => {
6115
6133
  {
6116
6134
  model: "gpt-5.6-sol",
6117
6135
  agent: "codex",
6118
- description: "Latest frontier agentic coding model."
6136
+ description: "Highest-capability Codex route for the hardest agentic coding work.",
6137
+ intelligenceTier: "fable",
6138
+ routingSummary: "hardest coding work; 5x Codex quota cost",
6139
+ accessCost: CODEX_PRO_20X_COST,
6140
+ marginalTokenCost: "Codex credits: $5/M uncached input, $0.50/M cached input, $30/M output",
6141
+ estimatedUsage: `Official Pro 20x range: 300\u20131,800 local messages per 5 hours; quota weight 5x Luna. ${CODEX_COMMUNITY_WEEKLY}`
6119
6142
  },
6120
6143
  {
6121
6144
  model: "gpt-5.6-terra",
6122
6145
  agent: "codex",
6123
- description: "Balanced agentic coding model for everyday work."
6146
+ description: "High-capability Codex route for complex coding and reasoning.",
6147
+ intelligenceTier: "opus",
6148
+ routingSummary: "complex coding and reasoning; 2.5x Codex quota cost",
6149
+ accessCost: CODEX_PRO_20X_COST,
6150
+ marginalTokenCost: "Codex credits: $2.50/M uncached input, $0.25/M cached input, $15/M output",
6151
+ estimatedUsage: `Official Pro 20x range: 400\u20132,200 local messages per 5 hours; quota weight 2.5x Luna. ${CODEX_COMMUNITY_WEEKLY}`
6124
6152
  },
6125
6153
  {
6126
6154
  model: "gpt-5.6-luna",
6127
6155
  agent: "codex",
6128
- description: "Fast and affordable agentic coding model."
6129
- },
6130
- {
6131
- model: "gpt-5.5",
6132
- agent: "codex",
6133
- description: "Frontier model for complex coding, research, and real-world work."
6156
+ description: "Default Codex route with strong everyday coding intelligence.",
6157
+ intelligenceTier: "sonnet",
6158
+ routingSummary: "everyday coding default; lowest Codex quota cost (1x)",
6159
+ accessCost: CODEX_PRO_20X_COST,
6160
+ marginalTokenCost: "Codex credits: $1/M uncached input, $0.10/M cached input, $6/M output",
6161
+ estimatedUsage: `Official Pro 20x range: 1,000\u20135,600 local messages per 5 hours; lowest Codex quota weight (1x). ${CODEX_COMMUNITY_WEEKLY}`
6134
6162
  },
6135
6163
  {
6136
6164
  model: "fable",
6137
6165
  agent: "claude",
6138
- description: "Fable 5 \xB7 Most capable for your hardest and longest-running tasks \xB7 $10/$50 per Mtok"
6166
+ description: "Highest-capability Claude route for the hardest and longest-running tasks.",
6167
+ intelligenceTier: "fable",
6168
+ routingSummary: "hardest long-running work; highest Claude cost; explicit request only",
6169
+ accessCost: CLAUDE_MAX_20X_COST,
6170
+ marginalTokenCost: "Claude API/extra usage: $10/M input, $12.50/M cache write, $1/M cache read, $50/M output",
6171
+ estimatedUsage: `${CLAUDE_COMMUNITY_SESSION}; Fable drains weighted quota fastest, so use only on explicit user request. No stable per-model token cap is published.`
6139
6172
  },
6140
6173
  {
6141
6174
  model: "opus",
6142
6175
  agent: "claude",
6143
- description: "Opus 4.8 with 1M context \xB7 Best for everyday, complex tasks \xB7 $5/$25 per Mtok"
6176
+ description: "High-capability Claude route for complex reasoning and tool-heavy work.",
6177
+ intelligenceTier: "opus",
6178
+ routingSummary: "complex reasoning and tool-heavy work; about 2.5x Sonnet marginal cost",
6179
+ accessCost: CLAUDE_MAX_20X_COST,
6180
+ marginalTokenCost: "Claude API/extra usage: $5/M input, $6.25/M cache write, $0.50/M cache read, $25/M output",
6181
+ estimatedUsage: `${CLAUDE_COMMUNITY_SESSION}; Opus uses the shared all-model weekly pool more quickly than Sonnet. No stable per-model token cap is published.`
6144
6182
  },
6145
6183
  {
6146
6184
  model: "sonnet",
6147
6185
  agent: "claude",
6148
- description: "Sonnet 5 \xB7 Efficient for routine tasks \xB7 $2/$10 per Mtok \xB7 promo through Aug 31"
6186
+ description: "Default Claude route for capable, efficient everyday work.",
6187
+ intelligenceTier: "sonnet",
6188
+ routingSummary: "capable everyday default; lowest Claude model cost",
6189
+ accessCost: CLAUDE_MAX_20X_COST,
6190
+ marginalTokenCost: "Claude API/extra usage introductory rate: $2/M input, $2.50/M cache write, $0.20/M cache read, $10/M output through 2026-08-31; then $3/M input and $15/M output",
6191
+ estimatedUsage: `${CLAUDE_COMMUNITY_SESSION}; Sonnet also has a separate weekly allowance and normally provides the highest Claude throughput. No stable weekly token cap is published.`
6149
6192
  },
6150
6193
  {
6151
6194
  model: "deepseek-pro",
6152
6195
  agent: "maestro",
6153
- description: "Sonnet-level performance at lower cost \xB7 Best for everyday tasks over coding"
6154
- },
6155
- {
6156
- model: "deepseek-flash",
6157
- agent: "maestro",
6158
- description: "DeepSeek V4 Flash \xB7 Fast and low-cost for lightweight everyday tasks"
6196
+ description: "API-priced Sonnet-level route for cost-efficient everyday work.",
6197
+ intelligenceTier: "sonnet",
6198
+ routingSummary: "cost-efficient everyday work; pay-per-token and cheapest route",
6199
+ accessCost: "DeepSeek V4 Pro pay-as-you-go API; no monthly subscription required",
6200
+ marginalTokenCost: "DeepSeek API: $0.435/M uncached input, $0.003625/M cached input, $0.87/M output",
6201
+ estimatedUsage: "No subscription token cap; pay per token. Official account concurrency limit is 500 requests."
6159
6202
  }
6160
6203
  ];
6161
6204
  FALLBACK_ORDER = {
@@ -7109,6 +7152,7 @@ async function spawnPlaywright(instanceKey, userId, reservedPort, browserBin = P
7109
7152
  String(port),
7110
7153
  "--host",
7111
7154
  "127.0.0.1",
7155
+ "--headed",
7112
7156
  "--user-data-dir",
7113
7157
  userDataDir
7114
7158
  ];
@@ -7117,6 +7161,7 @@ async function spawnPlaywright(instanceKey, userId, reservedPort, browserBin = P
7117
7161
  const childEnv = {
7118
7162
  ...process.env,
7119
7163
  NEGOTIUM_BROWSER_CAPABILITY: capability,
7164
+ NEGOTIUM_BROWSER_VAULT_USER_ID: userId,
7120
7165
  ...proxy ? {
7121
7166
  NEGOTIUM_BROWSER_PROXY_SERVER: proxy.server,
7122
7167
  ...proxy.username ? { NEGOTIUM_BROWSER_PROXY_USERNAME: proxy.username } : {},
@@ -8434,7 +8479,7 @@ function createSelfConfigToolDefinitions(ctx) {
8434
8479
  return [
8435
8480
  {
8436
8481
  name: "set_model",
8437
- description: "Set the model for THIS topic. Persists and applies from the NEXT turn. Claude: 'sonnet' / 'opus' / 'fable'. Codex: 'gpt-5.6-luna' (default) / 'gpt-5.6-terra' / 'gpt-5.6-sol'. Maestro: 'deepseek-pro' / 'deepseek-flash' / 'deepseek'. The model must match the topic's current agent's accepted list. Switch agent first via set_agent only when crossing the claude/codex/maestro runtime boundary. Fails if the user locked this setting. NEVER use 'fable' unless the user explicitly requests it.",
8482
+ description: "Set the model for THIS topic; it persists and applies from the NEXT turn. Use a model from the system prompt's same-agent catalog. Cross-agent changes require an explicit user-requested set_agent first. Fails if the user locked this setting. NEVER use 'fable' unless explicitly requested.",
8438
8483
  schema: { model: z2.string().describe("Model id valid for the topic's current agent.") },
8439
8484
  async handler({ model }) {
8440
8485
  return mcpResult(setSelfConfigModel(getCtx(), model));
@@ -8831,7 +8876,7 @@ function loadAgentPrompt(filename) {
8831
8876
  prompt: match[2].trim()
8832
8877
  };
8833
8878
  }
8834
- function buildRuntimeToolSection(agentKind, canSpawnSubagents = false, visualTools = false, fileDeliveryTools = false) {
8879
+ function buildRuntimeToolSection(agentKind, canSpawnSubagents = false, visualTools = false, fileDeliveryTools = false, currentModel, currentEffort) {
8835
8880
  const runtimeNamespace = "mcp__runtime";
8836
8881
  const taskNamespace = "mcp__task";
8837
8882
  const visualToolLine = agentKind === "codex" ? `To display charts, tables, or interactive HTML results to the user, call the \`show_html\` function in the \`${runtimeNamespace}\` namespace with { html: "<complete HTML string>", title?: "optional title" }.` : `To display charts, tables, or interactive HTML results to the user, call the MCP tool "${runtimeNamespace}__show_html" with { html: "<complete HTML string>", title?: "optional title" }.`;
@@ -8889,20 +8934,26 @@ function buildRuntimeToolSection(agentKind, canSpawnSubagents = false, visualToo
8889
8934
  "Do not use session communication to make another topic perform destructive changes without the user's clear intent.",
8890
8935
  ...spawnSubagentSection
8891
8936
  ];
8937
+ const modelCatalog = SELECTABLE_MODELS.map((candidate) => `- ${formatSelectableModel(candidate)}`);
8892
8938
  const topicConfig = [
8893
8939
  "",
8894
8940
  "## Topic Configuration (model / agent / effort)",
8941
+ `Current execution: agent=\`${agentKind}\`, model=\`${currentModel ?? "unknown (call get_model)"}\`, effort=\`${currentEffort ?? "provider default/off"}\`.`,
8895
8942
  "The user's configured agent/model/effort is intentional. Preserve it by default.",
8896
8943
  `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.`,
8897
8944
  "`set_effort` is available but discouraged; use it only when the user explicitly requests an effort change.",
8898
- "`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. In that case, move up one step within the same agent and end the turn. Do not use vague task complexity as a trigger.",
8945
+ "`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.",
8899
8946
  "`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.",
8900
8947
  "Never use `fable` unless the user explicitly requests it; it is expensive.",
8901
8948
  "",
8902
- "Accepted values:",
8903
- "- claude models: `sonnet` (default), `opus`, `fable`; efforts: `low`, `medium`, `high`, `xhigh`, `max`.",
8904
- "- codex models: `gpt-5.6-luna` (default), `gpt-5.6-terra`, `gpt-5.6-sol`; efforts: `low`, `medium`, `high`, `xhigh`, `max`.",
8905
- "- maestro models: `deepseek-pro` (default), `deepseek-flash`, `deepseek`; efforts: `low`, `medium`, `high`, `xhigh`, `max`.",
8949
+ "Model catalog (capability/cost routing guidance):",
8950
+ MODEL_COST_ROUTING_SUMMARY,
8951
+ ...modelCatalog,
8952
+ "",
8953
+ "Accepted effort values:",
8954
+ "- claude: `low`, `medium`, `high`, `xhigh`, `max`.",
8955
+ "- codex: `low`, `medium`, `high`, `xhigh`, `max`.",
8956
+ "- maestro: `low`, `medium`, `high`, `xhigh`, `max`.",
8906
8957
  "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."
8907
8958
  ];
8908
8959
  if (agentKind !== "claude") {
@@ -8925,7 +8976,7 @@ function buildTopicSystemPrompt(opts) {
8925
8976
  TOPIC_TITLE: opts.topicTitle,
8926
8977
  WORKSPACE_CWD: opts.workspaceCwd,
8927
8978
  UPLOADS_DIR: uploadsDir
8928
- }) + buildRuntimeToolSection(opts.agentKind, opts.canSpawnSubagents, opts.visualTools, opts.fileDeliveryTools);
8979
+ }) + buildRuntimeToolSection(opts.agentKind, opts.canSpawnSubagents, opts.visualTools, opts.fileDeliveryTools, opts.currentModel, opts.currentEffort);
8929
8980
  if (opts.description?.trim()) {
8930
8981
  prompt += `
8931
8982
 
@@ -8941,7 +8992,7 @@ function buildChannelSystemPrompt(opts) {
8941
8992
  TOPIC_TITLE: opts.topicTitle,
8942
8993
  WORKSPACE_CWD: opts.workspaceCwd,
8943
8994
  UPLOADS_DIR: uploadsDir
8944
- }) + buildRuntimeToolSection(opts.agentKind, false, opts.visualTools, opts.fileDeliveryTools);
8995
+ }) + buildRuntimeToolSection(opts.agentKind, false, opts.visualTools, opts.fileDeliveryTools, opts.currentModel, opts.currentEffort);
8945
8996
  }
8946
8997
  function buildManagerSystemPrompt(opts) {
8947
8998
  return `${buildTopicSystemPrompt(opts)}
@@ -8991,6 +9042,7 @@ User-uploaded files for this Channel are copied under "{{UPLOADS_DIR}}" as attac
8991
9042
  This is the shared "General" hub of the user's workspace.
8992
9043
  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;
8993
9044
  var init_builders = __esm(() => {
9045
+ init_model_catalog();
8994
9046
  init_config();
8995
9047
  init_logger();
8996
9048
  PROMPTS_DIR = resolve6(PROJECT_ROOT, "src/prompts");
@@ -9109,10 +9161,6 @@ function getArchiverDef() {
9109
9161
  }
9110
9162
  function runArchiverTurn(params) {
9111
9163
  const { userId, topicId, topicTitle, archivePath, messageCount, mode = "deleted-topic" } = params;
9112
- if (mode !== "active-topic" && messageCount < MIN_ARCHIVE_MESSAGES) {
9113
- logger.info({ userId, topicTitle, messageCount, min: MIN_ARCHIVE_MESSAGES }, "archiver: skipped \u2014 too few messages to distil");
9114
- return false;
9115
- }
9116
9164
  let archiverDef;
9117
9165
  try {
9118
9166
  archiverDef = getArchiverDef();
@@ -9320,7 +9368,7 @@ ${rolled.join(`
9320
9368
  logger.warn({ err: err2, topicTitle }, "archiver: failed to post #General notification");
9321
9369
  }
9322
9370
  }
9323
- var MAX_BRIEF_ENTRIES = 8, MIN_ARCHIVE_MESSAGES = 4, MAX_SESSION_STEPS = 20, activeArchiverSessions, _archiverDef = null;
9371
+ var MAX_BRIEF_ENTRIES = 8, MAX_SESSION_STEPS = 20, activeArchiverSessions, _archiverDef = null;
9324
9372
  var init_archiver = __esm(async () => {
9325
9373
  await init_agents();
9326
9374
  await init_bus();
@@ -9335,6 +9383,26 @@ var init_archiver = __esm(async () => {
9335
9383
  activeArchiverSessions = new Map;
9336
9384
  });
9337
9385
 
9386
+ // ../../packages/core/src/agents/memory-archive-policy.ts
9387
+ function countMemoryArchiveExchanges(rows) {
9388
+ let waitingForAssistant = false;
9389
+ let completed = 0;
9390
+ for (const row of rows) {
9391
+ const assistant = row.author_id === "ai" || Boolean(row.agent_type);
9392
+ const conversational = row.kind == null || row.kind === "message";
9393
+ if (!assistant && row.author_id !== "system" && conversational) {
9394
+ waitingForAssistant = true;
9395
+ continue;
9396
+ }
9397
+ if (assistant && conversational && waitingForAssistant) {
9398
+ completed++;
9399
+ waitingForAssistant = false;
9400
+ }
9401
+ }
9402
+ return completed;
9403
+ }
9404
+ var MIN_MEMORY_ARCHIVE_EXCHANGES = 6;
9405
+
9338
9406
  // ../../packages/core/src/storage/topic-transcript.ts
9339
9407
  function parseJsonField(value) {
9340
9408
  if (!value)
@@ -9422,8 +9490,9 @@ function archiveTopicMessages(topicId, topicTitle, options = {}) {
9422
9490
  counter++;
9423
9491
  }
9424
9492
  }
9425
- logger.info({ topicId, topicTitle, archive: path, messageCount: rows.length, lastRowid }, "archiveTopicMessages: archived topic messages");
9426
- return { path, messageCount: rows.length, lastRowid };
9493
+ const exchangeCount = countMemoryArchiveExchanges(rows);
9494
+ logger.info({ topicId, topicTitle, archive: path, messageCount: rows.length, exchangeCount, lastRowid }, "archiveTopicMessages: archived topic messages");
9495
+ return { path, messageCount: rows.length, exchangeCount, lastRowid };
9427
9496
  }
9428
9497
  var init_topic_archive = __esm(async () => {
9429
9498
  init_logger();
@@ -9641,6 +9710,7 @@ function archiveActiveTopicForMemory(topicId, userId, options) {
9641
9710
  if (!options.allowMentionOnly && topic.aiMode === "mention")
9642
9711
  return "mention-only-channel";
9643
9712
  let skipped = "empty";
9713
+ let skipMemoryTurn = false;
9644
9714
  const claim = claimTopicArchiveJob(topicId, (afterRowid) => {
9645
9715
  const pending = getMessagesForTopicAfterRowid(topicId, afterRowid);
9646
9716
  const minMessages = options.minMessages;
@@ -9649,6 +9719,8 @@ function archiveActiveTopicForMemory(topicId, userId, options) {
9649
9719
  logger.debug({ topicId, topicTitle: topic.title, pending: pending.length, minMessages }, "topic-memory-archiver: skipped below threshold");
9650
9720
  return null;
9651
9721
  }
9722
+ const exchangeCount = countMemoryArchiveExchanges(pending);
9723
+ skipMemoryTurn = options.minExchanges !== undefined && exchangeCount < options.minExchanges;
9652
9724
  const archived = (options.archiveMessages ?? archiveTopicMessages)(topicId, topic.title, {
9653
9725
  afterRowid,
9654
9726
  reason: options.reason
@@ -9666,6 +9738,18 @@ function archiveActiveTopicForMemory(topicId, userId, options) {
9666
9738
  if (claim.kind === "busy")
9667
9739
  return "busy";
9668
9740
  const { job } = claim;
9741
+ if (skipMemoryTurn) {
9742
+ settleTopicArchiveJob(topicId, job.archivePath, true);
9743
+ logger.info({
9744
+ topicId,
9745
+ topicTitle: topic.title,
9746
+ messageCount: job.messageCount,
9747
+ minExchanges: options.minExchanges,
9748
+ archive: job.archivePath,
9749
+ reason: options.reason
9750
+ }, "topic-memory-archiver: raw snapshot preserved below exchange threshold");
9751
+ return "below-threshold";
9752
+ }
9669
9753
  const memoryTopic = getTopicMemoryOrigin(topicId) ?? topic;
9670
9754
  const launched = (options.launchArchiver ?? runArchiverTurn)({
9671
9755
  userId,
@@ -11606,6 +11690,7 @@ __export(exports_turn_runner, {
11606
11690
  isPathInside: () => isPathInside,
11607
11691
  isLikelyCurrentChannelTrigger: () => isLikelyCurrentChannelTrigger,
11608
11692
  ingestAttachment: () => ingestAttachment,
11693
+ formatSelectableModel: () => formatSelectableModel,
11609
11694
  formatChannelTranscriptLine: () => formatChannelTranscriptLine,
11610
11695
  escapeRegExp: () => escapeRegExp2,
11611
11696
  escapeHtml: () => escapeHtml2,
@@ -11623,6 +11708,8 @@ __export(exports_turn_runner, {
11623
11708
  SESSION_EXPIRED_MSG: () => SESSION_EXPIRED_MSG,
11624
11709
  SELECTABLE_MODELS: () => SELECTABLE_MODELS,
11625
11710
  MODEL_OWNER: () => MODEL_OWNER,
11711
+ MODEL_COST_ROUTING_SUMMARY: () => MODEL_COST_ROUTING_SUMMARY,
11712
+ MODEL_COST_RESEARCHED_AT: () => MODEL_COST_RESEARCHED_AT,
11626
11713
  FALLBACK_ORDER: () => FALLBACK_ORDER,
11627
11714
  AGENT_DISPLAY_NAME: () => AGENT_DISPLAY_NAME
11628
11715
  });
@@ -12830,6 +12917,8 @@ function startAiTurn(params) {
12830
12917
  topicTitle: topic.title,
12831
12918
  workspaceCwd,
12832
12919
  agentKind,
12920
+ currentModel: resolvedModel,
12921
+ currentEffort: resolvedEffort,
12833
12922
  description: topic.description,
12834
12923
  canSpawnSubagents: peerBridge?.canSpawnSubagents ?? (topicRecord?.kind === "agent" && !topicRecord.isSubagent),
12835
12924
  visualTools,
@@ -13508,7 +13597,7 @@ async function deleteTopicCascadeImpl(topic, userId, options, deletingAncestorId
13508
13597
  logger.warn({ err: err2, topicId }, "deleteTopicCascade: archive failed but force=true - continuing with hard delete");
13509
13598
  }
13510
13599
  }
13511
- if (archived) {
13600
+ if (archived && archived.exchangeCount >= MIN_MEMORY_ARCHIVE_EXCHANGES) {
13512
13601
  try {
13513
13602
  const memoryTopic = getTopicMemoryOrigin(topicId) ?? topic;
13514
13603
  runArchiverTurn({
@@ -14048,7 +14137,7 @@ function createSpawnSubagentToolDefinition(ctx) {
14048
14137
  task: z3.string().describe("Self-contained task brief for the subagent. It sees nothing else \u2014 include all context."),
14049
14138
  name: z3.string().optional().describe("Short name for the subagent room. Auto-generated when omitted."),
14050
14139
  agent: z3.enum(["claude", "codex", "maestro"]).optional().describe("Agent backend override. Defaults to this room's agent."),
14051
- model: z3.string().optional().describe("Model override. Defaults to the agent's default.")
14140
+ model: z3.string().optional().describe(`Best-fit model override from the system prompt catalog. Omit agent+model to inherit ${ctx.agent}/${ctx.model ?? "default"}; overriding agent without model uses that agent's default.`)
14052
14141
  },
14053
14142
  async handler(input) {
14054
14143
  return spawnSubagent(ctx, input);
@@ -14556,6 +14645,7 @@ async function restartTopicSession(topicId, userId, reason = "topic-session-rest
14556
14645
  (options.archiveMemory ?? archiveActiveTopicForMemory)(topicId, userId, {
14557
14646
  reason: "reset",
14558
14647
  minMessages: 1,
14648
+ minExchanges: MIN_MEMORY_ARCHIVE_EXCHANGES,
14559
14649
  allowMentionOnly: true,
14560
14650
  skipBusyCheck: true
14561
14651
  });
@@ -32799,4 +32889,4 @@ switch (command) {
32799
32889
  }
32800
32890
  }
32801
32891
 
32802
- //# debugId=E0853F8F97A3BD9464756E2164756E21
32892
+ //# debugId=C8F4C2CD3CFA25F564756E2164756E21