@asteroidai/cli 1.9.98 → 1.9.103
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 +367 -81
- package/dist/core.js +379 -81
- package/package.json +1 -1
package/dist/asteroid.js
CHANGED
|
@@ -25,6 +25,61 @@ var COMMANDS = [
|
|
|
25
25
|
readOnly: false,
|
|
26
26
|
destructive: true
|
|
27
27
|
},
|
|
28
|
+
{
|
|
29
|
+
command: "batch create",
|
|
30
|
+
tool: "execution_batch_create",
|
|
31
|
+
title: "Create execution batch",
|
|
32
|
+
description: 'Queue one execution per row of `items` for `workflowId`, starting at `startAt` (ISO 8601; now or earlier starts at once). `config` paces the runs: `{type: "maxConcurrent", maxConcurrent}` or `{type: "timeBatching", batchSize, batchInterval}` (seconds). Each item takes `inputs` (see `workflow_version_inputs_get`), optional `profileId` or `profileGroupId`, `metadata`, and `workflowVersionId` to pin a version. Check the same body first with `execution_batch_validate`. Returns the batch; follow it with `execution_batch_get`, and its runs with `executions_list` filtered by `batchId`.',
|
|
33
|
+
method: "POST",
|
|
34
|
+
path: "/agents/v2/execution-batches",
|
|
35
|
+
args: [],
|
|
36
|
+
query: [],
|
|
37
|
+
binary: false,
|
|
38
|
+
readOnly: false,
|
|
39
|
+
destructive: false,
|
|
40
|
+
body: {
|
|
41
|
+
required: true,
|
|
42
|
+
contentType: "application/json",
|
|
43
|
+
fields: [
|
|
44
|
+
{
|
|
45
|
+
name: "workflowId",
|
|
46
|
+
flag: "workflow-id",
|
|
47
|
+
type: "string",
|
|
48
|
+
required: false,
|
|
49
|
+
description: "Workflow ID for this batch. Set this or agentId."
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: "name",
|
|
53
|
+
flag: "name",
|
|
54
|
+
type: "string",
|
|
55
|
+
required: true
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: "startAt",
|
|
59
|
+
flag: "start-at",
|
|
60
|
+
type: "string",
|
|
61
|
+
required: true,
|
|
62
|
+
description: "When to start triggering executions"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: "config",
|
|
66
|
+
flag: "config",
|
|
67
|
+
type: "json",
|
|
68
|
+
required: true,
|
|
69
|
+
description: "Discriminated union of batch configuration types (discriminated by type)"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: "items",
|
|
73
|
+
flag: "items",
|
|
74
|
+
type: "json",
|
|
75
|
+
required: true
|
|
76
|
+
}
|
|
77
|
+
],
|
|
78
|
+
files: [],
|
|
79
|
+
credentialOrganization: false
|
|
80
|
+
},
|
|
81
|
+
capability: "run_execution"
|
|
82
|
+
},
|
|
28
83
|
{
|
|
29
84
|
command: "batch get",
|
|
30
85
|
tool: "execution_batch_get",
|
|
@@ -127,6 +182,60 @@ var COMMANDS = [
|
|
|
127
182
|
destructive: false,
|
|
128
183
|
capability: "run_execution"
|
|
129
184
|
},
|
|
185
|
+
{
|
|
186
|
+
command: "batch validate",
|
|
187
|
+
tool: "execution_batch_validate",
|
|
188
|
+
title: "Validate execution batch",
|
|
189
|
+
description: "Check an `execution_batch_create` body without creating anything. Returns `valid`, `itemCount`, and `issues`: each row (by its 0-based `index` in `items`) whose inputs would fail the input schema of the version it runs. A problem with the whole batch, such as an empty `items` or a bad `config`, returns the same error create would.",
|
|
190
|
+
method: "POST",
|
|
191
|
+
path: "/agents/v2/execution-batches/validate",
|
|
192
|
+
args: [],
|
|
193
|
+
query: [],
|
|
194
|
+
binary: false,
|
|
195
|
+
readOnly: true,
|
|
196
|
+
destructive: false,
|
|
197
|
+
body: {
|
|
198
|
+
required: true,
|
|
199
|
+
contentType: "application/json",
|
|
200
|
+
fields: [
|
|
201
|
+
{
|
|
202
|
+
name: "workflowId",
|
|
203
|
+
flag: "workflow-id",
|
|
204
|
+
type: "string",
|
|
205
|
+
required: false,
|
|
206
|
+
description: "Workflow ID for this batch. Set this or agentId."
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
name: "name",
|
|
210
|
+
flag: "name",
|
|
211
|
+
type: "string",
|
|
212
|
+
required: true
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
name: "startAt",
|
|
216
|
+
flag: "start-at",
|
|
217
|
+
type: "string",
|
|
218
|
+
required: true,
|
|
219
|
+
description: "When to start triggering executions"
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
name: "config",
|
|
223
|
+
flag: "config",
|
|
224
|
+
type: "json",
|
|
225
|
+
required: true,
|
|
226
|
+
description: "Discriminated union of batch configuration types (discriminated by type)"
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
name: "items",
|
|
230
|
+
flag: "items",
|
|
231
|
+
type: "json",
|
|
232
|
+
required: true
|
|
233
|
+
}
|
|
234
|
+
],
|
|
235
|
+
files: [],
|
|
236
|
+
credentialOrganization: false
|
|
237
|
+
}
|
|
238
|
+
},
|
|
130
239
|
{
|
|
131
240
|
command: "context",
|
|
132
241
|
tool: "get_context",
|
|
@@ -1336,6 +1445,13 @@ var COMMANDS = [
|
|
|
1336
1445
|
type: "string[]",
|
|
1337
1446
|
required: false,
|
|
1338
1447
|
description: "Filter by login profile IDs (can specify multiple, there is an 'OR' condition applied to these)"
|
|
1448
|
+
},
|
|
1449
|
+
{
|
|
1450
|
+
name: "batchId",
|
|
1451
|
+
flag: "batch-id",
|
|
1452
|
+
type: "string",
|
|
1453
|
+
required: false,
|
|
1454
|
+
description: "Only executions started by this batch, an ID from `execution_batches_list`. Rows not yet triggered have no execution yet: list them with `scheduled_executions_list` and `batchId`."
|
|
1339
1455
|
}
|
|
1340
1456
|
],
|
|
1341
1457
|
binary: false,
|
|
@@ -1610,6 +1726,13 @@ var COMMANDS = [
|
|
|
1610
1726
|
type: "string[]",
|
|
1611
1727
|
required: false,
|
|
1612
1728
|
description: "Filter by login profile IDs (can specify multiple, there is an 'OR' condition applied to these)"
|
|
1729
|
+
},
|
|
1730
|
+
{
|
|
1731
|
+
name: "batchId",
|
|
1732
|
+
flag: "batch-id",
|
|
1733
|
+
type: "string",
|
|
1734
|
+
required: false,
|
|
1735
|
+
description: "Only executions started by this batch, an ID from `execution_batches_list`. Rows not yet triggered have no execution yet: list them with `scheduled_executions_list` and `batchId`."
|
|
1613
1736
|
}
|
|
1614
1737
|
],
|
|
1615
1738
|
binary: false,
|
|
@@ -1907,7 +2030,7 @@ var COMMANDS = [
|
|
|
1907
2030
|
command: "portal-skills add",
|
|
1908
2031
|
tool: "portal_skills_add",
|
|
1909
2032
|
title: "Add portal skills",
|
|
1910
|
-
description: "Add a portal's skills to an organization, by
|
|
2033
|
+
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
2034
|
method: "POST",
|
|
1912
2035
|
path: "/agents/v2/portal-skills/{portalId}",
|
|
1913
2036
|
args: [
|
|
@@ -1923,6 +2046,27 @@ var COMMANDS = [
|
|
|
1923
2046
|
readOnly: false,
|
|
1924
2047
|
destructive: false
|
|
1925
2048
|
},
|
|
2049
|
+
{
|
|
2050
|
+
command: "portal-skills list",
|
|
2051
|
+
tool: "portal_skills_list",
|
|
2052
|
+
title: "List portal skills",
|
|
2053
|
+
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`.",
|
|
2054
|
+
method: "GET",
|
|
2055
|
+
path: "/astro/v2/portal-skills",
|
|
2056
|
+
args: [],
|
|
2057
|
+
query: [
|
|
2058
|
+
{
|
|
2059
|
+
name: "query",
|
|
2060
|
+
flag: "query",
|
|
2061
|
+
type: "string",
|
|
2062
|
+
required: false,
|
|
2063
|
+
description: "A portal name or task, at least three characters. Omit to list every portal with skills."
|
|
2064
|
+
}
|
|
2065
|
+
],
|
|
2066
|
+
binary: false,
|
|
2067
|
+
readOnly: true,
|
|
2068
|
+
destructive: false
|
|
2069
|
+
},
|
|
1926
2070
|
{
|
|
1927
2071
|
command: "profile clear-cache",
|
|
1928
2072
|
tool: "profile_clear_browser_cache",
|
|
@@ -4441,7 +4585,7 @@ var COMMANDS = [
|
|
|
4441
4585
|
command: "workflow head publish",
|
|
4442
4586
|
tool: "workflow_head_publish",
|
|
4443
4587
|
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.",
|
|
4588
|
+
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
4589
|
method: "POST",
|
|
4446
4590
|
path: "/agents/v2/workflows/{workflowId}/head/publish",
|
|
4447
4591
|
args: [
|
|
@@ -5378,7 +5522,7 @@ var COMMANDS = [
|
|
|
5378
5522
|
command: "workflow version publish",
|
|
5379
5523
|
tool: "workflow_version_publish",
|
|
5380
5524
|
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`.",
|
|
5525
|
+
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
5526
|
method: "POST",
|
|
5383
5527
|
path: "/agents/v2/workflows/{workflowId}/versions/{versionId}/publish",
|
|
5384
5528
|
args: [
|
|
@@ -6033,16 +6177,26 @@ function leafOptions(leaf, command) {
|
|
|
6033
6177
|
for (const file of command.body?.files ?? []) {
|
|
6034
6178
|
leaf.option(`--${file} <path>`, "File to upload. Repeatable.", collect);
|
|
6035
6179
|
}
|
|
6036
|
-
if (command.files === "pull")
|
|
6180
|
+
if (command.files === "pull") {
|
|
6037
6181
|
leaf.option(
|
|
6038
6182
|
"--out <dir>",
|
|
6039
6183
|
"Write here instead of ./<workflow-name>/head (a version: ./<workflow-name>/.versions/...). --json prints the file listing instead."
|
|
6040
6184
|
);
|
|
6041
|
-
|
|
6185
|
+
leaf.option(
|
|
6186
|
+
"--keep-edits",
|
|
6187
|
+
"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."
|
|
6188
|
+
);
|
|
6189
|
+
}
|
|
6190
|
+
if (command.files === "push") {
|
|
6042
6191
|
leaf.option(
|
|
6043
6192
|
"--from <dir>",
|
|
6044
6193
|
"The pulled head folder to push. Defaults to the current folder."
|
|
6045
6194
|
);
|
|
6195
|
+
leaf.option(
|
|
6196
|
+
"--rebase",
|
|
6197
|
+
"When the head moved since your pull, pull it with --keep-edits and push again, twice at most."
|
|
6198
|
+
);
|
|
6199
|
+
}
|
|
6046
6200
|
}
|
|
6047
6201
|
function toParsed(leaf, positionals, dotted2) {
|
|
6048
6202
|
const globals = leaf.optsWithGlobals();
|
|
@@ -6649,7 +6803,7 @@ var ApiError = class extends Error {
|
|
|
6649
6803
|
status;
|
|
6650
6804
|
body;
|
|
6651
6805
|
};
|
|
6652
|
-
var VERSION = "1.9.
|
|
6806
|
+
var VERSION = "1.9.103";
|
|
6653
6807
|
function problem(status, body, fallback) {
|
|
6654
6808
|
if (body && typeof body === "object") {
|
|
6655
6809
|
const record2 = body;
|
|
@@ -7749,6 +7903,18 @@ function nodeFiles(cwd) {
|
|
|
7749
7903
|
};
|
|
7750
7904
|
}
|
|
7751
7905
|
|
|
7906
|
+
// src/sync/manifest.ts
|
|
7907
|
+
async function loadManifest(sync, dir) {
|
|
7908
|
+
const bytes = await sync.files.read(join3(dir, MANIFEST));
|
|
7909
|
+
if (!bytes) return void 0;
|
|
7910
|
+
return JSON.parse(Buffer.from(bytes).toString("utf8"));
|
|
7911
|
+
}
|
|
7912
|
+
async function writeManifest(sync, dir, manifest) {
|
|
7913
|
+
const text3 = `${JSON.stringify(manifest, null, 2)}
|
|
7914
|
+
`;
|
|
7915
|
+
await sync.files.write(join3(dir, MANIFEST), Buffer.from(text3, "utf8"));
|
|
7916
|
+
}
|
|
7917
|
+
|
|
7752
7918
|
// src/sync/workflow-pull.ts
|
|
7753
7919
|
var CONCURRENCY = 16;
|
|
7754
7920
|
function pullDir(root, tree, head) {
|
|
@@ -7756,6 +7922,9 @@ function pullDir(root, tree, head) {
|
|
|
7756
7922
|
if (head) return join3(base, "head");
|
|
7757
7923
|
return join3(base, ".versions", workflowDirName(tree.versionId, tree.version));
|
|
7758
7924
|
}
|
|
7925
|
+
function headFilesRequest(workflowId) {
|
|
7926
|
+
return get(`/agents/v2/workflows/${workflowId}/head/files`);
|
|
7927
|
+
}
|
|
7759
7928
|
async function agentFileBytes(sync, workflowId, tree, entry) {
|
|
7760
7929
|
const path = `/agents/v2/workflows/${workflowId}/versions/${tree.versionId}/files/${entry.fileId}/signed-url`;
|
|
7761
7930
|
const { url } = await sync.remote.json(get(path));
|
|
@@ -7765,32 +7934,105 @@ async function entryBytes(sync, workflowId, tree, entry) {
|
|
|
7765
7934
|
if (entry.contentBase64 !== void 0)
|
|
7766
7935
|
return Buffer.from(entry.contentBase64, "base64");
|
|
7767
7936
|
if (entry.content !== void 0) return Buffer.from(entry.content, "utf8");
|
|
7937
|
+
if (entry.storageObjectKey && sync.remote.object)
|
|
7938
|
+
return sync.remote.object(entry.storageObjectKey);
|
|
7768
7939
|
if (!entry.fileId) return void 0;
|
|
7769
7940
|
return agentFileBytes(sync, workflowId, tree, entry);
|
|
7770
7941
|
}
|
|
7771
|
-
|
|
7772
|
-
|
|
7773
|
-
|
|
7774
|
-
|
|
7775
|
-
|
|
7942
|
+
function inlineBytes(entry) {
|
|
7943
|
+
if (entry.contentBase64 !== void 0)
|
|
7944
|
+
return Buffer.from(entry.contentBase64, "base64");
|
|
7945
|
+
if (entry.content !== void 0) return Buffer.from(entry.content, "utf8");
|
|
7946
|
+
return void 0;
|
|
7947
|
+
}
|
|
7948
|
+
function wantedEntries(options, tree, skipped) {
|
|
7949
|
+
return tree.files.filter((entry) => {
|
|
7776
7950
|
if (!isContained(entry.path)) skipped.push(`${entry.path} (unsafe path)`);
|
|
7777
7951
|
else if (options.maxFileBytes !== void 0 && entry.size > options.maxFileBytes)
|
|
7778
7952
|
skipped.push(`${entry.path} (${entry.size} bytes, over the size limit)`);
|
|
7779
7953
|
else return true;
|
|
7780
7954
|
return false;
|
|
7781
7955
|
});
|
|
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
|
-
);
|
|
7956
|
+
}
|
|
7957
|
+
async function inBatches(items, each) {
|
|
7958
|
+
for (let i = 0; i < items.length; i += CONCURRENCY) {
|
|
7959
|
+
await Promise.all(items.slice(i, i + CONCURRENCY).map(each));
|
|
7792
7960
|
}
|
|
7793
|
-
|
|
7961
|
+
}
|
|
7962
|
+
async function writeEntries(sync, options, tree, dir) {
|
|
7963
|
+
const hashes = {};
|
|
7964
|
+
const skipped = [];
|
|
7965
|
+
let shared = 0;
|
|
7966
|
+
let written = 0;
|
|
7967
|
+
await inBatches(wantedEntries(options, tree, skipped), async (entry) => {
|
|
7968
|
+
const bytes = await entryBytes(sync, options.workflowId, tree, entry);
|
|
7969
|
+
if (!bytes) return void skipped.push(`${entry.path} (no content)`);
|
|
7970
|
+
await sync.files.write(join3(dir, entry.path), bytes);
|
|
7971
|
+
hashes[entry.path] = sha256(bytes);
|
|
7972
|
+
written++;
|
|
7973
|
+
if (entry.kind === "agent_file") shared++;
|
|
7974
|
+
});
|
|
7975
|
+
return { hashes, skipped, shared, kept: [], written };
|
|
7976
|
+
}
|
|
7977
|
+
async function mergeEntry(sync, options, tree, dir, previous, entry, out) {
|
|
7978
|
+
const local2 = await sync.files.read(join3(dir, entry.path));
|
|
7979
|
+
const localHash = local2 ? sha256(local2) : void 0;
|
|
7980
|
+
const baseline = previous.files[entry.path];
|
|
7981
|
+
const edited = baseline === void 0 ? local2 !== void 0 : localHash !== baseline;
|
|
7982
|
+
let bytes = inlineBytes(entry);
|
|
7983
|
+
let serverHash = bytes ? sha256(bytes) : entry.checksum;
|
|
7984
|
+
if (edited) {
|
|
7985
|
+
if (serverHash === void 0) {
|
|
7986
|
+
bytes = await entryBytes(sync, options.workflowId, tree, entry);
|
|
7987
|
+
if (!bytes) return void out.skipped.push(`${entry.path} (no content)`);
|
|
7988
|
+
serverHash = sha256(bytes);
|
|
7989
|
+
}
|
|
7990
|
+
out.hashes[entry.path] = serverHash;
|
|
7991
|
+
out.kept.push(entry.path);
|
|
7992
|
+
return;
|
|
7993
|
+
}
|
|
7994
|
+
if (serverHash !== void 0 && serverHash === localHash) {
|
|
7995
|
+
out.hashes[entry.path] = serverHash;
|
|
7996
|
+
return;
|
|
7997
|
+
}
|
|
7998
|
+
bytes ??= await entryBytes(sync, options.workflowId, tree, entry);
|
|
7999
|
+
if (!bytes) return void out.skipped.push(`${entry.path} (no content)`);
|
|
8000
|
+
serverHash = sha256(bytes);
|
|
8001
|
+
if (serverHash !== localHash) {
|
|
8002
|
+
await sync.files.write(join3(dir, entry.path), bytes);
|
|
8003
|
+
out.written++;
|
|
8004
|
+
if (entry.kind === "agent_file") out.shared++;
|
|
8005
|
+
}
|
|
8006
|
+
out.hashes[entry.path] = serverHash;
|
|
8007
|
+
}
|
|
8008
|
+
async function mergeEntries(sync, options, tree, dir, previous) {
|
|
8009
|
+
const out = {
|
|
8010
|
+
hashes: {},
|
|
8011
|
+
skipped: [],
|
|
8012
|
+
shared: 0,
|
|
8013
|
+
kept: [],
|
|
8014
|
+
written: 0
|
|
8015
|
+
};
|
|
8016
|
+
const wanted = wantedEntries(options, tree, out.skipped);
|
|
8017
|
+
await inBatches(
|
|
8018
|
+
wanted,
|
|
8019
|
+
(entry) => mergeEntry(sync, options, tree, dir, previous, entry, out)
|
|
8020
|
+
);
|
|
8021
|
+
const remote = new Set(tree.files.map((entry) => entry.path));
|
|
8022
|
+
for (const path of Object.keys(previous.files)) {
|
|
8023
|
+
if (path in out.hashes) continue;
|
|
8024
|
+
if (remote.has(path)) {
|
|
8025
|
+
out.hashes[path] = previous.files[path];
|
|
8026
|
+
continue;
|
|
8027
|
+
}
|
|
8028
|
+
const local2 = await sync.files.read(join3(dir, path));
|
|
8029
|
+
if (!local2) continue;
|
|
8030
|
+
if (sha256(local2) === previous.files[path])
|
|
8031
|
+
await sync.files.remove(join3(dir, path));
|
|
8032
|
+
else out.kept.push(path);
|
|
8033
|
+
}
|
|
8034
|
+
out.kept.sort();
|
|
8035
|
+
return out;
|
|
7794
8036
|
}
|
|
7795
8037
|
async function removeStale(sync, dir, keep) {
|
|
7796
8038
|
for (const path of await sync.files.list(dir)) {
|
|
@@ -7798,11 +8040,6 @@ async function removeStale(sync, dir, keep) {
|
|
|
7798
8040
|
await sync.files.remove(join3(dir, path));
|
|
7799
8041
|
}
|
|
7800
8042
|
}
|
|
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
8043
|
async function pullWorkflow(sync, options) {
|
|
7807
8044
|
const request = {
|
|
7808
8045
|
...options.request,
|
|
@@ -7812,20 +8049,25 @@ async function pullWorkflow(sync, options) {
|
|
|
7812
8049
|
const tree = await sync.remote.json(request);
|
|
7813
8050
|
const head = request.path.endsWith("/head/files");
|
|
7814
8051
|
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)));
|
|
8052
|
+
const previous = options.keepEdits ? await loadManifest(sync, dir) : void 0;
|
|
8053
|
+
const written = previous ? await mergeEntries(sync, options, tree, dir, previous) : await writeEntries(sync, options, tree, dir);
|
|
8054
|
+
if (!previous)
|
|
8055
|
+
await removeStale(sync, dir, new Set(Object.keys(written.hashes)));
|
|
7822
8056
|
if (options.manifest !== false) {
|
|
8057
|
+
const variants = request.query.get("variants") ?? void 0;
|
|
7823
8058
|
await writeManifest(sync, dir, {
|
|
7824
8059
|
workflowId: options.workflowId,
|
|
7825
8060
|
versionId: tree.versionId,
|
|
7826
8061
|
head,
|
|
7827
8062
|
rev: tree.rev,
|
|
7828
|
-
files: hashes
|
|
8063
|
+
files: written.hashes,
|
|
8064
|
+
...variants !== void 0 && { variants },
|
|
8065
|
+
...options.maxFileBytes !== void 0 && {
|
|
8066
|
+
maxFileBytes: options.maxFileBytes
|
|
8067
|
+
},
|
|
8068
|
+
...options.maxTotalBytes !== void 0 && {
|
|
8069
|
+
maxTotalBytes: options.maxTotalBytes
|
|
8070
|
+
}
|
|
7829
8071
|
});
|
|
7830
8072
|
}
|
|
7831
8073
|
return {
|
|
@@ -7836,9 +8078,11 @@ async function pullWorkflow(sync, options) {
|
|
|
7836
8078
|
rev: tree.rev,
|
|
7837
8079
|
dir,
|
|
7838
8080
|
head,
|
|
7839
|
-
written:
|
|
7840
|
-
shared,
|
|
7841
|
-
skipped
|
|
8081
|
+
written: written.written,
|
|
8082
|
+
shared: written.shared,
|
|
8083
|
+
skipped: written.skipped,
|
|
8084
|
+
kept: written.kept,
|
|
8085
|
+
...tree.variantKeys && { variantKeys: tree.variantKeys }
|
|
7842
8086
|
};
|
|
7843
8087
|
}
|
|
7844
8088
|
function formatWorkflowPull(result) {
|
|
@@ -7849,6 +8093,8 @@ function formatWorkflowPull(result) {
|
|
|
7849
8093
|
`Workflow version: ${result.versionId}${result.version ? ` (v${result.version})` : " (draft)"}`,
|
|
7850
8094
|
`Written to: ${result.dir}/`,
|
|
7851
8095
|
files,
|
|
8096
|
+
result.kept.length > 0 ? `Kept your edits in:
|
|
8097
|
+
${result.kept.map((s) => ` - ${s}`).join("\n")}` : null,
|
|
7852
8098
|
result.skipped.length > 0 ? `Skipped:
|
|
7853
8099
|
${result.skipped.map((s) => ` - ${s}`).join("\n")}` : null,
|
|
7854
8100
|
result.head ? `Edit the files, then run \`asteroid workflow push\` from ${result.dir}/.` : "This is a pinned version. It is read-only."
|
|
@@ -7856,14 +8102,14 @@ ${result.skipped.map((s) => ` - ${s}`).join("\n")}` : null,
|
|
|
7856
8102
|
}
|
|
7857
8103
|
|
|
7858
8104
|
// src/sync/workflow-push.ts
|
|
8105
|
+
var REBASE_ATTEMPTS = 3;
|
|
7859
8106
|
async function readManifest(sync, dir) {
|
|
7860
|
-
const
|
|
7861
|
-
if (!
|
|
8107
|
+
const manifest = await loadManifest(sync, dir);
|
|
8108
|
+
if (!manifest) {
|
|
7862
8109
|
throw new UsageError(
|
|
7863
8110
|
`no pulled workflow in ${dir}. Run \`asteroid workflow pull <workflow-id>\`, then push from the head folder it writes.`
|
|
7864
8111
|
);
|
|
7865
8112
|
}
|
|
7866
|
-
const manifest = JSON.parse(Buffer.from(bytes).toString("utf8"));
|
|
7867
8113
|
if (!manifest.head) {
|
|
7868
8114
|
throw new UsageError(
|
|
7869
8115
|
`${dir} holds a pinned workflow version, which is read-only. Push from a pulled head.`
|
|
@@ -7893,25 +8139,44 @@ async function localChanges(sync, dir, manifest) {
|
|
|
7893
8139
|
);
|
|
7894
8140
|
return { writes, deletes };
|
|
7895
8141
|
}
|
|
8142
|
+
function conflict(manifest) {
|
|
8143
|
+
return new UsageError(
|
|
8144
|
+
`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.`
|
|
8145
|
+
);
|
|
8146
|
+
}
|
|
7896
8147
|
async function patch(sync, manifest, writes, deletes) {
|
|
7897
8148
|
try {
|
|
7898
8149
|
await sync.remote.json({
|
|
7899
8150
|
method: "PATCH",
|
|
7900
8151
|
path: `/agents/v2/workflows/${manifest.workflowId}/head/files`,
|
|
7901
8152
|
query: new URLSearchParams(),
|
|
7902
|
-
json: {
|
|
8153
|
+
json: {
|
|
8154
|
+
baseRev: manifest.rev,
|
|
8155
|
+
writes: writes.map(({ path, contentBase64 }) => ({
|
|
8156
|
+
path,
|
|
8157
|
+
contentBase64
|
|
8158
|
+
})),
|
|
8159
|
+
deletes
|
|
8160
|
+
}
|
|
7903
8161
|
});
|
|
8162
|
+
return "applied";
|
|
7904
8163
|
} catch (error) {
|
|
7905
8164
|
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
|
-
);
|
|
8165
|
+
return "conflict";
|
|
7909
8166
|
}
|
|
7910
8167
|
}
|
|
8168
|
+
function headRequest(manifest) {
|
|
8169
|
+
const request = headFilesRequest(manifest.workflowId);
|
|
8170
|
+
if (manifest.variants !== void 0)
|
|
8171
|
+
request.query.set("variants", manifest.variants);
|
|
8172
|
+
if (manifest.maxFileBytes !== void 0)
|
|
8173
|
+
request.query.set("maxFileBytes", String(manifest.maxFileBytes));
|
|
8174
|
+
if (manifest.maxTotalBytes !== void 0)
|
|
8175
|
+
request.query.set("maxTotalBytes", String(manifest.maxTotalBytes));
|
|
8176
|
+
return request;
|
|
8177
|
+
}
|
|
7911
8178
|
async function refresh2(sync, dir, manifest, pushed) {
|
|
7912
|
-
const tree = await sync.remote.json(
|
|
7913
|
-
get(`/agents/v2/workflows/${manifest.workflowId}/head/files`)
|
|
7914
|
-
);
|
|
8179
|
+
const tree = await sync.remote.json(headRequest(manifest));
|
|
7915
8180
|
const files = {};
|
|
7916
8181
|
for (const entry of tree.files) {
|
|
7917
8182
|
if (entry.content !== void 0) {
|
|
@@ -7921,7 +8186,7 @@ async function refresh2(sync, dir, manifest, pushed) {
|
|
|
7921
8186
|
await sync.files.write(join3(dir, entry.path), bytes);
|
|
7922
8187
|
} else {
|
|
7923
8188
|
const local2 = await sync.files.read(join3(dir, entry.path));
|
|
7924
|
-
const hash = local2 ? sha256(local2) : pushed.get(entry.path)
|
|
8189
|
+
const hash = local2 ? sha256(local2) : pushed.get(entry.path);
|
|
7925
8190
|
if (hash) files[entry.path] = hash;
|
|
7926
8191
|
}
|
|
7927
8192
|
}
|
|
@@ -7933,43 +8198,58 @@ async function refresh2(sync, dir, manifest, pushed) {
|
|
|
7933
8198
|
await writeManifest(sync, dir, next);
|
|
7934
8199
|
return next;
|
|
7935
8200
|
}
|
|
7936
|
-
async function
|
|
7937
|
-
|
|
8201
|
+
async function rebase(sync, dir, manifest) {
|
|
8202
|
+
await pullWorkflow(sync, {
|
|
8203
|
+
workflowId: manifest.workflowId,
|
|
8204
|
+
request: headRequest(manifest),
|
|
8205
|
+
dir,
|
|
8206
|
+
keepEdits: true,
|
|
8207
|
+
maxFileBytes: manifest.maxFileBytes,
|
|
8208
|
+
maxTotalBytes: manifest.maxTotalBytes
|
|
8209
|
+
});
|
|
8210
|
+
return readManifest(sync, dir);
|
|
8211
|
+
}
|
|
8212
|
+
async function pushWorkflow(sync, dir, workflowId, options = {}) {
|
|
8213
|
+
let manifest = await readManifest(sync, dir);
|
|
7938
8214
|
if (workflowId !== void 0 && manifest.workflowId !== workflowId) {
|
|
7939
8215
|
throw new UsageError(
|
|
7940
8216
|
`${dir} holds workflow ${manifest.workflowId}, not ${workflowId}.`
|
|
7941
8217
|
);
|
|
7942
8218
|
}
|
|
7943
|
-
|
|
7944
|
-
|
|
7945
|
-
|
|
7946
|
-
|
|
7947
|
-
|
|
7948
|
-
|
|
7949
|
-
|
|
7950
|
-
|
|
7951
|
-
|
|
8219
|
+
let rebasedFrom;
|
|
8220
|
+
for (let attempt = 1; ; attempt++) {
|
|
8221
|
+
const { writes, deletes } = await localChanges(sync, dir, manifest);
|
|
8222
|
+
const result = {
|
|
8223
|
+
workflowId: manifest.workflowId,
|
|
8224
|
+
dir,
|
|
8225
|
+
written: writes.map((w) => w.path),
|
|
8226
|
+
deleted: deletes,
|
|
8227
|
+
...rebasedFrom !== void 0 && { rebasedFrom }
|
|
8228
|
+
};
|
|
8229
|
+
if (writes.length === 0 && deletes.length === 0) {
|
|
8230
|
+
return { ...result, rev: manifest.rev, versionId: manifest.versionId };
|
|
8231
|
+
}
|
|
8232
|
+
if (await patch(sync, manifest, writes, deletes) === "applied") {
|
|
8233
|
+
const next = await refresh2(
|
|
8234
|
+
sync,
|
|
8235
|
+
dir,
|
|
8236
|
+
manifest,
|
|
8237
|
+
new Map(writes.map((w) => [w.path, w.hash]))
|
|
8238
|
+
);
|
|
8239
|
+
return { ...result, rev: next.rev, versionId: next.versionId };
|
|
8240
|
+
}
|
|
8241
|
+
if (!options.rebase || attempt >= REBASE_ATTEMPTS) throw conflict(manifest);
|
|
8242
|
+
rebasedFrom ??= manifest.rev;
|
|
8243
|
+
manifest = await rebase(sync, dir, manifest);
|
|
7952
8244
|
}
|
|
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
8245
|
}
|
|
7967
8246
|
function formatWorkflowPush(result) {
|
|
8247
|
+
const rebased = result.rebasedFrom !== void 0 ? ` Rebased on the head first: your pull was rev ${result.rebasedFrom}.` : "";
|
|
7968
8248
|
if (result.written.length === 0 && result.deleted.length === 0) {
|
|
7969
|
-
return `Nothing to push: ${result.dir}/ matches the head you pulled (rev ${result.rev})
|
|
8249
|
+
return `Nothing to push: ${result.dir}/ matches the head you pulled (rev ${result.rev}).${rebased}`;
|
|
7970
8250
|
}
|
|
7971
8251
|
return [
|
|
7972
|
-
`Pushed to workflow ${result.workflowId}: ${result.written.length} written, ${result.deleted.length} deleted
|
|
8252
|
+
`Pushed to workflow ${result.workflowId}: ${result.written.length} written, ${result.deleted.length} deleted.${rebased}`,
|
|
7973
8253
|
...result.written.map((path) => ` + ${path}`),
|
|
7974
8254
|
...result.deleted.map((path) => ` - ${path}`),
|
|
7975
8255
|
`Head is now rev ${result.rev} (version ${result.versionId}).`
|
|
@@ -8000,9 +8280,9 @@ function executionOptions(parsed) {
|
|
|
8000
8280
|
dir: dir ? () => dir : void 0
|
|
8001
8281
|
};
|
|
8002
8282
|
}
|
|
8003
|
-
function
|
|
8283
|
+
function withoutFlags(parsed, ...names) {
|
|
8004
8284
|
const flags = new Map(parsed.flags);
|
|
8005
|
-
flags.delete(name);
|
|
8285
|
+
for (const name of names) flags.delete(name);
|
|
8006
8286
|
return { ...parsed, flags };
|
|
8007
8287
|
}
|
|
8008
8288
|
function runsLocally(command, parsed) {
|
|
@@ -8023,11 +8303,16 @@ async function pull(command, parsed, sync, deps) {
|
|
|
8023
8303
|
rejectPartial(parsed);
|
|
8024
8304
|
const result = await pullWorkflow(sync, {
|
|
8025
8305
|
workflowId: parsed.positionals[0],
|
|
8026
|
-
request: await buildRequest(
|
|
8027
|
-
|
|
8028
|
-
|
|
8306
|
+
request: await buildRequest(
|
|
8307
|
+
command,
|
|
8308
|
+
withoutFlags(parsed, "out", "keep-edits"),
|
|
8309
|
+
{
|
|
8310
|
+
io: deps.io
|
|
8311
|
+
}
|
|
8312
|
+
),
|
|
8029
8313
|
dir: flag(parsed, "out"),
|
|
8030
8314
|
root: deps.root ?? "",
|
|
8315
|
+
keepEdits: flag(parsed, "keep-edits") === "true",
|
|
8031
8316
|
...deps.workflowPull
|
|
8032
8317
|
});
|
|
8033
8318
|
return { result, text: formatWorkflowPull(result) };
|
|
@@ -8038,7 +8323,8 @@ async function execute(command, parsed, sync, deps) {
|
|
|
8038
8323
|
const result2 = await pushWorkflow(
|
|
8039
8324
|
sync,
|
|
8040
8325
|
flag(parsed, "from") ?? ".",
|
|
8041
|
-
parsed.positionals[0] || void 0
|
|
8326
|
+
parsed.positionals[0] || void 0,
|
|
8327
|
+
{ rebase: flag(parsed, "rebase") === "true" }
|
|
8042
8328
|
);
|
|
8043
8329
|
return { result: result2, text: formatWorkflowPush(result2) };
|
|
8044
8330
|
}
|
package/dist/core.js
CHANGED
|
@@ -46,6 +46,61 @@ var COMMANDS = [
|
|
|
46
46
|
readOnly: false,
|
|
47
47
|
destructive: true
|
|
48
48
|
},
|
|
49
|
+
{
|
|
50
|
+
command: "batch create",
|
|
51
|
+
tool: "execution_batch_create",
|
|
52
|
+
title: "Create execution batch",
|
|
53
|
+
description: 'Queue one execution per row of `items` for `workflowId`, starting at `startAt` (ISO 8601; now or earlier starts at once). `config` paces the runs: `{type: "maxConcurrent", maxConcurrent}` or `{type: "timeBatching", batchSize, batchInterval}` (seconds). Each item takes `inputs` (see `workflow_version_inputs_get`), optional `profileId` or `profileGroupId`, `metadata`, and `workflowVersionId` to pin a version. Check the same body first with `execution_batch_validate`. Returns the batch; follow it with `execution_batch_get`, and its runs with `executions_list` filtered by `batchId`.',
|
|
54
|
+
method: "POST",
|
|
55
|
+
path: "/agents/v2/execution-batches",
|
|
56
|
+
args: [],
|
|
57
|
+
query: [],
|
|
58
|
+
binary: false,
|
|
59
|
+
readOnly: false,
|
|
60
|
+
destructive: false,
|
|
61
|
+
body: {
|
|
62
|
+
required: true,
|
|
63
|
+
contentType: "application/json",
|
|
64
|
+
fields: [
|
|
65
|
+
{
|
|
66
|
+
name: "workflowId",
|
|
67
|
+
flag: "workflow-id",
|
|
68
|
+
type: "string",
|
|
69
|
+
required: false,
|
|
70
|
+
description: "Workflow ID for this batch. Set this or agentId."
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: "name",
|
|
74
|
+
flag: "name",
|
|
75
|
+
type: "string",
|
|
76
|
+
required: true
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: "startAt",
|
|
80
|
+
flag: "start-at",
|
|
81
|
+
type: "string",
|
|
82
|
+
required: true,
|
|
83
|
+
description: "When to start triggering executions"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: "config",
|
|
87
|
+
flag: "config",
|
|
88
|
+
type: "json",
|
|
89
|
+
required: true,
|
|
90
|
+
description: "Discriminated union of batch configuration types (discriminated by type)"
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: "items",
|
|
94
|
+
flag: "items",
|
|
95
|
+
type: "json",
|
|
96
|
+
required: true
|
|
97
|
+
}
|
|
98
|
+
],
|
|
99
|
+
files: [],
|
|
100
|
+
credentialOrganization: false
|
|
101
|
+
},
|
|
102
|
+
capability: "run_execution"
|
|
103
|
+
},
|
|
49
104
|
{
|
|
50
105
|
command: "batch get",
|
|
51
106
|
tool: "execution_batch_get",
|
|
@@ -148,6 +203,60 @@ var COMMANDS = [
|
|
|
148
203
|
destructive: false,
|
|
149
204
|
capability: "run_execution"
|
|
150
205
|
},
|
|
206
|
+
{
|
|
207
|
+
command: "batch validate",
|
|
208
|
+
tool: "execution_batch_validate",
|
|
209
|
+
title: "Validate execution batch",
|
|
210
|
+
description: "Check an `execution_batch_create` body without creating anything. Returns `valid`, `itemCount`, and `issues`: each row (by its 0-based `index` in `items`) whose inputs would fail the input schema of the version it runs. A problem with the whole batch, such as an empty `items` or a bad `config`, returns the same error create would.",
|
|
211
|
+
method: "POST",
|
|
212
|
+
path: "/agents/v2/execution-batches/validate",
|
|
213
|
+
args: [],
|
|
214
|
+
query: [],
|
|
215
|
+
binary: false,
|
|
216
|
+
readOnly: true,
|
|
217
|
+
destructive: false,
|
|
218
|
+
body: {
|
|
219
|
+
required: true,
|
|
220
|
+
contentType: "application/json",
|
|
221
|
+
fields: [
|
|
222
|
+
{
|
|
223
|
+
name: "workflowId",
|
|
224
|
+
flag: "workflow-id",
|
|
225
|
+
type: "string",
|
|
226
|
+
required: false,
|
|
227
|
+
description: "Workflow ID for this batch. Set this or agentId."
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
name: "name",
|
|
231
|
+
flag: "name",
|
|
232
|
+
type: "string",
|
|
233
|
+
required: true
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
name: "startAt",
|
|
237
|
+
flag: "start-at",
|
|
238
|
+
type: "string",
|
|
239
|
+
required: true,
|
|
240
|
+
description: "When to start triggering executions"
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
name: "config",
|
|
244
|
+
flag: "config",
|
|
245
|
+
type: "json",
|
|
246
|
+
required: true,
|
|
247
|
+
description: "Discriminated union of batch configuration types (discriminated by type)"
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
name: "items",
|
|
251
|
+
flag: "items",
|
|
252
|
+
type: "json",
|
|
253
|
+
required: true
|
|
254
|
+
}
|
|
255
|
+
],
|
|
256
|
+
files: [],
|
|
257
|
+
credentialOrganization: false
|
|
258
|
+
}
|
|
259
|
+
},
|
|
151
260
|
{
|
|
152
261
|
command: "context",
|
|
153
262
|
tool: "get_context",
|
|
@@ -1357,6 +1466,13 @@ var COMMANDS = [
|
|
|
1357
1466
|
type: "string[]",
|
|
1358
1467
|
required: false,
|
|
1359
1468
|
description: "Filter by login profile IDs (can specify multiple, there is an 'OR' condition applied to these)"
|
|
1469
|
+
},
|
|
1470
|
+
{
|
|
1471
|
+
name: "batchId",
|
|
1472
|
+
flag: "batch-id",
|
|
1473
|
+
type: "string",
|
|
1474
|
+
required: false,
|
|
1475
|
+
description: "Only executions started by this batch, an ID from `execution_batches_list`. Rows not yet triggered have no execution yet: list them with `scheduled_executions_list` and `batchId`."
|
|
1360
1476
|
}
|
|
1361
1477
|
],
|
|
1362
1478
|
binary: false,
|
|
@@ -1631,6 +1747,13 @@ var COMMANDS = [
|
|
|
1631
1747
|
type: "string[]",
|
|
1632
1748
|
required: false,
|
|
1633
1749
|
description: "Filter by login profile IDs (can specify multiple, there is an 'OR' condition applied to these)"
|
|
1750
|
+
},
|
|
1751
|
+
{
|
|
1752
|
+
name: "batchId",
|
|
1753
|
+
flag: "batch-id",
|
|
1754
|
+
type: "string",
|
|
1755
|
+
required: false,
|
|
1756
|
+
description: "Only executions started by this batch, an ID from `execution_batches_list`. Rows not yet triggered have no execution yet: list them with `scheduled_executions_list` and `batchId`."
|
|
1634
1757
|
}
|
|
1635
1758
|
],
|
|
1636
1759
|
binary: false,
|
|
@@ -1928,7 +2051,7 @@ var COMMANDS = [
|
|
|
1928
2051
|
command: "portal-skills add",
|
|
1929
2052
|
tool: "portal_skills_add",
|
|
1930
2053
|
title: "Add portal skills",
|
|
1931
|
-
description: "Add a portal's skills to an organization, by
|
|
2054
|
+
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
2055
|
method: "POST",
|
|
1933
2056
|
path: "/agents/v2/portal-skills/{portalId}",
|
|
1934
2057
|
args: [
|
|
@@ -1944,6 +2067,27 @@ var COMMANDS = [
|
|
|
1944
2067
|
readOnly: false,
|
|
1945
2068
|
destructive: false
|
|
1946
2069
|
},
|
|
2070
|
+
{
|
|
2071
|
+
command: "portal-skills list",
|
|
2072
|
+
tool: "portal_skills_list",
|
|
2073
|
+
title: "List portal skills",
|
|
2074
|
+
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`.",
|
|
2075
|
+
method: "GET",
|
|
2076
|
+
path: "/astro/v2/portal-skills",
|
|
2077
|
+
args: [],
|
|
2078
|
+
query: [
|
|
2079
|
+
{
|
|
2080
|
+
name: "query",
|
|
2081
|
+
flag: "query",
|
|
2082
|
+
type: "string",
|
|
2083
|
+
required: false,
|
|
2084
|
+
description: "A portal name or task, at least three characters. Omit to list every portal with skills."
|
|
2085
|
+
}
|
|
2086
|
+
],
|
|
2087
|
+
binary: false,
|
|
2088
|
+
readOnly: true,
|
|
2089
|
+
destructive: false
|
|
2090
|
+
},
|
|
1947
2091
|
{
|
|
1948
2092
|
command: "profile clear-cache",
|
|
1949
2093
|
tool: "profile_clear_browser_cache",
|
|
@@ -4462,7 +4606,7 @@ var COMMANDS = [
|
|
|
4462
4606
|
command: "workflow head publish",
|
|
4463
4607
|
tool: "workflow_head_publish",
|
|
4464
4608
|
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.",
|
|
4609
|
+
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
4610
|
method: "POST",
|
|
4467
4611
|
path: "/agents/v2/workflows/{workflowId}/head/publish",
|
|
4468
4612
|
args: [
|
|
@@ -5399,7 +5543,7 @@ var COMMANDS = [
|
|
|
5399
5543
|
command: "workflow version publish",
|
|
5400
5544
|
tool: "workflow_version_publish",
|
|
5401
5545
|
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`.",
|
|
5546
|
+
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
5547
|
method: "POST",
|
|
5404
5548
|
path: "/agents/v2/workflows/{workflowId}/versions/{versionId}/publish",
|
|
5405
5549
|
args: [
|
|
@@ -5903,16 +6047,26 @@ function leafOptions(leaf, command) {
|
|
|
5903
6047
|
for (const file of command.body?.files ?? []) {
|
|
5904
6048
|
leaf.option(`--${file} <path>`, "File to upload. Repeatable.", collect);
|
|
5905
6049
|
}
|
|
5906
|
-
if (command.files === "pull")
|
|
6050
|
+
if (command.files === "pull") {
|
|
5907
6051
|
leaf.option(
|
|
5908
6052
|
"--out <dir>",
|
|
5909
6053
|
"Write here instead of ./<workflow-name>/head (a version: ./<workflow-name>/.versions/...). --json prints the file listing instead."
|
|
5910
6054
|
);
|
|
5911
|
-
|
|
6055
|
+
leaf.option(
|
|
6056
|
+
"--keep-edits",
|
|
6057
|
+
"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."
|
|
6058
|
+
);
|
|
6059
|
+
}
|
|
6060
|
+
if (command.files === "push") {
|
|
5912
6061
|
leaf.option(
|
|
5913
6062
|
"--from <dir>",
|
|
5914
6063
|
"The pulled head folder to push. Defaults to the current folder."
|
|
5915
6064
|
);
|
|
6065
|
+
leaf.option(
|
|
6066
|
+
"--rebase",
|
|
6067
|
+
"When the head moved since your pull, pull it with --keep-edits and push again, twice at most."
|
|
6068
|
+
);
|
|
6069
|
+
}
|
|
5916
6070
|
}
|
|
5917
6071
|
function toParsed(leaf, positionals, dotted2) {
|
|
5918
6072
|
const globals = leaf.optsWithGlobals();
|
|
@@ -6703,6 +6857,18 @@ ${result.skipped.map((s) => ` - ${s}`).join("\n")}` : null,
|
|
|
6703
6857
|
].filter(Boolean).join("\n");
|
|
6704
6858
|
}
|
|
6705
6859
|
|
|
6860
|
+
// src/sync/manifest.ts
|
|
6861
|
+
async function loadManifest(sync, dir) {
|
|
6862
|
+
const bytes = await sync.files.read(join(dir, MANIFEST));
|
|
6863
|
+
if (!bytes) return void 0;
|
|
6864
|
+
return JSON.parse(Buffer.from(bytes).toString("utf8"));
|
|
6865
|
+
}
|
|
6866
|
+
async function writeManifest(sync, dir, manifest) {
|
|
6867
|
+
const text2 = `${JSON.stringify(manifest, null, 2)}
|
|
6868
|
+
`;
|
|
6869
|
+
await sync.files.write(join(dir, MANIFEST), Buffer.from(text2, "utf8"));
|
|
6870
|
+
}
|
|
6871
|
+
|
|
6706
6872
|
// src/sync/node-files.ts
|
|
6707
6873
|
import { mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
6708
6874
|
import { dirname, relative, resolve, sep } from "node:path";
|
|
@@ -6754,6 +6920,9 @@ function pullDir(root, tree, head) {
|
|
|
6754
6920
|
if (head) return join(base, "head");
|
|
6755
6921
|
return join(base, ".versions", workflowDirName(tree.versionId, tree.version));
|
|
6756
6922
|
}
|
|
6923
|
+
function headFilesRequest(workflowId) {
|
|
6924
|
+
return get(`/agents/v2/workflows/${workflowId}/head/files`);
|
|
6925
|
+
}
|
|
6757
6926
|
async function agentFileBytes(sync, workflowId, tree, entry) {
|
|
6758
6927
|
const path = `/agents/v2/workflows/${workflowId}/versions/${tree.versionId}/files/${entry.fileId}/signed-url`;
|
|
6759
6928
|
const { url } = await sync.remote.json(get(path));
|
|
@@ -6763,32 +6932,105 @@ async function entryBytes(sync, workflowId, tree, entry) {
|
|
|
6763
6932
|
if (entry.contentBase64 !== void 0)
|
|
6764
6933
|
return Buffer.from(entry.contentBase64, "base64");
|
|
6765
6934
|
if (entry.content !== void 0) return Buffer.from(entry.content, "utf8");
|
|
6935
|
+
if (entry.storageObjectKey && sync.remote.object)
|
|
6936
|
+
return sync.remote.object(entry.storageObjectKey);
|
|
6766
6937
|
if (!entry.fileId) return void 0;
|
|
6767
6938
|
return agentFileBytes(sync, workflowId, tree, entry);
|
|
6768
6939
|
}
|
|
6769
|
-
|
|
6770
|
-
|
|
6771
|
-
|
|
6772
|
-
|
|
6773
|
-
|
|
6940
|
+
function inlineBytes(entry) {
|
|
6941
|
+
if (entry.contentBase64 !== void 0)
|
|
6942
|
+
return Buffer.from(entry.contentBase64, "base64");
|
|
6943
|
+
if (entry.content !== void 0) return Buffer.from(entry.content, "utf8");
|
|
6944
|
+
return void 0;
|
|
6945
|
+
}
|
|
6946
|
+
function wantedEntries(options, tree, skipped) {
|
|
6947
|
+
return tree.files.filter((entry) => {
|
|
6774
6948
|
if (!isContained(entry.path)) skipped.push(`${entry.path} (unsafe path)`);
|
|
6775
6949
|
else if (options.maxFileBytes !== void 0 && entry.size > options.maxFileBytes)
|
|
6776
6950
|
skipped.push(`${entry.path} (${entry.size} bytes, over the size limit)`);
|
|
6777
6951
|
else return true;
|
|
6778
6952
|
return false;
|
|
6779
6953
|
});
|
|
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
|
-
);
|
|
6954
|
+
}
|
|
6955
|
+
async function inBatches(items, each) {
|
|
6956
|
+
for (let i = 0; i < items.length; i += CONCURRENCY) {
|
|
6957
|
+
await Promise.all(items.slice(i, i + CONCURRENCY).map(each));
|
|
6790
6958
|
}
|
|
6791
|
-
|
|
6959
|
+
}
|
|
6960
|
+
async function writeEntries(sync, options, tree, dir) {
|
|
6961
|
+
const hashes = {};
|
|
6962
|
+
const skipped = [];
|
|
6963
|
+
let shared = 0;
|
|
6964
|
+
let written = 0;
|
|
6965
|
+
await inBatches(wantedEntries(options, tree, skipped), async (entry) => {
|
|
6966
|
+
const bytes = await entryBytes(sync, options.workflowId, tree, entry);
|
|
6967
|
+
if (!bytes) return void skipped.push(`${entry.path} (no content)`);
|
|
6968
|
+
await sync.files.write(join(dir, entry.path), bytes);
|
|
6969
|
+
hashes[entry.path] = sha256(bytes);
|
|
6970
|
+
written++;
|
|
6971
|
+
if (entry.kind === "agent_file") shared++;
|
|
6972
|
+
});
|
|
6973
|
+
return { hashes, skipped, shared, kept: [], written };
|
|
6974
|
+
}
|
|
6975
|
+
async function mergeEntry(sync, options, tree, dir, previous, entry, out) {
|
|
6976
|
+
const local2 = await sync.files.read(join(dir, entry.path));
|
|
6977
|
+
const localHash = local2 ? sha256(local2) : void 0;
|
|
6978
|
+
const baseline = previous.files[entry.path];
|
|
6979
|
+
const edited = baseline === void 0 ? local2 !== void 0 : localHash !== baseline;
|
|
6980
|
+
let bytes = inlineBytes(entry);
|
|
6981
|
+
let serverHash = bytes ? sha256(bytes) : entry.checksum;
|
|
6982
|
+
if (edited) {
|
|
6983
|
+
if (serverHash === void 0) {
|
|
6984
|
+
bytes = await entryBytes(sync, options.workflowId, tree, entry);
|
|
6985
|
+
if (!bytes) return void out.skipped.push(`${entry.path} (no content)`);
|
|
6986
|
+
serverHash = sha256(bytes);
|
|
6987
|
+
}
|
|
6988
|
+
out.hashes[entry.path] = serverHash;
|
|
6989
|
+
out.kept.push(entry.path);
|
|
6990
|
+
return;
|
|
6991
|
+
}
|
|
6992
|
+
if (serverHash !== void 0 && serverHash === localHash) {
|
|
6993
|
+
out.hashes[entry.path] = serverHash;
|
|
6994
|
+
return;
|
|
6995
|
+
}
|
|
6996
|
+
bytes ??= await entryBytes(sync, options.workflowId, tree, entry);
|
|
6997
|
+
if (!bytes) return void out.skipped.push(`${entry.path} (no content)`);
|
|
6998
|
+
serverHash = sha256(bytes);
|
|
6999
|
+
if (serverHash !== localHash) {
|
|
7000
|
+
await sync.files.write(join(dir, entry.path), bytes);
|
|
7001
|
+
out.written++;
|
|
7002
|
+
if (entry.kind === "agent_file") out.shared++;
|
|
7003
|
+
}
|
|
7004
|
+
out.hashes[entry.path] = serverHash;
|
|
7005
|
+
}
|
|
7006
|
+
async function mergeEntries(sync, options, tree, dir, previous) {
|
|
7007
|
+
const out = {
|
|
7008
|
+
hashes: {},
|
|
7009
|
+
skipped: [],
|
|
7010
|
+
shared: 0,
|
|
7011
|
+
kept: [],
|
|
7012
|
+
written: 0
|
|
7013
|
+
};
|
|
7014
|
+
const wanted = wantedEntries(options, tree, out.skipped);
|
|
7015
|
+
await inBatches(
|
|
7016
|
+
wanted,
|
|
7017
|
+
(entry) => mergeEntry(sync, options, tree, dir, previous, entry, out)
|
|
7018
|
+
);
|
|
7019
|
+
const remote = new Set(tree.files.map((entry) => entry.path));
|
|
7020
|
+
for (const path of Object.keys(previous.files)) {
|
|
7021
|
+
if (path in out.hashes) continue;
|
|
7022
|
+
if (remote.has(path)) {
|
|
7023
|
+
out.hashes[path] = previous.files[path];
|
|
7024
|
+
continue;
|
|
7025
|
+
}
|
|
7026
|
+
const local2 = await sync.files.read(join(dir, path));
|
|
7027
|
+
if (!local2) continue;
|
|
7028
|
+
if (sha256(local2) === previous.files[path])
|
|
7029
|
+
await sync.files.remove(join(dir, path));
|
|
7030
|
+
else out.kept.push(path);
|
|
7031
|
+
}
|
|
7032
|
+
out.kept.sort();
|
|
7033
|
+
return out;
|
|
6792
7034
|
}
|
|
6793
7035
|
async function removeStale(sync, dir, keep) {
|
|
6794
7036
|
for (const path of await sync.files.list(dir)) {
|
|
@@ -6796,11 +7038,6 @@ async function removeStale(sync, dir, keep) {
|
|
|
6796
7038
|
await sync.files.remove(join(dir, path));
|
|
6797
7039
|
}
|
|
6798
7040
|
}
|
|
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
7041
|
async function pullWorkflow(sync, options) {
|
|
6805
7042
|
const request = {
|
|
6806
7043
|
...options.request,
|
|
@@ -6810,20 +7047,25 @@ async function pullWorkflow(sync, options) {
|
|
|
6810
7047
|
const tree = await sync.remote.json(request);
|
|
6811
7048
|
const head = request.path.endsWith("/head/files");
|
|
6812
7049
|
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)));
|
|
7050
|
+
const previous = options.keepEdits ? await loadManifest(sync, dir) : void 0;
|
|
7051
|
+
const written = previous ? await mergeEntries(sync, options, tree, dir, previous) : await writeEntries(sync, options, tree, dir);
|
|
7052
|
+
if (!previous)
|
|
7053
|
+
await removeStale(sync, dir, new Set(Object.keys(written.hashes)));
|
|
6820
7054
|
if (options.manifest !== false) {
|
|
7055
|
+
const variants = request.query.get("variants") ?? void 0;
|
|
6821
7056
|
await writeManifest(sync, dir, {
|
|
6822
7057
|
workflowId: options.workflowId,
|
|
6823
7058
|
versionId: tree.versionId,
|
|
6824
7059
|
head,
|
|
6825
7060
|
rev: tree.rev,
|
|
6826
|
-
files: hashes
|
|
7061
|
+
files: written.hashes,
|
|
7062
|
+
...variants !== void 0 && { variants },
|
|
7063
|
+
...options.maxFileBytes !== void 0 && {
|
|
7064
|
+
maxFileBytes: options.maxFileBytes
|
|
7065
|
+
},
|
|
7066
|
+
...options.maxTotalBytes !== void 0 && {
|
|
7067
|
+
maxTotalBytes: options.maxTotalBytes
|
|
7068
|
+
}
|
|
6827
7069
|
});
|
|
6828
7070
|
}
|
|
6829
7071
|
return {
|
|
@@ -6834,9 +7076,11 @@ async function pullWorkflow(sync, options) {
|
|
|
6834
7076
|
rev: tree.rev,
|
|
6835
7077
|
dir,
|
|
6836
7078
|
head,
|
|
6837
|
-
written:
|
|
6838
|
-
shared,
|
|
6839
|
-
skipped
|
|
7079
|
+
written: written.written,
|
|
7080
|
+
shared: written.shared,
|
|
7081
|
+
skipped: written.skipped,
|
|
7082
|
+
kept: written.kept,
|
|
7083
|
+
...tree.variantKeys && { variantKeys: tree.variantKeys }
|
|
6840
7084
|
};
|
|
6841
7085
|
}
|
|
6842
7086
|
function formatWorkflowPull(result) {
|
|
@@ -6847,6 +7091,8 @@ function formatWorkflowPull(result) {
|
|
|
6847
7091
|
`Workflow version: ${result.versionId}${result.version ? ` (v${result.version})` : " (draft)"}`,
|
|
6848
7092
|
`Written to: ${result.dir}/`,
|
|
6849
7093
|
files,
|
|
7094
|
+
result.kept.length > 0 ? `Kept your edits in:
|
|
7095
|
+
${result.kept.map((s) => ` - ${s}`).join("\n")}` : null,
|
|
6850
7096
|
result.skipped.length > 0 ? `Skipped:
|
|
6851
7097
|
${result.skipped.map((s) => ` - ${s}`).join("\n")}` : null,
|
|
6852
7098
|
result.head ? `Edit the files, then run \`asteroid workflow push\` from ${result.dir}/.` : "This is a pinned version. It is read-only."
|
|
@@ -6854,14 +7100,14 @@ ${result.skipped.map((s) => ` - ${s}`).join("\n")}` : null,
|
|
|
6854
7100
|
}
|
|
6855
7101
|
|
|
6856
7102
|
// src/sync/workflow-push.ts
|
|
7103
|
+
var REBASE_ATTEMPTS = 3;
|
|
6857
7104
|
async function readManifest(sync, dir) {
|
|
6858
|
-
const
|
|
6859
|
-
if (!
|
|
7105
|
+
const manifest = await loadManifest(sync, dir);
|
|
7106
|
+
if (!manifest) {
|
|
6860
7107
|
throw new UsageError(
|
|
6861
7108
|
`no pulled workflow in ${dir}. Run \`asteroid workflow pull <workflow-id>\`, then push from the head folder it writes.`
|
|
6862
7109
|
);
|
|
6863
7110
|
}
|
|
6864
|
-
const manifest = JSON.parse(Buffer.from(bytes).toString("utf8"));
|
|
6865
7111
|
if (!manifest.head) {
|
|
6866
7112
|
throw new UsageError(
|
|
6867
7113
|
`${dir} holds a pinned workflow version, which is read-only. Push from a pulled head.`
|
|
@@ -6891,25 +7137,44 @@ async function localChanges(sync, dir, manifest) {
|
|
|
6891
7137
|
);
|
|
6892
7138
|
return { writes, deletes };
|
|
6893
7139
|
}
|
|
7140
|
+
function conflict(manifest) {
|
|
7141
|
+
return new UsageError(
|
|
7142
|
+
`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.`
|
|
7143
|
+
);
|
|
7144
|
+
}
|
|
6894
7145
|
async function patch(sync, manifest, writes, deletes) {
|
|
6895
7146
|
try {
|
|
6896
7147
|
await sync.remote.json({
|
|
6897
7148
|
method: "PATCH",
|
|
6898
7149
|
path: `/agents/v2/workflows/${manifest.workflowId}/head/files`,
|
|
6899
7150
|
query: new URLSearchParams(),
|
|
6900
|
-
json: {
|
|
7151
|
+
json: {
|
|
7152
|
+
baseRev: manifest.rev,
|
|
7153
|
+
writes: writes.map(({ path, contentBase64 }) => ({
|
|
7154
|
+
path,
|
|
7155
|
+
contentBase64
|
|
7156
|
+
})),
|
|
7157
|
+
deletes
|
|
7158
|
+
}
|
|
6901
7159
|
});
|
|
7160
|
+
return "applied";
|
|
6902
7161
|
} catch (error) {
|
|
6903
7162
|
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
|
-
);
|
|
7163
|
+
return "conflict";
|
|
6907
7164
|
}
|
|
6908
7165
|
}
|
|
7166
|
+
function headRequest(manifest) {
|
|
7167
|
+
const request = headFilesRequest(manifest.workflowId);
|
|
7168
|
+
if (manifest.variants !== void 0)
|
|
7169
|
+
request.query.set("variants", manifest.variants);
|
|
7170
|
+
if (manifest.maxFileBytes !== void 0)
|
|
7171
|
+
request.query.set("maxFileBytes", String(manifest.maxFileBytes));
|
|
7172
|
+
if (manifest.maxTotalBytes !== void 0)
|
|
7173
|
+
request.query.set("maxTotalBytes", String(manifest.maxTotalBytes));
|
|
7174
|
+
return request;
|
|
7175
|
+
}
|
|
6909
7176
|
async function refresh(sync, dir, manifest, pushed) {
|
|
6910
|
-
const tree = await sync.remote.json(
|
|
6911
|
-
get(`/agents/v2/workflows/${manifest.workflowId}/head/files`)
|
|
6912
|
-
);
|
|
7177
|
+
const tree = await sync.remote.json(headRequest(manifest));
|
|
6913
7178
|
const files = {};
|
|
6914
7179
|
for (const entry of tree.files) {
|
|
6915
7180
|
if (entry.content !== void 0) {
|
|
@@ -6919,7 +7184,7 @@ async function refresh(sync, dir, manifest, pushed) {
|
|
|
6919
7184
|
await sync.files.write(join(dir, entry.path), bytes);
|
|
6920
7185
|
} else {
|
|
6921
7186
|
const local2 = await sync.files.read(join(dir, entry.path));
|
|
6922
|
-
const hash = local2 ? sha256(local2) : pushed.get(entry.path)
|
|
7187
|
+
const hash = local2 ? sha256(local2) : pushed.get(entry.path);
|
|
6923
7188
|
if (hash) files[entry.path] = hash;
|
|
6924
7189
|
}
|
|
6925
7190
|
}
|
|
@@ -6931,43 +7196,58 @@ async function refresh(sync, dir, manifest, pushed) {
|
|
|
6931
7196
|
await writeManifest(sync, dir, next);
|
|
6932
7197
|
return next;
|
|
6933
7198
|
}
|
|
6934
|
-
async function
|
|
6935
|
-
|
|
7199
|
+
async function rebase(sync, dir, manifest) {
|
|
7200
|
+
await pullWorkflow(sync, {
|
|
7201
|
+
workflowId: manifest.workflowId,
|
|
7202
|
+
request: headRequest(manifest),
|
|
7203
|
+
dir,
|
|
7204
|
+
keepEdits: true,
|
|
7205
|
+
maxFileBytes: manifest.maxFileBytes,
|
|
7206
|
+
maxTotalBytes: manifest.maxTotalBytes
|
|
7207
|
+
});
|
|
7208
|
+
return readManifest(sync, dir);
|
|
7209
|
+
}
|
|
7210
|
+
async function pushWorkflow(sync, dir, workflowId, options = {}) {
|
|
7211
|
+
let manifest = await readManifest(sync, dir);
|
|
6936
7212
|
if (workflowId !== void 0 && manifest.workflowId !== workflowId) {
|
|
6937
7213
|
throw new UsageError(
|
|
6938
7214
|
`${dir} holds workflow ${manifest.workflowId}, not ${workflowId}.`
|
|
6939
7215
|
);
|
|
6940
7216
|
}
|
|
6941
|
-
|
|
6942
|
-
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
|
|
6946
|
-
|
|
6947
|
-
|
|
6948
|
-
|
|
6949
|
-
|
|
7217
|
+
let rebasedFrom;
|
|
7218
|
+
for (let attempt = 1; ; attempt++) {
|
|
7219
|
+
const { writes, deletes } = await localChanges(sync, dir, manifest);
|
|
7220
|
+
const result = {
|
|
7221
|
+
workflowId: manifest.workflowId,
|
|
7222
|
+
dir,
|
|
7223
|
+
written: writes.map((w) => w.path),
|
|
7224
|
+
deleted: deletes,
|
|
7225
|
+
...rebasedFrom !== void 0 && { rebasedFrom }
|
|
7226
|
+
};
|
|
7227
|
+
if (writes.length === 0 && deletes.length === 0) {
|
|
7228
|
+
return { ...result, rev: manifest.rev, versionId: manifest.versionId };
|
|
7229
|
+
}
|
|
7230
|
+
if (await patch(sync, manifest, writes, deletes) === "applied") {
|
|
7231
|
+
const next = await refresh(
|
|
7232
|
+
sync,
|
|
7233
|
+
dir,
|
|
7234
|
+
manifest,
|
|
7235
|
+
new Map(writes.map((w) => [w.path, w.hash]))
|
|
7236
|
+
);
|
|
7237
|
+
return { ...result, rev: next.rev, versionId: next.versionId };
|
|
7238
|
+
}
|
|
7239
|
+
if (!options.rebase || attempt >= REBASE_ATTEMPTS) throw conflict(manifest);
|
|
7240
|
+
rebasedFrom ??= manifest.rev;
|
|
7241
|
+
manifest = await rebase(sync, dir, manifest);
|
|
6950
7242
|
}
|
|
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
7243
|
}
|
|
6965
7244
|
function formatWorkflowPush(result) {
|
|
7245
|
+
const rebased = result.rebasedFrom !== void 0 ? ` Rebased on the head first: your pull was rev ${result.rebasedFrom}.` : "";
|
|
6966
7246
|
if (result.written.length === 0 && result.deleted.length === 0) {
|
|
6967
|
-
return `Nothing to push: ${result.dir}/ matches the head you pulled (rev ${result.rev})
|
|
7247
|
+
return `Nothing to push: ${result.dir}/ matches the head you pulled (rev ${result.rev}).${rebased}`;
|
|
6968
7248
|
}
|
|
6969
7249
|
return [
|
|
6970
|
-
`Pushed to workflow ${result.workflowId}: ${result.written.length} written, ${result.deleted.length} deleted
|
|
7250
|
+
`Pushed to workflow ${result.workflowId}: ${result.written.length} written, ${result.deleted.length} deleted.${rebased}`,
|
|
6971
7251
|
...result.written.map((path) => ` + ${path}`),
|
|
6972
7252
|
...result.deleted.map((path) => ` - ${path}`),
|
|
6973
7253
|
`Head is now rev ${result.rev} (version ${result.versionId}).`
|
|
@@ -6998,11 +7278,17 @@ function executionOptions(parsed) {
|
|
|
6998
7278
|
dir: dir ? () => dir : void 0
|
|
6999
7279
|
};
|
|
7000
7280
|
}
|
|
7001
|
-
function
|
|
7281
|
+
function withoutFlags(parsed, ...names) {
|
|
7002
7282
|
const flags = new Map(parsed.flags);
|
|
7003
|
-
flags.delete(name);
|
|
7283
|
+
for (const name of names) flags.delete(name);
|
|
7004
7284
|
return { ...parsed, flags };
|
|
7005
7285
|
}
|
|
7286
|
+
function runsLocally(command, parsed) {
|
|
7287
|
+
if (command.local) return true;
|
|
7288
|
+
if (command.files === "pull") return !parsed.json;
|
|
7289
|
+
if (command.files === "push") return parsed.body === void 0;
|
|
7290
|
+
return false;
|
|
7291
|
+
}
|
|
7006
7292
|
var PARTIAL = ["paths", "variants"];
|
|
7007
7293
|
function rejectPartial(parsed) {
|
|
7008
7294
|
const used = PARTIAL.filter((name) => parsed.flags.has(name));
|
|
@@ -7015,11 +7301,16 @@ async function pull(command, parsed, sync, deps) {
|
|
|
7015
7301
|
rejectPartial(parsed);
|
|
7016
7302
|
const result = await pullWorkflow(sync, {
|
|
7017
7303
|
workflowId: parsed.positionals[0],
|
|
7018
|
-
request: await buildRequest(
|
|
7019
|
-
|
|
7020
|
-
|
|
7304
|
+
request: await buildRequest(
|
|
7305
|
+
command,
|
|
7306
|
+
withoutFlags(parsed, "out", "keep-edits"),
|
|
7307
|
+
{
|
|
7308
|
+
io: deps.io
|
|
7309
|
+
}
|
|
7310
|
+
),
|
|
7021
7311
|
dir: flag(parsed, "out"),
|
|
7022
7312
|
root: deps.root ?? "",
|
|
7313
|
+
keepEdits: flag(parsed, "keep-edits") === "true",
|
|
7023
7314
|
...deps.workflowPull
|
|
7024
7315
|
});
|
|
7025
7316
|
return { result, text: formatWorkflowPull(result) };
|
|
@@ -7030,7 +7321,8 @@ async function execute(command, parsed, sync, deps) {
|
|
|
7030
7321
|
const result2 = await pushWorkflow(
|
|
7031
7322
|
sync,
|
|
7032
7323
|
flag(parsed, "from") ?? ".",
|
|
7033
|
-
parsed.positionals[0] || void 0
|
|
7324
|
+
parsed.positionals[0] || void 0,
|
|
7325
|
+
{ rebase: flag(parsed, "rebase") === "true" }
|
|
7034
7326
|
);
|
|
7035
7327
|
return { result: result2, text: formatWorkflowPush(result2) };
|
|
7036
7328
|
}
|
|
@@ -7064,14 +7356,20 @@ export {
|
|
|
7064
7356
|
formatExecutionPull,
|
|
7065
7357
|
formatWorkflowPull,
|
|
7066
7358
|
formatWorkflowPush,
|
|
7359
|
+
headFilesRequest,
|
|
7067
7360
|
leadingWords,
|
|
7361
|
+
loadManifest,
|
|
7068
7362
|
matchCommand,
|
|
7069
7363
|
matchesGlob,
|
|
7070
7364
|
parseInvocation,
|
|
7365
|
+
pullDir,
|
|
7071
7366
|
pullExecution,
|
|
7072
7367
|
pullWorkflow,
|
|
7073
7368
|
pushWorkflow,
|
|
7369
|
+
readManifest,
|
|
7074
7370
|
render,
|
|
7075
7371
|
runSync,
|
|
7076
|
-
|
|
7372
|
+
runsLocally,
|
|
7373
|
+
withSyncCommands,
|
|
7374
|
+
writeManifest
|
|
7077
7375
|
};
|