hivenet 0.4.2 → 0.6.0

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.
@@ -1389,10 +1389,10 @@ function mergeDefs(...defs) {
1389
1389
  function cloneDef(schema) {
1390
1390
  return mergeDefs(schema._zod.def);
1391
1391
  }
1392
- function getElementAtPath(obj, path5) {
1393
- if (!path5)
1392
+ function getElementAtPath(obj, path6) {
1393
+ if (!path6)
1394
1394
  return obj;
1395
- return path5.reduce((acc, key) => acc?.[key], obj);
1395
+ return path6.reduce((acc, key) => acc?.[key], obj);
1396
1396
  }
1397
1397
  function promiseAllObject(promisesObj) {
1398
1398
  const keys = Object.keys(promisesObj);
@@ -1801,11 +1801,11 @@ function explicitlyAborted(x, startIndex = 0) {
1801
1801
  }
1802
1802
  return false;
1803
1803
  }
1804
- function prefixIssues(path5, issues) {
1804
+ function prefixIssues(path6, issues) {
1805
1805
  return issues.map((iss) => {
1806
1806
  var _a3;
1807
1807
  (_a3 = iss).path ?? (_a3.path = []);
1808
- iss.path.unshift(path5);
1808
+ iss.path.unshift(path6);
1809
1809
  return iss;
1810
1810
  });
1811
1811
  }
@@ -1952,16 +1952,16 @@ function flattenError(error51, mapper = (issue2) => issue2.message) {
1952
1952
  }
1953
1953
  function formatError(error51, mapper = (issue2) => issue2.message) {
1954
1954
  const fieldErrors = { _errors: [] };
1955
- const processError = (error52, path5 = []) => {
1955
+ const processError = (error52, path6 = []) => {
1956
1956
  for (const issue2 of error52.issues) {
1957
1957
  if (issue2.code === "invalid_union" && issue2.errors.length) {
1958
- issue2.errors.map((issues) => processError({ issues }, [...path5, ...issue2.path]));
1958
+ issue2.errors.map((issues) => processError({ issues }, [...path6, ...issue2.path]));
1959
1959
  } else if (issue2.code === "invalid_key") {
1960
- processError({ issues: issue2.issues }, [...path5, ...issue2.path]);
1960
+ processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
1961
1961
  } else if (issue2.code === "invalid_element") {
1962
- processError({ issues: issue2.issues }, [...path5, ...issue2.path]);
1962
+ processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
1963
1963
  } else {
1964
- const fullpath = [...path5, ...issue2.path];
1964
+ const fullpath = [...path6, ...issue2.path];
1965
1965
  if (fullpath.length === 0) {
1966
1966
  fieldErrors._errors.push(mapper(issue2));
1967
1967
  } else {
@@ -1988,17 +1988,17 @@ function formatError(error51, mapper = (issue2) => issue2.message) {
1988
1988
  }
1989
1989
  function treeifyError(error51, mapper = (issue2) => issue2.message) {
1990
1990
  const result = { errors: [] };
1991
- const processError = (error52, path5 = []) => {
1991
+ const processError = (error52, path6 = []) => {
1992
1992
  var _a3, _b;
1993
1993
  for (const issue2 of error52.issues) {
1994
1994
  if (issue2.code === "invalid_union" && issue2.errors.length) {
1995
- issue2.errors.map((issues) => processError({ issues }, [...path5, ...issue2.path]));
1995
+ issue2.errors.map((issues) => processError({ issues }, [...path6, ...issue2.path]));
1996
1996
  } else if (issue2.code === "invalid_key") {
1997
- processError({ issues: issue2.issues }, [...path5, ...issue2.path]);
1997
+ processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
1998
1998
  } else if (issue2.code === "invalid_element") {
1999
- processError({ issues: issue2.issues }, [...path5, ...issue2.path]);
1999
+ processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
2000
2000
  } else {
2001
- const fullpath = [...path5, ...issue2.path];
2001
+ const fullpath = [...path6, ...issue2.path];
2002
2002
  if (fullpath.length === 0) {
2003
2003
  result.errors.push(mapper(issue2));
2004
2004
  continue;
@@ -2030,8 +2030,8 @@ function treeifyError(error51, mapper = (issue2) => issue2.message) {
2030
2030
  }
2031
2031
  function toDotPath(_path) {
2032
2032
  const segs = [];
2033
- const path5 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
2034
- for (const seg of path5) {
2033
+ const path6 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
2034
+ for (const seg of path6) {
2035
2035
  if (typeof seg === "number")
2036
2036
  segs.push(`[${seg}]`);
2037
2037
  else if (typeof seg === "symbol")
@@ -14723,13 +14723,13 @@ function resolveRef(ref, ctx) {
14723
14723
  if (!ref.startsWith("#")) {
14724
14724
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
14725
14725
  }
14726
- const path5 = ref.slice(1).split("/").filter(Boolean);
14727
- if (path5.length === 0) {
14726
+ const path6 = ref.slice(1).split("/").filter(Boolean);
14727
+ if (path6.length === 0) {
14728
14728
  return ctx.rootSchema;
14729
14729
  }
14730
14730
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
14731
- if (path5[0] === defsKey) {
14732
- const key = path5[1];
14731
+ if (path6[0] === defsKey) {
14732
+ const key = path6[1];
14733
14733
  if (!key || !ctx.defs[key]) {
14734
14734
  throw new Error(`Reference not found: ${ref}`);
14735
14735
  }
@@ -15138,6 +15138,102 @@ function date4(params) {
15138
15138
  config(en_default());
15139
15139
 
15140
15140
  // ../spec/dist/index.js
15141
+ var MAX_ATTACHMENT_BYTES = 20 * 1024 * 1024;
15142
+ var MAX_ATTACHMENTS = 10;
15143
+ var MAX_ATTACHMENT_DESCRIPTION_LENGTH = 1e3;
15144
+ var ATTACHMENT_UPLOAD_PATH = "/v1/attachments";
15145
+ var attachmentDescriptionSchema = external_exports.string().trim().min(1).max(MAX_ATTACHMENT_DESCRIPTION_LENGTH).describe("Optional plain text: what this file shows and why it matters.");
15146
+ var attachmentIdSchema = external_exports.string().regex(/^att_[a-f0-9]{32}$/);
15147
+ var ATTACHMENT_TARGET_TYPES = [
15148
+ "event",
15149
+ "issue",
15150
+ "question",
15151
+ "eval",
15152
+ "investigation",
15153
+ "investigation_question",
15154
+ "investigation_reply"
15155
+ ];
15156
+ var attachmentTargetSchema = external_exports.object({
15157
+ type: external_exports.enum(ATTACHMENT_TARGET_TYPES),
15158
+ id: external_exports.string().min(1).max(64)
15159
+ });
15160
+ var attachmentManifestSchema = external_exports.object({
15161
+ filename: external_exports.string().min(1).max(180).refine(
15162
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: Reject control characters in filenames and HTTP headers.
15163
+ (name) => !/[\\/\u0000-\u001f\u007f]/.test(name) && name !== "." && name !== "..",
15164
+ "Use a filename without directories or control characters."
15165
+ ),
15166
+ mediaType: external_exports.string().max(127).regex(/^[a-z0-9!#$&^_.+-]+\/[a-z0-9!#$&^_.+-]+$/),
15167
+ size: external_exports.number().int().min(1).max(MAX_ATTACHMENT_BYTES),
15168
+ sha256: external_exports.string().regex(/^[a-f0-9]{64}$/)
15169
+ });
15170
+ var prepareAttachmentSchema = attachmentManifestSchema.extend({
15171
+ to: external_exports.string().min(1).max(253).optional(),
15172
+ scope: external_exports.enum(["reporter", "owner"]).default("reporter"),
15173
+ /** Self-reported agent name for reporter uploads. */
15174
+ uploader: external_exports.string().min(1).max(128).optional()
15175
+ });
15176
+ var preparedAttachmentSchema = external_exports.object({
15177
+ reference: external_exports.object({
15178
+ id: attachmentIdSchema,
15179
+ token: external_exports.string().regex(/^hva_[a-f0-9]{64}$/)
15180
+ }),
15181
+ uploadUrl: external_exports.url(),
15182
+ expiresAt: external_exports.string()
15183
+ });
15184
+ var attachmentReferenceSchema = preparedAttachmentSchema.shape.reference.extend({
15185
+ description: attachmentDescriptionSchema.optional()
15186
+ });
15187
+ var attachmentReferencesSchema = external_exports.array(attachmentReferenceSchema).max(MAX_ATTACHMENTS).refine(
15188
+ (refs) => new Set(refs.map((ref) => ref.id)).size === refs.length,
15189
+ "Each file can be attached once."
15190
+ );
15191
+ var attachFilesSchema = external_exports.object({
15192
+ target: attachmentTargetSchema,
15193
+ attachments: attachmentReferencesSchema.min(1)
15194
+ });
15195
+ var attachmentSchema = attachmentManifestSchema.extend({
15196
+ id: attachmentIdSchema,
15197
+ createdAt: external_exports.string(),
15198
+ source: external_exports.enum(["reporter", "owner"]),
15199
+ /** Reporter names are self-reported; owner uploads are authenticated. */
15200
+ uploader: external_exports.string().nullable(),
15201
+ /** The record the file belongs to — a report when shown on its issue or eval. */
15202
+ target: attachmentTargetSchema,
15203
+ description: attachmentDescriptionSchema.optional()
15204
+ });
15205
+ var deliveredAttachmentSchema = attachmentSchema.extend({
15206
+ url: external_exports.url(),
15207
+ expiresAt: external_exports.string()
15208
+ });
15209
+ var MEDIA_TYPES = {
15210
+ png: "image/png",
15211
+ jpg: "image/jpeg",
15212
+ jpeg: "image/jpeg",
15213
+ gif: "image/gif",
15214
+ webp: "image/webp",
15215
+ svg: "image/svg+xml",
15216
+ mp4: "video/mp4",
15217
+ webm: "video/webm",
15218
+ mp3: "audio/mpeg",
15219
+ m4a: "audio/mp4",
15220
+ wav: "audio/wav",
15221
+ ogg: "audio/ogg",
15222
+ pdf: "application/pdf",
15223
+ json: "application/json",
15224
+ jsonl: "application/x-ndjson",
15225
+ ndjson: "application/x-ndjson",
15226
+ txt: "text/plain",
15227
+ log: "text/plain",
15228
+ md: "text/markdown",
15229
+ csv: "text/csv",
15230
+ zip: "application/zip",
15231
+ html: "text/html"
15232
+ };
15233
+ function mediaTypeForFilename(filename) {
15234
+ const extension = /\.([^./\\]+)$/.exec(filename)?.[1]?.toLowerCase();
15235
+ return extension && MEDIA_TYPES[extension] || "application/octet-stream";
15236
+ }
15141
15237
  var PROTOCOL_VERSION = 1;
15142
15238
  var MAX_FEEDBACK_LENGTH = 5e3;
15143
15239
  var MAX_SUBJECT_LENGTH = 500;
@@ -15157,10 +15253,18 @@ var FEEDBACK_CATEGORIES = [
15157
15253
  "cli",
15158
15254
  "api",
15159
15255
  "model",
15160
- "eval",
15161
15256
  "ux",
15162
15257
  "other"
15163
15258
  ];
15259
+ var createQuestionInputSchema = external_exports.object({
15260
+ prompt: external_exports.string().trim().min(8).max(500).describe('The question, e.g. "If you used <feature>: what broke or was missing?"'),
15261
+ category: external_exports.enum(FEEDBACK_CATEGORIES).optional().describe("Only ask agents whose submission carries this category."),
15262
+ attachments: attachmentReferencesSchema.optional().describe("Uploaded files, each {id, token, description?}; every agent asked can open them."),
15263
+ idempotencyKey: external_exports.string().regex(/^[A-Za-z0-9_-]{8,64}$/).optional().describe(
15264
+ "Same key + same project = the same question, so a retried publish never duplicates."
15265
+ )
15266
+ });
15267
+ var LEGACY_CATEGORIES = ["eval"];
15164
15268
  var EVENT_STATUSES = [
15165
15269
  "new",
15166
15270
  "seen",
@@ -15169,10 +15273,20 @@ var EVENT_STATUSES = [
15169
15273
  "wont_fix",
15170
15274
  "spam"
15171
15275
  ];
15172
- var ISSUE_STATUSES = ["open", "in_progress", "resolved", "wont_fix", "spam"];
15276
+ var ISSUE_STATUSES = [
15277
+ "open",
15278
+ "in_progress",
15279
+ "needs_review",
15280
+ "resolved",
15281
+ "wont_fix",
15282
+ "spam"
15283
+ ];
15284
+ var issueStatusSchema = external_exports.enum(ISSUE_STATUSES);
15285
+ var EVAL_STATUSES = ["proposed", "accepted", "archived"];
15173
15286
  var VENDOR_CATEGORY_PATTERN = /^x-[a-z0-9][a-z0-9-]{0,38}$/;
15174
15287
  var categorySchema = external_exports.union([
15175
15288
  external_exports.enum(FEEDBACK_CATEGORIES),
15289
+ external_exports.enum(LEGACY_CATEGORIES),
15176
15290
  external_exports.string().regex(VENDOR_CATEGORY_PATTERN, "vendor categories must match x-<kebab-case>")
15177
15291
  ]);
15178
15292
  var sentimentSchema = external_exports.enum(["positive", "negative", "neutral"]);
@@ -15234,6 +15348,10 @@ var feedbackEventSchema = external_exports.object({
15234
15348
  author: authorSchema.optional(),
15235
15349
  thread: external_exports.object({ id: external_exports.string().regex(THREAD_ID_PATTERN) }),
15236
15350
  eval: evalReportSchema.optional(),
15351
+ /** Id of an owner/system question this submission answers (from a prior response's `ask`). */
15352
+ question: external_exports.string().max(64).optional(),
15353
+ /** Uploaded files, committed with the event or not at all. Bytes never travel in feedback JSON. */
15354
+ attachments: attachmentReferencesSchema.optional(),
15237
15355
  context: feedbackContextSchema.optional(),
15238
15356
  metadata: metadataSchema.optional(),
15239
15357
  /** Client-minted retry key: same key + same project = same stored event. */
@@ -15244,6 +15362,31 @@ var feedbackEventSchema = external_exports.object({
15244
15362
  }),
15245
15363
  consent: external_exports.object({ telemetry: external_exports.boolean() })
15246
15364
  });
15365
+ var knownIssueSchema = external_exports.object({
15366
+ title: external_exports.string().max(120),
15367
+ /** wont_fix is disclosed only when the owner attached a closing note. */
15368
+ status: external_exports.enum(["open", "wont_fix"]),
15369
+ /** This very report flipped a resolved issue back open — already recorded. */
15370
+ reopened: external_exports.boolean().optional(),
15371
+ /** Total reports on this issue, including this one. */
15372
+ reports: external_exports.number().int().positive(),
15373
+ /** The owner's closing note, verbatim — their explanation of the decision. */
15374
+ note: external_exports.string().max(MAX_GUIDANCE_LENGTH).optional(),
15375
+ /** Server-composed next step for the submitting agent. */
15376
+ hint: external_exports.string().max(300)
15377
+ });
15378
+ var ownerIssueSchema = external_exports.object({
15379
+ id: external_exports.string(),
15380
+ title: external_exports.string(),
15381
+ summary: external_exports.string().nullable(),
15382
+ status: issueStatusSchema,
15383
+ fingerprint: external_exports.string().nullable(),
15384
+ event_count: external_exports.number(),
15385
+ reopened_count: external_exports.number(),
15386
+ first_seen: external_exports.string(),
15387
+ last_seen: external_exports.string(),
15388
+ links_json: external_exports.string()
15389
+ });
15247
15390
  var ingestResponseSchema = external_exports.object({
15248
15391
  id: external_exports.string().max(64),
15249
15392
  thread: external_exports.string().regex(THREAD_ID_PATTERN),
@@ -15251,6 +15394,19 @@ var ingestResponseSchema = external_exports.object({
15251
15394
  resume: external_exports.string().max(512).optional(),
15252
15395
  /** Vendor-configured answer back to the submitting agent (known issues, workarounds). */
15253
15396
  guidance: external_exports.string().max(MAX_GUIDANCE_LENGTH).optional(),
15397
+ /** The report matched an already-tracked issue; recorded — don't file variants. */
15398
+ known_issue: knownIssueSchema.optional(),
15399
+ /**
15400
+ * A question from the project's team, delivered at most one per response.
15401
+ * Answering is optional and must be grounded in what the agent actually did;
15402
+ * `command` is ready to run and files the answer on this thread.
15403
+ */
15404
+ ask: external_exports.object({
15405
+ id: external_exports.string().max(64),
15406
+ prompt: external_exports.string().max(500),
15407
+ command: external_exports.string().max(512),
15408
+ attachments: external_exports.array(deliveredAttachmentSchema).optional()
15409
+ }).optional(),
15254
15410
  /** True when idempotencyKey matched an already-stored event; id/thread are the original's. */
15255
15411
  duplicate: external_exports.boolean().optional()
15256
15412
  });
@@ -15279,8 +15435,8 @@ var repoEntrySchema = external_exports.object({
15279
15435
  var repoListSchema = external_exports.array(repoEntrySchema).max(20);
15280
15436
  function formatIssues(error51) {
15281
15437
  return error51.issues.map((issue2) => {
15282
- const path5 = issue2.path.join(".");
15283
- return path5 ? `${path5}: ${issue2.message}` : issue2.message;
15438
+ const path6 = issue2.path.join(".");
15439
+ return path6 ? `${path6}: ${issue2.message}` : issue2.message;
15284
15440
  });
15285
15441
  }
15286
15442
  function validateFeedbackEvent(input) {
@@ -15297,8 +15453,11 @@ function validateIngestResponse(input) {
15297
15453
  }
15298
15454
 
15299
15455
  // ../core/dist/index.js
15300
- import { readFileSync } from "fs";
15456
+ import { createHash } from "crypto";
15457
+ import { readFile, stat } from "fs/promises";
15301
15458
  import path2 from "path";
15459
+ import { readFileSync } from "fs";
15460
+ import path3 from "path";
15302
15461
 
15303
15462
  // ../../node_modules/.pnpm/agent-cli-detector@0.1.5/node_modules/agent-cli-detector/dist/index.js
15304
15463
  import { execFileSync } from "child_process";
@@ -15662,29 +15821,152 @@ function matchesEnvSignal(signal, env) {
15662
15821
 
15663
15822
  // ../core/dist/index.js
15664
15823
  import { homedir } from "os";
15665
- import path3 from "path";
15824
+ import path22 from "path";
15666
15825
  import { mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
15667
- import path32 from "path";
15668
- import { appendFileSync, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
15669
15826
  import path4 from "path";
15827
+ import { randomUUID } from "crypto";
15828
+ import { appendFileSync, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync3, renameSync, rmSync } from "fs";
15829
+ import path5 from "path";
15670
15830
  import { randomBytes } from "crypto";
15671
15831
  import { createRequire } from "module";
15832
+ var FeedbackError = class extends Error {
15833
+ constructor(message) {
15834
+ super(message);
15835
+ this.name = "FeedbackError";
15836
+ }
15837
+ };
15838
+ async function readLocalAttachments(inputs, cwd = process.cwd()) {
15839
+ if (inputs.length > MAX_ATTACHMENTS)
15840
+ throw new FeedbackError(`At most ${MAX_ATTACHMENTS} files can be attached.`);
15841
+ const files = [];
15842
+ for (const input of inputs) {
15843
+ const selected = typeof input === "string" ? input : input.path;
15844
+ const filename = path2.basename(selected);
15845
+ const description = attachmentDescriptionSchema.optional().safeParse(typeof input === "string" ? void 0 : input.description);
15846
+ if (!description.success)
15847
+ throw new FeedbackError(
15848
+ `Invalid description for "${filename}": ${description.error.issues.map((issue2) => issue2.message).join("; ")}`
15849
+ );
15850
+ const file2 = path2.resolve(cwd, selected);
15851
+ const info = await stat(file2).catch(() => null);
15852
+ if (!info?.isFile())
15853
+ throw new FeedbackError(`Cannot attach "${selected}": choose an existing, readable file.`);
15854
+ if (info.size < 1 || info.size > MAX_ATTACHMENT_BYTES)
15855
+ throw new FeedbackError(`"${filename}" must be between 1 byte and 20 MiB.`);
15856
+ const bytes = new Uint8Array(
15857
+ await readFile(file2).catch(() => {
15858
+ throw new FeedbackError(`Cannot read "${selected}".`);
15859
+ })
15860
+ );
15861
+ const manifest = attachmentManifestSchema.safeParse({
15862
+ filename,
15863
+ mediaType: mediaTypeForFilename(filename),
15864
+ size: bytes.length,
15865
+ sha256: createHash("sha256").update(bytes).digest("hex")
15866
+ });
15867
+ if (!manifest.success)
15868
+ throw new FeedbackError(
15869
+ `Cannot attach "${filename}": ${manifest.error.issues.map((issue2) => issue2.message).join("; ")}`
15870
+ );
15871
+ files.push({
15872
+ manifest: manifest.data,
15873
+ bytes,
15874
+ ...description.data ? { description: description.data } : {}
15875
+ });
15876
+ }
15877
+ return files;
15878
+ }
15879
+ var AttachmentUploadError = class extends FeedbackError {
15880
+ constructor(message, rejected) {
15881
+ super(message);
15882
+ this.rejected = rejected;
15883
+ this.name = "AttachmentUploadError";
15884
+ }
15885
+ rejected;
15886
+ };
15887
+ async function send(fetchImpl, url2, init, timeoutMs) {
15888
+ let response;
15889
+ try {
15890
+ response = await fetchImpl(url2, {
15891
+ ...init,
15892
+ redirect: "error",
15893
+ signal: AbortSignal.timeout(timeoutMs)
15894
+ });
15895
+ } catch {
15896
+ throw new AttachmentUploadError("Could not reach the server to upload files.", false);
15897
+ }
15898
+ if (response.ok) return response;
15899
+ const body = await response.json().catch(() => null);
15900
+ const errors = Array.isArray(body?.errors) ? body.errors.map(String).join("; ") : void 0;
15901
+ if (response.status === 404 && !errors)
15902
+ throw new AttachmentUploadError("This server does not accept files.", false);
15903
+ throw new AttachmentUploadError(
15904
+ errors ?? `File upload failed (${response.status}).`,
15905
+ !(response.status >= 500 || response.status === 429 || response.status === 408)
15906
+ );
15907
+ }
15908
+ async function uploadFiles(options) {
15909
+ const fetchImpl = options.fetchImpl ?? fetch;
15910
+ const timeoutMs = options.timeoutMs ?? 6e4;
15911
+ const origin = new URL(options.endpoint).origin;
15912
+ const references = [];
15913
+ for (const file2 of options.files) {
15914
+ const response = await send(
15915
+ fetchImpl,
15916
+ `${origin}${ATTACHMENT_UPLOAD_PATH}`,
15917
+ {
15918
+ method: "POST",
15919
+ headers: {
15920
+ "content-type": "application/json",
15921
+ ...options.secretKey ? { authorization: `Bearer ${options.secretKey}` } : {},
15922
+ ...options.writeKey ? { [WRITE_KEY_HEADER]: options.writeKey } : {}
15923
+ },
15924
+ body: JSON.stringify({
15925
+ ...file2.manifest,
15926
+ to: options.to,
15927
+ scope: options.secretKey ? "owner" : "reporter",
15928
+ uploader: options.uploader
15929
+ })
15930
+ },
15931
+ timeoutMs
15932
+ );
15933
+ const prepared = preparedAttachmentSchema.safeParse(await response.json().catch(() => null));
15934
+ if (!prepared.success || new URL(prepared.data.uploadUrl).origin !== origin)
15935
+ throw new AttachmentUploadError("The server returned an invalid upload response.", true);
15936
+ await send(
15937
+ fetchImpl,
15938
+ prepared.data.uploadUrl,
15939
+ {
15940
+ method: "PUT",
15941
+ headers: { authorization: `Bearer ${prepared.data.reference.token}` },
15942
+ body: file2.bytes
15943
+ },
15944
+ // At least 100 KB/s: a 20 MiB file on a slow link still gets about 3.5 minutes.
15945
+ Math.max(timeoutMs, file2.bytes.length / 100)
15946
+ );
15947
+ references.push({
15948
+ ...prepared.data.reference,
15949
+ ...file2.description ? { description: file2.description } : {}
15950
+ });
15951
+ }
15952
+ return references;
15953
+ }
15672
15954
  var DSN_ENV_VAR = "HIVENET_DSN";
15673
15955
  var REGISTRY_ENV_VAR = "HIVENET_REGISTRY";
15674
15956
  var HOME_ENV_VAR = "HIVENET_HOME";
15675
15957
  var NO_TELEMETRY_ENV_VAR = "HIVENET_NO_TELEMETRY";
15676
15958
  function feedbackHomeDir(env = process.env) {
15677
15959
  const override = env[HOME_ENV_VAR];
15678
- return override?.trim() ? override : path3.join(homedir(), ".hivenet");
15960
+ return override?.trim() ? override : path22.join(homedir(), ".hivenet");
15679
15961
  }
15680
15962
  function isTelemetryDisabled(extraOptOutEnvVars = [], env = process.env) {
15681
15963
  const vars = ["DO_NOT_TRACK", NO_TELEMETRY_ENV_VAR, ...extraOptOutEnvVars];
15682
15964
  return vars.some((name) => env[name] === "1" || env[name] === "true");
15683
15965
  }
15684
15966
  var MAX_SESSION_ID_LENGTH = 256;
15685
- function detectAgentSafe() {
15967
+ function detectAgentSafe(env) {
15686
15968
  try {
15687
- const result = detectAgent();
15969
+ const result = detectAgent({ env });
15688
15970
  if (result.detected && result.agent) {
15689
15971
  return {
15690
15972
  id: result.agent.id,
@@ -15696,9 +15978,9 @@ function detectAgentSafe() {
15696
15978
  }
15697
15979
  return void 0;
15698
15980
  }
15699
- function detectSandboxSafe() {
15981
+ function detectSandboxSafe(env) {
15700
15982
  try {
15701
- const result = detectSandbox();
15983
+ const result = detectSandbox({ env });
15702
15984
  if (result.detected && result.sandbox) {
15703
15985
  return {
15704
15986
  id: result.sandbox.id,
@@ -15763,7 +16045,7 @@ function resolveSessions(sources, projectRoot, env) {
15763
16045
  }
15764
16046
  const value = "envVar" in source ? normalizeSessionId(env[source.envVar]) : normalizeSessionId(
15765
16047
  readDotenvKey(
15766
- path2.resolve(projectRoot, source.dotenvFile),
16048
+ path3.resolve(projectRoot, source.dotenvFile),
15767
16049
  source.dotenvKey ?? source.key
15768
16050
  )
15769
16051
  );
@@ -15779,8 +16061,8 @@ async function collectContext(options = {}) {
15779
16061
  return void 0;
15780
16062
  }
15781
16063
  const projectRoot = options.projectRoot ?? process.cwd();
15782
- const agent = detectAgentSafe();
15783
- const sandbox = detectSandboxSafe();
16064
+ const agent = detectAgentSafe(env);
16065
+ const sandbox = detectSandboxSafe(env);
15784
16066
  const ci = detectCi();
15785
16067
  const session = resolveSessions(options.sessions ?? [], projectRoot, env);
15786
16068
  let project;
@@ -15803,12 +16085,6 @@ async function collectContext(options = {}) {
15803
16085
  function inferAuthor(context) {
15804
16086
  return context?.agent ? "agent" : void 0;
15805
16087
  }
15806
- var FeedbackError = class extends Error {
15807
- constructor(message) {
15808
- super(message);
15809
- this.name = "FeedbackError";
15810
- }
15811
- };
15812
16088
  var CONFIG_FILE_NAME = "feedback.config.json";
15813
16089
  var DESCRIPTOR_CACHE_TTL_MS = 60 * 60 * 1e3;
15814
16090
  var RESOLVE_TIMEOUT_MS = 5e3;
@@ -15833,9 +16109,9 @@ function parseDsn(dsn) {
15833
16109
  };
15834
16110
  }
15835
16111
  function findConfigFile(startDir) {
15836
- let current = path32.resolve(startDir);
16112
+ let current = path4.resolve(startDir);
15837
16113
  while (true) {
15838
- const candidate = path32.join(current, CONFIG_FILE_NAME);
16114
+ const candidate = path4.join(current, CONFIG_FILE_NAME);
15839
16115
  try {
15840
16116
  const raw = readFileSync2(candidate, "utf8");
15841
16117
  const parsed = JSON.parse(raw);
@@ -15844,7 +16120,7 @@ function findConfigFile(startDir) {
15844
16120
  }
15845
16121
  } catch {
15846
16122
  }
15847
- const parent = path32.dirname(current);
16123
+ const parent = path4.dirname(current);
15848
16124
  if (parent === current) {
15849
16125
  return void 0;
15850
16126
  }
@@ -15852,7 +16128,7 @@ function findConfigFile(startDir) {
15852
16128
  }
15853
16129
  }
15854
16130
  function cacheFilePath(cacheDir) {
15855
- return path32.join(cacheDir, "descriptors.json");
16131
+ return path4.join(cacheDir, "descriptors.json");
15856
16132
  }
15857
16133
  function readCache(cacheDir) {
15858
16134
  try {
@@ -15977,7 +16253,7 @@ async function postEvent(options) {
15977
16253
  error: error51 instanceof Error ? error51.message : String(error51)
15978
16254
  };
15979
16255
  }
15980
- if (response.status >= 500) {
16256
+ if (response.status >= 500 || response.status === 429 || response.status === 408) {
15981
16257
  return { kind: "unreachable", error: `server error (${response.status})` };
15982
16258
  }
15983
16259
  if (!response.ok) {
@@ -16005,13 +16281,14 @@ async function postEvent(options) {
16005
16281
  }
16006
16282
  var OUTBOX_FILE = "outbox.jsonl";
16007
16283
  var MAX_OUTBOX_ENTRIES = 100;
16284
+ var STALE_CLAIM_MS = 30 * 60 * 1e3;
16008
16285
  function outboxPath(env = process.env) {
16009
- return path4.join(feedbackHomeDir(env), OUTBOX_FILE);
16286
+ return path5.join(feedbackHomeDir(env), OUTBOX_FILE);
16010
16287
  }
16011
16288
  function appendToOutbox(entry, env = process.env) {
16012
16289
  try {
16013
16290
  const file2 = outboxPath(env);
16014
- mkdirSync2(path4.dirname(file2), { recursive: true });
16291
+ mkdirSync2(path5.dirname(file2), { recursive: true });
16015
16292
  appendFileSync(file2, `${JSON.stringify(entry)}
16016
16293
  `);
16017
16294
  return true;
@@ -16019,19 +16296,43 @@ function appendToOutbox(entry, env = process.env) {
16019
16296
  return false;
16020
16297
  }
16021
16298
  }
16022
- function readOutbox(env) {
16299
+ function readEntries(file2) {
16023
16300
  try {
16024
- return readFileSync3(outboxPath(env), "utf8").split("\n").filter((line) => line.trim()).slice(-MAX_OUTBOX_ENTRIES).map((line) => JSON.parse(line));
16301
+ return readFileSync3(file2, "utf8").split("\n").filter((line) => line.trim()).flatMap((line) => {
16302
+ try {
16303
+ return [JSON.parse(line)];
16304
+ } catch {
16305
+ return [];
16306
+ }
16307
+ });
16025
16308
  } catch {
16026
16309
  return [];
16027
16310
  }
16028
16311
  }
16312
+ function claimQueue(env) {
16313
+ const file2 = outboxPath(env);
16314
+ let names;
16315
+ try {
16316
+ names = readdirSync(path5.dirname(file2));
16317
+ } catch {
16318
+ return [];
16319
+ }
16320
+ const claims = names.filter((name) => {
16321
+ const claimedAt = Number(/\.flushing-(\d+)-/.exec(name)?.[1]);
16322
+ return name.startsWith(OUTBOX_FILE) && Date.now() - claimedAt > STALE_CLAIM_MS;
16323
+ }).map((name) => path5.join(path5.dirname(file2), name));
16324
+ const fresh = `${file2}.flushing-${Date.now()}-${randomUUID()}`;
16325
+ try {
16326
+ renameSync(file2, fresh);
16327
+ claims.push(fresh);
16328
+ } catch {
16329
+ }
16330
+ return claims;
16331
+ }
16029
16332
  async function flushOutbox(options = {}) {
16030
16333
  const env = options.env ?? process.env;
16031
- const entries = readOutbox(env);
16032
- if (entries.length === 0) {
16033
- return { delivered: 0, remaining: 0 };
16034
- }
16334
+ const claims = claimQueue(env);
16335
+ const entries = claims.flatMap(readEntries).slice(-MAX_OUTBOX_ENTRIES);
16035
16336
  const failures = [];
16036
16337
  let delivered = 0;
16037
16338
  for (const entry of entries) {
@@ -16048,12 +16349,13 @@ async function flushOutbox(options = {}) {
16048
16349
  failures.push(entry);
16049
16350
  }
16050
16351
  }
16051
- try {
16052
- writeFileSync2(
16053
- outboxPath(env),
16054
- failures.map((entry) => JSON.stringify(entry)).join("\n") + (failures.length ? "\n" : "")
16055
- );
16056
- } catch {
16352
+ if (failures.every((entry) => appendToOutbox(entry, env))) {
16353
+ for (const claim of claims) {
16354
+ try {
16355
+ rmSync(claim, { force: true });
16356
+ } catch {
16357
+ }
16358
+ }
16057
16359
  }
16058
16360
  return { delivered, remaining: failures.length };
16059
16361
  }
@@ -16073,10 +16375,10 @@ function ownerApi(options) {
16073
16375
  const origin = (options.apiOrigin ?? new URL(DEFAULT_REGISTRY_URL).origin).replace(/\/$/, "");
16074
16376
  const fetchImpl = options.fetchImpl ?? fetch;
16075
16377
  const timeoutMs = options.timeoutMs ?? 15e3;
16076
- async function request(path5, init) {
16378
+ async function request(path6, init) {
16077
16379
  let response;
16078
16380
  try {
16079
- response = await fetchImpl(`${origin}${path5}`, {
16381
+ response = await fetchImpl(`${origin}${path6}`, {
16080
16382
  ...init,
16081
16383
  signal: AbortSignal.timeout(timeoutMs),
16082
16384
  headers: {
@@ -16104,7 +16406,34 @@ function ownerApi(options) {
16104
16406
  }
16105
16407
  return body;
16106
16408
  }
16409
+ async function upload(paths, cwd) {
16410
+ return uploadFiles({
16411
+ endpoint: origin,
16412
+ files: await readLocalAttachments(paths, cwd),
16413
+ secretKey: options.secretKey,
16414
+ fetchImpl,
16415
+ timeoutMs: Math.max(timeoutMs, 6e4)
16416
+ });
16417
+ }
16107
16418
  return {
16419
+ /** Upload local files and attach them to a record (at most 10 files per record). */
16420
+ async attachFiles(target, paths, cwd) {
16421
+ if (!paths.length) throw new FeedbackError("Select at least one file.");
16422
+ const attachments = await upload(paths, cwd);
16423
+ return request("/v1/attachments/attach", {
16424
+ method: "POST",
16425
+ body: JSON.stringify({ target, attachments })
16426
+ });
16427
+ },
16428
+ /** A record's files; issues and evals include their reports' files. */
16429
+ async listAttachments(target, offset = 0) {
16430
+ return request(
16431
+ `/v1/attachments?${new URLSearchParams({ type: target.type, id: target.id, offset: String(offset) })}`
16432
+ );
16433
+ },
16434
+ async deleteAttachment(id) {
16435
+ return request(`/v1/attachments/${encodeURIComponent(id)}`, { method: "DELETE" });
16436
+ },
16108
16437
  async listFeedback(filters = {}) {
16109
16438
  const params = new URLSearchParams();
16110
16439
  if (filters.status) params.set("status", filters.status);
@@ -16141,10 +16470,53 @@ function ownerApi(options) {
16141
16470
  body: JSON.stringify({ status, ...note !== void 0 ? { note } : {} })
16142
16471
  });
16143
16472
  },
16473
+ async listQuestions(filters = {}) {
16474
+ const query = filters.status ? `?status=${filters.status}` : "";
16475
+ return request(`/v1/questions${query}`);
16476
+ },
16477
+ /** Files are shared with every agent that receives the question. */
16478
+ async createQuestion(prompt, category, options2 = {}) {
16479
+ const attachments = options2.files?.length ? await upload(options2.files, options2.cwd) : [];
16480
+ return request("/v1/questions", {
16481
+ method: "POST",
16482
+ body: JSON.stringify({
16483
+ prompt,
16484
+ ...category ? { category } : {},
16485
+ ...attachments.length ? { attachments } : {},
16486
+ ...options2.idempotencyKey ? { idempotencyKey: options2.idempotencyKey } : {}
16487
+ })
16488
+ });
16489
+ },
16490
+ async closeQuestion(id) {
16491
+ return request(`/v1/questions/${encodeURIComponent(id)}`, {
16492
+ method: "PATCH",
16493
+ body: JSON.stringify({ status: "closed" })
16494
+ });
16495
+ },
16496
+ async listEvals(filters = {}) {
16497
+ const query = filters.status ? `?status=${filters.status}` : "";
16498
+ return request(`/v1/evals${query}`);
16499
+ },
16500
+ async getEval(id) {
16501
+ return request(`/v1/evals/${encodeURIComponent(id)}`);
16502
+ },
16503
+ /** Promote a failed-task report (category=eval event) into the eval corpus. */
16504
+ async promoteEval(eventId) {
16505
+ return request("/v1/evals", {
16506
+ method: "POST",
16507
+ body: JSON.stringify({ event_id: eventId })
16508
+ });
16509
+ },
16510
+ async setEvalStatus(id, status, note) {
16511
+ return request(`/v1/evals/${encodeURIComponent(id)}`, {
16512
+ method: "PATCH",
16513
+ body: JSON.stringify({ status, ...note !== void 0 ? { note } : {} })
16514
+ });
16515
+ },
16144
16516
  /** Attach an external link (Slack message, Linear/GitHub issue, PR) to evt_… or iss_…. */
16145
16517
  async addLink(id, url2, label) {
16146
- const path5 = id.startsWith("iss_") ? `/v1/issues/${id}` : `/v1/events/${id}`;
16147
- return request(path5, {
16518
+ const path6 = id.startsWith("iss_") ? `/v1/issues/${id}` : `/v1/events/${id}`;
16519
+ return request(path6, {
16148
16520
  method: "PATCH",
16149
16521
  body: JSON.stringify({ addLink: { url: url2, ...label !== void 0 ? { label } : {} } })
16150
16522
  });
@@ -16172,9 +16544,10 @@ function resolveThreadId(resume) {
16172
16544
  }
16173
16545
  return { id: generateThreadId(), resumed: false };
16174
16546
  }
16175
- async function sendFeedback(options) {
16547
+ async function prepareFeedback(options) {
16176
16548
  const env = options.env ?? process.env;
16177
16549
  const cwd = options.cwd ?? process.cwd();
16550
+ const files = await readLocalAttachments(options.attachments ?? [], cwd);
16178
16551
  const config2 = findConfigFile(cwd)?.config;
16179
16552
  const resolveOptions = {
16180
16553
  ...options.to !== void 0 ? { to: options.to } : {},
@@ -16210,6 +16583,7 @@ async function sendFeedback(options) {
16210
16583
  ...author ? { author } : {},
16211
16584
  thread: { id: thread.id },
16212
16585
  ...options.eval ? { eval: options.eval } : {},
16586
+ ...options.question ? { question: options.question } : {},
16213
16587
  ...context ? { context } : {},
16214
16588
  ...options.metadata && Object.keys(options.metadata).length > 0 ? { metadata: options.metadata } : {},
16215
16589
  // Minted before the first attempt so outbox retries replay the SAME event
@@ -16225,7 +16599,32 @@ async function sendFeedback(options) {
16225
16599
  if (!validated.ok) {
16226
16600
  throw new FeedbackError(`Invalid feedback event: ${validated.errors.join("; ")}`);
16227
16601
  }
16228
- const event = validated.data;
16602
+ return { event: validated.data, destination, resumed: thread.resumed, files };
16603
+ }
16604
+ async function sendFeedback(options) {
16605
+ const env = options.env ?? process.env;
16606
+ const { event, destination, resumed, files } = await prepareFeedback(options);
16607
+ const names = files.map((file2) => file2.manifest.filename);
16608
+ let filesNotAttached;
16609
+ if (files.length) {
16610
+ try {
16611
+ event.attachments = await uploadFiles({
16612
+ endpoint: destination.endpoint,
16613
+ files,
16614
+ ...destination.to ? { to: destination.to } : {},
16615
+ ...destination.writeKey ? { writeKey: destination.writeKey } : {},
16616
+ ...event.context?.agent?.id ? { uploader: event.context.agent.id } : {},
16617
+ ...options.fetchImpl ? { fetchImpl: options.fetchImpl } : {}
16618
+ });
16619
+ } catch (error51) {
16620
+ if (!(error51 instanceof AttachmentUploadError) || error51.rejected) {
16621
+ throw new FeedbackError(
16622
+ `${error51 instanceof Error ? error51.message : String(error51)} Nothing was sent.`
16623
+ );
16624
+ }
16625
+ filesNotAttached = { files: names, reason: error51.message };
16626
+ }
16627
+ }
16229
16628
  const result = await postEvent({
16230
16629
  endpoint: destination.endpoint,
16231
16630
  ...destination.writeKey ? { writeKey: destination.writeKey } : {},
@@ -16239,12 +16638,17 @@ async function sendFeedback(options) {
16239
16638
  response: result.response,
16240
16639
  event,
16241
16640
  destination,
16242
- resumed: thread.resumed
16641
+ resumed,
16642
+ ...filesNotAttached ? { filesNotAttached } : {}
16243
16643
  };
16244
16644
  }
16245
16645
  if (result.kind === "rejected") {
16246
16646
  throw new FeedbackError(`Feedback rejected: ${result.errors.join("; ")}`);
16247
16647
  }
16648
+ if (event.attachments) {
16649
+ delete event.attachments;
16650
+ filesNotAttached = { files: names, reason: result.error };
16651
+ }
16248
16652
  const queued = options.outbox !== false && appendToOutbox(
16249
16653
  {
16250
16654
  endpoint: destination.endpoint,
@@ -16260,20 +16664,29 @@ async function sendFeedback(options) {
16260
16664
  error: result.error,
16261
16665
  event,
16262
16666
  destination,
16263
- resumed: thread.resumed
16667
+ resumed,
16668
+ ...filesNotAttached ? { filesNotAttached } : {}
16264
16669
  };
16265
16670
  }
16266
16671
 
16267
16672
  export {
16268
16673
  __toESM,
16674
+ external_exports,
16675
+ MAX_ATTACHMENTS,
16676
+ attachmentDescriptionSchema,
16677
+ ATTACHMENT_TARGET_TYPES,
16678
+ attachmentTargetSchema,
16269
16679
  PROTOCOL_VERSION,
16270
16680
  MAX_FEEDBACK_LENGTH,
16271
16681
  WELL_KNOWN_PATH,
16272
16682
  FEEDBACK_CATEGORIES,
16273
16683
  EVENT_STATUSES,
16274
16684
  ISSUE_STATUSES,
16685
+ EVAL_STATUSES,
16275
16686
  sentimentSchema,
16276
16687
  authorSchema,
16688
+ evalReportSchema,
16689
+ feedbackEventSchema,
16277
16690
  validateFeedbackEvent,
16278
16691
  validateDescriptor,
16279
16692
  FeedbackError,
@@ -16284,6 +16697,7 @@ export {
16284
16697
  resolveApiOrigin,
16285
16698
  ownerApi,
16286
16699
  generateThreadId,
16700
+ prepareFeedback,
16287
16701
  sendFeedback,
16288
16702
  require_arg
16289
16703
  };