@asteroidai/cli 1.9.98 → 1.9.99
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/dist/asteroid.js +236 -81
- package/dist/core.js +248 -81
- package/package.json +1 -1
package/dist/asteroid.js
CHANGED
|
@@ -1907,7 +1907,7 @@ var COMMANDS = [
|
|
|
1907
1907
|
command: "portal-skills add",
|
|
1908
1908
|
tool: "portal_skills_add",
|
|
1909
1909
|
title: "Add portal skills",
|
|
1910
|
-
description: "Add a portal's skills to an organization, by
|
|
1910
|
+
description: "Add a portal's skills to an organization, by a `portalId` from `portal_skills_list`. Needs a Startup plan or above. Astro loads the portal's skills once it is added. Adding one the organization already has is a no-op.",
|
|
1911
1911
|
method: "POST",
|
|
1912
1912
|
path: "/agents/v2/portal-skills/{portalId}",
|
|
1913
1913
|
args: [
|
|
@@ -1923,6 +1923,27 @@ var COMMANDS = [
|
|
|
1923
1923
|
readOnly: false,
|
|
1924
1924
|
destructive: false
|
|
1925
1925
|
},
|
|
1926
|
+
{
|
|
1927
|
+
command: "portal-skills list",
|
|
1928
|
+
tool: "portal_skills_list",
|
|
1929
|
+
title: "List portal skills",
|
|
1930
|
+
description: "List the skills for third-party portals, or only for the portals matching a portal name or task, such as `caloptima eligibility`. A portal skill is general knowledge about how a portal works: sign-in, page quirks, field mappings. Each portal comes with its skill names, and whether the organization has added it. Add one with `portal_skills_add`.",
|
|
1931
|
+
method: "GET",
|
|
1932
|
+
path: "/astro/v2/portal-skills",
|
|
1933
|
+
args: [],
|
|
1934
|
+
query: [
|
|
1935
|
+
{
|
|
1936
|
+
name: "query",
|
|
1937
|
+
flag: "query",
|
|
1938
|
+
type: "string",
|
|
1939
|
+
required: false,
|
|
1940
|
+
description: "A portal name or task, at least three characters. Omit to list every portal with skills."
|
|
1941
|
+
}
|
|
1942
|
+
],
|
|
1943
|
+
binary: false,
|
|
1944
|
+
readOnly: true,
|
|
1945
|
+
destructive: false
|
|
1946
|
+
},
|
|
1926
1947
|
{
|
|
1927
1948
|
command: "profile clear-cache",
|
|
1928
1949
|
tool: "profile_clear_browser_cache",
|
|
@@ -4441,7 +4462,7 @@ var COMMANDS = [
|
|
|
4441
4462
|
command: "workflow head publish",
|
|
4442
4463
|
tool: "workflow_head_publish",
|
|
4443
4464
|
title: "Publish workflow head",
|
|
4444
|
-
description: "Publish the workflow's editable head, assigning it the next version number and making it the default for `workflow_execute_post`. Use after `workflow_files_patch`; `workflow_version_publish` does the same for a version you already have the ID of.",
|
|
4465
|
+
description: "Publish the workflow's editable head, assigning it the next version number and making it the default for `workflow_execute_post`. Use after `workflow_files_patch`; `workflow_version_publish` does the same for a version you already have the ID of. `schemaChanges` lists the input and output schema changes against the version published before, which can break callers that send inputs or read outputs by name.",
|
|
4445
4466
|
method: "POST",
|
|
4446
4467
|
path: "/agents/v2/workflows/{workflowId}/head/publish",
|
|
4447
4468
|
args: [
|
|
@@ -5378,7 +5399,7 @@ var COMMANDS = [
|
|
|
5378
5399
|
command: "workflow version publish",
|
|
5379
5400
|
tool: "workflow_version_publish",
|
|
5380
5401
|
title: "Publish workflow version",
|
|
5381
|
-
description: "Publish a workflow version, assigning it a permanent version number and making it the default for `workflow_execute_post`.",
|
|
5402
|
+
description: "Publish a workflow version, assigning it a permanent version number and making it the default for `workflow_execute_post`. This also publishes an older version again. `schemaChanges` lists the input and output schema changes against the version published before, which can break callers that send inputs or read outputs by name.",
|
|
5382
5403
|
method: "POST",
|
|
5383
5404
|
path: "/agents/v2/workflows/{workflowId}/versions/{versionId}/publish",
|
|
5384
5405
|
args: [
|
|
@@ -6033,16 +6054,26 @@ function leafOptions(leaf, command) {
|
|
|
6033
6054
|
for (const file of command.body?.files ?? []) {
|
|
6034
6055
|
leaf.option(`--${file} <path>`, "File to upload. Repeatable.", collect);
|
|
6035
6056
|
}
|
|
6036
|
-
if (command.files === "pull")
|
|
6057
|
+
if (command.files === "pull") {
|
|
6037
6058
|
leaf.option(
|
|
6038
6059
|
"--out <dir>",
|
|
6039
6060
|
"Write here instead of ./<workflow-name>/head (a version: ./<workflow-name>/.versions/...). --json prints the file listing instead."
|
|
6040
6061
|
);
|
|
6041
|
-
|
|
6062
|
+
leaf.option(
|
|
6063
|
+
"--keep-edits",
|
|
6064
|
+
"Into a folder pulled before: keep every file you edited since that pull and refresh the rest. From a head, a later push sends those edits on top of the new revision."
|
|
6065
|
+
);
|
|
6066
|
+
}
|
|
6067
|
+
if (command.files === "push") {
|
|
6042
6068
|
leaf.option(
|
|
6043
6069
|
"--from <dir>",
|
|
6044
6070
|
"The pulled head folder to push. Defaults to the current folder."
|
|
6045
6071
|
);
|
|
6072
|
+
leaf.option(
|
|
6073
|
+
"--rebase",
|
|
6074
|
+
"When the head moved since your pull, pull it with --keep-edits and push again, twice at most."
|
|
6075
|
+
);
|
|
6076
|
+
}
|
|
6046
6077
|
}
|
|
6047
6078
|
function toParsed(leaf, positionals, dotted2) {
|
|
6048
6079
|
const globals = leaf.optsWithGlobals();
|
|
@@ -6649,7 +6680,7 @@ var ApiError = class extends Error {
|
|
|
6649
6680
|
status;
|
|
6650
6681
|
body;
|
|
6651
6682
|
};
|
|
6652
|
-
var VERSION = "1.9.
|
|
6683
|
+
var VERSION = "1.9.99";
|
|
6653
6684
|
function problem(status, body, fallback) {
|
|
6654
6685
|
if (body && typeof body === "object") {
|
|
6655
6686
|
const record2 = body;
|
|
@@ -7749,6 +7780,18 @@ function nodeFiles(cwd) {
|
|
|
7749
7780
|
};
|
|
7750
7781
|
}
|
|
7751
7782
|
|
|
7783
|
+
// src/sync/manifest.ts
|
|
7784
|
+
async function loadManifest(sync, dir) {
|
|
7785
|
+
const bytes = await sync.files.read(join3(dir, MANIFEST));
|
|
7786
|
+
if (!bytes) return void 0;
|
|
7787
|
+
return JSON.parse(Buffer.from(bytes).toString("utf8"));
|
|
7788
|
+
}
|
|
7789
|
+
async function writeManifest(sync, dir, manifest) {
|
|
7790
|
+
const text3 = `${JSON.stringify(manifest, null, 2)}
|
|
7791
|
+
`;
|
|
7792
|
+
await sync.files.write(join3(dir, MANIFEST), Buffer.from(text3, "utf8"));
|
|
7793
|
+
}
|
|
7794
|
+
|
|
7752
7795
|
// src/sync/workflow-pull.ts
|
|
7753
7796
|
var CONCURRENCY = 16;
|
|
7754
7797
|
function pullDir(root, tree, head) {
|
|
@@ -7756,6 +7799,9 @@ function pullDir(root, tree, head) {
|
|
|
7756
7799
|
if (head) return join3(base, "head");
|
|
7757
7800
|
return join3(base, ".versions", workflowDirName(tree.versionId, tree.version));
|
|
7758
7801
|
}
|
|
7802
|
+
function headFilesRequest(workflowId) {
|
|
7803
|
+
return get(`/agents/v2/workflows/${workflowId}/head/files`);
|
|
7804
|
+
}
|
|
7759
7805
|
async function agentFileBytes(sync, workflowId, tree, entry) {
|
|
7760
7806
|
const path = `/agents/v2/workflows/${workflowId}/versions/${tree.versionId}/files/${entry.fileId}/signed-url`;
|
|
7761
7807
|
const { url } = await sync.remote.json(get(path));
|
|
@@ -7768,29 +7814,100 @@ async function entryBytes(sync, workflowId, tree, entry) {
|
|
|
7768
7814
|
if (!entry.fileId) return void 0;
|
|
7769
7815
|
return agentFileBytes(sync, workflowId, tree, entry);
|
|
7770
7816
|
}
|
|
7771
|
-
|
|
7772
|
-
|
|
7773
|
-
|
|
7774
|
-
|
|
7775
|
-
|
|
7817
|
+
function inlineBytes(entry) {
|
|
7818
|
+
if (entry.contentBase64 !== void 0)
|
|
7819
|
+
return Buffer.from(entry.contentBase64, "base64");
|
|
7820
|
+
if (entry.content !== void 0) return Buffer.from(entry.content, "utf8");
|
|
7821
|
+
return void 0;
|
|
7822
|
+
}
|
|
7823
|
+
function wantedEntries(options, tree, skipped) {
|
|
7824
|
+
return tree.files.filter((entry) => {
|
|
7776
7825
|
if (!isContained(entry.path)) skipped.push(`${entry.path} (unsafe path)`);
|
|
7777
7826
|
else if (options.maxFileBytes !== void 0 && entry.size > options.maxFileBytes)
|
|
7778
7827
|
skipped.push(`${entry.path} (${entry.size} bytes, over the size limit)`);
|
|
7779
7828
|
else return true;
|
|
7780
7829
|
return false;
|
|
7781
7830
|
});
|
|
7782
|
-
|
|
7783
|
-
|
|
7784
|
-
|
|
7785
|
-
|
|
7786
|
-
if (!bytes) return void skipped.push(`${entry.path} (no content)`);
|
|
7787
|
-
await sync.files.write(join3(dir, entry.path), bytes);
|
|
7788
|
-
hashes[entry.path] = sha256(bytes);
|
|
7789
|
-
if (entry.kind === "agent_file") shared++;
|
|
7790
|
-
})
|
|
7791
|
-
);
|
|
7831
|
+
}
|
|
7832
|
+
async function inBatches(items, each) {
|
|
7833
|
+
for (let i = 0; i < items.length; i += CONCURRENCY) {
|
|
7834
|
+
await Promise.all(items.slice(i, i + CONCURRENCY).map(each));
|
|
7792
7835
|
}
|
|
7793
|
-
|
|
7836
|
+
}
|
|
7837
|
+
async function writeEntries(sync, options, tree, dir) {
|
|
7838
|
+
const hashes = {};
|
|
7839
|
+
const skipped = [];
|
|
7840
|
+
let shared = 0;
|
|
7841
|
+
let written = 0;
|
|
7842
|
+
await inBatches(wantedEntries(options, tree, skipped), async (entry) => {
|
|
7843
|
+
const bytes = await entryBytes(sync, options.workflowId, tree, entry);
|
|
7844
|
+
if (!bytes) return void skipped.push(`${entry.path} (no content)`);
|
|
7845
|
+
await sync.files.write(join3(dir, entry.path), bytes);
|
|
7846
|
+
hashes[entry.path] = sha256(bytes);
|
|
7847
|
+
written++;
|
|
7848
|
+
if (entry.kind === "agent_file") shared++;
|
|
7849
|
+
});
|
|
7850
|
+
return { hashes, skipped, shared, kept: [], written };
|
|
7851
|
+
}
|
|
7852
|
+
async function mergeEntry(sync, options, tree, dir, previous, entry, out) {
|
|
7853
|
+
const local2 = await sync.files.read(join3(dir, entry.path));
|
|
7854
|
+
const localHash = local2 ? sha256(local2) : void 0;
|
|
7855
|
+
const baseline = previous.files[entry.path];
|
|
7856
|
+
const edited = baseline === void 0 ? local2 !== void 0 : localHash !== baseline;
|
|
7857
|
+
let bytes = inlineBytes(entry);
|
|
7858
|
+
let serverHash = bytes ? sha256(bytes) : entry.checksum;
|
|
7859
|
+
if (edited) {
|
|
7860
|
+
if (serverHash === void 0) {
|
|
7861
|
+
bytes = await entryBytes(sync, options.workflowId, tree, entry);
|
|
7862
|
+
if (!bytes) return void out.skipped.push(`${entry.path} (no content)`);
|
|
7863
|
+
serverHash = sha256(bytes);
|
|
7864
|
+
}
|
|
7865
|
+
out.hashes[entry.path] = serverHash;
|
|
7866
|
+
out.kept.push(entry.path);
|
|
7867
|
+
return;
|
|
7868
|
+
}
|
|
7869
|
+
if (serverHash !== void 0 && serverHash === localHash) {
|
|
7870
|
+
out.hashes[entry.path] = serverHash;
|
|
7871
|
+
return;
|
|
7872
|
+
}
|
|
7873
|
+
bytes ??= await entryBytes(sync, options.workflowId, tree, entry);
|
|
7874
|
+
if (!bytes) return void out.skipped.push(`${entry.path} (no content)`);
|
|
7875
|
+
serverHash = sha256(bytes);
|
|
7876
|
+
if (serverHash !== localHash) {
|
|
7877
|
+
await sync.files.write(join3(dir, entry.path), bytes);
|
|
7878
|
+
out.written++;
|
|
7879
|
+
if (entry.kind === "agent_file") out.shared++;
|
|
7880
|
+
}
|
|
7881
|
+
out.hashes[entry.path] = serverHash;
|
|
7882
|
+
}
|
|
7883
|
+
async function mergeEntries(sync, options, tree, dir, previous) {
|
|
7884
|
+
const out = {
|
|
7885
|
+
hashes: {},
|
|
7886
|
+
skipped: [],
|
|
7887
|
+
shared: 0,
|
|
7888
|
+
kept: [],
|
|
7889
|
+
written: 0
|
|
7890
|
+
};
|
|
7891
|
+
const wanted = wantedEntries(options, tree, out.skipped);
|
|
7892
|
+
await inBatches(
|
|
7893
|
+
wanted,
|
|
7894
|
+
(entry) => mergeEntry(sync, options, tree, dir, previous, entry, out)
|
|
7895
|
+
);
|
|
7896
|
+
const remote = new Set(tree.files.map((entry) => entry.path));
|
|
7897
|
+
for (const path of Object.keys(previous.files)) {
|
|
7898
|
+
if (path in out.hashes) continue;
|
|
7899
|
+
if (remote.has(path)) {
|
|
7900
|
+
out.hashes[path] = previous.files[path];
|
|
7901
|
+
continue;
|
|
7902
|
+
}
|
|
7903
|
+
const local2 = await sync.files.read(join3(dir, path));
|
|
7904
|
+
if (!local2) continue;
|
|
7905
|
+
if (sha256(local2) === previous.files[path])
|
|
7906
|
+
await sync.files.remove(join3(dir, path));
|
|
7907
|
+
else out.kept.push(path);
|
|
7908
|
+
}
|
|
7909
|
+
out.kept.sort();
|
|
7910
|
+
return out;
|
|
7794
7911
|
}
|
|
7795
7912
|
async function removeStale(sync, dir, keep) {
|
|
7796
7913
|
for (const path of await sync.files.list(dir)) {
|
|
@@ -7798,11 +7915,6 @@ async function removeStale(sync, dir, keep) {
|
|
|
7798
7915
|
await sync.files.remove(join3(dir, path));
|
|
7799
7916
|
}
|
|
7800
7917
|
}
|
|
7801
|
-
async function writeManifest(sync, dir, manifest) {
|
|
7802
|
-
const text3 = `${JSON.stringify(manifest, null, 2)}
|
|
7803
|
-
`;
|
|
7804
|
-
await sync.files.write(join3(dir, MANIFEST), Buffer.from(text3, "utf8"));
|
|
7805
|
-
}
|
|
7806
7918
|
async function pullWorkflow(sync, options) {
|
|
7807
7919
|
const request = {
|
|
7808
7920
|
...options.request,
|
|
@@ -7812,20 +7924,22 @@ async function pullWorkflow(sync, options) {
|
|
|
7812
7924
|
const tree = await sync.remote.json(request);
|
|
7813
7925
|
const head = request.path.endsWith("/head/files");
|
|
7814
7926
|
const dir = options.dir ?? pullDir(options.root ?? "", tree, head);
|
|
7815
|
-
const
|
|
7816
|
-
|
|
7817
|
-
|
|
7818
|
-
|
|
7819
|
-
dir
|
|
7820
|
-
);
|
|
7821
|
-
await removeStale(sync, dir, new Set(Object.keys(hashes)));
|
|
7927
|
+
const previous = options.keepEdits ? await loadManifest(sync, dir) : void 0;
|
|
7928
|
+
const written = previous ? await mergeEntries(sync, options, tree, dir, previous) : await writeEntries(sync, options, tree, dir);
|
|
7929
|
+
if (!previous)
|
|
7930
|
+
await removeStale(sync, dir, new Set(Object.keys(written.hashes)));
|
|
7822
7931
|
if (options.manifest !== false) {
|
|
7932
|
+
const variants = request.query.get("variants") ?? void 0;
|
|
7823
7933
|
await writeManifest(sync, dir, {
|
|
7824
7934
|
workflowId: options.workflowId,
|
|
7825
7935
|
versionId: tree.versionId,
|
|
7826
7936
|
head,
|
|
7827
7937
|
rev: tree.rev,
|
|
7828
|
-
files: hashes
|
|
7938
|
+
files: written.hashes,
|
|
7939
|
+
...variants !== void 0 && { variants },
|
|
7940
|
+
...options.maxFileBytes !== void 0 && {
|
|
7941
|
+
maxFileBytes: options.maxFileBytes
|
|
7942
|
+
}
|
|
7829
7943
|
});
|
|
7830
7944
|
}
|
|
7831
7945
|
return {
|
|
@@ -7836,9 +7950,11 @@ async function pullWorkflow(sync, options) {
|
|
|
7836
7950
|
rev: tree.rev,
|
|
7837
7951
|
dir,
|
|
7838
7952
|
head,
|
|
7839
|
-
written:
|
|
7840
|
-
shared,
|
|
7841
|
-
skipped
|
|
7953
|
+
written: written.written,
|
|
7954
|
+
shared: written.shared,
|
|
7955
|
+
skipped: written.skipped,
|
|
7956
|
+
kept: written.kept,
|
|
7957
|
+
...tree.variantKeys && { variantKeys: tree.variantKeys }
|
|
7842
7958
|
};
|
|
7843
7959
|
}
|
|
7844
7960
|
function formatWorkflowPull(result) {
|
|
@@ -7849,6 +7965,8 @@ function formatWorkflowPull(result) {
|
|
|
7849
7965
|
`Workflow version: ${result.versionId}${result.version ? ` (v${result.version})` : " (draft)"}`,
|
|
7850
7966
|
`Written to: ${result.dir}/`,
|
|
7851
7967
|
files,
|
|
7968
|
+
result.kept.length > 0 ? `Kept your edits in:
|
|
7969
|
+
${result.kept.map((s) => ` - ${s}`).join("\n")}` : null,
|
|
7852
7970
|
result.skipped.length > 0 ? `Skipped:
|
|
7853
7971
|
${result.skipped.map((s) => ` - ${s}`).join("\n")}` : null,
|
|
7854
7972
|
result.head ? `Edit the files, then run \`asteroid workflow push\` from ${result.dir}/.` : "This is a pinned version. It is read-only."
|
|
@@ -7856,14 +7974,14 @@ ${result.skipped.map((s) => ` - ${s}`).join("\n")}` : null,
|
|
|
7856
7974
|
}
|
|
7857
7975
|
|
|
7858
7976
|
// src/sync/workflow-push.ts
|
|
7977
|
+
var REBASE_ATTEMPTS = 3;
|
|
7859
7978
|
async function readManifest(sync, dir) {
|
|
7860
|
-
const
|
|
7861
|
-
if (!
|
|
7979
|
+
const manifest = await loadManifest(sync, dir);
|
|
7980
|
+
if (!manifest) {
|
|
7862
7981
|
throw new UsageError(
|
|
7863
7982
|
`no pulled workflow in ${dir}. Run \`asteroid workflow pull <workflow-id>\`, then push from the head folder it writes.`
|
|
7864
7983
|
);
|
|
7865
7984
|
}
|
|
7866
|
-
const manifest = JSON.parse(Buffer.from(bytes).toString("utf8"));
|
|
7867
7985
|
if (!manifest.head) {
|
|
7868
7986
|
throw new UsageError(
|
|
7869
7987
|
`${dir} holds a pinned workflow version, which is read-only. Push from a pulled head.`
|
|
@@ -7893,25 +8011,42 @@ async function localChanges(sync, dir, manifest) {
|
|
|
7893
8011
|
);
|
|
7894
8012
|
return { writes, deletes };
|
|
7895
8013
|
}
|
|
8014
|
+
function conflict(manifest) {
|
|
8015
|
+
return new UsageError(
|
|
8016
|
+
`the workflow head changed since your pull (rev ${manifest.rev}). Run \`asteroid workflow pull ${manifest.workflowId} --keep-edits\` to bring its changes under yours, then push again, or push with --rebase to do both.`
|
|
8017
|
+
);
|
|
8018
|
+
}
|
|
7896
8019
|
async function patch(sync, manifest, writes, deletes) {
|
|
7897
8020
|
try {
|
|
7898
8021
|
await sync.remote.json({
|
|
7899
8022
|
method: "PATCH",
|
|
7900
8023
|
path: `/agents/v2/workflows/${manifest.workflowId}/head/files`,
|
|
7901
8024
|
query: new URLSearchParams(),
|
|
7902
|
-
json: {
|
|
8025
|
+
json: {
|
|
8026
|
+
baseRev: manifest.rev,
|
|
8027
|
+
writes: writes.map(({ path, contentBase64 }) => ({
|
|
8028
|
+
path,
|
|
8029
|
+
contentBase64
|
|
8030
|
+
})),
|
|
8031
|
+
deletes
|
|
8032
|
+
}
|
|
7903
8033
|
});
|
|
8034
|
+
return "applied";
|
|
7904
8035
|
} catch (error) {
|
|
7905
8036
|
if (statusOf(error) !== 409) throw error;
|
|
7906
|
-
|
|
7907
|
-
`the workflow head changed since your pull (rev ${manifest.rev}). Copy your edits aside, run \`asteroid workflow pull ${manifest.workflowId}\`, reapply them, then push again.`
|
|
7908
|
-
);
|
|
8037
|
+
return "conflict";
|
|
7909
8038
|
}
|
|
7910
8039
|
}
|
|
8040
|
+
function headRequest(manifest) {
|
|
8041
|
+
const request = headFilesRequest(manifest.workflowId);
|
|
8042
|
+
if (manifest.variants !== void 0)
|
|
8043
|
+
request.query.set("variants", manifest.variants);
|
|
8044
|
+
if (manifest.maxFileBytes !== void 0)
|
|
8045
|
+
request.query.set("maxFileBytes", String(manifest.maxFileBytes));
|
|
8046
|
+
return request;
|
|
8047
|
+
}
|
|
7911
8048
|
async function refresh2(sync, dir, manifest, pushed) {
|
|
7912
|
-
const tree = await sync.remote.json(
|
|
7913
|
-
get(`/agents/v2/workflows/${manifest.workflowId}/head/files`)
|
|
7914
|
-
);
|
|
8049
|
+
const tree = await sync.remote.json(headRequest(manifest));
|
|
7915
8050
|
const files = {};
|
|
7916
8051
|
for (const entry of tree.files) {
|
|
7917
8052
|
if (entry.content !== void 0) {
|
|
@@ -7921,7 +8056,7 @@ async function refresh2(sync, dir, manifest, pushed) {
|
|
|
7921
8056
|
await sync.files.write(join3(dir, entry.path), bytes);
|
|
7922
8057
|
} else {
|
|
7923
8058
|
const local2 = await sync.files.read(join3(dir, entry.path));
|
|
7924
|
-
const hash = local2 ? sha256(local2) : pushed.get(entry.path)
|
|
8059
|
+
const hash = local2 ? sha256(local2) : pushed.get(entry.path);
|
|
7925
8060
|
if (hash) files[entry.path] = hash;
|
|
7926
8061
|
}
|
|
7927
8062
|
}
|
|
@@ -7933,43 +8068,57 @@ async function refresh2(sync, dir, manifest, pushed) {
|
|
|
7933
8068
|
await writeManifest(sync, dir, next);
|
|
7934
8069
|
return next;
|
|
7935
8070
|
}
|
|
7936
|
-
async function
|
|
7937
|
-
|
|
8071
|
+
async function rebase(sync, dir, manifest) {
|
|
8072
|
+
await pullWorkflow(sync, {
|
|
8073
|
+
workflowId: manifest.workflowId,
|
|
8074
|
+
request: headRequest(manifest),
|
|
8075
|
+
dir,
|
|
8076
|
+
keepEdits: true,
|
|
8077
|
+
maxFileBytes: manifest.maxFileBytes
|
|
8078
|
+
});
|
|
8079
|
+
return readManifest(sync, dir);
|
|
8080
|
+
}
|
|
8081
|
+
async function pushWorkflow(sync, dir, workflowId, options = {}) {
|
|
8082
|
+
let manifest = await readManifest(sync, dir);
|
|
7938
8083
|
if (workflowId !== void 0 && manifest.workflowId !== workflowId) {
|
|
7939
8084
|
throw new UsageError(
|
|
7940
8085
|
`${dir} holds workflow ${manifest.workflowId}, not ${workflowId}.`
|
|
7941
8086
|
);
|
|
7942
8087
|
}
|
|
7943
|
-
|
|
7944
|
-
|
|
7945
|
-
|
|
7946
|
-
|
|
7947
|
-
|
|
7948
|
-
|
|
7949
|
-
|
|
7950
|
-
|
|
7951
|
-
|
|
8088
|
+
let rebasedFrom;
|
|
8089
|
+
for (let attempt = 1; ; attempt++) {
|
|
8090
|
+
const { writes, deletes } = await localChanges(sync, dir, manifest);
|
|
8091
|
+
const result = {
|
|
8092
|
+
workflowId: manifest.workflowId,
|
|
8093
|
+
dir,
|
|
8094
|
+
written: writes.map((w) => w.path),
|
|
8095
|
+
deleted: deletes,
|
|
8096
|
+
...rebasedFrom !== void 0 && { rebasedFrom }
|
|
8097
|
+
};
|
|
8098
|
+
if (writes.length === 0 && deletes.length === 0) {
|
|
8099
|
+
return { ...result, rev: manifest.rev, versionId: manifest.versionId };
|
|
8100
|
+
}
|
|
8101
|
+
if (await patch(sync, manifest, writes, deletes) === "applied") {
|
|
8102
|
+
const next = await refresh2(
|
|
8103
|
+
sync,
|
|
8104
|
+
dir,
|
|
8105
|
+
manifest,
|
|
8106
|
+
new Map(writes.map((w) => [w.path, w.hash]))
|
|
8107
|
+
);
|
|
8108
|
+
return { ...result, rev: next.rev, versionId: next.versionId };
|
|
8109
|
+
}
|
|
8110
|
+
if (!options.rebase || attempt >= REBASE_ATTEMPTS) throw conflict(manifest);
|
|
8111
|
+
rebasedFrom ??= manifest.rev;
|
|
8112
|
+
manifest = await rebase(sync, dir, manifest);
|
|
7952
8113
|
}
|
|
7953
|
-
await patch(
|
|
7954
|
-
sync,
|
|
7955
|
-
manifest,
|
|
7956
|
-
writes.map(({ path, contentBase64 }) => ({ path, contentBase64 })),
|
|
7957
|
-
deletes
|
|
7958
|
-
);
|
|
7959
|
-
const next = await refresh2(
|
|
7960
|
-
sync,
|
|
7961
|
-
dir,
|
|
7962
|
-
manifest,
|
|
7963
|
-
new Map(writes.map((w) => [w.path, w.hash]))
|
|
7964
|
-
);
|
|
7965
|
-
return { ...result, rev: next.rev, versionId: next.versionId };
|
|
7966
8114
|
}
|
|
7967
8115
|
function formatWorkflowPush(result) {
|
|
8116
|
+
const rebased = result.rebasedFrom !== void 0 ? ` Rebased on the head first: your pull was rev ${result.rebasedFrom}.` : "";
|
|
7968
8117
|
if (result.written.length === 0 && result.deleted.length === 0) {
|
|
7969
|
-
return `Nothing to push: ${result.dir}/ matches the head you pulled (rev ${result.rev})
|
|
8118
|
+
return `Nothing to push: ${result.dir}/ matches the head you pulled (rev ${result.rev}).${rebased}`;
|
|
7970
8119
|
}
|
|
7971
8120
|
return [
|
|
7972
|
-
`Pushed to workflow ${result.workflowId}: ${result.written.length} written, ${result.deleted.length} deleted
|
|
8121
|
+
`Pushed to workflow ${result.workflowId}: ${result.written.length} written, ${result.deleted.length} deleted.${rebased}`,
|
|
7973
8122
|
...result.written.map((path) => ` + ${path}`),
|
|
7974
8123
|
...result.deleted.map((path) => ` - ${path}`),
|
|
7975
8124
|
`Head is now rev ${result.rev} (version ${result.versionId}).`
|
|
@@ -8000,9 +8149,9 @@ function executionOptions(parsed) {
|
|
|
8000
8149
|
dir: dir ? () => dir : void 0
|
|
8001
8150
|
};
|
|
8002
8151
|
}
|
|
8003
|
-
function
|
|
8152
|
+
function withoutFlags(parsed, ...names) {
|
|
8004
8153
|
const flags = new Map(parsed.flags);
|
|
8005
|
-
flags.delete(name);
|
|
8154
|
+
for (const name of names) flags.delete(name);
|
|
8006
8155
|
return { ...parsed, flags };
|
|
8007
8156
|
}
|
|
8008
8157
|
function runsLocally(command, parsed) {
|
|
@@ -8023,11 +8172,16 @@ async function pull(command, parsed, sync, deps) {
|
|
|
8023
8172
|
rejectPartial(parsed);
|
|
8024
8173
|
const result = await pullWorkflow(sync, {
|
|
8025
8174
|
workflowId: parsed.positionals[0],
|
|
8026
|
-
request: await buildRequest(
|
|
8027
|
-
|
|
8028
|
-
|
|
8175
|
+
request: await buildRequest(
|
|
8176
|
+
command,
|
|
8177
|
+
withoutFlags(parsed, "out", "keep-edits"),
|
|
8178
|
+
{
|
|
8179
|
+
io: deps.io
|
|
8180
|
+
}
|
|
8181
|
+
),
|
|
8029
8182
|
dir: flag(parsed, "out"),
|
|
8030
8183
|
root: deps.root ?? "",
|
|
8184
|
+
keepEdits: flag(parsed, "keep-edits") === "true",
|
|
8031
8185
|
...deps.workflowPull
|
|
8032
8186
|
});
|
|
8033
8187
|
return { result, text: formatWorkflowPull(result) };
|
|
@@ -8038,7 +8192,8 @@ async function execute(command, parsed, sync, deps) {
|
|
|
8038
8192
|
const result2 = await pushWorkflow(
|
|
8039
8193
|
sync,
|
|
8040
8194
|
flag(parsed, "from") ?? ".",
|
|
8041
|
-
parsed.positionals[0] || void 0
|
|
8195
|
+
parsed.positionals[0] || void 0,
|
|
8196
|
+
{ rebase: flag(parsed, "rebase") === "true" }
|
|
8042
8197
|
);
|
|
8043
8198
|
return { result: result2, text: formatWorkflowPush(result2) };
|
|
8044
8199
|
}
|
package/dist/core.js
CHANGED
|
@@ -1928,7 +1928,7 @@ var COMMANDS = [
|
|
|
1928
1928
|
command: "portal-skills add",
|
|
1929
1929
|
tool: "portal_skills_add",
|
|
1930
1930
|
title: "Add portal skills",
|
|
1931
|
-
description: "Add a portal's skills to an organization, by
|
|
1931
|
+
description: "Add a portal's skills to an organization, by a `portalId` from `portal_skills_list`. Needs a Startup plan or above. Astro loads the portal's skills once it is added. Adding one the organization already has is a no-op.",
|
|
1932
1932
|
method: "POST",
|
|
1933
1933
|
path: "/agents/v2/portal-skills/{portalId}",
|
|
1934
1934
|
args: [
|
|
@@ -1944,6 +1944,27 @@ var COMMANDS = [
|
|
|
1944
1944
|
readOnly: false,
|
|
1945
1945
|
destructive: false
|
|
1946
1946
|
},
|
|
1947
|
+
{
|
|
1948
|
+
command: "portal-skills list",
|
|
1949
|
+
tool: "portal_skills_list",
|
|
1950
|
+
title: "List portal skills",
|
|
1951
|
+
description: "List the skills for third-party portals, or only for the portals matching a portal name or task, such as `caloptima eligibility`. A portal skill is general knowledge about how a portal works: sign-in, page quirks, field mappings. Each portal comes with its skill names, and whether the organization has added it. Add one with `portal_skills_add`.",
|
|
1952
|
+
method: "GET",
|
|
1953
|
+
path: "/astro/v2/portal-skills",
|
|
1954
|
+
args: [],
|
|
1955
|
+
query: [
|
|
1956
|
+
{
|
|
1957
|
+
name: "query",
|
|
1958
|
+
flag: "query",
|
|
1959
|
+
type: "string",
|
|
1960
|
+
required: false,
|
|
1961
|
+
description: "A portal name or task, at least three characters. Omit to list every portal with skills."
|
|
1962
|
+
}
|
|
1963
|
+
],
|
|
1964
|
+
binary: false,
|
|
1965
|
+
readOnly: true,
|
|
1966
|
+
destructive: false
|
|
1967
|
+
},
|
|
1947
1968
|
{
|
|
1948
1969
|
command: "profile clear-cache",
|
|
1949
1970
|
tool: "profile_clear_browser_cache",
|
|
@@ -4462,7 +4483,7 @@ var COMMANDS = [
|
|
|
4462
4483
|
command: "workflow head publish",
|
|
4463
4484
|
tool: "workflow_head_publish",
|
|
4464
4485
|
title: "Publish workflow head",
|
|
4465
|
-
description: "Publish the workflow's editable head, assigning it the next version number and making it the default for `workflow_execute_post`. Use after `workflow_files_patch`; `workflow_version_publish` does the same for a version you already have the ID of.",
|
|
4486
|
+
description: "Publish the workflow's editable head, assigning it the next version number and making it the default for `workflow_execute_post`. Use after `workflow_files_patch`; `workflow_version_publish` does the same for a version you already have the ID of. `schemaChanges` lists the input and output schema changes against the version published before, which can break callers that send inputs or read outputs by name.",
|
|
4466
4487
|
method: "POST",
|
|
4467
4488
|
path: "/agents/v2/workflows/{workflowId}/head/publish",
|
|
4468
4489
|
args: [
|
|
@@ -5399,7 +5420,7 @@ var COMMANDS = [
|
|
|
5399
5420
|
command: "workflow version publish",
|
|
5400
5421
|
tool: "workflow_version_publish",
|
|
5401
5422
|
title: "Publish workflow version",
|
|
5402
|
-
description: "Publish a workflow version, assigning it a permanent version number and making it the default for `workflow_execute_post`.",
|
|
5423
|
+
description: "Publish a workflow version, assigning it a permanent version number and making it the default for `workflow_execute_post`. This also publishes an older version again. `schemaChanges` lists the input and output schema changes against the version published before, which can break callers that send inputs or read outputs by name.",
|
|
5403
5424
|
method: "POST",
|
|
5404
5425
|
path: "/agents/v2/workflows/{workflowId}/versions/{versionId}/publish",
|
|
5405
5426
|
args: [
|
|
@@ -5903,16 +5924,26 @@ function leafOptions(leaf, command) {
|
|
|
5903
5924
|
for (const file of command.body?.files ?? []) {
|
|
5904
5925
|
leaf.option(`--${file} <path>`, "File to upload. Repeatable.", collect);
|
|
5905
5926
|
}
|
|
5906
|
-
if (command.files === "pull")
|
|
5927
|
+
if (command.files === "pull") {
|
|
5907
5928
|
leaf.option(
|
|
5908
5929
|
"--out <dir>",
|
|
5909
5930
|
"Write here instead of ./<workflow-name>/head (a version: ./<workflow-name>/.versions/...). --json prints the file listing instead."
|
|
5910
5931
|
);
|
|
5911
|
-
|
|
5932
|
+
leaf.option(
|
|
5933
|
+
"--keep-edits",
|
|
5934
|
+
"Into a folder pulled before: keep every file you edited since that pull and refresh the rest. From a head, a later push sends those edits on top of the new revision."
|
|
5935
|
+
);
|
|
5936
|
+
}
|
|
5937
|
+
if (command.files === "push") {
|
|
5912
5938
|
leaf.option(
|
|
5913
5939
|
"--from <dir>",
|
|
5914
5940
|
"The pulled head folder to push. Defaults to the current folder."
|
|
5915
5941
|
);
|
|
5942
|
+
leaf.option(
|
|
5943
|
+
"--rebase",
|
|
5944
|
+
"When the head moved since your pull, pull it with --keep-edits and push again, twice at most."
|
|
5945
|
+
);
|
|
5946
|
+
}
|
|
5916
5947
|
}
|
|
5917
5948
|
function toParsed(leaf, positionals, dotted2) {
|
|
5918
5949
|
const globals = leaf.optsWithGlobals();
|
|
@@ -6703,6 +6734,18 @@ ${result.skipped.map((s) => ` - ${s}`).join("\n")}` : null,
|
|
|
6703
6734
|
].filter(Boolean).join("\n");
|
|
6704
6735
|
}
|
|
6705
6736
|
|
|
6737
|
+
// src/sync/manifest.ts
|
|
6738
|
+
async function loadManifest(sync, dir) {
|
|
6739
|
+
const bytes = await sync.files.read(join(dir, MANIFEST));
|
|
6740
|
+
if (!bytes) return void 0;
|
|
6741
|
+
return JSON.parse(Buffer.from(bytes).toString("utf8"));
|
|
6742
|
+
}
|
|
6743
|
+
async function writeManifest(sync, dir, manifest) {
|
|
6744
|
+
const text2 = `${JSON.stringify(manifest, null, 2)}
|
|
6745
|
+
`;
|
|
6746
|
+
await sync.files.write(join(dir, MANIFEST), Buffer.from(text2, "utf8"));
|
|
6747
|
+
}
|
|
6748
|
+
|
|
6706
6749
|
// src/sync/node-files.ts
|
|
6707
6750
|
import { mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
6708
6751
|
import { dirname, relative, resolve, sep } from "node:path";
|
|
@@ -6754,6 +6797,9 @@ function pullDir(root, tree, head) {
|
|
|
6754
6797
|
if (head) return join(base, "head");
|
|
6755
6798
|
return join(base, ".versions", workflowDirName(tree.versionId, tree.version));
|
|
6756
6799
|
}
|
|
6800
|
+
function headFilesRequest(workflowId) {
|
|
6801
|
+
return get(`/agents/v2/workflows/${workflowId}/head/files`);
|
|
6802
|
+
}
|
|
6757
6803
|
async function agentFileBytes(sync, workflowId, tree, entry) {
|
|
6758
6804
|
const path = `/agents/v2/workflows/${workflowId}/versions/${tree.versionId}/files/${entry.fileId}/signed-url`;
|
|
6759
6805
|
const { url } = await sync.remote.json(get(path));
|
|
@@ -6766,29 +6812,100 @@ async function entryBytes(sync, workflowId, tree, entry) {
|
|
|
6766
6812
|
if (!entry.fileId) return void 0;
|
|
6767
6813
|
return agentFileBytes(sync, workflowId, tree, entry);
|
|
6768
6814
|
}
|
|
6769
|
-
|
|
6770
|
-
|
|
6771
|
-
|
|
6772
|
-
|
|
6773
|
-
|
|
6815
|
+
function inlineBytes(entry) {
|
|
6816
|
+
if (entry.contentBase64 !== void 0)
|
|
6817
|
+
return Buffer.from(entry.contentBase64, "base64");
|
|
6818
|
+
if (entry.content !== void 0) return Buffer.from(entry.content, "utf8");
|
|
6819
|
+
return void 0;
|
|
6820
|
+
}
|
|
6821
|
+
function wantedEntries(options, tree, skipped) {
|
|
6822
|
+
return tree.files.filter((entry) => {
|
|
6774
6823
|
if (!isContained(entry.path)) skipped.push(`${entry.path} (unsafe path)`);
|
|
6775
6824
|
else if (options.maxFileBytes !== void 0 && entry.size > options.maxFileBytes)
|
|
6776
6825
|
skipped.push(`${entry.path} (${entry.size} bytes, over the size limit)`);
|
|
6777
6826
|
else return true;
|
|
6778
6827
|
return false;
|
|
6779
6828
|
});
|
|
6780
|
-
|
|
6781
|
-
|
|
6782
|
-
|
|
6783
|
-
|
|
6784
|
-
if (!bytes) return void skipped.push(`${entry.path} (no content)`);
|
|
6785
|
-
await sync.files.write(join(dir, entry.path), bytes);
|
|
6786
|
-
hashes[entry.path] = sha256(bytes);
|
|
6787
|
-
if (entry.kind === "agent_file") shared++;
|
|
6788
|
-
})
|
|
6789
|
-
);
|
|
6829
|
+
}
|
|
6830
|
+
async function inBatches(items, each) {
|
|
6831
|
+
for (let i = 0; i < items.length; i += CONCURRENCY) {
|
|
6832
|
+
await Promise.all(items.slice(i, i + CONCURRENCY).map(each));
|
|
6790
6833
|
}
|
|
6791
|
-
|
|
6834
|
+
}
|
|
6835
|
+
async function writeEntries(sync, options, tree, dir) {
|
|
6836
|
+
const hashes = {};
|
|
6837
|
+
const skipped = [];
|
|
6838
|
+
let shared = 0;
|
|
6839
|
+
let written = 0;
|
|
6840
|
+
await inBatches(wantedEntries(options, tree, skipped), async (entry) => {
|
|
6841
|
+
const bytes = await entryBytes(sync, options.workflowId, tree, entry);
|
|
6842
|
+
if (!bytes) return void skipped.push(`${entry.path} (no content)`);
|
|
6843
|
+
await sync.files.write(join(dir, entry.path), bytes);
|
|
6844
|
+
hashes[entry.path] = sha256(bytes);
|
|
6845
|
+
written++;
|
|
6846
|
+
if (entry.kind === "agent_file") shared++;
|
|
6847
|
+
});
|
|
6848
|
+
return { hashes, skipped, shared, kept: [], written };
|
|
6849
|
+
}
|
|
6850
|
+
async function mergeEntry(sync, options, tree, dir, previous, entry, out) {
|
|
6851
|
+
const local2 = await sync.files.read(join(dir, entry.path));
|
|
6852
|
+
const localHash = local2 ? sha256(local2) : void 0;
|
|
6853
|
+
const baseline = previous.files[entry.path];
|
|
6854
|
+
const edited = baseline === void 0 ? local2 !== void 0 : localHash !== baseline;
|
|
6855
|
+
let bytes = inlineBytes(entry);
|
|
6856
|
+
let serverHash = bytes ? sha256(bytes) : entry.checksum;
|
|
6857
|
+
if (edited) {
|
|
6858
|
+
if (serverHash === void 0) {
|
|
6859
|
+
bytes = await entryBytes(sync, options.workflowId, tree, entry);
|
|
6860
|
+
if (!bytes) return void out.skipped.push(`${entry.path} (no content)`);
|
|
6861
|
+
serverHash = sha256(bytes);
|
|
6862
|
+
}
|
|
6863
|
+
out.hashes[entry.path] = serverHash;
|
|
6864
|
+
out.kept.push(entry.path);
|
|
6865
|
+
return;
|
|
6866
|
+
}
|
|
6867
|
+
if (serverHash !== void 0 && serverHash === localHash) {
|
|
6868
|
+
out.hashes[entry.path] = serverHash;
|
|
6869
|
+
return;
|
|
6870
|
+
}
|
|
6871
|
+
bytes ??= await entryBytes(sync, options.workflowId, tree, entry);
|
|
6872
|
+
if (!bytes) return void out.skipped.push(`${entry.path} (no content)`);
|
|
6873
|
+
serverHash = sha256(bytes);
|
|
6874
|
+
if (serverHash !== localHash) {
|
|
6875
|
+
await sync.files.write(join(dir, entry.path), bytes);
|
|
6876
|
+
out.written++;
|
|
6877
|
+
if (entry.kind === "agent_file") out.shared++;
|
|
6878
|
+
}
|
|
6879
|
+
out.hashes[entry.path] = serverHash;
|
|
6880
|
+
}
|
|
6881
|
+
async function mergeEntries(sync, options, tree, dir, previous) {
|
|
6882
|
+
const out = {
|
|
6883
|
+
hashes: {},
|
|
6884
|
+
skipped: [],
|
|
6885
|
+
shared: 0,
|
|
6886
|
+
kept: [],
|
|
6887
|
+
written: 0
|
|
6888
|
+
};
|
|
6889
|
+
const wanted = wantedEntries(options, tree, out.skipped);
|
|
6890
|
+
await inBatches(
|
|
6891
|
+
wanted,
|
|
6892
|
+
(entry) => mergeEntry(sync, options, tree, dir, previous, entry, out)
|
|
6893
|
+
);
|
|
6894
|
+
const remote = new Set(tree.files.map((entry) => entry.path));
|
|
6895
|
+
for (const path of Object.keys(previous.files)) {
|
|
6896
|
+
if (path in out.hashes) continue;
|
|
6897
|
+
if (remote.has(path)) {
|
|
6898
|
+
out.hashes[path] = previous.files[path];
|
|
6899
|
+
continue;
|
|
6900
|
+
}
|
|
6901
|
+
const local2 = await sync.files.read(join(dir, path));
|
|
6902
|
+
if (!local2) continue;
|
|
6903
|
+
if (sha256(local2) === previous.files[path])
|
|
6904
|
+
await sync.files.remove(join(dir, path));
|
|
6905
|
+
else out.kept.push(path);
|
|
6906
|
+
}
|
|
6907
|
+
out.kept.sort();
|
|
6908
|
+
return out;
|
|
6792
6909
|
}
|
|
6793
6910
|
async function removeStale(sync, dir, keep) {
|
|
6794
6911
|
for (const path of await sync.files.list(dir)) {
|
|
@@ -6796,11 +6913,6 @@ async function removeStale(sync, dir, keep) {
|
|
|
6796
6913
|
await sync.files.remove(join(dir, path));
|
|
6797
6914
|
}
|
|
6798
6915
|
}
|
|
6799
|
-
async function writeManifest(sync, dir, manifest) {
|
|
6800
|
-
const text2 = `${JSON.stringify(manifest, null, 2)}
|
|
6801
|
-
`;
|
|
6802
|
-
await sync.files.write(join(dir, MANIFEST), Buffer.from(text2, "utf8"));
|
|
6803
|
-
}
|
|
6804
6916
|
async function pullWorkflow(sync, options) {
|
|
6805
6917
|
const request = {
|
|
6806
6918
|
...options.request,
|
|
@@ -6810,20 +6922,22 @@ async function pullWorkflow(sync, options) {
|
|
|
6810
6922
|
const tree = await sync.remote.json(request);
|
|
6811
6923
|
const head = request.path.endsWith("/head/files");
|
|
6812
6924
|
const dir = options.dir ?? pullDir(options.root ?? "", tree, head);
|
|
6813
|
-
const
|
|
6814
|
-
|
|
6815
|
-
|
|
6816
|
-
|
|
6817
|
-
dir
|
|
6818
|
-
);
|
|
6819
|
-
await removeStale(sync, dir, new Set(Object.keys(hashes)));
|
|
6925
|
+
const previous = options.keepEdits ? await loadManifest(sync, dir) : void 0;
|
|
6926
|
+
const written = previous ? await mergeEntries(sync, options, tree, dir, previous) : await writeEntries(sync, options, tree, dir);
|
|
6927
|
+
if (!previous)
|
|
6928
|
+
await removeStale(sync, dir, new Set(Object.keys(written.hashes)));
|
|
6820
6929
|
if (options.manifest !== false) {
|
|
6930
|
+
const variants = request.query.get("variants") ?? void 0;
|
|
6821
6931
|
await writeManifest(sync, dir, {
|
|
6822
6932
|
workflowId: options.workflowId,
|
|
6823
6933
|
versionId: tree.versionId,
|
|
6824
6934
|
head,
|
|
6825
6935
|
rev: tree.rev,
|
|
6826
|
-
files: hashes
|
|
6936
|
+
files: written.hashes,
|
|
6937
|
+
...variants !== void 0 && { variants },
|
|
6938
|
+
...options.maxFileBytes !== void 0 && {
|
|
6939
|
+
maxFileBytes: options.maxFileBytes
|
|
6940
|
+
}
|
|
6827
6941
|
});
|
|
6828
6942
|
}
|
|
6829
6943
|
return {
|
|
@@ -6834,9 +6948,11 @@ async function pullWorkflow(sync, options) {
|
|
|
6834
6948
|
rev: tree.rev,
|
|
6835
6949
|
dir,
|
|
6836
6950
|
head,
|
|
6837
|
-
written:
|
|
6838
|
-
shared,
|
|
6839
|
-
skipped
|
|
6951
|
+
written: written.written,
|
|
6952
|
+
shared: written.shared,
|
|
6953
|
+
skipped: written.skipped,
|
|
6954
|
+
kept: written.kept,
|
|
6955
|
+
...tree.variantKeys && { variantKeys: tree.variantKeys }
|
|
6840
6956
|
};
|
|
6841
6957
|
}
|
|
6842
6958
|
function formatWorkflowPull(result) {
|
|
@@ -6847,6 +6963,8 @@ function formatWorkflowPull(result) {
|
|
|
6847
6963
|
`Workflow version: ${result.versionId}${result.version ? ` (v${result.version})` : " (draft)"}`,
|
|
6848
6964
|
`Written to: ${result.dir}/`,
|
|
6849
6965
|
files,
|
|
6966
|
+
result.kept.length > 0 ? `Kept your edits in:
|
|
6967
|
+
${result.kept.map((s) => ` - ${s}`).join("\n")}` : null,
|
|
6850
6968
|
result.skipped.length > 0 ? `Skipped:
|
|
6851
6969
|
${result.skipped.map((s) => ` - ${s}`).join("\n")}` : null,
|
|
6852
6970
|
result.head ? `Edit the files, then run \`asteroid workflow push\` from ${result.dir}/.` : "This is a pinned version. It is read-only."
|
|
@@ -6854,14 +6972,14 @@ ${result.skipped.map((s) => ` - ${s}`).join("\n")}` : null,
|
|
|
6854
6972
|
}
|
|
6855
6973
|
|
|
6856
6974
|
// src/sync/workflow-push.ts
|
|
6975
|
+
var REBASE_ATTEMPTS = 3;
|
|
6857
6976
|
async function readManifest(sync, dir) {
|
|
6858
|
-
const
|
|
6859
|
-
if (!
|
|
6977
|
+
const manifest = await loadManifest(sync, dir);
|
|
6978
|
+
if (!manifest) {
|
|
6860
6979
|
throw new UsageError(
|
|
6861
6980
|
`no pulled workflow in ${dir}. Run \`asteroid workflow pull <workflow-id>\`, then push from the head folder it writes.`
|
|
6862
6981
|
);
|
|
6863
6982
|
}
|
|
6864
|
-
const manifest = JSON.parse(Buffer.from(bytes).toString("utf8"));
|
|
6865
6983
|
if (!manifest.head) {
|
|
6866
6984
|
throw new UsageError(
|
|
6867
6985
|
`${dir} holds a pinned workflow version, which is read-only. Push from a pulled head.`
|
|
@@ -6891,25 +7009,42 @@ async function localChanges(sync, dir, manifest) {
|
|
|
6891
7009
|
);
|
|
6892
7010
|
return { writes, deletes };
|
|
6893
7011
|
}
|
|
7012
|
+
function conflict(manifest) {
|
|
7013
|
+
return new UsageError(
|
|
7014
|
+
`the workflow head changed since your pull (rev ${manifest.rev}). Run \`asteroid workflow pull ${manifest.workflowId} --keep-edits\` to bring its changes under yours, then push again, or push with --rebase to do both.`
|
|
7015
|
+
);
|
|
7016
|
+
}
|
|
6894
7017
|
async function patch(sync, manifest, writes, deletes) {
|
|
6895
7018
|
try {
|
|
6896
7019
|
await sync.remote.json({
|
|
6897
7020
|
method: "PATCH",
|
|
6898
7021
|
path: `/agents/v2/workflows/${manifest.workflowId}/head/files`,
|
|
6899
7022
|
query: new URLSearchParams(),
|
|
6900
|
-
json: {
|
|
7023
|
+
json: {
|
|
7024
|
+
baseRev: manifest.rev,
|
|
7025
|
+
writes: writes.map(({ path, contentBase64 }) => ({
|
|
7026
|
+
path,
|
|
7027
|
+
contentBase64
|
|
7028
|
+
})),
|
|
7029
|
+
deletes
|
|
7030
|
+
}
|
|
6901
7031
|
});
|
|
7032
|
+
return "applied";
|
|
6902
7033
|
} catch (error) {
|
|
6903
7034
|
if (statusOf(error) !== 409) throw error;
|
|
6904
|
-
|
|
6905
|
-
`the workflow head changed since your pull (rev ${manifest.rev}). Copy your edits aside, run \`asteroid workflow pull ${manifest.workflowId}\`, reapply them, then push again.`
|
|
6906
|
-
);
|
|
7035
|
+
return "conflict";
|
|
6907
7036
|
}
|
|
6908
7037
|
}
|
|
7038
|
+
function headRequest(manifest) {
|
|
7039
|
+
const request = headFilesRequest(manifest.workflowId);
|
|
7040
|
+
if (manifest.variants !== void 0)
|
|
7041
|
+
request.query.set("variants", manifest.variants);
|
|
7042
|
+
if (manifest.maxFileBytes !== void 0)
|
|
7043
|
+
request.query.set("maxFileBytes", String(manifest.maxFileBytes));
|
|
7044
|
+
return request;
|
|
7045
|
+
}
|
|
6909
7046
|
async function refresh(sync, dir, manifest, pushed) {
|
|
6910
|
-
const tree = await sync.remote.json(
|
|
6911
|
-
get(`/agents/v2/workflows/${manifest.workflowId}/head/files`)
|
|
6912
|
-
);
|
|
7047
|
+
const tree = await sync.remote.json(headRequest(manifest));
|
|
6913
7048
|
const files = {};
|
|
6914
7049
|
for (const entry of tree.files) {
|
|
6915
7050
|
if (entry.content !== void 0) {
|
|
@@ -6919,7 +7054,7 @@ async function refresh(sync, dir, manifest, pushed) {
|
|
|
6919
7054
|
await sync.files.write(join(dir, entry.path), bytes);
|
|
6920
7055
|
} else {
|
|
6921
7056
|
const local2 = await sync.files.read(join(dir, entry.path));
|
|
6922
|
-
const hash = local2 ? sha256(local2) : pushed.get(entry.path)
|
|
7057
|
+
const hash = local2 ? sha256(local2) : pushed.get(entry.path);
|
|
6923
7058
|
if (hash) files[entry.path] = hash;
|
|
6924
7059
|
}
|
|
6925
7060
|
}
|
|
@@ -6931,43 +7066,57 @@ async function refresh(sync, dir, manifest, pushed) {
|
|
|
6931
7066
|
await writeManifest(sync, dir, next);
|
|
6932
7067
|
return next;
|
|
6933
7068
|
}
|
|
6934
|
-
async function
|
|
6935
|
-
|
|
7069
|
+
async function rebase(sync, dir, manifest) {
|
|
7070
|
+
await pullWorkflow(sync, {
|
|
7071
|
+
workflowId: manifest.workflowId,
|
|
7072
|
+
request: headRequest(manifest),
|
|
7073
|
+
dir,
|
|
7074
|
+
keepEdits: true,
|
|
7075
|
+
maxFileBytes: manifest.maxFileBytes
|
|
7076
|
+
});
|
|
7077
|
+
return readManifest(sync, dir);
|
|
7078
|
+
}
|
|
7079
|
+
async function pushWorkflow(sync, dir, workflowId, options = {}) {
|
|
7080
|
+
let manifest = await readManifest(sync, dir);
|
|
6936
7081
|
if (workflowId !== void 0 && manifest.workflowId !== workflowId) {
|
|
6937
7082
|
throw new UsageError(
|
|
6938
7083
|
`${dir} holds workflow ${manifest.workflowId}, not ${workflowId}.`
|
|
6939
7084
|
);
|
|
6940
7085
|
}
|
|
6941
|
-
|
|
6942
|
-
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
|
|
6946
|
-
|
|
6947
|
-
|
|
6948
|
-
|
|
6949
|
-
|
|
7086
|
+
let rebasedFrom;
|
|
7087
|
+
for (let attempt = 1; ; attempt++) {
|
|
7088
|
+
const { writes, deletes } = await localChanges(sync, dir, manifest);
|
|
7089
|
+
const result = {
|
|
7090
|
+
workflowId: manifest.workflowId,
|
|
7091
|
+
dir,
|
|
7092
|
+
written: writes.map((w) => w.path),
|
|
7093
|
+
deleted: deletes,
|
|
7094
|
+
...rebasedFrom !== void 0 && { rebasedFrom }
|
|
7095
|
+
};
|
|
7096
|
+
if (writes.length === 0 && deletes.length === 0) {
|
|
7097
|
+
return { ...result, rev: manifest.rev, versionId: manifest.versionId };
|
|
7098
|
+
}
|
|
7099
|
+
if (await patch(sync, manifest, writes, deletes) === "applied") {
|
|
7100
|
+
const next = await refresh(
|
|
7101
|
+
sync,
|
|
7102
|
+
dir,
|
|
7103
|
+
manifest,
|
|
7104
|
+
new Map(writes.map((w) => [w.path, w.hash]))
|
|
7105
|
+
);
|
|
7106
|
+
return { ...result, rev: next.rev, versionId: next.versionId };
|
|
7107
|
+
}
|
|
7108
|
+
if (!options.rebase || attempt >= REBASE_ATTEMPTS) throw conflict(manifest);
|
|
7109
|
+
rebasedFrom ??= manifest.rev;
|
|
7110
|
+
manifest = await rebase(sync, dir, manifest);
|
|
6950
7111
|
}
|
|
6951
|
-
await patch(
|
|
6952
|
-
sync,
|
|
6953
|
-
manifest,
|
|
6954
|
-
writes.map(({ path, contentBase64 }) => ({ path, contentBase64 })),
|
|
6955
|
-
deletes
|
|
6956
|
-
);
|
|
6957
|
-
const next = await refresh(
|
|
6958
|
-
sync,
|
|
6959
|
-
dir,
|
|
6960
|
-
manifest,
|
|
6961
|
-
new Map(writes.map((w) => [w.path, w.hash]))
|
|
6962
|
-
);
|
|
6963
|
-
return { ...result, rev: next.rev, versionId: next.versionId };
|
|
6964
7112
|
}
|
|
6965
7113
|
function formatWorkflowPush(result) {
|
|
7114
|
+
const rebased = result.rebasedFrom !== void 0 ? ` Rebased on the head first: your pull was rev ${result.rebasedFrom}.` : "";
|
|
6966
7115
|
if (result.written.length === 0 && result.deleted.length === 0) {
|
|
6967
|
-
return `Nothing to push: ${result.dir}/ matches the head you pulled (rev ${result.rev})
|
|
7116
|
+
return `Nothing to push: ${result.dir}/ matches the head you pulled (rev ${result.rev}).${rebased}`;
|
|
6968
7117
|
}
|
|
6969
7118
|
return [
|
|
6970
|
-
`Pushed to workflow ${result.workflowId}: ${result.written.length} written, ${result.deleted.length} deleted
|
|
7119
|
+
`Pushed to workflow ${result.workflowId}: ${result.written.length} written, ${result.deleted.length} deleted.${rebased}`,
|
|
6971
7120
|
...result.written.map((path) => ` + ${path}`),
|
|
6972
7121
|
...result.deleted.map((path) => ` - ${path}`),
|
|
6973
7122
|
`Head is now rev ${result.rev} (version ${result.versionId}).`
|
|
@@ -6998,11 +7147,17 @@ function executionOptions(parsed) {
|
|
|
6998
7147
|
dir: dir ? () => dir : void 0
|
|
6999
7148
|
};
|
|
7000
7149
|
}
|
|
7001
|
-
function
|
|
7150
|
+
function withoutFlags(parsed, ...names) {
|
|
7002
7151
|
const flags = new Map(parsed.flags);
|
|
7003
|
-
flags.delete(name);
|
|
7152
|
+
for (const name of names) flags.delete(name);
|
|
7004
7153
|
return { ...parsed, flags };
|
|
7005
7154
|
}
|
|
7155
|
+
function runsLocally(command, parsed) {
|
|
7156
|
+
if (command.local) return true;
|
|
7157
|
+
if (command.files === "pull") return !parsed.json;
|
|
7158
|
+
if (command.files === "push") return parsed.body === void 0;
|
|
7159
|
+
return false;
|
|
7160
|
+
}
|
|
7006
7161
|
var PARTIAL = ["paths", "variants"];
|
|
7007
7162
|
function rejectPartial(parsed) {
|
|
7008
7163
|
const used = PARTIAL.filter((name) => parsed.flags.has(name));
|
|
@@ -7015,11 +7170,16 @@ async function pull(command, parsed, sync, deps) {
|
|
|
7015
7170
|
rejectPartial(parsed);
|
|
7016
7171
|
const result = await pullWorkflow(sync, {
|
|
7017
7172
|
workflowId: parsed.positionals[0],
|
|
7018
|
-
request: await buildRequest(
|
|
7019
|
-
|
|
7020
|
-
|
|
7173
|
+
request: await buildRequest(
|
|
7174
|
+
command,
|
|
7175
|
+
withoutFlags(parsed, "out", "keep-edits"),
|
|
7176
|
+
{
|
|
7177
|
+
io: deps.io
|
|
7178
|
+
}
|
|
7179
|
+
),
|
|
7021
7180
|
dir: flag(parsed, "out"),
|
|
7022
7181
|
root: deps.root ?? "",
|
|
7182
|
+
keepEdits: flag(parsed, "keep-edits") === "true",
|
|
7023
7183
|
...deps.workflowPull
|
|
7024
7184
|
});
|
|
7025
7185
|
return { result, text: formatWorkflowPull(result) };
|
|
@@ -7030,7 +7190,8 @@ async function execute(command, parsed, sync, deps) {
|
|
|
7030
7190
|
const result2 = await pushWorkflow(
|
|
7031
7191
|
sync,
|
|
7032
7192
|
flag(parsed, "from") ?? ".",
|
|
7033
|
-
parsed.positionals[0] || void 0
|
|
7193
|
+
parsed.positionals[0] || void 0,
|
|
7194
|
+
{ rebase: flag(parsed, "rebase") === "true" }
|
|
7034
7195
|
);
|
|
7035
7196
|
return { result: result2, text: formatWorkflowPush(result2) };
|
|
7036
7197
|
}
|
|
@@ -7064,14 +7225,20 @@ export {
|
|
|
7064
7225
|
formatExecutionPull,
|
|
7065
7226
|
formatWorkflowPull,
|
|
7066
7227
|
formatWorkflowPush,
|
|
7228
|
+
headFilesRequest,
|
|
7067
7229
|
leadingWords,
|
|
7230
|
+
loadManifest,
|
|
7068
7231
|
matchCommand,
|
|
7069
7232
|
matchesGlob,
|
|
7070
7233
|
parseInvocation,
|
|
7234
|
+
pullDir,
|
|
7071
7235
|
pullExecution,
|
|
7072
7236
|
pullWorkflow,
|
|
7073
7237
|
pushWorkflow,
|
|
7238
|
+
readManifest,
|
|
7074
7239
|
render,
|
|
7075
7240
|
runSync,
|
|
7076
|
-
|
|
7241
|
+
runsLocally,
|
|
7242
|
+
withSyncCommands,
|
|
7243
|
+
writeManifest
|
|
7077
7244
|
};
|