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
|
@@ -3,8 +3,16 @@ import { getRegistry } from "#agents/registry";
|
|
|
3
3
|
import { resolveDefaultModel } from "#platform/config";
|
|
4
4
|
import { GENERAL_TOPIC_ID } from "#platform/constants";
|
|
5
5
|
import { getApiTopicConfig, setApiTopicConfig } from "#storage/api-topic-config";
|
|
6
|
-
import {
|
|
7
|
-
|
|
6
|
+
import {
|
|
7
|
+
defaultSurfaceScope,
|
|
8
|
+
defaultTopicSurface,
|
|
9
|
+
getManagerTopicForUser,
|
|
10
|
+
getTopic,
|
|
11
|
+
normalizeSurfaceScope,
|
|
12
|
+
normalizeTopicSurface,
|
|
13
|
+
upsertTopic,
|
|
14
|
+
} from "#storage/api-topics";
|
|
15
|
+
import type { TopicDto, TopicSurface } from "#types/api";
|
|
8
16
|
|
|
9
17
|
const LEGACY_PERSONAL_GENERAL_DESCRIPTION =
|
|
10
18
|
"나만의 개인 공간이에요. 대화와 AI 작업은 다른 사용자에게 공개되지 않습니다.";
|
|
@@ -17,8 +25,22 @@ export const PERSONAL_GENERAL_DESCRIPTION =
|
|
|
17
25
|
* The legacy `general` row is deliberately excluded: it was shared by every
|
|
18
26
|
* user and remains untouched for an explicit, operator-controlled migration.
|
|
19
27
|
*/
|
|
20
|
-
export function ensurePersonalGeneral(
|
|
21
|
-
|
|
28
|
+
export function ensurePersonalGeneral(
|
|
29
|
+
userId: string,
|
|
30
|
+
surface?: TopicSurface,
|
|
31
|
+
opts: { surfaceScope?: string | null } = {},
|
|
32
|
+
): TopicDto {
|
|
33
|
+
const scope = normalizeTopicSurface(surface ?? defaultTopicSurface());
|
|
34
|
+
// One General per user *per workspace*. A manager room is exempt from title
|
|
35
|
+
// conflicts precisely because several may share the name, and two workspaces
|
|
36
|
+
// sharing one private room would merge two customers' transcripts.
|
|
37
|
+
const surfaceScope =
|
|
38
|
+
opts.surfaceScope !== undefined
|
|
39
|
+
? normalizeSurfaceScope(opts.surfaceScope)
|
|
40
|
+
: scope === "otium"
|
|
41
|
+
? defaultSurfaceScope()
|
|
42
|
+
: null;
|
|
43
|
+
const existing = getManagerTopicForUser(userId, scope, { surfaceScope });
|
|
22
44
|
if (existing) {
|
|
23
45
|
if (existing.description === LEGACY_PERSONAL_GENERAL_DESCRIPTION) {
|
|
24
46
|
existing.description = PERSONAL_GENERAL_DESCRIPTION;
|
|
@@ -43,6 +65,8 @@ export function ensurePersonalGeneral(userId: string): TopicDto {
|
|
|
43
65
|
aiMode: "always",
|
|
44
66
|
aiMention: false,
|
|
45
67
|
participants: [{ userId, role: "owner" }],
|
|
68
|
+
surface: scope,
|
|
69
|
+
surfaceScope,
|
|
46
70
|
createdAt: now,
|
|
47
71
|
lastMessageAt: now,
|
|
48
72
|
};
|
|
@@ -130,6 +130,13 @@ export interface TopicDto {
|
|
|
130
130
|
visibility?: TopicVisibility;
|
|
131
131
|
/** The product surface that owns this topic (terminal / telegram / otium). */
|
|
132
132
|
surface?: TopicSurface;
|
|
133
|
+
/**
|
|
134
|
+
* Which instance of the surface owns this topic — for `otium`, the workspace
|
|
135
|
+
* (M-1/M-2). Fixed at creation, exactly like `surface`. Null/absent on
|
|
136
|
+
* `terminal` and `telegram`, which have exactly one instance each, and on
|
|
137
|
+
* Otium rooms created before the scope was known.
|
|
138
|
+
*/
|
|
139
|
+
surfaceScope?: string | null;
|
|
133
140
|
/** Stable execution placement. Absent means the hub runs this topic locally. */
|
|
134
141
|
executionNode?: {
|
|
135
142
|
nodeId: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const NEGOTIUM_VERSION = "0.2.
|
|
1
|
+
export const NEGOTIUM_VERSION = "0.2.28";
|
package/dist/storage.js
CHANGED
|
@@ -1204,8 +1204,11 @@ function deleteApiTopicConfig(topicId) {
|
|
|
1204
1204
|
var exports_api_topics = {};
|
|
1205
1205
|
__export(exports_api_topics, {
|
|
1206
1206
|
upsertTopic: () => upsertTopic,
|
|
1207
|
+
stampUnscopedOtiumTopics: () => stampUnscopedOtiumTopics,
|
|
1207
1208
|
setTopicSurfaces: () => setTopicSurfaces,
|
|
1208
1209
|
setTopicSessionId: () => setTopicSessionId,
|
|
1210
|
+
setSurfaceScopeRequired: () => setSurfaceScopeRequired,
|
|
1211
|
+
setDefaultSurfaceScope: () => setDefaultSurfaceScope,
|
|
1209
1212
|
setApiTopicAgent: () => setApiTopicAgent,
|
|
1210
1213
|
revokeSubagentTellTarget: () => revokeSubagentTellTarget,
|
|
1211
1214
|
reparentTopicChildren: () => reparentTopicChildren,
|
|
@@ -1214,10 +1217,12 @@ __export(exports_api_topics, {
|
|
|
1214
1217
|
normalizeTopicSurface: () => normalizeTopicSurface,
|
|
1215
1218
|
normalizeTopicState: () => normalizeTopicState,
|
|
1216
1219
|
normalizeTopicKind: () => normalizeTopicKind,
|
|
1220
|
+
normalizeSurfaceScope: () => normalizeSurfaceScope,
|
|
1217
1221
|
normalizeAiMode: () => normalizeAiMode,
|
|
1218
1222
|
listTopics: () => listTopics,
|
|
1219
1223
|
listSubagentTellTargetIds: () => listSubagentTellTargetIds,
|
|
1220
1224
|
isTopicVisible: () => isTopicVisible,
|
|
1225
|
+
isSurfaceScopeRequired: () => isSurfaceScopeRequired,
|
|
1221
1226
|
inferTopicKind: () => inferTopicKind,
|
|
1222
1227
|
inferAiMode: () => inferAiMode,
|
|
1223
1228
|
grantSubagentTellTarget: () => grantSubagentTellTarget,
|
|
@@ -1231,6 +1236,7 @@ __export(exports_api_topics, {
|
|
|
1231
1236
|
findTopicTitleConflict: () => findTopicTitleConflict,
|
|
1232
1237
|
deleteTopic: () => deleteTopic,
|
|
1233
1238
|
defaultTopicSurface: () => defaultTopicSurface,
|
|
1239
|
+
defaultSurfaceScope: () => defaultSurfaceScope,
|
|
1234
1240
|
clearTopicSessionId: () => clearTopicSessionId,
|
|
1235
1241
|
aiMentionFromMode: () => aiMentionFromMode,
|
|
1236
1242
|
addParticipantToDB: () => addParticipantToDB
|
|
@@ -1254,6 +1260,28 @@ function defaultTopicSurface() {
|
|
|
1254
1260
|
function normalizeTopicSurface(value) {
|
|
1255
1261
|
return value === "telegram" || value === "otium" || value === "terminal" ? value : "terminal";
|
|
1256
1262
|
}
|
|
1263
|
+
var activeSurfaceScope = null;
|
|
1264
|
+
function normalizeSurfaceScope(value) {
|
|
1265
|
+
if (typeof value !== "string")
|
|
1266
|
+
return null;
|
|
1267
|
+
const trimmed = value.trim();
|
|
1268
|
+
return trimmed ? trimmed : null;
|
|
1269
|
+
}
|
|
1270
|
+
function defaultSurfaceScope() {
|
|
1271
|
+
return activeSurfaceScope ?? normalizeSurfaceScope(process.env.NEGOTIUM_SURFACE_SCOPE);
|
|
1272
|
+
}
|
|
1273
|
+
function setDefaultSurfaceScope(scope) {
|
|
1274
|
+
const previous = activeSurfaceScope;
|
|
1275
|
+
activeSurfaceScope = normalizeSurfaceScope(scope);
|
|
1276
|
+
return previous;
|
|
1277
|
+
}
|
|
1278
|
+
var surfaceScopeRequired = false;
|
|
1279
|
+
function setSurfaceScopeRequired(required) {
|
|
1280
|
+
surfaceScopeRequired = required;
|
|
1281
|
+
}
|
|
1282
|
+
function isSurfaceScopeRequired() {
|
|
1283
|
+
return surfaceScopeRequired;
|
|
1284
|
+
}
|
|
1257
1285
|
function tableColumns2(table) {
|
|
1258
1286
|
const rows = db.query(`PRAGMA table_info(${table})`).all();
|
|
1259
1287
|
return new Set(rows.map((row) => row.name));
|
|
@@ -1385,6 +1413,7 @@ function initializeApiTopicsSchema() {
|
|
|
1385
1413
|
is_subagent INTEGER NOT NULL DEFAULT 0 CHECK (is_subagent IN (0,1)),
|
|
1386
1414
|
visibility TEXT NOT NULL DEFAULT 'visible' CHECK (visibility IN ('visible','hidden')),
|
|
1387
1415
|
surface TEXT NOT NULL DEFAULT 'terminal' CHECK (surface IN ('terminal','telegram','otium')),
|
|
1416
|
+
surface_scope TEXT,
|
|
1388
1417
|
browser_profile TEXT NOT NULL DEFAULT 'default',
|
|
1389
1418
|
browser_profile_owner TEXT,
|
|
1390
1419
|
session_id TEXT,
|
|
@@ -1430,8 +1459,8 @@ function initializeApiTopicsSchema() {
|
|
|
1430
1459
|
const legacyBaseEffort = row.base_effort ?? row.default_effort;
|
|
1431
1460
|
db.query(`INSERT INTO api_topics_next
|
|
1432
1461
|
(id,title,kind,description,agent,base_model,base_effort,response_policy,
|
|
1433
|
-
created_at,last_message_at,parent_topic_id,memory_topic_id,memory_key,is_fork,is_subagent,visibility,surface,session_id)
|
|
1434
|
-
VALUES (
|
|
1462
|
+
created_at,last_message_at,parent_topic_id,memory_topic_id,memory_key,is_fork,is_subagent,visibility,surface,surface_scope,session_id)
|
|
1463
|
+
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);
|
|
1435
1464
|
}
|
|
1436
1465
|
db.exec("DROP TABLE IF EXISTS topic_members");
|
|
1437
1466
|
db.exec("DROP TABLE api_topics");
|
|
@@ -1488,6 +1517,9 @@ function initializeApiTopicsSchema() {
|
|
|
1488
1517
|
if (!tableColumns2("api_topics").has("surface")) {
|
|
1489
1518
|
db.exec("ALTER TABLE api_topics ADD COLUMN surface TEXT NOT NULL DEFAULT 'terminal'");
|
|
1490
1519
|
}
|
|
1520
|
+
if (!tableColumns2("api_topics").has("surface_scope")) {
|
|
1521
|
+
db.exec("ALTER TABLE api_topics ADD COLUMN surface_scope TEXT");
|
|
1522
|
+
}
|
|
1491
1523
|
if (tableColumns2("api_topics").has("access_mode")) {
|
|
1492
1524
|
try {
|
|
1493
1525
|
db.exec("ALTER TABLE api_topics DROP COLUMN access_mode");
|
|
@@ -1523,6 +1555,31 @@ function initializeApiTopicsSchema() {
|
|
|
1523
1555
|
backfillTopicSurfaces();
|
|
1524
1556
|
db.exec("CREATE INDEX IF NOT EXISTS idx_api_topics_last_message ON api_topics(last_message_at DESC)");
|
|
1525
1557
|
db.exec("CREATE INDEX IF NOT EXISTS idx_api_topics_surface ON api_topics(surface)");
|
|
1558
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_api_topics_surface_scope ON api_topics(surface, surface_scope)");
|
|
1559
|
+
}
|
|
1560
|
+
var SURFACE_SCOPE_STAMP_MIGRATION = "api_topics_surface_scope_stamp_20260809";
|
|
1561
|
+
function stampUnscopedOtiumTopics(scope) {
|
|
1562
|
+
const normalized = normalizeSurfaceScope(scope);
|
|
1563
|
+
if (!normalized)
|
|
1564
|
+
return 0;
|
|
1565
|
+
db.exec(`
|
|
1566
|
+
CREATE TABLE IF NOT EXISTS api_schema_migrations (
|
|
1567
|
+
key TEXT PRIMARY KEY,
|
|
1568
|
+
applied_at TEXT NOT NULL
|
|
1569
|
+
)
|
|
1570
|
+
`);
|
|
1571
|
+
const applied = db.query("SELECT key FROM api_schema_migrations WHERE key = ?").get(SURFACE_SCOPE_STAMP_MIGRATION);
|
|
1572
|
+
if (applied)
|
|
1573
|
+
return 0;
|
|
1574
|
+
let stamped = 0;
|
|
1575
|
+
db.transaction(() => {
|
|
1576
|
+
stamped = Number(db.query("UPDATE api_topics SET surface_scope = ? WHERE surface = 'otium' AND surface_scope IS NULL").run(normalized).changes ?? 0);
|
|
1577
|
+
db.query("INSERT INTO api_schema_migrations (key, applied_at) VALUES (?, ?)").run(SURFACE_SCOPE_STAMP_MIGRATION, new Date().toISOString());
|
|
1578
|
+
})();
|
|
1579
|
+
if (stamped > 0) {
|
|
1580
|
+
logger.info({ scope: normalized, stamped }, "api_topics: surface scope stamped");
|
|
1581
|
+
}
|
|
1582
|
+
return stamped;
|
|
1526
1583
|
}
|
|
1527
1584
|
var SURFACE_BACKFILL_MIGRATION = "api_topics_surface_backfill_20260808";
|
|
1528
1585
|
function backfillTopicSurfaces() {
|
|
@@ -1544,22 +1601,24 @@ function backfillTopicSurfaces() {
|
|
|
1544
1601
|
logger.info({ surface }, "api_topics: surface backfilled");
|
|
1545
1602
|
}
|
|
1546
1603
|
function renameSurfaceTitleCollisions() {
|
|
1547
|
-
const rows = db.query("SELECT id, title, kind, surface FROM api_topics ORDER BY created_at ASC, rowid ASC").all();
|
|
1548
|
-
const
|
|
1604
|
+
const rows = db.query("SELECT id, title, kind, surface FROM api_topics WHERE kind != 'manager' ORDER BY created_at ASC, rowid ASC").all();
|
|
1605
|
+
const keyOf = (surface, kind, title) => [surface, kind, normalizedTitle(title)].join("\x00");
|
|
1606
|
+
const reserved = new Set(rows.map((row) => keyOf(row.surface, row.kind, row.title)));
|
|
1607
|
+
const used = new Set;
|
|
1549
1608
|
const update = db.query("UPDATE api_topics SET title = ? WHERE id = ?");
|
|
1550
1609
|
for (const row of rows) {
|
|
1551
|
-
const key = (title) =>
|
|
1552
|
-
if (!
|
|
1553
|
-
|
|
1610
|
+
const key = (title) => keyOf(row.surface, row.kind, title);
|
|
1611
|
+
if (!used.has(key(row.title))) {
|
|
1612
|
+
used.add(key(row.title));
|
|
1554
1613
|
continue;
|
|
1555
1614
|
}
|
|
1556
1615
|
let suffix = 2;
|
|
1557
1616
|
let candidate = `${row.title} (${suffix})`;
|
|
1558
|
-
while (
|
|
1617
|
+
while (reserved.has(key(candidate)) || used.has(key(candidate))) {
|
|
1559
1618
|
suffix += 1;
|
|
1560
1619
|
candidate = `${row.title} (${suffix})`;
|
|
1561
1620
|
}
|
|
1562
|
-
|
|
1621
|
+
used.add(key(candidate));
|
|
1563
1622
|
update.run(candidate, row.id);
|
|
1564
1623
|
logger.warn({ topicId: row.id, surface: row.surface, from: row.title, to: candidate }, "api_topics: renamed a duplicate title for surface-scoped uniqueness");
|
|
1565
1624
|
}
|
|
@@ -1623,7 +1682,8 @@ function rowToDto2(r, participants = getTopicParticipants(r.id), tellTargets) {
|
|
|
1623
1682
|
subagentReportMode: r.subagent_report_mode === "tell" || r.subagent_report_mode === "status-only" ? r.subagent_report_mode : "auto"
|
|
1624
1683
|
} : {},
|
|
1625
1684
|
visibility: normalizeTopicVisibility(r.visibility),
|
|
1626
|
-
surface: normalizeTopicSurface(r.surface)
|
|
1685
|
+
surface: normalizeTopicSurface(r.surface),
|
|
1686
|
+
surfaceScope: normalizeSurfaceScope(r.surface_scope)
|
|
1627
1687
|
};
|
|
1628
1688
|
}
|
|
1629
1689
|
function normalizeTopicVisibility(value) {
|
|
@@ -1691,6 +1751,13 @@ function normalizeTopicState(input) {
|
|
|
1691
1751
|
function normalizedTitle(title) {
|
|
1692
1752
|
return title.trim().toLowerCase();
|
|
1693
1753
|
}
|
|
1754
|
+
function surfaceScopeForWrite(t) {
|
|
1755
|
+
if (normalizeTopicSurface(t.surface ?? defaultTopicSurface()) !== "otium")
|
|
1756
|
+
return null;
|
|
1757
|
+
if (t.surfaceScope !== undefined)
|
|
1758
|
+
return normalizeSurfaceScope(t.surfaceScope);
|
|
1759
|
+
return defaultSurfaceScope();
|
|
1760
|
+
}
|
|
1694
1761
|
function upsertTopic(t) {
|
|
1695
1762
|
const normalized = normalizeTopicState({
|
|
1696
1763
|
id: t.id,
|
|
@@ -1703,8 +1770,8 @@ function upsertTopic(t) {
|
|
|
1703
1770
|
db.query(`INSERT INTO api_topics
|
|
1704
1771
|
(id,title,kind,description,agent,base_model,base_effort,response_policy,
|
|
1705
1772
|
created_at,last_message_at,parent_topic_id,memory_topic_id,memory_key,is_fork,is_subagent,visibility,surface,
|
|
1706
|
-
subagent_report_mode)
|
|
1707
|
-
VALUES (
|
|
1773
|
+
surface_scope,subagent_report_mode)
|
|
1774
|
+
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
|
1708
1775
|
ON CONFLICT(id) DO UPDATE SET
|
|
1709
1776
|
title = excluded.title,
|
|
1710
1777
|
kind = excluded.kind,
|
|
@@ -1722,7 +1789,11 @@ function upsertTopic(t) {
|
|
|
1722
1789
|
is_subagent = excluded.is_subagent,
|
|
1723
1790
|
visibility = excluded.visibility,
|
|
1724
1791
|
surface = excluded.surface,
|
|
1725
|
-
|
|
1792
|
+
-- A room's workspace is fixed at creation (M-1). COALESCE, not
|
|
1793
|
+
-- assignment: an update may fill in a scope that was unknown when the
|
|
1794
|
+
-- room was created, but may never move a room to another workspace.
|
|
1795
|
+
surface_scope = COALESCE(api_topics.surface_scope, excluded.surface_scope),
|
|
1796
|
+
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");
|
|
1726
1797
|
db.query("DELETE FROM topic_members WHERE topic_id = ?").run(t.id);
|
|
1727
1798
|
for (const participant of t.participants) {
|
|
1728
1799
|
db.query("INSERT INTO topic_members (topic_id,user_id,role) VALUES (?,?,?)").run(t.id, participant.userId, participant.role);
|
|
@@ -1736,7 +1807,8 @@ function upsertTopic(t) {
|
|
|
1736
1807
|
})();
|
|
1737
1808
|
}
|
|
1738
1809
|
function listTopics(opts = {}) {
|
|
1739
|
-
const
|
|
1810
|
+
const scoped = Object.hasOwn(opts, "surfaceScope");
|
|
1811
|
+
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();
|
|
1740
1812
|
const participants = getAllTopicParticipants();
|
|
1741
1813
|
const tellTargets = rows.some((row) => row.is_subagent !== 0) ? getAllSubagentTellTargets() : undefined;
|
|
1742
1814
|
return rows.map((row) => rowToDto2(row, participants.get(row.id) ?? [], tellTargets));
|
|
@@ -1745,15 +1817,24 @@ function getTopic(id) {
|
|
|
1745
1817
|
const r = db.query("SELECT * FROM api_topics WHERE id = ?").get(id);
|
|
1746
1818
|
return r ? rowToDto2(r) : null;
|
|
1747
1819
|
}
|
|
1748
|
-
function getManagerTopicForUser(userId) {
|
|
1749
|
-
const
|
|
1820
|
+
function getManagerTopicForUser(userId, surface, opts = {}) {
|
|
1821
|
+
const scoped = Object.hasOwn(opts, "surfaceScope");
|
|
1822
|
+
const sql = `SELECT t.* FROM api_topics t
|
|
1750
1823
|
JOIN topic_members m ON m.topic_id = t.id
|
|
1751
1824
|
WHERE t.kind = 'manager'
|
|
1752
1825
|
AND t.id != ?
|
|
1753
1826
|
AND m.user_id = ?
|
|
1754
1827
|
AND m.role = 'owner'
|
|
1828
|
+
${surface ? "AND t.surface = ?" : ""}
|
|
1829
|
+
${scoped ? "AND t.surface_scope IS ?" : ""}
|
|
1755
1830
|
ORDER BY t.created_at ASC
|
|
1756
|
-
LIMIT 1
|
|
1831
|
+
LIMIT 1`;
|
|
1832
|
+
const params = [GENERAL_TOPIC_ID, userId];
|
|
1833
|
+
if (surface)
|
|
1834
|
+
params.push(surface);
|
|
1835
|
+
if (scoped)
|
|
1836
|
+
params.push(normalizeSurfaceScope(opts.surfaceScope));
|
|
1837
|
+
const row = db.query(sql).get(...params);
|
|
1757
1838
|
return row ? rowToDto2(row) : null;
|
|
1758
1839
|
}
|
|
1759
1840
|
function getTopicMemoryOrigin(id) {
|
|
@@ -1788,18 +1869,17 @@ function getTopicByNameAndKind(title, kind) {
|
|
|
1788
1869
|
function findTopicTitleConflict(title, kind, opts = {}) {
|
|
1789
1870
|
const wanted = normalizedTitle(title);
|
|
1790
1871
|
const surface = normalizeTopicSurface(opts.surface ?? defaultTopicSurface());
|
|
1872
|
+
const surfaceScope = Object.hasOwn(opts, "surfaceScope") ? normalizeSurfaceScope(opts.surfaceScope) : surface === "otium" ? defaultSurfaceScope() : null;
|
|
1791
1873
|
const generalTitleRequested = wanted === normalizedTitle(GENERAL_TOPIC_ID);
|
|
1792
1874
|
if (generalTitleRequested && opts.excludeTopicId !== GENERAL_TOPIC_ID) {
|
|
1793
1875
|
const general = db.query("SELECT * FROM api_topics WHERE id = ?").get(GENERAL_TOPIC_ID);
|
|
1794
1876
|
if (general)
|
|
1795
1877
|
return rowToDto2(general);
|
|
1796
1878
|
}
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
params.push(kind, GENERAL_TOPIC_ID);
|
|
1802
|
-
}
|
|
1879
|
+
if (kind === "manager")
|
|
1880
|
+
return null;
|
|
1881
|
+
const params = [wanted, surface, surfaceScope, kind, GENERAL_TOPIC_ID];
|
|
1882
|
+
let sql = "SELECT * FROM api_topics WHERE LOWER(TRIM(title)) = ? AND surface = ? AND surface_scope IS ? AND (kind = ? OR id = ?)";
|
|
1803
1883
|
if (opts.excludeTopicId) {
|
|
1804
1884
|
sql += " AND id != ?";
|
|
1805
1885
|
params.push(opts.excludeTopicId);
|
|
@@ -1826,14 +1906,16 @@ function getTopicByNameForUser(title, userId, opts = {}) {
|
|
|
1826
1906
|
const qualified = /^(agent|channel|manager):(.+)$/i.exec(trimmed);
|
|
1827
1907
|
const requestedKind = qualified ? normalizeTopicKind(qualified[1]?.toLowerCase()) : null;
|
|
1828
1908
|
const requestedTitle = qualified ? qualified[2].trim() : trimmed;
|
|
1909
|
+
const scoped = Object.hasOwn(opts, "surfaceScope");
|
|
1829
1910
|
const rows = db.query(`SELECT t.* FROM api_topics t
|
|
1830
1911
|
WHERE LOWER(t.title) = LOWER(?)
|
|
1831
1912
|
AND t.id != ?
|
|
1832
1913
|
AND t.visibility != 'hidden'
|
|
1833
1914
|
AND (? IS NULL OR t.surface = ?)
|
|
1915
|
+
${scoped ? "AND t.surface_scope IS ?" : ""}
|
|
1834
1916
|
AND EXISTS (
|
|
1835
1917
|
SELECT 1 FROM topic_members m WHERE m.topic_id = t.id AND m.user_id = ?
|
|
1836
|
-
)`).all(requestedTitle, GENERAL_TOPIC_ID, opts.surface ?? null, opts.surface ?? null, userId);
|
|
1918
|
+
)`).all(requestedTitle, GENERAL_TOPIC_ID, opts.surface ?? null, opts.surface ?? null, ...scoped ? [normalizeSurfaceScope(opts.surfaceScope)] : [], userId);
|
|
1837
1919
|
const matches = requestedKind ? rows.filter((row) => row.kind === requestedKind) : rows;
|
|
1838
1920
|
return matches.length === 1 ? rowToDto2(matches[0]) : null;
|
|
1839
1921
|
}
|
|
@@ -3468,10 +3550,11 @@ __export(exports_token_stats, {
|
|
|
3468
3550
|
recordUsage: () => recordUsage,
|
|
3469
3551
|
getTopicStats: () => getTopicStats,
|
|
3470
3552
|
getStats: () => getStats,
|
|
3553
|
+
deleteTopicStats: () => deleteTopicStats,
|
|
3471
3554
|
calcCost: () => calcCost
|
|
3472
3555
|
});
|
|
3473
3556
|
import { createHash as createHash2 } from "crypto";
|
|
3474
|
-
import { mkdirSync as mkdirSync8 } from "fs";
|
|
3557
|
+
import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync7 } from "fs";
|
|
3475
3558
|
import { join as join7 } from "path";
|
|
3476
3559
|
function emptyBucket() {
|
|
3477
3560
|
return {
|
|
@@ -3561,6 +3644,26 @@ function recordUsage(userId, session, usage, context) {
|
|
|
3561
3644
|
logger.warn({ err: e, userId }, "token-stats: Failed to record");
|
|
3562
3645
|
}
|
|
3563
3646
|
}
|
|
3647
|
+
function deleteTopicStats(userId, topicId) {
|
|
3648
|
+
const path = queriesPath(userId);
|
|
3649
|
+
try {
|
|
3650
|
+
const kept = readJsonlLines(path).filter((line) => {
|
|
3651
|
+
try {
|
|
3652
|
+
const record = JSON.parse(line);
|
|
3653
|
+
return record.topicId !== topicId;
|
|
3654
|
+
} catch {
|
|
3655
|
+
return true;
|
|
3656
|
+
}
|
|
3657
|
+
});
|
|
3658
|
+
writeFileSync7(path, kept.length > 0 ? `${kept.join(`
|
|
3659
|
+
`)}
|
|
3660
|
+
` : "", "utf-8");
|
|
3661
|
+
} catch (e) {
|
|
3662
|
+
if (e.code === "ENOENT")
|
|
3663
|
+
return;
|
|
3664
|
+
logger.warn({ err: e, userId, topicId }, "token-stats: Failed to delete topic stats");
|
|
3665
|
+
}
|
|
3666
|
+
}
|
|
3564
3667
|
function getStats(userId, from, to) {
|
|
3565
3668
|
const records = loadRecords(userId);
|
|
3566
3669
|
const fromTs = from ? new Date(from).getTime() : 0;
|
|
@@ -3625,7 +3728,7 @@ function getStats(userId, from, to) {
|
|
|
3625
3728
|
estimatedCostUsd: calcCost(total)
|
|
3626
3729
|
};
|
|
3627
3730
|
}
|
|
3628
|
-
function getTopicStats(userId, topicId) {
|
|
3731
|
+
function getTopicStats(userId, topicId, activeProviderSessionId = getTopicSessionId(topicId) ?? undefined) {
|
|
3629
3732
|
const total = emptyBucket();
|
|
3630
3733
|
let currentSession;
|
|
3631
3734
|
for (const raw of loadRecords(userId)) {
|
|
@@ -3637,7 +3740,7 @@ function getTopicStats(userId, topicId) {
|
|
|
3637
3740
|
total.cacheReadInputTokens += raw.cacheReadInputTokens;
|
|
3638
3741
|
total.queries += 1;
|
|
3639
3742
|
total.estimatedCostUsd += raw.estimatedCostUsd;
|
|
3640
|
-
if (raw.contextTokens !== undefined && raw.contextWindow !== undefined && raw.contextWindow > 0 && (!currentSession || raw.timestamp > currentSession.timestamp)) {
|
|
3743
|
+
if (raw.contextTokens !== undefined && raw.contextWindow !== undefined && raw.contextWindow > 0 && activeProviderSessionId !== undefined && raw.providerSessionId === activeProviderSessionId && (!currentSession || raw.timestamp > currentSession.timestamp)) {
|
|
3641
3744
|
currentSession = {
|
|
3642
3745
|
timestamp: raw.timestamp,
|
|
3643
3746
|
topicId: raw.topicId,
|
|
@@ -3658,7 +3761,7 @@ __export(exports_topic_archive, {
|
|
|
3658
3761
|
archiveTopicMessages: () => archiveTopicMessages,
|
|
3659
3762
|
archiveConversationEvents: () => archiveConversationEvents
|
|
3660
3763
|
});
|
|
3661
|
-
import { mkdirSync as mkdirSync9, writeFileSync as
|
|
3764
|
+
import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync8 } from "fs";
|
|
3662
3765
|
import { join as join9 } from "path";
|
|
3663
3766
|
|
|
3664
3767
|
// ../../packages/core/src/agents/memory-archive-policy.ts
|
|
@@ -3818,7 +3921,7 @@ function archiveTopicMessages(topicId, topicTitle, options = {}) {
|
|
|
3818
3921
|
filename = `${safeTopic}_${date}${reasonSuffix}${counter === 1 ? "" : `_${counter}`}.jsonl`;
|
|
3819
3922
|
path = join9(archiveDir, filename);
|
|
3820
3923
|
try {
|
|
3821
|
-
|
|
3924
|
+
writeFileSync8(path, body, { flag: "wx" });
|
|
3822
3925
|
break;
|
|
3823
3926
|
} catch (err) {
|
|
3824
3927
|
if (err.code !== "EEXIST")
|
|
@@ -3891,7 +3994,7 @@ function archiveConversationEvents(topicId, topicTitle, userId, options = {}) {
|
|
|
3891
3994
|
const suffix = counter === 1 ? "" : `_${counter}`;
|
|
3892
3995
|
const path = join9(archiveDir, `${safeTopic}_${date}${reasonSuffix}_events${suffix}.jsonl`);
|
|
3893
3996
|
try {
|
|
3894
|
-
|
|
3997
|
+
writeFileSync8(path, body, { flag: "wx" });
|
|
3895
3998
|
logger.info({ topicId, topicTitle, archive: path, eventCount: entries.length }, "archiveConversationEvents: archived raw conversation events");
|
|
3896
3999
|
return { path, eventCount: entries.length };
|
|
3897
4000
|
} catch (error) {
|
|
@@ -4103,6 +4206,7 @@ export {
|
|
|
4103
4206
|
exports_tasks as tasks,
|
|
4104
4207
|
taskScopeKey,
|
|
4105
4208
|
taskFileMtimeNs,
|
|
4209
|
+
stampUnscopedOtiumTopics,
|
|
4106
4210
|
softDeleteApiMessagesByIdPrefix,
|
|
4107
4211
|
softDeleteApiMessage,
|
|
4108
4212
|
settleTopicArchiveJob,
|
|
@@ -4117,10 +4221,12 @@ export {
|
|
|
4117
4221
|
setTopicAgentAndSession,
|
|
4118
4222
|
setTopicAgentAndClearSession,
|
|
4119
4223
|
setTopicAgent,
|
|
4224
|
+
setSurfaceScopeRequired,
|
|
4120
4225
|
setSessionForTopic,
|
|
4121
4226
|
setLastShownConfig,
|
|
4122
4227
|
setGlobalAiName,
|
|
4123
4228
|
setDmSessionId,
|
|
4229
|
+
setDefaultSurfaceScope,
|
|
4124
4230
|
setApiTopicConfig,
|
|
4125
4231
|
setApiTopicAgent,
|
|
4126
4232
|
setApiMessageReactions,
|
|
@@ -4153,6 +4259,7 @@ export {
|
|
|
4153
4259
|
normalizeTopicSurface,
|
|
4154
4260
|
normalizeTopicState,
|
|
4155
4261
|
normalizeTopicKind,
|
|
4262
|
+
normalizeSurfaceScope,
|
|
4156
4263
|
normalizeAiMode,
|
|
4157
4264
|
markPendingAskState,
|
|
4158
4265
|
markPendingAskSources,
|
|
@@ -4168,6 +4275,7 @@ export {
|
|
|
4168
4275
|
isTopicVisible,
|
|
4169
4276
|
isTopicSummaryFile,
|
|
4170
4277
|
isTopicBriefFile,
|
|
4278
|
+
isSurfaceScopeRequired,
|
|
4171
4279
|
isRuntimeProcessLeaseAlive,
|
|
4172
4280
|
inferTopicKind,
|
|
4173
4281
|
inferAiMode,
|
|
@@ -4220,6 +4328,7 @@ export {
|
|
|
4220
4328
|
findRecentUserMessage,
|
|
4221
4329
|
findLastSessionIdForAgent,
|
|
4222
4330
|
describePendingAskState,
|
|
4331
|
+
deleteTopicStats,
|
|
4223
4332
|
deleteTopicBrief,
|
|
4224
4333
|
deleteTopicArchiveState,
|
|
4225
4334
|
deleteTopic,
|
|
@@ -4228,6 +4337,7 @@ export {
|
|
|
4228
4337
|
deleteMessagesForTopic,
|
|
4229
4338
|
deleteApiTopicConfig,
|
|
4230
4339
|
defaultTopicSurface,
|
|
4340
|
+
defaultSurfaceScope,
|
|
4231
4341
|
db2 as db,
|
|
4232
4342
|
createTasks,
|
|
4233
4343
|
createPendingAsk,
|
|
@@ -4272,4 +4382,4 @@ export {
|
|
|
4272
4382
|
ConversationLogDivergedError
|
|
4273
4383
|
};
|
|
4274
4384
|
|
|
4275
|
-
//# debugId=
|
|
4385
|
+
//# debugId=6AE30BDF1C411C3464756E2164756E21
|