tines 0.0.195 → 0.0.196
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 +27 -0
- package/dist/index.js +1559 -215
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3124,14 +3124,14 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3124
3124
|
*/
|
|
3125
3125
|
helpInformation(contextOptions) {
|
|
3126
3126
|
const helper = this.createHelp();
|
|
3127
|
-
const
|
|
3127
|
+
const context2 = this._getOutputContext(contextOptions);
|
|
3128
3128
|
helper.prepareContext({
|
|
3129
|
-
error:
|
|
3130
|
-
helpWidth:
|
|
3131
|
-
outputHasColors:
|
|
3129
|
+
error: context2.error,
|
|
3130
|
+
helpWidth: context2.helpWidth,
|
|
3131
|
+
outputHasColors: context2.hasColors
|
|
3132
3132
|
});
|
|
3133
3133
|
const text4 = helper.formatHelp(this, helper);
|
|
3134
|
-
if (
|
|
3134
|
+
if (context2.hasColors) return text4;
|
|
3135
3135
|
return this._outputConfiguration.stripColor(text4);
|
|
3136
3136
|
}
|
|
3137
3137
|
/**
|
|
@@ -3299,15 +3299,15 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3299
3299
|
Expecting one of '${allowedValues.join("', '")}'`);
|
|
3300
3300
|
}
|
|
3301
3301
|
const helpEvent = `${position}Help`;
|
|
3302
|
-
this.on(helpEvent, (
|
|
3302
|
+
this.on(helpEvent, (context2) => {
|
|
3303
3303
|
let helpStr;
|
|
3304
3304
|
if (typeof text4 === "function") {
|
|
3305
|
-
helpStr = text4({ error:
|
|
3305
|
+
helpStr = text4({ error: context2.error, command: context2.command });
|
|
3306
3306
|
} else {
|
|
3307
3307
|
helpStr = text4;
|
|
3308
3308
|
}
|
|
3309
3309
|
if (helpStr) {
|
|
3310
|
-
|
|
3310
|
+
context2.write(`${helpStr}
|
|
3311
3311
|
`);
|
|
3312
3312
|
}
|
|
3313
3313
|
});
|
|
@@ -3401,6 +3401,12 @@ function readBodyValue(value, stdin = processStdin) {
|
|
|
3401
3401
|
}
|
|
3402
3402
|
|
|
3403
3403
|
// ../shared/src/types.ts
|
|
3404
|
+
var STATE_CATEGORIES = [
|
|
3405
|
+
"backlog",
|
|
3406
|
+
"active",
|
|
3407
|
+
"awaiting_human",
|
|
3408
|
+
"done"
|
|
3409
|
+
];
|
|
3404
3410
|
function runRefLabel(run) {
|
|
3405
3411
|
return run.issue_ref ? `run on ${run.issue_ref.project_name}/${run.issue_ref.number}` : `run ${run.run_id}`;
|
|
3406
3412
|
}
|
|
@@ -3421,7 +3427,9 @@ var LABEL_COLORS = [
|
|
|
3421
3427
|
"violet",
|
|
3422
3428
|
"pink"
|
|
3423
3429
|
];
|
|
3430
|
+
var LABEL_NAME_MAX = 50;
|
|
3424
3431
|
var PROMPT_MAX_BYTES = 32 * 1024;
|
|
3432
|
+
var SKILL_MAX_FILES = 20;
|
|
3425
3433
|
var SKILL_MAX_TOTAL_BYTES = 100 * 1024;
|
|
3426
3434
|
var AGENT_GUIDELINES_NAME = "agent-guidelines";
|
|
3427
3435
|
var JOURNAL_NAME = "journal";
|
|
@@ -3451,6 +3459,7 @@ function repoDirFromUrl(url) {
|
|
|
3451
3459
|
return "repo";
|
|
3452
3460
|
return base;
|
|
3453
3461
|
}
|
|
3462
|
+
var ARTIFACT_TYPES = ["file", "text", "link", "pr", "folder"];
|
|
3454
3463
|
var ARTIFACT_FILE_MAX_BYTES = 25 * 1024 * 1024;
|
|
3455
3464
|
var ARTIFACT_TEXT_MAX_BYTES = 256 * 1024;
|
|
3456
3465
|
var ARTIFACT_FOLDER_MAX_BYTES = 50 * 1024 * 1024;
|
|
@@ -3464,13 +3473,13 @@ function canonicalGitHubRepoUrl(url) {
|
|
|
3464
3473
|
function parsePrSpec(spec) {
|
|
3465
3474
|
const short = spec.trim().match(/^([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)#(\d+)$/);
|
|
3466
3475
|
if (short) {
|
|
3467
|
-
const
|
|
3468
|
-
return
|
|
3476
|
+
const repoUrl3 = canonicalGitHubRepoUrl(`https://github.com/${short[1]}`);
|
|
3477
|
+
return repoUrl3 ? { repo_url: repoUrl3, number: Number.parseInt(short[2], 10) } : null;
|
|
3469
3478
|
}
|
|
3470
3479
|
const url = spec.trim().match(/^(.*?)\/pull\/(\d+)(?:[/?#].*)?$/);
|
|
3471
3480
|
if (!url) return null;
|
|
3472
|
-
const
|
|
3473
|
-
return
|
|
3481
|
+
const repoUrl2 = canonicalGitHubRepoUrl(url[1]);
|
|
3482
|
+
return repoUrl2 ? { repo_url: repoUrl2, number: Number.parseInt(url[2], 10) } : null;
|
|
3474
3483
|
}
|
|
3475
3484
|
var MODEL_TIERS = ["smartest", "balanced", "cheapest"];
|
|
3476
3485
|
var RUNNER_ONLINE_WINDOW_MS = 2 * 60 * 1e3;
|
|
@@ -3840,12 +3849,12 @@ function declaredContentType(reqs, type) {
|
|
|
3840
3849
|
// ../shared/src/artifact-site.ts
|
|
3841
3850
|
var ARTIFACT_SITE_LINK_TTL_MS = 60 * 60 * 1e3;
|
|
3842
3851
|
var ARTIFACT_SITE_INDEX = "index.html";
|
|
3843
|
-
function siteEntry(type, contentType,
|
|
3852
|
+
function siteEntry(type, contentType, files2 = []) {
|
|
3844
3853
|
if (type === "file" || type === "text") {
|
|
3845
3854
|
return (contentType ?? "").toLowerCase().startsWith("text/html") ? "" : null;
|
|
3846
3855
|
}
|
|
3847
3856
|
if (type === "folder") {
|
|
3848
|
-
return
|
|
3857
|
+
return files2.some((f) => f.path === ARTIFACT_SITE_INDEX) ? ARTIFACT_SITE_INDEX : null;
|
|
3849
3858
|
}
|
|
3850
3859
|
return null;
|
|
3851
3860
|
}
|
|
@@ -4076,12 +4085,12 @@ function createApiClient(options) {
|
|
|
4076
4085
|
/** JSON upsert for text/link/pr: creates the artifact or appends a version. */
|
|
4077
4086
|
putArtifact: (issueId, name2, body) => request("PUT", artifactPath(issueId, name2), body),
|
|
4078
4087
|
/** Raw-body upload for `file`: creates the artifact or appends a version. */
|
|
4079
|
-
uploadArtifactFile: async (issueId, name2,
|
|
4088
|
+
uploadArtifactFile: async (issueId, name2, bytes2, opts) => {
|
|
4080
4089
|
const res = await raw(
|
|
4081
4090
|
"PUT",
|
|
4082
4091
|
artifactPath(issueId, name2, `/file?filename=${encodeURIComponent(opts.filename)}`),
|
|
4083
4092
|
{
|
|
4084
|
-
body:
|
|
4093
|
+
body: bytes2,
|
|
4085
4094
|
headers: { "content-type": opts.contentType }
|
|
4086
4095
|
}
|
|
4087
4096
|
);
|
|
@@ -4091,9 +4100,9 @@ function createApiClient(options) {
|
|
|
4091
4100
|
* Multipart snapshot upload for `folder`: every file of the new version
|
|
4092
4101
|
* in one request (path as the part filename, MIME as the part type).
|
|
4093
4102
|
*/
|
|
4094
|
-
uploadArtifactFolder: async (issueId, name2,
|
|
4103
|
+
uploadArtifactFolder: async (issueId, name2, files2) => {
|
|
4095
4104
|
const form = new FormData();
|
|
4096
|
-
for (const file of
|
|
4105
|
+
for (const file of files2) {
|
|
4097
4106
|
form.append(
|
|
4098
4107
|
"file",
|
|
4099
4108
|
new Blob([file.bytes], { type: file.contentType }),
|
|
@@ -4416,10 +4425,10 @@ var INHERIT_RUNNER_ID = "*";
|
|
|
4416
4425
|
|
|
4417
4426
|
// ../shared/src/issue-transfer.ts
|
|
4418
4427
|
var compare = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
4419
|
-
function conflictsByKey(
|
|
4420
|
-
const repositories = new Map(
|
|
4428
|
+
function conflictsByKey(context2) {
|
|
4429
|
+
const repositories = new Map(context2.repos.map((repo) => [repo.item_id, repo]));
|
|
4421
4430
|
return new Map(
|
|
4422
|
-
|
|
4431
|
+
context2.conflicts.map((conflict) => {
|
|
4423
4432
|
const ids = [...new Set(conflict.item_ids)].sort(compare);
|
|
4424
4433
|
const key = JSON.stringify([conflict.dir, ids]);
|
|
4425
4434
|
return [
|
|
@@ -4460,6 +4469,8 @@ function deriveTransferConflictDeltas(before, after) {
|
|
|
4460
4469
|
}
|
|
4461
4470
|
|
|
4462
4471
|
// ../shared/src/library/types.ts
|
|
4472
|
+
var LIBRARY_V3_MAX_DEPTH = 64;
|
|
4473
|
+
var LIBRARY_V3_MAX_RECORDS = 1e3;
|
|
4463
4474
|
var LibraryValidationError = class extends Error {
|
|
4464
4475
|
constructor(diagnostics) {
|
|
4465
4476
|
super(diagnostics[0]?.message ?? "Invalid library document");
|
|
@@ -4508,6 +4519,11 @@ var oneOf = (values) => (v, p) => {
|
|
|
4508
4519
|
var nullable = (check) => (v, p) => {
|
|
4509
4520
|
if (v !== null) check(v, p);
|
|
4510
4521
|
};
|
|
4522
|
+
var array = (check, max = 1e3) => (v, p) => {
|
|
4523
|
+
if (!Array.isArray(v)) invalid(p, "invalid_type", "Expected an array");
|
|
4524
|
+
if (v.length > max) invalid(p, "package_too_large", `Array exceeds ${max} entries`);
|
|
4525
|
+
for (let i = 0; i < v.length; i++) check(v[i], pointer(p, i));
|
|
4526
|
+
};
|
|
4511
4527
|
function object(v, p) {
|
|
4512
4528
|
if (!v || typeof v !== "object" || Array.isArray(v) || ![null, Object.prototype].includes(Object.getPrototypeOf(v)))
|
|
4513
4529
|
invalid(p, "invalid_type", "Expected a plain object");
|
|
@@ -4539,14 +4555,166 @@ var id = (v, p) => {
|
|
|
4539
4555
|
if (!/^[A-Za-z0-9:_-]{1,80}$/.test(v))
|
|
4540
4556
|
invalid(p, "invalid_local_id", "Invalid document-local ID");
|
|
4541
4557
|
};
|
|
4558
|
+
var labelName = (v, p) => {
|
|
4559
|
+
text2(LABEL_NAME_MAX, true)(v, p);
|
|
4560
|
+
const name2 = v.trim();
|
|
4561
|
+
if (name2.startsWith("-") || /[\u0000-\u001f\u007f]/.test(name2))
|
|
4562
|
+
invalid(
|
|
4563
|
+
p,
|
|
4564
|
+
"invalid_field",
|
|
4565
|
+
"Label names cannot start with a hyphen or contain control characters"
|
|
4566
|
+
);
|
|
4567
|
+
};
|
|
4568
|
+
var slug = (v, p) => {
|
|
4569
|
+
text2(100, true)(v, p);
|
|
4570
|
+
if (!/^[a-z0-9-]+$/.test(v))
|
|
4571
|
+
invalid(p, "invalid_field", "Expected a slug ([a-z0-9-]+)");
|
|
4572
|
+
};
|
|
4542
4573
|
var bundledState = shape({ kind: oneOf(["bundled_state"]), state_id: id });
|
|
4543
4574
|
var systemState = shape({
|
|
4544
4575
|
kind: oneOf(["system_state"]),
|
|
4545
4576
|
workflow: oneOf(["Standard"]),
|
|
4546
4577
|
state_name: text2(100, true)
|
|
4547
4578
|
});
|
|
4579
|
+
var stateRef = (library) => discriminated("kind", {
|
|
4580
|
+
bundled_state: bundledState,
|
|
4581
|
+
...library ? { system_state: systemState } : {}
|
|
4582
|
+
});
|
|
4548
4583
|
var bundledWorkflow = shape({ kind: oneOf(["bundled_workflow"]), workflow_id: id });
|
|
4549
4584
|
var projectRef = shape({ kind: oneOf(["input_project"]), input_id: id });
|
|
4585
|
+
var requirement = (v, p) => {
|
|
4586
|
+
shape(
|
|
4587
|
+
{ artifact: slug },
|
|
4588
|
+
{ type: oneOf(ARTIFACT_TYPES), content_type: text2(100, true), description: text2(500) }
|
|
4589
|
+
)(v, p);
|
|
4590
|
+
const o = v;
|
|
4591
|
+
if (o.content_type !== void 0 && o.type !== "file" && o.type !== "text")
|
|
4592
|
+
invalid(pointer(p, "content_type"), "invalid_field", "content_type requires type file or text");
|
|
4593
|
+
};
|
|
4594
|
+
var workflow = (library) => shape({
|
|
4595
|
+
id,
|
|
4596
|
+
name: text2(200, true),
|
|
4597
|
+
description: text2(1e4),
|
|
4598
|
+
initial_state_id: id,
|
|
4599
|
+
states: array(
|
|
4600
|
+
shape({
|
|
4601
|
+
id,
|
|
4602
|
+
name: text2(100, true),
|
|
4603
|
+
category: oneOf(STATE_CATEGORIES),
|
|
4604
|
+
inherits_from: nullable(stateRef(library))
|
|
4605
|
+
})
|
|
4606
|
+
),
|
|
4607
|
+
transitions: array(
|
|
4608
|
+
shape({
|
|
4609
|
+
id,
|
|
4610
|
+
name: text2(100, true),
|
|
4611
|
+
from_state_id: id,
|
|
4612
|
+
to_state_id: id,
|
|
4613
|
+
requires: array(requirement)
|
|
4614
|
+
})
|
|
4615
|
+
)
|
|
4616
|
+
});
|
|
4617
|
+
var workspacePath = (v, p) => {
|
|
4618
|
+
text2(500, true)(v, p);
|
|
4619
|
+
const path2 = v;
|
|
4620
|
+
if (path2.includes("\\") || path2.includes("=") || path2.split("/").some((s) => !s || s === "." || s === ".."))
|
|
4621
|
+
invalid(
|
|
4622
|
+
p,
|
|
4623
|
+
"invalid_path",
|
|
4624
|
+
"Expected a relative workspace path without empty, dot or parent segments, backslashes or equals"
|
|
4625
|
+
);
|
|
4626
|
+
};
|
|
4627
|
+
var bytes = (v) => new TextEncoder().encode(v).byteLength;
|
|
4628
|
+
var prompt = (v, p) => {
|
|
4629
|
+
text2(PROMPT_MAX_BYTES)(v, p);
|
|
4630
|
+
if (bytes(v) > PROMPT_MAX_BYTES)
|
|
4631
|
+
invalid(p, "package_too_large", `Prompt exceeds ${PROMPT_MAX_BYTES} UTF-8 bytes`);
|
|
4632
|
+
};
|
|
4633
|
+
var files = (v, p) => {
|
|
4634
|
+
array(shape({ id, path: workspacePath, content: text2(SKILL_MAX_TOTAL_BYTES) }), SKILL_MAX_FILES)(
|
|
4635
|
+
v,
|
|
4636
|
+
p
|
|
4637
|
+
);
|
|
4638
|
+
const entries = v;
|
|
4639
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4640
|
+
let size = 0;
|
|
4641
|
+
for (const [i, file] of entries.entries()) {
|
|
4642
|
+
if (seen.has(file.path))
|
|
4643
|
+
invalid(pointer(pointer(p, i), "path"), "duplicate_path", "Duplicate skill file path");
|
|
4644
|
+
seen.add(file.path);
|
|
4645
|
+
size += bytes(file.path) + bytes(file.content);
|
|
4646
|
+
}
|
|
4647
|
+
if (size > SKILL_MAX_TOTAL_BYTES)
|
|
4648
|
+
invalid(p, "package_too_large", `Skill exceeds ${SKILL_MAX_TOTAL_BYTES} UTF-8 bytes`);
|
|
4649
|
+
};
|
|
4650
|
+
var repoUrl = (v, p) => {
|
|
4651
|
+
text2(1e3, true)(v, p);
|
|
4652
|
+
let url;
|
|
4653
|
+
try {
|
|
4654
|
+
url = new URL(v);
|
|
4655
|
+
} catch {
|
|
4656
|
+
invalid(p, "invalid_repo_url", "Expected a GitHub HTTPS repository URL");
|
|
4657
|
+
}
|
|
4658
|
+
if (url.protocol !== "https:" || url.username || url.password || url.search || url.hash || !canonicalGitHubRepoUrl(v))
|
|
4659
|
+
invalid(
|
|
4660
|
+
p,
|
|
4661
|
+
"invalid_repo_url",
|
|
4662
|
+
"Expected a GitHub HTTPS repository URL without credentials, query or fragment"
|
|
4663
|
+
);
|
|
4664
|
+
};
|
|
4665
|
+
var context = (library) => {
|
|
4666
|
+
const scope = library ? {
|
|
4667
|
+
scope: shape({}, { project_id: id, state: stateRef(true), label_id: id }),
|
|
4668
|
+
journal: bool
|
|
4669
|
+
} : { state_id: id };
|
|
4670
|
+
const common = {
|
|
4671
|
+
id,
|
|
4672
|
+
name: text2(100, true),
|
|
4673
|
+
description: text2(1e3),
|
|
4674
|
+
...scope
|
|
4675
|
+
};
|
|
4676
|
+
return (v, p) => {
|
|
4677
|
+
discriminated("kind", {
|
|
4678
|
+
prompt: shape({ ...common, kind: oneOf(["prompt"]), body: prompt }),
|
|
4679
|
+
skill: shape({ ...common, name: slug, kind: oneOf(["skill"]), files }),
|
|
4680
|
+
repo: shape({
|
|
4681
|
+
...common,
|
|
4682
|
+
kind: oneOf(["repo"]),
|
|
4683
|
+
// Whole-library transfer preserves ordinary repo declarations, including
|
|
4684
|
+
// local/file URLs. Workflow packages are shareable and stay GitHub-only.
|
|
4685
|
+
repo_url: library ? text2(1e3, true) : repoUrl,
|
|
4686
|
+
repo_branch: nullable(text2(200, true)),
|
|
4687
|
+
repo_dir: nullable(workspacePath)
|
|
4688
|
+
})
|
|
4689
|
+
})(v, p);
|
|
4690
|
+
const o = v;
|
|
4691
|
+
if (library && o.journal && o.kind !== "prompt")
|
|
4692
|
+
invalid(pointer(p, "journal"), "invalid_field", "Only prompts may be journals");
|
|
4693
|
+
};
|
|
4694
|
+
};
|
|
4695
|
+
var input = (v, p) => {
|
|
4696
|
+
shape(
|
|
4697
|
+
{
|
|
4698
|
+
id,
|
|
4699
|
+
key: text2(64, true),
|
|
4700
|
+
type: oneOf(["text", "workflow", "label", "project"]),
|
|
4701
|
+
label: text2(200, true),
|
|
4702
|
+
description: text2(1e3),
|
|
4703
|
+
required: bool,
|
|
4704
|
+
default: nullable(text2(1e4))
|
|
4705
|
+
},
|
|
4706
|
+
{ required_states: array(text2(100, true)) }
|
|
4707
|
+
)(v, p);
|
|
4708
|
+
const o = v;
|
|
4709
|
+
if (!/^[a-z][a-z0-9_]{0,63}$/.test(o.key))
|
|
4710
|
+
invalid(pointer(p, "key"), "invalid_input_key", "Invalid input key");
|
|
4711
|
+
if (o.required_states !== void 0 && o.type !== "workflow")
|
|
4712
|
+
invalid(
|
|
4713
|
+
pointer(p, "required_states"),
|
|
4714
|
+
"invalid_field",
|
|
4715
|
+
"Only workflow inputs may require states"
|
|
4716
|
+
);
|
|
4717
|
+
};
|
|
4550
4718
|
var preset = discriminated("kind", {
|
|
4551
4719
|
hourly: shape({ kind: oneOf(["hourly"]), every_hours: integer }, { minute: integer }),
|
|
4552
4720
|
daily: shape({ kind: oneOf(["daily"]), time: text2(5, true) }),
|
|
@@ -4585,6 +4753,695 @@ var schedule = shape({
|
|
|
4585
4753
|
require_all_closed: bool,
|
|
4586
4754
|
start_state: nullable(bundledState)
|
|
4587
4755
|
});
|
|
4756
|
+
function validateLibraryV3Shape(value) {
|
|
4757
|
+
const envelope = { format: oneOf(["tines.library"]), version: oneOf([3]), exported_at: integer };
|
|
4758
|
+
const digest = (v, p) => {
|
|
4759
|
+
text2(71)(v, p);
|
|
4760
|
+
if (v !== "" && !/^sha256:[0-9a-f]{64}$/.test(v))
|
|
4761
|
+
invalid(p, "invalid_digest", "Expected sha256 and 64 lowercase hex digits");
|
|
4762
|
+
};
|
|
4763
|
+
discriminated("profile", {
|
|
4764
|
+
workflow: shape(
|
|
4765
|
+
{
|
|
4766
|
+
...envelope,
|
|
4767
|
+
profile: oneOf(["workflow"]),
|
|
4768
|
+
main_workflow_id: id,
|
|
4769
|
+
workflows: array(workflow(false)),
|
|
4770
|
+
context: array(context(false)),
|
|
4771
|
+
inputs: array(input),
|
|
4772
|
+
text_uses: array(
|
|
4773
|
+
shape({
|
|
4774
|
+
id,
|
|
4775
|
+
target: shape({
|
|
4776
|
+
record_id: id,
|
|
4777
|
+
field: oneOf([
|
|
4778
|
+
"description",
|
|
4779
|
+
"body",
|
|
4780
|
+
"content",
|
|
4781
|
+
"title_template",
|
|
4782
|
+
"description_template"
|
|
4783
|
+
])
|
|
4784
|
+
}),
|
|
4785
|
+
input_id: id,
|
|
4786
|
+
token: text2(30100, true)
|
|
4787
|
+
})
|
|
4788
|
+
),
|
|
4789
|
+
schedules: array(schedule),
|
|
4790
|
+
routing: array(
|
|
4791
|
+
shape({
|
|
4792
|
+
id,
|
|
4793
|
+
scope: shape({ state_id: id }, { project: projectRef }),
|
|
4794
|
+
tier: oneOf(MODEL_TIERS)
|
|
4795
|
+
})
|
|
4796
|
+
)
|
|
4797
|
+
},
|
|
4798
|
+
{ digest }
|
|
4799
|
+
),
|
|
4800
|
+
library: shape(
|
|
4801
|
+
{
|
|
4802
|
+
...envelope,
|
|
4803
|
+
profile: oneOf(["library"]),
|
|
4804
|
+
workflows: array(workflow(true)),
|
|
4805
|
+
context: array(context(true)),
|
|
4806
|
+
projects: array(
|
|
4807
|
+
shape({
|
|
4808
|
+
id,
|
|
4809
|
+
name: text2(200, true),
|
|
4810
|
+
description: text2(1e4),
|
|
4811
|
+
default_workflow: nullable(
|
|
4812
|
+
discriminated("kind", {
|
|
4813
|
+
bundled_workflow: bundledWorkflow,
|
|
4814
|
+
system_workflow: shape({
|
|
4815
|
+
kind: oneOf(["system_workflow"]),
|
|
4816
|
+
name: oneOf(["Standard"])
|
|
4817
|
+
})
|
|
4818
|
+
})
|
|
4819
|
+
)
|
|
4820
|
+
})
|
|
4821
|
+
),
|
|
4822
|
+
labels: array(shape({ id, name: labelName, color: oneOf(LABEL_COLORS) }))
|
|
4823
|
+
},
|
|
4824
|
+
{ digest }
|
|
4825
|
+
)
|
|
4826
|
+
})(value, "");
|
|
4827
|
+
}
|
|
4828
|
+
|
|
4829
|
+
// ../shared/src/library/inputs.ts
|
|
4830
|
+
function inputToken(key, defaultValue) {
|
|
4831
|
+
const escaped = (defaultValue ?? "").replaceAll("\\", "\\\\").replaceAll(":", "\\:").replaceAll("}", "\\}");
|
|
4832
|
+
return `{{${key}:${escaped}}}`;
|
|
4833
|
+
}
|
|
4834
|
+
function renderDeclaredTokens(source, declarations) {
|
|
4835
|
+
const tokens = /* @__PURE__ */ new Set();
|
|
4836
|
+
for (const { token } of declarations) {
|
|
4837
|
+
if (!token || tokens.has(token))
|
|
4838
|
+
throw new LibraryValidationError([
|
|
4839
|
+
{
|
|
4840
|
+
path: "/text_uses",
|
|
4841
|
+
code: "duplicate_text_use",
|
|
4842
|
+
message: "Tokens must be non-empty and unique within a field"
|
|
4843
|
+
}
|
|
4844
|
+
]);
|
|
4845
|
+
tokens.add(token);
|
|
4846
|
+
}
|
|
4847
|
+
const matches = [];
|
|
4848
|
+
declarations.forEach(({ token }, index) => {
|
|
4849
|
+
for (let offset2 = 0; offset2 < source.length; ) {
|
|
4850
|
+
const start = source.indexOf(token, offset2);
|
|
4851
|
+
if (start < 0) break;
|
|
4852
|
+
matches.push({
|
|
4853
|
+
start,
|
|
4854
|
+
end: start + token.length,
|
|
4855
|
+
index,
|
|
4856
|
+
escaped: start > 0 && source[start - 1] === "\\"
|
|
4857
|
+
});
|
|
4858
|
+
offset2 = start + 1;
|
|
4859
|
+
}
|
|
4860
|
+
});
|
|
4861
|
+
matches.sort((a, b) => a.start - b.start || a.end - b.end);
|
|
4862
|
+
for (let i = 1; i < matches.length; i++) {
|
|
4863
|
+
if (matches[i].start < matches[i - 1].end)
|
|
4864
|
+
throw new LibraryValidationError([
|
|
4865
|
+
{
|
|
4866
|
+
path: "/text_uses",
|
|
4867
|
+
code: "overlapping_text_use",
|
|
4868
|
+
message: "Declared token occurrences overlap"
|
|
4869
|
+
}
|
|
4870
|
+
]);
|
|
4871
|
+
}
|
|
4872
|
+
let value = "";
|
|
4873
|
+
let offset = 0;
|
|
4874
|
+
const counts = declarations.map(() => 0);
|
|
4875
|
+
for (const match of matches) {
|
|
4876
|
+
value += source.slice(offset, match.escaped ? match.start - 1 : match.start);
|
|
4877
|
+
value += match.escaped ? declarations[match.index].token : declarations[match.index].value;
|
|
4878
|
+
if (!match.escaped) counts[match.index]++;
|
|
4879
|
+
offset = match.end;
|
|
4880
|
+
}
|
|
4881
|
+
return { value: value + source.slice(offset), counts };
|
|
4882
|
+
}
|
|
4883
|
+
|
|
4884
|
+
// ../shared/src/library/references.ts
|
|
4885
|
+
function validateLibraryV3References(document) {
|
|
4886
|
+
validateLibraryV3Shape(document);
|
|
4887
|
+
const ids = /* @__PURE__ */ new Map();
|
|
4888
|
+
let count = 0;
|
|
4889
|
+
const add = (id2, path2) => {
|
|
4890
|
+
count++;
|
|
4891
|
+
if (count > LIBRARY_V3_MAX_RECORDS)
|
|
4892
|
+
invalid(path2, "package_too_large", `Package exceeds ${LIBRARY_V3_MAX_RECORDS} records`);
|
|
4893
|
+
if (ids.has(id2)) invalid(path2, "duplicate_local_id", `Local ID already used at ${ids.get(id2)}`);
|
|
4894
|
+
ids.set(id2, path2);
|
|
4895
|
+
};
|
|
4896
|
+
const unique = (values, path2, code) => {
|
|
4897
|
+
if (new Set(values).size !== values.length) invalid(path2, code, "Values must be unique");
|
|
4898
|
+
};
|
|
4899
|
+
const workflows = new Map(document.workflows.map((w) => [w.id, w]));
|
|
4900
|
+
const states = /* @__PURE__ */ new Map();
|
|
4901
|
+
const fields = /* @__PURE__ */ new Map();
|
|
4902
|
+
const requireRef = (exists, path2, message3 = "Reference does not identify the required record kind") => {
|
|
4903
|
+
if (!exists) invalid(path2, "invalid_reference", message3);
|
|
4904
|
+
};
|
|
4905
|
+
const system = (ref, path2) => {
|
|
4906
|
+
requireRef(
|
|
4907
|
+
["Open", "Human Review", "Closed"].includes(ref.state_name),
|
|
4908
|
+
path2,
|
|
4909
|
+
"Unknown Standard state"
|
|
4910
|
+
);
|
|
4911
|
+
};
|
|
4912
|
+
for (const [wi, w] of document.workflows.entries()) {
|
|
4913
|
+
const p = `/workflows/${wi}`;
|
|
4914
|
+
add(w.id, `${p}/id`);
|
|
4915
|
+
fields.set(w.id, { description: w.description });
|
|
4916
|
+
if (!w.states.length)
|
|
4917
|
+
invalid(`${p}/states`, "no_states", "A workflow needs at least one state");
|
|
4918
|
+
unique(
|
|
4919
|
+
w.states.map((s) => s.name.trim()),
|
|
4920
|
+
`${p}/states`,
|
|
4921
|
+
"duplicate_state_name"
|
|
4922
|
+
);
|
|
4923
|
+
for (const [si, s] of w.states.entries()) {
|
|
4924
|
+
const sp = `${p}/states/${si}`;
|
|
4925
|
+
add(s.id, `${sp}/id`);
|
|
4926
|
+
states.set(s.id, { state: s, workflow: w.id, path: sp });
|
|
4927
|
+
}
|
|
4928
|
+
const local = new Map(w.states.map((s) => [s.id, s]));
|
|
4929
|
+
const initial = local.get(w.initial_state_id);
|
|
4930
|
+
requireRef(!!initial, `${p}/initial_state_id`, "Initial state must belong to its workflow");
|
|
4931
|
+
if (initial && initial.category !== "backlog" && initial.category !== "active")
|
|
4932
|
+
invalid(
|
|
4933
|
+
`${p}/initial_state_id`,
|
|
4934
|
+
"invalid_initial_state",
|
|
4935
|
+
"Initial state must be backlog or active"
|
|
4936
|
+
);
|
|
4937
|
+
unique(
|
|
4938
|
+
w.transitions.map((t) => `${t.from_state_id}\0${t.name.trim().toLowerCase()}`),
|
|
4939
|
+
`${p}/transitions`,
|
|
4940
|
+
"duplicate_action"
|
|
4941
|
+
);
|
|
4942
|
+
for (const [ti, t] of w.transitions.entries()) {
|
|
4943
|
+
const tp = `${p}/transitions/${ti}`;
|
|
4944
|
+
add(t.id, `${tp}/id`);
|
|
4945
|
+
requireRef(local.has(t.from_state_id), `${tp}/from_state_id`);
|
|
4946
|
+
requireRef(local.has(t.to_state_id), `${tp}/to_state_id`);
|
|
4947
|
+
if (t.from_state_id === t.to_state_id)
|
|
4948
|
+
invalid(tp, "self_transition", "Self transitions are not allowed");
|
|
4949
|
+
unique(
|
|
4950
|
+
t.requires.map((r) => r.artifact),
|
|
4951
|
+
`${tp}/requires`,
|
|
4952
|
+
"duplicate_requirement"
|
|
4953
|
+
);
|
|
4954
|
+
}
|
|
4955
|
+
}
|
|
4956
|
+
for (const { state, path: path2 } of states.values()) {
|
|
4957
|
+
const ref = state.inherits_from;
|
|
4958
|
+
if (ref?.kind === "bundled_state")
|
|
4959
|
+
requireRef(states.has(ref.state_id), `${path2}/inherits_from`);
|
|
4960
|
+
else if (ref?.kind === "system_state") system(ref, `${path2}/inherits_from`);
|
|
4961
|
+
}
|
|
4962
|
+
for (const entry of states.values()) {
|
|
4963
|
+
const chain = /* @__PURE__ */ new Set();
|
|
4964
|
+
let current = entry;
|
|
4965
|
+
let length = 0;
|
|
4966
|
+
while (current) {
|
|
4967
|
+
if (chain.has(current.state.id))
|
|
4968
|
+
invalid(
|
|
4969
|
+
`${entry.path}/inherits_from`,
|
|
4970
|
+
"inheritance_cycle",
|
|
4971
|
+
"State inheritance contains a cycle"
|
|
4972
|
+
);
|
|
4973
|
+
chain.add(current.state.id);
|
|
4974
|
+
length++;
|
|
4975
|
+
const ref = current.state.inherits_from;
|
|
4976
|
+
if (ref?.kind === "system_state") length++;
|
|
4977
|
+
if (length > 3)
|
|
4978
|
+
invalid(
|
|
4979
|
+
`${entry.path}/inherits_from`,
|
|
4980
|
+
"inheritance_depth",
|
|
4981
|
+
"Inheritance may contain at most three states"
|
|
4982
|
+
);
|
|
4983
|
+
current = ref?.kind === "bundled_state" ? states.get(ref.state_id) : void 0;
|
|
4984
|
+
}
|
|
4985
|
+
}
|
|
4986
|
+
const contextKeys = /* @__PURE__ */ new Set();
|
|
4987
|
+
for (const [ci, item] of document.context.entries()) {
|
|
4988
|
+
const p = `/context/${ci}`;
|
|
4989
|
+
add(item.id, `${p}/id`);
|
|
4990
|
+
fields.set(item.id, {
|
|
4991
|
+
description: item.description,
|
|
4992
|
+
...item.kind === "prompt" ? { body: item.body } : {}
|
|
4993
|
+
});
|
|
4994
|
+
let scope;
|
|
4995
|
+
if ("state_id" in item) {
|
|
4996
|
+
requireRef(states.has(item.state_id), `${p}/state_id`);
|
|
4997
|
+
scope = item.state_id;
|
|
4998
|
+
} else {
|
|
4999
|
+
const ref = item.scope.state;
|
|
5000
|
+
if (ref?.kind === "bundled_state") requireRef(states.has(ref.state_id), `${p}/scope/state`);
|
|
5001
|
+
if (ref?.kind === "system_state") system(ref, `${p}/scope/state`);
|
|
5002
|
+
if (document.profile !== "library")
|
|
5003
|
+
invalid(p, "invalid_scope", "Package context must have exact state scope");
|
|
5004
|
+
if (item.scope.project_id)
|
|
5005
|
+
requireRef(
|
|
5006
|
+
document.projects.some((r) => r.id === item.scope.project_id),
|
|
5007
|
+
`${p}/scope/project_id`
|
|
5008
|
+
);
|
|
5009
|
+
if (item.scope.label_id)
|
|
5010
|
+
requireRef(
|
|
5011
|
+
document.labels.some((r) => r.id === item.scope.label_id),
|
|
5012
|
+
`${p}/scope/label_id`
|
|
5013
|
+
);
|
|
5014
|
+
scope = JSON.stringify([
|
|
5015
|
+
item.scope.project_id ?? null,
|
|
5016
|
+
ref?.kind === "bundled_state" ? ["bundled", ref.state_id] : ref ? ["system", ref.state_name] : null,
|
|
5017
|
+
item.scope.label_id ?? null
|
|
5018
|
+
]);
|
|
5019
|
+
}
|
|
5020
|
+
const key = JSON.stringify([scope, item.kind, item.name.trim()]);
|
|
5021
|
+
if (contextKeys.has(key))
|
|
5022
|
+
invalid(p, "duplicate_context", "Context kind/name must be unique at its exact scope");
|
|
5023
|
+
contextKeys.add(key);
|
|
5024
|
+
if (item.kind === "skill")
|
|
5025
|
+
for (const [fi, file] of item.files.entries()) {
|
|
5026
|
+
add(file.id, `${p}/files/${fi}/id`);
|
|
5027
|
+
fields.set(file.id, { content: file.content });
|
|
5028
|
+
}
|
|
5029
|
+
}
|
|
5030
|
+
if (document.profile === "library") {
|
|
5031
|
+
for (const [i, p] of document.projects.entries()) {
|
|
5032
|
+
add(p.id, `/projects/${i}/id`);
|
|
5033
|
+
if (p.default_workflow?.kind === "bundled_workflow")
|
|
5034
|
+
requireRef(
|
|
5035
|
+
workflows.has(p.default_workflow.workflow_id),
|
|
5036
|
+
`/projects/${i}/default_workflow`
|
|
5037
|
+
);
|
|
5038
|
+
}
|
|
5039
|
+
for (const [i, label] of document.labels.entries()) add(label.id, `/labels/${i}/id`);
|
|
5040
|
+
unique(
|
|
5041
|
+
document.projects.map((p) => p.name.trim()),
|
|
5042
|
+
"/projects",
|
|
5043
|
+
"duplicate_project"
|
|
5044
|
+
);
|
|
5045
|
+
unique(
|
|
5046
|
+
document.labels.map((l) => l.name.trim().toLowerCase()),
|
|
5047
|
+
"/labels",
|
|
5048
|
+
"duplicate_label"
|
|
5049
|
+
);
|
|
5050
|
+
return;
|
|
5051
|
+
}
|
|
5052
|
+
requireRef(
|
|
5053
|
+
workflows.has(document.main_workflow_id),
|
|
5054
|
+
"/main_workflow_id",
|
|
5055
|
+
"Main workflow does not exist"
|
|
5056
|
+
);
|
|
5057
|
+
const reached = /* @__PURE__ */ new Set();
|
|
5058
|
+
const pending = [document.main_workflow_id];
|
|
5059
|
+
while (pending.length) {
|
|
5060
|
+
const id2 = pending.pop();
|
|
5061
|
+
if (reached.has(id2)) continue;
|
|
5062
|
+
reached.add(id2);
|
|
5063
|
+
for (const s of workflows.get(id2).states)
|
|
5064
|
+
if (s.inherits_from?.kind === "bundled_state")
|
|
5065
|
+
pending.push(states.get(s.inherits_from.state_id).workflow);
|
|
5066
|
+
}
|
|
5067
|
+
for (const [i, w] of document.workflows.entries())
|
|
5068
|
+
if (!reached.has(w.id))
|
|
5069
|
+
invalid(
|
|
5070
|
+
`/workflows/${i}`,
|
|
5071
|
+
"disconnected_workflow",
|
|
5072
|
+
"Every dependency workflow must be reachable through inheritance"
|
|
5073
|
+
);
|
|
5074
|
+
const inputs = new Map(document.inputs.map((input2) => [input2.id, input2]));
|
|
5075
|
+
unique(
|
|
5076
|
+
document.inputs.map((input2) => input2.key),
|
|
5077
|
+
"/inputs",
|
|
5078
|
+
"duplicate_input_key"
|
|
5079
|
+
);
|
|
5080
|
+
for (const [i, input2] of document.inputs.entries()) {
|
|
5081
|
+
add(input2.id, `/inputs/${i}/id`);
|
|
5082
|
+
if (input2.required_states)
|
|
5083
|
+
unique(
|
|
5084
|
+
input2.required_states.map((s) => s.trim()),
|
|
5085
|
+
`/inputs/${i}/required_states`,
|
|
5086
|
+
"duplicate_required_state"
|
|
5087
|
+
);
|
|
5088
|
+
}
|
|
5089
|
+
const project = (id2, path2) => requireRef(inputs.get(id2)?.type === "project", path2, "Expected a project input declaration");
|
|
5090
|
+
for (const [i, schedule2] of document.schedules.entries()) {
|
|
5091
|
+
const p = `/schedules/${i}`;
|
|
5092
|
+
add(schedule2.id, `${p}/id`);
|
|
5093
|
+
requireRef(workflows.has(schedule2.workflow.workflow_id), `${p}/workflow`);
|
|
5094
|
+
project(schedule2.project.input_id, `${p}/project`);
|
|
5095
|
+
if (schedule2.start_state)
|
|
5096
|
+
requireRef(
|
|
5097
|
+
states.get(schedule2.start_state.state_id)?.workflow === schedule2.workflow.workflow_id,
|
|
5098
|
+
`${p}/start_state`,
|
|
5099
|
+
"Start state must belong to the scheduled workflow"
|
|
5100
|
+
);
|
|
5101
|
+
fields.set(schedule2.id, {
|
|
5102
|
+
title_template: schedule2.title_template,
|
|
5103
|
+
description_template: schedule2.description_template
|
|
5104
|
+
});
|
|
5105
|
+
}
|
|
5106
|
+
const routingScopes = /* @__PURE__ */ new Set();
|
|
5107
|
+
for (const [i, routing] of document.routing.entries()) {
|
|
5108
|
+
const p = `/routing/${i}`;
|
|
5109
|
+
add(routing.id, `${p}/id`);
|
|
5110
|
+
requireRef(states.has(routing.scope.state_id), `${p}/scope/state_id`);
|
|
5111
|
+
if (routing.scope.project) project(routing.scope.project.input_id, `${p}/scope/project`);
|
|
5112
|
+
const key = JSON.stringify([routing.scope.state_id, routing.scope.project?.input_id ?? null]);
|
|
5113
|
+
if (routingScopes.has(key))
|
|
5114
|
+
invalid(p, "duplicate_routing", "Only one tier preference is allowed per scope");
|
|
5115
|
+
routingScopes.add(key);
|
|
5116
|
+
}
|
|
5117
|
+
const uses = /* @__PURE__ */ new Map();
|
|
5118
|
+
for (const [i, use] of document.text_uses.entries()) {
|
|
5119
|
+
const p = `/text_uses/${i}`;
|
|
5120
|
+
add(use.id, `${p}/id`);
|
|
5121
|
+
const input2 = inputs.get(use.input_id);
|
|
5122
|
+
requireRef(!!input2, `${p}/input_id`);
|
|
5123
|
+
const target = fields.get(use.target.record_id);
|
|
5124
|
+
requireRef(
|
|
5125
|
+
!!target && Object.hasOwn(target, use.target.field),
|
|
5126
|
+
`${p}/target`,
|
|
5127
|
+
"Text use must target an allowed field on this record kind"
|
|
5128
|
+
);
|
|
5129
|
+
if (use.token !== inputToken(input2.key, input2.default))
|
|
5130
|
+
invalid(`${p}/token`, "invalid_token", "Token must match its declared input key and default");
|
|
5131
|
+
const key = JSON.stringify([use.target.record_id, use.target.field]);
|
|
5132
|
+
const group = uses.get(key) ?? {
|
|
5133
|
+
source: target[use.target.field],
|
|
5134
|
+
declarations: [],
|
|
5135
|
+
paths: []
|
|
5136
|
+
};
|
|
5137
|
+
group.declarations.push({ token: use.token, value: "" });
|
|
5138
|
+
group.paths.push(p);
|
|
5139
|
+
uses.set(key, group);
|
|
5140
|
+
}
|
|
5141
|
+
for (const group of uses.values()) {
|
|
5142
|
+
const result = renderDeclaredTokens(group.source, group.declarations);
|
|
5143
|
+
result.counts.forEach((count2, i) => {
|
|
5144
|
+
if (!count2)
|
|
5145
|
+
invalid(
|
|
5146
|
+
pointer(group.paths[i], "token"),
|
|
5147
|
+
"unused_text_use",
|
|
5148
|
+
"Declaration must match at least one unescaped occurrence"
|
|
5149
|
+
);
|
|
5150
|
+
});
|
|
5151
|
+
}
|
|
5152
|
+
}
|
|
5153
|
+
|
|
5154
|
+
// ../shared/src/library/canonical.ts
|
|
5155
|
+
function canonicalValue(value, path2 = "", depth = 0) {
|
|
5156
|
+
if (depth > 64) invalid(path2, "maximum_depth", "JSON nesting exceeds 64");
|
|
5157
|
+
if (typeof value === "string") validateUnicode(value, path2);
|
|
5158
|
+
if (value === null || typeof value === "boolean" || typeof value === "string") {
|
|
5159
|
+
return JSON.stringify(value);
|
|
5160
|
+
}
|
|
5161
|
+
if (typeof value === "number") {
|
|
5162
|
+
if (!Number.isFinite(value))
|
|
5163
|
+
throw new LibraryValidationError([
|
|
5164
|
+
{ path: "", code: "invalid_number", message: "Numbers must be finite" }
|
|
5165
|
+
]);
|
|
5166
|
+
return JSON.stringify(value);
|
|
5167
|
+
}
|
|
5168
|
+
if (Array.isArray(value)) {
|
|
5169
|
+
for (let i = 0; i < value.length; i++)
|
|
5170
|
+
if (!Object.hasOwn(value, i))
|
|
5171
|
+
invalid(pointer(path2, i), "invalid_value", "Sparse arrays are not JSON values");
|
|
5172
|
+
return `[${value.map((v, i) => canonicalValue(v, pointer(path2, i), depth + 1)).join(",")}]`;
|
|
5173
|
+
}
|
|
5174
|
+
if (typeof value === "object") {
|
|
5175
|
+
if (![Object.prototype, null].includes(Object.getPrototypeOf(value)))
|
|
5176
|
+
invalid(path2, "invalid_value", "Expected a plain JSON object");
|
|
5177
|
+
for (const key of Object.keys(value)) validateUnicode(key, pointer(path2, key));
|
|
5178
|
+
return `{${Object.keys(value).sort().map(
|
|
5179
|
+
(key) => `${JSON.stringify(key)}:${canonicalValue(value[key], pointer(path2, key), depth + 1)}`
|
|
5180
|
+
).join(",")}}`;
|
|
5181
|
+
}
|
|
5182
|
+
throw new LibraryValidationError([
|
|
5183
|
+
{
|
|
5184
|
+
path: "",
|
|
5185
|
+
code: "invalid_value",
|
|
5186
|
+
message: "Undefined and non-JSON values are not canonicalizable"
|
|
5187
|
+
}
|
|
5188
|
+
]);
|
|
5189
|
+
}
|
|
5190
|
+
function canonicalizeLibraryValue(value) {
|
|
5191
|
+
return canonicalValue(value);
|
|
5192
|
+
}
|
|
5193
|
+
async function libraryDocumentDigest(document) {
|
|
5194
|
+
const { digest: _digest, ...unsigned } = document;
|
|
5195
|
+
validateLibraryV3References(unsigned);
|
|
5196
|
+
const bytes2 = new TextEncoder().encode(canonicalValue(unsigned));
|
|
5197
|
+
if (new TextEncoder().encode(canonicalValue({ ...unsigned, digest: `sha256:${"0".repeat(64)}` })).byteLength > LIBRARY_MAX_BYTES)
|
|
5198
|
+
invalid("", "package_too_large", `Document exceeds ${LIBRARY_MAX_BYTES} bytes`);
|
|
5199
|
+
const hash = await crypto.subtle.digest("SHA-256", bytes2);
|
|
5200
|
+
return `sha256:${Array.from(new Uint8Array(hash), (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
|
|
5201
|
+
}
|
|
5202
|
+
|
|
5203
|
+
// ../shared/src/library/parse.ts
|
|
5204
|
+
var StrictJsonParser = class {
|
|
5205
|
+
constructor(source) {
|
|
5206
|
+
this.source = source;
|
|
5207
|
+
}
|
|
5208
|
+
source;
|
|
5209
|
+
offset = 0;
|
|
5210
|
+
parse() {
|
|
5211
|
+
const value = this.value(0, "");
|
|
5212
|
+
this.space();
|
|
5213
|
+
if (this.offset !== this.source.length)
|
|
5214
|
+
this.fail("", "invalid_json", "Unexpected trailing input");
|
|
5215
|
+
return value;
|
|
5216
|
+
}
|
|
5217
|
+
value(depth, path2) {
|
|
5218
|
+
if (depth > LIBRARY_V3_MAX_DEPTH)
|
|
5219
|
+
this.fail(path2, "maximum_depth", `JSON nesting exceeds ${LIBRARY_V3_MAX_DEPTH}`);
|
|
5220
|
+
this.space();
|
|
5221
|
+
const char = this.source[this.offset];
|
|
5222
|
+
if (char === "{") return this.object(depth + 1, path2);
|
|
5223
|
+
if (char === "[") return this.array(depth + 1, path2);
|
|
5224
|
+
if (char === '"') return this.string(path2);
|
|
5225
|
+
if (char === "t" && this.take("true")) return true;
|
|
5226
|
+
if (char === "f" && this.take("false")) return false;
|
|
5227
|
+
if (char === "n" && this.take("null")) return null;
|
|
5228
|
+
if (char === "-" || char >= "0" && char <= "9") return this.number(path2);
|
|
5229
|
+
this.fail(path2, "invalid_json", "Expected a JSON value");
|
|
5230
|
+
}
|
|
5231
|
+
object(depth, path2) {
|
|
5232
|
+
this.offset++;
|
|
5233
|
+
const result = /* @__PURE__ */ Object.create(null);
|
|
5234
|
+
const keys = /* @__PURE__ */ new Set();
|
|
5235
|
+
this.space();
|
|
5236
|
+
if (this.source[this.offset] === "}") {
|
|
5237
|
+
this.offset++;
|
|
5238
|
+
return result;
|
|
5239
|
+
}
|
|
5240
|
+
while (true) {
|
|
5241
|
+
this.space();
|
|
5242
|
+
if (this.source[this.offset] !== '"')
|
|
5243
|
+
this.fail(path2, "invalid_json", "Expected an object key");
|
|
5244
|
+
const key = this.string(path2);
|
|
5245
|
+
const keyPath = `${path2}/${key.replaceAll("~", "~0").replaceAll("/", "~1")}`;
|
|
5246
|
+
if (keys.has(key))
|
|
5247
|
+
this.fail(keyPath, "duplicate_key", `Duplicate object key ${JSON.stringify(key)}`);
|
|
5248
|
+
keys.add(key);
|
|
5249
|
+
this.space();
|
|
5250
|
+
if (this.source[this.offset++] !== ":")
|
|
5251
|
+
this.fail(keyPath, "invalid_json", "Expected colon after object key");
|
|
5252
|
+
result[key] = this.value(depth, keyPath);
|
|
5253
|
+
this.space();
|
|
5254
|
+
const separator = this.source[this.offset++];
|
|
5255
|
+
if (separator === "}") return result;
|
|
5256
|
+
if (separator !== ",") this.fail(path2, "invalid_json", "Expected comma or closing brace");
|
|
5257
|
+
}
|
|
5258
|
+
}
|
|
5259
|
+
array(depth, path2) {
|
|
5260
|
+
this.offset++;
|
|
5261
|
+
const result = [];
|
|
5262
|
+
this.space();
|
|
5263
|
+
if (this.source[this.offset] === "]") {
|
|
5264
|
+
this.offset++;
|
|
5265
|
+
return result;
|
|
5266
|
+
}
|
|
5267
|
+
while (true) {
|
|
5268
|
+
result.push(this.value(depth, `${path2}/${result.length}`));
|
|
5269
|
+
this.space();
|
|
5270
|
+
const separator = this.source[this.offset++];
|
|
5271
|
+
if (separator === "]") return result;
|
|
5272
|
+
if (separator !== ",") this.fail(path2, "invalid_json", "Expected comma or closing bracket");
|
|
5273
|
+
}
|
|
5274
|
+
}
|
|
5275
|
+
string(path2) {
|
|
5276
|
+
const start = this.offset++;
|
|
5277
|
+
while (this.offset < this.source.length) {
|
|
5278
|
+
const code = this.source.charCodeAt(this.offset);
|
|
5279
|
+
if (code === 34) {
|
|
5280
|
+
this.offset++;
|
|
5281
|
+
let value;
|
|
5282
|
+
try {
|
|
5283
|
+
value = JSON.parse(this.source.slice(start, this.offset));
|
|
5284
|
+
} catch {
|
|
5285
|
+
this.fail(path2, "invalid_json", "Invalid JSON string escape");
|
|
5286
|
+
}
|
|
5287
|
+
for (let i = 0; i < value.length; i++) {
|
|
5288
|
+
const unit = value.charCodeAt(i);
|
|
5289
|
+
if (unit >= 55296 && unit <= 56319) {
|
|
5290
|
+
const next = value.charCodeAt(++i);
|
|
5291
|
+
if (!(next >= 56320 && next <= 57343))
|
|
5292
|
+
this.fail(path2, "invalid_unicode", "Lone UTF-16 surrogate is not allowed");
|
|
5293
|
+
} else if (unit >= 56320 && unit <= 57343)
|
|
5294
|
+
this.fail(path2, "invalid_unicode", "Lone UTF-16 surrogate is not allowed");
|
|
5295
|
+
}
|
|
5296
|
+
return value;
|
|
5297
|
+
}
|
|
5298
|
+
if (code < 32) this.fail(path2, "invalid_json", "Unescaped control character in string");
|
|
5299
|
+
if (code === 92) this.offset++;
|
|
5300
|
+
this.offset++;
|
|
5301
|
+
}
|
|
5302
|
+
this.fail(path2, "invalid_json", "Unterminated JSON string");
|
|
5303
|
+
}
|
|
5304
|
+
number(path2) {
|
|
5305
|
+
const rest = this.source.slice(this.offset);
|
|
5306
|
+
const match = /^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/.exec(rest);
|
|
5307
|
+
if (!match) this.fail(path2, "invalid_json", "Invalid number");
|
|
5308
|
+
this.offset += match[0].length;
|
|
5309
|
+
const value = Number(match[0]);
|
|
5310
|
+
if (!Number.isFinite(value)) this.fail(path2, "invalid_number", "Numbers must be finite");
|
|
5311
|
+
return value;
|
|
5312
|
+
}
|
|
5313
|
+
take(word) {
|
|
5314
|
+
if (!this.source.startsWith(word, this.offset)) return false;
|
|
5315
|
+
this.offset += word.length;
|
|
5316
|
+
return true;
|
|
5317
|
+
}
|
|
5318
|
+
space() {
|
|
5319
|
+
while (/[ \t\r\n]/.test(this.source[this.offset] ?? "x")) this.offset++;
|
|
5320
|
+
}
|
|
5321
|
+
fail(path2, code, message3) {
|
|
5322
|
+
throw new LibraryValidationError([{ path: path2, code, message: message3 }]);
|
|
5323
|
+
}
|
|
5324
|
+
};
|
|
5325
|
+
var TOP_LEVEL = {
|
|
5326
|
+
workflow: [
|
|
5327
|
+
"format",
|
|
5328
|
+
"version",
|
|
5329
|
+
"profile",
|
|
5330
|
+
"exported_at",
|
|
5331
|
+
"digest",
|
|
5332
|
+
"main_workflow_id",
|
|
5333
|
+
"workflows",
|
|
5334
|
+
"context",
|
|
5335
|
+
"inputs",
|
|
5336
|
+
"text_uses",
|
|
5337
|
+
"schedules",
|
|
5338
|
+
"routing"
|
|
5339
|
+
],
|
|
5340
|
+
library: [
|
|
5341
|
+
"format",
|
|
5342
|
+
"version",
|
|
5343
|
+
"profile",
|
|
5344
|
+
"exported_at",
|
|
5345
|
+
"digest",
|
|
5346
|
+
"projects",
|
|
5347
|
+
"labels",
|
|
5348
|
+
"workflows",
|
|
5349
|
+
"context"
|
|
5350
|
+
]
|
|
5351
|
+
};
|
|
5352
|
+
function structuralDocument(value, allowMissingDigest) {
|
|
5353
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
5354
|
+
throw new LibraryValidationError([
|
|
5355
|
+
{ path: "", code: "invalid_document", message: "Library document must be an object" }
|
|
5356
|
+
]);
|
|
5357
|
+
const object3 = value;
|
|
5358
|
+
if (object3.format !== "tines.library")
|
|
5359
|
+
throw new LibraryValidationError([
|
|
5360
|
+
{ path: "/format", code: "invalid_format", message: "Expected tines.library" }
|
|
5361
|
+
]);
|
|
5362
|
+
if (object3.version !== 3)
|
|
5363
|
+
throw new LibraryValidationError([
|
|
5364
|
+
{
|
|
5365
|
+
path: "/version",
|
|
5366
|
+
code: "unsupported_version",
|
|
5367
|
+
message: `Unsupported library version ${String(object3.version)}`
|
|
5368
|
+
}
|
|
5369
|
+
]);
|
|
5370
|
+
if (object3.profile !== "workflow" && object3.profile !== "library")
|
|
5371
|
+
throw new LibraryValidationError([
|
|
5372
|
+
{ path: "/profile", code: "invalid_profile", message: "Expected workflow or library profile" }
|
|
5373
|
+
]);
|
|
5374
|
+
const allowed = TOP_LEVEL[object3.profile];
|
|
5375
|
+
const unknown = Object.keys(object3).find((key) => !allowed.includes(key));
|
|
5376
|
+
if (unknown)
|
|
5377
|
+
throw new LibraryValidationError([
|
|
5378
|
+
{ path: `/${unknown}`, code: "unknown_field", message: `Unknown field ${unknown}` }
|
|
5379
|
+
]);
|
|
5380
|
+
if (!Number.isSafeInteger(object3.exported_at) || object3.exported_at < 0)
|
|
5381
|
+
throw new LibraryValidationError([
|
|
5382
|
+
{
|
|
5383
|
+
path: "/exported_at",
|
|
5384
|
+
code: "invalid_integer",
|
|
5385
|
+
message: "exported_at must be a non-negative safe integer"
|
|
5386
|
+
}
|
|
5387
|
+
]);
|
|
5388
|
+
if (!(allowMissingDigest && object3.digest === void 0) && (typeof object3.digest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(object3.digest)))
|
|
5389
|
+
throw new LibraryValidationError([
|
|
5390
|
+
{
|
|
5391
|
+
path: "/digest",
|
|
5392
|
+
code: "invalid_digest",
|
|
5393
|
+
message: "digest must be sha256 followed by 64 lowercase hex characters"
|
|
5394
|
+
}
|
|
5395
|
+
]);
|
|
5396
|
+
for (const key of allowed) {
|
|
5397
|
+
if (key === "digest" && allowMissingDigest) continue;
|
|
5398
|
+
if (!(key in object3))
|
|
5399
|
+
throw new LibraryValidationError([
|
|
5400
|
+
{ path: `/${key}`, code: "missing_field", message: `Missing required field ${key}` }
|
|
5401
|
+
]);
|
|
5402
|
+
}
|
|
5403
|
+
return {
|
|
5404
|
+
...object3,
|
|
5405
|
+
digest: typeof object3.digest === "string" ? object3.digest : ""
|
|
5406
|
+
};
|
|
5407
|
+
}
|
|
5408
|
+
async function parseLibraryV3Document(input2, options = {}) {
|
|
5409
|
+
const bytes2 = typeof input2 === "string" ? new TextEncoder().encode(input2) : input2;
|
|
5410
|
+
if (bytes2.byteLength > LIBRARY_MAX_BYTES)
|
|
5411
|
+
throw new LibraryValidationError([
|
|
5412
|
+
{
|
|
5413
|
+
path: "",
|
|
5414
|
+
code: "package_too_large",
|
|
5415
|
+
message: `Document exceeds ${LIBRARY_MAX_BYTES} bytes`
|
|
5416
|
+
}
|
|
5417
|
+
]);
|
|
5418
|
+
let source;
|
|
5419
|
+
try {
|
|
5420
|
+
source = typeof input2 === "string" ? input2 : new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(input2);
|
|
5421
|
+
} catch {
|
|
5422
|
+
throw new LibraryValidationError([
|
|
5423
|
+
{ path: "", code: "invalid_utf8", message: "Document is not valid UTF-8" }
|
|
5424
|
+
]);
|
|
5425
|
+
}
|
|
5426
|
+
const document = structuralDocument(
|
|
5427
|
+
new StrictJsonParser(source).parse(),
|
|
5428
|
+
options.allowMissingDigest ?? false
|
|
5429
|
+
);
|
|
5430
|
+
validateLibraryV3References(document);
|
|
5431
|
+
const actual = await libraryDocumentDigest(document);
|
|
5432
|
+
if (document.digest && document.digest !== actual)
|
|
5433
|
+
throw new LibraryValidationError([
|
|
5434
|
+
{
|
|
5435
|
+
path: "/digest",
|
|
5436
|
+
code: "digest_mismatch",
|
|
5437
|
+
message: `Document digest does not match; expected ${actual}`
|
|
5438
|
+
}
|
|
5439
|
+
]);
|
|
5440
|
+
return { ...document, digest: actual };
|
|
5441
|
+
}
|
|
5442
|
+
function parseStrictLibraryJson(source) {
|
|
5443
|
+
return new StrictJsonParser(source).parse();
|
|
5444
|
+
}
|
|
4588
5445
|
|
|
4589
5446
|
// ../shared/src/library/choices.ts
|
|
4590
5447
|
var PACKAGE_CHOICES_MAX_BYTES = 512 * 1024;
|
|
@@ -4737,48 +5594,48 @@ function commentLines(comment) {
|
|
|
4737
5594
|
function sinceLastRunLines(since, now = Date.now()) {
|
|
4738
5595
|
const prev = since.previous_run;
|
|
4739
5596
|
const ended = prev.ended_at === null ? "not ended" : `ended ${ageLabel(prev.ended_at, now)} ago`;
|
|
4740
|
-
const
|
|
5597
|
+
const lines2 = [
|
|
4741
5598
|
`since the last run (${prev.state_at_start_name ?? "unknown state"}, ${prev.run_id} ${ended}):`
|
|
4742
5599
|
];
|
|
4743
5600
|
const t = since.transition;
|
|
4744
5601
|
if (t) {
|
|
4745
5602
|
const via = t.action ? `via "${t.action}"` : "moved directly";
|
|
4746
5603
|
const stale = since.stale_artifacts.length > 0 ? ` \u2014 now stale: ${since.stale_artifacts.join(", ")}` : "";
|
|
4747
|
-
|
|
5604
|
+
lines2.push(
|
|
4748
5605
|
` moved from ${t.from_state.name} ${via} by ${actorLabel(t.actor)} ${ageLabel(t.at, now)} ago${stale}`
|
|
4749
5606
|
);
|
|
4750
5607
|
}
|
|
4751
|
-
for (const c of since.comments)
|
|
5608
|
+
for (const c of since.comments) lines2.push(...commentLines(c));
|
|
4752
5609
|
if (since.comment_count > since.comments.length) {
|
|
4753
5610
|
const hidden = since.comment_count - since.comments.length;
|
|
4754
|
-
|
|
5611
|
+
lines2.push(` \u2026 and ${hidden} earlier comment${hidden === 1 ? "" : "s"}`);
|
|
4755
5612
|
}
|
|
4756
|
-
return
|
|
5613
|
+
return lines2;
|
|
4757
5614
|
}
|
|
4758
5615
|
function roundLines(round, now = Date.now()) {
|
|
4759
5616
|
const b = round.boundary;
|
|
4760
5617
|
const from = b ? `since ${actorLabel(b.actor)} ${b.action ? `moved "${b.action}"` : "moved it directly"} ${timestamp(b.at)}` : `since created ${timestamp(round.boundary_at)}`;
|
|
4761
|
-
const
|
|
5618
|
+
const lines2 = [`round (${round.run_count} run${round.run_count === 1 ? "" : "s"} ${from}):`];
|
|
4762
5619
|
for (const stage of round.stages) {
|
|
4763
5620
|
const [latest, ...earlier] = stage.runs;
|
|
4764
5621
|
if (!latest) continue;
|
|
4765
|
-
|
|
5622
|
+
lines2.push(` ${stage.state.name ?? stage.state.id} \u2014 ${runHeadline(latest, now)}`);
|
|
4766
5623
|
if (latest.artifacts.length > 0) {
|
|
4767
|
-
|
|
5624
|
+
lines2.push(` ${latest.artifacts.map(artifactChangeLabel).join(" \xB7 ")}`);
|
|
4768
5625
|
}
|
|
4769
|
-
for (const run of earlier)
|
|
5626
|
+
for (const run of earlier) lines2.push(` ${earlierAttemptLine(run, now)}`);
|
|
4770
5627
|
if (latest.summary_comment) {
|
|
4771
|
-
|
|
4772
|
-
for (const line of latest.summary_comment.body.split("\n"))
|
|
5628
|
+
lines2.push(` summary (${latest.summary_comment.id}):`);
|
|
5629
|
+
for (const line of latest.summary_comment.body.split("\n")) lines2.push(` ${line}`);
|
|
4773
5630
|
}
|
|
4774
5631
|
const earlierComments = latest.earlier_comment_ids;
|
|
4775
5632
|
if (earlierComments.length > 0) {
|
|
4776
|
-
|
|
5633
|
+
lines2.push(
|
|
4777
5634
|
` ${earlierComments.length} earlier comment${earlierComments.length === 1 ? "" : "s"}: ${earlierComments.join(", ")}`
|
|
4778
5635
|
);
|
|
4779
5636
|
}
|
|
4780
5637
|
}
|
|
4781
|
-
return
|
|
5638
|
+
return lines2;
|
|
4782
5639
|
}
|
|
4783
5640
|
function runHeadline(run, now) {
|
|
4784
5641
|
const cost = runCostLabel(run);
|
|
@@ -4824,9 +5681,9 @@ function runRow(run) {
|
|
|
4824
5681
|
timestamp(run.created_at)
|
|
4825
5682
|
];
|
|
4826
5683
|
}
|
|
4827
|
-
function byteSize(
|
|
5684
|
+
function byteSize(bytes2) {
|
|
4828
5685
|
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
4829
|
-
let value = Math.max(0,
|
|
5686
|
+
let value = Math.max(0, bytes2);
|
|
4830
5687
|
let unit = 0;
|
|
4831
5688
|
while (value >= 1024 && unit < units.length - 1) {
|
|
4832
5689
|
value /= 1024;
|
|
@@ -5144,15 +6001,15 @@ async function resolveStateFlag(api, ref) {
|
|
|
5144
6001
|
if (sep2 < 1 || sep2 === ref.length - 1) {
|
|
5145
6002
|
die(`--state must look like <workflow>/<state>, got "${ref}"`);
|
|
5146
6003
|
}
|
|
5147
|
-
const
|
|
5148
|
-
const
|
|
5149
|
-
const state =
|
|
6004
|
+
const workflow2 = await resolveWorkflow(api, ref.slice(0, sep2));
|
|
6005
|
+
const stateRef2 = ref.slice(sep2 + 1);
|
|
6006
|
+
const state = workflow2.states.find((s) => s.name === stateRef2) ?? workflow2.states.find((s) => s.id === stateRef2);
|
|
5150
6007
|
if (!state) {
|
|
5151
6008
|
die(
|
|
5152
|
-
`workflow "${
|
|
6009
|
+
`workflow "${workflow2.name}" has no state "${stateRef2}" (have: ${workflow2.states.map((s) => s.name).join(", ")})`
|
|
5153
6010
|
);
|
|
5154
6011
|
}
|
|
5155
|
-
return { workflow, state };
|
|
6012
|
+
return { workflow: workflow2, state };
|
|
5156
6013
|
}
|
|
5157
6014
|
var collect = (value, previous) => [...previous, value];
|
|
5158
6015
|
async function resolveRunner(api, ref) {
|
|
@@ -5216,12 +6073,12 @@ function withScopeFlags(cmd) {
|
|
|
5216
6073
|
return cmd.option("-p, --project <name>", "scope: project name or id").option("-s, --state <workflow/state>", "scope: workflow-qualified state").option("-i, --issue <ref>", "scope: issue (<project>/<number>)").option("-l, --label <name>", "scope: issue label name or id").addHelpText("after", SCOPE_FLAGS_HELP);
|
|
5217
6074
|
}
|
|
5218
6075
|
function register(program3) {
|
|
5219
|
-
const
|
|
6076
|
+
const context2 = program3.command("context").description(
|
|
5220
6077
|
"Manage context items (prompts, skills, repo pointers) scoped to projects, states, and issues"
|
|
5221
6078
|
);
|
|
5222
6079
|
withList(
|
|
5223
6080
|
withScopeFlags(
|
|
5224
|
-
|
|
6081
|
+
context2.command("list").description(
|
|
5225
6082
|
"List context items (scope filters match every item whose scope includes the element)"
|
|
5226
6083
|
).option("-k, --kind <kind>", "filter by kind: prompt, skill, or repo").option("--exact", "only items whose scope sets exactly the given dimensions").option("-q, --search <text>", "search names and descriptions")
|
|
5227
6084
|
)
|
|
@@ -5259,7 +6116,7 @@ function register(program3) {
|
|
|
5259
6116
|
}
|
|
5260
6117
|
);
|
|
5261
6118
|
withCommon(
|
|
5262
|
-
|
|
6119
|
+
context2.command("show <id>").description("Show a context item (skills include their files)")
|
|
5263
6120
|
).action(async (id2, opts) => {
|
|
5264
6121
|
const item = await client(opts).getContextItem(id2);
|
|
5265
6122
|
if (opts.json) return printJson(item);
|
|
@@ -5267,7 +6124,7 @@ function register(program3) {
|
|
|
5267
6124
|
});
|
|
5268
6125
|
withCommon(
|
|
5269
6126
|
withScopeFlags(
|
|
5270
|
-
|
|
6127
|
+
context2.command("create").description("Create a context item scoped to a project, state, and/or issue").requiredOption("-k, --kind <kind>", "prompt, skill, or repo").requiredOption(
|
|
5271
6128
|
"-n, --name <name>",
|
|
5272
6129
|
"item name (slug-like for skills; the dedup/override key)"
|
|
5273
6130
|
).option("-d, --description <text>", "one-liner shown in lists").option("--body <md>", "prompt body: inline Markdown or @file (escape a literal @ as @@)").option(
|
|
@@ -5303,7 +6160,7 @@ function register(program3) {
|
|
|
5303
6160
|
);
|
|
5304
6161
|
withCommon(
|
|
5305
6162
|
withScopeFlags(
|
|
5306
|
-
|
|
6163
|
+
context2.command("edit <id>").description("Edit a context item: payload, name, description, or scope").option("-n, --name <name>", "rename the item").option("-d, --description <text>", "set the description").option("--body <md>", "prompt body: inline Markdown or @file (escape a literal @ as @@)").option("--file <path>=@<local>", "add or replace a skill file (repeatable)", collect, []).option("--remove-file <path>", "remove a skill file (repeatable)", collect, []).option("--repo-url <url>", "repo: clone URL").option("--branch <branch>", "repo: branch (empty string clears it)").option("--dir <dir>", "repo: checkout directory (empty string restores the URL default)").option(
|
|
5307
6164
|
"--unset <dimension>",
|
|
5308
6165
|
"drop a scope dimension: project, state, issue, or label (repeatable)",
|
|
5309
6166
|
collect,
|
|
@@ -5336,19 +6193,19 @@ function register(program3) {
|
|
|
5336
6193
|
if (current.kind !== "skill")
|
|
5337
6194
|
die(`--file/--remove-file only apply to skills (this is a ${current.kind})`);
|
|
5338
6195
|
if (body.expected_version === void 0) body.expected_version = current.version;
|
|
5339
|
-
const
|
|
6196
|
+
const files2 = new Map((current.files ?? []).map((f) => [f.path, f.content]));
|
|
5340
6197
|
for (const path2 of opts.removeFile) {
|
|
5341
|
-
if (!
|
|
6198
|
+
if (!files2.delete(path2)) {
|
|
5342
6199
|
die(
|
|
5343
|
-
`no file "${path2}" in skill "${current.name}" (have: ${[...
|
|
6200
|
+
`no file "${path2}" in skill "${current.name}" (have: ${[...files2.keys()].join(", ") || "none"})`
|
|
5344
6201
|
);
|
|
5345
6202
|
}
|
|
5346
6203
|
}
|
|
5347
6204
|
for (const spec of opts.file) {
|
|
5348
6205
|
const f = parseFileSpec(spec);
|
|
5349
|
-
|
|
6206
|
+
files2.set(f.path, f.content);
|
|
5350
6207
|
}
|
|
5351
|
-
body.files = [...
|
|
6208
|
+
body.files = [...files2.entries()].map(([path2, content]) => ({ path: path2, content }));
|
|
5352
6209
|
}
|
|
5353
6210
|
if (opts.repoUrl !== void 0) body.repo_url = opts.repoUrl;
|
|
5354
6211
|
if (opts.branch !== void 0) body.repo_branch = opts.branch === "" ? null : opts.branch;
|
|
@@ -5363,7 +6220,7 @@ function register(program3) {
|
|
|
5363
6220
|
console.log(`updated ${item.kind} "${item.name}" (${item.id}) \u2014 scope: ${item.scope.label}`);
|
|
5364
6221
|
}
|
|
5365
6222
|
);
|
|
5366
|
-
withCommon(
|
|
6223
|
+
withCommon(context2.command("delete <id>").description("Delete a context item")).action(
|
|
5367
6224
|
async (id2, opts) => {
|
|
5368
6225
|
const api = client(opts);
|
|
5369
6226
|
const item = await api.getContextItem(id2);
|
|
@@ -5372,7 +6229,7 @@ function register(program3) {
|
|
|
5372
6229
|
}
|
|
5373
6230
|
);
|
|
5374
6231
|
withCommon(
|
|
5375
|
-
|
|
6232
|
+
context2.command("init").description('Seed the global "agent-guidelines" prompt (a no-op if it already exists)')
|
|
5376
6233
|
).action(async (opts) => {
|
|
5377
6234
|
const api = client(opts);
|
|
5378
6235
|
const items = await listAll(
|
|
@@ -5424,98 +6281,98 @@ function repoLine(change) {
|
|
|
5424
6281
|
return ` checkout: ${show(before)} => ${show(after)}`;
|
|
5425
6282
|
}
|
|
5426
6283
|
function routingLines(preview) {
|
|
5427
|
-
const
|
|
6284
|
+
const lines2 = ["", "Routing:"];
|
|
5428
6285
|
for (const [label, explainer] of [
|
|
5429
6286
|
[" before", preview.routing.before],
|
|
5430
6287
|
[" after ", preview.routing.after]
|
|
5431
6288
|
]) {
|
|
5432
6289
|
if (!explainer) {
|
|
5433
|
-
|
|
6290
|
+
lines2.push(`${label}: no explanation available`);
|
|
5434
6291
|
continue;
|
|
5435
6292
|
}
|
|
5436
|
-
|
|
6293
|
+
lines2.push(`${label}: ${explainer.verdict}`);
|
|
5437
6294
|
if (explainer.pin) {
|
|
5438
|
-
|
|
6295
|
+
lines2.push(
|
|
5439
6296
|
` pin: runner ${explainer.pin.runner_name ?? explainer.pin.runner_id}, tier ${explainer.pin.tier ?? "default"}`
|
|
5440
6297
|
);
|
|
5441
6298
|
}
|
|
5442
6299
|
for (const check of explainer.checks) {
|
|
5443
|
-
|
|
6300
|
+
lines2.push(
|
|
5444
6301
|
` ${check.ok ? "pass" : "FAIL"} ${check.name}: ${check.detail}${check.action?.label ? ` (${check.action.label})` : ""}`
|
|
5445
6302
|
);
|
|
5446
|
-
if (check.action?.cli)
|
|
6303
|
+
if (check.action?.cli) lines2.push(` fix: ${check.action.cli}`);
|
|
5447
6304
|
}
|
|
5448
6305
|
if (explainer.matched_rule)
|
|
5449
|
-
|
|
6306
|
+
lines2.push(` matched rule ${explainer.matched_rule.scope_label}`);
|
|
5450
6307
|
for (const tie of explainer.ambiguous_rules) {
|
|
5451
|
-
|
|
6308
|
+
lines2.push(` tied rule ${tie.scope_label}`);
|
|
5452
6309
|
}
|
|
5453
6310
|
if (explainer.runner_rule)
|
|
5454
|
-
|
|
5455
|
-
if (explainer.tier_override)
|
|
6311
|
+
lines2.push(` runner source rule ${explainer.runner_rule.scope_label}`);
|
|
6312
|
+
if (explainer.tier_override) lines2.push(` tier override ${explainer.tier_override}`);
|
|
5456
6313
|
for (const target of explainer.targets) {
|
|
5457
|
-
|
|
6314
|
+
lines2.push(
|
|
5458
6315
|
` target ${target.runner_name} \u2014 ${target.tier}${target.model ? ` / ${target.model}` : ""}: ${target.verdict} \u2014 ${target.detail}`
|
|
5459
6316
|
);
|
|
5460
6317
|
}
|
|
5461
6318
|
if (explainer.active_run)
|
|
5462
|
-
|
|
6319
|
+
lines2.push(
|
|
5463
6320
|
` active run ${explainer.active_run.id} (${explainer.active_run.status})`
|
|
5464
6321
|
);
|
|
5465
6322
|
if (explainer.queue_position !== null)
|
|
5466
|
-
|
|
5467
|
-
|
|
6323
|
+
lines2.push(` queue position ${explainer.queue_position}`);
|
|
6324
|
+
lines2.push(
|
|
5468
6325
|
` attempts ${explainer.attempt_count}/${explainer.attempt_limit}; parked ${explainer.parked ? "yes" : "no"}`
|
|
5469
6326
|
);
|
|
5470
6327
|
}
|
|
5471
|
-
|
|
5472
|
-
return
|
|
6328
|
+
lines2.push(" Capacity, heartbeats and spending are advisory and may change at any moment.");
|
|
6329
|
+
return lines2;
|
|
5473
6330
|
}
|
|
5474
6331
|
function repoDetail(repo) {
|
|
5475
6332
|
return `${repo.url}; branch ${repo.branch ?? "repository default branch"}; directory ${repo.dir}`;
|
|
5476
6333
|
}
|
|
5477
6334
|
function effectiveRepositoryLines(preview) {
|
|
5478
|
-
const
|
|
5479
|
-
for (const [label,
|
|
6335
|
+
const lines2 = ["", "Effective repositories:"];
|
|
6336
|
+
for (const [label, context2] of [
|
|
5480
6337
|
[" before", preview.context.before],
|
|
5481
6338
|
[" after ", preview.context.after]
|
|
5482
6339
|
]) {
|
|
5483
|
-
|
|
5484
|
-
if (!
|
|
5485
|
-
for (const repo of
|
|
5486
|
-
|
|
5487
|
-
|
|
5488
|
-
for (const loser of
|
|
6340
|
+
lines2.push(`${label}:`);
|
|
6341
|
+
if (!context2.repos.length) lines2.push(" none");
|
|
6342
|
+
for (const repo of context2.repos) {
|
|
6343
|
+
lines2.push(` ${repo.name} \u2014 effective (${repo.scope.label})`);
|
|
6344
|
+
lines2.push(` ${repoDetail(repo)}`);
|
|
6345
|
+
for (const loser of context2.overridden.filter(
|
|
5489
6346
|
(item) => item.kind === "repo" && item.name === repo.name
|
|
5490
6347
|
)) {
|
|
5491
|
-
|
|
6348
|
+
lines2.push(
|
|
5492
6349
|
` overridden candidate ${loser.name} (${loser.scope.label}) \u2014 overridden by ${loser.overridden_by}`
|
|
5493
6350
|
);
|
|
5494
|
-
|
|
6351
|
+
lines2.push(
|
|
5495
6352
|
` ${loser.repo ? repoDetail(loser.repo) : "checkout details unavailable from this server"}`
|
|
5496
6353
|
);
|
|
5497
6354
|
}
|
|
5498
6355
|
}
|
|
5499
6356
|
}
|
|
5500
|
-
return
|
|
6357
|
+
return lines2;
|
|
5501
6358
|
}
|
|
5502
6359
|
function conflictLines(preview) {
|
|
5503
|
-
const
|
|
6360
|
+
const lines2 = ["", "Repository checkout conflicts:"];
|
|
5504
6361
|
const deltas = deriveTransferConflictDeltas(preview.context.before, preview.context.after);
|
|
5505
|
-
if (!deltas.length) return [...
|
|
6362
|
+
if (!deltas.length) return [...lines2, " none"];
|
|
5506
6363
|
const participants = (items) => items.map(
|
|
5507
6364
|
(item) => item.name && item.scope_label ? `${item.name} (${item.scope_label})` : item.item_id
|
|
5508
6365
|
).join(", ");
|
|
5509
6366
|
for (const delta of deltas) {
|
|
5510
|
-
|
|
5511
|
-
if (delta.before.length)
|
|
5512
|
-
if (delta.after.length)
|
|
6367
|
+
lines2.push(` ${delta.change[0].toUpperCase()}${delta.change.slice(1)} \u2014 ${delta.dir}`);
|
|
6368
|
+
if (delta.before.length) lines2.push(` Before: ${participants(delta.before)}`);
|
|
6369
|
+
if (delta.after.length) lines2.push(` After: ${participants(delta.after)}`);
|
|
5513
6370
|
}
|
|
5514
|
-
return
|
|
6371
|
+
return lines2;
|
|
5515
6372
|
}
|
|
5516
6373
|
function formatTransferPreview(preview) {
|
|
5517
6374
|
const p = preview.preserved;
|
|
5518
|
-
const
|
|
6375
|
+
const lines2 = [
|
|
5519
6376
|
`Move ${preview.old_ref.ref} from ${preview.source.name} to ${preview.destination.name}`,
|
|
5520
6377
|
` ${preview.number_notice} The issue keeps its stable ID (${preview.issue_id}) and every old ref keeps working.`,
|
|
5521
6378
|
"",
|
|
@@ -5526,37 +6383,37 @@ function formatTransferPreview(preview) {
|
|
|
5526
6383
|
` pins: ${p.pinned_runner_id ?? "none"}${p.pinned_tier ? ` (${p.pinned_tier})` : ""}; attempts ${p.attempt_count}${p.parked ? "; parked" : ""}`
|
|
5527
6384
|
];
|
|
5528
6385
|
const changes = inspectableChanges(preview);
|
|
5529
|
-
|
|
6386
|
+
lines2.push("", changes.length ? "Guidance:" : "Guidance: none.");
|
|
5530
6387
|
changes.forEach((change, index) => {
|
|
5531
|
-
|
|
6388
|
+
lines2.push(` [${index}] ${change.kind} "${change.name}" \u2014 ${CHANGE_LABEL[change.change]}`);
|
|
5532
6389
|
const scopeBefore = change.scope_before?.label ?? "\u2014";
|
|
5533
6390
|
const scopeAfter = change.scope_after?.label ?? "\u2014";
|
|
5534
|
-
|
|
5535
|
-
|
|
6391
|
+
lines2.push(` scope: ${scopeBefore} => ${scopeAfter}`);
|
|
6392
|
+
lines2.push(
|
|
5536
6393
|
` before: ${effectiveness(Boolean(change.scope_before), change.effective_before)}; after: ${effectiveness(Boolean(change.scope_after), change.effective_after)}`
|
|
5537
6394
|
);
|
|
5538
6395
|
const repo = repoLine(change);
|
|
5539
|
-
if (repo)
|
|
6396
|
+
if (repo) lines2.push(repo);
|
|
5540
6397
|
});
|
|
5541
|
-
|
|
5542
|
-
|
|
6398
|
+
lines2.push(...effectiveRepositoryLines(preview), ...conflictLines(preview));
|
|
6399
|
+
lines2.push(...routingLines(preview));
|
|
5543
6400
|
if (preview.schedule) {
|
|
5544
|
-
|
|
6401
|
+
lines2.push(
|
|
5545
6402
|
"",
|
|
5546
6403
|
`Schedule: ${preview.schedule.name} (${preview.schedule.project_name}) \u2014 ${preview.schedule.notice}`
|
|
5547
6404
|
);
|
|
5548
6405
|
}
|
|
5549
6406
|
if (preview.blockers.length) {
|
|
5550
|
-
|
|
6407
|
+
lines2.push("", "Cannot move yet:");
|
|
5551
6408
|
for (const blocker of preview.blockers) {
|
|
5552
|
-
|
|
6409
|
+
lines2.push(
|
|
5553
6410
|
` ${blocker.code}: ${blocker.message}${blocker.remedy ? ` (${blocker.remedy})` : ""}`
|
|
5554
6411
|
);
|
|
5555
6412
|
}
|
|
5556
6413
|
}
|
|
5557
6414
|
if (preview.noop)
|
|
5558
|
-
|
|
5559
|
-
return
|
|
6415
|
+
lines2.push("", "This issue is already in that project; moving would change nothing.");
|
|
6416
|
+
return lines2.join("\n");
|
|
5560
6417
|
}
|
|
5561
6418
|
function formatTransferItem(preview, index) {
|
|
5562
6419
|
const change = inspectableChanges(preview)[index];
|
|
@@ -5722,10 +6579,10 @@ function effectiveContentType(type, source, explicit, sniff) {
|
|
|
5722
6579
|
if (source.kind === "file-stdin") return "application/octet-stream";
|
|
5723
6580
|
return void 0;
|
|
5724
6581
|
}
|
|
5725
|
-
function planAttach(
|
|
5726
|
-
const { ref, name: name2, positional, flags, probe } =
|
|
6582
|
+
function planAttach(input2, sniff) {
|
|
6583
|
+
const { ref, name: name2, positional, flags, probe } = input2;
|
|
5727
6584
|
assertOneSource(flags, positional);
|
|
5728
|
-
const gates = flags.ignoreGates ? [] :
|
|
6585
|
+
const gates = flags.ignoreGates ? [] : input2.gates;
|
|
5729
6586
|
const plan = positional !== void 0 ? planPositional(name2, positional, gates, probe, flags) : planFlags(flags, probe);
|
|
5730
6587
|
if (gates.length > 0) {
|
|
5731
6588
|
checkAccepted(plan, gates, ref, name2, flags, positional, sniff);
|
|
@@ -6119,14 +6976,14 @@ function readTextSource(source) {
|
|
|
6119
6976
|
throw new Error(`not a text source: ${source.kind}`);
|
|
6120
6977
|
}
|
|
6121
6978
|
function walkFolder(dir) {
|
|
6122
|
-
const
|
|
6979
|
+
const files2 = [];
|
|
6123
6980
|
const walk = (abs, rel) => {
|
|
6124
6981
|
for (const entry of readdirSync(abs, { withFileTypes: true })) {
|
|
6125
6982
|
const nextAbs = join2(abs, entry.name);
|
|
6126
6983
|
const nextRel = rel ? `${rel}/${entry.name}` : entry.name;
|
|
6127
6984
|
if (entry.isDirectory()) walk(nextAbs, nextRel);
|
|
6128
6985
|
else if (entry.isFile()) {
|
|
6129
|
-
|
|
6986
|
+
files2.push({
|
|
6130
6987
|
path: nextRel,
|
|
6131
6988
|
contentType: sniffContentType(entry.name),
|
|
6132
6989
|
bytes: readFileSync4(nextAbs)
|
|
@@ -6135,7 +6992,7 @@ function walkFolder(dir) {
|
|
|
6135
6992
|
}
|
|
6136
6993
|
};
|
|
6137
6994
|
walk(dir, "");
|
|
6138
|
-
return
|
|
6995
|
+
return files2;
|
|
6139
6996
|
}
|
|
6140
6997
|
function printExplainer(issue, ex) {
|
|
6141
6998
|
console.log(`${issue.project_name}/#${issue.number} ${issue.title}`);
|
|
@@ -6576,34 +7433,34 @@ function register2(program3) {
|
|
|
6576
7433
|
).action(async (ref, opts) => {
|
|
6577
7434
|
const api = client(opts);
|
|
6578
7435
|
const issue = await resolveIssue(api, ref);
|
|
6579
|
-
const
|
|
6580
|
-
if (opts.json && !opts.out) return printJson(
|
|
7436
|
+
const context2 = await api.getIssueContext(issue.id);
|
|
7437
|
+
if (opts.json && !opts.out) return printJson(context2);
|
|
6581
7438
|
if (opts.out === void 0) {
|
|
6582
|
-
if (
|
|
6583
|
-
if (
|
|
7439
|
+
if (context2.prompt.text) console.log(context2.prompt.text);
|
|
7440
|
+
if (context2.skills.length > 0) {
|
|
6584
7441
|
console.log(`
|
|
6585
|
-
skills: ${
|
|
7442
|
+
skills: ${context2.skills.map((s) => s.name).join(", ")}`);
|
|
6586
7443
|
}
|
|
6587
|
-
for (const repo of
|
|
7444
|
+
for (const repo of context2.repos) {
|
|
6588
7445
|
console.log(
|
|
6589
7446
|
`repo: ${repo.name} ${repo.url}${repo.branch ? `#${repo.branch}` : ""} \u2192 ${repo.dir}/`
|
|
6590
7447
|
);
|
|
6591
7448
|
}
|
|
6592
|
-
for (const o of
|
|
7449
|
+
for (const o of context2.overridden) {
|
|
6593
7450
|
console.log(
|
|
6594
7451
|
`overridden: ${o.kind} "${o.name}" [${o.scope.label}] (overridden by ${o.overridden_by})`
|
|
6595
7452
|
);
|
|
6596
7453
|
}
|
|
6597
|
-
for (const c of
|
|
7454
|
+
for (const c of context2.conflicts) {
|
|
6598
7455
|
console.log(
|
|
6599
7456
|
`conflict: repos ${c.item_ids.join(", ")} all resolve to checkout dir "${c.dir}"`
|
|
6600
7457
|
);
|
|
6601
7458
|
}
|
|
6602
7459
|
return;
|
|
6603
7460
|
}
|
|
6604
|
-
if (
|
|
7461
|
+
if (context2.conflicts.length > 0) {
|
|
6605
7462
|
die(
|
|
6606
|
-
`refusing to write: checkout-directory conflict${
|
|
7463
|
+
`refusing to write: checkout-directory conflict${context2.conflicts.length === 1 ? "" : "s"} among the effective repos (${context2.conflicts.map((c) => `"${c.dir}": ${c.item_ids.join(", ")}`).join("; ")}); rename or re-dir the items first`
|
|
6607
7464
|
);
|
|
6608
7465
|
}
|
|
6609
7466
|
if (existsSync3(opts.out) && readdirSync(opts.out).length > 0 && !opts.force) {
|
|
@@ -6612,20 +7469,20 @@ skills: ${context.skills.map((s) => s.name).join(", ")}`);
|
|
|
6612
7469
|
mkdirSync2(opts.out, { recursive: true });
|
|
6613
7470
|
writeFileSync2(
|
|
6614
7471
|
join2(opts.out, "prompt.md"),
|
|
6615
|
-
|
|
7472
|
+
context2.prompt.text ? `${context2.prompt.text}
|
|
6616
7473
|
` : ""
|
|
6617
7474
|
);
|
|
6618
|
-
for (const skill of
|
|
7475
|
+
for (const skill of context2.skills) {
|
|
6619
7476
|
for (const file of skill.files) {
|
|
6620
7477
|
const target = join2(opts.out, "skills", skill.name, file.path);
|
|
6621
7478
|
mkdirSync2(dirname2(target), { recursive: true });
|
|
6622
7479
|
writeFileSync2(target, file.content);
|
|
6623
7480
|
}
|
|
6624
7481
|
}
|
|
6625
|
-
writeFileSync2(join2(opts.out, "repos.json"), `${JSON.stringify(
|
|
7482
|
+
writeFileSync2(join2(opts.out, "repos.json"), `${JSON.stringify(context2.repos, null, 2)}
|
|
6626
7483
|
`);
|
|
6627
7484
|
console.log(
|
|
6628
|
-
`wrote ${opts.out}/prompt.md, ${
|
|
7485
|
+
`wrote ${opts.out}/prompt.md, ${context2.skills.length} skill${context2.skills.length === 1 ? "" : "s"}, repos.json (${context2.repos.length} repo${context2.repos.length === 1 ? "" : "s"})`
|
|
6629
7486
|
);
|
|
6630
7487
|
});
|
|
6631
7488
|
withCommon(
|
|
@@ -6646,9 +7503,9 @@ skills: ${context.skills.map((s) => s.name).join(", ")}`);
|
|
|
6646
7503
|
).action(async (ref, opts) => {
|
|
6647
7504
|
const api = client(opts);
|
|
6648
7505
|
const issue = await resolveIssue(api, ref);
|
|
6649
|
-
const
|
|
6650
|
-
if (opts.json) return printJson(
|
|
6651
|
-
console.log(
|
|
7506
|
+
const prompt2 = await api.getIssuePrompt(issue.id);
|
|
7507
|
+
if (opts.json) return printJson(prompt2);
|
|
7508
|
+
console.log(prompt2.text);
|
|
6652
7509
|
});
|
|
6653
7510
|
const artifactsCmd = issues.command("artifacts").description(
|
|
6654
7511
|
"Typed, versioned attachments on an issue \u2014 the work products transition requirements gate on"
|
|
@@ -6711,11 +7568,11 @@ skills: ${context.skills.map((s) => s.name).join(", ")}`);
|
|
|
6711
7568
|
v.reaffirmed_from !== null ? `reaffirmed v${v.reaffirmed_from}` : v.file_count !== null ? `${v.file_count} file${v.file_count === 1 ? "" : "s"}` : v.filename ?? v.url ?? (v.pr_repo_url ? prRefLabel(v) : "")
|
|
6712
7569
|
])
|
|
6713
7570
|
);
|
|
6714
|
-
const
|
|
6715
|
-
if (
|
|
7571
|
+
const files2 = artifact.current_version.files;
|
|
7572
|
+
if (files2 && files2.length > 0) {
|
|
6716
7573
|
console.log(`
|
|
6717
7574
|
files (v${artifact.current_version.version}):`);
|
|
6718
|
-
table(
|
|
7575
|
+
table(files2.map((f) => [` ${f.path}`, f.content_type, `${f.size_bytes} bytes`]));
|
|
6719
7576
|
}
|
|
6720
7577
|
});
|
|
6721
7578
|
withCommon(
|
|
@@ -6754,30 +7611,30 @@ files (v${artifact.current_version.version}):`);
|
|
|
6754
7611
|
let siteHtml = null;
|
|
6755
7612
|
if (plan.type === "folder") {
|
|
6756
7613
|
const dir = plan.source.dir;
|
|
6757
|
-
const
|
|
6758
|
-
if (
|
|
6759
|
-
const index =
|
|
7614
|
+
const files2 = walkFolder(dir);
|
|
7615
|
+
if (files2.length === 0) die(`${dir} contains no files to snapshot`);
|
|
7616
|
+
const index = files2.find((f) => f.path === ARTIFACT_SITE_INDEX);
|
|
6760
7617
|
if (index) siteHtml = index.bytes.toString("utf8");
|
|
6761
|
-
artifact = await api.uploadArtifactFolder(issue.id, name2,
|
|
7618
|
+
artifact = await api.uploadArtifactFolder(issue.id, name2, files2);
|
|
6762
7619
|
if (opts.description !== void 0) {
|
|
6763
7620
|
artifact = await api.putArtifact(issue.id, name2, { description: opts.description });
|
|
6764
7621
|
}
|
|
6765
7622
|
} else if (plan.type === "file") {
|
|
6766
7623
|
const fromPath = plan.source.kind === "file-path" ? plan.source.path : null;
|
|
6767
|
-
let
|
|
7624
|
+
let bytes2;
|
|
6768
7625
|
if (fromPath === null) {
|
|
6769
7626
|
if (process.stdin.isTTY) die('"-" reads the file from stdin, but stdin is a terminal');
|
|
6770
|
-
|
|
7627
|
+
bytes2 = readFileSync4(0);
|
|
6771
7628
|
} else {
|
|
6772
7629
|
try {
|
|
6773
|
-
|
|
7630
|
+
bytes2 = readFileSync4(fromPath);
|
|
6774
7631
|
} catch (err) {
|
|
6775
7632
|
die(`cannot read ${fromPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
6776
7633
|
}
|
|
6777
7634
|
}
|
|
6778
7635
|
const contentType = plan.contentType ?? (fromPath === null ? "application/octet-stream" : sniffContentType(fromPath));
|
|
6779
|
-
if (siteEntry("file", contentType) !== null) siteHtml =
|
|
6780
|
-
artifact = await api.uploadArtifactFile(issue.id, name2,
|
|
7636
|
+
if (siteEntry("file", contentType) !== null) siteHtml = bytes2.toString("utf8");
|
|
7637
|
+
artifact = await api.uploadArtifactFile(issue.id, name2, bytes2, {
|
|
6781
7638
|
filename: plan.filename ?? (fromPath === null ? name2 : basename(fromPath)),
|
|
6782
7639
|
contentType
|
|
6783
7640
|
});
|
|
@@ -6901,9 +7758,9 @@ files (v${artifact.current_version.version}):`);
|
|
|
6901
7758
|
`refusing to write folder artifact into non-empty directory "${opts.out}"; choose a new or empty directory`
|
|
6902
7759
|
);
|
|
6903
7760
|
}
|
|
6904
|
-
const
|
|
7761
|
+
const files2 = version.files ?? [];
|
|
6905
7762
|
let total = 0;
|
|
6906
|
-
for (const file of
|
|
7763
|
+
for (const file of files2) {
|
|
6907
7764
|
const content2 = await api.getArtifactContent(issue.id, name2, {
|
|
6908
7765
|
version: opts.version,
|
|
6909
7766
|
path: file.path
|
|
@@ -6914,26 +7771,26 @@ files (v${artifact.current_version.version}):`);
|
|
|
6914
7771
|
total += content2.bytes.byteLength;
|
|
6915
7772
|
}
|
|
6916
7773
|
return console.log(
|
|
6917
|
-
`wrote ${
|
|
7774
|
+
`wrote ${files2.length} file${files2.length === 1 ? "" : "s"} (${total} bytes) from "${name2}" v${version.version} into ${opts.out}/`
|
|
6918
7775
|
);
|
|
6919
7776
|
}
|
|
6920
7777
|
const content = await api.getArtifactContent(issue.id, name2, { version: opts.version });
|
|
6921
|
-
const
|
|
7778
|
+
const bytes2 = Buffer.from(content.bytes);
|
|
6922
7779
|
if (opts.out !== void 0) {
|
|
6923
7780
|
let target2 = opts.out;
|
|
6924
7781
|
if (existsSync3(target2) && statSync2(target2).isDirectory()) {
|
|
6925
7782
|
target2 = join2(target2, version.filename ?? name2);
|
|
6926
7783
|
}
|
|
6927
|
-
writeFileSync2(target2,
|
|
6928
|
-
return console.log(`wrote ${target2} (${
|
|
7784
|
+
writeFileSync2(target2, bytes2);
|
|
7785
|
+
return console.log(`wrote ${target2} (${bytes2.byteLength} bytes, ${content.content_type})`);
|
|
6929
7786
|
}
|
|
6930
7787
|
if ((content.content_type ?? "").startsWith("text/")) {
|
|
6931
|
-
process.stdout.write(
|
|
7788
|
+
process.stdout.write(bytes2);
|
|
6932
7789
|
return;
|
|
6933
7790
|
}
|
|
6934
7791
|
const target = version.filename ?? name2;
|
|
6935
|
-
writeFileSync2(target,
|
|
6936
|
-
console.log(`wrote ${target} (${
|
|
7792
|
+
writeFileSync2(target, bytes2);
|
|
7793
|
+
console.log(`wrote ${target} (${bytes2.byteLength} bytes, ${content.content_type})`);
|
|
6937
7794
|
}
|
|
6938
7795
|
);
|
|
6939
7796
|
withCommon(
|
|
@@ -7016,14 +7873,14 @@ async function journalItemAt(api, scope) {
|
|
|
7016
7873
|
);
|
|
7017
7874
|
return items.find((i) => i.name === JOURNAL_NAME) ?? null;
|
|
7018
7875
|
}
|
|
7019
|
-
function stateScope(issue, state,
|
|
7876
|
+
function stateScope(issue, state, workflow2) {
|
|
7020
7877
|
return {
|
|
7021
7878
|
project_id: issue.project_id,
|
|
7022
7879
|
project_name: issue.project_name,
|
|
7023
7880
|
workflow_state_id: state.id,
|
|
7024
7881
|
workflow_state_name: state.name,
|
|
7025
|
-
workflow_id:
|
|
7026
|
-
workflow_name:
|
|
7882
|
+
workflow_id: workflow2.id,
|
|
7883
|
+
workflow_name: workflow2.name,
|
|
7027
7884
|
issue_id: null,
|
|
7028
7885
|
issue_ref: null,
|
|
7029
7886
|
label_id: null,
|
|
@@ -7032,11 +7889,11 @@ function stateScope(issue, state, workflow) {
|
|
|
7032
7889
|
label: `project ${issue.project_name} \xB7 state ${state.name}`
|
|
7033
7890
|
};
|
|
7034
7891
|
}
|
|
7035
|
-
async function resolveJournal(api, ref,
|
|
7892
|
+
async function resolveJournal(api, ref, stateRef2) {
|
|
7036
7893
|
const issue = await resolveIssue(api, ref);
|
|
7037
|
-
if (
|
|
7038
|
-
const { workflow, state } = await resolveStateFlag(api,
|
|
7039
|
-
const scope = stateScope(issue, state,
|
|
7894
|
+
if (stateRef2 !== void 0) {
|
|
7895
|
+
const { workflow: workflow2, state } = await resolveStateFlag(api, stateRef2);
|
|
7896
|
+
const scope = stateScope(issue, state, workflow2);
|
|
7040
7897
|
return { issue, scope, note: null, item: await journalItemAt(api, scope) };
|
|
7041
7898
|
}
|
|
7042
7899
|
try {
|
|
@@ -7336,14 +8193,14 @@ function register6(program3) {
|
|
|
7336
8193
|
for (const starter of response.items) {
|
|
7337
8194
|
console.log(`${starter.id} \u2014 ${starter.name}`);
|
|
7338
8195
|
console.log(` ${starter.description}`);
|
|
7339
|
-
for (const
|
|
8196
|
+
for (const input2 of starter.inputs) {
|
|
7340
8197
|
console.log(
|
|
7341
|
-
` --${inputFlag(
|
|
8198
|
+
` --${inputFlag(input2)}${input2.required ? " (required)" : " (optional)"} \u2014 ${input2.description ?? input2.label}`
|
|
7342
8199
|
);
|
|
7343
8200
|
}
|
|
7344
|
-
for (const
|
|
8201
|
+
for (const workflow2 of starter.creates.workflows) {
|
|
7345
8202
|
console.log(
|
|
7346
|
-
` workflow: ${
|
|
8203
|
+
` workflow: ${workflow2.name}${workflow2.default ? " (default)" : ""} \u2014 ${workflow2.states.join(" \u2192 ")}`
|
|
7347
8204
|
);
|
|
7348
8205
|
}
|
|
7349
8206
|
for (const item of starter.creates.context) console.log(` ${item.kind}: ${item.name}`);
|
|
@@ -7391,7 +8248,7 @@ function register6(program3) {
|
|
|
7391
8248
|
)
|
|
7392
8249
|
).action(
|
|
7393
8250
|
async (name2, opts) => {
|
|
7394
|
-
const
|
|
8251
|
+
const prompt2 = typeof opts.prompt === "string" ? readBodyValue(opts.prompt) : void 0;
|
|
7395
8252
|
const suppliedInputs = [
|
|
7396
8253
|
["repo_url", "--repo", opts.repo],
|
|
7397
8254
|
["repo_branch", "--branch", opts.branch],
|
|
@@ -7413,7 +8270,7 @@ function register6(program3) {
|
|
|
7413
8270
|
`unknown starter "${opts.starter}"; choose ${response.items.map((item) => item.id).join(", ")} (see \`tines projects starters\`)`
|
|
7414
8271
|
);
|
|
7415
8272
|
}
|
|
7416
|
-
const declared = new Map(starter.inputs.map((
|
|
8273
|
+
const declared = new Map(starter.inputs.map((input2) => [input2.key, input2]));
|
|
7417
8274
|
for (const [key, flag, value] of suppliedInputs) {
|
|
7418
8275
|
if (value !== void 0 && !declared.has(key))
|
|
7419
8276
|
die(`${flag} is not used by starter "${starter.id}"`);
|
|
@@ -7422,14 +8279,14 @@ function register6(program3) {
|
|
|
7422
8279
|
for (const [key, , value] of suppliedInputs) {
|
|
7423
8280
|
if (value !== void 0 && declared.has(key)) inputs[key] = value.trim();
|
|
7424
8281
|
}
|
|
7425
|
-
for (const
|
|
7426
|
-
const value = inputs[
|
|
7427
|
-
if (
|
|
7428
|
-
die(`starter "${starter.id}" requires --${inputFlag(
|
|
7429
|
-
const max =
|
|
7430
|
-
if (value.length > max) die(`--${inputFlag(
|
|
8282
|
+
for (const input2 of starter.inputs) {
|
|
8283
|
+
const value = inputs[input2.key] ?? "";
|
|
8284
|
+
if (input2.required && !value)
|
|
8285
|
+
die(`starter "${starter.id}" requires --${inputFlag(input2)}`);
|
|
8286
|
+
const max = input2.max ?? 1e4;
|
|
8287
|
+
if (value.length > max) die(`--${inputFlag(input2)} is longer than ${max} characters`);
|
|
7431
8288
|
}
|
|
7432
|
-
if (starter.creates.workflows.some((
|
|
8289
|
+
if (starter.creates.workflows.some((workflow2) => workflow2.default) && opts.defaultWorkflow) {
|
|
7433
8290
|
die(`starter "${starter.id}" sets the default workflow; do not pass --default-workflow`);
|
|
7434
8291
|
}
|
|
7435
8292
|
starterRequest = { id: starter.id, inputs };
|
|
@@ -7444,7 +8301,7 @@ function register6(program3) {
|
|
|
7444
8301
|
name: name2,
|
|
7445
8302
|
description: opts.description,
|
|
7446
8303
|
default_workflow_id: workflowId,
|
|
7447
|
-
...typeof opts.prompt === "string" ? { initial_prompt:
|
|
8304
|
+
...typeof opts.prompt === "string" ? { initial_prompt: prompt2 } : opts.prompt === false ? { initial_prompt: "" } : {},
|
|
7448
8305
|
...starterRequest ? { starter: starterRequest } : {}
|
|
7449
8306
|
};
|
|
7450
8307
|
const project = await api.createProject(body);
|
|
@@ -7453,9 +8310,9 @@ function register6(program3) {
|
|
|
7453
8310
|
`created project "${project.name}" (${project.id})${typeof opts.prompt === "string" ? ' with its "conventions" prompt' : ""}`
|
|
7454
8311
|
);
|
|
7455
8312
|
if (project.starter) {
|
|
7456
|
-
for (const
|
|
8313
|
+
for (const workflow2 of project.starter.workflows) {
|
|
7457
8314
|
console.log(
|
|
7458
|
-
`workflow: ${
|
|
8315
|
+
`workflow: ${workflow2.name} (${workflow2.id})${workflow2.reused ? " \u2014 reused" : ""}`
|
|
7459
8316
|
);
|
|
7460
8317
|
}
|
|
7461
8318
|
for (const item of project.starter.context)
|
|
@@ -7540,10 +8397,10 @@ default workflow: ${defaultWorkflow}`);
|
|
|
7540
8397
|
console.log(`deleted project "${project.name}" (${project.id})`);
|
|
7541
8398
|
});
|
|
7542
8399
|
}
|
|
7543
|
-
function inputFlag(
|
|
7544
|
-
if (
|
|
7545
|
-
if (
|
|
7546
|
-
return
|
|
8400
|
+
function inputFlag(input2) {
|
|
8401
|
+
if (input2.key === "repo_url") return "repo";
|
|
8402
|
+
if (input2.key === "repo_branch") return "branch";
|
|
8403
|
+
return input2.key.replaceAll("_", "-");
|
|
7547
8404
|
}
|
|
7548
8405
|
function isBlank(starter) {
|
|
7549
8406
|
return starter.creates.workflows.length === 0 && starter.creates.context.length === 0 && starter.creates.first_issue === null;
|
|
@@ -7703,25 +8560,25 @@ function renderStreamEvent(event) {
|
|
|
7703
8560
|
}
|
|
7704
8561
|
return [];
|
|
7705
8562
|
case "assistant": {
|
|
7706
|
-
const
|
|
8563
|
+
const lines2 = [];
|
|
7707
8564
|
for (const block of event.message?.content ?? []) {
|
|
7708
8565
|
if (block.type === "text" && block.text?.trim()) {
|
|
7709
|
-
|
|
8566
|
+
lines2.push(`[agent] ${clip(block.text.trim(), 2e3)}`);
|
|
7710
8567
|
} else if (block.type === "tool_use") {
|
|
7711
8568
|
const args = block.input === void 0 ? "" : JSON.stringify(block.input);
|
|
7712
|
-
|
|
8569
|
+
lines2.push(`[tool] ${block.name ?? "tool"} ${clip(args, 300)}`.trimEnd());
|
|
7713
8570
|
}
|
|
7714
8571
|
}
|
|
7715
|
-
return
|
|
8572
|
+
return lines2;
|
|
7716
8573
|
}
|
|
7717
8574
|
case "user": {
|
|
7718
|
-
const
|
|
8575
|
+
const lines2 = [];
|
|
7719
8576
|
for (const block of event.message?.content ?? []) {
|
|
7720
8577
|
if (block.type === "tool_result" && block.is_error) {
|
|
7721
|
-
|
|
8578
|
+
lines2.push(`[tool] error: ${clip(resultText(block.content), 300)}`);
|
|
7722
8579
|
}
|
|
7723
8580
|
}
|
|
7724
|
-
return
|
|
8581
|
+
return lines2;
|
|
7725
8582
|
}
|
|
7726
8583
|
case "result": {
|
|
7727
8584
|
const parts = [];
|
|
@@ -7729,9 +8586,9 @@ function renderStreamEvent(event) {
|
|
|
7729
8586
|
if (typeof event.total_cost_usd === "number")
|
|
7730
8587
|
parts.push(`$${event.total_cost_usd.toFixed(2)}`);
|
|
7731
8588
|
const detail = parts.length > 0 ? ` (${parts.join(", ")})` : "";
|
|
7732
|
-
const
|
|
7733
|
-
if (event.is_error && event.result)
|
|
7734
|
-
return
|
|
8589
|
+
const lines2 = [`[session] result: ${event.subtype ?? "done"}${detail}`];
|
|
8590
|
+
if (event.is_error && event.result) lines2.push(`[error] ${clip(event.result, 2e3)}`);
|
|
8591
|
+
return lines2;
|
|
7735
8592
|
}
|
|
7736
8593
|
case "rate_limit_event": {
|
|
7737
8594
|
const info = event.rate_limit_info;
|
|
@@ -7975,16 +8832,16 @@ var CodexStreamRenderer = class {
|
|
|
7975
8832
|
).length : 0;
|
|
7976
8833
|
const complete = supplied === 4 && Object.keys(rawUsage).length === 4 && rawCached + rawWrite <= totalInput;
|
|
7977
8834
|
const previousInput = previous ? metric(previous.input_tokens) !== void 0 && metric(previous.cached_input_tokens) !== void 0 && metric(previous.cache_write_input_tokens) !== void 0 ? previous.input_tokens - previous.cached_input_tokens - previous.cache_write_input_tokens : void 0 : void 0;
|
|
7978
|
-
const
|
|
8835
|
+
const input2 = complete ? totalInput - rawCached - rawWrite : void 0;
|
|
7979
8836
|
let status = complete ? "complete" : supplied < 4 ? "missing" : "invalid";
|
|
7980
8837
|
if (complete && previous && ["input_tokens", "cached_input_tokens", "cache_write_input_tokens", "output_tokens"].some(
|
|
7981
8838
|
(key) => rawUsage[key] < previous[key]
|
|
7982
|
-
) ||
|
|
8839
|
+
) || input2 !== void 0 && previousInput !== void 0 && input2 < previousInput) {
|
|
7983
8840
|
this.stickyStatus = "nonmonotonic";
|
|
7984
8841
|
}
|
|
7985
8842
|
status = this.stickyStatus ?? status;
|
|
7986
8843
|
const usage = {};
|
|
7987
|
-
if (complete) usage.input_tokens =
|
|
8844
|
+
if (complete) usage.input_tokens = input2;
|
|
7988
8845
|
if (rawCached !== void 0) usage.cache_read_tokens = rawCached;
|
|
7989
8846
|
if (rawWrite !== void 0) usage.cache_write_tokens = rawWrite;
|
|
7990
8847
|
if (output !== void 0) usage.output_tokens = output;
|
|
@@ -8211,17 +9068,17 @@ function keepWorkspace(mode, outcome) {
|
|
|
8211
9068
|
function shellQuote(value) {
|
|
8212
9069
|
return `'${value.replaceAll("'", `'\\''`)}'`;
|
|
8213
9070
|
}
|
|
8214
|
-
function expandCommandTemplate(template,
|
|
8215
|
-
return template.replaceAll("{prompt_file}", shellQuote(
|
|
9071
|
+
function expandCommandTemplate(template, input2) {
|
|
9072
|
+
return template.replaceAll("{prompt_file}", shellQuote(input2.promptFile)).replaceAll("{workspace}", shellQuote(input2.workspace)).replaceAll("{model}", input2.model ? shellQuote(input2.model) : "''");
|
|
8216
9073
|
}
|
|
8217
|
-
function buildHarnessInvocation(spec,
|
|
9074
|
+
function buildHarnessInvocation(spec, input2) {
|
|
8218
9075
|
switch (spec.harness) {
|
|
8219
9076
|
case "claude_code":
|
|
8220
9077
|
return {
|
|
8221
9078
|
file: "sh",
|
|
8222
9079
|
args: [
|
|
8223
9080
|
"-c",
|
|
8224
|
-
`claude -p${
|
|
9081
|
+
`claude -p${input2.resumeSessionId ? ` --resume ${shellQuote(input2.resumeSessionId)}` : ""} --output-format stream-json --verbose${input2.model ? ` --model ${shellQuote(input2.model)}` : ""} < ${shellQuote(input2.promptFile)}`
|
|
8225
9082
|
]
|
|
8226
9083
|
};
|
|
8227
9084
|
case "codex":
|
|
@@ -8231,13 +9088,13 @@ function buildHarnessInvocation(spec, input) {
|
|
|
8231
9088
|
"exec",
|
|
8232
9089
|
"--json",
|
|
8233
9090
|
"--skip-git-repo-check",
|
|
8234
|
-
...
|
|
8235
|
-
|
|
9091
|
+
...input2.model ? ["--model", input2.model] : [],
|
|
9092
|
+
input2.prompt
|
|
8236
9093
|
]
|
|
8237
9094
|
};
|
|
8238
9095
|
case "custom": {
|
|
8239
9096
|
if (!spec.command) throw new Error("the custom harness needs a --command template");
|
|
8240
|
-
return { file: "sh", args: ["-c", expandCommandTemplate(spec.command,
|
|
9097
|
+
return { file: "sh", args: ["-c", expandCommandTemplate(spec.command, input2)] };
|
|
8241
9098
|
}
|
|
8242
9099
|
}
|
|
8243
9100
|
}
|
|
@@ -8253,16 +9110,16 @@ function formatLaunchCommand(invocation) {
|
|
|
8253
9110
|
return invocation.args[1];
|
|
8254
9111
|
return [invocation.file, ...invocation.args].map(launchWord).join(" ");
|
|
8255
9112
|
}
|
|
8256
|
-
function formatLaunchBanner(invocation,
|
|
9113
|
+
function formatLaunchBanner(invocation, input2, meta) {
|
|
8257
9114
|
const fields = [
|
|
8258
9115
|
`harness=${meta.harness}`,
|
|
8259
|
-
`model=${
|
|
9116
|
+
`model=${input2.model ?? "(fixed)"}`,
|
|
8260
9117
|
`timeout=${meta.timeoutMinutes}m`,
|
|
8261
9118
|
`cli=${meta.cliVersion}`,
|
|
8262
|
-
`workspace=${
|
|
9119
|
+
`workspace=${input2.workspace}`,
|
|
8263
9120
|
// The one line that says a send-back reused a session rather than
|
|
8264
9121
|
// re-exploring the repository from zero.
|
|
8265
|
-
...
|
|
9122
|
+
...input2.resumeSessionId ? [`resumed=${meta.resumedFromRunId ?? input2.resumeSessionId}`] : []
|
|
8266
9123
|
];
|
|
8267
9124
|
return `$ ${formatLaunchCommand(invocation)}
|
|
8268
9125
|
# tines runner: ${fields.join(" ")}
|
|
@@ -8445,9 +9302,9 @@ var LogBatcher = class {
|
|
|
8445
9302
|
let lost = 0;
|
|
8446
9303
|
while (this.pending.length > 1 && held > cap) {
|
|
8447
9304
|
const dropped = this.pending.shift();
|
|
8448
|
-
const
|
|
8449
|
-
held -=
|
|
8450
|
-
lost +=
|
|
9305
|
+
const bytes2 = Buffer.byteLength(dropped.chunk, "utf8");
|
|
9306
|
+
held -= bytes2;
|
|
9307
|
+
lost += bytes2;
|
|
8451
9308
|
}
|
|
8452
9309
|
const head = this.pending[0];
|
|
8453
9310
|
head.chunk = `[log] ${lost} bytes lost (supervisor unreachable)
|
|
@@ -8602,8 +9459,8 @@ async function installAgentCli(opts) {
|
|
|
8602
9459
|
return { binDir: binDirOf(prefix), version, source: "fresh" };
|
|
8603
9460
|
}
|
|
8604
9461
|
function tail(output) {
|
|
8605
|
-
const
|
|
8606
|
-
return
|
|
9462
|
+
const lines2 = output.trim().split("\n").filter(Boolean).slice(-3);
|
|
9463
|
+
return lines2.length > 0 ? `: ${lines2.join(" / ")}` : "";
|
|
8607
9464
|
}
|
|
8608
9465
|
function message(err) {
|
|
8609
9466
|
return err instanceof Error ? err.message : String(err);
|
|
@@ -10269,7 +11126,352 @@ function register10(program3) {
|
|
|
10269
11126
|
}
|
|
10270
11127
|
|
|
10271
11128
|
// src/commands/workflows.ts
|
|
11129
|
+
import { readFileSync as readFileSync11 } from "node:fs";
|
|
11130
|
+
|
|
11131
|
+
// src/workflow-packages.ts
|
|
11132
|
+
import { createInterface as createInterface4 } from "node:readline/promises";
|
|
11133
|
+
import { createHash } from "node:crypto";
|
|
10272
11134
|
import { readFileSync as readFileSync10 } from "node:fs";
|
|
11135
|
+
var PACKAGE_STDIN = "-";
|
|
11136
|
+
var MAX_LOCAL_JSON_BYTES = 32 * 1024 * 1024;
|
|
11137
|
+
function readPackageSource(path2) {
|
|
11138
|
+
try {
|
|
11139
|
+
const bytes2 = readFileSync10(path2 === PACKAGE_STDIN ? 0 : path2);
|
|
11140
|
+
if (bytes2.byteLength > MAX_LOCAL_JSON_BYTES)
|
|
11141
|
+
throw new Error(`input exceeds ${MAX_LOCAL_JSON_BYTES} bytes`);
|
|
11142
|
+
return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes2);
|
|
11143
|
+
} catch (error) {
|
|
11144
|
+
throw new Error(
|
|
11145
|
+
`cannot read ${path2 === PACKAGE_STDIN ? "stdin" : path2}: ${error instanceof Error ? error.message : String(error)}`
|
|
11146
|
+
);
|
|
11147
|
+
}
|
|
11148
|
+
}
|
|
11149
|
+
function readStrictObject(path2, label) {
|
|
11150
|
+
const value = parseStrictLibraryJson(readPackageSource(path2));
|
|
11151
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
11152
|
+
throw new Error(`${label} must contain a JSON object`);
|
|
11153
|
+
}
|
|
11154
|
+
return value;
|
|
11155
|
+
}
|
|
11156
|
+
function normalizeApiBase(value) {
|
|
11157
|
+
return new URL(value).toString().replace(/\/$/, "");
|
|
11158
|
+
}
|
|
11159
|
+
function saveWorkflowPackagePlan(path2, apiBase, plan) {
|
|
11160
|
+
const saved = {
|
|
11161
|
+
format: "tines.workflow-install-plan",
|
|
11162
|
+
version: 1,
|
|
11163
|
+
api_base: normalizeApiBase(apiBase),
|
|
11164
|
+
document_digest: plan.document_digest,
|
|
11165
|
+
plan
|
|
11166
|
+
};
|
|
11167
|
+
writeJsonFile(path2, saved, { secret: true });
|
|
11168
|
+
return saved;
|
|
11169
|
+
}
|
|
11170
|
+
function readWorkflowPackagePlan(path2) {
|
|
11171
|
+
const saved = readStrictObject(path2, "plan file");
|
|
11172
|
+
const outerKeys = ["format", "version", "api_base", "document_digest", "plan"];
|
|
11173
|
+
const planKeys = [
|
|
11174
|
+
"operations",
|
|
11175
|
+
"document",
|
|
11176
|
+
"resolved",
|
|
11177
|
+
"allocation",
|
|
11178
|
+
"plan_id",
|
|
11179
|
+
"plan_digest",
|
|
11180
|
+
"document_digest",
|
|
11181
|
+
"issued_at",
|
|
11182
|
+
"expires_at",
|
|
11183
|
+
"actor_key",
|
|
11184
|
+
"compiler_version",
|
|
11185
|
+
"plan_token",
|
|
11186
|
+
"budget"
|
|
11187
|
+
];
|
|
11188
|
+
if (Object.keys(saved).length !== outerKeys.length || !Object.keys(saved).every((key) => outerKeys.includes(key)) || saved.format !== "tines.workflow-install-plan" || saved.version !== 1 || typeof saved.api_base !== "string" || typeof saved.document_digest !== "string" || !saved.plan || typeof saved.plan !== "object" || typeof saved.plan.plan_token !== "string" || typeof saved.plan.plan_digest !== "string" || Object.keys(saved.plan).length !== planKeys.length || !Object.keys(saved.plan).every((key) => planKeys.includes(key))) {
|
|
11189
|
+
throw new Error("invalid workflow package plan file");
|
|
11190
|
+
}
|
|
11191
|
+
const result = saved;
|
|
11192
|
+
assertPlanBinding(result.plan);
|
|
11193
|
+
return result;
|
|
11194
|
+
}
|
|
11195
|
+
function tokenPayload(token) {
|
|
11196
|
+
const parts = token.split(".");
|
|
11197
|
+
if (token.length > 7e5 || parts.length !== 3 || parts[0] !== "wip1" || !parts.slice(1).every((part) => /^[A-Za-z0-9_-]+$/.test(part)))
|
|
11198
|
+
throw new Error("invalid workflow package plan token");
|
|
11199
|
+
try {
|
|
11200
|
+
const bytes2 = Buffer.from(parts[1], "base64url");
|
|
11201
|
+
if (bytes2.toString("base64url") !== parts[1]) throw new Error();
|
|
11202
|
+
const value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes2));
|
|
11203
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error();
|
|
11204
|
+
return value;
|
|
11205
|
+
} catch {
|
|
11206
|
+
throw new Error("invalid workflow package plan token");
|
|
11207
|
+
}
|
|
11208
|
+
}
|
|
11209
|
+
function expectedWorkflowPackageOperations(plan) {
|
|
11210
|
+
const operations = [];
|
|
11211
|
+
for (const workflow2 of plan.resolved.workflows) {
|
|
11212
|
+
const workflowId = plan.allocation.records[workflow2.id]?.id;
|
|
11213
|
+
const href = `/workflows/${workflowId}`;
|
|
11214
|
+
operations.push({
|
|
11215
|
+
action: "create",
|
|
11216
|
+
kind: "workflow",
|
|
11217
|
+
local_id: workflow2.id,
|
|
11218
|
+
id: workflowId,
|
|
11219
|
+
name: workflow2.name,
|
|
11220
|
+
href,
|
|
11221
|
+
relationship: workflow2.id === plan.document.main_workflow_id ? "main" : "dependency"
|
|
11222
|
+
});
|
|
11223
|
+
for (const state of workflow2.states)
|
|
11224
|
+
operations.push({
|
|
11225
|
+
action: "create",
|
|
11226
|
+
kind: "state",
|
|
11227
|
+
local_id: state.id,
|
|
11228
|
+
id: plan.allocation.records[state.id]?.id,
|
|
11229
|
+
name: state.name,
|
|
11230
|
+
href: `${href}#state-${plan.allocation.records[state.id]?.id}`
|
|
11231
|
+
});
|
|
11232
|
+
for (const transition of workflow2.transitions)
|
|
11233
|
+
operations.push({
|
|
11234
|
+
action: "create",
|
|
11235
|
+
kind: "transition",
|
|
11236
|
+
local_id: transition.id,
|
|
11237
|
+
id: plan.allocation.records[transition.id]?.id,
|
|
11238
|
+
name: transition.name,
|
|
11239
|
+
href
|
|
11240
|
+
});
|
|
11241
|
+
}
|
|
11242
|
+
for (const item of plan.resolved.context) {
|
|
11243
|
+
const workflow2 = plan.resolved.workflows.find(
|
|
11244
|
+
(candidate) => candidate.states.some((state) => state.id === item.state_id)
|
|
11245
|
+
);
|
|
11246
|
+
const href = `/context?workflow=${plan.allocation.records[workflow2?.id ?? ""]?.id}&q=${encodeURIComponent(item.name)}`;
|
|
11247
|
+
operations.push({
|
|
11248
|
+
action: "create",
|
|
11249
|
+
kind: item.kind,
|
|
11250
|
+
local_id: item.id,
|
|
11251
|
+
id: plan.allocation.records[item.id]?.id,
|
|
11252
|
+
name: item.name,
|
|
11253
|
+
href
|
|
11254
|
+
});
|
|
11255
|
+
if (item.kind === "skill")
|
|
11256
|
+
for (const file of item.files)
|
|
11257
|
+
operations.push({
|
|
11258
|
+
action: "create",
|
|
11259
|
+
kind: "file",
|
|
11260
|
+
local_id: file.id,
|
|
11261
|
+
id: plan.allocation.records[file.id]?.id,
|
|
11262
|
+
name: file.path,
|
|
11263
|
+
href
|
|
11264
|
+
});
|
|
11265
|
+
}
|
|
11266
|
+
for (const input2 of plan.resolved.inputs) {
|
|
11267
|
+
if (input2.mode === "create")
|
|
11268
|
+
operations.push({
|
|
11269
|
+
action: "create",
|
|
11270
|
+
kind: "label",
|
|
11271
|
+
local_id: input2.input_id,
|
|
11272
|
+
id: input2.id,
|
|
11273
|
+
name: input2.value,
|
|
11274
|
+
href: "/labels"
|
|
11275
|
+
});
|
|
11276
|
+
if (input2.mode === "reuse")
|
|
11277
|
+
operations.push({
|
|
11278
|
+
action: "reuse",
|
|
11279
|
+
kind: input2.type,
|
|
11280
|
+
local_id: input2.input_id,
|
|
11281
|
+
id: input2.id,
|
|
11282
|
+
name: input2.value,
|
|
11283
|
+
href: input2.type === "workflow" ? `/workflows/${input2.id}` : input2.type === "project" ? `/projects/${input2.id}` : "/labels"
|
|
11284
|
+
});
|
|
11285
|
+
}
|
|
11286
|
+
for (const skip of plan.resolved.skipped)
|
|
11287
|
+
operations.push({
|
|
11288
|
+
action: "skip",
|
|
11289
|
+
kind: skip.kind,
|
|
11290
|
+
local_id: skip.local_id,
|
|
11291
|
+
id: null,
|
|
11292
|
+
name: skip.kind === "schedule" ? plan.document.schedules.find((item) => item.id === skip.local_id)?.name : plan.document.routing.find((item) => item.id === skip.local_id)?.tier,
|
|
11293
|
+
href: null
|
|
11294
|
+
});
|
|
11295
|
+
return operations;
|
|
11296
|
+
}
|
|
11297
|
+
function assertPlanBinding(plan) {
|
|
11298
|
+
const payload = tokenPayload(plan.plan_token);
|
|
11299
|
+
const { plan_digest: signedDigest, ...unsigned } = payload;
|
|
11300
|
+
const digest = `sha256:${createHash("sha256").update(canonicalizeLibraryValue({ plan: unsigned, resolved: plan.resolved })).digest("hex")}`;
|
|
11301
|
+
const mirrors = {
|
|
11302
|
+
id: plan.plan_id,
|
|
11303
|
+
plan_digest: plan.plan_digest,
|
|
11304
|
+
document_digest: plan.document_digest,
|
|
11305
|
+
issued_at: plan.issued_at,
|
|
11306
|
+
expires_at: plan.expires_at,
|
|
11307
|
+
actor_key: plan.actor_key,
|
|
11308
|
+
compiler_version: plan.compiler_version,
|
|
11309
|
+
allocation: plan.allocation,
|
|
11310
|
+
budget: plan.budget
|
|
11311
|
+
};
|
|
11312
|
+
for (const [field, value] of Object.entries(mirrors)) {
|
|
11313
|
+
const signedField = field === "id" ? "id" : field;
|
|
11314
|
+
if (canonicalizeLibraryValue(payload[signedField]) !== canonicalizeLibraryValue(value))
|
|
11315
|
+
throw new Error(`saved workflow package plan has modified ${field}`);
|
|
11316
|
+
}
|
|
11317
|
+
if (signedDigest !== digest || plan.plan_digest !== digest)
|
|
11318
|
+
throw new Error("saved workflow package plan digest does not bind its review");
|
|
11319
|
+
if (canonicalizeLibraryValue(plan.operations) !== canonicalizeLibraryValue(expectedWorkflowPackageOperations(plan)))
|
|
11320
|
+
throw new Error("saved workflow package plan has modified operations");
|
|
11321
|
+
}
|
|
11322
|
+
var lines = (heading, values) => values.length ? [heading, ...values.map((value) => ` ${value}`)] : [heading, " none"];
|
|
11323
|
+
function formatWorkflowPackageReview(plan) {
|
|
11324
|
+
const { document, resolved } = plan;
|
|
11325
|
+
const output = [
|
|
11326
|
+
`Workflow package plan ${plan.plan_digest}`,
|
|
11327
|
+
` file: ${plan.document_digest}`,
|
|
11328
|
+
` valid until: ${new Date(plan.expires_at).toISOString()}`,
|
|
11329
|
+
` budget: ${plan.budget.statements} statements, ${plan.budget.max_parameters} parameters, ${plan.budget.max_sql_bytes} SQL bytes, ${plan.budget.max_value_bytes} value bytes`,
|
|
11330
|
+
"",
|
|
11331
|
+
...lines(
|
|
11332
|
+
"Operations:",
|
|
11333
|
+
plan.operations.map(
|
|
11334
|
+
(operation) => `${operation.action} ${operation.kind} ${operation.local_id}: ${operation.name}${operation.relationship ? ` (${operation.relationship})` : ""}${operation.id ? ` [${operation.id}]` : ""}`
|
|
11335
|
+
)
|
|
11336
|
+
),
|
|
11337
|
+
"",
|
|
11338
|
+
...lines(
|
|
11339
|
+
"Workflows and gates:",
|
|
11340
|
+
document.workflows.flatMap((workflow2) => [
|
|
11341
|
+
`${workflow2.id}: ${resolved.names[workflow2.id] ?? workflow2.name} \u2014 ${workflow2.description}`,
|
|
11342
|
+
...workflow2.states.map(
|
|
11343
|
+
(state) => ` state ${state.id}: ${state.name} (${state.category})${state.inherits_from ? ` inherits ${state.inherits_from.state_id}` : ""}`
|
|
11344
|
+
),
|
|
11345
|
+
...workflow2.transitions.map(
|
|
11346
|
+
(transition) => ` transition ${transition.id}: ${transition.name} ${transition.from_state_id} -> ${transition.to_state_id}; gates ${transition.requires.length ? transition.requires.map((gate) => `${gate.artifact}${gate.type ? `:${gate.type}` : ""}${gate.content_type ? `:${gate.content_type}` : ""}`).join(", ") : "none"}`
|
|
11347
|
+
)
|
|
11348
|
+
])
|
|
11349
|
+
),
|
|
11350
|
+
"",
|
|
11351
|
+
...lines(
|
|
11352
|
+
"Inputs:",
|
|
11353
|
+
document.inputs.map((declaration) => {
|
|
11354
|
+
const input2 = resolved.inputs.find((candidate) => candidate.input_id === declaration.id);
|
|
11355
|
+
return `${declaration.id} ${declaration.key}: ${declaration.label} \u2014 ${declaration.description}
|
|
11356
|
+
type: ${declaration.type}; required: ${declaration.required ? "yes" : "no"}; default: ${declaration.default ?? "none"}; required states: ${declaration.required_states?.join(", ") || "none"}
|
|
11357
|
+
resolution: ${input2?.mode ?? "missing"}; value: ${input2?.value ?? "missing"}${input2?.id ? ` [${input2.id}]` : ""}`;
|
|
11358
|
+
})
|
|
11359
|
+
),
|
|
11360
|
+
"",
|
|
11361
|
+
...lines(
|
|
11362
|
+
"Original and rendered text:",
|
|
11363
|
+
resolved.patches.map(
|
|
11364
|
+
(patch) => `${patch.record_id}.${patch.field}
|
|
11365
|
+
original: ${patch.original}
|
|
11366
|
+
rendered: ${patch.rendered}
|
|
11367
|
+
uses: ${patch.uses.length ? patch.uses.map((use) => `${use.id} -> ${use.input_id} (${use.count} occurrence${use.count === 1 ? "" : "s"})`).join(", ") : "none"}`
|
|
11368
|
+
)
|
|
11369
|
+
),
|
|
11370
|
+
"",
|
|
11371
|
+
...lines(
|
|
11372
|
+
"Context:",
|
|
11373
|
+
document.context.flatMap((item) => {
|
|
11374
|
+
const header = `${item.kind} ${item.id} in ${item.state_id}: ${item.name} \u2014 ${item.description}`;
|
|
11375
|
+
if (item.kind === "prompt") return [header, ` body: ${item.body}`];
|
|
11376
|
+
if (item.kind === "repo")
|
|
11377
|
+
return [
|
|
11378
|
+
header,
|
|
11379
|
+
` repository: ${item.repo_url}; branch ${item.repo_branch ?? "default"}; directory ${item.repo_dir ?? "default"}`
|
|
11380
|
+
];
|
|
11381
|
+
return [
|
|
11382
|
+
header,
|
|
11383
|
+
...item.files.map((file) => ` file ${file.id} ${file.path}:
|
|
11384
|
+
${file.content}`)
|
|
11385
|
+
];
|
|
11386
|
+
})
|
|
11387
|
+
),
|
|
11388
|
+
"",
|
|
11389
|
+
...lines(
|
|
11390
|
+
"Schedules:",
|
|
11391
|
+
resolved.schedules.map(
|
|
11392
|
+
(schedule2) => `${schedule2.local_id}: ${resolved.names[schedule2.local_id] ?? schedule2.definition.name}; project ${schedule2.project_id}; ${JSON.stringify(schedule2.definition)}`
|
|
11393
|
+
)
|
|
11394
|
+
),
|
|
11395
|
+
"",
|
|
11396
|
+
...lines(
|
|
11397
|
+
"Routing capabilities:",
|
|
11398
|
+
resolved.routing.flatMap((routing) => [
|
|
11399
|
+
`${routing.local_id}: ${routing.tier}; winning rule ${routing.winning_rule_id}`,
|
|
11400
|
+
...routing.targets.map(
|
|
11401
|
+
(target) => ` ${target.name} (${target.status}) ${target.model ?? "default model"} / ${target.config}; supported ${target.supported ? "yes" : "no"}`
|
|
11402
|
+
),
|
|
11403
|
+
...routing.warnings.map((warning) => ` warning: ${warning}`)
|
|
11404
|
+
])
|
|
11405
|
+
),
|
|
11406
|
+
"",
|
|
11407
|
+
...lines(
|
|
11408
|
+
"Skipped:",
|
|
11409
|
+
resolved.skipped.map((item) => `${item.kind} ${item.local_id}`)
|
|
11410
|
+
)
|
|
11411
|
+
];
|
|
11412
|
+
return output.join("\n");
|
|
11413
|
+
}
|
|
11414
|
+
function formatValidation(result) {
|
|
11415
|
+
const output = [result.valid ? "valid workflow package" : "invalid workflow package"];
|
|
11416
|
+
if (result.digest) output.push(`digest: ${result.digest}`);
|
|
11417
|
+
for (const diagnostic of result.diagnostics) {
|
|
11418
|
+
output.push(`${diagnostic.path || "/"}: ${diagnostic.code}: ${diagnostic.message}`);
|
|
11419
|
+
}
|
|
11420
|
+
output.push(
|
|
11421
|
+
`limits: ${result.limits.max_document_bytes} bytes, ${result.limits.max_records} records, depth ${result.limits.max_depth}`
|
|
11422
|
+
);
|
|
11423
|
+
return output.join("\n");
|
|
11424
|
+
}
|
|
11425
|
+
async function localDocument(path2) {
|
|
11426
|
+
const raw = readPackageSource(path2);
|
|
11427
|
+
const document = await parseLibraryV3Document(raw);
|
|
11428
|
+
if (document.profile !== "workflow") throw new Error("expected a workflow-profile package");
|
|
11429
|
+
return { raw, document };
|
|
11430
|
+
}
|
|
11431
|
+
function canonicalWorkflowPackage(document) {
|
|
11432
|
+
return canonicalizeLibraryValue(document);
|
|
11433
|
+
}
|
|
11434
|
+
async function askToInstall(plan) {
|
|
11435
|
+
const rl = createInterface4({ input: process.stdin, output: process.stderr });
|
|
11436
|
+
try {
|
|
11437
|
+
process.stderr.write(`${formatWorkflowPackageReview(plan)}
|
|
11438
|
+
`);
|
|
11439
|
+
const answer = await rl.question(`Install exactly plan ${plan.plan_digest}? [y/N] `).catch(() => "");
|
|
11440
|
+
return /^y(es)?$/i.test(answer.trim());
|
|
11441
|
+
} finally {
|
|
11442
|
+
rl.close();
|
|
11443
|
+
}
|
|
11444
|
+
}
|
|
11445
|
+
async function recoverOrInstall(api, raw, plan) {
|
|
11446
|
+
const checked = (receipt) => {
|
|
11447
|
+
if (receipt.id !== plan.plan_id || receipt.document_digest !== plan.document_digest || receipt.plan_digest !== plan.plan_digest)
|
|
11448
|
+
throw new Error("recovered workflow package receipt does not match the saved plan");
|
|
11449
|
+
return receipt;
|
|
11450
|
+
};
|
|
11451
|
+
try {
|
|
11452
|
+
return checked(await api.getWorkflowPackageReceipt(plan.plan_id));
|
|
11453
|
+
} catch (error) {
|
|
11454
|
+
if (!(error instanceof ApiError) || error.status !== 404) throw error;
|
|
11455
|
+
}
|
|
11456
|
+
const request = {
|
|
11457
|
+
document_json: raw,
|
|
11458
|
+
plan_token: plan.plan_token,
|
|
11459
|
+
confirmation: { plan_digest: plan.plan_digest }
|
|
11460
|
+
};
|
|
11461
|
+
try {
|
|
11462
|
+
return checked(await api.installWorkflowPackage(request));
|
|
11463
|
+
} catch (first) {
|
|
11464
|
+
if (!(first instanceof ApiNetworkError)) throw first;
|
|
11465
|
+
try {
|
|
11466
|
+
return checked(await api.getWorkflowPackageReceipt(plan.plan_id));
|
|
11467
|
+
} catch (recovery) {
|
|
11468
|
+
if (!(recovery instanceof ApiError) || recovery.status !== 404) throw first;
|
|
11469
|
+
}
|
|
11470
|
+
return checked(await api.installWorkflowPackage(request));
|
|
11471
|
+
}
|
|
11472
|
+
}
|
|
11473
|
+
|
|
11474
|
+
// src/commands/workflows.ts
|
|
10273
11475
|
function readJsonBody(inline, file) {
|
|
10274
11476
|
if (inline !== void 0 && file !== void 0) {
|
|
10275
11477
|
die("pass the JSON inline or with --file, not both");
|
|
@@ -10278,14 +11480,14 @@ function readJsonBody(inline, file) {
|
|
|
10278
11480
|
if (file !== void 0 && file !== "-") {
|
|
10279
11481
|
let raw;
|
|
10280
11482
|
try {
|
|
10281
|
-
raw =
|
|
11483
|
+
raw = readFileSync11(file, "utf8");
|
|
10282
11484
|
} catch (err) {
|
|
10283
11485
|
die(`cannot read ${file}: ${err instanceof Error ? err.message : String(err)}`);
|
|
10284
11486
|
}
|
|
10285
11487
|
return parseJsonObject(raw, file);
|
|
10286
11488
|
}
|
|
10287
11489
|
if (file === "-" || !process.stdin.isTTY) {
|
|
10288
|
-
const raw =
|
|
11490
|
+
const raw = readFileSync11(0, "utf8");
|
|
10289
11491
|
if (raw.trim() === "") {
|
|
10290
11492
|
if (file === "-") die("no JSON on stdin");
|
|
10291
11493
|
return void 0;
|
|
@@ -10351,8 +11553,8 @@ name issues must carry (see: tines issues artifacts --help).
|
|
|
10351
11553
|
async function loadLibrary(api) {
|
|
10352
11554
|
const workflows = await listAll((page) => api.listWorkflows(page));
|
|
10353
11555
|
const states = /* @__PURE__ */ new Map();
|
|
10354
|
-
for (const
|
|
10355
|
-
for (const state of
|
|
11556
|
+
for (const workflow2 of workflows) {
|
|
11557
|
+
for (const state of workflow2.states) states.set(state.id, { workflow: workflow2, state });
|
|
10356
11558
|
}
|
|
10357
11559
|
const children = /* @__PURE__ */ new Map();
|
|
10358
11560
|
for (const entry of states.values()) {
|
|
@@ -10370,13 +11572,13 @@ function stateLabel(lib, id2) {
|
|
|
10370
11572
|
return entry ? qualify(entry) : id2;
|
|
10371
11573
|
}
|
|
10372
11574
|
function inheritanceLines(lib, state) {
|
|
10373
|
-
const
|
|
11575
|
+
const lines2 = [];
|
|
10374
11576
|
if (state.inherits_from !== null) {
|
|
10375
|
-
|
|
11577
|
+
lines2.push(` inherits from: ${stateLabel(lib, state.inherits_from)}`);
|
|
10376
11578
|
}
|
|
10377
11579
|
const kids = lib.children.get(state.id) ?? [];
|
|
10378
|
-
if (kids.length > 0)
|
|
10379
|
-
return
|
|
11580
|
+
if (kids.length > 0) lines2.push(` inherited by: ${kids.map(qualify).join(", ")}`);
|
|
11581
|
+
return lines2;
|
|
10380
11582
|
}
|
|
10381
11583
|
async function resolveStateBases(states, library) {
|
|
10382
11584
|
if (!Array.isArray(states)) return;
|
|
@@ -10396,24 +11598,24 @@ async function resolveStateBases(states, library) {
|
|
|
10396
11598
|
function resolveBasePair(lib, pair, stateName) {
|
|
10397
11599
|
const sep2 = pair.indexOf("/");
|
|
10398
11600
|
const workflowRef = pair.slice(0, sep2).trim();
|
|
10399
|
-
const
|
|
11601
|
+
const stateRef2 = pair.slice(sep2 + 1).trim();
|
|
10400
11602
|
const where = `state "${stateName}" inherits from "${pair}"`;
|
|
10401
|
-
if (!workflowRef || !
|
|
11603
|
+
if (!workflowRef || !stateRef2) {
|
|
10402
11604
|
die(`${where}, which is not a <workflow>/<state> pair`);
|
|
10403
11605
|
}
|
|
10404
11606
|
const byName = lib.workflows.filter((w) => w.name === workflowRef);
|
|
10405
11607
|
if (byName.length > 1)
|
|
10406
11608
|
die(`${where}, but workflow name "${workflowRef}" is ambiguous; use an id`);
|
|
10407
|
-
const
|
|
10408
|
-
if (!
|
|
11609
|
+
const workflow2 = lib.workflows.find((w) => w.id === workflowRef) ?? byName[0];
|
|
11610
|
+
if (!workflow2) {
|
|
10409
11611
|
die(
|
|
10410
11612
|
`${where}, but there is no workflow "${workflowRef}" (have: ${lib.workflows.map((w) => w.name).join(", ")})`
|
|
10411
11613
|
);
|
|
10412
11614
|
}
|
|
10413
|
-
const state =
|
|
11615
|
+
const state = workflow2.states.find((s) => s.name === stateRef2) ?? workflow2.states.find((s) => s.id === stateRef2);
|
|
10414
11616
|
if (!state) {
|
|
10415
11617
|
die(
|
|
10416
|
-
`${where}, but workflow "${
|
|
11618
|
+
`${where}, but workflow "${workflow2.name}" has no state "${stateRef2}" (have: ${workflow2.states.map((s) => s.name).join(", ")})`
|
|
10417
11619
|
);
|
|
10418
11620
|
}
|
|
10419
11621
|
return state.id;
|
|
@@ -10451,6 +11653,7 @@ warning: ${w}`);
|
|
|
10451
11653
|
}
|
|
10452
11654
|
function register11(program3) {
|
|
10453
11655
|
const workflows = program3.command("workflows").description("Manage the workflow library");
|
|
11656
|
+
registerPackageCommands(workflows);
|
|
10454
11657
|
withList(workflows.command("list").description("List the workflow library")).action(
|
|
10455
11658
|
async (opts) => {
|
|
10456
11659
|
const api = client(opts);
|
|
@@ -10561,6 +11764,147 @@ see \`tines workflows create --help\` for the expected shape`
|
|
|
10561
11764
|
console.log(`deleted workflow "${wf.name}" (${wf.id})`);
|
|
10562
11765
|
});
|
|
10563
11766
|
}
|
|
11767
|
+
var collect2 = (value, previous) => [...previous, value];
|
|
11768
|
+
async function resolveExportState(api, ref) {
|
|
11769
|
+
const all = await listAll((page) => api.listWorkflows(page));
|
|
11770
|
+
const exact = all.flatMap((workflow2) => workflow2.states).find((state) => state.id === ref);
|
|
11771
|
+
if (exact) return exact.id;
|
|
11772
|
+
if (ref.includes("/")) {
|
|
11773
|
+
const separator = ref.indexOf("/");
|
|
11774
|
+
const workflow2 = pickWorkflow(all, ref.slice(0, separator).trim());
|
|
11775
|
+
const states = workflow2.states.filter(
|
|
11776
|
+
(state) => state.name === ref.slice(separator + 1).trim()
|
|
11777
|
+
);
|
|
11778
|
+
if (states.length === 1) return states[0].id;
|
|
11779
|
+
}
|
|
11780
|
+
const named = all.flatMap((workflow2) => workflow2.states).filter((state) => state.name === ref);
|
|
11781
|
+
if (named.length === 1) return named[0].id;
|
|
11782
|
+
if (named.length > 1) die(`state name "${ref}" is ambiguous; use an id`);
|
|
11783
|
+
die(`no state named "${ref}"; use a state id or <workflow>/<state>`);
|
|
11784
|
+
}
|
|
11785
|
+
function registerPackageCommands(workflows) {
|
|
11786
|
+
withCommon(
|
|
11787
|
+
workflows.command("export <workflow-id-or-unambiguous-name>").description("Export a canonical workflow package JSON document").option("--project <id-or-name>", "source project for selected project-bound configuration").option("--schedule <id>", "include this source schedule ID (repeatable)", collect2, []).option(
|
|
11788
|
+
"--tier <state-ref=tier>",
|
|
11789
|
+
"include a state tier preference (repeatable)",
|
|
11790
|
+
collect2,
|
|
11791
|
+
[]
|
|
11792
|
+
).option("--project-routing", "make every --tier selector project scoped").option("--inputs <file>", "JSON object containing input declarations and text uses")
|
|
11793
|
+
).action(
|
|
11794
|
+
async (ref, opts) => {
|
|
11795
|
+
const api = client(opts);
|
|
11796
|
+
const all = await listAll((page) => api.listWorkflows(page));
|
|
11797
|
+
const workflow2 = pickWorkflow(all, ref);
|
|
11798
|
+
const sourceProject = opts.project ? await resolveProject(api, opts.project) : void 0;
|
|
11799
|
+
if ((opts.schedule.length || opts.projectRouting) && !sourceProject)
|
|
11800
|
+
die("--project is required with --schedule or --project-routing");
|
|
11801
|
+
const tiers = [];
|
|
11802
|
+
for (const selector of opts.tier) {
|
|
11803
|
+
const separator = selector.lastIndexOf("=");
|
|
11804
|
+
if (separator < 1) die(`invalid --tier "${selector}"; expected <state-ref>=<tier>`);
|
|
11805
|
+
const tier = selector.slice(separator + 1);
|
|
11806
|
+
if (!["smartest", "balanced", "cheapest"].includes(tier))
|
|
11807
|
+
die(`invalid tier "${tier}"; expected smartest, balanced, or cheapest`);
|
|
11808
|
+
tiers.push({
|
|
11809
|
+
state_id: await resolveExportState(api, selector.slice(0, separator)),
|
|
11810
|
+
tier,
|
|
11811
|
+
project_scoped: Boolean(opts.projectRouting)
|
|
11812
|
+
});
|
|
11813
|
+
}
|
|
11814
|
+
const authoring = opts.inputs ? readStrictObject(opts.inputs, "inputs file") : void 0;
|
|
11815
|
+
const document = await api.exportWorkflowPackage(workflow2.id, {
|
|
11816
|
+
source_project_id: sourceProject?.id,
|
|
11817
|
+
schedule_ids: opts.schedule,
|
|
11818
|
+
tiers,
|
|
11819
|
+
authoring
|
|
11820
|
+
});
|
|
11821
|
+
process.stdout.write(`${canonicalWorkflowPackage(document)}
|
|
11822
|
+
`);
|
|
11823
|
+
}
|
|
11824
|
+
);
|
|
11825
|
+
withCommon(
|
|
11826
|
+
workflows.command("validate <file>").description("Validate a workflow package file (use - for stdin)")
|
|
11827
|
+
).action(async (path2, opts) => {
|
|
11828
|
+
const result = await client(opts).validateLibrary({ document_json: readPackageSource(path2) });
|
|
11829
|
+
if (opts.json) printJson(result);
|
|
11830
|
+
else console.log(formatValidation(result));
|
|
11831
|
+
if (!result.valid) process.exitCode = 1;
|
|
11832
|
+
});
|
|
11833
|
+
withCommon(
|
|
11834
|
+
workflows.command("preview <file>").description("Prepare and fully review a destination workflow package plan").option("--choices <file>", "destination choices JSON file").option("--plan-out <file>", "atomically save the signed plan for a later install")
|
|
11835
|
+
).action(async (path2, opts) => {
|
|
11836
|
+
const raw = readPackageSource(path2);
|
|
11837
|
+
const choices = opts.choices ? readStrictObject(opts.choices, "choices file") : void 0;
|
|
11838
|
+
const plan = await client(opts).prepareWorkflowPackage({ document_json: raw, choices });
|
|
11839
|
+
if (opts.planOut) saveWorkflowPackagePlan(opts.planOut, resolveUrl(opts), plan);
|
|
11840
|
+
if (opts.json) printJson(plan);
|
|
11841
|
+
else console.log(formatWorkflowPackageReview(plan));
|
|
11842
|
+
});
|
|
11843
|
+
withCommon(
|
|
11844
|
+
workflows.command("install <file>").description("Install one exactly reviewed workflow package plan").option("--choices <file>", "destination choices JSON file (interactive preparation only)").option("--plan <file>", "signed plan saved by workflows preview").option("--confirm <plan-digest>", "exact reviewed plan digest (required outside a TTY)")
|
|
11845
|
+
).action(
|
|
11846
|
+
async (path2, opts) => {
|
|
11847
|
+
if (path2 === "-" && process.stdin.isTTY)
|
|
11848
|
+
die('"-" reads the package from stdin, but stdin is a terminal');
|
|
11849
|
+
if (!opts.plan && !process.stdin.isTTY)
|
|
11850
|
+
die(
|
|
11851
|
+
"non-interactive install requires a separate prior preview with --plan-out, then --plan and --confirm"
|
|
11852
|
+
);
|
|
11853
|
+
if (opts.plan && opts.choices)
|
|
11854
|
+
die("--choices cannot be used with --plan; the saved choices are authoritative");
|
|
11855
|
+
if (path2 === "-" && !opts.plan)
|
|
11856
|
+
die(
|
|
11857
|
+
"stdin package input cannot also provide interactive confirmation; use preview --plan-out first"
|
|
11858
|
+
);
|
|
11859
|
+
const { raw, document } = await localDocument(path2);
|
|
11860
|
+
const apiBase = normalizeUrl(resolveUrl(opts));
|
|
11861
|
+
const api = client(opts);
|
|
11862
|
+
let plan;
|
|
11863
|
+
let planPath = opts.plan;
|
|
11864
|
+
if (planPath) {
|
|
11865
|
+
const saved = readWorkflowPackagePlan(planPath);
|
|
11866
|
+
if (normalizeUrl(saved.api_base) !== apiBase)
|
|
11867
|
+
die(`plan belongs to ${saved.api_base}, not ${apiBase}`);
|
|
11868
|
+
if (saved.document_digest !== document.digest || saved.plan.document_digest !== document.digest || canonicalWorkflowPackage(saved.plan.document) !== canonicalWorkflowPackage(document))
|
|
11869
|
+
die(
|
|
11870
|
+
`package digest ${document.digest} does not match saved plan ${saved.document_digest}`
|
|
11871
|
+
);
|
|
11872
|
+
plan = saved.plan;
|
|
11873
|
+
} else {
|
|
11874
|
+
const choices = opts.choices ? readStrictObject(opts.choices, "choices file") : void 0;
|
|
11875
|
+
plan = await api.prepareWorkflowPackage({ document_json: raw, choices });
|
|
11876
|
+
planPath = `${path2}.plan.json`;
|
|
11877
|
+
saveWorkflowPackagePlan(planPath, apiBase, plan);
|
|
11878
|
+
console.error(`saved retryable signed plan to ${planPath}`);
|
|
11879
|
+
}
|
|
11880
|
+
if (opts.confirm !== void 0 && opts.confirm !== plan.plan_digest)
|
|
11881
|
+
die(`confirmation digest does not match reviewed plan ${plan.plan_digest}`);
|
|
11882
|
+
if (!process.stdin.isTTY && opts.confirm !== plan.plan_digest)
|
|
11883
|
+
die(`non-interactive install requires --confirm ${plan.plan_digest}`);
|
|
11884
|
+
if (process.stdin.isTTY && opts.confirm === void 0) {
|
|
11885
|
+
if (!await askToInstall(plan)) die("installation declined");
|
|
11886
|
+
} else {
|
|
11887
|
+
process.stderr.write(`${formatWorkflowPackageReview(plan)}
|
|
11888
|
+
`);
|
|
11889
|
+
}
|
|
11890
|
+
const receipt = await recoverOrInstall(api, raw, plan);
|
|
11891
|
+
if (opts.json) printJson(receipt);
|
|
11892
|
+
else {
|
|
11893
|
+
console.log(`installed workflow package (${receipt.id})`);
|
|
11894
|
+
console.log(`plan: ${receipt.plan_digest}`);
|
|
11895
|
+
for (const object3 of receipt.objects)
|
|
11896
|
+
console.log(` ${object3.kind}: ${object3.name} \u2014 ${object3.href}`);
|
|
11897
|
+
}
|
|
11898
|
+
}
|
|
11899
|
+
);
|
|
11900
|
+
}
|
|
11901
|
+
function normalizeUrl(value) {
|
|
11902
|
+
try {
|
|
11903
|
+
return new URL(value).toString().replace(/\/$/, "");
|
|
11904
|
+
} catch {
|
|
11905
|
+
die(`invalid API base URL: ${value}`);
|
|
11906
|
+
}
|
|
11907
|
+
}
|
|
10564
11908
|
|
|
10565
11909
|
// src/commands/usage.ts
|
|
10566
11910
|
var money = (value) => usageCostLabel(value);
|
|
@@ -10647,14 +11991,14 @@ function register12(program3) {
|
|
|
10647
11991
|
if (opts.state && !opts.workflow) throw new Error("--state requires --workflow");
|
|
10648
11992
|
const api = client(opts);
|
|
10649
11993
|
const project = opts.project && opts.project !== "unknown" ? (await resolveProject(api, opts.project)).id : opts.project;
|
|
10650
|
-
const
|
|
11994
|
+
const workflow2 = opts.workflow && opts.workflow !== "unknown" ? (await resolveWorkflow(api, opts.workflow)).id : opts.workflow;
|
|
10651
11995
|
const runner = opts.runner && opts.runner !== "unknown" ? (await resolveRunner(api, opts.runner)).id : opts.runner;
|
|
10652
11996
|
const report = await api.getUsage({
|
|
10653
11997
|
window: opts.window,
|
|
10654
11998
|
from: opts.from,
|
|
10655
11999
|
to: opts.to,
|
|
10656
12000
|
project,
|
|
10657
|
-
workflow,
|
|
12001
|
+
workflow: workflow2,
|
|
10658
12002
|
state: opts.state,
|
|
10659
12003
|
runner,
|
|
10660
12004
|
tier: opts.tier,
|