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
@@ -948,6 +948,624 @@ function getRegisteredCronSession(topicId, agent) {
948
948
  }
949
949
  }
950
950
 
951
+ // ../../packages/core/src/platform/constants.ts
952
+ var FROM_AUTO_CONTINUE = "auto-continue";
953
+ var FROM_SELF_SCHEDULE = "self-schedule";
954
+ var RESERVED_TOPIC_NAMES = new Set([
955
+ FROM_AUTO_CONTINUE,
956
+ FROM_SELF_SCHEDULE,
957
+ "general"
958
+ ]);
959
+ var GENERAL_TOPIC_ID = "general";
960
+
961
+ // ../../packages/core/src/storage/storage-host.ts
962
+ import { mkdirSync as mkdirSync3 } from "fs";
963
+ import { homedir as homedir2 } from "os";
964
+ import { dirname as dirname4, join as join2, resolve as resolve5 } from "path";
965
+
966
+ // ../../packages/core/src/storage/sqlite.ts
967
+ var isBun = typeof process.versions.bun === "string";
968
+ var Database;
969
+ if (isBun) {
970
+ ({ Database } = await import("bun:sqlite"));
971
+ } else {
972
+ const nodeSqliteSpecifier = ["node", "sqlite"].join(":");
973
+ const { DatabaseSync } = await import(nodeSqliteSpecifier);
974
+
975
+ class NodeDatabase {
976
+ #db;
977
+ constructor(path, options = {}) {
978
+ this.#db = options.readonly ? new DatabaseSync(path, { readOnly: true }) : new DatabaseSync(path);
979
+ }
980
+ query(sql) {
981
+ return this.#db.prepare(sql);
982
+ }
983
+ prepare(sql) {
984
+ return this.#db.prepare(sql);
985
+ }
986
+ exec(sql) {
987
+ this.#db.exec(sql);
988
+ }
989
+ run(sql, ...params) {
990
+ return this.#db.prepare(sql).run(...params);
991
+ }
992
+ transaction(fn) {
993
+ const run = (begin) => (...args) => {
994
+ this.#db.exec(begin);
995
+ try {
996
+ const result = fn(...args);
997
+ this.#db.exec("COMMIT");
998
+ return result;
999
+ } catch (err) {
1000
+ this.#db.exec("ROLLBACK");
1001
+ throw err;
1002
+ }
1003
+ };
1004
+ const tx = run("BEGIN");
1005
+ tx.deferred = run("BEGIN DEFERRED");
1006
+ tx.immediate = run("BEGIN IMMEDIATE");
1007
+ tx.exclusive = run("BEGIN EXCLUSIVE");
1008
+ return tx;
1009
+ }
1010
+ close() {
1011
+ this.#db.close();
1012
+ }
1013
+ }
1014
+ Database = NodeDatabase;
1015
+ }
1016
+ function closeDatabase(database) {
1017
+ try {
1018
+ database.close(true);
1019
+ } catch {
1020
+ try {
1021
+ database.close();
1022
+ } catch {}
1023
+ }
1024
+ }
1025
+
1026
+ // ../../packages/core/src/storage/storage-host.ts
1027
+ var STORAGE_HOST_STATE = Symbol.for("negotium.storage-host.state.v1");
1028
+ function storageState() {
1029
+ const holder = globalThis;
1030
+ const existing = holder[STORAGE_HOST_STATE];
1031
+ if (existing)
1032
+ return existing;
1033
+ const created = {
1034
+ configuredHost: {},
1035
+ fallbackDatabase: null,
1036
+ fallbackDatabasePath: null,
1037
+ frames: [],
1038
+ schemaInitializers: [],
1039
+ initializedSchemas: new WeakMap,
1040
+ initializingDatabases: new WeakSet
1041
+ };
1042
+ Object.defineProperty(holder, STORAGE_HOST_STATE, {
1043
+ value: created,
1044
+ enumerable: false,
1045
+ writable: false,
1046
+ configurable: false
1047
+ });
1048
+ return created;
1049
+ }
1050
+ function envPath(name, fallback) {
1051
+ const value = process.env[name]?.trim();
1052
+ return resolve5(value || fallback);
1053
+ }
1054
+ function defaultStateDir() {
1055
+ return envPath("NEGOTIUM_STATE_DIR", join2(homedir2(), ".negotium"));
1056
+ }
1057
+ function defaultDataDir() {
1058
+ return envPath("NEGOTIUM_DATA_DIR", join2(defaultStateDir(), "data"));
1059
+ }
1060
+ function defaultSessionsDatabasePath() {
1061
+ return envPath("SESSIONS_DB_PATH", join2(resolveStorageDataDir(), "sessions.db"));
1062
+ }
1063
+ var SQLITE_INIT_RETRY_MS = 25;
1064
+ var SQLITE_INIT_TIMEOUT_MS = 5000;
1065
+ var SQLITE_INIT_SLEEP = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));
1066
+ function isSqliteBusy(error) {
1067
+ const message = error instanceof Error ? error.message : String(error);
1068
+ return /database is (?:locked|busy)|SQLITE_(?:BUSY|LOCKED)/i.test(message);
1069
+ }
1070
+ function execWithBusyRetry(database, sql, timeoutMs = SQLITE_INIT_TIMEOUT_MS) {
1071
+ const deadline = Date.now() + timeoutMs;
1072
+ while (true) {
1073
+ try {
1074
+ database.exec(sql);
1075
+ return;
1076
+ } catch (error) {
1077
+ if (!isSqliteBusy(error) || Date.now() >= deadline)
1078
+ throw error;
1079
+ Atomics.wait(SQLITE_INIT_SLEEP, 0, 0, Math.min(SQLITE_INIT_RETRY_MS, deadline - Date.now()));
1080
+ }
1081
+ }
1082
+ }
1083
+ function initializeDatabase(database) {
1084
+ database.exec("PRAGMA busy_timeout = 5000");
1085
+ execWithBusyRetry(database, "PRAGMA journal_mode = WAL");
1086
+ database.exec("PRAGMA foreign_keys = ON");
1087
+ database.exec("PRAGMA wal_autocheckpoint = 1000");
1088
+ try {
1089
+ database.exec("PRAGMA wal_checkpoint(TRUNCATE)");
1090
+ } catch {}
1091
+ }
1092
+ function defaultDatabase() {
1093
+ const path = defaultSessionsDatabasePath();
1094
+ const state = storageState();
1095
+ if (state.fallbackDatabase && state.fallbackDatabasePath === path)
1096
+ return state.fallbackDatabase;
1097
+ if (state.fallbackDatabase)
1098
+ closeDatabase(state.fallbackDatabase);
1099
+ mkdirSync3(dirname4(path), { recursive: true });
1100
+ state.fallbackDatabase = new Database(path, { create: true });
1101
+ state.fallbackDatabasePath = path;
1102
+ initializeDatabase(state.fallbackDatabase);
1103
+ return state.fallbackDatabase;
1104
+ }
1105
+ function resolveStorageDatabase() {
1106
+ return storageState().configuredHost.database ?? defaultDatabase();
1107
+ }
1108
+ function resolveStorageDataDir() {
1109
+ return storageState().configuredHost.dataDir ?? defaultDataDir();
1110
+ }
1111
+ function registerStorageSchemaInitializer(initialize, priority = 100) {
1112
+ const initializers = storageState().schemaInitializers;
1113
+ initializers.push({ initialize, priority });
1114
+ initializers.sort((a, b) => a.priority - b.priority);
1115
+ }
1116
+ function ensureStorageSchemas(database = resolveStorageDatabase()) {
1117
+ const state = storageState();
1118
+ if (state.initializingDatabases.has(database))
1119
+ return;
1120
+ let initialized = state.initializedSchemas.get(database);
1121
+ if (!initialized) {
1122
+ initialized = new Set;
1123
+ state.initializedSchemas.set(database, initialized);
1124
+ }
1125
+ state.initializingDatabases.add(database);
1126
+ try {
1127
+ for (const entry of state.schemaInitializers) {
1128
+ if (initialized.has(entry.initialize))
1129
+ continue;
1130
+ initialized.add(entry.initialize);
1131
+ try {
1132
+ entry.initialize(database);
1133
+ } catch (error) {
1134
+ initialized.delete(entry.initialize);
1135
+ throw error;
1136
+ }
1137
+ }
1138
+ } finally {
1139
+ state.initializingDatabases.delete(database);
1140
+ }
1141
+ }
1142
+ var internalStorageDatabase = new Proxy({}, {
1143
+ get(_target, property) {
1144
+ const database = resolveStorageDatabase();
1145
+ ensureStorageSchemas(database);
1146
+ const value = Reflect.get(database, property, database);
1147
+ return typeof value === "function" ? value.bind(database) : value;
1148
+ },
1149
+ set(_target, property, value) {
1150
+ const database = resolveStorageDatabase();
1151
+ ensureStorageSchemas(database);
1152
+ return Reflect.set(database, property, value, database);
1153
+ }
1154
+ });
1155
+
1156
+ // ../../packages/core/src/storage/forum-db.ts
1157
+ var db = internalStorageDatabase;
1158
+
1159
+ // ../../packages/core/src/storage/api-topics.ts
1160
+ function defaultTopicSurface() {
1161
+ return normalizeTopicSurface(process.env.NEGOTIUM_DEFAULT_SURFACE);
1162
+ }
1163
+ function normalizeTopicSurface(value) {
1164
+ return value === "telegram" || value === "otium" || value === "terminal" ? value : "terminal";
1165
+ }
1166
+ function normalizeSurfaceScope(value) {
1167
+ if (typeof value !== "string")
1168
+ return null;
1169
+ const trimmed = value.trim();
1170
+ return trimmed ? trimmed : null;
1171
+ }
1172
+ function tableColumns(table) {
1173
+ const rows = db.query(`PRAGMA table_info(${table})`).all();
1174
+ return new Set(rows.map((row) => row.name));
1175
+ }
1176
+ function initializeApiTopicsSchema() {
1177
+ db.exec(`
1178
+ CREATE TABLE IF NOT EXISTS api_topics (
1179
+ id TEXT PRIMARY KEY,
1180
+ title TEXT NOT NULL,
1181
+ kind TEXT NOT NULL DEFAULT 'channel',
1182
+ description TEXT,
1183
+ agent TEXT,
1184
+ default_model TEXT,
1185
+ default_effort TEXT,
1186
+ participants TEXT,
1187
+ created_at TEXT NOT NULL,
1188
+ last_message_at TEXT,
1189
+ is_archived INTEGER NOT NULL DEFAULT 0,
1190
+ ai_mention INTEGER NOT NULL DEFAULT 0,
1191
+ ai_mode TEXT
1192
+ )
1193
+ `);
1194
+ const initialTopicColumns = tableColumns("api_topics");
1195
+ const legacyTopicSchema = !initialTopicColumns.has("response_policy");
1196
+ const needsCanonicalTopicRebuild = legacyTopicSchema || !initialTopicColumns.has("agent");
1197
+ if (legacyTopicSchema) {
1198
+ try {
1199
+ db.exec("ALTER TABLE api_topics RENAME COLUMN default_agent TO agent");
1200
+ } catch {}
1201
+ try {
1202
+ db.exec("ALTER TABLE api_topics ADD COLUMN ai_mention INTEGER NOT NULL DEFAULT 0");
1203
+ } catch {}
1204
+ try {
1205
+ db.exec("ALTER TABLE api_topics ADD COLUMN parent_topic_id TEXT");
1206
+ } catch {}
1207
+ try {
1208
+ db.exec("ALTER TABLE api_topics ADD COLUMN is_fork INTEGER NOT NULL DEFAULT 0");
1209
+ } catch {}
1210
+ try {
1211
+ db.exec("ALTER TABLE api_topics ADD COLUMN is_subagent INTEGER NOT NULL DEFAULT 0");
1212
+ } catch {}
1213
+ try {
1214
+ db.exec("ALTER TABLE api_topics ADD COLUMN session_id TEXT");
1215
+ } catch {}
1216
+ try {
1217
+ db.exec("ALTER TABLE api_topics ADD COLUMN kind TEXT NOT NULL DEFAULT 'channel'");
1218
+ } catch {}
1219
+ try {
1220
+ db.exec("ALTER TABLE api_topics ADD COLUMN ai_mode TEXT");
1221
+ } catch {}
1222
+ db.exec(`
1223
+ CREATE TABLE IF NOT EXISTS api_schema_migrations (
1224
+ key TEXT PRIMARY KEY,
1225
+ applied_at TEXT NOT NULL
1226
+ )
1227
+ `);
1228
+ const ALWAYS_RESPOND_MIGRATION = "api_topics_ai_invited_default_always_respond_20260623";
1229
+ const alwaysRespondMigration = db.query("SELECT key FROM api_schema_migrations WHERE key = ?").get(ALWAYS_RESPOND_MIGRATION);
1230
+ if (!alwaysRespondMigration) {
1231
+ db.transaction(() => {
1232
+ db.query("UPDATE api_topics SET ai_mention = 0 WHERE agent IS NOT NULL").run();
1233
+ db.query("INSERT INTO api_schema_migrations (key, applied_at) VALUES (?, ?)").run(ALWAYS_RESPOND_MIGRATION, new Date().toISOString());
1234
+ })();
1235
+ }
1236
+ const GENERAL_AGENT_KIND_MIGRATION = "api_topics_general_agent_kind_20260704";
1237
+ const generalAgentKindMigration = db.query("SELECT key FROM api_schema_migrations WHERE key = ?").get(GENERAL_AGENT_KIND_MIGRATION);
1238
+ if (!generalAgentKindMigration) {
1239
+ db.transaction(() => {
1240
+ db.query("UPDATE api_topics SET kind = 'agent', ai_mention = 0 WHERE id = ?").run(GENERAL_TOPIC_ID);
1241
+ db.query("INSERT INTO api_schema_migrations (key, applied_at) VALUES (?, ?)").run(GENERAL_AGENT_KIND_MIGRATION, new Date().toISOString());
1242
+ })();
1243
+ }
1244
+ const AI_MODE_MIGRATION = "api_topics_ai_mode_20260704";
1245
+ const aiModeMigration = db.query("SELECT key FROM api_schema_migrations WHERE key = ?").get(AI_MODE_MIGRATION);
1246
+ if (!aiModeMigration) {
1247
+ db.transaction(() => {
1248
+ db.query(`UPDATE api_topics
1249
+ SET
1250
+ kind = CASE
1251
+ WHEN id = ? THEN 'agent'
1252
+ WHEN kind = 'channel' AND agent IS NOT NULL AND ai_mention = 0 THEN 'agent'
1253
+ ELSE kind
1254
+ END,
1255
+ ai_mode = CASE
1256
+ WHEN id = ? THEN 'always'
1257
+ WHEN agent IS NULL THEN 'off'
1258
+ WHEN kind = 'agent' THEN 'always'
1259
+ WHEN kind = 'channel' AND ai_mention != 0 THEN 'mention'
1260
+ WHEN kind = 'channel' AND ai_mention = 0 THEN 'always'
1261
+ WHEN ai_mention != 0 THEN 'mention'
1262
+ ELSE 'always'
1263
+ END`).run(GENERAL_TOPIC_ID, GENERAL_TOPIC_ID);
1264
+ db.query("INSERT INTO api_schema_migrations (key, applied_at) VALUES (?, ?)").run(AI_MODE_MIGRATION, new Date().toISOString());
1265
+ })();
1266
+ }
1267
+ const GENERAL_MANAGER_KIND_MIGRATION = "api_topics_general_manager_kind_20260704";
1268
+ const generalManagerKindMigration = db.query("SELECT key FROM api_schema_migrations WHERE key = ?").get(GENERAL_MANAGER_KIND_MIGRATION);
1269
+ if (!generalManagerKindMigration) {
1270
+ db.transaction(() => {
1271
+ db.query("UPDATE api_topics SET kind = 'manager', ai_mention = 0, ai_mode = 'always' WHERE id = ?").run(GENERAL_TOPIC_ID);
1272
+ db.query("INSERT INTO api_schema_migrations (key, applied_at) VALUES (?, ?)").run(GENERAL_MANAGER_KIND_MIGRATION, new Date().toISOString());
1273
+ })();
1274
+ }
1275
+ }
1276
+ function createCanonicalTopicsTable(name) {
1277
+ db.exec(`
1278
+ CREATE TABLE IF NOT EXISTS ${name} (
1279
+ id TEXT PRIMARY KEY,
1280
+ title TEXT NOT NULL,
1281
+ kind TEXT NOT NULL CHECK (kind IN ('channel','agent','manager')),
1282
+ description TEXT,
1283
+ agent TEXT CHECK (agent IS NULL OR agent IN ('maestro','claude','codex')),
1284
+ base_model TEXT,
1285
+ base_effort TEXT CHECK (base_effort IS NULL OR base_effort IN ('low','medium','high','xhigh','max')),
1286
+ response_policy TEXT NOT NULL CHECK (response_policy IN ('off','mention','always')),
1287
+ created_at TEXT NOT NULL,
1288
+ last_message_at TEXT,
1289
+ parent_topic_id TEXT,
1290
+ memory_topic_id TEXT,
1291
+ memory_key TEXT,
1292
+ is_fork INTEGER NOT NULL DEFAULT 0 CHECK (is_fork IN (0,1)),
1293
+ is_subagent INTEGER NOT NULL DEFAULT 0 CHECK (is_subagent IN (0,1)),
1294
+ visibility TEXT NOT NULL DEFAULT 'visible' CHECK (visibility IN ('visible','hidden')),
1295
+ surface TEXT NOT NULL DEFAULT 'terminal' CHECK (surface IN ('terminal','telegram','otium')),
1296
+ surface_scope TEXT,
1297
+ browser_profile TEXT NOT NULL DEFAULT 'default',
1298
+ browser_profile_owner TEXT,
1299
+ session_id TEXT,
1300
+ CHECK (
1301
+ (kind = 'channel' AND response_policy = 'off' AND agent IS NULL) OR
1302
+ (kind = 'channel' AND response_policy = 'mention' AND agent IS NOT NULL) OR
1303
+ (kind IN ('agent','manager') AND response_policy = 'always' AND agent IS NOT NULL)
1304
+ )
1305
+ )
1306
+ `);
1307
+ }
1308
+ function createTopicMembersTable() {
1309
+ db.exec(`
1310
+ CREATE TABLE IF NOT EXISTS topic_members (
1311
+ topic_id TEXT NOT NULL REFERENCES api_topics(id) ON DELETE CASCADE,
1312
+ user_id TEXT NOT NULL,
1313
+ role TEXT NOT NULL CHECK (role IN ('owner','member')),
1314
+ PRIMARY KEY (topic_id, user_id)
1315
+ )
1316
+ `);
1317
+ db.exec("CREATE INDEX IF NOT EXISTS idx_topic_members_user ON topic_members(user_id)");
1318
+ }
1319
+ if (needsCanonicalTopicRebuild) {
1320
+ const legacyRows = db.query("SELECT * FROM api_topics").all();
1321
+ const existingMemberRows = tableColumns("topic_members").has("topic_id") ? db.query("SELECT topic_id, user_id, role FROM topic_members").all() : [];
1322
+ const configColumns = tableColumns("api_topic_config");
1323
+ 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;
1324
+ const previousForeignKeys = db.query("PRAGMA foreign_keys").get()?.foreign_keys;
1325
+ db.exec("PRAGMA foreign_keys = OFF");
1326
+ try {
1327
+ db.transaction(() => {
1328
+ createCanonicalTopicsTable("api_topics_next");
1329
+ for (const row of legacyRows) {
1330
+ const selectedAgent = legacyAgentOverrides.get(String(row.id)) ?? row.agent ?? row.runtime_agent ?? row.default_agent ?? undefined;
1331
+ const normalized = normalizeTopicState({
1332
+ id: String(row.id),
1333
+ kind: normalizeTopicKind(row.kind),
1334
+ agent: selectedAgent,
1335
+ aiMode: normalizeAiMode(row.response_policy ?? row.ai_mode),
1336
+ aiMention: Number(row.ai_mention ?? 0) !== 0
1337
+ });
1338
+ const legacyBaseModel = row.base_model ?? row.default_model;
1339
+ const legacyBaseEffort = row.base_effort ?? row.default_effort;
1340
+ db.query(`INSERT INTO api_topics_next
1341
+ (id,title,kind,description,agent,base_model,base_effort,response_policy,
1342
+ created_at,last_message_at,parent_topic_id,memory_topic_id,memory_key,is_fork,is_subagent,visibility,surface,surface_scope,session_id)
1343
+ 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);
1344
+ }
1345
+ db.exec("DROP TABLE IF EXISTS topic_members");
1346
+ db.exec("DROP TABLE api_topics");
1347
+ db.exec("ALTER TABLE api_topics_next RENAME TO api_topics");
1348
+ createTopicMembersTable();
1349
+ if (existingMemberRows.length > 0) {
1350
+ for (const member of existingMemberRows) {
1351
+ 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");
1352
+ }
1353
+ } else {
1354
+ for (const row of legacyRows) {
1355
+ let participants = [];
1356
+ try {
1357
+ const parsed = JSON.parse(String(row.participants ?? "[]"));
1358
+ if (Array.isArray(parsed))
1359
+ participants = parsed;
1360
+ } catch {
1361
+ participants = [];
1362
+ }
1363
+ for (const participant of participants) {
1364
+ if (!participant?.userId)
1365
+ continue;
1366
+ 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");
1367
+ }
1368
+ }
1369
+ }
1370
+ })();
1371
+ } finally {
1372
+ db.exec(`PRAGMA foreign_keys = ${previousForeignKeys ? "ON" : "OFF"}`);
1373
+ }
1374
+ } else {
1375
+ createCanonicalTopicsTable("api_topics");
1376
+ createTopicMembersTable();
1377
+ }
1378
+ db.exec(`
1379
+ CREATE TABLE IF NOT EXISTS subagent_tell_grants (
1380
+ subagent_topic_id TEXT NOT NULL REFERENCES api_topics(id) ON DELETE CASCADE,
1381
+ target_topic_id TEXT NOT NULL REFERENCES api_topics(id) ON DELETE CASCADE,
1382
+ granted_by_topic_id TEXT NOT NULL REFERENCES api_topics(id) ON DELETE CASCADE,
1383
+ created_at TEXT NOT NULL,
1384
+ PRIMARY KEY (subagent_topic_id, target_topic_id)
1385
+ )
1386
+ `);
1387
+ db.exec(`
1388
+ DELETE FROM subagent_tell_grants
1389
+ WHERE subagent_topic_id NOT IN (SELECT id FROM api_topics)
1390
+ OR target_topic_id NOT IN (SELECT id FROM api_topics)
1391
+ OR granted_by_topic_id NOT IN (SELECT id FROM api_topics)
1392
+ `);
1393
+ db.exec("DELETE FROM topic_members WHERE topic_id NOT IN (SELECT id FROM api_topics)");
1394
+ if (!tableColumns("api_topics").has("visibility")) {
1395
+ db.exec("ALTER TABLE api_topics ADD COLUMN visibility TEXT NOT NULL DEFAULT 'visible'");
1396
+ }
1397
+ if (!tableColumns("api_topics").has("surface")) {
1398
+ db.exec("ALTER TABLE api_topics ADD COLUMN surface TEXT NOT NULL DEFAULT 'terminal'");
1399
+ }
1400
+ if (!tableColumns("api_topics").has("surface_scope")) {
1401
+ db.exec("ALTER TABLE api_topics ADD COLUMN surface_scope TEXT");
1402
+ }
1403
+ if (tableColumns("api_topics").has("access_mode")) {
1404
+ try {
1405
+ db.exec("ALTER TABLE api_topics DROP COLUMN access_mode");
1406
+ } catch (err) {
1407
+ logger.warn({ err }, "api_topics: could not drop the retired access_mode column");
1408
+ }
1409
+ }
1410
+ if (!tableColumns("api_topics").has("browser_profile")) {
1411
+ db.exec("ALTER TABLE api_topics ADD COLUMN browser_profile TEXT NOT NULL DEFAULT 'default'");
1412
+ }
1413
+ if (!tableColumns("api_topics").has("browser_profile_owner")) {
1414
+ db.exec("ALTER TABLE api_topics ADD COLUMN browser_profile_owner TEXT");
1415
+ }
1416
+ if (!tableColumns("api_topics").has("subagent_report_mode")) {
1417
+ db.exec("ALTER TABLE api_topics ADD COLUMN subagent_report_mode TEXT NOT NULL DEFAULT 'auto'");
1418
+ }
1419
+ if (!tableColumns("api_topics").has("memory_topic_id")) {
1420
+ db.exec("ALTER TABLE api_topics ADD COLUMN memory_topic_id TEXT");
1421
+ }
1422
+ if (!tableColumns("api_topics").has("memory_key")) {
1423
+ db.exec("ALTER TABLE api_topics ADD COLUMN memory_key TEXT");
1424
+ }
1425
+ db.exec(`
1426
+ UPDATE api_topics
1427
+ SET browser_profile_owner = (
1428
+ SELECT m.user_id FROM topic_members m
1429
+ WHERE m.topic_id = api_topics.id AND m.role = 'owner'
1430
+ ORDER BY m.rowid
1431
+ LIMIT 1
1432
+ )
1433
+ WHERE browser_profile_owner IS NULL
1434
+ `);
1435
+ backfillTopicSurfaces();
1436
+ db.exec("CREATE INDEX IF NOT EXISTS idx_api_topics_last_message ON api_topics(last_message_at DESC)");
1437
+ db.exec("CREATE INDEX IF NOT EXISTS idx_api_topics_surface ON api_topics(surface)");
1438
+ db.exec("CREATE INDEX IF NOT EXISTS idx_api_topics_surface_scope ON api_topics(surface, surface_scope)");
1439
+ }
1440
+ var SURFACE_BACKFILL_MIGRATION = "api_topics_surface_backfill_20260808";
1441
+ function backfillTopicSurfaces() {
1442
+ db.exec(`
1443
+ CREATE TABLE IF NOT EXISTS api_schema_migrations (
1444
+ key TEXT PRIMARY KEY,
1445
+ applied_at TEXT NOT NULL
1446
+ )
1447
+ `);
1448
+ const applied = db.query("SELECT key FROM api_schema_migrations WHERE key = ?").get(SURFACE_BACKFILL_MIGRATION);
1449
+ if (applied)
1450
+ return;
1451
+ const surface = defaultTopicSurface();
1452
+ db.transaction(() => {
1453
+ db.query("UPDATE api_topics SET surface = ?").run(surface);
1454
+ renameSurfaceTitleCollisions();
1455
+ db.query("INSERT INTO api_schema_migrations (key, applied_at) VALUES (?, ?)").run(SURFACE_BACKFILL_MIGRATION, new Date().toISOString());
1456
+ })();
1457
+ logger.info({ surface }, "api_topics: surface backfilled");
1458
+ }
1459
+ function renameSurfaceTitleCollisions() {
1460
+ const rows = db.query("SELECT id, title, kind, surface FROM api_topics WHERE kind != 'manager' ORDER BY created_at ASC, rowid ASC").all();
1461
+ const keyOf = (surface, kind, title) => [surface, kind, normalizedTitle(title)].join("\x00");
1462
+ const reserved = new Set(rows.map((row) => keyOf(row.surface, row.kind, row.title)));
1463
+ const used = new Set;
1464
+ const update = db.query("UPDATE api_topics SET title = ? WHERE id = ?");
1465
+ for (const row of rows) {
1466
+ const key = (title) => keyOf(row.surface, row.kind, title);
1467
+ if (!used.has(key(row.title))) {
1468
+ used.add(key(row.title));
1469
+ continue;
1470
+ }
1471
+ let suffix = 2;
1472
+ let candidate = `${row.title} (${suffix})`;
1473
+ while (reserved.has(key(candidate)) || used.has(key(candidate))) {
1474
+ suffix += 1;
1475
+ candidate = `${row.title} (${suffix})`;
1476
+ }
1477
+ used.add(key(candidate));
1478
+ update.run(candidate, row.id);
1479
+ logger.warn({ topicId: row.id, surface: row.surface, from: row.title, to: candidate }, "api_topics: renamed a duplicate title for surface-scoped uniqueness");
1480
+ }
1481
+ }
1482
+ registerStorageSchemaInitializer(initializeApiTopicsSchema, 20);
1483
+ function getTopicParticipants(topicId) {
1484
+ 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 }));
1485
+ }
1486
+ function rowToDto(r, participants = getTopicParticipants(r.id), tellTargets) {
1487
+ const normalized = normalizeTopicState({
1488
+ id: r.id,
1489
+ kind: normalizeTopicKind(r.kind),
1490
+ agent: r.agent ?? undefined,
1491
+ aiMode: normalizeAiMode(r.response_policy)
1492
+ });
1493
+ return {
1494
+ id: r.id,
1495
+ title: r.title,
1496
+ kind: normalized.kind,
1497
+ description: r.description ?? undefined,
1498
+ agent: normalized.agent,
1499
+ defaultModel: r.base_model ?? "deepseek-pro",
1500
+ defaultEffort: r.base_effort ?? undefined,
1501
+ aiMode: normalized.aiMode,
1502
+ aiMention: aiMentionFromMode(normalized.aiMode),
1503
+ participants,
1504
+ createdAt: r.created_at,
1505
+ lastMessageAt: r.last_message_at ?? new Date().toISOString(),
1506
+ parentTopicId: r.parent_topic_id ?? undefined,
1507
+ memoryTopicId: r.memory_topic_id ?? undefined,
1508
+ memoryKey: r.memory_key ?? undefined,
1509
+ isFork: r.is_fork !== 0,
1510
+ ...r.is_subagent !== 0 ? { isSubagent: true } : {},
1511
+ ...r.is_subagent !== 0 ? {
1512
+ subagentTellTargetIds: tellTargets ? tellTargets.get(r.id) ?? [] : listSubagentTellTargetIds(r.id)
1513
+ } : {},
1514
+ ...r.is_subagent !== 0 ? {
1515
+ subagentReportMode: r.subagent_report_mode === "tell" || r.subagent_report_mode === "status-only" ? r.subagent_report_mode : "auto"
1516
+ } : {},
1517
+ visibility: normalizeTopicVisibility(r.visibility),
1518
+ surface: normalizeTopicSurface(r.surface),
1519
+ surfaceScope: normalizeSurfaceScope(r.surface_scope)
1520
+ };
1521
+ }
1522
+ function normalizeTopicVisibility(value) {
1523
+ return value === "hidden" ? "hidden" : "visible";
1524
+ }
1525
+ function normalizeTopicKind(value) {
1526
+ return value === "channel" || value === "agent" || value === "manager" ? value : null;
1527
+ }
1528
+ function normalizeAiMode(value) {
1529
+ return value === "off" || value === "mention" || value === "always" ? value : null;
1530
+ }
1531
+ function aiMentionFromMode(mode) {
1532
+ return mode === "mention";
1533
+ }
1534
+ function normalizeTopicState(input) {
1535
+ if (input.kind === "manager" || input.id === GENERAL_TOPIC_ID) {
1536
+ return {
1537
+ kind: "manager",
1538
+ aiMode: "always",
1539
+ agent: input.agent ?? resolveFallbackAgent()
1540
+ };
1541
+ }
1542
+ const requestedKind = input.kind;
1543
+ const kind = requestedKind ?? (input.aiMode === "always" ? "agent" : input.aiMode === "mention" || input.aiMode === "off" ? "channel" : input.agent && input.aiMention !== true ? "agent" : "channel");
1544
+ if (kind === "agent") {
1545
+ return {
1546
+ kind: "agent",
1547
+ aiMode: "always",
1548
+ agent: input.agent ?? resolveFallbackAgent()
1549
+ };
1550
+ }
1551
+ const agent = input.aiMode === "off" ? undefined : input.agent ?? undefined;
1552
+ return {
1553
+ kind: "channel",
1554
+ aiMode: agent ? "mention" : "off",
1555
+ agent
1556
+ };
1557
+ }
1558
+ function normalizedTitle(title) {
1559
+ return title.trim().toLowerCase();
1560
+ }
1561
+ function getTopic(id) {
1562
+ const r = db.query("SELECT * FROM api_topics WHERE id = ?").get(id);
1563
+ return r ? rowToDto(r) : null;
1564
+ }
1565
+ function listSubagentTellTargetIds(subagentTopicId) {
1566
+ 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);
1567
+ }
1568
+
951
1569
  // ../../packages/core/src/platform/mcp-config.ts
952
1570
  function buildStdioMcpServer(agent, serverFile, serverArgs, env) {
953
1571
  if (agent === "codex") {
@@ -1454,7 +2072,7 @@ function getMcpServersForQuery(opts) {
1454
2072
  if (opts.sessionType === "cron") {
1455
2073
  if (!opts.topicId)
1456
2074
  throw new Error("getMcpServersForQuery: cron sessionType requires topicId");
1457
- return getCronMcpServers({
2075
+ return hideProviderIntrospectionForOtium(getCronMcpServers({
1458
2076
  userId: opts.userId || "local",
1459
2077
  session: opts.session || "cron",
1460
2078
  topicId: opts.topicId,
@@ -1468,7 +2086,7 @@ function getMcpServersForQuery(opts) {
1468
2086
  playwrightCapability: opts.playwrightCapability,
1469
2087
  visualTools: opts.visualTools,
1470
2088
  fileDeliveryTools: opts.fileDeliveryTools
1471
- });
2089
+ }), opts.topicId);
1472
2090
  }
1473
2091
  if (opts.sessionType === "dm" || opts.sessionType === "ephemeral") {
1474
2092
  return getDmMcpServers({
@@ -1481,7 +2099,7 @@ function getMcpServersForQuery(opts) {
1481
2099
  });
1482
2100
  }
1483
2101
  if (opts.sessionType === "manager") {
1484
- return mergeHostMcpServers(getManagerMcpServers({
2102
+ return hideProviderIntrospectionForOtium(mergeHostMcpServers(getManagerMcpServers({
1485
2103
  userId: opts.userId || "local",
1486
2104
  session: opts.session,
1487
2105
  topicId: opts.topicId,
@@ -1496,9 +2114,9 @@ function getMcpServersForQuery(opts) {
1496
2114
  autoContinue: opts.autoContinue,
1497
2115
  visualTools: opts.visualTools,
1498
2116
  fileDeliveryTools: opts.fileDeliveryTools
1499
- }), opts.hostMcpServers);
2117
+ }), opts.hostMcpServers), opts.topicId);
1500
2118
  }
1501
- return getForumMcpServers({
2119
+ return hideProviderIntrospectionForOtium(getForumMcpServers({
1502
2120
  userId: opts.userId || "local",
1503
2121
  actorUserId: opts.actorUserId,
1504
2122
  vaultUserId: opts.vaultUserId,
@@ -1523,201 +2141,21 @@ function getMcpServersForQuery(opts) {
1523
2141
  extra: opts.mcpExtra,
1524
2142
  silent: opts.silent,
1525
2143
  peerBridge: opts.peerBridge
1526
- });
2144
+ }), opts.topicId);
2145
+ }
2146
+ function hideProviderIntrospectionForOtium(servers, topicId) {
2147
+ if (!topicId || getTopic(topicId)?.surface !== "otium")
2148
+ return servers;
2149
+ const filtered = { ...servers };
2150
+ delete filtered["agent-health"];
2151
+ delete filtered["token-stats"];
2152
+ return filtered;
1527
2153
  }
1528
2154
 
1529
2155
  // ../../packages/core/src/storage/vault.ts
1530
2156
  import { chmodSync as chmodSync2, mkdirSync as mkdirSync4 } from "fs";
1531
2157
  import { join as join3 } from "path";
1532
2158
 
1533
- // ../../packages/core/src/storage/sqlite.ts
1534
- var isBun = typeof process.versions.bun === "string";
1535
- var Database;
1536
- if (isBun) {
1537
- ({ Database } = await import("bun:sqlite"));
1538
- } else {
1539
- const nodeSqliteSpecifier = ["node", "sqlite"].join(":");
1540
- const { DatabaseSync } = await import(nodeSqliteSpecifier);
1541
-
1542
- class NodeDatabase {
1543
- #db;
1544
- constructor(path, options = {}) {
1545
- this.#db = options.readonly ? new DatabaseSync(path, { readOnly: true }) : new DatabaseSync(path);
1546
- }
1547
- query(sql) {
1548
- return this.#db.prepare(sql);
1549
- }
1550
- prepare(sql) {
1551
- return this.#db.prepare(sql);
1552
- }
1553
- exec(sql) {
1554
- this.#db.exec(sql);
1555
- }
1556
- run(sql, ...params) {
1557
- return this.#db.prepare(sql).run(...params);
1558
- }
1559
- transaction(fn) {
1560
- const run = (begin) => (...args) => {
1561
- this.#db.exec(begin);
1562
- try {
1563
- const result = fn(...args);
1564
- this.#db.exec("COMMIT");
1565
- return result;
1566
- } catch (err) {
1567
- this.#db.exec("ROLLBACK");
1568
- throw err;
1569
- }
1570
- };
1571
- const tx = run("BEGIN");
1572
- tx.deferred = run("BEGIN DEFERRED");
1573
- tx.immediate = run("BEGIN IMMEDIATE");
1574
- tx.exclusive = run("BEGIN EXCLUSIVE");
1575
- return tx;
1576
- }
1577
- close() {
1578
- this.#db.close();
1579
- }
1580
- }
1581
- Database = NodeDatabase;
1582
- }
1583
- function closeDatabase(database) {
1584
- try {
1585
- database.close(true);
1586
- } catch {
1587
- try {
1588
- database.close();
1589
- } catch {}
1590
- }
1591
- }
1592
-
1593
- // ../../packages/core/src/storage/storage-host.ts
1594
- import { mkdirSync as mkdirSync3 } from "fs";
1595
- import { homedir as homedir2 } from "os";
1596
- import { dirname as dirname4, join as join2, resolve as resolve5 } from "path";
1597
- var STORAGE_HOST_STATE = Symbol.for("negotium.storage-host.state.v1");
1598
- function storageState() {
1599
- const holder = globalThis;
1600
- const existing = holder[STORAGE_HOST_STATE];
1601
- if (existing)
1602
- return existing;
1603
- const created = {
1604
- configuredHost: {},
1605
- fallbackDatabase: null,
1606
- fallbackDatabasePath: null,
1607
- frames: [],
1608
- schemaInitializers: [],
1609
- initializedSchemas: new WeakMap,
1610
- initializingDatabases: new WeakSet
1611
- };
1612
- Object.defineProperty(holder, STORAGE_HOST_STATE, {
1613
- value: created,
1614
- enumerable: false,
1615
- writable: false,
1616
- configurable: false
1617
- });
1618
- return created;
1619
- }
1620
- function envPath(name, fallback) {
1621
- const value = process.env[name]?.trim();
1622
- return resolve5(value || fallback);
1623
- }
1624
- function defaultStateDir() {
1625
- return envPath("NEGOTIUM_STATE_DIR", join2(homedir2(), ".negotium"));
1626
- }
1627
- function defaultDataDir() {
1628
- return envPath("NEGOTIUM_DATA_DIR", join2(defaultStateDir(), "data"));
1629
- }
1630
- function defaultSessionsDatabasePath() {
1631
- return envPath("SESSIONS_DB_PATH", join2(resolveStorageDataDir(), "sessions.db"));
1632
- }
1633
- var SQLITE_INIT_RETRY_MS = 25;
1634
- var SQLITE_INIT_TIMEOUT_MS = 5000;
1635
- var SQLITE_INIT_SLEEP = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));
1636
- function isSqliteBusy(error) {
1637
- const message = error instanceof Error ? error.message : String(error);
1638
- return /database is (?:locked|busy)|SQLITE_(?:BUSY|LOCKED)/i.test(message);
1639
- }
1640
- function execWithBusyRetry(database, sql, timeoutMs = SQLITE_INIT_TIMEOUT_MS) {
1641
- const deadline = Date.now() + timeoutMs;
1642
- while (true) {
1643
- try {
1644
- database.exec(sql);
1645
- return;
1646
- } catch (error) {
1647
- if (!isSqliteBusy(error) || Date.now() >= deadline)
1648
- throw error;
1649
- Atomics.wait(SQLITE_INIT_SLEEP, 0, 0, Math.min(SQLITE_INIT_RETRY_MS, deadline - Date.now()));
1650
- }
1651
- }
1652
- }
1653
- function initializeDatabase(database) {
1654
- database.exec("PRAGMA busy_timeout = 5000");
1655
- execWithBusyRetry(database, "PRAGMA journal_mode = WAL");
1656
- database.exec("PRAGMA foreign_keys = ON");
1657
- database.exec("PRAGMA wal_autocheckpoint = 1000");
1658
- try {
1659
- database.exec("PRAGMA wal_checkpoint(TRUNCATE)");
1660
- } catch {}
1661
- }
1662
- function defaultDatabase() {
1663
- const path = defaultSessionsDatabasePath();
1664
- const state = storageState();
1665
- if (state.fallbackDatabase && state.fallbackDatabasePath === path)
1666
- return state.fallbackDatabase;
1667
- if (state.fallbackDatabase)
1668
- closeDatabase(state.fallbackDatabase);
1669
- mkdirSync3(dirname4(path), { recursive: true });
1670
- state.fallbackDatabase = new Database(path, { create: true });
1671
- state.fallbackDatabasePath = path;
1672
- initializeDatabase(state.fallbackDatabase);
1673
- return state.fallbackDatabase;
1674
- }
1675
- function resolveStorageDatabase() {
1676
- return storageState().configuredHost.database ?? defaultDatabase();
1677
- }
1678
- function resolveStorageDataDir() {
1679
- return storageState().configuredHost.dataDir ?? defaultDataDir();
1680
- }
1681
- function ensureStorageSchemas(database = resolveStorageDatabase()) {
1682
- const state = storageState();
1683
- if (state.initializingDatabases.has(database))
1684
- return;
1685
- let initialized = state.initializedSchemas.get(database);
1686
- if (!initialized) {
1687
- initialized = new Set;
1688
- state.initializedSchemas.set(database, initialized);
1689
- }
1690
- state.initializingDatabases.add(database);
1691
- try {
1692
- for (const entry of state.schemaInitializers) {
1693
- if (initialized.has(entry.initialize))
1694
- continue;
1695
- initialized.add(entry.initialize);
1696
- try {
1697
- entry.initialize(database);
1698
- } catch (error) {
1699
- initialized.delete(entry.initialize);
1700
- throw error;
1701
- }
1702
- }
1703
- } finally {
1704
- state.initializingDatabases.delete(database);
1705
- }
1706
- }
1707
- var internalStorageDatabase = new Proxy({}, {
1708
- get(_target, property) {
1709
- const database = resolveStorageDatabase();
1710
- ensureStorageSchemas(database);
1711
- const value = Reflect.get(database, property, database);
1712
- return typeof value === "function" ? value.bind(database) : value;
1713
- },
1714
- set(_target, property, value) {
1715
- const database = resolveStorageDatabase();
1716
- ensureStorageSchemas(database);
1717
- return Reflect.set(database, property, value, database);
1718
- }
1719
- });
1720
-
1721
2159
  // ../../packages/core/src/storage/vault-crypto-core.ts
1722
2160
  import { createCipheriv, createDecipheriv, createHash as createHash2, randomBytes as randomBytes3 } from "crypto";
1723
2161
  var ENVELOPE_PREFIX = "otium-vault:v1:";
@@ -2578,7 +3016,7 @@ import { tmpdir } from "os";
2578
3016
  import { dirname as dirname6, join as join4 } from "path";
2579
3017
 
2580
3018
  // ../../packages/core/src/version.ts
2581
- var NEGOTIUM_VERSION = "0.18.0";
3019
+ var NEGOTIUM_VERSION = "0.18.2";
2582
3020
 
2583
3021
  // ../../packages/core/src/agents/codex-native-multi-agent.ts
2584
3022
  var moduleRequire = createRequire2(import.meta.url);
@@ -4452,4 +4890,4 @@ export {
4452
4890
  buildClaudeDisallowedTools
4453
4891
  };
4454
4892
 
4455
- //# debugId=0D1535C9F423BF3D64756E2164756E21
4893
+ //# debugId=CAE7C2B6A4FAB8F464756E2164756E21