negotium 0.3.11 → 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.11";
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,
@@ -46086,10 +46169,25 @@ function option2(args, name) {
46086
46169
  const index = args.indexOf(`--${name}`);
46087
46170
  return index >= 0 ? args[index + 1]?.trim() : undefined;
46088
46171
  }
46089
- function localNodeNameDefault() {
46090
- const normalized = hostname2().trim().toLowerCase().replace(/\.local$/, "").replace(/[^a-z0-9._-]+/g, "-").replace(/^[-._]+|[-._]+$/g, "").slice(0, 32).replace(/[-._]+$/g, "");
46172
+ function normalizeNodeNameCandidate(raw) {
46173
+ const normalized = raw.trim().toLowerCase().replace(/\.local$/, "").replace(/[^a-z0-9._-]+/g, "-").replace(/^[-._]+|[-._]+$/g, "").slice(0, 32).replace(/[-._]+$/g, "");
46091
46174
  return normalized || undefined;
46092
46175
  }
46176
+ function localNodeNameDefault() {
46177
+ const configured = process.env.NEGOTIUM_NODE_NAME?.trim();
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());
46190
+ }
46093
46191
  async function confirmEnrollment(message) {
46094
46192
  if (!process.stdin.isTTY)
46095
46193
  return false;
@@ -46122,13 +46220,12 @@ async function joinCommand(args) {
46122
46220
  try {
46123
46221
  const invite = parseEnrollmentInvite(code);
46124
46222
  const resuming = isEnrollmentPending(invite);
46125
- let nodeName = option2(args, "name") || localNodeNameDefault();
46223
+ const nodeName = option2(args, "name") || localNodeNameDefault();
46126
46224
  if (resuming) {
46127
46225
  console.log(`Resuming interrupted Otium enrollment with ${invite.central}`);
46128
46226
  } else {
46129
46227
  const preview = await previewEnrollment(invite);
46130
46228
  const workspace = preview.preview?.workspace;
46131
- nodeName ||= preview.preview?.suggestedNodeName || undefined;
46132
46229
  console.log(`Otium workspace: ${workspace?.name ?? workspace?.slug ?? workspace?.id}`);
46133
46230
  console.log(` central: ${invite.central}`);
46134
46231
  console.log(` transport: ${preview.preview?.transport ?? "relay"}`);
@@ -46181,6 +46278,7 @@ async function joinCommand(args) {
46181
46278
  console.log("\nnext: `negotium-otium serve` (mounts the otium peer routes automatically)");
46182
46279
  }
46183
46280
  var init_join_cli = __esm(async () => {
46281
+ await init_src();
46184
46282
  await init_central();
46185
46283
  await init_enrollment();
46186
46284
  await init_join();
@@ -47227,4 +47325,4 @@ switch (command) {
47227
47325
  }
47228
47326
  }
47229
47327
 
47230
- //# debugId=4271BDC4F3BBFD0764756E2164756E21
47328
+ //# debugId=28B32B58D5A6FD9664756E2164756E21