negotium 0.3.12 → 0.3.13

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
@@ -1869,7 +1869,7 @@ var exports_version = {};
1869
1869
  __export(exports_version, {
1870
1870
  NEGOTIUM_VERSION: () => NEGOTIUM_VERSION
1871
1871
  });
1872
- var NEGOTIUM_VERSION = "0.3.12";
1872
+ var NEGOTIUM_VERSION = "0.3.13";
1873
1873
 
1874
1874
  // ../../packages/core/src/agents/codex-native-multi-agent.ts
1875
1875
  import { spawn } from "child_process";
@@ -20499,7 +20499,8 @@ function gatewayPayloadHash(params, requestId, actorUserId) {
20499
20499
  requestId,
20500
20500
  params.allowAutoContinue ?? true,
20501
20501
  params.respond ?? true,
20502
- params.threadRootId ?? null
20502
+ params.threadRootId ?? null,
20503
+ params.attachments ?? []
20503
20504
  ])).digest("hex");
20504
20505
  }
20505
20506
  function submitRuntimeGatewayTurn(params) {
@@ -20512,6 +20513,10 @@ function submitRuntimeGatewayTurn(params) {
20512
20513
  return duplicateResult(existing, params, requestId, actorUserId, payloadHash);
20513
20514
  }
20514
20515
  const createdAt = new Date().toISOString();
20516
+ const attachments2 = params.attachments?.map(resolveAttachmentByFileId);
20517
+ if (attachments2?.some((attachment) => !attachment)) {
20518
+ throw new Error("gateway attachment could not be resolved");
20519
+ }
20515
20520
  const message = {
20516
20521
  id: randomUUID17(),
20517
20522
  topicId: params.topic.id,
@@ -20520,6 +20525,7 @@ function submitRuntimeGatewayTurn(params) {
20520
20525
  sourceAdapter: "runtime-gateway",
20521
20526
  sourceMessageId: params.clientMessageId,
20522
20527
  text: params.text,
20528
+ ...attachments2?.length ? { attachments: attachments2 } : {},
20523
20529
  ...params.threadRootId ? { threadRootId: params.threadRootId } : {},
20524
20530
  createdAt
20525
20531
  };
@@ -20545,7 +20551,8 @@ function submitRuntimeGatewayTurn(params) {
20545
20551
  {
20546
20552
  prompt: params.text,
20547
20553
  actorUserId,
20548
- ...params.actorLabel ? { actorLabel: params.actorLabel } : {}
20554
+ ...params.actorLabel ? { actorLabel: params.actorLabel } : {},
20555
+ ...params.attachments?.length ? { attachments: params.attachments } : {}
20549
20556
  }
20550
20557
  ],
20551
20558
  allowAutoContinue: params.allowAutoContinue ?? true,
@@ -20592,6 +20599,7 @@ function submitRuntimeGatewayTurn(params) {
20592
20599
  }
20593
20600
  var RuntimeGatewayIdempotencyConflictError;
20594
20601
  var init_submit_runtime_gateway_turn = __esm(async () => {
20602
+ init_file_hooks();
20595
20603
  await init_api_messages();
20596
20604
  await init_api_topics();
20597
20605
  await init_forum_db();
@@ -29814,6 +29822,46 @@ class NodeFileStore {
29814
29822
  storeLocalFileAsUpload: (absPath, access = {}) => this.store(absPath, access),
29815
29823
  deleteFilesForTopic: (topicId) => this.deleteForTopic(topicId)
29816
29824
  };
29825
+ async storeUploadedFile(fileId, file, access) {
29826
+ if (!FILE_ID_RE.test(fileId) || file.size > MAX_NODE_UPLOAD_BYTES)
29827
+ return null;
29828
+ this.#ensureDir();
29829
+ const filename = basename10(file.name || "upload") || "upload";
29830
+ const extension = safeExtension(filename);
29831
+ const mimeType = file.type || MIME_BY_EXT2[extension] || "application/octet-stream";
29832
+ const existing = this.#metadata(fileId);
29833
+ if (existing) {
29834
+ const matches = existing.filename === filename && existing.mimeType === mimeType && existing.sizeBytes === file.size && existing.ownerUserId === access.ownerUserId && existing.topicId === access.topicId && existsSync31(join40(this.uploadDir, existing.savedName));
29835
+ return matches ? this.#attachment(fileId, existing) : null;
29836
+ }
29837
+ const savedName = `${fileId}${extension}`;
29838
+ const savedPath = join40(this.uploadDir, savedName);
29839
+ try {
29840
+ const sizeBytes = await Bun.write(savedPath, file);
29841
+ if (sizeBytes !== file.size)
29842
+ throw new Error("incomplete upload write");
29843
+ const metadata = {
29844
+ filename,
29845
+ mimeType,
29846
+ sizeBytes,
29847
+ savedName,
29848
+ ownerUserId: access.ownerUserId,
29849
+ topicId: access.topicId,
29850
+ visibility: "private"
29851
+ };
29852
+ writeFileSync23(this.#metadataPath(fileId), JSON.stringify(metadata), { mode: 384 });
29853
+ return this.#attachment(fileId, metadata);
29854
+ } catch (error2) {
29855
+ rmSync9(savedPath, { force: true });
29856
+ rmSync9(this.#metadataPath(fileId), { force: true });
29857
+ logger.warn({ err: error2, fileId }, "node files: failed to store gateway upload");
29858
+ return null;
29859
+ }
29860
+ }
29861
+ allows(fileId, access) {
29862
+ const metadata = this.#metadata(fileId);
29863
+ return Boolean(metadata && metadata.ownerUserId === access.ownerUserId && metadata.topicId === access.topicId && existsSync31(join40(this.uploadDir, metadata.savedName)));
29864
+ }
29817
29865
  store(absPath, access = {}) {
29818
29866
  this.#ensureDir();
29819
29867
  const fileId = randomUUID24();
@@ -29822,7 +29870,7 @@ class NodeFileStore {
29822
29870
  const savedPath = join40(this.uploadDir, savedName);
29823
29871
  try {
29824
29872
  const stats = statSync18(absPath);
29825
- if (!stats.isFile() || stats.size > MAX_UPLOAD_BYTES)
29873
+ if (!stats.isFile() || stats.size > MAX_NODE_UPLOAD_BYTES)
29826
29874
  return null;
29827
29875
  const metadata = {
29828
29876
  filename: basename10(absPath),
@@ -29875,10 +29923,10 @@ class NodeFileStore {
29875
29923
  }
29876
29924
  }
29877
29925
  }
29878
- var MAX_UPLOAD_BYTES, FILE_ID_RE, MIME_BY_EXT2, nodeFileStore;
29926
+ var MAX_NODE_UPLOAD_BYTES, FILE_ID_RE, MIME_BY_EXT2, nodeFileStore;
29879
29927
  var init_files = __esm(async () => {
29880
29928
  await init_node_host();
29881
- MAX_UPLOAD_BYTES = 2 * 1024 * 1024 * 1024;
29929
+ MAX_NODE_UPLOAD_BYTES = 2 * 1024 * 1024 * 1024;
29882
29930
  FILE_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
29883
29931
  MIME_BY_EXT2 = {
29884
29932
  ".csv": "text/csv",
@@ -30213,11 +30261,34 @@ function createNodeControlHandler(options) {
30213
30261
  "canonical-history-import",
30214
30262
  "canonical-topic-abort",
30215
30263
  "canonical-session-reset",
30216
- "canonical-session-compact"
30264
+ "canonical-session-compact",
30265
+ "canonical-input-files"
30217
30266
  ],
30218
30267
  cursor: latestRuntimeEventSeq()
30219
30268
  });
30220
30269
  }
30270
+ if (req.method === "POST" && runtimePath === "/input-files") {
30271
+ const form = await req.formData();
30272
+ const topicId = requiredText(form.get("topicId"), "topicId");
30273
+ const userId = requiredText(form.get("userId"), "userId");
30274
+ const fileId = requiredText(form.get("fileId"), "fileId");
30275
+ const file = form.get("file");
30276
+ if (!(file instanceof File))
30277
+ throw new ControlRequestError("file is required");
30278
+ if (file.size > MAX_NODE_UPLOAD_BYTES)
30279
+ return jsonError(413, "File too large");
30280
+ const topic = getTopic(topicId);
30281
+ if (!topic || !topicInRequestScope(req, topic) || !topic.participants.some((participant) => participant.userId === userId)) {
30282
+ return jsonError(404, "Topic not found");
30283
+ }
30284
+ const attachment = await nodeFileStore.storeUploadedFile(fileId, file, {
30285
+ topicId,
30286
+ ownerUserId: userId
30287
+ });
30288
+ if (!attachment)
30289
+ return jsonError(409, "File id is already bound to another upload");
30290
+ return Response.json({ ok: true, v: NODE_RUNTIME_CONTRACT_VERSION, attachment }, { status: 201 });
30291
+ }
30221
30292
  if (req.method === "POST" && runtimePath === "/turns") {
30222
30293
  const body = await bodyRecord(req);
30223
30294
  if (body.v !== NODE_RUNTIME_CONTRACT_VERSION)
@@ -30231,16 +30302,27 @@ function createNodeControlHandler(options) {
30231
30302
  const actorUserId = body.actorUserId === undefined ? undefined : requiredText(body.actorUserId, "actorUserId");
30232
30303
  const actorLabel = body.actorLabel === undefined ? undefined : requiredText(body.actorLabel, "actorLabel");
30233
30304
  const vaultUserId = body.vaultUserId === undefined ? undefined : requiredText(body.vaultUserId, "vaultUserId");
30234
- const text2 = requiredText(body.text, "text");
30305
+ if (typeof body.text !== "string")
30306
+ throw new ControlRequestError("text is required");
30307
+ const text2 = body.text;
30235
30308
  const clientMessageId = requiredText(body.clientMessageId, "clientMessageId");
30236
30309
  const requestId = body.requestId === undefined ? undefined : requiredText(body.requestId, "requestId");
30237
30310
  const threadRootId = body.threadRootId === undefined ? undefined : requiredText(body.threadRootId, "threadRootId");
30311
+ const attachments2 = body.attachments === undefined ? [] : Array.isArray(body.attachments) && body.attachments.every((value) => typeof value === "string" && value.trim()) ? body.attachments : (() => {
30312
+ throw new ControlRequestError("attachments must be an array of file ids");
30313
+ })();
30314
+ if (!text2.trim() && attachments2.length === 0) {
30315
+ throw new ControlRequestError("text or attachments required");
30316
+ }
30238
30317
  const topic = getTopic(topicId);
30239
30318
  if (!topic)
30240
30319
  return jsonError(404, "Topic not found");
30241
30320
  if (!topic.participants.some((participant) => participant.userId === userId)) {
30242
30321
  return jsonError(404, "Topic not found");
30243
30322
  }
30323
+ if (attachments2.some((fileId) => !nodeFileStore.allows(fileId, { topicId, ownerUserId: userId }))) {
30324
+ return jsonError(404, "Attachment not found");
30325
+ }
30244
30326
  if (threadRootId) {
30245
30327
  const root = getApiMessage(topicId, threadRootId);
30246
30328
  if (!root || root.deleted || root.threadRootId) {
@@ -30258,6 +30340,7 @@ function createNodeControlHandler(options) {
30258
30340
  requestId,
30259
30341
  allowAutoContinue: body.allowAutoContinue !== false,
30260
30342
  respond: body.respond !== false,
30343
+ ...attachments2.length ? { attachments: attachments2 } : {},
30261
30344
  ...threadRootId ? { threadRootId } : {}
30262
30345
  });
30263
30346
  return Response.json({
@@ -45240,7 +45323,7 @@ function allowedRuntimePath(path, method) {
45240
45323
  return /^\/topics\/[^/]+(\/messages)?$/.test(path);
45241
45324
  }
45242
45325
  if (method === "POST") {
45243
- if (path === "/turns")
45326
+ if (path === "/turns" || path === "/input-files")
45244
45327
  return true;
45245
45328
  if (path === "/topics")
45246
45329
  return true;
@@ -45359,7 +45442,7 @@ function localCapabilities() {
45359
45442
  runtimeVersion: RUNTIME_VERSION,
45360
45443
  features: {
45361
45444
  remoteAsk: true,
45362
- inputFiles: false,
45445
+ inputFiles: true,
45363
45446
  outputFiles: true,
45364
45447
  visualBridge: true,
45365
45448
  askUserBridge: true,
@@ -46092,7 +46175,18 @@ function normalizeNodeNameCandidate(raw) {
46092
46175
  }
46093
46176
  function localNodeNameDefault() {
46094
46177
  const configured = process.env.NEGOTIUM_NODE_NAME?.trim();
46095
- return configured && normalizeNodeNameCandidate(configured) || normalizeNodeNameCandidate(hostname2());
46178
+ if (configured) {
46179
+ const normalized = normalizeNodeNameCandidate(configured);
46180
+ if (normalized)
46181
+ return normalized;
46182
+ }
46183
+ const aiName = getGlobalAiName().trim();
46184
+ if (aiName && aiName !== DEFAULT_AI_NAME) {
46185
+ const normalized = normalizeNodeNameCandidate(aiName);
46186
+ if (normalized)
46187
+ return normalized;
46188
+ }
46189
+ return normalizeNodeNameCandidate(hostname2());
46096
46190
  }
46097
46191
  async function confirmEnrollment(message) {
46098
46192
  if (!process.stdin.isTTY)
@@ -46126,13 +46220,12 @@ async function joinCommand(args) {
46126
46220
  try {
46127
46221
  const invite = parseEnrollmentInvite(code);
46128
46222
  const resuming = isEnrollmentPending(invite);
46129
- let nodeName = option2(args, "name") || localNodeNameDefault();
46223
+ const nodeName = option2(args, "name") || localNodeNameDefault();
46130
46224
  if (resuming) {
46131
46225
  console.log(`Resuming interrupted Otium enrollment with ${invite.central}`);
46132
46226
  } else {
46133
46227
  const preview = await previewEnrollment(invite);
46134
46228
  const workspace = preview.preview?.workspace;
46135
- nodeName ||= preview.preview?.suggestedNodeName || undefined;
46136
46229
  console.log(`Otium workspace: ${workspace?.name ?? workspace?.slug ?? workspace?.id}`);
46137
46230
  console.log(` central: ${invite.central}`);
46138
46231
  console.log(` transport: ${preview.preview?.transport ?? "relay"}`);
@@ -46185,6 +46278,7 @@ async function joinCommand(args) {
46185
46278
  console.log("\nnext: `negotium-otium serve` (mounts the otium peer routes automatically)");
46186
46279
  }
46187
46280
  var init_join_cli = __esm(async () => {
46281
+ await init_src();
46188
46282
  await init_central();
46189
46283
  await init_enrollment();
46190
46284
  await init_join();
@@ -47231,4 +47325,4 @@ switch (command) {
47231
47325
  }
47232
47326
  }
47233
47327
 
47234
- //# debugId=555568B5D7BF77D064756E2164756E21
47328
+ //# debugId=28B32B58D5A6FD9664756E2164756E21