@vecteur/cli 0.5.4 → 0.5.6
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/bin/vecteur.js +34 -4
- package/package.json +1 -1
- package/src/client.js +59 -2
- package/src/contract.js +4 -3
- package/src/download.js +12 -2
- package/src/format.js +45 -6
- package/src/mcp.js +7 -5
package/bin/vecteur.js
CHANGED
|
@@ -9,9 +9,9 @@ import {
|
|
|
9
9
|
import {
|
|
10
10
|
clearCredentials, credentialsPath, readCredentials, setCurrentOrigin, writeCredentials, selectProject,
|
|
11
11
|
} from "../src/credentials.js";
|
|
12
|
-
import { downloadArtifact } from "../src/download.js";
|
|
12
|
+
import { downloadArtifact, downloadProjection } from "../src/download.js";
|
|
13
13
|
import { humanProjects, resolveNamed, resolveRunProject, resolveWorkspace } from "../src/projects.js";
|
|
14
|
-
import { canonicalJson, human, humanCost, progress } from "../src/format.js";
|
|
14
|
+
import { canonicalJson, currentTransferTables, human, humanCost, progress } from "../src/format.js";
|
|
15
15
|
|
|
16
16
|
function manifest() {
|
|
17
17
|
return JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
@@ -43,7 +43,7 @@ function refuseOldNode() {
|
|
|
43
43
|
|
|
44
44
|
function helpText(version) {
|
|
45
45
|
return `vecteur ${version}
|
|
46
|
-
login | logout | whoami | project list | project create <name> [--workspace <name-or-id>] | project use <name-or-id> | project get <id> | run submit [-p <id>] -a <ask> | run get <id> | run events <id> | artifact get <id> [--output <path>] [-p <id>] | mcp
|
|
46
|
+
login | logout | whoami | project list | project create <name> [--workspace <name-or-id>] | project use <name-or-id> | project get <id> | run submit [-p <id>] -a <ask> | run get <id> | run events <id> | artifact get <id> [--output <path>] [-p <id>] | projection get <run-id> <projection-id> [--output <path>] [-p <id>] | mcp
|
|
47
47
|
--help --version --json --origin <url> --no-browser --local -p/--project -a/--ask --output <path>
|
|
48
48
|
`;
|
|
49
49
|
}
|
|
@@ -156,6 +156,10 @@ function parseCommand(argv) {
|
|
|
156
156
|
&& ask === undefined) {
|
|
157
157
|
return { kind: "artifact_get", id: args[2], projectId, output, origin, json };
|
|
158
158
|
}
|
|
159
|
+
if (args.length === 4 && args[0] === "projection" && args[1] === "get"
|
|
160
|
+
&& value(args[2]) && value(args[3]) && ask === undefined) {
|
|
161
|
+
return { kind: "projection_get", runId: args[2], id: args[3], projectId, output, origin, json };
|
|
162
|
+
}
|
|
159
163
|
throw new CliError("usage_invalid", "Invalid command grammar");
|
|
160
164
|
}
|
|
161
165
|
|
|
@@ -319,11 +323,23 @@ async function main() {
|
|
|
319
323
|
: `Downloaded ${value.bytes} bytes to ${value.path} (${value.content_type})\n`);
|
|
320
324
|
return;
|
|
321
325
|
}
|
|
326
|
+
else if (command.kind === "projection_get") {
|
|
327
|
+
const projectId = await resolveRunProject(client, command.projectId);
|
|
328
|
+
const run = await client.runGet(command.runId);
|
|
329
|
+
const matches = run.events.filter(event => event.kind === "engineering_projection"
|
|
330
|
+
&& event.payload?.projection_id === command.id && event.payload?.kind === "transfer_table");
|
|
331
|
+
if (matches.length !== 1) throw new CliError("projection_invalid", "Run has no unique transfer table projection");
|
|
332
|
+
value = await downloadProjection(client, projectId, command.runId, matches[0].payload, command.output);
|
|
333
|
+
process.stdout.write(command.json ? canonicalJson(value)
|
|
334
|
+
: `Downloaded ${value.bytes} bytes to ${value.path} (${value.content_type}; ${value.disposition})\n`);
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
322
337
|
else if (command.kind === "run_submit") {
|
|
323
338
|
const projectId = await resolveRunProject(client, command.projectId);
|
|
324
339
|
value = await client.runSubmit(projectId, command.ask, onEvent);
|
|
325
340
|
if (!command.json) {
|
|
326
|
-
|
|
341
|
+
const currentTransferIds = await verifiedTransferIds(client, value);
|
|
342
|
+
process.stdout.write(human(value, { projectId, currentTransferIds }));
|
|
327
343
|
try {
|
|
328
344
|
const cost = await client.customerCost(projectId);
|
|
329
345
|
process.stdout.write(`\n${humanCost(cost)}`);
|
|
@@ -341,9 +357,23 @@ async function main() {
|
|
|
341
357
|
if (!command.json && !renderOptions.projectId && config.projectId) {
|
|
342
358
|
renderOptions = { projectId: config.projectId };
|
|
343
359
|
}
|
|
360
|
+
if (!command.json && Array.isArray(value?.events)) {
|
|
361
|
+
renderOptions.currentTransferIds = await verifiedTransferIds(client, value);
|
|
362
|
+
}
|
|
344
363
|
process.stdout.write(command.json ? canonicalJson(value) : human(value, renderOptions));
|
|
345
364
|
}
|
|
346
365
|
|
|
366
|
+
async function verifiedTransferIds(client, run) {
|
|
367
|
+
const ids = new Set();
|
|
368
|
+
for (const projection of currentTransferTables(run.events ?? [])) {
|
|
369
|
+
const read = await client.projectionGet(
|
|
370
|
+
projection.project_id, run.run_id, projection, async () => {},
|
|
371
|
+
);
|
|
372
|
+
if (read.disposition === "current") ids.add(projection.projection_id);
|
|
373
|
+
}
|
|
374
|
+
return ids;
|
|
375
|
+
}
|
|
376
|
+
|
|
347
377
|
main().catch((error) => {
|
|
348
378
|
const code = error instanceof CliError ? error.code : "internal_error";
|
|
349
379
|
process.stderr.write(`${code}: ${redact(error?.message ?? "CLI failed")}\n`);
|
package/package.json
CHANGED
package/src/client.js
CHANGED
|
@@ -278,7 +278,7 @@ export class VecteurClient {
|
|
|
278
278
|
this.fetchImpl = fetchImpl;
|
|
279
279
|
}
|
|
280
280
|
|
|
281
|
-
async request(method, path, { body, idempotencyKey, accept, signal } = {}) {
|
|
281
|
+
async request(method, path, { body, idempotencyKey, accept, acceptEncoding, signal } = {}) {
|
|
282
282
|
const controller = new AbortController();
|
|
283
283
|
const cancel = () => controller.abort();
|
|
284
284
|
signal?.addEventListener("abort", cancel, { once: true });
|
|
@@ -316,6 +316,7 @@ export class VecteurClient {
|
|
|
316
316
|
...(body === undefined ? {} : { "Content-Type": "application/json" }),
|
|
317
317
|
...(idempotencyKey ? { "Idempotency-Key": idempotencyKey } : {}),
|
|
318
318
|
...(accept ? { Accept: accept } : {}),
|
|
319
|
+
...(acceptEncoding ? { "Accept-Encoding": acceptEncoding } : {}),
|
|
319
320
|
},
|
|
320
321
|
body: body === undefined ? undefined : JSON.stringify(body),
|
|
321
322
|
});
|
|
@@ -488,13 +489,19 @@ export class VecteurClient {
|
|
|
488
489
|
}
|
|
489
490
|
const query = new URLSearchParams({ project_id: projectId });
|
|
490
491
|
const response = await this.request(
|
|
491
|
-
"GET", `/api/artifacts/${encodeURIComponent(casRef)}?${query}`, {
|
|
492
|
+
"GET", `/api/artifacts/${encodeURIComponent(casRef)}?${query}`, {
|
|
493
|
+
acceptEncoding: "identity", signal,
|
|
494
|
+
},
|
|
492
495
|
);
|
|
493
496
|
try {
|
|
494
497
|
const contentType = response.headers.get("content-type");
|
|
495
498
|
if (!Object.values(ARTIFACT_MEDIA_TYPES).includes(contentType)) {
|
|
496
499
|
throw new CliError("artifact_invalid", "Server returned an undeclared artifact media type");
|
|
497
500
|
}
|
|
501
|
+
const contentEncoding = response.headers.get("content-encoding");
|
|
502
|
+
if (contentEncoding && contentEncoding.toLowerCase() !== "identity") {
|
|
503
|
+
throw new CliError("artifact_invalid", "Server returned encoded artifact bytes");
|
|
504
|
+
}
|
|
498
505
|
const declared = response.headers.get("content-length");
|
|
499
506
|
if (declared === null || !/^(?:0|[1-9][0-9]*)$/.test(declared)
|
|
500
507
|
|| !Number.isSafeInteger(Number(declared))) {
|
|
@@ -528,6 +535,56 @@ export class VecteurClient {
|
|
|
528
535
|
}
|
|
529
536
|
}
|
|
530
537
|
|
|
538
|
+
async projectionGet(projectId, runId, projection, onChunk, signal = undefined) {
|
|
539
|
+
const ref = projection?.payload_ref;
|
|
540
|
+
if (typeof projectId !== "string" || !projectId || typeof runId !== "string" || !runId
|
|
541
|
+
|| projection?.kind !== "transfer_table" || projection?.run_id !== runId
|
|
542
|
+
|| projection?.project_id !== projectId
|
|
543
|
+
|| !/^projection-[0-9a-f]{64}$/.test(projection?.projection_id ?? "")
|
|
544
|
+
|| ref?.schema !== "vecteur.owner-transfer-table.v1"
|
|
545
|
+
|| ref?.media_type !== "application/vnd.vecteur.owner-transfer-table+json"
|
|
546
|
+
|| !/^[0-9a-f]{64}$/.test(ref?.payload_sha256 ?? "")
|
|
547
|
+
|| typeof onChunk !== "function") {
|
|
548
|
+
throw new CliError("projection_invalid", "projection get requires a sealed transfer table on this Run");
|
|
549
|
+
}
|
|
550
|
+
const route = `/api/projects/${encodeURIComponent(projectId)}/runs/${encodeURIComponent(runId)}`
|
|
551
|
+
+ `/projections/${encodeURIComponent(projection.projection_id)}/payload`;
|
|
552
|
+
const response = await this.request("GET", route, {
|
|
553
|
+
accept: ref.media_type, acceptEncoding: "identity", signal,
|
|
554
|
+
});
|
|
555
|
+
try {
|
|
556
|
+
if (response.headers.get("content-type")?.split(";", 1)[0] !== ref.media_type
|
|
557
|
+
|| (response.headers.get("content-encoding") ?? "identity").toLowerCase() !== "identity") {
|
|
558
|
+
throw new CliError("projection_invalid", "Server returned the wrong projection media type");
|
|
559
|
+
}
|
|
560
|
+
const disposition = response.headers.get("x-vecteur-projection-disposition");
|
|
561
|
+
if (disposition !== "current" && disposition !== "historical") {
|
|
562
|
+
throw new CliError("projection_invalid", "Server returned no valid projection disposition");
|
|
563
|
+
}
|
|
564
|
+
const declared = response.headers.get("content-length");
|
|
565
|
+
if (declared === null || !/^(?:0|[1-9][0-9]*)$/.test(declared)
|
|
566
|
+
|| Number(declared) > 256 * 1024 || !response.body) {
|
|
567
|
+
throw new CliError("projection_invalid", "Projection byte length is invalid");
|
|
568
|
+
}
|
|
569
|
+
const digest = createHash("sha256");
|
|
570
|
+
let bytes = 0;
|
|
571
|
+
for await (const chunk of response.body) {
|
|
572
|
+
response[RESPONSE_ACTIVITY]?.();
|
|
573
|
+
bytes += chunk.byteLength;
|
|
574
|
+
if (bytes > Number(declared)) throw new CliError("projection_invalid", "Projection exceeds its declared size");
|
|
575
|
+
digest.update(chunk);
|
|
576
|
+
await onChunk(chunk);
|
|
577
|
+
}
|
|
578
|
+
if (bytes !== Number(declared) || digest.digest("hex") !== ref.payload_sha256) {
|
|
579
|
+
throw new CliError("projection_invalid", "Projection bytes differ from RunWire");
|
|
580
|
+
}
|
|
581
|
+
return { projection_id: projection.projection_id, project_id: projectId,
|
|
582
|
+
run_id: runId, content_type: ref.media_type, bytes, disposition };
|
|
583
|
+
} finally {
|
|
584
|
+
response[RELEASE_TIMEOUT]?.();
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
531
588
|
async runSubmit(projectId, ask, onEvent = null, signal = undefined) {
|
|
532
589
|
const value = await this.#json("POST", `/api/projects/${encodeURIComponent(projectId)}/runs`, {
|
|
533
590
|
body: { ask, attachments: [], parent_state_hash: null, ui_context: null },
|
package/src/contract.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// GENERATED by scripts/gen-contract.mjs from owner TypeScript projections.
|
|
2
|
-
export const OWNER_CONTRACT_IDENTITY = "api-sha256:
|
|
2
|
+
export const OWNER_CONTRACT_IDENTITY = "api-sha256:8f81ea544eecd9e81da3fd56ad816ac24991cb0e68f3b1cb45180711eaf16056;run-wire-sha256:823acb39f2ff5bca7ea54db4beb4589d681bedc5673867a6e6dcdf5c212cf7db";
|
|
3
3
|
export const RECOVERY_ACTIONS = Object.freeze({"delete_project":{"label":"Delete a project","href":"/projects"},"upgrade_plan":{"label":"Upgrade your plan","href":"/dashboard/billing"},"wait_for_reset":{"label":"Wait until the UTC reset","href":null}});
|
|
4
|
-
export const EVENT_KINDS = Object.freeze(["source","step_started","step_finished","tool_call","tool_result","synthesis","artifact","terminal","heartbeat","assistant_delta","knowledge_binding","engineering_projection","provider_attempt","clarification","input_rows"]);
|
|
4
|
+
export const EVENT_KINDS = Object.freeze(["source","step_started","step_finished","tool_call","tool_result","synthesis","artifact","terminal","heartbeat","assistant_delta","knowledge_binding","engineering_projection","provider_attempt","clarification","clarification_evidence","input_rows"]);
|
|
5
5
|
export const RETIRED_EVENT_KINDS = Object.freeze(["context_frame","cta","step_label"]);
|
|
6
6
|
export const TERMINAL_FIELDS = Object.freeze(["answer","defect","deliverable_asked","evidence_dag","instruction_source","intent","missing_input_need","missing_inputs","provider_retry_offer","recovery","refusal_unfounded","state","steps","unmet_wanted"]);
|
|
7
|
+
export const TERMINAL_STATES = Object.freeze(["ok","partial","blocked","refused","infeasible_physics","capability_gap"]);
|
|
7
8
|
export const ARTIFACT_MEDIA_TYPES = Object.freeze({"binary":"application/octet-stream","code":"text/plain","csv":"text/csv","docx":"application/vnd.openxmlformats-officedocument.wordprocessingml.document","drive":"application/vnd.google-drive","html":"text/html","jpeg":"image/jpeg","json":"application/json","md":"text/markdown","pdf":"application/pdf","png":"image/png","svg":"image/svg+xml","xlsx":"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"});
|
|
8
9
|
export const TERMINAL_RECOVERY_DECLARATIONS = Object.freeze({"answer_now":[["start_run",["project_id"]]],"artifact_publication_missing":[["start_run",["project_id"]]],"artifact_store_unavailable":[["start_run",["project_id"]]],"budget_exhausted":[["start_run",["project_id"]]],"cancelled":[["start_run",["project_id"]]],"cause_unrecorded":[["start_run",["project_id"]]],"clarification_custody_invalid":[["start_run",["project_id"]]],"clarification_custody_lost":[["start_run",["project_id"]]],"clarification_resume_invalid":[["start_run",["project_id"]]],"clarification_unpersisted":[["start_run",["project_id"]]],"context_unselected":[["start_run",["project_id"]]],"credential_class_disclosure":[["start_run",["project_id"]]],"deliverable_missing":[["start_run",["project_id"]]],"design_state_missing":[["start_run",["project_id"]]],"evidence_admission_invalid":[["start_run",["project_id"]]],"evidence_artifact_invalid":[["start_run",["project_id"]]],"evidence_artifact_unbound":[["start_run",["project_id"]]],"evidence_binding_unresolved":[["start_run",["project_id"]]],"evidence_context_invalid":[["start_run",["project_id"]]],"evidence_extract_unreached":[["start_run",["project_id"]]],"evidence_missing":[["start_run",["project_id"]]],"evidence_owner_receipt_mismatch":[["start_run",["project_id"]]],"evidence_owner_receipt_missing":[["start_run",["project_id"]]],"evidence_payload_invalid":[["start_run",["project_id"]]],"evidence_reached_invalid":[["start_run",["project_id"]]],"evidence_replay_invalid":[["start_run",["project_id"]]],"evidence_run_mismatch":[["start_run",["project_id"]]],"evidence_seal_collision":[["start_run",["project_id"]]],"evidence_selected_unreached":[["start_run",["project_id"]]],"execution_route_invalid":[["start_run",["project_id"]]],"executor_root_invalid":[["start_run",["project_id"]]],"extraction_offer_invalid":[["start_run",["project_id"]]],"extraction_offer_tampered":[["start_run",["project_id"]]],"fact_support_unread":[["start_run",["project_id"]]],"harness_arm_invalid":[["start_run",["project_id"]]],"harness_event_unmapped":[["start_run",["project_id"]]],"harness_exited":[["start_run",["project_id"]]],"harness_mapping_failed":[["start_run",["project_id"]]],"harness_session_invalid":[["start_run",["project_id"]]],"harness_session_missing":[["start_run",["project_id"]]],"harness_timeout":[["start_run",["project_id"]]],"infeasible_physics":[["start_run",["project_id"]]],"lab_denied":[["start_run",["project_id"]]],"lab_failed":[["start_run",["project_id"]]],"lab_not_reproduced":[["start_run",["project_id"]]],"lease_interval_invalid":[["start_run",["project_id"]]],"ledger_malformed":[["start_run",["project_id"]]],"max_tokens":[["start_run",["project_id"]]],"no_facts":[["start_run",["project_id"]]],"not_a_decision_i_can_make":[["start_run",["project_id"]]],"out_of_physics":[["start_run",["project_id"]]],"profile_eligibility_changed":[["start_run",["project_id"]]],"prompt_provenance_unavailable":[["start_run",["project_id"]]],"provider_budget_exhausted":[["start_run",["project_id"]]],"provider_budget_missing":[["start_run",["project_id"]]],"provider_budget_unavailable":[["start_run",["project_id"]]],"provider_relay_absent":[["start_run",["project_id"]]],"provider_route_control_invalid":[["start_run",["project_id"]]],"provider_route_exhausted":[["start_run",["project_id"]],["retry_run",["source_run_id","offer_id","generation"]]],"provider_route_failure_unobserved":[["start_run",["project_id"]]],"provider_route_reconciliation_invalid":[["start_run",["project_id"]]],"provider_route_restart_terminal":[["start_run",["project_id"]]],"provider_training_opted_in":[["start_run",["project_id"]]],"publish_seal_missing":[["start_run",["project_id"]]],"refusal_unfounded":[["start_run",["project_id"]]],"replay_capture_digest_mismatch":[["start_run",["project_id"]]],"replay_capture_invalid":[["start_run",["project_id"]]],"replay_config_incomplete":[["start_run",["project_id"]]],"replay_failed":[["start_run",["project_id"]]],"replay_no_requests":[["start_run",["project_id"]]],"replay_patch_invalid":[["start_run",["project_id"]]],"replay_plugin_absent":[["start_run",["project_id"]]],"replay_provider_socket":[["start_run",["project_id"]]],"replay_receipt_invalid":[["start_run",["project_id"]]],"replay_receipt_owner_chown":[["start_run",["project_id"]]],"replay_receipt_owner_incomplete":[["start_run",["project_id"]]],"replay_receipt_owner_invalid":[["start_run",["project_id"]]],"replay_request_observation_count":[["start_run",["project_id"]]],"replay_request_observation_invalid":[["start_run",["project_id"]]],"replay_request_observation_missing":[["start_run",["project_id"]]],"replay_request_owner_chown":[["start_run",["project_id"]]],"replay_socket_observation_invalid":[["start_run",["project_id"]]],"replay_socket_observation_missing":[["start_run",["project_id"]]],"replay_socket_owner_chown":[["start_run",["project_id"]]],"row_budget":[["start_run",["project_id"]]],"run_database_capability_refused":[["start_run",["project_id"]]],"runtime_unavailable":[["start_run",["project_id"]]],"sealed_contract_violated":[["start_run",["project_id"]]],"sealed_file_missing":[["start_run",["project_id"]]],"seat_patch_absent":[["start_run",["project_id"]]],"seat_patch_drift":[["start_run",["project_id"]]],"selection_admission_invalid":[["start_run",["project_id"]]],"selection_context_invalid":[["start_run",["project_id"]]],"selection_receipt_conflict":[["start_run",["project_id"]]],"selection_receipt_invalid":[["start_run",["project_id"]]],"selection_receipt_missing":[["start_run",["project_id"]]],"selection_required":[["start_run",["project_id"]]],"state_corrupt":[["start_run",["project_id"]]],"state_file_invalid":[["start_run",["project_id"]]],"state_invalid":[["start_run",["project_id"]]],"state_payload_overflow":[["start_run",["project_id"]]],"state_unencodable":[["start_run",["project_id"]]],"state_version_unknown":[["start_run",["project_id"]]],"unreceipted_numbers":[["start_run",["project_id"]]],"usage_egress_mismatch":[["start_run",["project_id"]]],"wall_budget":[["start_run",["project_id"]]],"wanted_not_computed":[["start_run",["project_id"]]]});
|
|
9
10
|
export const ORIGIN_KINDS = Object.freeze(["user","hypothesis","derived","knowledge","file","catalog","default"]);
|
|
@@ -47,6 +48,6 @@ const dimension=(v)=>shape(v,["uncached_input","cache_read","cache_write","outpu
|
|
|
47
48
|
const usage=(v)=>exact(v,["coverage","provider_id","model_id","observed_at","billable_dimensions"])&&["complete","incomplete"].includes(v.coverage)&&[v.provider_id,v.model_id].every(text)&&integer(v.observed_at)&&v.observed_at>=0&&Array.isArray(v.billable_dimensions)&&v.billable_dimensions.length>0&&v.billable_dimensions.every(dimension);
|
|
48
49
|
const control=v=>ownerRunControl(v)&&/^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/.test(v.id)&&integer(v.sequence)&&v.sequence>=1&&v.sequence<=32&&(v.kind==="steer"?typeof v.text==="string"&&v.text.trim().length>0&&Buffer.byteLength(v.text,"utf8")<=8192:v.text===undefined||v.text===null)&&((v.attempt===null&&v.step===null)||(integer(v.attempt)&&v.attempt>=0&&integer(v.step)&&v.step>0))&&(v.egress===null||(integer(v.egress.candidate)&&v.egress.candidate>=0&&integer(v.egress.call)&&v.egress.call>=0))&&(v.state==="queued"?v.attempt===null&&v.egress===null:v.state==="claimed"?v.attempt!==null&&v.egress===null:v.state==="dispatched"?v.attempt!==null&&v.egress!==null:v.egress===null);
|
|
49
50
|
const controls=v=>Array.isArray(v)&&v.length<=32&&v.every(control)&&new Set(v.map(x=>x.id)).size===v.length&&v.every((x,i)=>i===0||v[i-1].sequence<x.sequence);
|
|
50
|
-
export const validateAdmitted=(v)=>shape(v,["run_id","state_hash","result_hash","requested_route_id","profile","terminal","usage","events","controls"],["duration_seconds"])&&[v.run_id,v.state_hash,v.
|
|
51
|
+
export const validateAdmitted=(v)=>shape(v,["run_id","state_hash","result_hash","requested_route_id","profile","terminal","usage","events","controls"],["duration_seconds"])&&[v.run_id,v.state_hash,v.requested_route_id,v.profile].every(text)&&((v.terminal===""&&v.result_hash===""&&Array.isArray(v.events)&&(v.events.length===0||v.events.at(-1)?.kind!=="terminal"))||(TERMINAL_STATES.includes(v.terminal)&&text(v.result_hash)))&&usage(v.usage)&&Array.isArray(v.events)&&controls(v.controls)&&(v.duration_seconds===undefined||v.duration_seconds===null||(integer(v.duration_seconds)&&v.duration_seconds>=0));
|
|
51
52
|
export const validatePatCreated=(v)=>exact(v,["id","name","token","prefix","project_id","scopes","created_at","expires_at"])&&[v.id,v.name,v.token,v.prefix].every(text)&&(v.project_id===null||text(v.project_id))&&Array.isArray(v.scopes)&&v.scopes.every(s=>["project:read","project:execute"].includes(s))&&integer(v.created_at)&&integer(v.expires_at);
|
|
52
53
|
export const validateDeviceAuthorization=(v)=>exact(v,["device_code","user_code","verification_uri","verification_uri_complete","expires_at","interval"])&&[v.device_code,v.user_code,v.verification_uri,v.verification_uri_complete].every(text)&&integer(v.expires_at)&&integer(v.interval)&&v.interval>=0;
|
package/src/download.js
CHANGED
|
@@ -14,12 +14,22 @@ function defaultName(casRef) {
|
|
|
14
14
|
* a digest-derived basename, so traversal and shell metacharacters cannot arrive from RunWire.
|
|
15
15
|
*/
|
|
16
16
|
export async function downloadArtifact(client, casRef, projectId, output) {
|
|
17
|
-
|
|
17
|
+
return downloadTo(output ?? defaultName(casRef),
|
|
18
|
+
chunk => client.artifactGet(casRef, projectId, chunk));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function downloadProjection(client, projectId, runId, projection, output) {
|
|
22
|
+
return downloadTo(output ?? `transfer-table-${projection.projection_id.slice(11, 23)}.json`,
|
|
23
|
+
chunk => client.projectionGet(projectId, runId, projection, chunk));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function downloadTo(filename, fetchBytes) {
|
|
27
|
+
const target = resolve(filename);
|
|
18
28
|
const temporary = resolve(dirname(target), `.vecteur-${basename(target)}-${randomUUID()}.part`);
|
|
19
29
|
let handle;
|
|
20
30
|
try {
|
|
21
31
|
handle = await open(temporary, "wx", 0o600);
|
|
22
|
-
const result = await
|
|
32
|
+
const result = await fetchBytes(chunk => handle.writeFile(chunk));
|
|
23
33
|
await handle.sync();
|
|
24
34
|
await handle.close();
|
|
25
35
|
handle = undefined;
|
package/src/format.js
CHANGED
|
@@ -207,8 +207,30 @@ function recoveryLine(action) {
|
|
|
207
207
|
return `Recovery: ${action.capability}${args ? ` ${args}` : ""}`;
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
-
export function
|
|
211
|
-
const
|
|
210
|
+
export function currentTransferTables(events) {
|
|
211
|
+
const candidates = new Map();
|
|
212
|
+
const cleared = new Map();
|
|
213
|
+
for (const event of events) {
|
|
214
|
+
if (event.kind !== "engineering_projection") continue;
|
|
215
|
+
const projection = event.payload;
|
|
216
|
+
if (projection?.kind === "projection_clear" && projection.clear_kind === "transfer_table") {
|
|
217
|
+
const ids = cleared.get(projection.node_id) ?? new Set();
|
|
218
|
+
ids.add(projection.execution_id);
|
|
219
|
+
cleared.set(projection.node_id, ids);
|
|
220
|
+
} else if (projection?.kind === "transfer_table") {
|
|
221
|
+
const rows = candidates.get(projection.node_id) ?? [];
|
|
222
|
+
rows.push(projection);
|
|
223
|
+
candidates.set(projection.node_id, rows);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return [...candidates.entries()].flatMap(([node, rows]) => {
|
|
227
|
+
const current = [...rows].reverse().find(row => !cleared.get(node)?.has(row.execution_id));
|
|
228
|
+
return current ? [current] : [];
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export function humanRun(value, { projectId, cost, currentTransferIds } = {}) {
|
|
233
|
+
const terminal = value.terminal === "" ? null : terminalPresentation(value);
|
|
212
234
|
const origins = value.events.flatMap(event => event.kind === "step_finished"
|
|
213
235
|
? inputLines(event.payload?.step?.inputs ?? event.payload?.inputs) : []);
|
|
214
236
|
const artifacts = value.events.filter(event => event.kind === "artifact").map(event => event.payload);
|
|
@@ -217,11 +239,25 @@ export function humanRun(value, { projectId, cost } = {}) {
|
|
|
217
239
|
}
|
|
218
240
|
const tasks = taskLines(value.events);
|
|
219
241
|
const assumptions = assumptionLines(value.events);
|
|
242
|
+
const currentTables = currentTransferTables(value.events).filter(projection =>
|
|
243
|
+
currentTransferIds?.has(projection.projection_id));
|
|
220
244
|
const lines = [];
|
|
221
|
-
if (terminal
|
|
245
|
+
if (terminal?.answer !== undefined) lines.push(terminal.answer, "");
|
|
222
246
|
if (tasks.length) lines.push("Steps:", ...tasks, "");
|
|
223
247
|
if (origins.length) lines.push("Inputs and origins:", ...origins, "");
|
|
224
248
|
if (assumptions.length) lines.push("Assumptions:", ...assumptions, "");
|
|
249
|
+
for (const projection of currentTables) {
|
|
250
|
+
if (typeof projection.search_status !== "string" || projection.origin !== "unattributed"
|
|
251
|
+
|| typeof projection.validation_status !== "string" || typeof projection.withheld !== "boolean") {
|
|
252
|
+
throw new CliError("projection_invalid", "Transfer table metadata is malformed");
|
|
253
|
+
}
|
|
254
|
+
lines.push(`Transfer table: ${plainLine(projection.search_status)} · input origins ${projection.origin}`
|
|
255
|
+
+ ` · validation ${plainLine(projection.validation_status)}`
|
|
256
|
+
+ ` · path ${projection.withheld ? "withheld" : "shown"}`);
|
|
257
|
+
const project = projectId ? ` --project ${shellQuote(projectId)}` : "";
|
|
258
|
+
lines.push(` vecteur projection get ${shellQuote(value.run_id)} ${shellQuote(projection.projection_id)}`
|
|
259
|
+
+ ` --output ${shellQuote("transfer-table.json")}${project}`, "");
|
|
260
|
+
}
|
|
225
261
|
if (artifacts.length) {
|
|
226
262
|
lines.push("Artifacts:");
|
|
227
263
|
for (const artifact of artifacts) {
|
|
@@ -231,9 +267,12 @@ export function humanRun(value, { projectId, cost } = {}) {
|
|
|
231
267
|
}
|
|
232
268
|
lines.push("");
|
|
233
269
|
}
|
|
234
|
-
lines.push(`Run: ${plainLine(value.run_id)}
|
|
235
|
-
if (terminal
|
|
236
|
-
|
|
270
|
+
lines.push(`Run: ${plainLine(value.run_id)}`);
|
|
271
|
+
if (terminal) {
|
|
272
|
+
lines.push(`Terminal: ${terminal.state}`);
|
|
273
|
+
if (terminal.defect !== undefined) lines.push(`Cause: ${terminal.defect}`);
|
|
274
|
+
lines.push(...terminal.recovery.map(recoveryLine));
|
|
275
|
+
}
|
|
237
276
|
if (cost) lines.push("", humanCost(cost).trimEnd());
|
|
238
277
|
return `${lines.join("\n")}\n`;
|
|
239
278
|
}
|
package/src/mcp.js
CHANGED
|
@@ -3,7 +3,7 @@ import { serveStdio } from "@modelcontextprotocol/server/stdio";
|
|
|
3
3
|
import * as z from "zod/v4";
|
|
4
4
|
|
|
5
5
|
import { CliError, VecteurClient, redact } from "./client.js";
|
|
6
|
-
import { EVENT_KINDS, RETIRED_EVENT_KINDS } from "./contract.js";
|
|
6
|
+
import { EVENT_KINDS, RETIRED_EVENT_KINDS, TERMINAL_STATES } from "./contract.js";
|
|
7
7
|
import { canonicalJson } from "./format.js";
|
|
8
8
|
|
|
9
9
|
const projectInput = z.object({ project_id: z.string().min(1) }).strict();
|
|
@@ -53,9 +53,9 @@ const runControlOutput = z.object({
|
|
|
53
53
|
text: z.string().nullable().optional(),
|
|
54
54
|
}).strict();
|
|
55
55
|
const admittedOutput = z.object({
|
|
56
|
-
run_id: z.string().min(1), state_hash: z.string().min(1), result_hash: z.string()
|
|
56
|
+
run_id: z.string().min(1), state_hash: z.string().min(1), result_hash: z.string(),
|
|
57
57
|
requested_route_id: z.string().min(1), profile: z.string().min(1),
|
|
58
|
-
terminal: z.enum([
|
|
58
|
+
terminal: z.union([z.enum([...TERMINAL_STATES]), z.literal("")]),
|
|
59
59
|
duration_seconds: z.number().int().safe().nonnegative().nullable().optional(),
|
|
60
60
|
usage: z.object({
|
|
61
61
|
coverage: z.enum(["complete", "incomplete"]), provider_id: z.string().min(1),
|
|
@@ -64,7 +64,9 @@ const admittedOutput = z.object({
|
|
|
64
64
|
}).strict(),
|
|
65
65
|
events: z.array(eventOutput),
|
|
66
66
|
controls: z.array(runControlOutput),
|
|
67
|
-
}).strict()
|
|
67
|
+
}).strict().refine(value => value.terminal === ""
|
|
68
|
+
? value.result_hash === "" && value.events.at(-1)?.kind !== "terminal"
|
|
69
|
+
: value.result_hash.length > 0, "Run terminal and result hash are not paired");
|
|
68
70
|
|
|
69
71
|
function success(value) {
|
|
70
72
|
return {
|
|
@@ -114,7 +116,7 @@ export function createVecteurMcpServer(client = new VecteurClient(), shutdownSig
|
|
|
114
116
|
return client.runSubmit(projectId, ask, onEvent, signal(context));
|
|
115
117
|
});
|
|
116
118
|
|
|
117
|
-
tool(server, "vecteur_run_get", "Get one
|
|
119
|
+
tool(server, "vecteur_run_get", "Get one Vecteur run record, including an open run.", runInput, admittedOutput,
|
|
118
120
|
({ run_id: runId }, context) => client.runGet(runId, signal(context)));
|
|
119
121
|
|
|
120
122
|
tool(server, "vecteur_run_events", "Validate run events against its direct record.", runInput,
|