negotium 0.2.26 → 0.2.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-helpers.js +200 -118
- package/dist/agent-helpers.js.map +13 -13
- package/dist/hosted-agent.js +2 -2
- package/dist/hosted-agent.js.map +2 -2
- package/dist/main.js +1124 -511
- package/dist/main.js.map +43 -41
- package/dist/mcp-factories.js +294 -216
- package/dist/mcp-factories.js.map +13 -13
- package/dist/registry.js +2 -2
- package/dist/registry.js.map +2 -2
- package/dist/runtime/src/application/submit-runtime-gateway-turn.ts +8 -0
- package/dist/runtime/src/index.ts +6 -0
- package/dist/runtime/src/mcp/session-comm/default-host.ts +25 -6
- package/dist/runtime/src/mcp/session-comm/peer-forward.ts +14 -3
- package/dist/runtime/src/mcp/session-comm/server.ts +1 -1
- package/dist/runtime/src/mcp/session-comm/topics.ts +52 -16
- package/dist/runtime/src/mcp-runtime-host.ts +2 -2
- package/dist/runtime/src/node-host.ts +1 -1
- package/dist/runtime/src/runtime/turn-event-stream.ts +9 -1
- package/dist/runtime/src/runtime/turn-runner.ts +11 -2
- package/dist/runtime/src/storage/api-topics.ts +217 -32
- package/dist/runtime/src/storage/runtime-turn-requests.ts +26 -2
- package/dist/runtime/src/storage/token-stats.ts +27 -2
- package/dist/runtime/src/topics/create.ts +27 -1
- package/dist/runtime/src/topics/derive.ts +20 -6
- package/dist/runtime/src/topics/lifecycle.ts +2 -0
- package/dist/runtime/src/topics/personal-general.ts +28 -4
- package/dist/runtime/src/types/api.ts +7 -0
- package/dist/runtime/src/version.ts +1 -1
- package/dist/storage.js +141 -31
- package/dist/storage.js.map +4 -4
- package/dist/types/packages/core/src/mcp/session-comm/peer-forward.d.ts +7 -2
- package/dist/types/packages/core/src/runtime/turn-event-stream.d.ts +2 -0
- package/dist/types/packages/core/src/runtime/turn-runner.d.ts +3 -0
- package/dist/types/packages/core/src/storage/api-topics.d.ts +42 -3
- package/dist/types/packages/core/src/storage/runtime-turn-requests.d.ts +6 -0
- package/dist/types/packages/core/src/storage/token-stats.d.ts +2 -1
- package/dist/types/packages/core/src/topics/derive.d.ts +2 -0
- package/dist/types/packages/core/src/topics/personal-general.d.ts +4 -2
- package/dist/types/packages/core/src/types/api.d.ts +7 -0
- package/dist/types/packages/core/src/version.d.ts +1 -1
- package/package.json +1 -1
package/dist/mcp-factories.js
CHANGED
|
@@ -1016,7 +1016,7 @@ var init_claude_registry = __esm(() => {
|
|
|
1016
1016
|
});
|
|
1017
1017
|
|
|
1018
1018
|
// ../../packages/core/src/version.ts
|
|
1019
|
-
var NEGOTIUM_VERSION = "0.2.
|
|
1019
|
+
var NEGOTIUM_VERSION = "0.2.28";
|
|
1020
1020
|
|
|
1021
1021
|
// ../../packages/core/src/agents/codex-native-multi-agent.ts
|
|
1022
1022
|
import { spawn as spawn2 } from "child_process";
|
|
@@ -4065,6 +4065,15 @@ function defaultTopicSurface() {
|
|
|
4065
4065
|
function normalizeTopicSurface(value2) {
|
|
4066
4066
|
return value2 === "telegram" || value2 === "otium" || value2 === "terminal" ? value2 : "terminal";
|
|
4067
4067
|
}
|
|
4068
|
+
function normalizeSurfaceScope(value2) {
|
|
4069
|
+
if (typeof value2 !== "string")
|
|
4070
|
+
return null;
|
|
4071
|
+
const trimmed = value2.trim();
|
|
4072
|
+
return trimmed ? trimmed : null;
|
|
4073
|
+
}
|
|
4074
|
+
function defaultSurfaceScope() {
|
|
4075
|
+
return activeSurfaceScope ?? normalizeSurfaceScope(process.env.NEGOTIUM_SURFACE_SCOPE);
|
|
4076
|
+
}
|
|
4068
4077
|
function tableColumns(table) {
|
|
4069
4078
|
const rows = db.query(`PRAGMA table_info(${table})`).all();
|
|
4070
4079
|
return new Set(rows.map((row) => row.name));
|
|
@@ -4189,6 +4198,7 @@ function initializeApiTopicsSchema() {
|
|
|
4189
4198
|
is_subagent INTEGER NOT NULL DEFAULT 0 CHECK (is_subagent IN (0,1)),
|
|
4190
4199
|
visibility TEXT NOT NULL DEFAULT 'visible' CHECK (visibility IN ('visible','hidden')),
|
|
4191
4200
|
surface TEXT NOT NULL DEFAULT 'terminal' CHECK (surface IN ('terminal','telegram','otium')),
|
|
4201
|
+
surface_scope TEXT,
|
|
4192
4202
|
browser_profile TEXT NOT NULL DEFAULT 'default',
|
|
4193
4203
|
browser_profile_owner TEXT,
|
|
4194
4204
|
session_id TEXT,
|
|
@@ -4234,8 +4244,8 @@ function initializeApiTopicsSchema() {
|
|
|
4234
4244
|
const legacyBaseEffort = row.base_effort ?? row.default_effort;
|
|
4235
4245
|
db.query(`INSERT INTO api_topics_next
|
|
4236
4246
|
(id,title,kind,description,agent,base_model,base_effort,response_policy,
|
|
4237
|
-
created_at,last_message_at,parent_topic_id,memory_topic_id,memory_key,is_fork,is_subagent,visibility,surface,session_id)
|
|
4238
|
-
VALUES (
|
|
4247
|
+
created_at,last_message_at,parent_topic_id,memory_topic_id,memory_key,is_fork,is_subagent,visibility,surface,surface_scope,session_id)
|
|
4248
|
+
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`).run(String(row.id), String(row.title), normalized.kind, typeof row.description === "string" ? row.description : null, normalized.agent ?? null, typeof legacyBaseModel === "string" ? legacyBaseModel : null, typeof legacyBaseEffort === "string" ? legacyBaseEffort : null, normalized.aiMode, String(row.created_at), typeof row.last_message_at === "string" ? row.last_message_at : null, typeof row.parent_topic_id === "string" ? row.parent_topic_id : null, typeof row.memory_topic_id === "string" ? row.memory_topic_id : null, typeof row.memory_key === "string" ? row.memory_key : null, Number(row.is_fork ?? 0) !== 0 ? 1 : 0, Number(row.is_subagent ?? 0) !== 0 ? 1 : 0, row.visibility === "hidden" ? "hidden" : "visible", row.surface === undefined ? defaultTopicSurface() : normalizeTopicSurface(row.surface), normalizeSurfaceScope(row.surface_scope), typeof row.session_id === "string" ? row.session_id : null);
|
|
4239
4249
|
}
|
|
4240
4250
|
db.exec("DROP TABLE IF EXISTS topic_members");
|
|
4241
4251
|
db.exec("DROP TABLE api_topics");
|
|
@@ -4292,6 +4302,9 @@ function initializeApiTopicsSchema() {
|
|
|
4292
4302
|
if (!tableColumns("api_topics").has("surface")) {
|
|
4293
4303
|
db.exec("ALTER TABLE api_topics ADD COLUMN surface TEXT NOT NULL DEFAULT 'terminal'");
|
|
4294
4304
|
}
|
|
4305
|
+
if (!tableColumns("api_topics").has("surface_scope")) {
|
|
4306
|
+
db.exec("ALTER TABLE api_topics ADD COLUMN surface_scope TEXT");
|
|
4307
|
+
}
|
|
4295
4308
|
if (tableColumns("api_topics").has("access_mode")) {
|
|
4296
4309
|
try {
|
|
4297
4310
|
db.exec("ALTER TABLE api_topics DROP COLUMN access_mode");
|
|
@@ -4327,6 +4340,7 @@ function initializeApiTopicsSchema() {
|
|
|
4327
4340
|
backfillTopicSurfaces();
|
|
4328
4341
|
db.exec("CREATE INDEX IF NOT EXISTS idx_api_topics_last_message ON api_topics(last_message_at DESC)");
|
|
4329
4342
|
db.exec("CREATE INDEX IF NOT EXISTS idx_api_topics_surface ON api_topics(surface)");
|
|
4343
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_api_topics_surface_scope ON api_topics(surface, surface_scope)");
|
|
4330
4344
|
}
|
|
4331
4345
|
function backfillTopicSurfaces() {
|
|
4332
4346
|
db.exec(`
|
|
@@ -4347,22 +4361,24 @@ function backfillTopicSurfaces() {
|
|
|
4347
4361
|
logger.info({ surface }, "api_topics: surface backfilled");
|
|
4348
4362
|
}
|
|
4349
4363
|
function renameSurfaceTitleCollisions() {
|
|
4350
|
-
const rows = db.query("SELECT id, title, kind, surface FROM api_topics ORDER BY created_at ASC, rowid ASC").all();
|
|
4351
|
-
const
|
|
4364
|
+
const rows = db.query("SELECT id, title, kind, surface FROM api_topics WHERE kind != 'manager' ORDER BY created_at ASC, rowid ASC").all();
|
|
4365
|
+
const keyOf = (surface, kind, title) => [surface, kind, normalizedTitle(title)].join("\x00");
|
|
4366
|
+
const reserved = new Set(rows.map((row) => keyOf(row.surface, row.kind, row.title)));
|
|
4367
|
+
const used = new Set;
|
|
4352
4368
|
const update = db.query("UPDATE api_topics SET title = ? WHERE id = ?");
|
|
4353
4369
|
for (const row of rows) {
|
|
4354
|
-
const key = (title) =>
|
|
4355
|
-
if (!
|
|
4356
|
-
|
|
4370
|
+
const key = (title) => keyOf(row.surface, row.kind, title);
|
|
4371
|
+
if (!used.has(key(row.title))) {
|
|
4372
|
+
used.add(key(row.title));
|
|
4357
4373
|
continue;
|
|
4358
4374
|
}
|
|
4359
4375
|
let suffix = 2;
|
|
4360
4376
|
let candidate = `${row.title} (${suffix})`;
|
|
4361
|
-
while (
|
|
4377
|
+
while (reserved.has(key(candidate)) || used.has(key(candidate))) {
|
|
4362
4378
|
suffix += 1;
|
|
4363
4379
|
candidate = `${row.title} (${suffix})`;
|
|
4364
4380
|
}
|
|
4365
|
-
|
|
4381
|
+
used.add(key(candidate));
|
|
4366
4382
|
update.run(candidate, row.id);
|
|
4367
4383
|
logger.warn({ topicId: row.id, surface: row.surface, from: row.title, to: candidate }, "api_topics: renamed a duplicate title for surface-scoped uniqueness");
|
|
4368
4384
|
}
|
|
@@ -4425,7 +4441,8 @@ function rowToDto2(r, participants = getTopicParticipants(r.id), tellTargets) {
|
|
|
4425
4441
|
subagentReportMode: r.subagent_report_mode === "tell" || r.subagent_report_mode === "status-only" ? r.subagent_report_mode : "auto"
|
|
4426
4442
|
} : {},
|
|
4427
4443
|
visibility: normalizeTopicVisibility(r.visibility),
|
|
4428
|
-
surface: normalizeTopicSurface(r.surface)
|
|
4444
|
+
surface: normalizeTopicSurface(r.surface),
|
|
4445
|
+
surfaceScope: normalizeSurfaceScope(r.surface_scope)
|
|
4429
4446
|
};
|
|
4430
4447
|
}
|
|
4431
4448
|
function normalizeTopicVisibility(value2) {
|
|
@@ -4473,6 +4490,13 @@ function normalizeTopicState(input) {
|
|
|
4473
4490
|
function normalizedTitle(title) {
|
|
4474
4491
|
return title.trim().toLowerCase();
|
|
4475
4492
|
}
|
|
4493
|
+
function surfaceScopeForWrite(t) {
|
|
4494
|
+
if (normalizeTopicSurface(t.surface ?? defaultTopicSurface()) !== "otium")
|
|
4495
|
+
return null;
|
|
4496
|
+
if (t.surfaceScope !== undefined)
|
|
4497
|
+
return normalizeSurfaceScope(t.surfaceScope);
|
|
4498
|
+
return defaultSurfaceScope();
|
|
4499
|
+
}
|
|
4476
4500
|
function upsertTopic(t) {
|
|
4477
4501
|
const normalized = normalizeTopicState({
|
|
4478
4502
|
id: t.id,
|
|
@@ -4485,8 +4509,8 @@ function upsertTopic(t) {
|
|
|
4485
4509
|
db.query(`INSERT INTO api_topics
|
|
4486
4510
|
(id,title,kind,description,agent,base_model,base_effort,response_policy,
|
|
4487
4511
|
created_at,last_message_at,parent_topic_id,memory_topic_id,memory_key,is_fork,is_subagent,visibility,surface,
|
|
4488
|
-
subagent_report_mode)
|
|
4489
|
-
VALUES (
|
|
4512
|
+
surface_scope,subagent_report_mode)
|
|
4513
|
+
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
|
4490
4514
|
ON CONFLICT(id) DO UPDATE SET
|
|
4491
4515
|
title = excluded.title,
|
|
4492
4516
|
kind = excluded.kind,
|
|
@@ -4504,7 +4528,11 @@ function upsertTopic(t) {
|
|
|
4504
4528
|
is_subagent = excluded.is_subagent,
|
|
4505
4529
|
visibility = excluded.visibility,
|
|
4506
4530
|
surface = excluded.surface,
|
|
4507
|
-
|
|
4531
|
+
-- A room's workspace is fixed at creation (M-1). COALESCE, not
|
|
4532
|
+
-- assignment: an update may fill in a scope that was unknown when the
|
|
4533
|
+
-- room was created, but may never move a room to another workspace.
|
|
4534
|
+
surface_scope = COALESCE(api_topics.surface_scope, excluded.surface_scope),
|
|
4535
|
+
subagent_report_mode = excluded.subagent_report_mode`).run(t.id, t.title, normalized.kind, t.description ?? null, normalized.agent ?? null, t.defaultModel ?? null, t.defaultEffort ?? null, normalized.aiMode, t.createdAt, t.lastMessageAt ?? null, t.parentTopicId ?? null, t.memoryTopicId ?? null, t.memoryKey ?? null, t.isFork ? 1 : 0, t.isSubagent ? 1 : 0, normalizeTopicVisibility(t.visibility), normalizeTopicSurface(t.surface ?? defaultTopicSurface()), surfaceScopeForWrite(t), t.subagentReportMode ?? "auto");
|
|
4508
4536
|
db.query("DELETE FROM topic_members WHERE topic_id = ?").run(t.id);
|
|
4509
4537
|
for (const participant of t.participants) {
|
|
4510
4538
|
db.query("INSERT INTO topic_members (topic_id,user_id,role) VALUES (?,?,?)").run(t.id, participant.userId, participant.role);
|
|
@@ -4518,7 +4546,8 @@ function upsertTopic(t) {
|
|
|
4518
4546
|
})();
|
|
4519
4547
|
}
|
|
4520
4548
|
function listTopics(opts = {}) {
|
|
4521
|
-
const
|
|
4549
|
+
const scoped = Object.hasOwn(opts, "surfaceScope");
|
|
4550
|
+
const rows = opts.surface ? db.query(`SELECT * FROM api_topics WHERE surface = ?${scoped ? " AND surface_scope IS ?" : ""} ORDER BY last_message_at DESC`).all(...scoped ? [normalizeTopicSurface(opts.surface), normalizeSurfaceScope(opts.surfaceScope)] : [normalizeTopicSurface(opts.surface)]) : db.query("SELECT * FROM api_topics ORDER BY last_message_at DESC").all();
|
|
4522
4551
|
const participants = getAllTopicParticipants();
|
|
4523
4552
|
const tellTargets = rows.some((row) => row.is_subagent !== 0) ? getAllSubagentTellTargets() : undefined;
|
|
4524
4553
|
return rows.map((row) => rowToDto2(row, participants.get(row.id) ?? [], tellTargets));
|
|
@@ -4527,15 +4556,24 @@ function getTopic(id) {
|
|
|
4527
4556
|
const r = db.query("SELECT * FROM api_topics WHERE id = ?").get(id);
|
|
4528
4557
|
return r ? rowToDto2(r) : null;
|
|
4529
4558
|
}
|
|
4530
|
-
function getManagerTopicForUser(userId) {
|
|
4531
|
-
const
|
|
4559
|
+
function getManagerTopicForUser(userId, surface, opts = {}) {
|
|
4560
|
+
const scoped = Object.hasOwn(opts, "surfaceScope");
|
|
4561
|
+
const sql = `SELECT t.* FROM api_topics t
|
|
4532
4562
|
JOIN topic_members m ON m.topic_id = t.id
|
|
4533
4563
|
WHERE t.kind = 'manager'
|
|
4534
4564
|
AND t.id != ?
|
|
4535
4565
|
AND m.user_id = ?
|
|
4536
4566
|
AND m.role = 'owner'
|
|
4567
|
+
${surface ? "AND t.surface = ?" : ""}
|
|
4568
|
+
${scoped ? "AND t.surface_scope IS ?" : ""}
|
|
4537
4569
|
ORDER BY t.created_at ASC
|
|
4538
|
-
LIMIT 1
|
|
4570
|
+
LIMIT 1`;
|
|
4571
|
+
const params = [GENERAL_TOPIC_ID, userId];
|
|
4572
|
+
if (surface)
|
|
4573
|
+
params.push(surface);
|
|
4574
|
+
if (scoped)
|
|
4575
|
+
params.push(normalizeSurfaceScope(opts.surfaceScope));
|
|
4576
|
+
const row = db.query(sql).get(...params);
|
|
4539
4577
|
return row ? rowToDto2(row) : null;
|
|
4540
4578
|
}
|
|
4541
4579
|
function getTopicMemoryOrigin(id) {
|
|
@@ -4566,18 +4604,17 @@ function getTopicByName(title) {
|
|
|
4566
4604
|
function findTopicTitleConflict(title, kind, opts = {}) {
|
|
4567
4605
|
const wanted = normalizedTitle(title);
|
|
4568
4606
|
const surface = normalizeTopicSurface(opts.surface ?? defaultTopicSurface());
|
|
4607
|
+
const surfaceScope = Object.hasOwn(opts, "surfaceScope") ? normalizeSurfaceScope(opts.surfaceScope) : surface === "otium" ? defaultSurfaceScope() : null;
|
|
4569
4608
|
const generalTitleRequested = wanted === normalizedTitle(GENERAL_TOPIC_ID);
|
|
4570
4609
|
if (generalTitleRequested && opts.excludeTopicId !== GENERAL_TOPIC_ID) {
|
|
4571
4610
|
const general = db.query("SELECT * FROM api_topics WHERE id = ?").get(GENERAL_TOPIC_ID);
|
|
4572
4611
|
if (general)
|
|
4573
4612
|
return rowToDto2(general);
|
|
4574
4613
|
}
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
params.push(kind, GENERAL_TOPIC_ID);
|
|
4580
|
-
}
|
|
4614
|
+
if (kind === "manager")
|
|
4615
|
+
return null;
|
|
4616
|
+
const params = [wanted, surface, surfaceScope, kind, GENERAL_TOPIC_ID];
|
|
4617
|
+
let sql = "SELECT * FROM api_topics WHERE LOWER(TRIM(title)) = ? AND surface = ? AND surface_scope IS ? AND (kind = ? OR id = ?)";
|
|
4581
4618
|
if (opts.excludeTopicId) {
|
|
4582
4619
|
sql += " AND id != ?";
|
|
4583
4620
|
params.push(opts.excludeTopicId);
|
|
@@ -4658,7 +4695,7 @@ function reparentTopicChildren(deletedTopicId, replacementParentTopicId) {
|
|
|
4658
4695
|
}
|
|
4659
4696
|
return rows.map((row) => row.id);
|
|
4660
4697
|
}
|
|
4661
|
-
var DEFAULT_AGENT_ROOM_AGENT = "maestro", SURFACE_BACKFILL_MIGRATION = "api_topics_surface_backfill_20260808";
|
|
4698
|
+
var DEFAULT_AGENT_ROOM_AGENT = "maestro", activeSurfaceScope = null, SURFACE_BACKFILL_MIGRATION = "api_topics_surface_backfill_20260808";
|
|
4662
4699
|
var init_api_topics = __esm(async () => {
|
|
4663
4700
|
init_constants();
|
|
4664
4701
|
init_logger();
|
|
@@ -4989,7 +5026,8 @@ function mergeRuntimeUserTurnRequest(input) {
|
|
|
4989
5026
|
const now = Date.now();
|
|
4990
5027
|
return db.transaction(() => {
|
|
4991
5028
|
const rows = db.query("SELECT * FROM runtime_user_turn_requests WHERE topic_id = ? ORDER BY created_at ASC, rowid ASC").all(input.topicId);
|
|
4992
|
-
const
|
|
5029
|
+
const thread = input.execution.threadRootId;
|
|
5030
|
+
const previous = rows.map(rowToRequest).filter((request) => request.execution?.threadRootId === thread);
|
|
4993
5031
|
const omittedRequestIds = new Set([
|
|
4994
5032
|
...input.omitRequestIds ?? [],
|
|
4995
5033
|
...previous.filter((request) => Boolean(request.execution?.providerSessionId)).map((request) => request.requestId)
|
|
@@ -5025,7 +5063,11 @@ function mergeRuntimeUserTurnRequest(input) {
|
|
|
5025
5063
|
execution.sessionIdSpecified = true;
|
|
5026
5064
|
}
|
|
5027
5065
|
const attachments = flattenUserTurnAttachments(userMessages);
|
|
5028
|
-
|
|
5066
|
+
const absorbed = previous.map((request) => request.requestId);
|
|
5067
|
+
if (absorbed.length > 0) {
|
|
5068
|
+
db.query(`DELETE FROM runtime_user_turn_requests
|
|
5069
|
+
WHERE topic_id = ? AND request_id IN (${absorbed.map(() => "?").join(",")})`).run(input.topicId, ...absorbed);
|
|
5070
|
+
}
|
|
5029
5071
|
db.query(`INSERT INTO runtime_user_turn_requests
|
|
5030
5072
|
(request_id, topic_id, user_id, prompt, user_messages_json, attachments_json,
|
|
5031
5073
|
allow_auto_continue, execution_json, topic_epoch, created_at,
|
|
@@ -7480,8 +7522,10 @@ var init_conversations = __esm(async () => {
|
|
|
7480
7522
|
|
|
7481
7523
|
// ../../packages/core/src/topics/personal-general.ts
|
|
7482
7524
|
import { randomUUID as randomUUID8 } from "crypto";
|
|
7483
|
-
function ensurePersonalGeneral(userId) {
|
|
7484
|
-
const
|
|
7525
|
+
function ensurePersonalGeneral(userId, surface, opts = {}) {
|
|
7526
|
+
const scope = normalizeTopicSurface(surface ?? defaultTopicSurface());
|
|
7527
|
+
const surfaceScope = opts.surfaceScope !== undefined ? normalizeSurfaceScope(opts.surfaceScope) : scope === "otium" ? defaultSurfaceScope() : null;
|
|
7528
|
+
const existing = getManagerTopicForUser(userId, scope, { surfaceScope });
|
|
7485
7529
|
if (existing) {
|
|
7486
7530
|
if (existing.description === LEGACY_PERSONAL_GENERAL_DESCRIPTION) {
|
|
7487
7531
|
existing.description = PERSONAL_GENERAL_DESCRIPTION;
|
|
@@ -7505,6 +7549,8 @@ function ensurePersonalGeneral(userId) {
|
|
|
7505
7549
|
aiMode: "always",
|
|
7506
7550
|
aiMention: false,
|
|
7507
7551
|
participants: [{ userId, role: "owner" }],
|
|
7552
|
+
surface: scope,
|
|
7553
|
+
surfaceScope,
|
|
7508
7554
|
createdAt: now,
|
|
7509
7555
|
lastMessageAt: now
|
|
7510
7556
|
};
|
|
@@ -12046,8 +12092,8 @@ function updateTopic(topicId, patch) {
|
|
|
12046
12092
|
function isParticipant(topic, userId) {
|
|
12047
12093
|
return topic.participants.some((p) => p.userId === userId);
|
|
12048
12094
|
}
|
|
12049
|
-
function nextDerivedTopicTitle(sourceTitle, kind, suffix, surface) {
|
|
12050
|
-
const visibleTitles = new Set(listTopics(surface ? { surface } : {}).filter((topic) => topic.kind === kind).map((topic) => topic.title.toLowerCase()));
|
|
12095
|
+
function nextDerivedTopicTitle(sourceTitle, kind, suffix, surface, surfaceScope) {
|
|
12096
|
+
const visibleTitles = new Set(listTopics(surface ? { surface, surfaceScope: surfaceScope ?? null } : {}).filter((topic) => topic.kind === kind).map((topic) => topic.title.toLowerCase()));
|
|
12051
12097
|
let n = 1;
|
|
12052
12098
|
let title = `${sourceTitle}-${suffix}-${n}`;
|
|
12053
12099
|
while (visibleTitles.has(title.toLowerCase())) {
|
|
@@ -12112,8 +12158,9 @@ async function createDerivedTopicImpl(topic, sourceTopicId, userId, copyHistory,
|
|
|
12112
12158
|
] : [{ userId, role: "owner" }];
|
|
12113
12159
|
const kind = topic.kind ?? inferTopicKind(topic);
|
|
12114
12160
|
const surface = topic.surface ?? defaultTopicSurface();
|
|
12115
|
-
const
|
|
12116
|
-
const
|
|
12161
|
+
const surfaceScope = topic.surfaceScope ?? null;
|
|
12162
|
+
const title = opts?.name?.trim() || nextDerivedTopicTitle(topic.title, kind, suffix, surface, surfaceScope);
|
|
12163
|
+
const conflict = findTopicTitleConflict(title, kind, { surface, surfaceScope });
|
|
12117
12164
|
if (conflict) {
|
|
12118
12165
|
logger.info({ sourceTopicId, title, kind, conflictTopicId: conflict.id }, "createDerivedTopic: title conflict");
|
|
12119
12166
|
throw new TopicTitleConflictError(title);
|
|
@@ -12136,7 +12183,8 @@ async function createDerivedTopicImpl(topic, sourceTopicId, userId, copyHistory,
|
|
|
12136
12183
|
isFork: copyHistory,
|
|
12137
12184
|
...subagent ? { isSubagent: true } : {},
|
|
12138
12185
|
visibility: topic.visibility,
|
|
12139
|
-
surface
|
|
12186
|
+
surface,
|
|
12187
|
+
surfaceScope
|
|
12140
12188
|
};
|
|
12141
12189
|
let sessionId;
|
|
12142
12190
|
let rollbackHandle;
|
|
@@ -12244,7 +12292,10 @@ async function createDerivedTopicImpl(topic, sourceTopicId, userId, copyHistory,
|
|
|
12244
12292
|
if (!currentSource || currentSource.kind === "manager" || !isParticipant(currentSource, userId) || subagent && isRuntimeTopicMaintenance(sourceTopicId)) {
|
|
12245
12293
|
throw new TopicDeriveBusyError("Source topic changed while deriving; try again");
|
|
12246
12294
|
}
|
|
12247
|
-
const transactionalConflict = findTopicTitleConflict(title, kind, {
|
|
12295
|
+
const transactionalConflict = findTopicTitleConflict(title, kind, {
|
|
12296
|
+
surface,
|
|
12297
|
+
surfaceScope
|
|
12298
|
+
});
|
|
12248
12299
|
if (transactionalConflict)
|
|
12249
12300
|
throw new TopicTitleConflictError(title);
|
|
12250
12301
|
upsertTopic(derived);
|
|
@@ -12533,13 +12584,14 @@ async function forwardToPeer(args) {
|
|
|
12533
12584
|
error: forwarded.configured ? "remote session bridge is configured but unavailable" : "remote nodes are not connected on this negotium node (standalone mode)"
|
|
12534
12585
|
};
|
|
12535
12586
|
}
|
|
12536
|
-
async function peerSessionsForUser(userId, sourceQueryId) {
|
|
12587
|
+
async function peerSessionsForUser(userId, sourceQueryId, fromTopicId) {
|
|
12537
12588
|
if (activeBridge)
|
|
12538
|
-
return activeBridge.sessions(userId, sourceQueryId);
|
|
12589
|
+
return activeBridge.sessions(userId, sourceQueryId, fromTopicId);
|
|
12539
12590
|
const sessions = await callLoopbackBridge({
|
|
12540
12591
|
action: "sessions",
|
|
12541
12592
|
userId,
|
|
12542
|
-
sourceQueryId
|
|
12593
|
+
sourceQueryId,
|
|
12594
|
+
fromTopicId
|
|
12543
12595
|
});
|
|
12544
12596
|
if (sessions.result)
|
|
12545
12597
|
return sessions.result;
|
|
@@ -13324,6 +13376,190 @@ var init_self_schedules = __esm(async () => {
|
|
|
13324
13376
|
});
|
|
13325
13377
|
});
|
|
13326
13378
|
|
|
13379
|
+
// ../../packages/core/src/storage/token-stats.ts
|
|
13380
|
+
import { createHash as createHash6 } from "crypto";
|
|
13381
|
+
import { mkdirSync as mkdirSync17, writeFileSync as writeFileSync15 } from "fs";
|
|
13382
|
+
import { join as join26 } from "path";
|
|
13383
|
+
function emptyBucket() {
|
|
13384
|
+
return {
|
|
13385
|
+
inputTokens: 0,
|
|
13386
|
+
outputTokens: 0,
|
|
13387
|
+
cacheCreationInputTokens: 0,
|
|
13388
|
+
cacheReadInputTokens: 0,
|
|
13389
|
+
queries: 0,
|
|
13390
|
+
estimatedCostUsd: 0
|
|
13391
|
+
};
|
|
13392
|
+
}
|
|
13393
|
+
function tokenStatsFileId(userId) {
|
|
13394
|
+
const rawUserId = String(userId);
|
|
13395
|
+
return /^[A-Za-z0-9][A-Za-z0-9_.@-]{0,255}$/.test(rawUserId) && !rawUserId.includes("..") ? rawUserId : `sha256-${createHash6("sha256").update(rawUserId).digest("hex")}`;
|
|
13396
|
+
}
|
|
13397
|
+
function queriesPath(userId) {
|
|
13398
|
+
const fileId = tokenStatsFileId(userId);
|
|
13399
|
+
const logDir = resolveStorageLogDir();
|
|
13400
|
+
mkdirSync17(logDir, { recursive: true });
|
|
13401
|
+
return join26(logDir, `token-queries-${fileId}.jsonl`);
|
|
13402
|
+
}
|
|
13403
|
+
function loadRecords(userId) {
|
|
13404
|
+
try {
|
|
13405
|
+
return readJsonlLines(queriesPath(userId)).flatMap((line) => {
|
|
13406
|
+
try {
|
|
13407
|
+
return [JSON.parse(line)];
|
|
13408
|
+
} catch {
|
|
13409
|
+
return [];
|
|
13410
|
+
}
|
|
13411
|
+
});
|
|
13412
|
+
} catch {
|
|
13413
|
+
return [];
|
|
13414
|
+
}
|
|
13415
|
+
}
|
|
13416
|
+
function calcCost(b) {
|
|
13417
|
+
return b.estimatedCostUsd;
|
|
13418
|
+
}
|
|
13419
|
+
function estimateUsageCost(agent, model, usage) {
|
|
13420
|
+
const prices = TOKEN_PRICES[`${agent}:${model}`];
|
|
13421
|
+
if (!prices)
|
|
13422
|
+
return 0;
|
|
13423
|
+
return (usage.inputTokens * prices.input + usage.outputTokens * prices.output + (agent === "claude" ? usage.cacheCreationInputTokens * (prices.cacheWrite ?? prices.input) : 0) + usage.cacheReadInputTokens * prices.cacheRead) / 1e6;
|
|
13424
|
+
}
|
|
13425
|
+
function isQueryRecord(value2) {
|
|
13426
|
+
if (!value2 || typeof value2 !== "object")
|
|
13427
|
+
return false;
|
|
13428
|
+
const record = value2;
|
|
13429
|
+
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";
|
|
13430
|
+
}
|
|
13431
|
+
function recordUsage(userId, session, usage, context) {
|
|
13432
|
+
const cacheReadInputTokens = usage.cacheReadInputTokens ?? 0;
|
|
13433
|
+
const inputTokens = context.agent === "claude" ? usage.inputTokens : Math.max(0, usage.inputTokens - cacheReadInputTokens);
|
|
13434
|
+
const normalized = {
|
|
13435
|
+
inputTokens,
|
|
13436
|
+
outputTokens: usage.outputTokens,
|
|
13437
|
+
cacheCreationInputTokens: usage.cacheCreationInputTokens ?? 0,
|
|
13438
|
+
cacheReadInputTokens
|
|
13439
|
+
};
|
|
13440
|
+
const record = {
|
|
13441
|
+
schemaVersion: 2,
|
|
13442
|
+
timestamp: new Date().toISOString(),
|
|
13443
|
+
session,
|
|
13444
|
+
topicId: context.topicId,
|
|
13445
|
+
...context.providerSessionId ? { providerSessionId: context.providerSessionId } : {},
|
|
13446
|
+
agent: context.agent,
|
|
13447
|
+
model: context.model,
|
|
13448
|
+
...normalized,
|
|
13449
|
+
...usage.contextTokens !== undefined ? { contextTokens: usage.contextTokens } : {},
|
|
13450
|
+
...usage.contextWindow !== undefined ? { contextWindow: usage.contextWindow } : {},
|
|
13451
|
+
estimatedCostUsd: usage.costUsd ?? estimateUsageCost(context.agent, context.model, normalized)
|
|
13452
|
+
};
|
|
13453
|
+
try {
|
|
13454
|
+
appendJsonlEntry(queriesPath(userId), record);
|
|
13455
|
+
} catch (e) {
|
|
13456
|
+
logger.warn({ err: e, userId }, "token-stats: Failed to record");
|
|
13457
|
+
}
|
|
13458
|
+
}
|
|
13459
|
+
function deleteTopicStats(userId, topicId) {
|
|
13460
|
+
const path = queriesPath(userId);
|
|
13461
|
+
try {
|
|
13462
|
+
const kept = readJsonlLines(path).filter((line) => {
|
|
13463
|
+
try {
|
|
13464
|
+
const record = JSON.parse(line);
|
|
13465
|
+
return record.topicId !== topicId;
|
|
13466
|
+
} catch {
|
|
13467
|
+
return true;
|
|
13468
|
+
}
|
|
13469
|
+
});
|
|
13470
|
+
writeFileSync15(path, kept.length > 0 ? `${kept.join(`
|
|
13471
|
+
`)}
|
|
13472
|
+
` : "", "utf-8");
|
|
13473
|
+
} catch (e) {
|
|
13474
|
+
if (e.code === "ENOENT")
|
|
13475
|
+
return;
|
|
13476
|
+
logger.warn({ err: e, userId, topicId }, "token-stats: Failed to delete topic stats");
|
|
13477
|
+
}
|
|
13478
|
+
}
|
|
13479
|
+
function getStats(userId, from, to) {
|
|
13480
|
+
const records = loadRecords(userId);
|
|
13481
|
+
const fromTs = from ? new Date(from).getTime() : 0;
|
|
13482
|
+
const toTs = to ? new Date(to).getTime() : Infinity;
|
|
13483
|
+
if (from && Number.isNaN(fromTs) || to && Number.isNaN(toTs)) {
|
|
13484
|
+
logger.warn({ from, to }, "token-stats: Invalid date range, returning empty");
|
|
13485
|
+
return {
|
|
13486
|
+
total: emptyBucket(),
|
|
13487
|
+
byHour: {},
|
|
13488
|
+
bySession: {},
|
|
13489
|
+
currentSessions: [],
|
|
13490
|
+
ignoredLegacyRecords: 0,
|
|
13491
|
+
estimatedCostUsd: 0
|
|
13492
|
+
};
|
|
13493
|
+
}
|
|
13494
|
+
const total = emptyBucket();
|
|
13495
|
+
const byHour = {};
|
|
13496
|
+
const bySession = {};
|
|
13497
|
+
const currentSessions = new Map;
|
|
13498
|
+
let ignoredLegacyRecords = 0;
|
|
13499
|
+
for (const raw of records) {
|
|
13500
|
+
if (!isQueryRecord(raw)) {
|
|
13501
|
+
ignoredLegacyRecords += 1;
|
|
13502
|
+
continue;
|
|
13503
|
+
}
|
|
13504
|
+
const r = raw;
|
|
13505
|
+
const ts = new Date(r.timestamp).getTime();
|
|
13506
|
+
if (ts < fromTs || ts > toTs)
|
|
13507
|
+
continue;
|
|
13508
|
+
const hourKey = r.timestamp.slice(0, 13);
|
|
13509
|
+
if (!byHour[hourKey])
|
|
13510
|
+
byHour[hourKey] = emptyBucket();
|
|
13511
|
+
if (!bySession[r.session])
|
|
13512
|
+
bySession[r.session] = emptyBucket();
|
|
13513
|
+
for (const bucket of [total, byHour[hourKey], bySession[r.session]]) {
|
|
13514
|
+
bucket.inputTokens += r.inputTokens;
|
|
13515
|
+
bucket.outputTokens += r.outputTokens;
|
|
13516
|
+
bucket.cacheCreationInputTokens += r.cacheCreationInputTokens;
|
|
13517
|
+
bucket.cacheReadInputTokens += r.cacheReadInputTokens;
|
|
13518
|
+
bucket.queries += 1;
|
|
13519
|
+
bucket.estimatedCostUsd += r.estimatedCostUsd;
|
|
13520
|
+
}
|
|
13521
|
+
if (r.contextTokens !== undefined && r.contextWindow !== undefined && r.contextWindow > 0) {
|
|
13522
|
+
currentSessions.set(r.topicId, {
|
|
13523
|
+
timestamp: r.timestamp,
|
|
13524
|
+
topicId: r.topicId,
|
|
13525
|
+
topicTitle: r.session,
|
|
13526
|
+
...r.providerSessionId ? { providerSessionId: r.providerSessionId } : {},
|
|
13527
|
+
agent: r.agent,
|
|
13528
|
+
model: r.model,
|
|
13529
|
+
contextTokens: r.contextTokens,
|
|
13530
|
+
contextWindow: r.contextWindow
|
|
13531
|
+
});
|
|
13532
|
+
}
|
|
13533
|
+
}
|
|
13534
|
+
return {
|
|
13535
|
+
total,
|
|
13536
|
+
byHour,
|
|
13537
|
+
bySession,
|
|
13538
|
+
currentSessions: [...currentSessions.values()].sort((a, b) => b.timestamp.localeCompare(a.timestamp)),
|
|
13539
|
+
ignoredLegacyRecords,
|
|
13540
|
+
estimatedCostUsd: calcCost(total)
|
|
13541
|
+
};
|
|
13542
|
+
}
|
|
13543
|
+
var TOKEN_PRICES;
|
|
13544
|
+
var init_token_stats = __esm(async () => {
|
|
13545
|
+
init_jsonl();
|
|
13546
|
+
init_logger();
|
|
13547
|
+
await init_api_topics();
|
|
13548
|
+
await init_storage_host();
|
|
13549
|
+
TOKEN_PRICES = {
|
|
13550
|
+
"codex:gpt-5.6-sol": { input: 5, cacheRead: 0.5, output: 30 },
|
|
13551
|
+
"codex:gpt-5.6-terra": { input: 2.5, cacheRead: 0.25, output: 15 },
|
|
13552
|
+
"codex:gpt-5.6-luna": { input: 1, cacheRead: 0.1, output: 6 },
|
|
13553
|
+
"claude:fable": { input: 10, cacheWrite: 12.5, cacheRead: 1, output: 50 },
|
|
13554
|
+
"claude:opus": { input: 5, cacheWrite: 6.25, cacheRead: 0.5, output: 25 },
|
|
13555
|
+
"claude:sonnet": { input: 2, cacheWrite: 2.5, cacheRead: 0.2, output: 10 },
|
|
13556
|
+
"maestro:kimi-k3": { input: 3, cacheRead: 0.3, output: 15 },
|
|
13557
|
+
"maestro:kimi-k2.7-code": { input: 0.95, cacheRead: 0.19, output: 4 },
|
|
13558
|
+
"maestro:deepseek-pro": { input: 0.435, cacheRead: 0.003625, output: 0.87 },
|
|
13559
|
+
"maestro:deepseek-flash": { input: 0.14, cacheRead: 0.0028, output: 0.28 }
|
|
13560
|
+
};
|
|
13561
|
+
});
|
|
13562
|
+
|
|
13327
13563
|
// ../../packages/core/src/topics/lifecycle.ts
|
|
13328
13564
|
var exports_lifecycle = {};
|
|
13329
13565
|
__export(exports_lifecycle, {
|
|
@@ -13373,6 +13609,7 @@ async function cleanupParticipantResources(topic, userIds, sessionId, cwd, purge
|
|
|
13373
13609
|
cleanupSessionInboxFiles(participantUserId, topic.id, topic.title);
|
|
13374
13610
|
clearQueryState(participantUserId, topic.id, topic.title);
|
|
13375
13611
|
clearQueryUsageAlert(participantUserId, topic.id);
|
|
13612
|
+
deleteTopicStats(participantUserId, topic.id);
|
|
13376
13613
|
deletePendingAsksForTopic({ userId: participantUserId, topicName: topic.title });
|
|
13377
13614
|
}
|
|
13378
13615
|
return true;
|
|
@@ -13527,6 +13764,7 @@ var init_lifecycle = __esm(async () => {
|
|
|
13527
13764
|
await init_runtime_turn_requests();
|
|
13528
13765
|
await init_self_schedules();
|
|
13529
13766
|
await init_session_asks();
|
|
13767
|
+
await init_token_stats();
|
|
13530
13768
|
await init_topic_archive();
|
|
13531
13769
|
await init_topic_archive_state();
|
|
13532
13770
|
TopicArchiveRequiredError = class TopicArchiveRequiredError extends Error {
|
|
@@ -13561,8 +13799,8 @@ var init_lifecycle = __esm(async () => {
|
|
|
13561
13799
|
|
|
13562
13800
|
// ../../packages/core/src/runtime/attachments.ts
|
|
13563
13801
|
import { randomUUID as randomUUID13 } from "crypto";
|
|
13564
|
-
import { copyFileSync as copyFileSync2, mkdirSync as
|
|
13565
|
-
import { basename as basename6, join as
|
|
13802
|
+
import { copyFileSync as copyFileSync2, mkdirSync as mkdirSync18, writeFileSync as writeFileSync16 } from "fs";
|
|
13803
|
+
import { basename as basename6, join as join27 } from "path";
|
|
13566
13804
|
function workspaceCwdFor(topicId) {
|
|
13567
13805
|
return resolveTopicWorkspaceDir(topicId);
|
|
13568
13806
|
}
|
|
@@ -13575,7 +13813,7 @@ function materializePromptAttachments(topicId, queryId, attachmentIds) {
|
|
|
13575
13813
|
if (!attachmentIds?.length)
|
|
13576
13814
|
return [];
|
|
13577
13815
|
const out = [];
|
|
13578
|
-
const destDir =
|
|
13816
|
+
const destDir = join27(workspaceCwdFor(topicId), "attachments", queryId);
|
|
13579
13817
|
for (const rawId of attachmentIds) {
|
|
13580
13818
|
if (typeof rawId !== "string")
|
|
13581
13819
|
continue;
|
|
@@ -13589,10 +13827,10 @@ function materializePromptAttachments(topicId, queryId, attachmentIds) {
|
|
|
13589
13827
|
continue;
|
|
13590
13828
|
}
|
|
13591
13829
|
try {
|
|
13592
|
-
|
|
13830
|
+
mkdirSync18(destDir, { recursive: true });
|
|
13593
13831
|
const index = String(out.length + 1).padStart(2, "0");
|
|
13594
13832
|
const safeName = safeAttachmentFilename(attachment.filename, fileId);
|
|
13595
|
-
const destPath =
|
|
13833
|
+
const destPath = join27(destDir, `${index}-${fileId.slice(0, 8)}-${safeName}`);
|
|
13596
13834
|
copyFileSync2(sourcePath, destPath);
|
|
13597
13835
|
out.push({
|
|
13598
13836
|
id: attachment.id,
|
|
@@ -13622,14 +13860,14 @@ function promptWithAttachments(prompt, attachments) {
|
|
|
13622
13860
|
return composeAttachmentPrompt(prompt, attachments.map(({ filename, path }) => attachmentPromptLine(filename, path)));
|
|
13623
13861
|
}
|
|
13624
13862
|
function ingestAttachment(args) {
|
|
13625
|
-
const destDir =
|
|
13626
|
-
|
|
13863
|
+
const destDir = join27(UPLOADS_DIR, args.topicId);
|
|
13864
|
+
mkdirSync18(destDir, { recursive: true });
|
|
13627
13865
|
const safeName = safeAttachmentFilename(args.filename, "upload");
|
|
13628
|
-
const destPath =
|
|
13866
|
+
const destPath = join27(destDir, `${Date.now()}-${randomUUID13().slice(0, 8)}-${safeName}`);
|
|
13629
13867
|
if (args.sourcePath !== undefined) {
|
|
13630
13868
|
copyFileSync2(args.sourcePath, destPath);
|
|
13631
13869
|
} else if (args.bytes !== undefined) {
|
|
13632
|
-
|
|
13870
|
+
writeFileSync16(destPath, args.bytes);
|
|
13633
13871
|
} else {
|
|
13634
13872
|
throw new Error("ingestAttachment: provide sourcePath or bytes");
|
|
13635
13873
|
}
|
|
@@ -14097,7 +14335,7 @@ var init_ask_user_gates = __esm(async () => {
|
|
|
14097
14335
|
});
|
|
14098
14336
|
|
|
14099
14337
|
// ../../packages/core/src/agents/mcp-tools/ask-user.ts
|
|
14100
|
-
import { createHash as
|
|
14338
|
+
import { createHash as createHash7, randomUUID as randomUUID14 } from "crypto";
|
|
14101
14339
|
import { z as z3 } from "zod";
|
|
14102
14340
|
function normalizeAskText(value2, maxChars) {
|
|
14103
14341
|
if (typeof value2 !== "string")
|
|
@@ -14134,7 +14372,7 @@ function normalizeAskUserQuestionInput(input) {
|
|
|
14134
14372
|
return { question, choices };
|
|
14135
14373
|
}
|
|
14136
14374
|
function askBodyHash(question, choices) {
|
|
14137
|
-
return
|
|
14375
|
+
return createHash7("sha256").update(JSON.stringify({ question, choices })).digest("hex");
|
|
14138
14376
|
}
|
|
14139
14377
|
function answerToolResult(answer) {
|
|
14140
14378
|
if (!answer.userId)
|
|
@@ -14755,169 +14993,6 @@ var init_visuals = __esm(async () => {
|
|
|
14755
14993
|
init_visual_html();
|
|
14756
14994
|
});
|
|
14757
14995
|
|
|
14758
|
-
// ../../packages/core/src/storage/token-stats.ts
|
|
14759
|
-
import { createHash as createHash7 } from "crypto";
|
|
14760
|
-
import { mkdirSync as mkdirSync18 } from "fs";
|
|
14761
|
-
import { join as join27 } from "path";
|
|
14762
|
-
function emptyBucket() {
|
|
14763
|
-
return {
|
|
14764
|
-
inputTokens: 0,
|
|
14765
|
-
outputTokens: 0,
|
|
14766
|
-
cacheCreationInputTokens: 0,
|
|
14767
|
-
cacheReadInputTokens: 0,
|
|
14768
|
-
queries: 0,
|
|
14769
|
-
estimatedCostUsd: 0
|
|
14770
|
-
};
|
|
14771
|
-
}
|
|
14772
|
-
function tokenStatsFileId(userId) {
|
|
14773
|
-
const rawUserId = String(userId);
|
|
14774
|
-
return /^[A-Za-z0-9][A-Za-z0-9_.@-]{0,255}$/.test(rawUserId) && !rawUserId.includes("..") ? rawUserId : `sha256-${createHash7("sha256").update(rawUserId).digest("hex")}`;
|
|
14775
|
-
}
|
|
14776
|
-
function queriesPath(userId) {
|
|
14777
|
-
const fileId = tokenStatsFileId(userId);
|
|
14778
|
-
const logDir = resolveStorageLogDir();
|
|
14779
|
-
mkdirSync18(logDir, { recursive: true });
|
|
14780
|
-
return join27(logDir, `token-queries-${fileId}.jsonl`);
|
|
14781
|
-
}
|
|
14782
|
-
function loadRecords(userId) {
|
|
14783
|
-
try {
|
|
14784
|
-
return readJsonlLines(queriesPath(userId)).flatMap((line) => {
|
|
14785
|
-
try {
|
|
14786
|
-
return [JSON.parse(line)];
|
|
14787
|
-
} catch {
|
|
14788
|
-
return [];
|
|
14789
|
-
}
|
|
14790
|
-
});
|
|
14791
|
-
} catch {
|
|
14792
|
-
return [];
|
|
14793
|
-
}
|
|
14794
|
-
}
|
|
14795
|
-
function calcCost(b) {
|
|
14796
|
-
return b.estimatedCostUsd;
|
|
14797
|
-
}
|
|
14798
|
-
function estimateUsageCost(agent, model, usage) {
|
|
14799
|
-
const prices = TOKEN_PRICES[`${agent}:${model}`];
|
|
14800
|
-
if (!prices)
|
|
14801
|
-
return 0;
|
|
14802
|
-
return (usage.inputTokens * prices.input + usage.outputTokens * prices.output + (agent === "claude" ? usage.cacheCreationInputTokens * (prices.cacheWrite ?? prices.input) : 0) + usage.cacheReadInputTokens * prices.cacheRead) / 1e6;
|
|
14803
|
-
}
|
|
14804
|
-
function isQueryRecord(value2) {
|
|
14805
|
-
if (!value2 || typeof value2 !== "object")
|
|
14806
|
-
return false;
|
|
14807
|
-
const record = value2;
|
|
14808
|
-
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";
|
|
14809
|
-
}
|
|
14810
|
-
function recordUsage(userId, session, usage, context) {
|
|
14811
|
-
const cacheReadInputTokens = usage.cacheReadInputTokens ?? 0;
|
|
14812
|
-
const inputTokens = context.agent === "claude" ? usage.inputTokens : Math.max(0, usage.inputTokens - cacheReadInputTokens);
|
|
14813
|
-
const normalized = {
|
|
14814
|
-
inputTokens,
|
|
14815
|
-
outputTokens: usage.outputTokens,
|
|
14816
|
-
cacheCreationInputTokens: usage.cacheCreationInputTokens ?? 0,
|
|
14817
|
-
cacheReadInputTokens
|
|
14818
|
-
};
|
|
14819
|
-
const record = {
|
|
14820
|
-
schemaVersion: 2,
|
|
14821
|
-
timestamp: new Date().toISOString(),
|
|
14822
|
-
session,
|
|
14823
|
-
topicId: context.topicId,
|
|
14824
|
-
...context.providerSessionId ? { providerSessionId: context.providerSessionId } : {},
|
|
14825
|
-
agent: context.agent,
|
|
14826
|
-
model: context.model,
|
|
14827
|
-
...normalized,
|
|
14828
|
-
...usage.contextTokens !== undefined ? { contextTokens: usage.contextTokens } : {},
|
|
14829
|
-
...usage.contextWindow !== undefined ? { contextWindow: usage.contextWindow } : {},
|
|
14830
|
-
estimatedCostUsd: usage.costUsd ?? estimateUsageCost(context.agent, context.model, normalized)
|
|
14831
|
-
};
|
|
14832
|
-
try {
|
|
14833
|
-
appendJsonlEntry(queriesPath(userId), record);
|
|
14834
|
-
} catch (e) {
|
|
14835
|
-
logger.warn({ err: e, userId }, "token-stats: Failed to record");
|
|
14836
|
-
}
|
|
14837
|
-
}
|
|
14838
|
-
function getStats(userId, from, to) {
|
|
14839
|
-
const records = loadRecords(userId);
|
|
14840
|
-
const fromTs = from ? new Date(from).getTime() : 0;
|
|
14841
|
-
const toTs = to ? new Date(to).getTime() : Infinity;
|
|
14842
|
-
if (from && Number.isNaN(fromTs) || to && Number.isNaN(toTs)) {
|
|
14843
|
-
logger.warn({ from, to }, "token-stats: Invalid date range, returning empty");
|
|
14844
|
-
return {
|
|
14845
|
-
total: emptyBucket(),
|
|
14846
|
-
byHour: {},
|
|
14847
|
-
bySession: {},
|
|
14848
|
-
currentSessions: [],
|
|
14849
|
-
ignoredLegacyRecords: 0,
|
|
14850
|
-
estimatedCostUsd: 0
|
|
14851
|
-
};
|
|
14852
|
-
}
|
|
14853
|
-
const total = emptyBucket();
|
|
14854
|
-
const byHour = {};
|
|
14855
|
-
const bySession = {};
|
|
14856
|
-
const currentSessions = new Map;
|
|
14857
|
-
let ignoredLegacyRecords = 0;
|
|
14858
|
-
for (const raw of records) {
|
|
14859
|
-
if (!isQueryRecord(raw)) {
|
|
14860
|
-
ignoredLegacyRecords += 1;
|
|
14861
|
-
continue;
|
|
14862
|
-
}
|
|
14863
|
-
const r = raw;
|
|
14864
|
-
const ts = new Date(r.timestamp).getTime();
|
|
14865
|
-
if (ts < fromTs || ts > toTs)
|
|
14866
|
-
continue;
|
|
14867
|
-
const hourKey = r.timestamp.slice(0, 13);
|
|
14868
|
-
if (!byHour[hourKey])
|
|
14869
|
-
byHour[hourKey] = emptyBucket();
|
|
14870
|
-
if (!bySession[r.session])
|
|
14871
|
-
bySession[r.session] = emptyBucket();
|
|
14872
|
-
for (const bucket of [total, byHour[hourKey], bySession[r.session]]) {
|
|
14873
|
-
bucket.inputTokens += r.inputTokens;
|
|
14874
|
-
bucket.outputTokens += r.outputTokens;
|
|
14875
|
-
bucket.cacheCreationInputTokens += r.cacheCreationInputTokens;
|
|
14876
|
-
bucket.cacheReadInputTokens += r.cacheReadInputTokens;
|
|
14877
|
-
bucket.queries += 1;
|
|
14878
|
-
bucket.estimatedCostUsd += r.estimatedCostUsd;
|
|
14879
|
-
}
|
|
14880
|
-
if (r.contextTokens !== undefined && r.contextWindow !== undefined && r.contextWindow > 0) {
|
|
14881
|
-
currentSessions.set(r.topicId, {
|
|
14882
|
-
timestamp: r.timestamp,
|
|
14883
|
-
topicId: r.topicId,
|
|
14884
|
-
topicTitle: r.session,
|
|
14885
|
-
...r.providerSessionId ? { providerSessionId: r.providerSessionId } : {},
|
|
14886
|
-
agent: r.agent,
|
|
14887
|
-
model: r.model,
|
|
14888
|
-
contextTokens: r.contextTokens,
|
|
14889
|
-
contextWindow: r.contextWindow
|
|
14890
|
-
});
|
|
14891
|
-
}
|
|
14892
|
-
}
|
|
14893
|
-
return {
|
|
14894
|
-
total,
|
|
14895
|
-
byHour,
|
|
14896
|
-
bySession,
|
|
14897
|
-
currentSessions: [...currentSessions.values()].sort((a, b) => b.timestamp.localeCompare(a.timestamp)),
|
|
14898
|
-
ignoredLegacyRecords,
|
|
14899
|
-
estimatedCostUsd: calcCost(total)
|
|
14900
|
-
};
|
|
14901
|
-
}
|
|
14902
|
-
var TOKEN_PRICES;
|
|
14903
|
-
var init_token_stats = __esm(async () => {
|
|
14904
|
-
init_jsonl();
|
|
14905
|
-
init_logger();
|
|
14906
|
-
await init_storage_host();
|
|
14907
|
-
TOKEN_PRICES = {
|
|
14908
|
-
"codex:gpt-5.6-sol": { input: 5, cacheRead: 0.5, output: 30 },
|
|
14909
|
-
"codex:gpt-5.6-terra": { input: 2.5, cacheRead: 0.25, output: 15 },
|
|
14910
|
-
"codex:gpt-5.6-luna": { input: 1, cacheRead: 0.1, output: 6 },
|
|
14911
|
-
"claude:fable": { input: 10, cacheWrite: 12.5, cacheRead: 1, output: 50 },
|
|
14912
|
-
"claude:opus": { input: 5, cacheWrite: 6.25, cacheRead: 0.5, output: 25 },
|
|
14913
|
-
"claude:sonnet": { input: 2, cacheWrite: 2.5, cacheRead: 0.2, output: 10 },
|
|
14914
|
-
"maestro:kimi-k3": { input: 3, cacheRead: 0.3, output: 15 },
|
|
14915
|
-
"maestro:kimi-k2.7-code": { input: 0.95, cacheRead: 0.19, output: 4 },
|
|
14916
|
-
"maestro:deepseek-pro": { input: 0.435, cacheRead: 0.003625, output: 0.87 },
|
|
14917
|
-
"maestro:deepseek-flash": { input: 0.14, cacheRead: 0.0028, output: 0.28 }
|
|
14918
|
-
};
|
|
14919
|
-
});
|
|
14920
|
-
|
|
14921
14996
|
// ../../packages/core/src/runtime/turn-event-stream.ts
|
|
14922
14997
|
import { randomUUID as randomUUID15 } from "crypto";
|
|
14923
14998
|
import { realpathSync as realpathSync6, statSync as statSync9 } from "fs";
|
|
@@ -15033,10 +15108,11 @@ async function runTurnEventStream(topicId, topicTitle, queryId, events, control,
|
|
|
15033
15108
|
agentType,
|
|
15034
15109
|
model,
|
|
15035
15110
|
sourceNode: execution?.sourceNode,
|
|
15111
|
+
...execution?.threadRootId ? { threadRootId: execution.threadRootId } : {},
|
|
15036
15112
|
usage,
|
|
15037
15113
|
createdAt: new Date().toISOString()
|
|
15038
15114
|
};
|
|
15039
|
-
appendApiMessage(message);
|
|
15115
|
+
appendApiMessage(message, execution?.threadRootId ? { updateTopicLastMessageAt: false } : undefined);
|
|
15040
15116
|
hub.broadcastMessage(topicId, message);
|
|
15041
15117
|
lastVisibleMessageId = message.id;
|
|
15042
15118
|
visibleMessageIds.push(message.id);
|
|
@@ -15552,7 +15628,7 @@ var init_turn_session = __esm(async () => {
|
|
|
15552
15628
|
});
|
|
15553
15629
|
|
|
15554
15630
|
// ../../packages/core/src/storage/app-settings.ts
|
|
15555
|
-
import { existsSync as existsSync19, mkdirSync as mkdirSync19, readFileSync as readFileSync19, writeFileSync as
|
|
15631
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync19, readFileSync as readFileSync19, writeFileSync as writeFileSync17 } from "fs";
|
|
15556
15632
|
import { dirname as dirname15, join as join28 } from "path";
|
|
15557
15633
|
function settingsFile() {
|
|
15558
15634
|
return join28(resolveStorageDataDir(), "otium-settings.json");
|
|
@@ -16070,6 +16146,7 @@ async function drainOneDurableUserTurn() {
|
|
|
16070
16146
|
bridgeSessionFromHistory: execution?.bridgeSessionFromHistory,
|
|
16071
16147
|
peerBridge: execution?.peerBridge,
|
|
16072
16148
|
from: execution?.from,
|
|
16149
|
+
threadRootId: execution?.threadRootId,
|
|
16073
16150
|
_queryId: request.requestId,
|
|
16074
16151
|
_runtimeEpoch: execution?.runtimeEpoch ?? request.topicEpoch,
|
|
16075
16152
|
onSettled: () => {
|
|
@@ -16142,6 +16219,7 @@ function startAiTurn(params) {
|
|
|
16142
16219
|
let sessionId = sessionResolution.sessionId;
|
|
16143
16220
|
const deferredSessionId = params.sessionId === undefined && !sessionResolution.isolated ? undefined : sessionId;
|
|
16144
16221
|
const sourceNode = params.sourceNode;
|
|
16222
|
+
const threadRootId = params.threadRootId;
|
|
16145
16223
|
const topicId = topic.id;
|
|
16146
16224
|
const requestId = params.requestId;
|
|
16147
16225
|
const depth = params.depth;
|
|
@@ -16670,7 +16748,7 @@ function startAiTurn(params) {
|
|
|
16670
16748
|
WsHub.get().broadcastTyping(topicId, "ai");
|
|
16671
16749
|
WsHub.get().broadcastAiActive(topicId, queryId);
|
|
16672
16750
|
}
|
|
16673
|
-
streamAgentEvents(topicId, topic.title, queryId, events, control, agentKind, resolvedModel, resolvedEffort, userId, !sessionRetried, onSessionId, { silent, peerBridge, sourceNode }).then(async (streamOutcome) => {
|
|
16751
|
+
streamAgentEvents(topicId, topic.title, queryId, events, control, agentKind, resolvedModel, resolvedEffort, userId, !sessionRetried, onSessionId, { silent, peerBridge, sourceNode, ...threadRootId ? { threadRootId } : {} }).then(async (streamOutcome) => {
|
|
16674
16752
|
let outcome = streamOutcome;
|
|
16675
16753
|
if (outcome.kind === "session-expired") {
|
|
16676
16754
|
const retry = resolveSessionRetry({
|
|
@@ -21078,4 +21156,4 @@ export {
|
|
|
21078
21156
|
createAgentHealthMcpServer
|
|
21079
21157
|
};
|
|
21080
21158
|
|
|
21081
|
-
//# debugId=
|
|
21159
|
+
//# debugId=D1868E45E4303D2164756E2164756E21
|