negotium 0.6.13 → 0.6.15

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/main.js CHANGED
@@ -3708,7 +3708,7 @@ var exports_version = {};
3708
3708
  __export(exports_version, {
3709
3709
  NEGOTIUM_VERSION: () => NEGOTIUM_VERSION
3710
3710
  });
3711
- var NEGOTIUM_VERSION = "0.6.13";
3711
+ var NEGOTIUM_VERSION = "0.6.15";
3712
3712
 
3713
3713
  // ../../packages/core/src/agents/codex-native-multi-agent.ts
3714
3714
  import { spawn as spawn2 } from "child_process";
@@ -24195,6 +24195,7 @@ __export(exports_node_host, {
24195
24195
  writeDecisionGraphSvg: () => writeDecisionGraphSvg,
24196
24196
  upsertTopic: () => upsertTopic,
24197
24197
  updateTopicSettings: () => updateTopicSettings,
24198
+ updateApiMessageText: () => updateApiMessageText,
24198
24199
  topicService: () => topicService,
24199
24200
  switchTopicModel: () => switchTopicModel,
24200
24201
  switchTopicEffort: () => switchTopicEffort,
@@ -24215,6 +24216,8 @@ __export(exports_node_host, {
24215
24216
  setFileHooks: () => setFileHooks,
24216
24217
  setCuaRsMcpPort: () => setCuaRsMcpPort,
24217
24218
  setBashrsCompletionSink: () => setBashrsCompletionSink,
24219
+ setApiTopicConfig: () => setApiTopicConfig,
24220
+ setApiMessageReactions: () => setApiMessageReactions,
24218
24221
  saveVaultEntry: () => saveVaultEntry,
24219
24222
  runtimeBus: () => runtimeBus,
24220
24223
  runShutdown: () => runShutdown,
@@ -24245,6 +24248,7 @@ __export(exports_node_host, {
24245
24248
  getPortableTopicVisual: () => getPortableTopicVisual,
24246
24249
  getLastMessagePreviews: () => getLastMessagePreviews,
24247
24250
  getGlobalAiName: () => getGlobalAiName,
24251
+ getApiTopicConfig: () => getApiTopicConfig,
24248
24252
  getApiMessage: () => getApiMessage,
24249
24253
  flushBashrsCompletions: () => flushBashrsCompletions,
24250
24254
  executeVaultCommand: () => executeVaultCommand,
@@ -24300,6 +24304,7 @@ var init_node_host = __esm(async () => {
24300
24304
  await init_turn_runner();
24301
24305
  await init_visual_store();
24302
24306
  await init_api_messages();
24307
+ await init_api_topic_config();
24303
24308
  await init_api_topics();
24304
24309
  await init_app_settings();
24305
24310
  await init_decisions();
@@ -32804,6 +32809,8 @@ function createNodeControlHandler(options) {
32804
32809
  "canonical-visual-read",
32805
32810
  "canonical-topic-list",
32806
32811
  "canonical-topic-create",
32812
+ "canonical-topic-create-config",
32813
+ "canonical-topic-config",
32807
32814
  "canonical-topic-derive",
32808
32815
  "canonical-manager-topic",
32809
32816
  ...process.env.NEGOTIUM_CRON === "0" ? [] : ["scheduler.cron.gateway.v1"],
@@ -33098,7 +33105,61 @@ function createNodeControlHandler(options) {
33098
33105
  const result = listThreadMessages(topicId, rootId);
33099
33106
  return Response.json({ ok: true, v: NODE_RUNTIME_CONTRACT_VERSION, ...result });
33100
33107
  }
33108
+ const runtimeSystemMessageMatch = runtimePath.match(/^\/topics\/([^/]+)\/messages\/system$/);
33109
+ if (runtimeSystemMessageMatch && req.method === "POST") {
33110
+ const body = await bodyRecord(req);
33111
+ if (body.v !== NODE_RUNTIME_CONTRACT_VERSION)
33112
+ return jsonError(400, "Unsupported v");
33113
+ const topicId = decodeURIComponent(runtimeSystemMessageMatch[1]);
33114
+ const topic = getTopic(topicId);
33115
+ if (!topic || !topicInRequestScope(req, topic))
33116
+ return jsonError(404, "Topic not found");
33117
+ const text2 = requiredText(body.text, "text");
33118
+ const message = {
33119
+ id: randomUUID27(),
33120
+ topicId,
33121
+ authorId: "system",
33122
+ text: text2,
33123
+ kind: "system",
33124
+ createdAt: new Date().toISOString()
33125
+ };
33126
+ appendApiMessage(message, { notify: false });
33127
+ WsHub.get().broadcastMessage(topicId, message);
33128
+ return Response.json({ ok: true, v: NODE_RUNTIME_CONTRACT_VERSION, message });
33129
+ }
33101
33130
  const runtimeMessageMatch = runtimePath.match(/^\/topics\/([^/]+)\/messages\/([^/]+)$/);
33131
+ if (runtimeMessageMatch && req.method === "PATCH") {
33132
+ const body = await bodyRecord(req);
33133
+ if (body.v !== NODE_RUNTIME_CONTRACT_VERSION)
33134
+ return jsonError(400, "Unsupported v");
33135
+ const topicId = decodeURIComponent(runtimeMessageMatch[1]);
33136
+ const messageId = decodeURIComponent(runtimeMessageMatch[2]);
33137
+ const topic = getTopic(topicId);
33138
+ if (!topic || !topicInRequestScope(req, topic))
33139
+ return jsonError(404, "Topic not found");
33140
+ const actorUserId = requiredText(body.actorUserId, "actorUserId");
33141
+ const text2 = requiredText(body.text, "text");
33142
+ if (body.allowAdmin !== undefined && typeof body.allowAdmin !== "boolean") {
33143
+ return jsonError(400, "allowAdmin must be boolean");
33144
+ }
33145
+ const existing = getApiMessage(topicId, messageId);
33146
+ if (!existing || existing.deleted)
33147
+ return jsonError(404, "Message not found");
33148
+ if (existing.authorId !== actorUserId && body.allowAdmin !== true) {
33149
+ return jsonError(403, "Not allowed");
33150
+ }
33151
+ const editedAt = new Date().toISOString();
33152
+ const message = updateApiMessageText(topicId, messageId, text2, editedAt);
33153
+ if (!message)
33154
+ return jsonError(404, "Message not found");
33155
+ WsHub.get().broadcastMessageUpdated(topicId, messageId, { text: text2, editedAt });
33156
+ return Response.json({
33157
+ ok: true,
33158
+ v: NODE_RUNTIME_CONTRACT_VERSION,
33159
+ message,
33160
+ previousText: existing.text
33161
+ });
33162
+ }
33102
33163
  if (runtimeMessageMatch && req.method === "DELETE") {
33103
33164
  const body = await bodyRecord(req);
33104
33165
  if (body.v !== NODE_RUNTIME_CONTRACT_VERSION)
@@ -33128,6 +33189,45 @@ function createNodeControlHandler(options) {
33128
33189
  message
33129
33190
  });
33130
33191
  }
33192
+ const runtimeReactionMatch = runtimePath.match(/^\/topics\/([^/]+)\/messages\/([^/]+)\/reactions$/);
33193
+ if (runtimeReactionMatch && req.method === "POST") {
33194
+ const body = await bodyRecord(req);
33195
+ if (body.v !== NODE_RUNTIME_CONTRACT_VERSION)
33196
+ return jsonError(400, "Unsupported v");
33197
+ const topicId = decodeURIComponent(runtimeReactionMatch[1]);
33198
+ const messageId = decodeURIComponent(runtimeReactionMatch[2]);
33199
+ const topic = getTopic(topicId);
33200
+ if (!topic || !topicInRequestScope(req, topic))
33201
+ return jsonError(404, "Topic not found");
33202
+ const actorUserId = requiredText(body.actorUserId, "actorUserId");
33203
+ const emoji = requiredText(body.emoji, "emoji");
33204
+ if (emoji.length > 16)
33205
+ return jsonError(400, "emoji is too long");
33206
+ if (body.actorName !== undefined && typeof body.actorName !== "string") {
33207
+ return jsonError(400, "actorName must be a string");
33208
+ }
33209
+ const existing = getApiMessage(topicId, messageId);
33210
+ if (!existing || existing.deleted)
33211
+ return jsonError(404, "Message not found");
33212
+ const current3 = existing.reactions ?? [];
33213
+ const hasReaction = current3.some((reaction) => reaction.emoji === emoji && reaction.userId === actorUserId);
33214
+ const reactions = hasReaction ? current3.filter((reaction) => !(reaction.emoji === emoji && reaction.userId === actorUserId)) : [
33215
+ ...current3,
33216
+ {
33217
+ emoji,
33218
+ userId: actorUserId,
33219
+ userName: typeof body.actorName === "string" ? body.actorName : ""
33220
+ }
33221
+ ];
33222
+ const message = setApiMessageReactions(topicId, messageId, reactions);
33223
+ if (!message)
33224
+ return jsonError(404, "Message not found");
33225
+ return Response.json({
33226
+ ok: true,
33227
+ v: NODE_RUNTIME_CONTRACT_VERSION,
33228
+ reactions: message.reactions ?? []
33229
+ });
33230
+ }
33131
33231
  if (req.method === "GET" && runtimePath === "/topics") {
33132
33232
  const topics = gatewayVisibleTopics(req);
33133
33233
  const previews = getLastMessagePreviews(topics.map((topic) => topic.id));
@@ -33136,7 +33236,11 @@ function createNodeControlHandler(options) {
33136
33236
  v: NODE_RUNTIME_CONTRACT_VERSION,
33137
33237
  topics: topics.map((topic) => {
33138
33238
  const preview = previews.get(topic.id);
33139
- return preview ? { ...topic, lastMessagePreview: preview } : topic;
33239
+ return {
33240
+ ...topic,
33241
+ config: getApiTopicConfig(topic.id) ?? {},
33242
+ ...preview ? { lastMessagePreview: preview } : {}
33243
+ };
33140
33244
  }),
33141
33245
  cursor: latestRuntimeEventSeq()
33142
33246
  });
@@ -33148,16 +33252,27 @@ function createNodeControlHandler(options) {
33148
33252
  const userId = requiredText(body.userId, "userId");
33149
33253
  const title = requiredText(body.title, "title");
33150
33254
  const agent = body.agent;
33151
- if (agent !== undefined && !["claude", "codex", "maestro"].includes(String(agent))) {
33255
+ if (agent !== undefined && agent !== null && !["none", "claude", "codex", "maestro"].includes(String(agent))) {
33152
33256
  return jsonError(400, "Invalid agent");
33153
33257
  }
33258
+ const kind = body.kind ?? "agent";
33259
+ if (kind !== "agent" && kind !== "channel")
33260
+ return jsonError(400, "Invalid kind");
33261
+ if (body.model !== undefined && typeof body.model !== "string") {
33262
+ return jsonError(400, "model must be a string");
33263
+ }
33264
+ if (body.effort !== undefined && !["low", "medium", "high", "xhigh", "max"].includes(String(body.effort))) {
33265
+ return jsonError(400, "Invalid effort");
33266
+ }
33154
33267
  const topic = topicService.create({
33155
33268
  title,
33156
33269
  userId,
33157
- kind: "agent",
33270
+ kind,
33158
33271
  surface: "otium",
33159
33272
  ...requestSurfaceScope(req),
33160
- ...agent ? { agent } : {}
33273
+ ...agent === null || agent === "none" ? { agent: "none" } : agent ? { agent } : {},
33274
+ ...typeof body.model === "string" ? { model: body.model } : {},
33275
+ ...typeof body.effort === "string" ? { effort: body.effort } : {}
33161
33276
  });
33162
33277
  return Response.json({ ok: true, v: NODE_RUNTIME_CONTRACT_VERSION, topic }, { status: 201 });
33163
33278
  }
@@ -33369,13 +33484,81 @@ function createNodeControlHandler(options) {
33369
33484
  });
33370
33485
  }
33371
33486
  const runtimeTopicMatch = runtimePath.match(/^\/topics\/([^/]+)$/);
33487
+ const runtimeTopicConfigMatch = runtimePath.match(/^\/topics\/([^/]+)\/config$/);
33488
+ if (runtimeTopicConfigMatch && req.method === "GET") {
33489
+ const topicId = decodeURIComponent(runtimeTopicConfigMatch[1]);
33490
+ const topic = getTopic(topicId);
33491
+ if (!topic || !topicInRequestScope(req, topic))
33492
+ return jsonError(404, "Topic not found");
33493
+ return Response.json({
33494
+ ok: true,
33495
+ v: NODE_RUNTIME_CONTRACT_VERSION,
33496
+ topic,
33497
+ config: getApiTopicConfig(topicId) ?? {}
33498
+ });
33499
+ }
33500
+ if (runtimeTopicConfigMatch && req.method === "PATCH") {
33501
+ const body = await bodyRecord(req);
33502
+ if (body.v !== NODE_RUNTIME_CONTRACT_VERSION)
33503
+ return jsonError(400, "Unsupported v");
33504
+ const topicId = decodeURIComponent(runtimeTopicConfigMatch[1]);
33505
+ const topic = getTopic(topicId);
33506
+ if (!topic || !topicInRequestScope(req, topic))
33507
+ return jsonError(404, "Topic not found");
33508
+ const next = { ...getApiTopicConfig(topicId) ?? {} };
33509
+ for (const key of ["model", "effort"]) {
33510
+ if (!(key in body))
33511
+ continue;
33512
+ const value = body[key];
33513
+ if (value === null)
33514
+ delete next[key];
33515
+ else if (typeof value === "string" && value.trim()) {
33516
+ if (key === "effort" && !["low", "medium", "high", "xhigh", "max"].includes(value)) {
33517
+ return jsonError(400, "Invalid effort");
33518
+ }
33519
+ next[key] = value;
33520
+ } else
33521
+ return jsonError(400, `${key} must be a string or null`);
33522
+ }
33523
+ if ("mcp" in body) {
33524
+ if (body.mcp === null)
33525
+ delete next.mcp;
33526
+ else if (Array.isArray(body.mcp) && body.mcp.every((item) => typeof item === "string")) {
33527
+ next.mcp = [...new Set(body.mcp.map((item) => item.trim()).filter(Boolean))];
33528
+ } else
33529
+ return jsonError(400, "mcp must be an array of strings or null");
33530
+ }
33531
+ for (const key of ["agentLocked", "modelLocked", "effortLocked"]) {
33532
+ if (!(key in body))
33533
+ continue;
33534
+ const value = body[key];
33535
+ if (value === null || value === false)
33536
+ delete next[key];
33537
+ else if (value === true)
33538
+ next[key] = true;
33539
+ else
33540
+ return jsonError(400, `${key} must be a boolean or null`);
33541
+ }
33542
+ setApiTopicConfig(topicId, next);
33543
+ return Response.json({
33544
+ ok: true,
33545
+ v: NODE_RUNTIME_CONTRACT_VERSION,
33546
+ topic,
33547
+ config: getApiTopicConfig(topicId) ?? {}
33548
+ });
33549
+ }
33372
33550
  if (runtimeTopicMatch && req.method === "GET") {
33373
33551
  const topic = getTopic(decodeURIComponent(runtimeTopicMatch[1]));
33374
33552
  if (topic && !topicInRequestScope(req, topic))
33375
33553
  return jsonError(404, "Topic not found");
33376
33554
  if (!topic)
33377
33555
  return jsonError(404, "Topic not found");
33378
- return Response.json({ ok: true, v: NODE_RUNTIME_CONTRACT_VERSION, topic });
33556
+ return Response.json({
33557
+ ok: true,
33558
+ v: NODE_RUNTIME_CONTRACT_VERSION,
33559
+ topic,
33560
+ config: getApiTopicConfig(topic.id) ?? {}
33561
+ });
33379
33562
  }
33380
33563
  if (runtimeTopicMatch && req.method === "DELETE") {
33381
33564
  const topicId = decodeURIComponent(runtimeTopicMatch[1]);
@@ -46914,7 +47097,9 @@ function allowedRuntimePath(path, method) {
46914
47097
  return true;
46915
47098
  if (path === "/cron/scripts" || path === "/cron/jobs")
46916
47099
  return true;
46917
- if (/^\/topics\/[^/]+(\/messages|\/usage)?$/.test(path))
47100
+ if (/^\/topics\/[^/]+(\/messages|\/usage|\/config)?$/.test(path))
47101
+ return true;
47102
+ if (/^\/topics\/[^/]+\/messages\/[^/]+\/thread$/.test(path))
46918
47103
  return true;
46919
47104
  if (/^\/topics\/[^/]+\/files\/[^/]+$/.test(path))
46920
47105
  return true;
@@ -46929,11 +47114,19 @@ function allowedRuntimePath(path, method) {
46929
47114
  return true;
46930
47115
  if (/^\/topics\/[^/]+\/messages\/[^/]+\/ask-answer$/.test(path))
46931
47116
  return true;
47117
+ if (/^\/topics\/[^/]+\/messages\/system$/.test(path))
47118
+ return true;
47119
+ if (/^\/topics\/[^/]+\/messages\/[^/]+\/reactions$/.test(path))
47120
+ return true;
46932
47121
  return /^\/topics\/[^/]+\/(abort|session\/(reset|compact))$/.test(path);
46933
47122
  }
46934
47123
  if (method === "DELETE" || method === "PATCH") {
46935
47124
  if (method === "DELETE" && /^\/topics\/[^/]+\/messages\/[^/]+$/.test(path))
46936
47125
  return true;
47126
+ if (method === "PATCH" && /^\/topics\/[^/]+\/messages\/[^/]+$/.test(path))
47127
+ return true;
47128
+ if (method === "PATCH" && /^\/topics\/[^/]+\/config$/.test(path))
47129
+ return true;
46937
47130
  return /^\/topics\/[^/]+$/.test(path) || /^\/cron\/jobs\/[^/]+$/.test(path);
46938
47131
  }
46939
47132
  return false;
@@ -48984,4 +49177,4 @@ switch (command) {
48984
49177
  }
48985
49178
  }
48986
49179
 
48987
- //# debugId=6567233F28C2818664756E2164756E21
49180
+ //# debugId=B2409FC66A8292B364756E2164756E21