negotium 0.2.15 → 0.2.17

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 (43) hide show
  1. package/dist/agent-helpers.js +220 -32
  2. package/dist/agent-helpers.js.map +12 -12
  3. package/dist/background-bash.js.map +1 -1
  4. package/dist/browser-runtime.js.map +1 -1
  5. package/dist/{chunk-5g5a63vv.js → chunk-5cpdbbeh.js} +56 -6
  6. package/dist/{chunk-5g5a63vv.js.map → chunk-5cpdbbeh.js.map} +5 -5
  7. package/dist/hosted-agent.js +119 -9
  8. package/dist/hosted-agent.js.map +8 -8
  9. package/dist/main.js +931 -419
  10. package/dist/main.js.map +25 -24
  11. package/dist/mcp-factories.js +289 -42
  12. package/dist/mcp-factories.js.map +13 -13
  13. package/dist/prompts.js.map +1 -1
  14. package/dist/query-runtime.js.map +1 -1
  15. package/dist/registry.js +3 -3
  16. package/dist/registry.js.map +2 -2
  17. package/dist/rollout.js +1 -1
  18. package/dist/runtime/src/agents/codex-provider.ts +53 -2
  19. package/dist/runtime/src/agents/compaction-support.ts +1 -1
  20. package/dist/runtime/src/agents/maestro-provider.ts +3 -0
  21. package/dist/runtime/src/agents/rollout/codex.ts +62 -0
  22. package/dist/runtime/src/index.ts +1 -0
  23. package/dist/runtime/src/mcp/factories/token-stats.ts +33 -11
  24. package/dist/runtime/src/mcp/runtime-spec.ts +58 -1
  25. package/dist/runtime/src/node-host.ts +1 -0
  26. package/dist/runtime/src/runtime/turn-event-stream.ts +43 -2
  27. package/dist/runtime/src/runtime/turn-runner.ts +20 -9
  28. package/dist/runtime/src/storage/token-stats.ts +200 -19
  29. package/dist/runtime/src/types.ts +6 -0
  30. package/dist/runtime/src/version.ts +1 -1
  31. package/dist/runtime-helpers.js.map +1 -1
  32. package/dist/storage.js +112 -11
  33. package/dist/storage.js.map +4 -4
  34. package/dist/types/packages/core/src/agents/codex-provider.d.ts +13 -0
  35. package/dist/types/packages/core/src/agents/compaction-support.d.ts +1 -0
  36. package/dist/types/packages/core/src/agents/rollout/codex.d.ts +9 -0
  37. package/dist/types/packages/core/src/mcp/factories/token-stats.d.ts +4 -2
  38. package/dist/types/packages/core/src/runtime/turn-runner.d.ts +4 -0
  39. package/dist/types/packages/core/src/storage/token-stats.d.ts +35 -6
  40. package/dist/types/packages/core/src/types.d.ts +6 -0
  41. package/dist/types/packages/core/src/version.d.ts +1 -1
  42. package/dist/vault.js.map +1 -1
  43. package/package.json +2 -2
@@ -952,7 +952,7 @@ var init_claude_registry = __esm(() => {
952
952
  });
953
953
 
954
954
  // ../../packages/core/src/version.ts
955
- var NEGOTIUM_VERSION = "0.2.15";
955
+ var NEGOTIUM_VERSION = "0.2.17";
956
956
 
957
957
  // ../../packages/core/src/agents/codex-native-multi-agent.ts
958
958
  import { spawn as spawn2 } from "child_process";
@@ -1292,7 +1292,7 @@ var init_canonical_bridge_config = __esm(() => {
1292
1292
  });
1293
1293
 
1294
1294
  // ../../packages/core/src/mcp/runtime-spec.ts
1295
- import { createHmac, timingSafeEqual } from "crypto";
1295
+ import { createHash, createHmac, timingSafeEqual } from "crypto";
1296
1296
  function encodeTokenPart(value2) {
1297
1297
  return Buffer.from(JSON.stringify(value2), "utf-8").toString("base64url");
1298
1298
  }
@@ -1325,19 +1325,69 @@ function buildRuntimeMcpSpec(agent, ctx) {
1325
1325
  return {
1326
1326
  type: "sse",
1327
1327
  url: `${base}/sse?${query}`,
1328
- timeout: CLAUDE_MCP_TOOL_TIMEOUT_MS
1328
+ timeout: CLAUDE_MCP_TOOL_TIMEOUT_MS,
1329
+ ...agent === "maestro" ? { lifecycle: "turn" } : {}
1329
1330
  };
1330
1331
  }
1332
+ function hostedMcpCacheIdentity(surface, ctx) {
1333
+ let semanticContext;
1334
+ switch (surface) {
1335
+ case "system-health":
1336
+ semanticContext = {};
1337
+ break;
1338
+ case "token-stats":
1339
+ case "agent-health":
1340
+ semanticContext = { userId: ctx.userId };
1341
+ break;
1342
+ case "task":
1343
+ semanticContext = {
1344
+ userId: ctx.userId,
1345
+ topicTitle: ctx.topicTitle,
1346
+ topicId: ctx.topicId ?? null
1347
+ };
1348
+ break;
1349
+ case "wiki":
1350
+ case "skills":
1351
+ semanticContext = {
1352
+ userId: ctx.userId,
1353
+ topicId: ctx.wikiTopicId ?? ctx.topicId ?? null
1354
+ };
1355
+ break;
1356
+ case "vault":
1357
+ semanticContext = { userId: ctx.userId, cwd: ctx.cwd, agent: ctx.agent };
1358
+ break;
1359
+ case "session-comm":
1360
+ semanticContext = {
1361
+ userId: ctx.userId,
1362
+ topicTitle: ctx.topicTitle,
1363
+ topicId: ctx.topicId ?? null,
1364
+ subagentParentTopicId: ctx.subagentParentTopicId ?? null,
1365
+ depth: ctx.depth ?? 0,
1366
+ silent: ctx.silent ?? false,
1367
+ agent: ctx.agent,
1368
+ peerBridge: ctx.peerBridge ?? null
1369
+ };
1370
+ break;
1371
+ }
1372
+ const digest = createHash("sha256").update(JSON.stringify([surface, semanticContext])).digest("hex").slice(0, 24);
1373
+ return `hosted:${surface}:${digest}`;
1374
+ }
1331
1375
  function buildHostedMcpSpec(agent, surface, ctx) {
1332
1376
  const token = issueHostedMcpToken(surface, ctx);
1333
1377
  const base = `http://127.0.0.1:${runtimePort}${RUNTIME_MCP_BASE_PATH}/${surface}`;
1334
1378
  const query = `token=${encodeURIComponent(token)}`;
1335
1379
  if (agent === "codex")
1336
1380
  return { url: `${base}/mcp?${query}` };
1381
+ const queryBound = surface === "session-comm" && (ctx.silent === true || ctx.peerBridge !== undefined);
1382
+ const lifecycle = queryBound ? "turn" : surface === "session-comm" ? "session" : "process";
1337
1383
  return {
1338
1384
  type: "sse",
1339
1385
  url: `${base}/sse?${query}`,
1340
- timeout: CLAUDE_MCP_TOOL_TIMEOUT_MS
1386
+ timeout: CLAUDE_MCP_TOOL_TIMEOUT_MS,
1387
+ ...agent === "maestro" ? {
1388
+ lifecycle,
1389
+ cacheKey: hostedMcpCacheIdentity(surface, ctx)
1390
+ } : {}
1341
1391
  };
1342
1392
  }
1343
1393
  var RUNTIME_MCP_KEY = "runtime", RUNTIME_MCP_BASE_PATH = "/mcp/runtime", TOKEN_TTL_MS, CLAUDE_MCP_TOOL_TIMEOUT_MS = 600000, runtimePort;
@@ -2190,9 +2240,9 @@ var init_sqlite = __esm(async () => {
2190
2240
  });
2191
2241
 
2192
2242
  // ../../packages/core/src/storage/vault-crypto-core.ts
2193
- import { createCipheriv, createDecipheriv, createHash, randomBytes as randomBytes3 } from "crypto";
2243
+ import { createCipheriv, createDecipheriv, createHash as createHash2, randomBytes as randomBytes3 } from "crypto";
2194
2244
  function encryptionKey(masterKey) {
2195
- return createHash("sha256").update("otium-vault-value-v1\x00", "utf8").update(masterKey, "utf8").digest().subarray(0, KEY_BYTES);
2245
+ return createHash2("sha256").update("otium-vault-value-v1\x00", "utf8").update(masterKey, "utf8").digest().subarray(0, KEY_BYTES);
2196
2246
  }
2197
2247
  function aad(userId, key) {
2198
2248
  return Buffer.from(`${userId}\x00${key.toUpperCase()}`, "utf8");
@@ -2674,6 +2724,36 @@ function extractLatestCodexContextUsage(jsonl) {
2674
2724
  }
2675
2725
  return;
2676
2726
  }
2727
+ function extractLatestCodexTokenTotals(jsonl) {
2728
+ const lines = jsonl.trimEnd().split(`
2729
+ `);
2730
+ for (let index = lines.length - 1;index >= 0; index -= 1) {
2731
+ try {
2732
+ const entry = JSON.parse(lines[index] ?? "");
2733
+ if (entry.type !== "event_msg" || entry.payload?.type !== "token_count")
2734
+ continue;
2735
+ const usage = entry.payload.info?.total_token_usage;
2736
+ if (!usage)
2737
+ continue;
2738
+ const values = [
2739
+ usage.input_tokens,
2740
+ usage.output_tokens,
2741
+ usage.cached_input_tokens,
2742
+ usage.cache_write_input_tokens
2743
+ ];
2744
+ if (values.some((value2) => value2 !== undefined && (!Number.isFinite(value2) || value2 < 0))) {
2745
+ continue;
2746
+ }
2747
+ return {
2748
+ inputTokens: usage.input_tokens ?? 0,
2749
+ outputTokens: usage.output_tokens ?? 0,
2750
+ cachedInputTokens: usage.cached_input_tokens ?? 0,
2751
+ cacheWriteInputTokens: usage.cache_write_input_tokens ?? 0
2752
+ };
2753
+ } catch {}
2754
+ }
2755
+ return;
2756
+ }
2677
2757
  function readLatestCodexContextUsage(threadId) {
2678
2758
  const path = latestCodexRolloutPath(threadId);
2679
2759
  if (!path)
@@ -2685,6 +2765,17 @@ function readLatestCodexContextUsage(threadId) {
2685
2765
  return;
2686
2766
  }
2687
2767
  }
2768
+ function readLatestCodexTokenTotals(threadId) {
2769
+ const path = latestCodexRolloutPath(threadId);
2770
+ if (!path)
2771
+ return;
2772
+ try {
2773
+ return extractLatestCodexTokenTotals(readFileSync6(path, "utf8"));
2774
+ } catch (error) {
2775
+ logger.debug({ error, threadId }, "codex token totals: rollout read failed");
2776
+ return;
2777
+ }
2778
+ }
2688
2779
  function migrateCodexRolloutNativeMultiAgentMetadata(threadId) {
2689
2780
  const path = latestCodexRolloutPath(threadId);
2690
2781
  if (!path)
@@ -6013,7 +6104,7 @@ var init_browser_profiles = __esm(async () => {
6013
6104
 
6014
6105
  // ../../packages/core/src/platform/playwright/manager.ts
6015
6106
  import { execFileSync as execFileSync4, spawn as spawn6 } from "child_process";
6016
- import { createHash as createHash2, randomBytes as randomBytes6, timingSafeEqual as timingSafeEqual3 } from "crypto";
6107
+ import { createHash as createHash3, randomBytes as randomBytes6, timingSafeEqual as timingSafeEqual3 } from "crypto";
6017
6108
  import {
6018
6109
  cpSync,
6019
6110
  existsSync as existsSync11,
@@ -6108,7 +6199,7 @@ function parseInstanceKey(instanceKey) {
6108
6199
  };
6109
6200
  }
6110
6201
  function portFileName(instanceKey) {
6111
- return createHash2("sha256").update(instanceKey).digest("hex").slice(0, 24);
6202
+ return createHash3("sha256").update(instanceKey).digest("hex").slice(0, 24);
6112
6203
  }
6113
6204
  function writePortFile(instanceKey, port) {
6114
6205
  try {
@@ -8909,6 +9000,7 @@ var init_tool_format = __esm(() => {
8909
9000
  var exports_codex_provider = {};
8910
9001
  __export(exports_codex_provider, {
8911
9002
  toCodexMcpServers: () => toCodexMcpServers,
9003
+ normalizeCodexTurnUsage: () => normalizeCodexTurnUsage,
8912
9004
  codexProvider: () => codexProvider
8913
9005
  });
8914
9006
  import { execFileSync as execFileSync6 } from "child_process";
@@ -8916,6 +9008,20 @@ import { existsSync as existsSync15, readFileSync as readFileSync14, realpathSyn
8916
9008
  import { homedir as homedir6 } from "os";
8917
9009
  import { dirname as dirname11, isAbsolute as isAbsolute3, join as join16, relative, resolve as resolve12 } from "path";
8918
9010
  import { Codex } from "@openai/codex-sdk";
9011
+ function sameCodexUsage(usage, total) {
9012
+ return usage.input_tokens === total.inputTokens && usage.output_tokens === total.outputTokens && (usage.cached_input_tokens ?? 0) === total.cachedInputTokens && (usage.cache_write_input_tokens ?? 0) === total.cacheWriteInputTokens;
9013
+ }
9014
+ function normalizeCodexTurnUsage(usage, baseline, rolloutTotal) {
9015
+ if (!baseline || !rolloutTotal || !sameCodexUsage(usage, rolloutTotal))
9016
+ return usage;
9017
+ const delta = (current2, previous) => current2 >= previous ? current2 - previous : current2;
9018
+ return {
9019
+ input_tokens: delta(rolloutTotal.inputTokens, baseline.inputTokens),
9020
+ output_tokens: delta(rolloutTotal.outputTokens, baseline.outputTokens),
9021
+ cached_input_tokens: delta(rolloutTotal.cachedInputTokens, baseline.cachedInputTokens),
9022
+ cache_write_input_tokens: delta(rolloutTotal.cacheWriteInputTokens, baseline.cacheWriteInputTokens)
9023
+ };
9024
+ }
8919
9025
  function mapEffort(effort) {
8920
9026
  if (!effort)
8921
9027
  return;
@@ -9315,6 +9421,7 @@ async function* codexProvider(opts) {
9315
9421
  ...opts.effort ? { modelReasoningEffort: mapEffort(opts.effort) } : {}
9316
9422
  };
9317
9423
  let currentSessionId = opts.sessionId;
9424
+ let usageBaseline = currentSessionId ? readLatestCodexTokenTotals(currentSessionId) : undefined;
9318
9425
  let thread = currentSessionId ? codex.resumeThread(currentSessionId, threadOptions) : codex.startThread(threadOptions);
9319
9426
  let prompt = promptForThread(opts, true);
9320
9427
  let agentTextSoFar = "";
@@ -9373,6 +9480,7 @@ async function* codexProvider(opts) {
9373
9480
  thread = codex.startThread(threadOptions);
9374
9481
  prompt = promptForThread(opts, true);
9375
9482
  currentSessionId = undefined;
9483
+ usageBaseline = undefined;
9376
9484
  startResult = await startStreamedWithTracking(thread, prompt, attemptAbort.signal, trackedPids);
9377
9485
  }
9378
9486
  if (attemptAbort.signal.aborted) {
@@ -9473,10 +9581,11 @@ async function* codexProvider(opts) {
9473
9581
  break;
9474
9582
  }
9475
9583
  case "turn.completed": {
9476
- const usage = event.usage;
9477
- if (!usage)
9584
+ const rawUsage = event.usage;
9585
+ if (!rawUsage)
9478
9586
  break;
9479
9587
  attemptCompleted = true;
9588
+ const usage = normalizeCodexTurnUsage(rawUsage, usageBaseline, currentSessionId ? readLatestCodexTokenTotals(currentSessionId) : undefined);
9480
9589
  const contextUsage = currentSessionId ? readLatestCodexContextUsage(currentSessionId) : undefined;
9481
9590
  yield {
9482
9591
  type: "result",
@@ -9485,6 +9594,7 @@ async function* codexProvider(opts) {
9485
9594
  usage: {
9486
9595
  inputTokens: usage.input_tokens,
9487
9596
  outputTokens: usage.output_tokens,
9597
+ cacheCreationInputTokens: usage.cache_write_input_tokens,
9488
9598
  cacheReadInputTokens: usage.cached_input_tokens,
9489
9599
  ...contextUsage
9490
9600
  }
@@ -9677,6 +9787,7 @@ function maestroProvider(opts) {
9677
9787
  ...opts,
9678
9788
  enableToolSearch: !opts.toolPolicy && opts.enableToolSearch !== false,
9679
9789
  toolResultTruncation: buildMaestroToolResultTruncation(opts),
9790
+ ephemeralSystemPrompt: opts.ephemeralSystemPrompt,
9680
9791
  apiKeyOverrides: resolveMaestroApiKeyOverrides(userId),
9681
9792
  agent: "maestro",
9682
9793
  disallowedTools: buildMaestroDisallowedTools(callerDisallowedTools, opts.toolPolicy),
@@ -11839,7 +11950,7 @@ __export(exports_derive, {
11839
11950
  TopicForkCompactionError: () => TopicForkCompactionError,
11840
11951
  TopicDeriveBusyError: () => TopicDeriveBusyError
11841
11952
  });
11842
- import { createHash as createHash3, randomUUID as randomUUID11 } from "crypto";
11953
+ import { createHash as createHash4, randomUUID as randomUUID11 } from "crypto";
11843
11954
  import { mkdirSync as mkdirSync14, rmSync as rmSync5, unlinkSync as unlinkSync13 } from "fs";
11844
11955
  function getTopics() {
11845
11956
  return listTopics().filter((topic) => !isLegacySharedGeneral(topic.id));
@@ -11905,7 +12016,7 @@ function captureForkSnapshot(sourceTopicId, userId, topicTitle) {
11905
12016
  maxRowid: messageRows.at(-1)?.rowid ?? 0
11906
12017
  },
11907
12018
  active: isTopicRunning(sourceTopicId),
11908
- canonicalDigest: createHash3("sha256").update(entries.map((entry) => JSON.stringify(entry)).join(`
12019
+ canonicalDigest: createHash4("sha256").update(entries.map((entry) => JSON.stringify(entry)).join(`
11909
12020
  `)).digest("hex")
11910
12021
  };
11911
12022
  }
@@ -12398,7 +12509,7 @@ __export(exports_session_asks, {
12398
12509
  clearPendingAsk: () => clearPendingAsk,
12399
12510
  PENDING_ASK_TTL_MS: () => PENDING_ASK_TTL_MS
12400
12511
  });
12401
- import { createHash as createHash4 } from "crypto";
12512
+ import { createHash as createHash5 } from "crypto";
12402
12513
  import {
12403
12514
  closeSync as closeSync3,
12404
12515
  mkdirSync as mkdirSync16,
@@ -12412,14 +12523,14 @@ import {
12412
12523
  import { dirname as dirname14, join as join25 } from "path";
12413
12524
  function pendingAskDir(userId) {
12414
12525
  const rawUserId = String(userId);
12415
- const safeUserId = /^[A-Za-z0-9][A-Za-z0-9_.@-]{0,255}$/.test(rawUserId) && !rawUserId.includes("..") ? rawUserId : `sha256-${createHash4("sha256").update(rawUserId).digest("hex")}`;
12526
+ const safeUserId = /^[A-Za-z0-9][A-Za-z0-9_.@-]{0,255}$/.test(rawUserId) && !rawUserId.includes("..") ? rawUserId : `sha256-${createHash5("sha256").update(rawUserId).digest("hex")}`;
12416
12527
  return join25(resolveStorageSessionAsksDir(), safeUserId);
12417
12528
  }
12418
12529
  function encodeAskKey(key) {
12419
12530
  return JSON.stringify([key.from, key.to]);
12420
12531
  }
12421
12532
  function pendingAskPath(key) {
12422
- const digest = createHash4("sha256").update(encodeAskKey(key)).digest("hex");
12533
+ const digest = createHash5("sha256").update(encodeAskKey(key)).digest("hex");
12423
12534
  return join25(pendingAskDir(key.userId), `${ASK_FILENAME_PREFIX}${digest}.pending`);
12424
12535
  }
12425
12536
  function v2PendingAskPath(key) {
@@ -13920,7 +14031,7 @@ var init_ask_user_gates = __esm(async () => {
13920
14031
  });
13921
14032
 
13922
14033
  // ../../packages/core/src/agents/mcp-tools/ask-user.ts
13923
- import { createHash as createHash5, randomUUID as randomUUID14 } from "crypto";
14034
+ import { createHash as createHash6, randomUUID as randomUUID14 } from "crypto";
13924
14035
  import { z as z3 } from "zod";
13925
14036
  function normalizeAskText(value2, maxChars) {
13926
14037
  if (typeof value2 !== "string")
@@ -13957,7 +14068,7 @@ function normalizeAskUserQuestionInput(input) {
13957
14068
  return { question, choices };
13958
14069
  }
13959
14070
  function askBodyHash(question, choices) {
13960
- return createHash5("sha256").update(JSON.stringify({ question, choices })).digest("hex");
14071
+ return createHash6("sha256").update(JSON.stringify({ question, choices })).digest("hex");
13961
14072
  }
13962
14073
  function answerToolResult(answer) {
13963
14074
  if (!answer.userId)
@@ -14585,7 +14696,7 @@ var init_visuals = __esm(async () => {
14585
14696
  });
14586
14697
 
14587
14698
  // ../../packages/core/src/storage/token-stats.ts
14588
- import { createHash as createHash6 } from "crypto";
14699
+ import { createHash as createHash7 } from "crypto";
14589
14700
  import { mkdirSync as mkdirSync18 } from "fs";
14590
14701
  import { join as join27 } from "path";
14591
14702
  function emptyBucket() {
@@ -14594,12 +14705,13 @@ function emptyBucket() {
14594
14705
  outputTokens: 0,
14595
14706
  cacheCreationInputTokens: 0,
14596
14707
  cacheReadInputTokens: 0,
14597
- queries: 0
14708
+ queries: 0,
14709
+ estimatedCostUsd: 0
14598
14710
  };
14599
14711
  }
14600
14712
  function tokenStatsFileId(userId) {
14601
14713
  const rawUserId = String(userId);
14602
- return /^[A-Za-z0-9][A-Za-z0-9_.@-]{0,255}$/.test(rawUserId) && !rawUserId.includes("..") ? rawUserId : `sha256-${createHash6("sha256").update(rawUserId).digest("hex")}`;
14714
+ return /^[A-Za-z0-9][A-Za-z0-9_.@-]{0,255}$/.test(rawUserId) && !rawUserId.includes("..") ? rawUserId : `sha256-${createHash7("sha256").update(rawUserId).digest("hex")}`;
14603
14715
  }
14604
14716
  function queriesPath(userId) {
14605
14717
  const fileId = tokenStatsFileId(userId);
@@ -14621,16 +14733,41 @@ function loadRecords(userId) {
14621
14733
  }
14622
14734
  }
14623
14735
  function calcCost(b) {
14624
- return b.inputTokens / 1e6 * 3 + b.outputTokens / 1e6 * 15 + b.cacheCreationInputTokens / 1e6 * 3.75 + b.cacheReadInputTokens / 1e6 * 0.3;
14736
+ return b.estimatedCostUsd;
14737
+ }
14738
+ function estimateUsageCost(agent, model, usage) {
14739
+ const prices = TOKEN_PRICES[`${agent}:${model}`];
14740
+ if (!prices)
14741
+ return 0;
14742
+ return (usage.inputTokens * prices.input + usage.outputTokens * prices.output + (agent === "claude" ? usage.cacheCreationInputTokens * (prices.cacheWrite ?? prices.input) : 0) + usage.cacheReadInputTokens * prices.cacheRead) / 1e6;
14625
14743
  }
14626
- function recordUsage(userId, session, usage) {
14744
+ function isQueryRecord(value2) {
14745
+ if (!value2 || typeof value2 !== "object")
14746
+ return false;
14747
+ const record = value2;
14748
+ return record.schemaVersion === 2 && typeof record.timestamp === "string" && typeof record.session === "string" && typeof record.topicId === "string" && typeof record.agent === "string" && typeof record.model === "string" && typeof record.inputTokens === "number" && typeof record.outputTokens === "number" && typeof record.cacheCreationInputTokens === "number" && typeof record.cacheReadInputTokens === "number" && typeof record.estimatedCostUsd === "number";
14749
+ }
14750
+ function recordUsage(userId, session, usage, context) {
14751
+ const cacheReadInputTokens = usage.cacheReadInputTokens ?? 0;
14752
+ const inputTokens = context.agent === "claude" ? usage.inputTokens : Math.max(0, usage.inputTokens - cacheReadInputTokens);
14753
+ const normalized = {
14754
+ inputTokens,
14755
+ outputTokens: usage.outputTokens,
14756
+ cacheCreationInputTokens: usage.cacheCreationInputTokens ?? 0,
14757
+ cacheReadInputTokens
14758
+ };
14627
14759
  const record = {
14760
+ schemaVersion: 2,
14628
14761
  timestamp: new Date().toISOString(),
14629
14762
  session,
14630
- inputTokens: usage.inputTokens,
14631
- outputTokens: usage.outputTokens,
14632
- cacheCreationInputTokens: usage.cacheCreationInputTokens ?? 0,
14633
- cacheReadInputTokens: usage.cacheReadInputTokens ?? 0
14763
+ topicId: context.topicId,
14764
+ ...context.providerSessionId ? { providerSessionId: context.providerSessionId } : {},
14765
+ agent: context.agent,
14766
+ model: context.model,
14767
+ ...normalized,
14768
+ ...usage.contextTokens !== undefined ? { contextTokens: usage.contextTokens } : {},
14769
+ ...usage.contextWindow !== undefined ? { contextWindow: usage.contextWindow } : {},
14770
+ estimatedCostUsd: usage.costUsd ?? estimateUsageCost(context.agent, context.model, normalized)
14634
14771
  };
14635
14772
  try {
14636
14773
  appendJsonlEntry(queriesPath(userId), record);
@@ -14644,12 +14781,26 @@ function getStats(userId, from, to) {
14644
14781
  const toTs = to ? new Date(to).getTime() : Infinity;
14645
14782
  if (from && Number.isNaN(fromTs) || to && Number.isNaN(toTs)) {
14646
14783
  logger.warn({ from, to }, "token-stats: Invalid date range, returning empty");
14647
- return { total: emptyBucket(), byHour: {}, bySession: {}, estimatedCostUsd: 0 };
14784
+ return {
14785
+ total: emptyBucket(),
14786
+ byHour: {},
14787
+ bySession: {},
14788
+ currentSessions: [],
14789
+ ignoredLegacyRecords: 0,
14790
+ estimatedCostUsd: 0
14791
+ };
14648
14792
  }
14649
14793
  const total = emptyBucket();
14650
14794
  const byHour = {};
14651
14795
  const bySession = {};
14652
- for (const r of records) {
14796
+ const currentSessions = new Map;
14797
+ let ignoredLegacyRecords = 0;
14798
+ for (const raw of records) {
14799
+ if (!isQueryRecord(raw)) {
14800
+ ignoredLegacyRecords += 1;
14801
+ continue;
14802
+ }
14803
+ const r = raw;
14653
14804
  const ts = new Date(r.timestamp).getTime();
14654
14805
  if (ts < fromTs || ts > toTs)
14655
14806
  continue;
@@ -14664,14 +14815,47 @@ function getStats(userId, from, to) {
14664
14815
  bucket.cacheCreationInputTokens += r.cacheCreationInputTokens;
14665
14816
  bucket.cacheReadInputTokens += r.cacheReadInputTokens;
14666
14817
  bucket.queries += 1;
14818
+ bucket.estimatedCostUsd += r.estimatedCostUsd;
14819
+ }
14820
+ if (r.contextTokens !== undefined && r.contextWindow !== undefined && r.contextWindow > 0) {
14821
+ currentSessions.set(r.topicId, {
14822
+ timestamp: r.timestamp,
14823
+ topicId: r.topicId,
14824
+ topicTitle: r.session,
14825
+ ...r.providerSessionId ? { providerSessionId: r.providerSessionId } : {},
14826
+ agent: r.agent,
14827
+ model: r.model,
14828
+ contextTokens: r.contextTokens,
14829
+ contextWindow: r.contextWindow
14830
+ });
14667
14831
  }
14668
14832
  }
14669
- return { total, byHour, bySession, estimatedCostUsd: calcCost(total) };
14833
+ return {
14834
+ total,
14835
+ byHour,
14836
+ bySession,
14837
+ currentSessions: [...currentSessions.values()].sort((a, b) => b.timestamp.localeCompare(a.timestamp)),
14838
+ ignoredLegacyRecords,
14839
+ estimatedCostUsd: calcCost(total)
14840
+ };
14670
14841
  }
14842
+ var TOKEN_PRICES;
14671
14843
  var init_token_stats = __esm(async () => {
14672
14844
  init_jsonl();
14673
14845
  init_logger();
14674
14846
  await init_storage_host();
14847
+ TOKEN_PRICES = {
14848
+ "codex:gpt-5.6-sol": { input: 5, cacheRead: 0.5, output: 30 },
14849
+ "codex:gpt-5.6-terra": { input: 2.5, cacheRead: 0.25, output: 15 },
14850
+ "codex:gpt-5.6-luna": { input: 1, cacheRead: 0.1, output: 6 },
14851
+ "claude:fable": { input: 10, cacheWrite: 12.5, cacheRead: 1, output: 50 },
14852
+ "claude:opus": { input: 5, cacheWrite: 6.25, cacheRead: 0.5, output: 25 },
14853
+ "claude:sonnet": { input: 2, cacheWrite: 2.5, cacheRead: 0.2, output: 10 },
14854
+ "maestro:kimi-k3": { input: 3, cacheRead: 0.3, output: 15 },
14855
+ "maestro:kimi-k2.7-code": { input: 0.95, cacheRead: 0.19, output: 4 },
14856
+ "maestro:deepseek-pro": { input: 0.435, cacheRead: 0.003625, output: 0.87 },
14857
+ "maestro:deepseek-flash": { input: 0.14, cacheRead: 0.0028, output: 0.28 }
14858
+ };
14675
14859
  });
14676
14860
 
14677
14861
  // ../../packages/core/src/runtime/turn-event-stream.ts
@@ -14700,6 +14884,9 @@ async function runTurnEventStream(topicId, topicTitle, queryId, events, control,
14700
14884
  let pendingSawDelta = false;
14701
14885
  let accumulatedText = "";
14702
14886
  let pendingText = "";
14887
+ let assistantContextTokens = 0;
14888
+ let toolContextTokens = 0;
14889
+ let lastContextProgressAt = 0;
14703
14890
  let lastVisibleMessageId = null;
14704
14891
  const visibleMessageIds = [];
14705
14892
  let lastTaskPanelText = null;
@@ -14710,6 +14897,28 @@ async function runTurnEventStream(topicId, topicTitle, queryId, events, control,
14710
14897
  const toolSummaryOptions = {
14711
14898
  cwd: control.injectParams?.cwd ?? workspaceCwdFor(topicId)
14712
14899
  };
14900
+ const recordEventUsage = (usage) => {
14901
+ recordUsage(userId, topicTitle, usage, {
14902
+ topicId,
14903
+ ...control.sessionId ? { providerSessionId: control.sessionId } : {},
14904
+ agent: agentType,
14905
+ model
14906
+ });
14907
+ };
14908
+ const broadcastContextProgress = (force = false) => {
14909
+ if (silent)
14910
+ return;
14911
+ const now = Date.now();
14912
+ if (!force && lastContextProgressAt > 0 && now - lastContextProgressAt < 250)
14913
+ return;
14914
+ lastContextProgressAt = now;
14915
+ hub.broadcastAiStatus(topicId, {
14916
+ kind: "context_progress",
14917
+ queryId,
14918
+ assistantTokens: assistantContextTokens,
14919
+ toolTokens: toolContextTokens
14920
+ });
14921
+ };
14713
14922
  const nextSyntheticToolUseId = () => `tool-${queryId}-${++syntheticToolCounter}`;
14714
14923
  const bindToolUseId = (providerToolUseId, fallback) => {
14715
14924
  const providerId = normalizeToolUseId(providerToolUseId);
@@ -14819,6 +15028,8 @@ async function runTurnEventStream(topicId, topicTitle, queryId, events, control,
14819
15028
  const incoming = event.content;
14820
15029
  accumulatedText += incoming;
14821
15030
  pendingText += incoming;
15031
+ assistantContextTokens = estimateTextTokens(accumulatedText);
15032
+ broadcastContextProgress();
14822
15033
  break;
14823
15034
  }
14824
15035
  case "text":
@@ -14826,10 +15037,15 @@ async function runTurnEventStream(topicId, topicTitle, queryId, events, control,
14826
15037
  const incoming = event.content;
14827
15038
  accumulatedText += incoming;
14828
15039
  pendingText += incoming;
15040
+ assistantContextTokens = estimateTextTokens(accumulatedText);
15041
+ broadcastContextProgress();
14829
15042
  }
14830
15043
  break;
14831
15044
  case "tool_use":
14832
15045
  emitPendingAssistantMessage();
15046
+ toolContextTokens += estimateTextTokens(`${event.name}
15047
+ ${JSON.stringify(event.input ?? {})}`);
15048
+ broadcastContextProgress(true);
14833
15049
  if (isVisualsShowHtmlTool(event.name)) {
14834
15050
  const input = event.input;
14835
15051
  if (typeof input.html !== "string" || input.html.trim().length === 0) {
@@ -14983,6 +15199,8 @@ async function runTurnEventStream(topicId, topicTitle, queryId, events, control,
14983
15199
  }
14984
15200
  break;
14985
15201
  case "tool_result":
15202
+ toolContextTokens += estimateTextTokens(event.content);
15203
+ broadcastContextProgress(true);
14986
15204
  if (isVisualToolResultHandled(event.toolUseId))
14987
15205
  break;
14988
15206
  if (!silent) {
@@ -15028,7 +15246,7 @@ async function runTurnEventStream(topicId, topicTitle, queryId, events, control,
15028
15246
  break;
15029
15247
  case "result":
15030
15248
  if (event.usage)
15031
- recordUsage(userId, topicTitle, event.usage);
15249
+ recordEventUsage(event.usage);
15032
15250
  {
15033
15251
  const usage = event.usage ? {
15034
15252
  input: event.usage.inputTokens,
@@ -15078,6 +15296,8 @@ async function runTurnEventStream(topicId, topicTitle, queryId, events, control,
15078
15296
  }
15079
15297
  break;
15080
15298
  case "error":
15299
+ if (event.usage)
15300
+ recordEventUsage(event.usage);
15081
15301
  logger.warn({ topicId, queryId, agentType, model, silent, error: event.content }, "ai: provider returned error");
15082
15302
  terminalEmitted = true;
15083
15303
  errorOccurred = true;
@@ -15366,6 +15586,7 @@ __export(exports_turn_runner, {
15366
15586
  channelTranscriptSpeaker: () => channelTranscriptSpeaker,
15367
15587
  canonicalModelId: () => canonicalModelId,
15368
15588
  buildVideoHtml: () => buildVideoHtml,
15589
+ buildTurnReminderQuery: () => buildTurnReminderQuery,
15369
15590
  buildMermaidHtml: () => buildMermaidHtml,
15370
15591
  buildMentionOnlyChannelPrompt: () => buildMentionOnlyChannelPrompt,
15371
15592
  buildImageHtml: () => buildImageHtml,
@@ -15391,6 +15612,18 @@ function withDefaultPlaywright(configuredMcp, isManager) {
15391
15612
  enabled.add("background-bash");
15392
15613
  return [...enabled];
15393
15614
  }
15615
+ function buildTurnReminderQuery(agent, prompt, reminders) {
15616
+ const reminderPrompt = reminders.join(`
15617
+
15618
+ `);
15619
+ if (!reminderPrompt)
15620
+ return { prompt };
15621
+ if (agent === "maestro")
15622
+ return { prompt, ephemeralSystemPrompt: reminderPrompt };
15623
+ return { prompt: `${prompt}
15624
+
15625
+ ${reminderPrompt}` };
15626
+ }
15394
15627
  function appendSystemMessage(topicId, text2) {
15395
15628
  const message = {
15396
15629
  id: randomUUID16(),
@@ -16338,15 +16571,11 @@ function startAiTurn(params) {
16338
16571
  if (consumePlaywrightUnavailable(userId, topic.title)) {
16339
16572
  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>");
16340
16573
  }
16341
- const effectivePrompt = turnReminders.length > 0 ? `${agentPrompt}
16342
-
16343
- ${turnReminders.join(`
16344
-
16345
- `)}` : agentPrompt;
16574
+ const reminderQuery = buildTurnReminderQuery(agentKind, agentPrompt, turnReminders);
16346
16575
  try {
16347
16576
  yield* runAgent({
16348
16577
  agent: agentKind,
16349
- prompt: effectivePrompt,
16578
+ ...reminderQuery,
16350
16579
  attachments: promptAttachments,
16351
16580
  cwd: workspaceCwd,
16352
16581
  systemPrompt,
@@ -19551,7 +19780,7 @@ function createTokenStatsMcpServer(context, host = defaultTokenStatsMcpHost) {
19551
19780
  throw new Error("token-stats MCP requires userId");
19552
19781
  const server = new McpServer6({ name: "token-stats", version: "1.0.0" });
19553
19782
  server.tool("get_usage_stats", [
19554
- "Claude \uC0AC\uC6A9\uB7C9 \uD1B5\uACC4\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4. \uC644\uB8CC\uB41C \uCFFC\uB9AC\uB9C8\uB2E4 \uC138\uC158/\uC2DC\uAC04 \uC815\uBCF4\uAC00 \uAE30\uB85D\uB429\uB2C8\uB2E4. \uD604\uC7AC \uC2E4\uD589 \uC911\uC778 turn\uC740 \uC644\uB8CC \uD6C4 \uC9D1\uACC4\uB429\uB2C8\uB2E4.",
19783
+ "Claude/Codex/Maestro \uC0AC\uC6A9\uB7C9\uACFC provider session\uC758 \uCD5C\uC2E0 context \uC810\uC720\uC728\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. \uD604\uC7AC \uC2E4\uD589 \uC911\uC778 turn\uC740 \uC644\uB8CC \uD6C4 \uC9D1\uACC4\uB429\uB2C8\uB2E4.",
19555
19784
  "",
19556
19785
  "from/to \uC0DD\uB7B5 \uC2DC \uC804\uCCB4 \uAE30\uAC04. \uC790\uC5F0\uC5B4 \uC2DC\uAC04 \uD45C\uD604\uC744 ISO \uD615\uC2DD\uC73C\uB85C \uBCC0\uD658\uD574\uC11C \uB118\uACA8\uC8FC\uC138\uC694.",
19557
19786
  " \uC608: '\uC9C0\uB09C 3\uC2DC\uAC04' \u2192 from = \uD604\uC7AC -3h",
@@ -19565,18 +19794,36 @@ function createTokenStatsMcpServer(context, host = defaultTokenStatsMcpHost) {
19565
19794
  to: z7.string().optional().describe("\uC885\uB8CC \uC2DC\uAC01 ISO 8601. \uC0DD\uB7B5 \uC2DC \uD604\uC7AC"),
19566
19795
  groupBy: z7.enum(["hour", "session"]).optional().describe("\uC9D1\uACC4 \uAE30\uC900. \uAE30\uBCF8\uAC12: hour")
19567
19796
  }, async ({ from, to, groupBy = "hour" }) => {
19568
- const { total, byHour, bySession, estimatedCostUsd } = host.getStats(context.userId, from, to);
19797
+ const snapshot = host.getStats(context.userId, from, to);
19798
+ const {
19799
+ total,
19800
+ byHour,
19801
+ bySession,
19802
+ currentSessions = [],
19803
+ ignoredLegacyRecords = 0,
19804
+ estimatedCostUsd
19805
+ } = snapshot;
19569
19806
  const lines = [
19570
- "\uD83D\uDCCA Claude \uC0AC\uC6A9\uB7C9",
19807
+ "\uD83D\uDCCA Agent \uC0AC\uC6A9\uB7C9",
19571
19808
  from || to ? `\uAE30\uAC04: ${from ?? "\uC804\uCCB4"} ~ ${to ?? "\uD604\uC7AC"}` : "\uAE30\uAC04: \uC804\uCCB4",
19572
19809
  "",
19573
19810
  `\uCFFC\uB9AC \uD69F\uC218: ${total.queries.toLocaleString()}\uD68C`,
19574
- `\uC785\uB825 \uD1A0\uD070: ${total.inputTokens.toLocaleString()}`,
19811
+ `\uCE90\uC2DC \uBBF8\uC801\uC911 \uC785\uB825: ${total.inputTokens.toLocaleString()}`,
19575
19812
  `\uCD9C\uB825 \uD1A0\uD070: ${total.outputTokens.toLocaleString()}`,
19576
19813
  `\uCE90\uC2DC \uC4F0\uAE30: ${total.cacheCreationInputTokens.toLocaleString()}`,
19577
19814
  `\uCE90\uC2DC \uC77D\uAE30: ${total.cacheReadInputTokens.toLocaleString()}`,
19578
19815
  `\uC608\uC0C1 \uBE44\uC6A9: $${estimatedCostUsd.toFixed(4)} USD`
19579
19816
  ];
19817
+ if (ignoredLegacyRecords > 0) {
19818
+ lines.push(`\uAD6C\uD615 \uB808\uCF54\uB4DC \uC81C\uC678: ${ignoredLegacyRecords.toLocaleString()}\uAC74`);
19819
+ }
19820
+ if (currentSessions.length > 0) {
19821
+ lines.push("", `\uD83D\uDCD0 \uD604\uC7AC context (${currentSessions.length}\uAC1C provider session)`);
19822
+ for (const current3 of currentSessions) {
19823
+ const percent = current3.contextTokens / current3.contextWindow * 100;
19824
+ lines.push(` ${current3.topicTitle} \xB7 ${current3.agent}/${current3.model} ${current3.contextTokens.toLocaleString()} / ${current3.contextWindow.toLocaleString()} (${percent.toFixed(1)}%)`);
19825
+ }
19826
+ }
19580
19827
  const extraSummaryLines = host.extraSummaryLines?.(context.userId) ?? [];
19581
19828
  if (extraSummaryLines.length > 0)
19582
19829
  lines.push("", ...extraSummaryLines);
@@ -19584,7 +19831,7 @@ function createTokenStatsMcpServer(context, host = defaultTokenStatsMcpHost) {
19584
19831
  const sorted = Object.entries(bySession).map(([name, bucket]) => ({ name, ...bucket, cost: host.calcCost(bucket) })).sort((left, right) => right.cost - left.cost);
19585
19832
  lines.push("", `\uD83D\uDDC2 \uC138\uC158\uBCC4 \uC0AC\uC6A9\uB7C9 (${sorted.length}\uAC1C)`);
19586
19833
  for (const session of sorted) {
19587
- lines.push(` ${session.name} \uCFFC\uB9AC ${session.queries}\uD68C \uC785\uB825 ${session.inputTokens.toLocaleString()} / \uCD9C\uB825 ${session.outputTokens.toLocaleString()} $${session.cost.toFixed(4)}`);
19834
+ lines.push(` ${session.name} \uCFFC\uB9AC ${session.queries}\uD68C \uCE90\uC2DC \uBBF8\uC801\uC911 \uC785\uB825 ${session.inputTokens.toLocaleString()} / \uCE90\uC2DC \uC77D\uAE30 ${session.cacheReadInputTokens.toLocaleString()} / \uCD9C\uB825 ${session.outputTokens.toLocaleString()} $${session.cost.toFixed(4)}`);
19588
19835
  }
19589
19836
  } else {
19590
19837
  const hours = Object.keys(byHour).sort();
@@ -19696,4 +19943,4 @@ export {
19696
19943
  createAgentHealthMcpServer
19697
19944
  };
19698
19945
 
19699
- //# debugId=AF3DB07211992E0064756E2164756E21
19946
+ //# debugId=AD1DC675E382EEA164756E2164756E21