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 +14 -0
- package/dist/{chunk-DNR6HNJD.js → chunk-A3UY57EQ.js} +263 -25
- package/dist/{chunk-AK23JXBN.js → chunk-OEHURQBM.js} +1 -1
- package/dist/cli.js +85 -14
- package/dist/{doctor-EA2GJRUS.js → doctor-UOEWIXXV.js} +7 -2
- package/dist/index.cjs +249 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +174 -24
- package/dist/index.d.ts +174 -24
- package/dist/index.js +248 -28
- package/dist/index.js.map +1 -1
- package/dist/owner-I3AOD2FJ.js +1005 -0
- package/package.json +2 -1
- package/dist/owner-ZWROA7AU.js +0 -539
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:
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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)
|
|
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"
|
|
16347
|
-
delivered
|
|
16542
|
+
if (result.kind === "accepted") {
|
|
16543
|
+
delivered++;
|
|
16348
16544
|
} else {
|
|
16349
16545
|
failures.push(entry);
|
|
16350
16546
|
}
|
|
16351
16547
|
}
|
|
16352
|
-
|
|
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:
|
|
16557
|
+
return { delivered, remaining: remaining.length };
|
|
16361
16558
|
}
|
|
16362
16559
|
async function resolveApiOrigin(options) {
|
|
16363
|
-
|
|
16364
|
-
|
|
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.
|
|
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(
|
|
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,
|
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
attachmentFlags
|
|
4
|
-
} from "./chunk-
|
|
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
|
-
|
|
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:
|
|
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.
|
|
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-
|
|
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-
|
|
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
|
|
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
|
-
|
|
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
|
}
|