negotium 0.18.0 → 0.18.2

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 (29) hide show
  1. package/dist/agent-helpers.js +7082 -7062
  2. package/dist/agent-helpers.js.map +10 -10
  3. package/dist/{chunk-th3a0ew7.js → chunk-3d1pjgfw.js} +633 -195
  4. package/dist/{chunk-th3a0ew7.js.map → chunk-3d1pjgfw.js.map} +9 -6
  5. package/dist/{chunk-kgwgwc3r.js → chunk-44ajn328.js} +3 -3
  6. package/dist/{chunk-kgwgwc3r.js.map → chunk-44ajn328.js.map} +2 -2
  7. package/dist/{chunk-axa09pgx.js → chunk-q2s5f70x.js} +1 -1
  8. package/dist/hosted-agent.js +634 -196
  9. package/dist/hosted-agent.js.map +10 -7
  10. package/dist/main.js +5942 -5908
  11. package/dist/main.js.map +13 -13
  12. package/dist/mcp-factories.js +3418 -3393
  13. package/dist/mcp-factories.js.map +11 -11
  14. package/dist/prompts.js +18 -14
  15. package/dist/prompts.js.map +3 -3
  16. package/dist/registry.js +3 -3
  17. package/dist/rollout.js +2 -2
  18. package/dist/runtime/src/agents/mcp-tools/spawn-subagent.ts +43 -16
  19. package/dist/runtime/src/mcp/factories/decision.ts +11 -3
  20. package/dist/runtime/src/mcp/session-comm/server.ts +1 -1
  21. package/dist/runtime/src/platform/mcp-config.ts +79 -58
  22. package/dist/runtime/src/prompts/builders.ts +34 -17
  23. package/dist/runtime/src/prompts/sessions/surfaces/otium-manager.md +18 -0
  24. package/dist/runtime/src/version.ts +1 -1
  25. package/dist/types/packages/core/src/agents/mcp-tools/spawn-subagent.d.ts +8 -4
  26. package/dist/types/packages/core/src/mcp/factories/decision.d.ts +2 -0
  27. package/dist/types/packages/core/src/version.d.ts +1 -1
  28. package/package.json +1 -1
  29. /package/dist/{chunk-axa09pgx.js.map → chunk-q2s5f70x.js.map} +0 -0
@@ -1025,6 +1025,624 @@ function getRegisteredCronSession(topicId, agent) {
1025
1025
  }
1026
1026
  }
1027
1027
 
1028
+ // ../../packages/core/src/platform/constants.ts
1029
+ var FROM_AUTO_CONTINUE = "auto-continue";
1030
+ var FROM_SELF_SCHEDULE = "self-schedule";
1031
+ var RESERVED_TOPIC_NAMES = new Set([
1032
+ FROM_AUTO_CONTINUE,
1033
+ FROM_SELF_SCHEDULE,
1034
+ "general"
1035
+ ]);
1036
+ var GENERAL_TOPIC_ID = "general";
1037
+
1038
+ // ../../packages/core/src/storage/storage-host.ts
1039
+ import { mkdirSync as mkdirSync3 } from "fs";
1040
+ import { homedir as homedir2 } from "os";
1041
+ import { dirname as dirname3, join as join2, resolve as resolve5 } from "path";
1042
+
1043
+ // ../../packages/core/src/storage/sqlite.ts
1044
+ var isBun = typeof process.versions.bun === "string";
1045
+ var Database;
1046
+ if (isBun) {
1047
+ ({ Database } = await import("bun:sqlite"));
1048
+ } else {
1049
+ const nodeSqliteSpecifier = ["node", "sqlite"].join(":");
1050
+ const { DatabaseSync } = await import(nodeSqliteSpecifier);
1051
+
1052
+ class NodeDatabase {
1053
+ #db;
1054
+ constructor(path, options = {}) {
1055
+ this.#db = options.readonly ? new DatabaseSync(path, { readOnly: true }) : new DatabaseSync(path);
1056
+ }
1057
+ query(sql) {
1058
+ return this.#db.prepare(sql);
1059
+ }
1060
+ prepare(sql) {
1061
+ return this.#db.prepare(sql);
1062
+ }
1063
+ exec(sql) {
1064
+ this.#db.exec(sql);
1065
+ }
1066
+ run(sql, ...params) {
1067
+ return this.#db.prepare(sql).run(...params);
1068
+ }
1069
+ transaction(fn) {
1070
+ const run = (begin) => (...args) => {
1071
+ this.#db.exec(begin);
1072
+ try {
1073
+ const result = fn(...args);
1074
+ this.#db.exec("COMMIT");
1075
+ return result;
1076
+ } catch (err) {
1077
+ this.#db.exec("ROLLBACK");
1078
+ throw err;
1079
+ }
1080
+ };
1081
+ const tx = run("BEGIN");
1082
+ tx.deferred = run("BEGIN DEFERRED");
1083
+ tx.immediate = run("BEGIN IMMEDIATE");
1084
+ tx.exclusive = run("BEGIN EXCLUSIVE");
1085
+ return tx;
1086
+ }
1087
+ close() {
1088
+ this.#db.close();
1089
+ }
1090
+ }
1091
+ Database = NodeDatabase;
1092
+ }
1093
+ function closeDatabase(database) {
1094
+ try {
1095
+ database.close(true);
1096
+ } catch {
1097
+ try {
1098
+ database.close();
1099
+ } catch {}
1100
+ }
1101
+ }
1102
+
1103
+ // ../../packages/core/src/storage/storage-host.ts
1104
+ var STORAGE_HOST_STATE = Symbol.for("negotium.storage-host.state.v1");
1105
+ function storageState() {
1106
+ const holder = globalThis;
1107
+ const existing = holder[STORAGE_HOST_STATE];
1108
+ if (existing)
1109
+ return existing;
1110
+ const created = {
1111
+ configuredHost: {},
1112
+ fallbackDatabase: null,
1113
+ fallbackDatabasePath: null,
1114
+ frames: [],
1115
+ schemaInitializers: [],
1116
+ initializedSchemas: new WeakMap,
1117
+ initializingDatabases: new WeakSet
1118
+ };
1119
+ Object.defineProperty(holder, STORAGE_HOST_STATE, {
1120
+ value: created,
1121
+ enumerable: false,
1122
+ writable: false,
1123
+ configurable: false
1124
+ });
1125
+ return created;
1126
+ }
1127
+ function envPath(name, fallback) {
1128
+ const value = process.env[name]?.trim();
1129
+ return resolve5(value || fallback);
1130
+ }
1131
+ function defaultStateDir() {
1132
+ return envPath("NEGOTIUM_STATE_DIR", join2(homedir2(), ".negotium"));
1133
+ }
1134
+ function defaultDataDir() {
1135
+ return envPath("NEGOTIUM_DATA_DIR", join2(defaultStateDir(), "data"));
1136
+ }
1137
+ function defaultSessionsDatabasePath() {
1138
+ return envPath("SESSIONS_DB_PATH", join2(resolveStorageDataDir(), "sessions.db"));
1139
+ }
1140
+ var SQLITE_INIT_RETRY_MS = 25;
1141
+ var SQLITE_INIT_TIMEOUT_MS = 5000;
1142
+ var SQLITE_INIT_SLEEP = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));
1143
+ function isSqliteBusy(error) {
1144
+ const message = error instanceof Error ? error.message : String(error);
1145
+ return /database is (?:locked|busy)|SQLITE_(?:BUSY|LOCKED)/i.test(message);
1146
+ }
1147
+ function execWithBusyRetry(database, sql, timeoutMs = SQLITE_INIT_TIMEOUT_MS) {
1148
+ const deadline = Date.now() + timeoutMs;
1149
+ while (true) {
1150
+ try {
1151
+ database.exec(sql);
1152
+ return;
1153
+ } catch (error) {
1154
+ if (!isSqliteBusy(error) || Date.now() >= deadline)
1155
+ throw error;
1156
+ Atomics.wait(SQLITE_INIT_SLEEP, 0, 0, Math.min(SQLITE_INIT_RETRY_MS, deadline - Date.now()));
1157
+ }
1158
+ }
1159
+ }
1160
+ function initializeDatabase(database) {
1161
+ database.exec("PRAGMA busy_timeout = 5000");
1162
+ execWithBusyRetry(database, "PRAGMA journal_mode = WAL");
1163
+ database.exec("PRAGMA foreign_keys = ON");
1164
+ database.exec("PRAGMA wal_autocheckpoint = 1000");
1165
+ try {
1166
+ database.exec("PRAGMA wal_checkpoint(TRUNCATE)");
1167
+ } catch {}
1168
+ }
1169
+ function defaultDatabase() {
1170
+ const path = defaultSessionsDatabasePath();
1171
+ const state = storageState();
1172
+ if (state.fallbackDatabase && state.fallbackDatabasePath === path)
1173
+ return state.fallbackDatabase;
1174
+ if (state.fallbackDatabase)
1175
+ closeDatabase(state.fallbackDatabase);
1176
+ mkdirSync3(dirname3(path), { recursive: true });
1177
+ state.fallbackDatabase = new Database(path, { create: true });
1178
+ state.fallbackDatabasePath = path;
1179
+ initializeDatabase(state.fallbackDatabase);
1180
+ return state.fallbackDatabase;
1181
+ }
1182
+ function resolveStorageDatabase() {
1183
+ return storageState().configuredHost.database ?? defaultDatabase();
1184
+ }
1185
+ function resolveStorageDataDir() {
1186
+ return storageState().configuredHost.dataDir ?? defaultDataDir();
1187
+ }
1188
+ function registerStorageSchemaInitializer(initialize, priority = 100) {
1189
+ const initializers = storageState().schemaInitializers;
1190
+ initializers.push({ initialize, priority });
1191
+ initializers.sort((a, b) => a.priority - b.priority);
1192
+ }
1193
+ function ensureStorageSchemas(database = resolveStorageDatabase()) {
1194
+ const state = storageState();
1195
+ if (state.initializingDatabases.has(database))
1196
+ return;
1197
+ let initialized = state.initializedSchemas.get(database);
1198
+ if (!initialized) {
1199
+ initialized = new Set;
1200
+ state.initializedSchemas.set(database, initialized);
1201
+ }
1202
+ state.initializingDatabases.add(database);
1203
+ try {
1204
+ for (const entry of state.schemaInitializers) {
1205
+ if (initialized.has(entry.initialize))
1206
+ continue;
1207
+ initialized.add(entry.initialize);
1208
+ try {
1209
+ entry.initialize(database);
1210
+ } catch (error) {
1211
+ initialized.delete(entry.initialize);
1212
+ throw error;
1213
+ }
1214
+ }
1215
+ } finally {
1216
+ state.initializingDatabases.delete(database);
1217
+ }
1218
+ }
1219
+ var internalStorageDatabase = new Proxy({}, {
1220
+ get(_target, property) {
1221
+ const database = resolveStorageDatabase();
1222
+ ensureStorageSchemas(database);
1223
+ const value = Reflect.get(database, property, database);
1224
+ return typeof value === "function" ? value.bind(database) : value;
1225
+ },
1226
+ set(_target, property, value) {
1227
+ const database = resolveStorageDatabase();
1228
+ ensureStorageSchemas(database);
1229
+ return Reflect.set(database, property, value, database);
1230
+ }
1231
+ });
1232
+
1233
+ // ../../packages/core/src/storage/forum-db.ts
1234
+ var db = internalStorageDatabase;
1235
+
1236
+ // ../../packages/core/src/storage/api-topics.ts
1237
+ function defaultTopicSurface() {
1238
+ return normalizeTopicSurface(process.env.NEGOTIUM_DEFAULT_SURFACE);
1239
+ }
1240
+ function normalizeTopicSurface(value) {
1241
+ return value === "telegram" || value === "otium" || value === "terminal" ? value : "terminal";
1242
+ }
1243
+ function normalizeSurfaceScope(value) {
1244
+ if (typeof value !== "string")
1245
+ return null;
1246
+ const trimmed = value.trim();
1247
+ return trimmed ? trimmed : null;
1248
+ }
1249
+ function tableColumns(table) {
1250
+ const rows = db.query(`PRAGMA table_info(${table})`).all();
1251
+ return new Set(rows.map((row) => row.name));
1252
+ }
1253
+ function initializeApiTopicsSchema() {
1254
+ db.exec(`
1255
+ CREATE TABLE IF NOT EXISTS api_topics (
1256
+ id TEXT PRIMARY KEY,
1257
+ title TEXT NOT NULL,
1258
+ kind TEXT NOT NULL DEFAULT 'channel',
1259
+ description TEXT,
1260
+ agent TEXT,
1261
+ default_model TEXT,
1262
+ default_effort TEXT,
1263
+ participants TEXT,
1264
+ created_at TEXT NOT NULL,
1265
+ last_message_at TEXT,
1266
+ is_archived INTEGER NOT NULL DEFAULT 0,
1267
+ ai_mention INTEGER NOT NULL DEFAULT 0,
1268
+ ai_mode TEXT
1269
+ )
1270
+ `);
1271
+ const initialTopicColumns = tableColumns("api_topics");
1272
+ const legacyTopicSchema = !initialTopicColumns.has("response_policy");
1273
+ const needsCanonicalTopicRebuild = legacyTopicSchema || !initialTopicColumns.has("agent");
1274
+ if (legacyTopicSchema) {
1275
+ try {
1276
+ db.exec("ALTER TABLE api_topics RENAME COLUMN default_agent TO agent");
1277
+ } catch {}
1278
+ try {
1279
+ db.exec("ALTER TABLE api_topics ADD COLUMN ai_mention INTEGER NOT NULL DEFAULT 0");
1280
+ } catch {}
1281
+ try {
1282
+ db.exec("ALTER TABLE api_topics ADD COLUMN parent_topic_id TEXT");
1283
+ } catch {}
1284
+ try {
1285
+ db.exec("ALTER TABLE api_topics ADD COLUMN is_fork INTEGER NOT NULL DEFAULT 0");
1286
+ } catch {}
1287
+ try {
1288
+ db.exec("ALTER TABLE api_topics ADD COLUMN is_subagent INTEGER NOT NULL DEFAULT 0");
1289
+ } catch {}
1290
+ try {
1291
+ db.exec("ALTER TABLE api_topics ADD COLUMN session_id TEXT");
1292
+ } catch {}
1293
+ try {
1294
+ db.exec("ALTER TABLE api_topics ADD COLUMN kind TEXT NOT NULL DEFAULT 'channel'");
1295
+ } catch {}
1296
+ try {
1297
+ db.exec("ALTER TABLE api_topics ADD COLUMN ai_mode TEXT");
1298
+ } catch {}
1299
+ db.exec(`
1300
+ CREATE TABLE IF NOT EXISTS api_schema_migrations (
1301
+ key TEXT PRIMARY KEY,
1302
+ applied_at TEXT NOT NULL
1303
+ )
1304
+ `);
1305
+ const ALWAYS_RESPOND_MIGRATION = "api_topics_ai_invited_default_always_respond_20260623";
1306
+ const alwaysRespondMigration = db.query("SELECT key FROM api_schema_migrations WHERE key = ?").get(ALWAYS_RESPOND_MIGRATION);
1307
+ if (!alwaysRespondMigration) {
1308
+ db.transaction(() => {
1309
+ db.query("UPDATE api_topics SET ai_mention = 0 WHERE agent IS NOT NULL").run();
1310
+ db.query("INSERT INTO api_schema_migrations (key, applied_at) VALUES (?, ?)").run(ALWAYS_RESPOND_MIGRATION, new Date().toISOString());
1311
+ })();
1312
+ }
1313
+ const GENERAL_AGENT_KIND_MIGRATION = "api_topics_general_agent_kind_20260704";
1314
+ const generalAgentKindMigration = db.query("SELECT key FROM api_schema_migrations WHERE key = ?").get(GENERAL_AGENT_KIND_MIGRATION);
1315
+ if (!generalAgentKindMigration) {
1316
+ db.transaction(() => {
1317
+ db.query("UPDATE api_topics SET kind = 'agent', ai_mention = 0 WHERE id = ?").run(GENERAL_TOPIC_ID);
1318
+ db.query("INSERT INTO api_schema_migrations (key, applied_at) VALUES (?, ?)").run(GENERAL_AGENT_KIND_MIGRATION, new Date().toISOString());
1319
+ })();
1320
+ }
1321
+ const AI_MODE_MIGRATION = "api_topics_ai_mode_20260704";
1322
+ const aiModeMigration = db.query("SELECT key FROM api_schema_migrations WHERE key = ?").get(AI_MODE_MIGRATION);
1323
+ if (!aiModeMigration) {
1324
+ db.transaction(() => {
1325
+ db.query(`UPDATE api_topics
1326
+ SET
1327
+ kind = CASE
1328
+ WHEN id = ? THEN 'agent'
1329
+ WHEN kind = 'channel' AND agent IS NOT NULL AND ai_mention = 0 THEN 'agent'
1330
+ ELSE kind
1331
+ END,
1332
+ ai_mode = CASE
1333
+ WHEN id = ? THEN 'always'
1334
+ WHEN agent IS NULL THEN 'off'
1335
+ WHEN kind = 'agent' THEN 'always'
1336
+ WHEN kind = 'channel' AND ai_mention != 0 THEN 'mention'
1337
+ WHEN kind = 'channel' AND ai_mention = 0 THEN 'always'
1338
+ WHEN ai_mention != 0 THEN 'mention'
1339
+ ELSE 'always'
1340
+ END`).run(GENERAL_TOPIC_ID, GENERAL_TOPIC_ID);
1341
+ db.query("INSERT INTO api_schema_migrations (key, applied_at) VALUES (?, ?)").run(AI_MODE_MIGRATION, new Date().toISOString());
1342
+ })();
1343
+ }
1344
+ const GENERAL_MANAGER_KIND_MIGRATION = "api_topics_general_manager_kind_20260704";
1345
+ const generalManagerKindMigration = db.query("SELECT key FROM api_schema_migrations WHERE key = ?").get(GENERAL_MANAGER_KIND_MIGRATION);
1346
+ if (!generalManagerKindMigration) {
1347
+ db.transaction(() => {
1348
+ db.query("UPDATE api_topics SET kind = 'manager', ai_mention = 0, ai_mode = 'always' WHERE id = ?").run(GENERAL_TOPIC_ID);
1349
+ db.query("INSERT INTO api_schema_migrations (key, applied_at) VALUES (?, ?)").run(GENERAL_MANAGER_KIND_MIGRATION, new Date().toISOString());
1350
+ })();
1351
+ }
1352
+ }
1353
+ function createCanonicalTopicsTable(name) {
1354
+ db.exec(`
1355
+ CREATE TABLE IF NOT EXISTS ${name} (
1356
+ id TEXT PRIMARY KEY,
1357
+ title TEXT NOT NULL,
1358
+ kind TEXT NOT NULL CHECK (kind IN ('channel','agent','manager')),
1359
+ description TEXT,
1360
+ agent TEXT CHECK (agent IS NULL OR agent IN ('maestro','claude','codex')),
1361
+ base_model TEXT,
1362
+ base_effort TEXT CHECK (base_effort IS NULL OR base_effort IN ('low','medium','high','xhigh','max')),
1363
+ response_policy TEXT NOT NULL CHECK (response_policy IN ('off','mention','always')),
1364
+ created_at TEXT NOT NULL,
1365
+ last_message_at TEXT,
1366
+ parent_topic_id TEXT,
1367
+ memory_topic_id TEXT,
1368
+ memory_key TEXT,
1369
+ is_fork INTEGER NOT NULL DEFAULT 0 CHECK (is_fork IN (0,1)),
1370
+ is_subagent INTEGER NOT NULL DEFAULT 0 CHECK (is_subagent IN (0,1)),
1371
+ visibility TEXT NOT NULL DEFAULT 'visible' CHECK (visibility IN ('visible','hidden')),
1372
+ surface TEXT NOT NULL DEFAULT 'terminal' CHECK (surface IN ('terminal','telegram','otium')),
1373
+ surface_scope TEXT,
1374
+ browser_profile TEXT NOT NULL DEFAULT 'default',
1375
+ browser_profile_owner TEXT,
1376
+ session_id TEXT,
1377
+ CHECK (
1378
+ (kind = 'channel' AND response_policy = 'off' AND agent IS NULL) OR
1379
+ (kind = 'channel' AND response_policy = 'mention' AND agent IS NOT NULL) OR
1380
+ (kind IN ('agent','manager') AND response_policy = 'always' AND agent IS NOT NULL)
1381
+ )
1382
+ )
1383
+ `);
1384
+ }
1385
+ function createTopicMembersTable() {
1386
+ db.exec(`
1387
+ CREATE TABLE IF NOT EXISTS topic_members (
1388
+ topic_id TEXT NOT NULL REFERENCES api_topics(id) ON DELETE CASCADE,
1389
+ user_id TEXT NOT NULL,
1390
+ role TEXT NOT NULL CHECK (role IN ('owner','member')),
1391
+ PRIMARY KEY (topic_id, user_id)
1392
+ )
1393
+ `);
1394
+ db.exec("CREATE INDEX IF NOT EXISTS idx_topic_members_user ON topic_members(user_id)");
1395
+ }
1396
+ if (needsCanonicalTopicRebuild) {
1397
+ const legacyRows = db.query("SELECT * FROM api_topics").all();
1398
+ const existingMemberRows = tableColumns("topic_members").has("topic_id") ? db.query("SELECT topic_id, user_id, role FROM topic_members").all() : [];
1399
+ const configColumns = tableColumns("api_topic_config");
1400
+ const legacyAgentOverrides = configColumns.has("agent") ? new Map(db.query("SELECT topic_id, agent FROM api_topic_config WHERE agent IS NOT NULL").all().map((row) => [row.topic_id, row.agent])) : new Map;
1401
+ const previousForeignKeys = db.query("PRAGMA foreign_keys").get()?.foreign_keys;
1402
+ db.exec("PRAGMA foreign_keys = OFF");
1403
+ try {
1404
+ db.transaction(() => {
1405
+ createCanonicalTopicsTable("api_topics_next");
1406
+ for (const row of legacyRows) {
1407
+ const selectedAgent = legacyAgentOverrides.get(String(row.id)) ?? row.agent ?? row.runtime_agent ?? row.default_agent ?? undefined;
1408
+ const normalized = normalizeTopicState({
1409
+ id: String(row.id),
1410
+ kind: normalizeTopicKind(row.kind),
1411
+ agent: selectedAgent,
1412
+ aiMode: normalizeAiMode(row.response_policy ?? row.ai_mode),
1413
+ aiMention: Number(row.ai_mention ?? 0) !== 0
1414
+ });
1415
+ const legacyBaseModel = row.base_model ?? row.default_model;
1416
+ const legacyBaseEffort = row.base_effort ?? row.default_effort;
1417
+ db.query(`INSERT INTO api_topics_next
1418
+ (id,title,kind,description,agent,base_model,base_effort,response_policy,
1419
+ created_at,last_message_at,parent_topic_id,memory_topic_id,memory_key,is_fork,is_subagent,visibility,surface,surface_scope,session_id)
1420
+ 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);
1421
+ }
1422
+ db.exec("DROP TABLE IF EXISTS topic_members");
1423
+ db.exec("DROP TABLE api_topics");
1424
+ db.exec("ALTER TABLE api_topics_next RENAME TO api_topics");
1425
+ createTopicMembersTable();
1426
+ if (existingMemberRows.length > 0) {
1427
+ for (const member of existingMemberRows) {
1428
+ db.query("INSERT OR REPLACE INTO topic_members (topic_id,user_id,role) VALUES (?,?,?)").run(member.topic_id, member.user_id, member.role === "owner" ? "owner" : "member");
1429
+ }
1430
+ } else {
1431
+ for (const row of legacyRows) {
1432
+ let participants = [];
1433
+ try {
1434
+ const parsed = JSON.parse(String(row.participants ?? "[]"));
1435
+ if (Array.isArray(parsed))
1436
+ participants = parsed;
1437
+ } catch {
1438
+ participants = [];
1439
+ }
1440
+ for (const participant of participants) {
1441
+ if (!participant?.userId)
1442
+ continue;
1443
+ db.query("INSERT OR REPLACE INTO topic_members (topic_id,user_id,role) VALUES (?,?,?)").run(String(row.id), participant.userId, participant.role === "owner" ? "owner" : "member");
1444
+ }
1445
+ }
1446
+ }
1447
+ })();
1448
+ } finally {
1449
+ db.exec(`PRAGMA foreign_keys = ${previousForeignKeys ? "ON" : "OFF"}`);
1450
+ }
1451
+ } else {
1452
+ createCanonicalTopicsTable("api_topics");
1453
+ createTopicMembersTable();
1454
+ }
1455
+ db.exec(`
1456
+ CREATE TABLE IF NOT EXISTS subagent_tell_grants (
1457
+ subagent_topic_id TEXT NOT NULL REFERENCES api_topics(id) ON DELETE CASCADE,
1458
+ target_topic_id TEXT NOT NULL REFERENCES api_topics(id) ON DELETE CASCADE,
1459
+ granted_by_topic_id TEXT NOT NULL REFERENCES api_topics(id) ON DELETE CASCADE,
1460
+ created_at TEXT NOT NULL,
1461
+ PRIMARY KEY (subagent_topic_id, target_topic_id)
1462
+ )
1463
+ `);
1464
+ db.exec(`
1465
+ DELETE FROM subagent_tell_grants
1466
+ WHERE subagent_topic_id NOT IN (SELECT id FROM api_topics)
1467
+ OR target_topic_id NOT IN (SELECT id FROM api_topics)
1468
+ OR granted_by_topic_id NOT IN (SELECT id FROM api_topics)
1469
+ `);
1470
+ db.exec("DELETE FROM topic_members WHERE topic_id NOT IN (SELECT id FROM api_topics)");
1471
+ if (!tableColumns("api_topics").has("visibility")) {
1472
+ db.exec("ALTER TABLE api_topics ADD COLUMN visibility TEXT NOT NULL DEFAULT 'visible'");
1473
+ }
1474
+ if (!tableColumns("api_topics").has("surface")) {
1475
+ db.exec("ALTER TABLE api_topics ADD COLUMN surface TEXT NOT NULL DEFAULT 'terminal'");
1476
+ }
1477
+ if (!tableColumns("api_topics").has("surface_scope")) {
1478
+ db.exec("ALTER TABLE api_topics ADD COLUMN surface_scope TEXT");
1479
+ }
1480
+ if (tableColumns("api_topics").has("access_mode")) {
1481
+ try {
1482
+ db.exec("ALTER TABLE api_topics DROP COLUMN access_mode");
1483
+ } catch (err) {
1484
+ logger.warn({ err }, "api_topics: could not drop the retired access_mode column");
1485
+ }
1486
+ }
1487
+ if (!tableColumns("api_topics").has("browser_profile")) {
1488
+ db.exec("ALTER TABLE api_topics ADD COLUMN browser_profile TEXT NOT NULL DEFAULT 'default'");
1489
+ }
1490
+ if (!tableColumns("api_topics").has("browser_profile_owner")) {
1491
+ db.exec("ALTER TABLE api_topics ADD COLUMN browser_profile_owner TEXT");
1492
+ }
1493
+ if (!tableColumns("api_topics").has("subagent_report_mode")) {
1494
+ db.exec("ALTER TABLE api_topics ADD COLUMN subagent_report_mode TEXT NOT NULL DEFAULT 'auto'");
1495
+ }
1496
+ if (!tableColumns("api_topics").has("memory_topic_id")) {
1497
+ db.exec("ALTER TABLE api_topics ADD COLUMN memory_topic_id TEXT");
1498
+ }
1499
+ if (!tableColumns("api_topics").has("memory_key")) {
1500
+ db.exec("ALTER TABLE api_topics ADD COLUMN memory_key TEXT");
1501
+ }
1502
+ db.exec(`
1503
+ UPDATE api_topics
1504
+ SET browser_profile_owner = (
1505
+ SELECT m.user_id FROM topic_members m
1506
+ WHERE m.topic_id = api_topics.id AND m.role = 'owner'
1507
+ ORDER BY m.rowid
1508
+ LIMIT 1
1509
+ )
1510
+ WHERE browser_profile_owner IS NULL
1511
+ `);
1512
+ backfillTopicSurfaces();
1513
+ db.exec("CREATE INDEX IF NOT EXISTS idx_api_topics_last_message ON api_topics(last_message_at DESC)");
1514
+ db.exec("CREATE INDEX IF NOT EXISTS idx_api_topics_surface ON api_topics(surface)");
1515
+ db.exec("CREATE INDEX IF NOT EXISTS idx_api_topics_surface_scope ON api_topics(surface, surface_scope)");
1516
+ }
1517
+ var SURFACE_BACKFILL_MIGRATION = "api_topics_surface_backfill_20260808";
1518
+ function backfillTopicSurfaces() {
1519
+ db.exec(`
1520
+ CREATE TABLE IF NOT EXISTS api_schema_migrations (
1521
+ key TEXT PRIMARY KEY,
1522
+ applied_at TEXT NOT NULL
1523
+ )
1524
+ `);
1525
+ const applied = db.query("SELECT key FROM api_schema_migrations WHERE key = ?").get(SURFACE_BACKFILL_MIGRATION);
1526
+ if (applied)
1527
+ return;
1528
+ const surface = defaultTopicSurface();
1529
+ db.transaction(() => {
1530
+ db.query("UPDATE api_topics SET surface = ?").run(surface);
1531
+ renameSurfaceTitleCollisions();
1532
+ db.query("INSERT INTO api_schema_migrations (key, applied_at) VALUES (?, ?)").run(SURFACE_BACKFILL_MIGRATION, new Date().toISOString());
1533
+ })();
1534
+ logger.info({ surface }, "api_topics: surface backfilled");
1535
+ }
1536
+ function renameSurfaceTitleCollisions() {
1537
+ const rows = db.query("SELECT id, title, kind, surface FROM api_topics WHERE kind != 'manager' ORDER BY created_at ASC, rowid ASC").all();
1538
+ const keyOf = (surface, kind, title) => [surface, kind, normalizedTitle(title)].join("\x00");
1539
+ const reserved = new Set(rows.map((row) => keyOf(row.surface, row.kind, row.title)));
1540
+ const used = new Set;
1541
+ const update = db.query("UPDATE api_topics SET title = ? WHERE id = ?");
1542
+ for (const row of rows) {
1543
+ const key = (title) => keyOf(row.surface, row.kind, title);
1544
+ if (!used.has(key(row.title))) {
1545
+ used.add(key(row.title));
1546
+ continue;
1547
+ }
1548
+ let suffix = 2;
1549
+ let candidate = `${row.title} (${suffix})`;
1550
+ while (reserved.has(key(candidate)) || used.has(key(candidate))) {
1551
+ suffix += 1;
1552
+ candidate = `${row.title} (${suffix})`;
1553
+ }
1554
+ used.add(key(candidate));
1555
+ update.run(candidate, row.id);
1556
+ logger.warn({ topicId: row.id, surface: row.surface, from: row.title, to: candidate }, "api_topics: renamed a duplicate title for surface-scoped uniqueness");
1557
+ }
1558
+ }
1559
+ registerStorageSchemaInitializer(initializeApiTopicsSchema, 20);
1560
+ function getTopicParticipants(topicId) {
1561
+ return db.query("SELECT user_id, role FROM topic_members WHERE topic_id = ? ORDER BY rowid").all(topicId).map((row) => ({ userId: row.user_id, role: row.role }));
1562
+ }
1563
+ function rowToDto(r, participants = getTopicParticipants(r.id), tellTargets) {
1564
+ const normalized = normalizeTopicState({
1565
+ id: r.id,
1566
+ kind: normalizeTopicKind(r.kind),
1567
+ agent: r.agent ?? undefined,
1568
+ aiMode: normalizeAiMode(r.response_policy)
1569
+ });
1570
+ return {
1571
+ id: r.id,
1572
+ title: r.title,
1573
+ kind: normalized.kind,
1574
+ description: r.description ?? undefined,
1575
+ agent: normalized.agent,
1576
+ defaultModel: r.base_model ?? "deepseek-pro",
1577
+ defaultEffort: r.base_effort ?? undefined,
1578
+ aiMode: normalized.aiMode,
1579
+ aiMention: aiMentionFromMode(normalized.aiMode),
1580
+ participants,
1581
+ createdAt: r.created_at,
1582
+ lastMessageAt: r.last_message_at ?? new Date().toISOString(),
1583
+ parentTopicId: r.parent_topic_id ?? undefined,
1584
+ memoryTopicId: r.memory_topic_id ?? undefined,
1585
+ memoryKey: r.memory_key ?? undefined,
1586
+ isFork: r.is_fork !== 0,
1587
+ ...r.is_subagent !== 0 ? { isSubagent: true } : {},
1588
+ ...r.is_subagent !== 0 ? {
1589
+ subagentTellTargetIds: tellTargets ? tellTargets.get(r.id) ?? [] : listSubagentTellTargetIds(r.id)
1590
+ } : {},
1591
+ ...r.is_subagent !== 0 ? {
1592
+ subagentReportMode: r.subagent_report_mode === "tell" || r.subagent_report_mode === "status-only" ? r.subagent_report_mode : "auto"
1593
+ } : {},
1594
+ visibility: normalizeTopicVisibility(r.visibility),
1595
+ surface: normalizeTopicSurface(r.surface),
1596
+ surfaceScope: normalizeSurfaceScope(r.surface_scope)
1597
+ };
1598
+ }
1599
+ function normalizeTopicVisibility(value) {
1600
+ return value === "hidden" ? "hidden" : "visible";
1601
+ }
1602
+ function normalizeTopicKind(value) {
1603
+ return value === "channel" || value === "agent" || value === "manager" ? value : null;
1604
+ }
1605
+ function normalizeAiMode(value) {
1606
+ return value === "off" || value === "mention" || value === "always" ? value : null;
1607
+ }
1608
+ function aiMentionFromMode(mode) {
1609
+ return mode === "mention";
1610
+ }
1611
+ function normalizeTopicState(input) {
1612
+ if (input.kind === "manager" || input.id === GENERAL_TOPIC_ID) {
1613
+ return {
1614
+ kind: "manager",
1615
+ aiMode: "always",
1616
+ agent: input.agent ?? resolveFallbackAgent()
1617
+ };
1618
+ }
1619
+ const requestedKind = input.kind;
1620
+ const kind = requestedKind ?? (input.aiMode === "always" ? "agent" : input.aiMode === "mention" || input.aiMode === "off" ? "channel" : input.agent && input.aiMention !== true ? "agent" : "channel");
1621
+ if (kind === "agent") {
1622
+ return {
1623
+ kind: "agent",
1624
+ aiMode: "always",
1625
+ agent: input.agent ?? resolveFallbackAgent()
1626
+ };
1627
+ }
1628
+ const agent = input.aiMode === "off" ? undefined : input.agent ?? undefined;
1629
+ return {
1630
+ kind: "channel",
1631
+ aiMode: agent ? "mention" : "off",
1632
+ agent
1633
+ };
1634
+ }
1635
+ function normalizedTitle(title) {
1636
+ return title.trim().toLowerCase();
1637
+ }
1638
+ function getTopic(id) {
1639
+ const r = db.query("SELECT * FROM api_topics WHERE id = ?").get(id);
1640
+ return r ? rowToDto(r) : null;
1641
+ }
1642
+ function listSubagentTellTargetIds(subagentTopicId) {
1643
+ return db.query("SELECT target_topic_id FROM subagent_tell_grants WHERE subagent_topic_id = ? ORDER BY created_at").all(subagentTopicId).map((row) => row.target_topic_id);
1644
+ }
1645
+
1028
1646
  // ../../packages/core/src/platform/mcp-config.ts
1029
1647
  function buildStdioMcpServer(agent, serverFile, serverArgs, env) {
1030
1648
  if (agent === "codex") {
@@ -1531,7 +2149,7 @@ function getMcpServersForQuery(opts) {
1531
2149
  if (opts.sessionType === "cron") {
1532
2150
  if (!opts.topicId)
1533
2151
  throw new Error("getMcpServersForQuery: cron sessionType requires topicId");
1534
- return getCronMcpServers({
2152
+ return hideProviderIntrospectionForOtium(getCronMcpServers({
1535
2153
  userId: opts.userId || "local",
1536
2154
  session: opts.session || "cron",
1537
2155
  topicId: opts.topicId,
@@ -1545,7 +2163,7 @@ function getMcpServersForQuery(opts) {
1545
2163
  playwrightCapability: opts.playwrightCapability,
1546
2164
  visualTools: opts.visualTools,
1547
2165
  fileDeliveryTools: opts.fileDeliveryTools
1548
- });
2166
+ }), opts.topicId);
1549
2167
  }
1550
2168
  if (opts.sessionType === "dm" || opts.sessionType === "ephemeral") {
1551
2169
  return getDmMcpServers({
@@ -1558,7 +2176,7 @@ function getMcpServersForQuery(opts) {
1558
2176
  });
1559
2177
  }
1560
2178
  if (opts.sessionType === "manager") {
1561
- return mergeHostMcpServers(getManagerMcpServers({
2179
+ return hideProviderIntrospectionForOtium(mergeHostMcpServers(getManagerMcpServers({
1562
2180
  userId: opts.userId || "local",
1563
2181
  session: opts.session,
1564
2182
  topicId: opts.topicId,
@@ -1573,9 +2191,9 @@ function getMcpServersForQuery(opts) {
1573
2191
  autoContinue: opts.autoContinue,
1574
2192
  visualTools: opts.visualTools,
1575
2193
  fileDeliveryTools: opts.fileDeliveryTools
1576
- }), opts.hostMcpServers);
2194
+ }), opts.hostMcpServers), opts.topicId);
1577
2195
  }
1578
- return getForumMcpServers({
2196
+ return hideProviderIntrospectionForOtium(getForumMcpServers({
1579
2197
  userId: opts.userId || "local",
1580
2198
  actorUserId: opts.actorUserId,
1581
2199
  vaultUserId: opts.vaultUserId,
@@ -1600,201 +2218,21 @@ function getMcpServersForQuery(opts) {
1600
2218
  extra: opts.mcpExtra,
1601
2219
  silent: opts.silent,
1602
2220
  peerBridge: opts.peerBridge
1603
- });
2221
+ }), opts.topicId);
2222
+ }
2223
+ function hideProviderIntrospectionForOtium(servers, topicId) {
2224
+ if (!topicId || getTopic(topicId)?.surface !== "otium")
2225
+ return servers;
2226
+ const filtered = { ...servers };
2227
+ delete filtered["agent-health"];
2228
+ delete filtered["token-stats"];
2229
+ return filtered;
1604
2230
  }
1605
2231
 
1606
2232
  // ../../packages/core/src/storage/vault.ts
1607
2233
  import { chmodSync as chmodSync2, mkdirSync as mkdirSync4 } from "fs";
1608
2234
  import { join as join3 } from "path";
1609
2235
 
1610
- // ../../packages/core/src/storage/sqlite.ts
1611
- var isBun = typeof process.versions.bun === "string";
1612
- var Database;
1613
- if (isBun) {
1614
- ({ Database } = await import("bun:sqlite"));
1615
- } else {
1616
- const nodeSqliteSpecifier = ["node", "sqlite"].join(":");
1617
- const { DatabaseSync } = await import(nodeSqliteSpecifier);
1618
-
1619
- class NodeDatabase {
1620
- #db;
1621
- constructor(path, options = {}) {
1622
- this.#db = options.readonly ? new DatabaseSync(path, { readOnly: true }) : new DatabaseSync(path);
1623
- }
1624
- query(sql) {
1625
- return this.#db.prepare(sql);
1626
- }
1627
- prepare(sql) {
1628
- return this.#db.prepare(sql);
1629
- }
1630
- exec(sql) {
1631
- this.#db.exec(sql);
1632
- }
1633
- run(sql, ...params) {
1634
- return this.#db.prepare(sql).run(...params);
1635
- }
1636
- transaction(fn) {
1637
- const run = (begin) => (...args) => {
1638
- this.#db.exec(begin);
1639
- try {
1640
- const result = fn(...args);
1641
- this.#db.exec("COMMIT");
1642
- return result;
1643
- } catch (err) {
1644
- this.#db.exec("ROLLBACK");
1645
- throw err;
1646
- }
1647
- };
1648
- const tx = run("BEGIN");
1649
- tx.deferred = run("BEGIN DEFERRED");
1650
- tx.immediate = run("BEGIN IMMEDIATE");
1651
- tx.exclusive = run("BEGIN EXCLUSIVE");
1652
- return tx;
1653
- }
1654
- close() {
1655
- this.#db.close();
1656
- }
1657
- }
1658
- Database = NodeDatabase;
1659
- }
1660
- function closeDatabase(database) {
1661
- try {
1662
- database.close(true);
1663
- } catch {
1664
- try {
1665
- database.close();
1666
- } catch {}
1667
- }
1668
- }
1669
-
1670
- // ../../packages/core/src/storage/storage-host.ts
1671
- import { mkdirSync as mkdirSync3 } from "fs";
1672
- import { homedir as homedir2 } from "os";
1673
- import { dirname as dirname3, join as join2, resolve as resolve5 } from "path";
1674
- var STORAGE_HOST_STATE = Symbol.for("negotium.storage-host.state.v1");
1675
- function storageState() {
1676
- const holder = globalThis;
1677
- const existing = holder[STORAGE_HOST_STATE];
1678
- if (existing)
1679
- return existing;
1680
- const created = {
1681
- configuredHost: {},
1682
- fallbackDatabase: null,
1683
- fallbackDatabasePath: null,
1684
- frames: [],
1685
- schemaInitializers: [],
1686
- initializedSchemas: new WeakMap,
1687
- initializingDatabases: new WeakSet
1688
- };
1689
- Object.defineProperty(holder, STORAGE_HOST_STATE, {
1690
- value: created,
1691
- enumerable: false,
1692
- writable: false,
1693
- configurable: false
1694
- });
1695
- return created;
1696
- }
1697
- function envPath(name, fallback) {
1698
- const value = process.env[name]?.trim();
1699
- return resolve5(value || fallback);
1700
- }
1701
- function defaultStateDir() {
1702
- return envPath("NEGOTIUM_STATE_DIR", join2(homedir2(), ".negotium"));
1703
- }
1704
- function defaultDataDir() {
1705
- return envPath("NEGOTIUM_DATA_DIR", join2(defaultStateDir(), "data"));
1706
- }
1707
- function defaultSessionsDatabasePath() {
1708
- return envPath("SESSIONS_DB_PATH", join2(resolveStorageDataDir(), "sessions.db"));
1709
- }
1710
- var SQLITE_INIT_RETRY_MS = 25;
1711
- var SQLITE_INIT_TIMEOUT_MS = 5000;
1712
- var SQLITE_INIT_SLEEP = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));
1713
- function isSqliteBusy(error) {
1714
- const message = error instanceof Error ? error.message : String(error);
1715
- return /database is (?:locked|busy)|SQLITE_(?:BUSY|LOCKED)/i.test(message);
1716
- }
1717
- function execWithBusyRetry(database, sql, timeoutMs = SQLITE_INIT_TIMEOUT_MS) {
1718
- const deadline = Date.now() + timeoutMs;
1719
- while (true) {
1720
- try {
1721
- database.exec(sql);
1722
- return;
1723
- } catch (error) {
1724
- if (!isSqliteBusy(error) || Date.now() >= deadline)
1725
- throw error;
1726
- Atomics.wait(SQLITE_INIT_SLEEP, 0, 0, Math.min(SQLITE_INIT_RETRY_MS, deadline - Date.now()));
1727
- }
1728
- }
1729
- }
1730
- function initializeDatabase(database) {
1731
- database.exec("PRAGMA busy_timeout = 5000");
1732
- execWithBusyRetry(database, "PRAGMA journal_mode = WAL");
1733
- database.exec("PRAGMA foreign_keys = ON");
1734
- database.exec("PRAGMA wal_autocheckpoint = 1000");
1735
- try {
1736
- database.exec("PRAGMA wal_checkpoint(TRUNCATE)");
1737
- } catch {}
1738
- }
1739
- function defaultDatabase() {
1740
- const path = defaultSessionsDatabasePath();
1741
- const state = storageState();
1742
- if (state.fallbackDatabase && state.fallbackDatabasePath === path)
1743
- return state.fallbackDatabase;
1744
- if (state.fallbackDatabase)
1745
- closeDatabase(state.fallbackDatabase);
1746
- mkdirSync3(dirname3(path), { recursive: true });
1747
- state.fallbackDatabase = new Database(path, { create: true });
1748
- state.fallbackDatabasePath = path;
1749
- initializeDatabase(state.fallbackDatabase);
1750
- return state.fallbackDatabase;
1751
- }
1752
- function resolveStorageDatabase() {
1753
- return storageState().configuredHost.database ?? defaultDatabase();
1754
- }
1755
- function resolveStorageDataDir() {
1756
- return storageState().configuredHost.dataDir ?? defaultDataDir();
1757
- }
1758
- function ensureStorageSchemas(database = resolveStorageDatabase()) {
1759
- const state = storageState();
1760
- if (state.initializingDatabases.has(database))
1761
- return;
1762
- let initialized = state.initializedSchemas.get(database);
1763
- if (!initialized) {
1764
- initialized = new Set;
1765
- state.initializedSchemas.set(database, initialized);
1766
- }
1767
- state.initializingDatabases.add(database);
1768
- try {
1769
- for (const entry of state.schemaInitializers) {
1770
- if (initialized.has(entry.initialize))
1771
- continue;
1772
- initialized.add(entry.initialize);
1773
- try {
1774
- entry.initialize(database);
1775
- } catch (error) {
1776
- initialized.delete(entry.initialize);
1777
- throw error;
1778
- }
1779
- }
1780
- } finally {
1781
- state.initializingDatabases.delete(database);
1782
- }
1783
- }
1784
- var internalStorageDatabase = new Proxy({}, {
1785
- get(_target, property) {
1786
- const database = resolveStorageDatabase();
1787
- ensureStorageSchemas(database);
1788
- const value = Reflect.get(database, property, database);
1789
- return typeof value === "function" ? value.bind(database) : value;
1790
- },
1791
- set(_target, property, value) {
1792
- const database = resolveStorageDatabase();
1793
- ensureStorageSchemas(database);
1794
- return Reflect.set(database, property, value, database);
1795
- }
1796
- });
1797
-
1798
2236
  // ../../packages/core/src/storage/vault-crypto-core.ts
1799
2237
  import { createCipheriv, createDecipheriv, createHash as createHash2, randomBytes as randomBytes3 } from "crypto";
1800
2238
  var ENVELOPE_PREFIX = "otium-vault:v1:";
@@ -2586,4 +3024,4 @@ function formatDateBucket(d) {
2586
3024
 
2587
3025
  export { __require, CLAUDE_EFFORT_VALUES, CODEX_EFFORT_VALUES, MAESTRO_EFFORT_VALUES, logger, MODEL_SONNET, MODEL_OPUS, MODEL_FABLE, configureRolloutHost, FIXTURES_DIR, clone, assertUuidLike, ensureCwdExists, extractChatPairs, parseJsonlText, writeJsonlFile, hostedCodexHomePath, extractLatestCodexPatchPreview, extractCodexPatchCallIds, readCodexPatchCallIds, readLatestCodexPatchPreview, extractLatestCodexContextUsage, readLatestCodexContextUsage, migrateCodexRolloutNativeMultiAgentMetadata, latestCodexRolloutPath, writeCodexRollout, decodeUuidV7Timestamp };
2588
3026
 
2589
- //# debugId=185F5001F0B79C6C64756E2164756E21
3027
+ //# debugId=54820657F6417AF864756E2164756E21