hivenet 0.6.0 → 0.7.1

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/README.md CHANGED
@@ -64,6 +64,8 @@ is still sent — or queued — without them, and the output names them so you c
64
64
  later with `--resume <thread> --attach <path>`. A file the server rejects stops the send.
65
65
  After a text-only report the CLI suggests that same command (`attach_hint` with `--json`),
66
66
  since a screenshot or log often says more than the text.
67
+ New deliveries include `attachmentCount` in `--json` output (zero for text-only reports).
68
+ Duplicate receipts omit it because the original report's files may differ from this attempt.
67
69
 
68
70
  Structured reports can come from one JSON object, with the same fields as the SDK:
69
71
 
@@ -85,6 +87,18 @@ optional and must be grounded in what you actually did; never guess.
85
87
  - `--json` machine output · `--dry-run` shows the exact event before anything is sent.
86
88
  - Destinations: `--to <slug|domain>`, `--dsn`, `HIVENET_DSN`, or feedback.config.json.
87
89
 
90
+ Self-hosters use the same CLI with their own server (0.7.0+):
91
+
92
+ ```sh
93
+ npx hivenet@^0.7.0 --registry https://feedback.example.com --to my-project "Test feedback"
94
+ ```
95
+
96
+ Copy the full command from your instance's Integration page. `--registry` + `--to` pins that
97
+ installation ahead of ambient DSN/config settings; explicit `--dsn` still wins. Older clients
98
+ can use `--dsn "<your instance's DSN>"`. Owner commands use your own secret key and
99
+ `--api https://feedback.example.com`. See [self-hosting and forks](../../docs/self-hosting.md)
100
+ for setup, checks, SDK/MCP access and publishing a customized client.
101
+
88
102
  Vendors: claim a slug and get paste-ready snippets at https://hivenet.app.
89
103
  The reference self-hosted ingest implementation remains available in the
90
104
  [Hivenet repository](https://github.com/zvadaadam/hivenet/tree/master/packages/server).
@@ -15234,6 +15234,169 @@ function mediaTypeForFilename(filename) {
15234
15234
  const extension = /\.([^./\\]+)$/.exec(filename)?.[1]?.toLowerCase();
15235
15235
  return extension && MEDIA_TYPES[extension] || "application/octet-stream";
15236
15236
  }
15237
+ var SECRET_ENV_NAME = /KEY|SECRET|TOKEN(?!S)|PASSWORD|CREDENTIAL|AUTH/i;
15238
+ var SECRET_VALUE_PATTERNS = [
15239
+ [/hv_sec_[A-Za-z0-9]{8,}/, "a Hivenet secret key"],
15240
+ [/sk-ant-[A-Za-z0-9_-]{8,}/, "an Anthropic API key"],
15241
+ // OpenAI (sk-, sk-proj-, sk-svcacct-), OpenRouter (sk-or-v1-) and others.
15242
+ [/\bsk-[A-Za-z0-9_-]{20,}/, "an API key (sk-\u2026)"],
15243
+ [/\bAIza[0-9A-Za-z_-]{35}/, "a Google API key"],
15244
+ [/\bgh[pousr]_[A-Za-z0-9]{20,}/, "a GitHub token"],
15245
+ [/\bgithub_pat_[A-Za-z0-9_]{20,}/, "a GitHub token"],
15246
+ [/\bAKIA[0-9A-Z]{16}\b/, "an AWS access key"],
15247
+ [/\bxox[abprs]-[A-Za-z0-9-]{10,}/, "a Slack token"]
15248
+ ];
15249
+ var evalCriterionIdSchema = external_exports.string().regex(
15250
+ /^[a-z][a-z0-9_]{0,47}$/,
15251
+ "Criterion ids are snake_case: a lowercase letter, then lowercase letters, digits or _ (at most 48)."
15252
+ );
15253
+ var weightSchema = external_exports.number().positive().max(100).optional();
15254
+ var evalJudgeCriterionSchema = external_exports.object({
15255
+ id: evalCriterionIdSchema,
15256
+ kind: external_exports.literal("judge"),
15257
+ description: external_exports.string().trim().min(1).max(2e3).describe("The yes/no question the judge answers about the result."),
15258
+ weight: weightSchema
15259
+ });
15260
+ var evalCommandCriterionSchema = external_exports.object({
15261
+ id: evalCriterionIdSchema,
15262
+ kind: external_exports.literal("command"),
15263
+ description: external_exports.string().trim().min(1).max(500).describe("What the check proves, for people reading results."),
15264
+ command: external_exports.string().trim().min(1).max(4e3),
15265
+ contains: external_exports.string().min(1).max(1e3).optional().describe("Stdout must contain this text."),
15266
+ regex: external_exports.string().min(1).max(500).optional().describe("Stdout must match this regex."),
15267
+ timeout_sec: external_exports.number().int().min(1).max(600).optional(),
15268
+ weight: weightSchema
15269
+ }).refine((criterion) => !(criterion.contains && criterion.regex), {
15270
+ message: "A command criterion takes contains or regex, not both."
15271
+ });
15272
+ var evalCriterionSchema = external_exports.discriminatedUnion("kind", [
15273
+ evalJudgeCriterionSchema,
15274
+ evalCommandCriterionSchema
15275
+ ]);
15276
+ var evalJudgeSchema = external_exports.object({
15277
+ model: external_exports.string().trim().min(1).max(128).default("anthropic/claude-sonnet-5"),
15278
+ /** Give the judge the agent's ATIF trajectory, e.g. to grade which tool or skill it used. */
15279
+ reads_trajectory: external_exports.boolean().default(false),
15280
+ /** Workspace files the judge reads, relative to the workdir. */
15281
+ files: external_exports.array(external_exports.string().trim().min(1).max(256)).max(20).default([])
15282
+ });
15283
+ var ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]{0,63}$/;
15284
+ var SINGLE_LINE = /^[^\r\n]+$/;
15285
+ var evalMcpServerSchema = external_exports.object({
15286
+ name: external_exports.string().regex(/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/),
15287
+ transport: external_exports.enum(["stdio", "sse", "streamable-http"]),
15288
+ url: external_exports.url().max(2e3).optional(),
15289
+ command: external_exports.string().trim().min(1).max(500).optional(),
15290
+ args: external_exports.array(external_exports.string().max(500)).max(30).default([])
15291
+ });
15292
+ var evalEnvironmentSchema = external_exports.object({
15293
+ /** The base image, e.g. `node:22-slim`. */
15294
+ image: external_exports.string().trim().regex(/^[A-Za-z0-9][A-Za-z0-9._\-/:@]{0,255}$/, "image must be a Docker image reference."),
15295
+ /** Dockerfile RUN lines, cached in the image: toolchains, not the product under test. */
15296
+ build: external_exports.array(external_exports.string().trim().regex(SINGLE_LINE).max(2e3)).max(30).default([]),
15297
+ /**
15298
+ * Runs before every trial, so each one tests the current product. Harbor runs it as the
15299
+ * healthcheck and retries it on failure, so it must be safe to run twice.
15300
+ */
15301
+ setup: external_exports.object({
15302
+ command: external_exports.string().trim().min(1).max(8e3),
15303
+ timeout_sec: external_exports.number().int().min(10).max(3600).default(600)
15304
+ }).nullable().default(null),
15305
+ workdir: external_exports.string().regex(/^\/[A-Za-z0-9._\-/]{0,255}$/).default("/app"),
15306
+ network: external_exports.object({
15307
+ mode: external_exports.enum(["public", "allowlist", "no-network"]).default("public"),
15308
+ allowed_hosts: external_exports.array(external_exports.string().trim().min(1).max(253)).max(50).default([])
15309
+ }).prefault({}),
15310
+ /** Values reach setup and the agent; secret-like names take only `${VAR}` templates. */
15311
+ env: external_exports.record(external_exports.string().regex(ENV_NAME), external_exports.string().max(2e3)).default({}),
15312
+ mcp_servers: external_exports.array(evalMcpServerSchema).max(10).default([])
15313
+ });
15314
+ var evalAgentSchema = external_exports.object({
15315
+ timeout_sec: external_exports.number().int().min(30).max(7200).default(900)
15316
+ });
15317
+ var evalVersionFieldsSchema = external_exports.object({
15318
+ /** The task in the owner's words. Never the reporter's `actual` or `mistake`. */
15319
+ instruction: external_exports.string().trim().min(1).max(2e4),
15320
+ /** What "done" means, in one or two sentences. */
15321
+ end_goal: external_exports.string().trim().min(1).max(2e3).optional(),
15322
+ criteria: external_exports.array(evalCriterionSchema).min(1).max(30),
15323
+ judge: evalJudgeSchema.optional(),
15324
+ environment: evalEnvironmentSchema,
15325
+ agent: evalAgentSchema.prefault({}),
15326
+ /** A reference solution for Harbor's `oracle` agent, as a bash script. */
15327
+ solution: external_exports.string().trim().min(1).max(2e4).optional(),
15328
+ change_note: external_exports.string().trim().min(1).max(500).optional()
15329
+ });
15330
+ var evalVersionInputSchema = evalVersionFieldsSchema.refine(
15331
+ (input) => new Set(input.criteria.map((c) => c.id)).size === input.criteria.length,
15332
+ { message: "Criterion ids must be unique.", path: ["criteria"] }
15333
+ );
15334
+ var HARBOR_ID = external_exports.string().regex(/^[0-9a-f-]{36}$/i, "Harbor ids are UUIDs.");
15335
+ var boundedRecord = (max) => external_exports.record(external_exports.string().min(1).max(200), external_exports.number()).refine((record2) => Object.keys(record2).length <= max, `At most ${max} keys.`);
15336
+ var evalTrialScenarioSchema = external_exports.object({
15337
+ /** Folders the job mounted (`harbor run --mounts`), by name: the path stays on the machine. */
15338
+ mounts: external_exports.array(external_exports.string().min(1).max(200)).max(5).optional(),
15339
+ /** `--skill` */
15340
+ skills: external_exports.array(external_exports.string().min(1).max(200)).max(20).optional(),
15341
+ /** `--mcp-config` */
15342
+ mcp_servers: external_exports.array(external_exports.string().min(1).max(100)).max(20).optional(),
15343
+ /** `--extra-instruction` text, or the file `--extra-instruction-path` named. */
15344
+ instructions: external_exports.array(external_exports.string().min(1).max(500)).max(5).optional(),
15345
+ /** `--ak` and `--ae`, without secret-looking names. */
15346
+ agent_options: external_exports.record(external_exports.string().min(1).max(64), external_exports.string().max(200)).refine((record2) => Object.keys(record2).length <= 20, "At most 20 agent options.").optional()
15347
+ });
15348
+ var evalTrialCiSchema = external_exports.object({
15349
+ provider: external_exports.literal("github"),
15350
+ /** `owner/name` */
15351
+ repository: external_exports.string().min(1).max(200),
15352
+ /** The commit under test: a pull request's head, else the commit that triggered the run. */
15353
+ commit: external_exports.string().regex(/^[0-9a-f]{7,64}$/i).optional(),
15354
+ branch: external_exports.string().min(1).max(250).optional(),
15355
+ pull_request: external_exports.number().int().positive().optional(),
15356
+ /** What started the workflow: pull_request, push, schedule, workflow_dispatch… */
15357
+ event: external_exports.string().min(1).max(50).optional(),
15358
+ run_url: external_exports.url({ protocol: /^https?$/ }).max(500).optional()
15359
+ });
15360
+ var evalTrialUploadSchema = external_exports.object({
15361
+ harbor_trial_id: HARBOR_ID,
15362
+ harbor_job_id: HARBOR_ID,
15363
+ harbor_job_name: external_exports.string().max(200).optional(),
15364
+ /** Set on a regrade: the trial it derives from. */
15365
+ source_harbor_trial_id: HARBOR_ID.optional(),
15366
+ /** `[task].name` from the trial's result: `<project slug>/<eval id>` for Hivenet tasks. */
15367
+ task_name: external_exports.string().min(1).max(300),
15368
+ /** `task.digest` from the trial's lock.json. */
15369
+ task_digest: external_exports.string().regex(/^sha256:[0-9a-f]{64}$/),
15370
+ agent: external_exports.string().min(1).max(100),
15371
+ agent_version: external_exports.string().max(100).optional(),
15372
+ model: external_exports.string().max(200).optional(),
15373
+ /** The reasoning effort it ran at (`harbor run --ak reasoning_effort=…`): part of the lane. */
15374
+ effort: external_exports.string().min(1).max(40).optional(),
15375
+ harbor_version: external_exports.string().max(50).optional(),
15376
+ exception: external_exports.object({ type: external_exports.string().min(1).max(200), message: external_exports.string().max(2e3) }).optional(),
15377
+ /** reward.json. Absent when the verifier wrote nothing: the trial wasn't graded. */
15378
+ rewards: boundedRecord(32).optional(),
15379
+ /** reward-details.json leaves, keyed by criterion id. */
15380
+ criteria: boundedRecord(64).optional(),
15381
+ metrics: external_exports.object({
15382
+ input_tokens: external_exports.number().int().nonnegative(),
15383
+ output_tokens: external_exports.number().int().nonnegative(),
15384
+ cache_tokens: external_exports.number().int().nonnegative(),
15385
+ cost_usd: external_exports.number().nonnegative(),
15386
+ setup_sec: external_exports.number().nonnegative(),
15387
+ agent_sec: external_exports.number().nonnegative(),
15388
+ verifier_sec: external_exports.number().nonnegative()
15389
+ }).partial().optional(),
15390
+ started_at: external_exports.iso.datetime({ offset: true }).optional(),
15391
+ finished_at: external_exports.iso.datetime({ offset: true }).optional(),
15392
+ scenario: evalTrialScenarioSchema.optional(),
15393
+ ci: evalTrialCiSchema.optional()
15394
+ });
15395
+ var MAX_TRIALS_PER_PUSH = 200;
15396
+ var MAX_TRAJECTORY_BYTES = 8 * 1024 * 1024;
15397
+ var evalResultsUploadSchema = external_exports.object({
15398
+ trials: external_exports.array(evalTrialUploadSchema).min(1).max(MAX_TRIALS_PER_PUSH)
15399
+ });
15237
15400
  var PROTOCOL_VERSION = 1;
15238
15401
  var MAX_FEEDBACK_LENGTH = 5e3;
15239
15402
  var MAX_SUBJECT_LENGTH = 500;
@@ -15337,8 +15500,12 @@ var metadataSchema = external_exports.record(
15337
15500
  external_exports.string().min(1).max(40),
15338
15501
  external_exports.union([external_exports.string().max(200), external_exports.number().finite(), external_exports.boolean()])
15339
15502
  ).refine((value) => Object.keys(value).length <= 20, { message: "at most 20 metadata keys" });
15503
+ var threadSchema = external_exports.object({ id: external_exports.string().regex(THREAD_ID_PATTERN) });
15504
+ function newThreadId() {
15505
+ return crypto.randomUUID().replaceAll("-", "");
15506
+ }
15340
15507
  var feedbackEventSchema = external_exports.object({
15341
- v: external_exports.literal(PROTOCOL_VERSION),
15508
+ v: external_exports.literal(PROTOCOL_VERSION).default(PROTOCOL_VERSION),
15342
15509
  /** Tenant slug or domain this feedback is addressed to. Optional when the write key implies it. */
15343
15510
  to: external_exports.string().max(253).optional(),
15344
15511
  feedback: external_exports.string().min(1).max(MAX_FEEDBACK_LENGTH),
@@ -15346,7 +15513,7 @@ var feedbackEventSchema = external_exports.object({
15346
15513
  subject: external_exports.string().min(1).max(MAX_SUBJECT_LENGTH).optional(),
15347
15514
  sentiment: sentimentSchema.optional(),
15348
15515
  author: authorSchema.optional(),
15349
- thread: external_exports.object({ id: external_exports.string().regex(THREAD_ID_PATTERN) }),
15516
+ thread: threadSchema.optional().transform((thread) => thread ?? { id: newThreadId() }),
15350
15517
  eval: evalReportSchema.optional(),
15351
15518
  /** Id of an owner/system question this submission answers (from a prior response's `ask`). */
15352
15519
  question: external_exports.string().max(64).optional(),
@@ -15359,8 +15526,8 @@ var feedbackEventSchema = external_exports.object({
15359
15526
  client: external_exports.object({
15360
15527
  name: external_exports.string().max(64),
15361
15528
  version: external_exports.string().max(32)
15362
- }),
15363
- consent: external_exports.object({ telemetry: external_exports.boolean() })
15529
+ }).default({ name: "http", version: "0" }),
15530
+ consent: external_exports.object({ telemetry: external_exports.boolean() }).default({ telemetry: false })
15364
15531
  });
15365
15532
  var knownIssueSchema = external_exports.object({
15366
15533
  title: external_exports.string().max(120),
@@ -16087,7 +16254,7 @@ function inferAuthor(context) {
16087
16254
  }
16088
16255
  var CONFIG_FILE_NAME = "feedback.config.json";
16089
16256
  var DESCRIPTOR_CACHE_TTL_MS = 60 * 60 * 1e3;
16090
- var RESOLVE_TIMEOUT_MS = 5e3;
16257
+ var RESOLVE_TIMEOUT_MS = 1e4;
16091
16258
  function parseDsn(dsn) {
16092
16259
  let url2;
16093
16260
  try {
@@ -16203,7 +16370,17 @@ function destinationFromDescriptor(descriptor, to) {
16203
16370
  };
16204
16371
  }
16205
16372
  function registryUrl(env = process.env) {
16206
- return env[REGISTRY_ENV_VAR]?.trim() || DEFAULT_REGISTRY_URL;
16373
+ const value = env[REGISTRY_ENV_VAR]?.trim() || DEFAULT_REGISTRY_URL;
16374
+ try {
16375
+ const url2 = new URL(value);
16376
+ if (!["http:", "https:"].includes(url2.protocol) || url2.username || url2.password || url2.search || url2.hash || url2.pathname !== "/" || !/^[a-z0-9._:[\]-]+$/i.test(url2.hostname))
16377
+ throw new Error("Not an origin");
16378
+ return url2.origin;
16379
+ } catch {
16380
+ throw new FeedbackError(
16381
+ "Registry must be an HTTP(S) origin, such as https://feedback.example.com, without credentials, a path, query or fragment."
16382
+ );
16383
+ }
16207
16384
  }
16208
16385
  async function resolveDestination(options = {}) {
16209
16386
  const env = options.env ?? process.env;
@@ -16212,6 +16389,19 @@ async function resolveDestination(options = {}) {
16212
16389
  if (options.dsn) {
16213
16390
  return parseDsn(options.dsn);
16214
16391
  }
16392
+ if (options.registry !== void 0) {
16393
+ const to2 = options.to?.trim();
16394
+ if (!options.registry.trim() || !to2 || to2.includes(".")) {
16395
+ throw new FeedbackError("Use --registry with --to <project-slug>, not a domain.");
16396
+ }
16397
+ const origin = registryUrl({ [REGISTRY_ENV_VAR]: options.registry });
16398
+ return resolveViaDescriptor(
16399
+ `${origin}/v1/registry/${encodeURIComponent(to2)}`,
16400
+ to2,
16401
+ fetchImpl,
16402
+ cacheDir
16403
+ );
16404
+ }
16215
16405
  const envDsn = env[DSN_ENV_VAR]?.trim();
16216
16406
  if (envDsn) {
16217
16407
  return parseDsn(envDsn);
@@ -16274,23 +16464,24 @@ async function postEvent(options) {
16274
16464
  body = void 0;
16275
16465
  }
16276
16466
  const parsed = validateIngestResponse(body);
16277
- return {
16278
- kind: "accepted",
16279
- response: parsed.ok ? parsed.data : { id: "unknown", thread: options.event.thread.id }
16280
- };
16467
+ return parsed.ok ? { kind: "accepted", response: parsed.data } : { kind: "unreachable", error: "Server returned an invalid delivery receipt." };
16281
16468
  }
16282
16469
  var OUTBOX_FILE = "outbox.jsonl";
16283
- var MAX_OUTBOX_ENTRIES = 100;
16470
+ var MAX_FLUSH_ENTRIES = 100;
16284
16471
  var STALE_CLAIM_MS = 30 * 60 * 1e3;
16285
16472
  function outboxPath(env = process.env) {
16286
16473
  return path5.join(feedbackHomeDir(env), OUTBOX_FILE);
16287
16474
  }
16288
16475
  function appendToOutbox(entry, env = process.env) {
16476
+ return appendEntriesToOutbox([entry], env);
16477
+ }
16478
+ function appendEntriesToOutbox(entries, env) {
16479
+ if (entries.length === 0) return true;
16289
16480
  try {
16290
16481
  const file2 = outboxPath(env);
16291
16482
  mkdirSync2(path5.dirname(file2), { recursive: true });
16292
- appendFileSync(file2, `${JSON.stringify(entry)}
16293
- `);
16483
+ appendFileSync(file2, entries.map((entry) => `${JSON.stringify(entry)}
16484
+ `).join(""));
16294
16485
  return true;
16295
16486
  } catch {
16296
16487
  return false;
@@ -16332,24 +16523,30 @@ function claimQueue(env) {
16332
16523
  async function flushOutbox(options = {}) {
16333
16524
  const env = options.env ?? process.env;
16334
16525
  const claims = claimQueue(env);
16335
- const entries = claims.flatMap(readEntries).slice(-MAX_OUTBOX_ENTRIES);
16526
+ const entries = claims.flatMap(readEntries);
16527
+ const deadline = Date.now() + (options.maxDurationMs ?? Number.POSITIVE_INFINITY);
16336
16528
  const failures = [];
16529
+ let attempted = 0;
16337
16530
  let delivered = 0;
16338
- for (const entry of entries) {
16531
+ for (const entry of entries.slice(0, MAX_FLUSH_ENTRIES)) {
16532
+ const timeLeft = deadline - Date.now();
16533
+ if (timeLeft <= 0) break;
16534
+ attempted++;
16339
16535
  const result = await postEvent({
16340
16536
  endpoint: entry.endpoint,
16341
16537
  ...entry.writeKey ? { writeKey: entry.writeKey } : {},
16342
16538
  event: entry.event,
16343
- timeoutMs: options.timeoutMs ?? 5e3,
16539
+ timeoutMs: Math.max(1, Math.floor(Math.min(options.timeoutMs ?? 5e3, timeLeft))),
16344
16540
  ...options.fetchImpl ? { fetchImpl: options.fetchImpl } : {}
16345
16541
  });
16346
- if (result.kind === "accepted" || result.kind === "rejected") {
16347
- delivered += result.kind === "accepted" ? 1 : 0;
16542
+ if (result.kind === "accepted") {
16543
+ delivered++;
16348
16544
  } else {
16349
16545
  failures.push(entry);
16350
16546
  }
16351
16547
  }
16352
- if (failures.every((entry) => appendToOutbox(entry, env))) {
16548
+ const remaining = entries.slice(attempted).concat(failures);
16549
+ if (appendEntriesToOutbox(remaining, env)) {
16353
16550
  for (const claim of claims) {
16354
16551
  try {
16355
16552
  rmSync(claim, { force: true });
@@ -16357,16 +16554,22 @@ async function flushOutbox(options = {}) {
16357
16554
  }
16358
16555
  }
16359
16556
  }
16360
- return { delivered, remaining: failures.length };
16557
+ return { delivered, remaining: remaining.length };
16361
16558
  }
16362
16559
  async function resolveApiOrigin(options) {
16363
- if (!options.to && !options.dsn) {
16364
- return new URL(DEFAULT_REGISTRY_URL).origin;
16560
+ const env = options.env ?? process.env;
16561
+ if (options.registry !== void 0 && !options.to && !options.dsn) {
16562
+ if (!options.registry.trim()) throw new FeedbackError("Registry must not be empty.");
16563
+ return registryUrl({ [REGISTRY_ENV_VAR]: options.registry });
16564
+ }
16565
+ if (!options.to && !options.dsn && !env[DSN_ENV_VAR]?.trim()) {
16566
+ return registryUrl(env);
16365
16567
  }
16366
16568
  const destination = await resolveDestination({
16367
16569
  ...options.to !== void 0 ? { to: options.to } : {},
16368
16570
  ...options.dsn !== void 0 ? { dsn: options.dsn } : {},
16369
- ...options.env ? { env: options.env } : {},
16571
+ ...options.registry !== void 0 ? { registry: options.registry } : {},
16572
+ env,
16370
16573
  ...options.fetchImpl ? { fetchImpl: options.fetchImpl } : {}
16371
16574
  });
16372
16575
  return new URL(destination.endpoint).origin;
@@ -16375,12 +16578,12 @@ function ownerApi(options) {
16375
16578
  const origin = (options.apiOrigin ?? new URL(DEFAULT_REGISTRY_URL).origin).replace(/\/$/, "");
16376
16579
  const fetchImpl = options.fetchImpl ?? fetch;
16377
16580
  const timeoutMs = options.timeoutMs ?? 15e3;
16378
- async function request(path6, init) {
16581
+ async function request(path6, init, timeout = timeoutMs) {
16379
16582
  let response;
16380
16583
  try {
16381
16584
  response = await fetchImpl(`${origin}${path6}`, {
16382
16585
  ...init,
16383
- signal: AbortSignal.timeout(timeoutMs),
16586
+ signal: AbortSignal.timeout(timeout),
16384
16587
  headers: {
16385
16588
  authorization: `Bearer ${options.secretKey}`,
16386
16589
  ...init?.body ? { "content-type": "application/json" } : {},
@@ -16513,6 +16716,33 @@ function ownerApi(options) {
16513
16716
  body: JSON.stringify({ status, ...note !== void 0 ? { note } : {} })
16514
16717
  });
16515
16718
  },
16719
+ /** Draft a runnable version of an eval (a person approves it in the dashboard). */
16720
+ async draftEvalVersion(id, fields) {
16721
+ return request(`/v1/evals/${encodeURIComponent(id)}/versions`, {
16722
+ method: "POST",
16723
+ body: JSON.stringify(fields)
16724
+ });
16725
+ },
16726
+ /** Compiled Harbor tasks: every accepted eval's current version, or the given refs. */
16727
+ async pullEvalTasks(refs = []) {
16728
+ const query = refs.map((ref) => `ref=${encodeURIComponent(ref)}`).join("&");
16729
+ return request(`/v1/evals/tasks${query ? `?${query}` : ""}`);
16730
+ },
16731
+ /** Harbor trials read from a job directory, with their grades. */
16732
+ async pushEvalResults(trials) {
16733
+ return request("/v1/evals/results", {
16734
+ method: "POST",
16735
+ body: JSON.stringify({ trials })
16736
+ });
16737
+ },
16738
+ /** A stored trial's ATIF trajectory (`agent/trajectory.json`), as read from disk. */
16739
+ async pushTrialTrajectory(harborTrialId, trajectory) {
16740
+ return request(
16741
+ `/v1/evals/trials/${encodeURIComponent(harborTrialId)}/trajectory`,
16742
+ { method: "PUT", body: trajectory },
16743
+ Math.max(timeoutMs, 6e4)
16744
+ );
16745
+ },
16516
16746
  /** Attach an external link (Slack message, Linear/GitHub issue, PR) to evt_… or iss_…. */
16517
16747
  async addLink(id, url2, label) {
16518
16748
  const path6 = id.startsWith("iss_") ? `/v1/issues/${id}` : `/v1/events/${id}`;
@@ -16552,6 +16782,7 @@ async function prepareFeedback(options) {
16552
16782
  const resolveOptions = {
16553
16783
  ...options.to !== void 0 ? { to: options.to } : {},
16554
16784
  ...options.dsn !== void 0 ? { dsn: options.dsn } : {},
16785
+ ...options.registry !== void 0 ? { registry: options.registry } : {},
16555
16786
  cwd,
16556
16787
  env,
16557
16788
  ...options.fetchImpl ? { fetchImpl: options.fetchImpl } : {}
@@ -16676,9 +16907,14 @@ export {
16676
16907
  attachmentDescriptionSchema,
16677
16908
  ATTACHMENT_TARGET_TYPES,
16678
16909
  attachmentTargetSchema,
16910
+ SECRET_ENV_NAME,
16911
+ SECRET_VALUE_PATTERNS,
16912
+ MAX_TRIALS_PER_PUSH,
16913
+ MAX_TRAJECTORY_BYTES,
16679
16914
  PROTOCOL_VERSION,
16680
16915
  MAX_FEEDBACK_LENGTH,
16681
16916
  WELL_KNOWN_PATH,
16917
+ DEFAULT_REGISTRY_URL,
16682
16918
  FEEDBACK_CATEGORIES,
16683
16919
  EVENT_STATUSES,
16684
16920
  ISSUE_STATUSES,
@@ -16686,11 +16922,13 @@ export {
16686
16922
  sentimentSchema,
16687
16923
  authorSchema,
16688
16924
  evalReportSchema,
16925
+ threadSchema,
16689
16926
  feedbackEventSchema,
16690
16927
  validateFeedbackEvent,
16691
16928
  validateDescriptor,
16692
16929
  FeedbackError,
16693
16930
  parseDsn,
16931
+ findConfigFile,
16694
16932
  registryUrl,
16695
16933
  postEvent,
16696
16934
  flushOutbox,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  FeedbackError,
3
3
  attachmentDescriptionSchema
4
- } from "./chunk-DNR6HNJD.js";
4
+ } from "./chunk-A3UY57EQ.js";
5
5
 
6
6
  // src/attachment-flags.ts
7
7
  function attachmentFlags() {
package/dist/cli.js CHANGED
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  attachmentFlags
4
- } from "./chunk-AK23JXBN.js";
4
+ } from "./chunk-OEHURQBM.js";
5
5
  import {
6
+ DEFAULT_REGISTRY_URL,
6
7
  FEEDBACK_CATEGORIES,
7
8
  FeedbackError,
8
9
  MAX_ATTACHMENTS,
@@ -13,12 +14,42 @@ import {
13
14
  evalReportSchema,
14
15
  external_exports,
15
16
  feedbackEventSchema,
17
+ findConfigFile,
16
18
  flushOutbox,
17
19
  prepareFeedback,
20
+ registryUrl,
18
21
  require_arg,
19
22
  sendFeedback,
20
- sentimentSchema
21
- } from "./chunk-DNR6HNJD.js";
23
+ sentimentSchema,
24
+ threadSchema
25
+ } from "./chunk-A3UY57EQ.js";
26
+
27
+ // ../snippets/dist/index.js
28
+ function registryOrigin(input) {
29
+ if (!input.endpoint) return void 0;
30
+ const url = new URL(input.endpoint);
31
+ if (!["http:", "https:"].includes(url.protocol) || !/^[a-z0-9._:[\]-]+$/i.test(url.hostname)) {
32
+ throw new Error("The reporting endpoint must use an HTTP(S) server hostname.");
33
+ }
34
+ return url.origin === new URL(DEFAULT_REGISTRY_URL).origin ? void 0 : url.origin;
35
+ }
36
+ function invocationFlags(input) {
37
+ if (input.slug) {
38
+ const registry = registryOrigin(input);
39
+ return `${registry ? `--registry "${registry}" ` : ""}--to ${input.slug} `;
40
+ }
41
+ if (input.domain) return `--to ${input.domain} `;
42
+ if (input.dsn) return `--dsn "${input.dsn}" `;
43
+ return "";
44
+ }
45
+ function cliInvocation(input) {
46
+ const version = input.slug && registryOrigin(input) ? "^0.7.0" : "latest";
47
+ return `npx --yes hivenet@${version}`;
48
+ }
49
+ function agentCommand(input, extra = "", trailing = '"<specific, actionable feedback>"') {
50
+ const parts = [invocationFlags(input).trim(), extra.trim(), trailing].filter(Boolean);
51
+ return `${cliInvocation(input)} ${parts.join(" ")}`;
52
+ }
22
53
 
23
54
  // src/cli.ts
24
55
  var import_arg = __toESM(require_arg(), 1);
@@ -42,7 +73,7 @@ var inputSchema = feedbackEventSchema.pick({
42
73
  to: feedbackEventSchema.shape.to.unwrap().trim().min(1).optional(),
43
74
  dsn: external_exports.url().optional(),
44
75
  eval: evalReportSchema.strict().optional(),
45
- resume: feedbackEventSchema.shape.thread.shape.id.optional(),
76
+ resume: threadSchema.shape.id.optional(),
46
77
  question: feedbackEventSchema.shape.question.unwrap().min(1).optional(),
47
78
  attachments: external_exports.array(
48
79
  external_exports.preprocess(
@@ -174,18 +205,18 @@ function inputTooLarge() {
174
205
 
175
206
  // src/cli.ts
176
207
  var CLI_NAME = "hivenet";
177
- var CLI_VERSION = "0.6.0";
208
+ var CLI_VERSION = "0.7.1";
178
209
  var rawArgs = process.argv.slice(2);
179
210
  var flagArgs = rawArgs.slice(0, rawArgs.includes("--") ? rawArgs.indexOf("--") : void 0);
180
211
  async function main() {
181
212
  const subcommand = process.argv[2];
182
213
  if (subcommand === "doctor") {
183
- const { runDoctor } = await import("./doctor-EA2GJRUS.js");
214
+ const { runDoctor } = await import("./doctor-UOEWIXXV.js");
184
215
  await runDoctor(process.argv.slice(3), CLI_VERSION);
185
216
  return;
186
217
  }
187
218
  if (subcommand === "inbox" || subcommand === "triage" || subcommand === "issues" || subcommand === "issue" || subcommand === "questions" || subcommand === "ask" || subcommand === "question" || subcommand === "evals" || subcommand === "eval" || subcommand === "promote" || subcommand === "attach") {
188
- const owner = await import("./owner-ZWROA7AU.js");
219
+ const owner = await import("./owner-I3AOD2FJ.js");
189
220
  const run = {
190
221
  inbox: owner.runInbox,
191
222
  triage: owner.runTriage,
@@ -209,6 +240,7 @@ async function main() {
209
240
  "--version": Boolean,
210
241
  "--to": String,
211
242
  "--dsn": String,
243
+ "--registry": String,
212
244
  "--category": String,
213
245
  "--subject": String,
214
246
  "--sentiment": String,
@@ -259,7 +291,7 @@ async function main() {
259
291
  }
260
292
  let options;
261
293
  if (args["--input"] !== void 0) {
262
- const allowed = /* @__PURE__ */ new Set(["_", "--input", "--json", "--dry-run", "--to", "--dsn"]);
294
+ const allowed = /* @__PURE__ */ new Set(["_", "--input", "--json", "--dry-run", "--to", "--dsn", "--registry"]);
263
295
  const conflicts = Object.keys(args).filter((flag) => !allowed.has(flag));
264
296
  if (args._.length || conflicts.length)
265
297
  throw new FeedbackInputError(
@@ -305,6 +337,7 @@ Usage: npx ${CLI_NAME} --to <slug|domain> "<specific, actionable feedback>"`
305
337
  ...args["--attach"] ? { attachments: args["--attach"] } : {}
306
338
  };
307
339
  }
340
+ if (args["--registry"] !== void 0) options.registry = args["--registry"];
308
341
  options.clientName = CLI_NAME;
309
342
  options.clientVersion = CLI_VERSION;
310
343
  if (args["--dry-run"]) {
@@ -312,11 +345,38 @@ Usage: npx ${CLI_NAME} --to <slug|domain> "<specific, actionable feedback>"`
312
345
  console.log(JSON.stringify(preview, null, 2));
313
346
  return;
314
347
  }
315
- flushOutbox({ timeoutMs: 3e3 }).catch(() => {
348
+ flushOutbox({ timeoutMs: 3e3, maxDurationMs: 250 }).catch(() => {
316
349
  });
350
+ const requestedTo = options.to?.trim() ?? findConfigFile(options.cwd ?? process.cwd())?.config.slug;
351
+ const selectedDsn = options.dsn || process.env.HIVENET_DSN?.trim();
317
352
  const result = await sendFeedback(options);
318
353
  if (!result.delivered && !result.queued) process.exitCode = 1;
319
- const resume = (result.delivered ? result.response.resume : void 0) ?? `npx ${CLI_NAME}${result.destination.to ? ` --to ${result.destination.to}` : ""} --resume ${result.event.thread.id}`;
354
+ const destination = result.destination;
355
+ let directDsn;
356
+ if (selectedDsn && !destination.descriptor) {
357
+ const url = new URL(destination.endpoint);
358
+ url.username = destination.writeKey ?? "";
359
+ url.pathname = destination.to ?? "";
360
+ directDsn = url.toString();
361
+ }
362
+ const resume = agentCommand(
363
+ destination.descriptor ? requestedTo?.includes(".") ? { domain: requestedTo } : {
364
+ ...destination.to ? { slug: destination.to } : {},
365
+ endpoint: `${registryUrl(options.registry !== void 0 ? { HIVENET_REGISTRY: options.registry } : process.env)}/v1/feedback`
366
+ } : directDsn ? { dsn: directDsn } : (
367
+ // A config-only endpoint need not expose a registry or /v1/feedback.
368
+ // Continue using that config, including its exact path and optional key.
369
+ { ...destination.to ? { slug: destination.to } : {} }
370
+ ),
371
+ `--resume ${result.delivered ? result.response.thread : result.event.thread.id}`,
372
+ ""
373
+ );
374
+ if (result.delivered) {
375
+ result.response.resume = resume;
376
+ if (result.response.ask) {
377
+ result.response.ask.command = `${resume} --answer ${result.response.ask.id} "<your grounded answer>"`;
378
+ }
379
+ }
320
380
  const answer = result.event.question;
321
381
  const attach = `${resume}${answer ? ` --answer ${answer}` : ""} --attach <path> "<what it shows>"`;
322
382
  const attachHint = result.filesNotAttached ? `Add them later: ${attach}` : result.delivered && !result.event.attachments?.length && !answer ? `Have a screenshot or log that shows this? Attach it: ${attach}` : void 0;
@@ -327,7 +387,8 @@ Usage: npx ${CLI_NAME} --to <slug|domain> "<specific, actionable feedback>"`
327
387
  ...result.delivered ? {
328
388
  delivered: true,
329
389
  ...result.destination.to ? { to: result.destination.to } : {},
330
- ...result.response
390
+ ...result.response,
391
+ ...!result.response.duplicate ? { attachmentCount: result.event.attachments?.length ?? 0 } : {}
331
392
  } : {
332
393
  delivered: false,
333
394
  queued: result.queued,
@@ -346,11 +407,20 @@ Usage: npx ${CLI_NAME} --to <slug|domain> "<specific, actionable feedback>"`
346
407
  const toLabel = result.destination.to ?? result.destination.endpoint;
347
408
  if (result.delivered) {
348
409
  console.log(`\u2713 Feedback sent to ${toLabel} (${result.response.id})`);
349
- if (result.event.attachments?.length)
410
+ if (!result.response.duplicate && result.event.attachments?.length)
350
411
  console.log(` Attached ${result.event.attachments.length} file(s).`);
351
412
  console.log(` Continue this thread: ${resume}`);
352
413
  if (attachHint && !result.filesNotAttached) console.log(` ${attachHint}`);
353
- if (result.response.guidance) {
414
+ const known = result.response.known_issue;
415
+ if (known) {
416
+ console.log(
417
+ ` Known issue (tracker data): ${JSON.stringify(known.title)} (${known.status}, ${known.reports} reports)`
418
+ );
419
+ console.log(` ${known.hint}`);
420
+ if (known.note) console.log(` Owner decision: ${known.note}`);
421
+ }
422
+ const mirrored = known && result.response.guidance?.startsWith("[known issue]");
423
+ if (result.response.guidance && !mirrored) {
354
424
  console.log(` From ${toLabel}: ${result.response.guidance}`);
355
425
  }
356
426
  if (result.response.ask) {
@@ -451,6 +521,7 @@ function printHelp() {
451
521
  Options
452
522
  --to, -t <slug|domain> Destination: registry slug (acme) or domain (acme.dev)
453
523
  --dsn <url> Explicit DSN: https://hv_pub_KEY@host/slug
524
+ --registry <origin> Use this server's registry with --to <slug> (overrides local settings)
454
525
  --category, -c <cat> ${FEEDBACK_CATEGORIES.join(" | ")} | x-<vendor>
455
526
  --subject, -s <subject> Exact item the feedback is about (see table below)
456
527
  --meta <key=value> Vendor-defined enrichment, repeatable (e.g. --meta sdk_version=1.2.3)
@@ -501,7 +572,7 @@ function printHelp() {
501
572
  feedback is still delivered anonymously.
502
573
 
503
574
  Destination resolution
504
- --dsn \u2192 HIVENET_DSN \u2192 nearest feedback.config.json \u2192 --to slug (registry)
575
+ --dsn \u2192 --registry + --to \u2192 HIVENET_DSN \u2192 nearest feedback.config.json \u2192 --to slug (registry)
505
576
  \u2192 --to domain (https://<domain>/.well-known/agent-feedback.json)
506
577
  `);
507
578
  }