@asteroidai/cli 1.9.4 → 1.9.12
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 +1302 -469
- package/dist/core.js +1074 -260
- package/package.json +3 -2
- package/src/commands.ts +13 -0
- package/src/core.ts +31 -0
- package/src/generated/commands.ts +199 -182
- package/src/main.ts +2 -2
- package/src/program.test.ts +19 -0
- package/src/program.ts +25 -1
- package/src/reference.ts +13 -1
- package/src/run.test.ts +100 -3
- package/src/run.ts +57 -19
- package/src/sync/activities.ts +80 -0
- package/src/sync/commands.ts +63 -0
- package/src/sync/execution-pull.ts +273 -0
- package/src/sync/node-files.ts +47 -0
- package/src/sync/paths.ts +45 -0
- package/src/sync/run.ts +136 -0
- package/src/sync/sync.test.ts +502 -0
- package/src/sync/types.ts +58 -0
- package/src/sync/workflow-pull.ts +185 -0
- package/src/sync/workflow-push.ts +161 -0
- package/src/types.ts +4 -0
package/dist/core.js
CHANGED
|
@@ -145,7 +145,8 @@ var COMMANDS = [
|
|
|
145
145
|
query: [],
|
|
146
146
|
binary: false,
|
|
147
147
|
readOnly: false,
|
|
148
|
-
destructive: false
|
|
148
|
+
destructive: false,
|
|
149
|
+
capability: "run_execution"
|
|
149
150
|
},
|
|
150
151
|
{
|
|
151
152
|
command: "context",
|
|
@@ -1051,7 +1052,8 @@ var COMMANDS = [
|
|
|
1051
1052
|
],
|
|
1052
1053
|
files: [],
|
|
1053
1054
|
credentialOrganization: false
|
|
1054
|
-
}
|
|
1055
|
+
},
|
|
1056
|
+
capability: "run_execution"
|
|
1055
1057
|
},
|
|
1056
1058
|
{
|
|
1057
1059
|
command: "execution resume",
|
|
@@ -1071,7 +1073,8 @@ var COMMANDS = [
|
|
|
1071
1073
|
query: [],
|
|
1072
1074
|
binary: false,
|
|
1073
1075
|
readOnly: false,
|
|
1074
|
-
destructive: false
|
|
1076
|
+
destructive: false,
|
|
1077
|
+
capability: "run_execution"
|
|
1075
1078
|
},
|
|
1076
1079
|
{
|
|
1077
1080
|
command: "insight get",
|
|
@@ -1919,7 +1922,8 @@ var COMMANDS = [
|
|
|
1919
1922
|
],
|
|
1920
1923
|
files: [],
|
|
1921
1924
|
credentialOrganization: false
|
|
1922
|
-
}
|
|
1925
|
+
},
|
|
1926
|
+
capability: "run_execution"
|
|
1923
1927
|
},
|
|
1924
1928
|
{
|
|
1925
1929
|
command: "schedule delete",
|
|
@@ -1945,7 +1949,8 @@ var COMMANDS = [
|
|
|
1945
1949
|
query: [],
|
|
1946
1950
|
binary: false,
|
|
1947
1951
|
readOnly: false,
|
|
1948
|
-
destructive: true
|
|
1952
|
+
destructive: true,
|
|
1953
|
+
capability: "run_execution"
|
|
1949
1954
|
},
|
|
1950
1955
|
{
|
|
1951
1956
|
command: "schedule get",
|
|
@@ -2126,7 +2131,8 @@ var COMMANDS = [
|
|
|
2126
2131
|
],
|
|
2127
2132
|
files: [],
|
|
2128
2133
|
credentialOrganization: false
|
|
2129
|
-
}
|
|
2134
|
+
},
|
|
2135
|
+
capability: "run_execution"
|
|
2130
2136
|
},
|
|
2131
2137
|
{
|
|
2132
2138
|
command: "scheduled-execution cancel",
|
|
@@ -2146,7 +2152,8 @@ var COMMANDS = [
|
|
|
2146
2152
|
query: [],
|
|
2147
2153
|
binary: false,
|
|
2148
2154
|
readOnly: false,
|
|
2149
|
-
destructive: false
|
|
2155
|
+
destructive: false,
|
|
2156
|
+
capability: "run_execution"
|
|
2150
2157
|
},
|
|
2151
2158
|
{
|
|
2152
2159
|
command: "scheduled-execution create",
|
|
@@ -2214,7 +2221,8 @@ var COMMANDS = [
|
|
|
2214
2221
|
],
|
|
2215
2222
|
files: [],
|
|
2216
2223
|
credentialOrganization: false
|
|
2217
|
-
}
|
|
2224
|
+
},
|
|
2225
|
+
capability: "run_execution"
|
|
2218
2226
|
},
|
|
2219
2227
|
{
|
|
2220
2228
|
command: "scheduled-execution list",
|
|
@@ -2316,7 +2324,8 @@ var COMMANDS = [
|
|
|
2316
2324
|
],
|
|
2317
2325
|
files: [],
|
|
2318
2326
|
credentialOrganization: false
|
|
2319
|
-
}
|
|
2327
|
+
},
|
|
2328
|
+
capability: "run_execution"
|
|
2320
2329
|
},
|
|
2321
2330
|
{
|
|
2322
2331
|
command: "schema validate",
|
|
@@ -3456,7 +3465,8 @@ var COMMANDS = [
|
|
|
3456
3465
|
],
|
|
3457
3466
|
files: [],
|
|
3458
3467
|
credentialOrganization: false
|
|
3459
|
-
}
|
|
3468
|
+
},
|
|
3469
|
+
capability: "run_execution"
|
|
3460
3470
|
},
|
|
3461
3471
|
{
|
|
3462
3472
|
command: "workflow execute-sync",
|
|
@@ -3555,112 +3565,8 @@ var COMMANDS = [
|
|
|
3555
3565
|
],
|
|
3556
3566
|
files: [],
|
|
3557
3567
|
credentialOrganization: false
|
|
3558
|
-
}
|
|
3559
|
-
|
|
3560
|
-
{
|
|
3561
|
-
command: "workflow file list",
|
|
3562
|
-
tool: "workflow_files_list",
|
|
3563
|
-
title: "List workflow files",
|
|
3564
|
-
description: "List the workflow's editable head as a directory of files: the graph rendered as structural files (settings.yaml, per-node instructions.md, scripts) alongside the workflow's own files. Those files are referenced by fileId unless contents=all is set. Pass paths to read named files only, or contents=none for a manifest with no bodies. The returned rev is what you pass as baseRev to write.",
|
|
3565
|
-
method: "GET",
|
|
3566
|
-
path: "/agents/v2/workflows/{workflowId}/head/files",
|
|
3567
|
-
args: [
|
|
3568
|
-
{
|
|
3569
|
-
name: "workflowId",
|
|
3570
|
-
flag: "workflow-id",
|
|
3571
|
-
type: "string",
|
|
3572
|
-
required: true
|
|
3573
|
-
}
|
|
3574
|
-
],
|
|
3575
|
-
query: [
|
|
3576
|
-
{
|
|
3577
|
-
name: "contents",
|
|
3578
|
-
flag: "contents",
|
|
3579
|
-
type: "string",
|
|
3580
|
-
required: false,
|
|
3581
|
-
description: "Which contents come inline: none (a pure manifest), structural (the default), or all (structural plus file bytes within the response budget).",
|
|
3582
|
-
enum: ["none", "structural", "all"]
|
|
3583
|
-
},
|
|
3584
|
-
{
|
|
3585
|
-
name: "paths",
|
|
3586
|
-
flag: "paths",
|
|
3587
|
-
type: "string[]",
|
|
3588
|
-
required: false,
|
|
3589
|
-
description: "Return only the files at these exact tree paths. A path that names nothing yields no entry. The inline budget applies to the filtered set, so a narrow read can inline files a whole-tree read would have to skip."
|
|
3590
|
-
},
|
|
3591
|
-
{
|
|
3592
|
-
name: "variants",
|
|
3593
|
-
flag: "variants",
|
|
3594
|
-
type: "string",
|
|
3595
|
-
required: false,
|
|
3596
|
-
description: `Narrow which variants/<key>/ files the tree carries: "none" strips every variant, a comma-separated list keeps only those variant keys. Files outside variants/ are always kept, and the response's variantKeys still names every variant. Ignored unless the workflow has variant mode enabled; absent keeps every variant.`
|
|
3597
|
-
},
|
|
3598
|
-
{
|
|
3599
|
-
name: "maxFileBytes",
|
|
3600
|
-
flag: "max-file-bytes",
|
|
3601
|
-
type: "integer",
|
|
3602
|
-
required: false,
|
|
3603
|
-
description: "Per-file ceiling, in bytes, for inlined file contents. Clamped to the server maximum. Files above the ceiling stay references."
|
|
3604
|
-
},
|
|
3605
|
-
{
|
|
3606
|
-
name: "maxTotalBytes",
|
|
3607
|
-
flag: "max-total-bytes",
|
|
3608
|
-
type: "integer",
|
|
3609
|
-
required: false,
|
|
3610
|
-
description: "Ceiling, in bytes, on the total inlined file contents in one response. Clamped to the server maximum. Files are admitted in path order until the ceiling is reached; the rest stay references."
|
|
3611
|
-
}
|
|
3612
|
-
],
|
|
3613
|
-
binary: false,
|
|
3614
|
-
readOnly: true,
|
|
3615
|
-
destructive: false
|
|
3616
|
-
},
|
|
3617
|
-
{
|
|
3618
|
-
command: "workflow file patch",
|
|
3619
|
-
tool: "workflow_files_patch",
|
|
3620
|
-
title: "Write workflow files",
|
|
3621
|
-
description: "Write and delete files on the workflow's editable head in one patch. Structural files re-derive the graph; the workflow's own files go to its file store, you just write paths. Pass the rev from `workflow_files_list` as baseRev; a stale value is rejected with 409, so re-read and retry. Editing a published or already-executed version forks a new one instead of rewriting it. Each write carries `contentBase64`: base64-encode the file content, plain text is rejected with 400.",
|
|
3622
|
-
method: "PATCH",
|
|
3623
|
-
path: "/agents/v2/workflows/{workflowId}/head/files",
|
|
3624
|
-
args: [
|
|
3625
|
-
{
|
|
3626
|
-
name: "workflowId",
|
|
3627
|
-
flag: "workflow-id",
|
|
3628
|
-
type: "string",
|
|
3629
|
-
required: true
|
|
3630
|
-
}
|
|
3631
|
-
],
|
|
3632
|
-
query: [],
|
|
3633
|
-
binary: false,
|
|
3634
|
-
readOnly: false,
|
|
3635
|
-
destructive: true,
|
|
3636
|
-
body: {
|
|
3637
|
-
required: true,
|
|
3638
|
-
contentType: "application/json",
|
|
3639
|
-
fields: [
|
|
3640
|
-
{
|
|
3641
|
-
name: "baseRev",
|
|
3642
|
-
flag: "base-rev",
|
|
3643
|
-
type: "integer",
|
|
3644
|
-
required: true,
|
|
3645
|
-
description: "The revision this edit is based on. Agent-file-only patches overlay the current head even when this is stale. A structural-file patch is rejected with 409 when it does not match the stored head revision."
|
|
3646
|
-
},
|
|
3647
|
-
{
|
|
3648
|
-
name: "writes",
|
|
3649
|
-
flag: "writes",
|
|
3650
|
-
type: "json",
|
|
3651
|
-
required: true,
|
|
3652
|
-
description: "Files to upsert."
|
|
3653
|
-
},
|
|
3654
|
-
{
|
|
3655
|
-
name: "deletes",
|
|
3656
|
-
flag: "deletes",
|
|
3657
|
-
type: "string[]",
|
|
3658
|
-
required: true
|
|
3659
|
-
}
|
|
3660
|
-
],
|
|
3661
|
-
files: [],
|
|
3662
|
-
credentialOrganization: false
|
|
3663
|
-
}
|
|
3568
|
+
},
|
|
3569
|
+
capability: "run_execution"
|
|
3664
3570
|
},
|
|
3665
3571
|
{
|
|
3666
3572
|
command: "workflow get",
|
|
@@ -3708,7 +3614,8 @@ var COMMANDS = [
|
|
|
3708
3614
|
query: [],
|
|
3709
3615
|
binary: false,
|
|
3710
3616
|
readOnly: false,
|
|
3711
|
-
destructive: false
|
|
3617
|
+
destructive: false,
|
|
3618
|
+
capability: "publish"
|
|
3712
3619
|
},
|
|
3713
3620
|
{
|
|
3714
3621
|
command: "workflow head revert",
|
|
@@ -3834,6 +3741,113 @@ var COMMANDS = [
|
|
|
3834
3741
|
readOnly: true,
|
|
3835
3742
|
destructive: false
|
|
3836
3743
|
},
|
|
3744
|
+
{
|
|
3745
|
+
command: "workflow pull",
|
|
3746
|
+
tool: "workflow_files_list",
|
|
3747
|
+
title: "List workflow files",
|
|
3748
|
+
description: "List the workflow's editable head as a directory of files: the graph rendered as structural files (settings.yaml, per-node instructions.md, scripts) alongside the workflow's own files. Those files are referenced by fileId unless contents=all is set. Pass paths to read named files only, or contents=none for a manifest with no bodies. The returned rev is what you pass as baseRev to write.",
|
|
3749
|
+
method: "GET",
|
|
3750
|
+
path: "/agents/v2/workflows/{workflowId}/head/files",
|
|
3751
|
+
args: [
|
|
3752
|
+
{
|
|
3753
|
+
name: "workflowId",
|
|
3754
|
+
flag: "workflow-id",
|
|
3755
|
+
type: "string",
|
|
3756
|
+
required: true
|
|
3757
|
+
}
|
|
3758
|
+
],
|
|
3759
|
+
query: [
|
|
3760
|
+
{
|
|
3761
|
+
name: "contents",
|
|
3762
|
+
flag: "contents",
|
|
3763
|
+
type: "string",
|
|
3764
|
+
required: false,
|
|
3765
|
+
description: "Which contents come inline: none (a pure manifest), structural (the default), or all (structural plus file bytes within the response budget).",
|
|
3766
|
+
enum: ["none", "structural", "all"]
|
|
3767
|
+
},
|
|
3768
|
+
{
|
|
3769
|
+
name: "paths",
|
|
3770
|
+
flag: "paths",
|
|
3771
|
+
type: "string[]",
|
|
3772
|
+
required: false,
|
|
3773
|
+
description: "Return only the files at these exact tree paths. A path that names nothing yields no entry. The inline budget applies to the filtered set, so a narrow read can inline files a whole-tree read would have to skip."
|
|
3774
|
+
},
|
|
3775
|
+
{
|
|
3776
|
+
name: "variants",
|
|
3777
|
+
flag: "variants",
|
|
3778
|
+
type: "string",
|
|
3779
|
+
required: false,
|
|
3780
|
+
description: `Narrow which variants/<key>/ files the tree carries: "none" strips every variant, a comma-separated list keeps only those variant keys. Files outside variants/ are always kept, and the response's variantKeys still names every variant. Ignored unless the workflow has variant mode enabled; absent keeps every variant.`
|
|
3781
|
+
},
|
|
3782
|
+
{
|
|
3783
|
+
name: "maxFileBytes",
|
|
3784
|
+
flag: "max-file-bytes",
|
|
3785
|
+
type: "integer",
|
|
3786
|
+
required: false,
|
|
3787
|
+
description: "Per-file ceiling, in bytes, for inlined file contents. Clamped to the server maximum. Files above the ceiling stay references."
|
|
3788
|
+
},
|
|
3789
|
+
{
|
|
3790
|
+
name: "maxTotalBytes",
|
|
3791
|
+
flag: "max-total-bytes",
|
|
3792
|
+
type: "integer",
|
|
3793
|
+
required: false,
|
|
3794
|
+
description: "Ceiling, in bytes, on the total inlined file contents in one response. Clamped to the server maximum. Files are admitted in path order until the ceiling is reached; the rest stay references."
|
|
3795
|
+
}
|
|
3796
|
+
],
|
|
3797
|
+
binary: false,
|
|
3798
|
+
readOnly: true,
|
|
3799
|
+
destructive: false,
|
|
3800
|
+
files: "pull"
|
|
3801
|
+
},
|
|
3802
|
+
{
|
|
3803
|
+
command: "workflow push",
|
|
3804
|
+
tool: "workflow_files_patch",
|
|
3805
|
+
title: "Write workflow files",
|
|
3806
|
+
description: "Write and delete files on the workflow's editable head in one patch. Structural files re-derive the graph; the workflow's own files go to its file store, you just write paths. Pass the rev from `workflow_files_list` as baseRev; a stale value is rejected with 409, so re-read and retry. Editing a published or already-executed version forks a new one instead of rewriting it. Each write carries `contentBase64`: base64-encode the file content, plain text is rejected with 400.",
|
|
3807
|
+
method: "PATCH",
|
|
3808
|
+
path: "/agents/v2/workflows/{workflowId}/head/files",
|
|
3809
|
+
args: [
|
|
3810
|
+
{
|
|
3811
|
+
name: "workflowId",
|
|
3812
|
+
flag: "workflow-id",
|
|
3813
|
+
type: "string",
|
|
3814
|
+
required: true
|
|
3815
|
+
}
|
|
3816
|
+
],
|
|
3817
|
+
query: [],
|
|
3818
|
+
binary: false,
|
|
3819
|
+
readOnly: false,
|
|
3820
|
+
destructive: true,
|
|
3821
|
+
body: {
|
|
3822
|
+
required: true,
|
|
3823
|
+
contentType: "application/json",
|
|
3824
|
+
fields: [
|
|
3825
|
+
{
|
|
3826
|
+
name: "baseRev",
|
|
3827
|
+
flag: "base-rev",
|
|
3828
|
+
type: "integer",
|
|
3829
|
+
required: true,
|
|
3830
|
+
description: "The revision this edit is based on. Agent-file-only patches overlay the current head even when this is stale. A structural-file patch is rejected with 409 when it does not match the stored head revision."
|
|
3831
|
+
},
|
|
3832
|
+
{
|
|
3833
|
+
name: "writes",
|
|
3834
|
+
flag: "writes",
|
|
3835
|
+
type: "json",
|
|
3836
|
+
required: true,
|
|
3837
|
+
description: "Files to upsert."
|
|
3838
|
+
},
|
|
3839
|
+
{
|
|
3840
|
+
name: "deletes",
|
|
3841
|
+
flag: "deletes",
|
|
3842
|
+
type: "string[]",
|
|
3843
|
+
required: true
|
|
3844
|
+
}
|
|
3845
|
+
],
|
|
3846
|
+
files: [],
|
|
3847
|
+
credentialOrganization: false
|
|
3848
|
+
},
|
|
3849
|
+
files: "push"
|
|
3850
|
+
},
|
|
3837
3851
|
{
|
|
3838
3852
|
command: "workflow rename",
|
|
3839
3853
|
tool: "workflow_rename",
|
|
@@ -4236,15 +4250,16 @@ var COMMANDS = [
|
|
|
4236
4250
|
],
|
|
4237
4251
|
files: [],
|
|
4238
4252
|
credentialOrganization: false
|
|
4239
|
-
}
|
|
4253
|
+
},
|
|
4254
|
+
capability: "run_execution"
|
|
4240
4255
|
},
|
|
4241
4256
|
{
|
|
4242
|
-
command: "workflow version file
|
|
4243
|
-
tool: "
|
|
4244
|
-
title: "
|
|
4245
|
-
description: "
|
|
4257
|
+
command: "workflow version file url",
|
|
4258
|
+
tool: "workflow_version_file_signed_url",
|
|
4259
|
+
title: "Get workflow version file signed URL",
|
|
4260
|
+
description: "Get a short-lived signed URL for downloading one of a workflow version's files directly from storage. Prefer this over a redirect-based download when reading a file's contents.",
|
|
4246
4261
|
method: "GET",
|
|
4247
|
-
path: "/agents/v2/workflows/{workflowId}/versions/{versionId}/files",
|
|
4262
|
+
path: "/agents/v2/workflows/{workflowId}/versions/{versionId}/files/{fileId}/signed-url",
|
|
4248
4263
|
args: [
|
|
4249
4264
|
{
|
|
4250
4265
|
name: "workflowId",
|
|
@@ -4257,93 +4272,30 @@ var COMMANDS = [
|
|
|
4257
4272
|
flag: "version-id",
|
|
4258
4273
|
type: "string",
|
|
4259
4274
|
required: true
|
|
4275
|
+
},
|
|
4276
|
+
{
|
|
4277
|
+
name: "fileId",
|
|
4278
|
+
flag: "file-id",
|
|
4279
|
+
type: "string",
|
|
4280
|
+
required: true
|
|
4260
4281
|
}
|
|
4261
4282
|
],
|
|
4262
|
-
query: [
|
|
4283
|
+
query: [],
|
|
4284
|
+
binary: false,
|
|
4285
|
+
readOnly: true,
|
|
4286
|
+
destructive: false
|
|
4287
|
+
},
|
|
4288
|
+
{
|
|
4289
|
+
command: "workflow version get",
|
|
4290
|
+
tool: "workflow_version_get",
|
|
4291
|
+
title: "Get workflow version",
|
|
4292
|
+
description: "Get a full workflow version by `versionId`, including the complete node graph, rules, and settings.",
|
|
4293
|
+
method: "GET",
|
|
4294
|
+
path: "/agents/v2/workflows/{workflowId}/versions/{versionId}",
|
|
4295
|
+
args: [
|
|
4263
4296
|
{
|
|
4264
|
-
name: "
|
|
4265
|
-
flag: "
|
|
4266
|
-
type: "string",
|
|
4267
|
-
required: false,
|
|
4268
|
-
description: "Which contents come inline: none (a pure manifest), structural (the default), or all (structural plus file bytes within the response budget).",
|
|
4269
|
-
enum: ["none", "structural", "all"]
|
|
4270
|
-
},
|
|
4271
|
-
{
|
|
4272
|
-
name: "paths",
|
|
4273
|
-
flag: "paths",
|
|
4274
|
-
type: "string[]",
|
|
4275
|
-
required: false,
|
|
4276
|
-
description: "Return only the files at these exact tree paths. A path that names nothing yields no entry. The inline budget applies to the filtered set, so a narrow read can inline files a whole-tree read would have to skip."
|
|
4277
|
-
},
|
|
4278
|
-
{
|
|
4279
|
-
name: "variants",
|
|
4280
|
-
flag: "variants",
|
|
4281
|
-
type: "string",
|
|
4282
|
-
required: false,
|
|
4283
|
-
description: `Narrow which variants/<key>/ files the tree carries: "none" strips every variant, a comma-separated list keeps only those variant keys. Files outside variants/ are always kept, and the response's variantKeys still names every variant. Ignored unless the workflow has variant mode enabled; absent keeps every variant.`
|
|
4284
|
-
},
|
|
4285
|
-
{
|
|
4286
|
-
name: "maxFileBytes",
|
|
4287
|
-
flag: "max-file-bytes",
|
|
4288
|
-
type: "integer",
|
|
4289
|
-
required: false,
|
|
4290
|
-
description: "Per-file ceiling, in bytes, for inlined file contents. Clamped to the server maximum. Files above the ceiling stay references."
|
|
4291
|
-
},
|
|
4292
|
-
{
|
|
4293
|
-
name: "maxTotalBytes",
|
|
4294
|
-
flag: "max-total-bytes",
|
|
4295
|
-
type: "integer",
|
|
4296
|
-
required: false,
|
|
4297
|
-
description: "Ceiling, in bytes, on the total inlined file contents in one response. Clamped to the server maximum. Files are admitted in path order until the ceiling is reached; the rest stay references."
|
|
4298
|
-
}
|
|
4299
|
-
],
|
|
4300
|
-
binary: false,
|
|
4301
|
-
readOnly: true,
|
|
4302
|
-
destructive: false
|
|
4303
|
-
},
|
|
4304
|
-
{
|
|
4305
|
-
command: "workflow version file url",
|
|
4306
|
-
tool: "workflow_version_file_signed_url",
|
|
4307
|
-
title: "Get workflow version file signed URL",
|
|
4308
|
-
description: "Get a short-lived signed URL for downloading one of a workflow version's files directly from storage. Prefer this over a redirect-based download when reading a file's contents.",
|
|
4309
|
-
method: "GET",
|
|
4310
|
-
path: "/agents/v2/workflows/{workflowId}/versions/{versionId}/files/{fileId}/signed-url",
|
|
4311
|
-
args: [
|
|
4312
|
-
{
|
|
4313
|
-
name: "workflowId",
|
|
4314
|
-
flag: "workflow-id",
|
|
4315
|
-
type: "string",
|
|
4316
|
-
required: true
|
|
4317
|
-
},
|
|
4318
|
-
{
|
|
4319
|
-
name: "versionId",
|
|
4320
|
-
flag: "version-id",
|
|
4321
|
-
type: "string",
|
|
4322
|
-
required: true
|
|
4323
|
-
},
|
|
4324
|
-
{
|
|
4325
|
-
name: "fileId",
|
|
4326
|
-
flag: "file-id",
|
|
4327
|
-
type: "string",
|
|
4328
|
-
required: true
|
|
4329
|
-
}
|
|
4330
|
-
],
|
|
4331
|
-
query: [],
|
|
4332
|
-
binary: false,
|
|
4333
|
-
readOnly: true,
|
|
4334
|
-
destructive: false
|
|
4335
|
-
},
|
|
4336
|
-
{
|
|
4337
|
-
command: "workflow version get",
|
|
4338
|
-
tool: "workflow_version_get",
|
|
4339
|
-
title: "Get workflow version",
|
|
4340
|
-
description: "Get a full workflow version by `versionId`, including the complete node graph, rules, and settings.",
|
|
4341
|
-
method: "GET",
|
|
4342
|
-
path: "/agents/v2/workflows/{workflowId}/versions/{versionId}",
|
|
4343
|
-
args: [
|
|
4344
|
-
{
|
|
4345
|
-
name: "workflowId",
|
|
4346
|
-
flag: "workflow-id",
|
|
4297
|
+
name: "workflowId",
|
|
4298
|
+
flag: "workflow-id",
|
|
4347
4299
|
type: "string",
|
|
4348
4300
|
required: true
|
|
4349
4301
|
},
|
|
@@ -4455,7 +4407,72 @@ var COMMANDS = [
|
|
|
4455
4407
|
query: [],
|
|
4456
4408
|
binary: false,
|
|
4457
4409
|
readOnly: false,
|
|
4458
|
-
destructive: false
|
|
4410
|
+
destructive: false,
|
|
4411
|
+
capability: "publish"
|
|
4412
|
+
},
|
|
4413
|
+
{
|
|
4414
|
+
command: "workflow version pull",
|
|
4415
|
+
tool: "workflow_version_files_list",
|
|
4416
|
+
title: "List workflow version files",
|
|
4417
|
+
description: "List a version's files, like `workflow_files_list` does for the editable head. Versions are immutable, so this is read-only.",
|
|
4418
|
+
method: "GET",
|
|
4419
|
+
path: "/agents/v2/workflows/{workflowId}/versions/{versionId}/files",
|
|
4420
|
+
args: [
|
|
4421
|
+
{
|
|
4422
|
+
name: "workflowId",
|
|
4423
|
+
flag: "workflow-id",
|
|
4424
|
+
type: "string",
|
|
4425
|
+
required: true
|
|
4426
|
+
},
|
|
4427
|
+
{
|
|
4428
|
+
name: "versionId",
|
|
4429
|
+
flag: "version-id",
|
|
4430
|
+
type: "string",
|
|
4431
|
+
required: true
|
|
4432
|
+
}
|
|
4433
|
+
],
|
|
4434
|
+
query: [
|
|
4435
|
+
{
|
|
4436
|
+
name: "contents",
|
|
4437
|
+
flag: "contents",
|
|
4438
|
+
type: "string",
|
|
4439
|
+
required: false,
|
|
4440
|
+
description: "Which contents come inline: none (a pure manifest), structural (the default), or all (structural plus file bytes within the response budget).",
|
|
4441
|
+
enum: ["none", "structural", "all"]
|
|
4442
|
+
},
|
|
4443
|
+
{
|
|
4444
|
+
name: "paths",
|
|
4445
|
+
flag: "paths",
|
|
4446
|
+
type: "string[]",
|
|
4447
|
+
required: false,
|
|
4448
|
+
description: "Return only the files at these exact tree paths. A path that names nothing yields no entry. The inline budget applies to the filtered set, so a narrow read can inline files a whole-tree read would have to skip."
|
|
4449
|
+
},
|
|
4450
|
+
{
|
|
4451
|
+
name: "variants",
|
|
4452
|
+
flag: "variants",
|
|
4453
|
+
type: "string",
|
|
4454
|
+
required: false,
|
|
4455
|
+
description: `Narrow which variants/<key>/ files the tree carries: "none" strips every variant, a comma-separated list keeps only those variant keys. Files outside variants/ are always kept, and the response's variantKeys still names every variant. Ignored unless the workflow has variant mode enabled; absent keeps every variant.`
|
|
4456
|
+
},
|
|
4457
|
+
{
|
|
4458
|
+
name: "maxFileBytes",
|
|
4459
|
+
flag: "max-file-bytes",
|
|
4460
|
+
type: "integer",
|
|
4461
|
+
required: false,
|
|
4462
|
+
description: "Per-file ceiling, in bytes, for inlined file contents. Clamped to the server maximum. Files above the ceiling stay references."
|
|
4463
|
+
},
|
|
4464
|
+
{
|
|
4465
|
+
name: "maxTotalBytes",
|
|
4466
|
+
flag: "max-total-bytes",
|
|
4467
|
+
type: "integer",
|
|
4468
|
+
required: false,
|
|
4469
|
+
description: "Ceiling, in bytes, on the total inlined file contents in one response. Clamped to the server maximum. Files are admitted in path order until the ceiling is reached; the rest stay references."
|
|
4470
|
+
}
|
|
4471
|
+
],
|
|
4472
|
+
binary: false,
|
|
4473
|
+
readOnly: true,
|
|
4474
|
+
destructive: false,
|
|
4475
|
+
files: "pull"
|
|
4459
4476
|
},
|
|
4460
4477
|
{
|
|
4461
4478
|
command: "workflow version validate",
|
|
@@ -4555,16 +4572,16 @@ function isScalar(value2) {
|
|
|
4555
4572
|
return value2 === null || ["string", "number", "boolean"].includes(typeof value2);
|
|
4556
4573
|
}
|
|
4557
4574
|
function indent(text2) {
|
|
4558
|
-
return text2.split("\n").map((
|
|
4575
|
+
return text2.split("\n").map((line2) => ` ${line2}`).join("\n");
|
|
4559
4576
|
}
|
|
4560
4577
|
function text(value2) {
|
|
4561
4578
|
return clean(isScalar(value2) ? String(value2) : JSON.stringify(value2));
|
|
4562
4579
|
}
|
|
4563
4580
|
function cell(value2, options = {}) {
|
|
4564
4581
|
if (value2 === null || value2 === void 0) return "";
|
|
4565
|
-
const
|
|
4566
|
-
if (options.full ||
|
|
4567
|
-
return `${
|
|
4582
|
+
const line2 = text(value2).replace(/\s+/g, " ");
|
|
4583
|
+
if (options.full || line2.length <= MAX_CELL) return line2;
|
|
4584
|
+
return `${line2.slice(0, MAX_CELL - 1)}\u2026`;
|
|
4568
4585
|
}
|
|
4569
4586
|
function columns(rows, options) {
|
|
4570
4587
|
const scalarKeys = [];
|
|
@@ -4612,8 +4629,8 @@ function table(input, options = {}) {
|
|
|
4612
4629
|
const widths = keys.map(
|
|
4613
4630
|
(key, i) => Math.max(key.length, ...cells.map((r) => r[i].length))
|
|
4614
4631
|
);
|
|
4615
|
-
const
|
|
4616
|
-
return [
|
|
4632
|
+
const line2 = (values) => values.map((v, i) => v.padEnd(widths[i])).join(" ").trimEnd();
|
|
4633
|
+
return [line2(keys.map((k) => k.toUpperCase())), ...cells.map(line2)].join(
|
|
4617
4634
|
"\n"
|
|
4618
4635
|
);
|
|
4619
4636
|
}
|
|
@@ -4727,12 +4744,12 @@ var DOTTED = /^--([a-z0-9-]+\.[a-z0-9.-]+)(?:=(.*))?$/;
|
|
|
4727
4744
|
function collect(value2, previous) {
|
|
4728
4745
|
return [...previous ?? [], value2];
|
|
4729
4746
|
}
|
|
4730
|
-
function optionText(
|
|
4731
|
-
const text2 =
|
|
4747
|
+
function optionText(option2) {
|
|
4748
|
+
const text2 = option2.description?.replace(/\s+/g, " ").trim() ?? "";
|
|
4732
4749
|
const parts = [text2 && !/[.!?]$/.test(text2) ? `${text2}.` : text2];
|
|
4733
|
-
if (
|
|
4734
|
-
if (
|
|
4735
|
-
if (
|
|
4750
|
+
if (option2.enum) parts.push(`One of: ${option2.enum.join(", ")}.`);
|
|
4751
|
+
if (option2.type.endsWith("[]")) parts.push("Repeatable.");
|
|
4752
|
+
if (option2.required) parts.push("Required.");
|
|
4736
4753
|
return parts.filter(Boolean).join(" ");
|
|
4737
4754
|
}
|
|
4738
4755
|
function splitDotted(argv) {
|
|
@@ -4759,32 +4776,44 @@ function group(parent, name, groups, path) {
|
|
|
4759
4776
|
return created;
|
|
4760
4777
|
}
|
|
4761
4778
|
function leafOptions(leaf, command) {
|
|
4762
|
-
for (const
|
|
4763
|
-
if (
|
|
4764
|
-
leaf.option(`--${
|
|
4765
|
-
leaf.option(`--no-${
|
|
4779
|
+
for (const option2 of [...command.query, ...command.body?.fields ?? []]) {
|
|
4780
|
+
if (option2.type === "boolean") {
|
|
4781
|
+
leaf.option(`--${option2.flag}`, optionText(option2));
|
|
4782
|
+
leaf.option(`--no-${option2.flag}`, `Set ${option2.flag} to false.`);
|
|
4766
4783
|
continue;
|
|
4767
4784
|
}
|
|
4768
|
-
const placeholder =
|
|
4785
|
+
const placeholder = option2.enum ? option2.enum.join("|") : option2.type.replace("[]", "");
|
|
4769
4786
|
leaf.option(
|
|
4770
|
-
`--${
|
|
4771
|
-
optionText(
|
|
4787
|
+
`--${option2.flag} <${placeholder}>`,
|
|
4788
|
+
optionText(option2),
|
|
4772
4789
|
collect
|
|
4773
4790
|
);
|
|
4791
|
+
if (option2.negatable)
|
|
4792
|
+
leaf.option(`--no-${option2.flag}`, `Skip ${option2.flag}.`);
|
|
4774
4793
|
}
|
|
4775
4794
|
for (const file of command.body?.files ?? []) {
|
|
4776
4795
|
leaf.option(`--${file} <path>`, "File to upload. Repeatable.", collect);
|
|
4777
4796
|
}
|
|
4797
|
+
if (command.files === "pull")
|
|
4798
|
+
leaf.option(
|
|
4799
|
+
"--out <dir>",
|
|
4800
|
+
"Write here instead of ./<workflow-name>/head (a version: ./<workflow-name>/.versions/...). --json prints the file listing instead."
|
|
4801
|
+
);
|
|
4802
|
+
if (command.files === "push")
|
|
4803
|
+
leaf.option(
|
|
4804
|
+
"--from <dir>",
|
|
4805
|
+
"The pulled head folder to push. Defaults to the current folder."
|
|
4806
|
+
);
|
|
4778
4807
|
}
|
|
4779
4808
|
function toParsed(leaf, positionals, dotted) {
|
|
4780
4809
|
const globals = leaf.optsWithGlobals();
|
|
4781
4810
|
const own = leaf.opts();
|
|
4782
4811
|
const flags = new Map(dotted);
|
|
4783
|
-
for (const
|
|
4784
|
-
const
|
|
4785
|
-
const value2 = own[
|
|
4786
|
-
if (!
|
|
4787
|
-
flags.set(
|
|
4812
|
+
for (const option2 of leaf.options) {
|
|
4813
|
+
const flag2 = option2.long?.replace(/^--(no-)?/, "");
|
|
4814
|
+
const value2 = own[option2.attributeName()];
|
|
4815
|
+
if (!flag2 || value2 === void 0 || flags.has(flag2)) continue;
|
|
4816
|
+
flags.set(flag2, Array.isArray(value2) ? value2.map(String) : [String(value2)]);
|
|
4788
4817
|
}
|
|
4789
4818
|
return {
|
|
4790
4819
|
positionals,
|
|
@@ -4797,7 +4826,11 @@ function toParsed(leaf, positionals, dotted) {
|
|
|
4797
4826
|
}
|
|
4798
4827
|
function defineCommands(root, options, dotted = /* @__PURE__ */ new Map(), onInvocation = () => void 0) {
|
|
4799
4828
|
if (options.description) root.description(options.description);
|
|
4800
|
-
if (options.version)
|
|
4829
|
+
if (options.version)
|
|
4830
|
+
root.addHelpText(
|
|
4831
|
+
"after",
|
|
4832
|
+
"\nRun `asteroid --version` to print the CLI version."
|
|
4833
|
+
);
|
|
4801
4834
|
for (const [flags, text2] of GLOBAL_FLAGS) {
|
|
4802
4835
|
if (!flags.startsWith("-h")) root.option(flags, text2);
|
|
4803
4836
|
}
|
|
@@ -4859,8 +4892,8 @@ ${notes.join("\n")}`);
|
|
|
4859
4892
|
);
|
|
4860
4893
|
}
|
|
4861
4894
|
const unknownDotted = [...dotted.keys()].filter(
|
|
4862
|
-
(
|
|
4863
|
-
(f) =>
|
|
4895
|
+
(flag2) => !(command.body?.fields ?? []).some(
|
|
4896
|
+
(f) => flag2.startsWith(`${f.flag}.`)
|
|
4864
4897
|
)
|
|
4865
4898
|
);
|
|
4866
4899
|
if (unknownDotted.length > 0) {
|
|
@@ -4881,6 +4914,15 @@ async function parseInvocation(argv, options) {
|
|
|
4881
4914
|
let stdout = "";
|
|
4882
4915
|
let stderr = "";
|
|
4883
4916
|
let result;
|
|
4917
|
+
if (options.version && (argv[0] === "-v" || argv[0] === "--version")) {
|
|
4918
|
+
return {
|
|
4919
|
+
kind: "exit",
|
|
4920
|
+
code: 0,
|
|
4921
|
+
stdout: `${options.version}
|
|
4922
|
+
`,
|
|
4923
|
+
stderr: ""
|
|
4924
|
+
};
|
|
4925
|
+
}
|
|
4884
4926
|
const { argv: rest, dotted } = splitDotted(argv);
|
|
4885
4927
|
const root = new Command("asteroid").exitOverride().helpCommand(false).configureOutput({
|
|
4886
4928
|
writeOut: (text2) => stdout += text2,
|
|
@@ -4915,34 +4957,34 @@ import { basename } from "node:path";
|
|
|
4915
4957
|
function camel(segment) {
|
|
4916
4958
|
return segment.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
|
|
4917
4959
|
}
|
|
4918
|
-
function scalar(
|
|
4919
|
-
const type =
|
|
4920
|
-
if (
|
|
4960
|
+
function scalar(option2, raw) {
|
|
4961
|
+
const type = option2.type.replace("[]", "");
|
|
4962
|
+
if (option2.enum && !option2.enum.includes(raw)) {
|
|
4921
4963
|
throw new UsageError(
|
|
4922
|
-
`--${
|
|
4964
|
+
`--${option2.flag} must be one of ${option2.enum.join(", ")}`
|
|
4923
4965
|
);
|
|
4924
4966
|
}
|
|
4925
4967
|
if (type === "integer" || type === "number") {
|
|
4926
4968
|
const value2 = Number(raw);
|
|
4927
4969
|
if (raw.trim() === "" || Number.isNaN(value2) || type === "integer" && !Number.isInteger(value2)) {
|
|
4928
4970
|
throw new UsageError(
|
|
4929
|
-
`--${
|
|
4971
|
+
`--${option2.flag} must be ${type === "integer" ? "an integer" : "a number"}`
|
|
4930
4972
|
);
|
|
4931
4973
|
}
|
|
4932
4974
|
return value2;
|
|
4933
4975
|
}
|
|
4934
4976
|
if (type === "boolean") {
|
|
4935
4977
|
if (raw !== "true" && raw !== "false") {
|
|
4936
|
-
throw new UsageError(`--${
|
|
4978
|
+
throw new UsageError(`--${option2.flag} must be true or false`);
|
|
4937
4979
|
}
|
|
4938
4980
|
return raw === "true";
|
|
4939
4981
|
}
|
|
4940
|
-
if (type === "json") return parseJson(raw, `--${
|
|
4982
|
+
if (type === "json") return parseJson(raw, `--${option2.flag}`);
|
|
4941
4983
|
return raw;
|
|
4942
4984
|
}
|
|
4943
|
-
function value(
|
|
4944
|
-
if (
|
|
4945
|
-
return scalar(
|
|
4985
|
+
function value(option2, raws) {
|
|
4986
|
+
if (option2.type.endsWith("[]")) return raws.map((raw) => scalar(option2, raw));
|
|
4987
|
+
return scalar(option2, raws.at(-1));
|
|
4946
4988
|
}
|
|
4947
4989
|
var nodeReader = {
|
|
4948
4990
|
text: async (path) => readFileSync(path, "utf8"),
|
|
@@ -4954,12 +4996,12 @@ async function readInput(raw, io) {
|
|
|
4954
4996
|
return raw;
|
|
4955
4997
|
}
|
|
4956
4998
|
async function resolveInputs(command, parsed, io) {
|
|
4957
|
-
const jsonFlags = [...command.query, ...command.body?.fields ?? []].filter((
|
|
4999
|
+
const jsonFlags = [...command.query, ...command.body?.fields ?? []].filter((option2) => option2.type === "json").map((option2) => option2.flag);
|
|
4958
5000
|
const flags = new Map(parsed.flags);
|
|
4959
|
-
for (const
|
|
4960
|
-
const raws = flags.get(
|
|
5001
|
+
for (const flag2 of jsonFlags) {
|
|
5002
|
+
const raws = flags.get(flag2);
|
|
4961
5003
|
if (raws)
|
|
4962
|
-
flags.set(
|
|
5004
|
+
flags.set(flag2, await Promise.all(raws.map((raw) => readInput(raw, io))));
|
|
4963
5005
|
}
|
|
4964
5006
|
const body = parsed.body === void 0 ? void 0 : await readInput(parsed.body, io);
|
|
4965
5007
|
return { ...parsed, flags, body };
|
|
@@ -5009,18 +5051,18 @@ async function buildRequest(command, input, context = {}) {
|
|
|
5009
5051
|
});
|
|
5010
5052
|
const flags = new Map(parsed.flags);
|
|
5011
5053
|
const query = new URLSearchParams();
|
|
5012
|
-
for (const
|
|
5013
|
-
const raws = flags.get(
|
|
5014
|
-
flags.delete(
|
|
5054
|
+
for (const option2 of command.query) {
|
|
5055
|
+
const raws = flags.get(option2.flag);
|
|
5056
|
+
flags.delete(option2.flag);
|
|
5015
5057
|
if (!raws) {
|
|
5016
|
-
if (
|
|
5058
|
+
if (option2.required) throw new UsageError(`--${option2.flag} is required`);
|
|
5017
5059
|
continue;
|
|
5018
5060
|
}
|
|
5019
|
-
const parsedValue = value(
|
|
5061
|
+
const parsedValue = value(option2, raws);
|
|
5020
5062
|
const values = Array.isArray(parsedValue) ? parsedValue : [parsedValue];
|
|
5021
|
-
if (
|
|
5022
|
-
query.append(
|
|
5023
|
-
else for (const v of values) query.append(
|
|
5063
|
+
if (option2.explode === false)
|
|
5064
|
+
query.append(option2.name, values.map(String).join(","));
|
|
5065
|
+
else for (const v of values) query.append(option2.name, String(v));
|
|
5024
5066
|
}
|
|
5025
5067
|
const built = { method: command.method, path, query };
|
|
5026
5068
|
const body = command.body;
|
|
@@ -5070,11 +5112,11 @@ async function buildRequest(command, input, context = {}) {
|
|
|
5070
5112
|
flags.delete(field.flag);
|
|
5071
5113
|
json[field.name] = value(field, raws);
|
|
5072
5114
|
}
|
|
5073
|
-
for (const [
|
|
5074
|
-
const [head, ...rest] =
|
|
5115
|
+
for (const [flag2, raws] of [...flags]) {
|
|
5116
|
+
const [head, ...rest] = flag2.split(".");
|
|
5075
5117
|
const field = body.fields.find((f) => f.flag === head);
|
|
5076
5118
|
if (!field || rest.length === 0) continue;
|
|
5077
|
-
flags.delete(
|
|
5119
|
+
flags.delete(flag2);
|
|
5078
5120
|
const current = json[field.name];
|
|
5079
5121
|
json[field.name] = typeof current === "object" && current !== null ? current : {};
|
|
5080
5122
|
setPath(json, [field.name, ...rest.map(camel)], loose(raws.at(-1)));
|
|
@@ -5105,12 +5147,784 @@ function rejectUnknown(flags) {
|
|
|
5105
5147
|
);
|
|
5106
5148
|
}
|
|
5107
5149
|
}
|
|
5150
|
+
|
|
5151
|
+
// src/sync/commands.ts
|
|
5152
|
+
function option(name, type, description, extra = {}) {
|
|
5153
|
+
return { name, flag: name, type, required: false, description, ...extra };
|
|
5154
|
+
}
|
|
5155
|
+
var local = {
|
|
5156
|
+
method: "GET",
|
|
5157
|
+
path: "",
|
|
5158
|
+
binary: false,
|
|
5159
|
+
destructive: false,
|
|
5160
|
+
local: true
|
|
5161
|
+
};
|
|
5162
|
+
var SYNC_COMMANDS = [
|
|
5163
|
+
{
|
|
5164
|
+
...local,
|
|
5165
|
+
command: "execution pull",
|
|
5166
|
+
tool: "execution_pull",
|
|
5167
|
+
title: "Pull an execution to disk",
|
|
5168
|
+
description: "Write an execution to `<workflow-name>/executions/<id>/`: summary.json, activities.md (the step-by-step log), and the files the run produced.",
|
|
5169
|
+
readOnly: true,
|
|
5170
|
+
args: [
|
|
5171
|
+
{
|
|
5172
|
+
name: "executionId",
|
|
5173
|
+
flag: "execution-id",
|
|
5174
|
+
type: "string",
|
|
5175
|
+
required: true
|
|
5176
|
+
}
|
|
5177
|
+
],
|
|
5178
|
+
query: [
|
|
5179
|
+
option(
|
|
5180
|
+
"files",
|
|
5181
|
+
"string",
|
|
5182
|
+
"Only download files whose path matches this glob. --no-files skips downloads.",
|
|
5183
|
+
{
|
|
5184
|
+
negatable: true
|
|
5185
|
+
}
|
|
5186
|
+
),
|
|
5187
|
+
option(
|
|
5188
|
+
"summary-only",
|
|
5189
|
+
"boolean",
|
|
5190
|
+
"Write summary.json only: no activities, no files."
|
|
5191
|
+
),
|
|
5192
|
+
option(
|
|
5193
|
+
"max-file-size",
|
|
5194
|
+
"integer",
|
|
5195
|
+
"Skip files larger than this many bytes."
|
|
5196
|
+
),
|
|
5197
|
+
option(
|
|
5198
|
+
"dir",
|
|
5199
|
+
"string",
|
|
5200
|
+
"Write here instead of `<workflow-name>/executions/<id>/` under the current directory."
|
|
5201
|
+
)
|
|
5202
|
+
]
|
|
5203
|
+
}
|
|
5204
|
+
];
|
|
5205
|
+
|
|
5206
|
+
// src/commands.ts
|
|
5207
|
+
function withSyncCommands(commands) {
|
|
5208
|
+
return [...commands, ...SYNC_COMMANDS].sort(
|
|
5209
|
+
(a, b) => a.command.localeCompare(b.command)
|
|
5210
|
+
);
|
|
5211
|
+
}
|
|
5212
|
+
var ALL_COMMANDS = withSyncCommands(COMMANDS);
|
|
5213
|
+
|
|
5214
|
+
// src/sync/activities.ts
|
|
5215
|
+
function indentJson(data) {
|
|
5216
|
+
return JSON.stringify(data, null, 2).split("\n").map((line2) => ` ${line2}`).join("\n");
|
|
5217
|
+
}
|
|
5218
|
+
function action(label, payload) {
|
|
5219
|
+
const info = payload.info;
|
|
5220
|
+
const detail = info && Object.keys(info).length > 0 ? `
|
|
5221
|
+
info:
|
|
5222
|
+
${indentJson(info)}` : "";
|
|
5223
|
+
return `[${label}: ${payload.actionName}]:${detail}
|
|
5224
|
+
`;
|
|
5225
|
+
}
|
|
5226
|
+
function line(payload) {
|
|
5227
|
+
switch (payload.activityType) {
|
|
5228
|
+
case "reasoning":
|
|
5229
|
+
return `[reasoning]:
|
|
5230
|
+
${payload.reasoning}
|
|
5231
|
+
`;
|
|
5232
|
+
case "generic":
|
|
5233
|
+
return `[message]:
|
|
5234
|
+
${payload.message}
|
|
5235
|
+
`;
|
|
5236
|
+
case "user_message_received":
|
|
5237
|
+
return `[user_message]:
|
|
5238
|
+
${payload.message}
|
|
5239
|
+
`;
|
|
5240
|
+
case "action_started":
|
|
5241
|
+
return action("action_started", payload);
|
|
5242
|
+
case "action_completed":
|
|
5243
|
+
return action("action_completed", payload);
|
|
5244
|
+
case "action_failed":
|
|
5245
|
+
return `[action_failed: ${payload.actionName}]:
|
|
5246
|
+
failure: ${payload.message}
|
|
5247
|
+
`;
|
|
5248
|
+
case "compaction_performed":
|
|
5249
|
+
return `[compaction]: summary_tokens: ${payload.summaryTokens}, duration_ms: ${payload.durationMs}
|
|
5250
|
+
`;
|
|
5251
|
+
case "compaction_started":
|
|
5252
|
+
return "[compaction_started]\n";
|
|
5253
|
+
default: {
|
|
5254
|
+
const summary2 = payload.display?.summaryText;
|
|
5255
|
+
return summary2 ? `[${payload.activityType}]: ${summary2}
|
|
5256
|
+
` : `[${payload.activityType}]
|
|
5257
|
+
`;
|
|
5258
|
+
}
|
|
5259
|
+
}
|
|
5260
|
+
}
|
|
5261
|
+
function formatActivitiesMarkdown(activities, status, createdAt) {
|
|
5262
|
+
const lines = [
|
|
5263
|
+
"# Execution Activities\n",
|
|
5264
|
+
`Status: ${status}`,
|
|
5265
|
+
`Started: ${createdAt}`
|
|
5266
|
+
];
|
|
5267
|
+
let currentTurn = "";
|
|
5268
|
+
for (const { payload } of activities) {
|
|
5269
|
+
const turnId = payload.display?.turnId ?? "";
|
|
5270
|
+
if (turnId && turnId !== currentTurn) {
|
|
5271
|
+
currentTurn = turnId;
|
|
5272
|
+
lines.push(`
|
|
5273
|
+
---
|
|
5274
|
+
|
|
5275
|
+
## ${turnId}
|
|
5276
|
+
`);
|
|
5277
|
+
} else if (!currentTurn && !turnId) {
|
|
5278
|
+
currentTurn = "_started";
|
|
5279
|
+
lines.push("\n---\n");
|
|
5280
|
+
}
|
|
5281
|
+
lines.push(line(payload));
|
|
5282
|
+
}
|
|
5283
|
+
return lines.join("\n");
|
|
5284
|
+
}
|
|
5285
|
+
|
|
5286
|
+
// ../workflow-files/src/representation/name-charset.ts
|
|
5287
|
+
var NAME_CHARSET_RE = /^[A-Za-z0-9 !#$%&'()+,.;=@^`{}[\]~-]+$/;
|
|
5288
|
+
var WINDOWS_RESERVED = /* @__PURE__ */ new Set([
|
|
5289
|
+
"con",
|
|
5290
|
+
"prn",
|
|
5291
|
+
"aux",
|
|
5292
|
+
"nul",
|
|
5293
|
+
...Array.from({ length: 9 }, (_, i) => `com${i + 1}`),
|
|
5294
|
+
...Array.from({ length: 9 }, (_, i) => `lpt${i + 1}`)
|
|
5295
|
+
]);
|
|
5296
|
+
function isNameCharsetConforming(name) {
|
|
5297
|
+
return NAME_CHARSET_RE.test(name);
|
|
5298
|
+
}
|
|
5299
|
+
|
|
5300
|
+
// ../workflow-files/src/representation/types.ts
|
|
5301
|
+
function normalizeNodeName(name) {
|
|
5302
|
+
let result = name.toLowerCase();
|
|
5303
|
+
result = result.replace(/[^a-z0-9]+/g, "_");
|
|
5304
|
+
result = result.replace(/^_+|_+$/g, "");
|
|
5305
|
+
if (result.length > 80) {
|
|
5306
|
+
result = result.slice(0, 80).replace(/_+$/, "");
|
|
5307
|
+
}
|
|
5308
|
+
if (result === "") {
|
|
5309
|
+
throw new Error(
|
|
5310
|
+
`representation: node name "${name}" produces an empty directory name after normalization`
|
|
5311
|
+
);
|
|
5312
|
+
}
|
|
5313
|
+
return result;
|
|
5314
|
+
}
|
|
5315
|
+
function nodeDirName(name) {
|
|
5316
|
+
if (isNameCharsetConforming(name)) {
|
|
5317
|
+
return name.replaceAll(" ", "_");
|
|
5318
|
+
}
|
|
5319
|
+
return normalizeNodeName(name);
|
|
5320
|
+
}
|
|
5321
|
+
function agentDirName(name) {
|
|
5322
|
+
try {
|
|
5323
|
+
return nodeDirName(name);
|
|
5324
|
+
} catch {
|
|
5325
|
+
return "agent";
|
|
5326
|
+
}
|
|
5327
|
+
}
|
|
5328
|
+
function executionDirName(executionId) {
|
|
5329
|
+
return executionId.slice(0, 8);
|
|
5330
|
+
}
|
|
5331
|
+
function workflowDirName(workflowId, version, parentId, parentVersion) {
|
|
5332
|
+
const short = workflowId.slice(0, 8);
|
|
5333
|
+
if (version !== void 0 && version !== null) {
|
|
5334
|
+
return `v${version}_${short}`;
|
|
5335
|
+
}
|
|
5336
|
+
if (parentId) {
|
|
5337
|
+
if (parentVersion !== void 0 && parentVersion !== null) {
|
|
5338
|
+
return `draft_${short}_from_v${parentVersion}`;
|
|
5339
|
+
}
|
|
5340
|
+
return `draft_${short}_from_draft_${parentId.slice(0, 8)}`;
|
|
5341
|
+
}
|
|
5342
|
+
return `draft_${short}`;
|
|
5343
|
+
}
|
|
5344
|
+
|
|
5345
|
+
// src/sync/paths.ts
|
|
5346
|
+
import { createHash } from "node:crypto";
|
|
5347
|
+
function join(...parts) {
|
|
5348
|
+
return parts.filter((part) => part !== "").join("/").replace(/\/{2,}/g, "/");
|
|
5349
|
+
}
|
|
5350
|
+
function workflowRoot(root, workflowName) {
|
|
5351
|
+
return join(root, agentDirName(workflowName));
|
|
5352
|
+
}
|
|
5353
|
+
function isContained(path) {
|
|
5354
|
+
if (path === "" || path.startsWith("/") || path.includes("\\")) return false;
|
|
5355
|
+
return path.split("/").every((segment) => segment !== ".." && segment !== "");
|
|
5356
|
+
}
|
|
5357
|
+
function sha256(bytes) {
|
|
5358
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
5359
|
+
}
|
|
5360
|
+
function matchesGlob(path, pattern) {
|
|
5361
|
+
let source = "";
|
|
5362
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
5363
|
+
const ch = pattern[i];
|
|
5364
|
+
if (ch === "*" && pattern[i + 1] === "*" && pattern[i + 2] === "/") {
|
|
5365
|
+
source += "(?:.*/)?";
|
|
5366
|
+
i += 2;
|
|
5367
|
+
} else if (ch === "*" && pattern[i + 1] === "*") {
|
|
5368
|
+
source += ".*";
|
|
5369
|
+
i += 1;
|
|
5370
|
+
} else if (ch === "*") source += "[^/]*";
|
|
5371
|
+
else if (ch === "?") source += "[^/]";
|
|
5372
|
+
else source += ch.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
5373
|
+
}
|
|
5374
|
+
return new RegExp(`^${source}$`).test(path);
|
|
5375
|
+
}
|
|
5376
|
+
|
|
5377
|
+
// src/sync/types.ts
|
|
5378
|
+
var MANIFEST = ".asteroid.json";
|
|
5379
|
+
function get(path, query = {}) {
|
|
5380
|
+
return { method: "GET", path, query: new URLSearchParams(query) };
|
|
5381
|
+
}
|
|
5382
|
+
function statusOf(error) {
|
|
5383
|
+
const status = error?.status;
|
|
5384
|
+
return typeof status === "number" ? status : void 0;
|
|
5385
|
+
}
|
|
5386
|
+
|
|
5387
|
+
// src/sync/execution-pull.ts
|
|
5388
|
+
var DIRECTORIES = ["workspace", "downloads", "shared"];
|
|
5389
|
+
function relativeFilePath(file) {
|
|
5390
|
+
const at = file.filePath.indexOf(`/${file.directory}/`);
|
|
5391
|
+
if (at === -1)
|
|
5392
|
+
return `${file.directory}/${file.filePath.split("/").pop() ?? "unknown"}`;
|
|
5393
|
+
return file.filePath.slice(at + 1).replace(/^workspace\/\.execution-context\//, "");
|
|
5394
|
+
}
|
|
5395
|
+
function executionDir(options, execution) {
|
|
5396
|
+
const chosen = options.dir?.(execution);
|
|
5397
|
+
if (chosen) return chosen;
|
|
5398
|
+
const root = workflowRoot(
|
|
5399
|
+
options.root,
|
|
5400
|
+
execution.workflowName ?? execution.workflowId
|
|
5401
|
+
);
|
|
5402
|
+
return join(root, "executions", executionDirName(execution.id));
|
|
5403
|
+
}
|
|
5404
|
+
async function writeText(sync, path, text2) {
|
|
5405
|
+
await sync.files.write(path, Buffer.from(text2, "utf8"));
|
|
5406
|
+
}
|
|
5407
|
+
async function writeActivities(sync, dir, execution) {
|
|
5408
|
+
try {
|
|
5409
|
+
const activities = await sync.remote.json(
|
|
5410
|
+
get(`/agents/v2/executions/${execution.id}/activities`, { order: "asc" })
|
|
5411
|
+
);
|
|
5412
|
+
if (activities.length === 0) return false;
|
|
5413
|
+
const markdown = formatActivitiesMarkdown(
|
|
5414
|
+
activities,
|
|
5415
|
+
execution.status,
|
|
5416
|
+
execution.createdAt
|
|
5417
|
+
);
|
|
5418
|
+
await writeText(sync, join(dir, "activities.md"), markdown);
|
|
5419
|
+
return true;
|
|
5420
|
+
} catch {
|
|
5421
|
+
return false;
|
|
5422
|
+
}
|
|
5423
|
+
}
|
|
5424
|
+
async function listFiles(sync, executionId) {
|
|
5425
|
+
const listing = await sync.remote.json(
|
|
5426
|
+
get(`/agents/v2/executions/${executionId}/files`)
|
|
5427
|
+
);
|
|
5428
|
+
return DIRECTORIES.flatMap((name) => listing.directories[name] ?? []);
|
|
5429
|
+
}
|
|
5430
|
+
async function download(sync, executionId, file) {
|
|
5431
|
+
const path = `/agents/v2/executions/${executionId}/files/${file.id}/signed-url`;
|
|
5432
|
+
const { url } = await sync.remote.json(get(path));
|
|
5433
|
+
return sync.remote.download(url);
|
|
5434
|
+
}
|
|
5435
|
+
function selectFiles(files, options, skipped) {
|
|
5436
|
+
return files.filter((file) => {
|
|
5437
|
+
const path = relativeFilePath(file);
|
|
5438
|
+
if (!isContained(path)) skipped.push(`${path} (unsafe path)`);
|
|
5439
|
+
else if (options.glob !== void 0 && !matchesGlob(path, options.glob))
|
|
5440
|
+
skipped.push(`${path} (excluded by --files)`);
|
|
5441
|
+
else if (options.maxFileBytes !== void 0 && file.fileSize > options.maxFileBytes)
|
|
5442
|
+
skipped.push(`${path} (${file.fileSize} bytes, over the size limit)`);
|
|
5443
|
+
else return true;
|
|
5444
|
+
return false;
|
|
5445
|
+
});
|
|
5446
|
+
}
|
|
5447
|
+
async function pullFiles(sync, dir, options) {
|
|
5448
|
+
const skipped = [];
|
|
5449
|
+
const failed = [];
|
|
5450
|
+
let files;
|
|
5451
|
+
try {
|
|
5452
|
+
files = await listFiles(sync, options.executionId);
|
|
5453
|
+
} catch (error) {
|
|
5454
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
5455
|
+
return {
|
|
5456
|
+
downloaded: 0,
|
|
5457
|
+
candidates: 0,
|
|
5458
|
+
skipped,
|
|
5459
|
+
failed: [`file listing (${reason})`]
|
|
5460
|
+
};
|
|
5461
|
+
}
|
|
5462
|
+
let downloaded = 0;
|
|
5463
|
+
await Promise.all(
|
|
5464
|
+
selectFiles(files, options, skipped).map(async (file) => {
|
|
5465
|
+
try {
|
|
5466
|
+
const bytes = await download(sync, options.executionId, file);
|
|
5467
|
+
await sync.files.write(join(dir, relativeFilePath(file)), bytes);
|
|
5468
|
+
downloaded++;
|
|
5469
|
+
} catch (error) {
|
|
5470
|
+
failed.push(
|
|
5471
|
+
`${file.fileName} (${error instanceof Error ? error.message : String(error)})`
|
|
5472
|
+
);
|
|
5473
|
+
}
|
|
5474
|
+
})
|
|
5475
|
+
);
|
|
5476
|
+
return { downloaded, candidates: files.length, skipped, failed };
|
|
5477
|
+
}
|
|
5478
|
+
async function pullExecution(sync, options) {
|
|
5479
|
+
const execution = await sync.remote.json(
|
|
5480
|
+
get(`/agents/v2/executions/${options.executionId}`)
|
|
5481
|
+
);
|
|
5482
|
+
const dir = executionDir(options, execution);
|
|
5483
|
+
await writeText(
|
|
5484
|
+
sync,
|
|
5485
|
+
join(dir, "summary.json"),
|
|
5486
|
+
JSON.stringify(execution, null, 2)
|
|
5487
|
+
);
|
|
5488
|
+
const result = {
|
|
5489
|
+
execution,
|
|
5490
|
+
dir,
|
|
5491
|
+
written: ["summary.json"],
|
|
5492
|
+
downloaded: 0,
|
|
5493
|
+
candidates: 0,
|
|
5494
|
+
skipped: [],
|
|
5495
|
+
failed: []
|
|
5496
|
+
};
|
|
5497
|
+
if (execution.purgedAt) return result;
|
|
5498
|
+
if (options.activities !== false && await writeActivities(sync, dir, execution))
|
|
5499
|
+
result.written.push("activities.md");
|
|
5500
|
+
if (options.files === false) {
|
|
5501
|
+
return {
|
|
5502
|
+
...result,
|
|
5503
|
+
skippedBy: options.activities === false ? "--summary-only" : "--no-files"
|
|
5504
|
+
};
|
|
5505
|
+
}
|
|
5506
|
+
return { ...result, ...await pullFiles(sync, dir, options) };
|
|
5507
|
+
}
|
|
5508
|
+
function filesLine(result) {
|
|
5509
|
+
const parts = [...result.written];
|
|
5510
|
+
if (result.downloaded > 0) parts.push(`${result.downloaded} downloaded`);
|
|
5511
|
+
const counted = `Files: ${result.written.length + result.downloaded} written (${parts.join(", ")})`;
|
|
5512
|
+
if (result.skippedBy)
|
|
5513
|
+
return `${counted}; downloads skipped (${result.skippedBy})`;
|
|
5514
|
+
if (result.candidates === 0) return `${counted}; no execution files matched`;
|
|
5515
|
+
return counted;
|
|
5516
|
+
}
|
|
5517
|
+
function failures(execution) {
|
|
5518
|
+
const list = execution.scriptFailures ?? [];
|
|
5519
|
+
if (list.length === 0) return null;
|
|
5520
|
+
const lines = list.map(
|
|
5521
|
+
(f) => ` - ${f.nodeName ?? "(unknown node)"}: ${(f.error ?? "").split("\n")[0]}`
|
|
5522
|
+
);
|
|
5523
|
+
return `Script failures: ${list.length}
|
|
5524
|
+
${lines.join("\n")}`;
|
|
5525
|
+
}
|
|
5526
|
+
function purged(result) {
|
|
5527
|
+
const { execution } = result;
|
|
5528
|
+
return [
|
|
5529
|
+
`Execution: ${execution.id}`,
|
|
5530
|
+
`Status: ${execution.status}`,
|
|
5531
|
+
`Purged: ${execution.purgedAt}`,
|
|
5532
|
+
"This run was retention-purged. Activities, files, inputs and result are gone. Do not diagnose it.",
|
|
5533
|
+
`Written to: ${result.dir}/`
|
|
5534
|
+
].join("\n");
|
|
5535
|
+
}
|
|
5536
|
+
function formatExecutionPull(result) {
|
|
5537
|
+
const { execution } = result;
|
|
5538
|
+
if (execution.purgedAt) return purged(result);
|
|
5539
|
+
const outcome = execution.executionResult;
|
|
5540
|
+
return [
|
|
5541
|
+
`Execution: ${execution.id}`,
|
|
5542
|
+
`Status: ${execution.status}`,
|
|
5543
|
+
`Workflow: ${execution.workflowName ?? execution.workflowId}`,
|
|
5544
|
+
execution.workflowVersionId ? `Workflow version: ${execution.workflowVersionId}` : null,
|
|
5545
|
+
execution.inputs ? `Inputs: ${JSON.stringify(execution.inputs)}` : null,
|
|
5546
|
+
outcome ? `Result: ${outcome.outcome}: ${outcome.reasoning}` : null,
|
|
5547
|
+
`Created: ${execution.createdAt}`,
|
|
5548
|
+
execution.terminalAt ? `Completed: ${execution.terminalAt}` : null,
|
|
5549
|
+
execution.duration != null ? `Duration: ${execution.duration.toFixed(1)}s` : null,
|
|
5550
|
+
failures(execution),
|
|
5551
|
+
filesLine(result),
|
|
5552
|
+
result.skipped.length > 0 ? `Skipped:
|
|
5553
|
+
${result.skipped.map((s) => ` - ${s}`).join("\n")}` : null,
|
|
5554
|
+
result.failed.length > 0 ? `Failed: ${result.failed.join(", ")}` : null,
|
|
5555
|
+
`Written to: ${result.dir}/`
|
|
5556
|
+
].filter(Boolean).join("\n");
|
|
5557
|
+
}
|
|
5558
|
+
|
|
5559
|
+
// src/sync/node-files.ts
|
|
5560
|
+
import { mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
5561
|
+
import { dirname, relative, resolve, sep } from "node:path";
|
|
5562
|
+
function missing(error) {
|
|
5563
|
+
return error.code === "ENOENT";
|
|
5564
|
+
}
|
|
5565
|
+
async function walk(root, dir, out) {
|
|
5566
|
+
for (const entry of await readdir(dir, { withFileTypes: true })) {
|
|
5567
|
+
const path = resolve(dir, entry.name);
|
|
5568
|
+
if (entry.isDirectory()) await walk(root, path, out);
|
|
5569
|
+
else if (entry.isFile())
|
|
5570
|
+
out.push(relative(root, path).split(sep).join("/"));
|
|
5571
|
+
}
|
|
5572
|
+
}
|
|
5573
|
+
function nodeFiles(cwd) {
|
|
5574
|
+
const at = (path) => resolve(cwd, path);
|
|
5575
|
+
return {
|
|
5576
|
+
async read(path) {
|
|
5577
|
+
try {
|
|
5578
|
+
return new Uint8Array(await readFile(at(path)));
|
|
5579
|
+
} catch (error) {
|
|
5580
|
+
if (missing(error)) return void 0;
|
|
5581
|
+
throw error;
|
|
5582
|
+
}
|
|
5583
|
+
},
|
|
5584
|
+
async write(path, bytes) {
|
|
5585
|
+
await mkdir(dirname(at(path)), { recursive: true });
|
|
5586
|
+
await writeFile(at(path), bytes);
|
|
5587
|
+
},
|
|
5588
|
+
async remove(path) {
|
|
5589
|
+
await rm(at(path), { force: true });
|
|
5590
|
+
},
|
|
5591
|
+
async list(dir) {
|
|
5592
|
+
const out = [];
|
|
5593
|
+
try {
|
|
5594
|
+
await walk(at(dir), at(dir), out);
|
|
5595
|
+
} catch (error) {
|
|
5596
|
+
if (!missing(error)) throw error;
|
|
5597
|
+
}
|
|
5598
|
+
return out.sort();
|
|
5599
|
+
}
|
|
5600
|
+
};
|
|
5601
|
+
}
|
|
5602
|
+
|
|
5603
|
+
// src/sync/workflow-pull.ts
|
|
5604
|
+
var CONCURRENCY = 16;
|
|
5605
|
+
function pullDir(root, tree, head) {
|
|
5606
|
+
const base = workflowRoot(root, tree.workflowName);
|
|
5607
|
+
if (head) return join(base, "head");
|
|
5608
|
+
return join(base, ".versions", workflowDirName(tree.versionId, tree.version));
|
|
5609
|
+
}
|
|
5610
|
+
async function agentFileBytes(sync, workflowId, tree, entry) {
|
|
5611
|
+
const path = `/agents/v2/workflows/${workflowId}/versions/${tree.versionId}/files/${entry.fileId}/signed-url`;
|
|
5612
|
+
const { url } = await sync.remote.json(get(path));
|
|
5613
|
+
return sync.remote.download(url);
|
|
5614
|
+
}
|
|
5615
|
+
async function entryBytes(sync, workflowId, tree, entry) {
|
|
5616
|
+
if (entry.contentBase64 !== void 0)
|
|
5617
|
+
return Buffer.from(entry.contentBase64, "base64");
|
|
5618
|
+
if (entry.content !== void 0) return Buffer.from(entry.content, "utf8");
|
|
5619
|
+
if (!entry.fileId) return void 0;
|
|
5620
|
+
return agentFileBytes(sync, workflowId, tree, entry);
|
|
5621
|
+
}
|
|
5622
|
+
async function writeEntries(sync, options, tree, dir) {
|
|
5623
|
+
const hashes = {};
|
|
5624
|
+
const skipped = [];
|
|
5625
|
+
let shared = 0;
|
|
5626
|
+
const wanted = tree.files.filter((entry) => {
|
|
5627
|
+
if (!isContained(entry.path)) skipped.push(`${entry.path} (unsafe path)`);
|
|
5628
|
+
else if (options.maxFileBytes !== void 0 && entry.size > options.maxFileBytes)
|
|
5629
|
+
skipped.push(`${entry.path} (${entry.size} bytes, over the size limit)`);
|
|
5630
|
+
else return true;
|
|
5631
|
+
return false;
|
|
5632
|
+
});
|
|
5633
|
+
for (let i = 0; i < wanted.length; i += CONCURRENCY) {
|
|
5634
|
+
await Promise.all(
|
|
5635
|
+
wanted.slice(i, i + CONCURRENCY).map(async (entry) => {
|
|
5636
|
+
const bytes = await entryBytes(sync, options.workflowId, tree, entry);
|
|
5637
|
+
if (!bytes) return void skipped.push(`${entry.path} (no content)`);
|
|
5638
|
+
await sync.files.write(join(dir, entry.path), bytes);
|
|
5639
|
+
hashes[entry.path] = sha256(bytes);
|
|
5640
|
+
if (entry.kind === "agent_file") shared++;
|
|
5641
|
+
})
|
|
5642
|
+
);
|
|
5643
|
+
}
|
|
5644
|
+
return { hashes, skipped, shared };
|
|
5645
|
+
}
|
|
5646
|
+
async function removeStale(sync, dir, keep) {
|
|
5647
|
+
for (const path of await sync.files.list(dir)) {
|
|
5648
|
+
if (!keep.has(path) && path !== MANIFEST)
|
|
5649
|
+
await sync.files.remove(join(dir, path));
|
|
5650
|
+
}
|
|
5651
|
+
}
|
|
5652
|
+
async function writeManifest(sync, dir, manifest) {
|
|
5653
|
+
const text2 = `${JSON.stringify(manifest, null, 2)}
|
|
5654
|
+
`;
|
|
5655
|
+
await sync.files.write(join(dir, MANIFEST), Buffer.from(text2, "utf8"));
|
|
5656
|
+
}
|
|
5657
|
+
async function pullWorkflow(sync, options) {
|
|
5658
|
+
const request = {
|
|
5659
|
+
...options.request,
|
|
5660
|
+
query: new URLSearchParams(options.request.query)
|
|
5661
|
+
};
|
|
5662
|
+
request.query.set("contents", "all");
|
|
5663
|
+
const tree = await sync.remote.json(request);
|
|
5664
|
+
const head = request.path.endsWith("/head/files");
|
|
5665
|
+
const dir = options.dir ?? pullDir(options.root ?? "", tree, head);
|
|
5666
|
+
const { hashes, skipped, shared } = await writeEntries(
|
|
5667
|
+
sync,
|
|
5668
|
+
options,
|
|
5669
|
+
tree,
|
|
5670
|
+
dir
|
|
5671
|
+
);
|
|
5672
|
+
await removeStale(sync, dir, new Set(Object.keys(hashes)));
|
|
5673
|
+
if (options.manifest !== false) {
|
|
5674
|
+
await writeManifest(sync, dir, {
|
|
5675
|
+
workflowId: options.workflowId,
|
|
5676
|
+
versionId: tree.versionId,
|
|
5677
|
+
head,
|
|
5678
|
+
rev: tree.rev,
|
|
5679
|
+
files: hashes
|
|
5680
|
+
});
|
|
5681
|
+
}
|
|
5682
|
+
return {
|
|
5683
|
+
workflowId: options.workflowId,
|
|
5684
|
+
workflowName: tree.workflowName,
|
|
5685
|
+
versionId: tree.versionId,
|
|
5686
|
+
version: tree.version,
|
|
5687
|
+
rev: tree.rev,
|
|
5688
|
+
dir,
|
|
5689
|
+
head,
|
|
5690
|
+
written: Object.keys(hashes).length,
|
|
5691
|
+
shared,
|
|
5692
|
+
skipped
|
|
5693
|
+
};
|
|
5694
|
+
}
|
|
5695
|
+
function formatWorkflowPull(result) {
|
|
5696
|
+
const definition = result.written - result.shared;
|
|
5697
|
+
const files = result.written === 0 ? "Files: none written" : result.shared === 0 ? `Files: ${result.written} written` : `Files: ${result.written} written (${definition} workflow definition, ${result.shared} shared)`;
|
|
5698
|
+
return [
|
|
5699
|
+
`Workflow: ${result.workflowName} (${result.workflowId})`,
|
|
5700
|
+
`Workflow version: ${result.versionId}${result.version ? ` (v${result.version})` : " (draft)"}`,
|
|
5701
|
+
`Written to: ${result.dir}/`,
|
|
5702
|
+
files,
|
|
5703
|
+
result.skipped.length > 0 ? `Skipped:
|
|
5704
|
+
${result.skipped.map((s) => ` - ${s}`).join("\n")}` : null,
|
|
5705
|
+
result.head ? `Edit the files, then run \`asteroid workflow push\` from ${result.dir}/.` : "This is a pinned version. It is read-only."
|
|
5706
|
+
].filter(Boolean).join("\n");
|
|
5707
|
+
}
|
|
5708
|
+
|
|
5709
|
+
// src/sync/workflow-push.ts
|
|
5710
|
+
async function readManifest(sync, dir) {
|
|
5711
|
+
const bytes = await sync.files.read(join(dir, MANIFEST));
|
|
5712
|
+
if (!bytes) {
|
|
5713
|
+
throw new UsageError(
|
|
5714
|
+
`no pulled workflow in ${dir}. Run \`asteroid workflow pull <workflow-id>\`, then push from the head folder it writes.`
|
|
5715
|
+
);
|
|
5716
|
+
}
|
|
5717
|
+
const manifest = JSON.parse(Buffer.from(bytes).toString("utf8"));
|
|
5718
|
+
if (!manifest.head) {
|
|
5719
|
+
throw new UsageError(
|
|
5720
|
+
`${dir} holds a pinned workflow version, which is read-only. Push from a pulled head.`
|
|
5721
|
+
);
|
|
5722
|
+
}
|
|
5723
|
+
return manifest;
|
|
5724
|
+
}
|
|
5725
|
+
async function localChanges(sync, dir, manifest) {
|
|
5726
|
+
const local2 = (await sync.files.list(dir)).filter(
|
|
5727
|
+
(path) => path !== MANIFEST
|
|
5728
|
+
);
|
|
5729
|
+
const writes = [];
|
|
5730
|
+
for (const path of local2) {
|
|
5731
|
+
const bytes = await sync.files.read(join(dir, path));
|
|
5732
|
+
if (!bytes) continue;
|
|
5733
|
+
const hash = sha256(bytes);
|
|
5734
|
+
if (manifest.files[path] === hash) continue;
|
|
5735
|
+
writes.push({
|
|
5736
|
+
path,
|
|
5737
|
+
contentBase64: Buffer.from(bytes).toString("base64"),
|
|
5738
|
+
hash
|
|
5739
|
+
});
|
|
5740
|
+
}
|
|
5741
|
+
const present = new Set(local2);
|
|
5742
|
+
const deletes = Object.keys(manifest.files).filter(
|
|
5743
|
+
(path) => !present.has(path)
|
|
5744
|
+
);
|
|
5745
|
+
return { writes, deletes };
|
|
5746
|
+
}
|
|
5747
|
+
async function patch(sync, manifest, writes, deletes) {
|
|
5748
|
+
try {
|
|
5749
|
+
await sync.remote.json({
|
|
5750
|
+
method: "PATCH",
|
|
5751
|
+
path: `/agents/v2/workflows/${manifest.workflowId}/head/files`,
|
|
5752
|
+
query: new URLSearchParams(),
|
|
5753
|
+
json: { baseRev: manifest.rev, writes, deletes }
|
|
5754
|
+
});
|
|
5755
|
+
} catch (error) {
|
|
5756
|
+
if (statusOf(error) !== 409) throw error;
|
|
5757
|
+
throw new UsageError(
|
|
5758
|
+
`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.`
|
|
5759
|
+
);
|
|
5760
|
+
}
|
|
5761
|
+
}
|
|
5762
|
+
async function refresh(sync, dir, manifest, pushed) {
|
|
5763
|
+
const tree = await sync.remote.json(
|
|
5764
|
+
get(`/agents/v2/workflows/${manifest.workflowId}/head/files`)
|
|
5765
|
+
);
|
|
5766
|
+
const files = {};
|
|
5767
|
+
for (const entry of tree.files) {
|
|
5768
|
+
if (entry.content !== void 0) {
|
|
5769
|
+
const bytes = Buffer.from(entry.content, "utf8");
|
|
5770
|
+
files[entry.path] = sha256(bytes);
|
|
5771
|
+
if (pushed.get(entry.path) !== files[entry.path])
|
|
5772
|
+
await sync.files.write(join(dir, entry.path), bytes);
|
|
5773
|
+
} else {
|
|
5774
|
+
const local2 = await sync.files.read(join(dir, entry.path));
|
|
5775
|
+
const hash = local2 ? sha256(local2) : pushed.get(entry.path) ?? entry.checksum;
|
|
5776
|
+
if (hash) files[entry.path] = hash;
|
|
5777
|
+
}
|
|
5778
|
+
}
|
|
5779
|
+
for (const path of await sync.files.list(dir)) {
|
|
5780
|
+
if (path !== MANIFEST && !(path in files))
|
|
5781
|
+
await sync.files.remove(join(dir, path));
|
|
5782
|
+
}
|
|
5783
|
+
const next = { ...manifest, versionId: tree.versionId, rev: tree.rev, files };
|
|
5784
|
+
await writeManifest(sync, dir, next);
|
|
5785
|
+
return next;
|
|
5786
|
+
}
|
|
5787
|
+
async function pushWorkflow(sync, dir, workflowId) {
|
|
5788
|
+
const manifest = await readManifest(sync, dir);
|
|
5789
|
+
if (workflowId !== void 0 && manifest.workflowId !== workflowId) {
|
|
5790
|
+
throw new UsageError(
|
|
5791
|
+
`${dir} holds workflow ${manifest.workflowId}, not ${workflowId}.`
|
|
5792
|
+
);
|
|
5793
|
+
}
|
|
5794
|
+
const { writes, deletes } = await localChanges(sync, dir, manifest);
|
|
5795
|
+
const result = {
|
|
5796
|
+
workflowId: manifest.workflowId,
|
|
5797
|
+
dir,
|
|
5798
|
+
written: writes.map((w) => w.path),
|
|
5799
|
+
deleted: deletes
|
|
5800
|
+
};
|
|
5801
|
+
if (writes.length === 0 && deletes.length === 0) {
|
|
5802
|
+
return { ...result, rev: manifest.rev, versionId: manifest.versionId };
|
|
5803
|
+
}
|
|
5804
|
+
await patch(
|
|
5805
|
+
sync,
|
|
5806
|
+
manifest,
|
|
5807
|
+
writes.map(({ path, contentBase64 }) => ({ path, contentBase64 })),
|
|
5808
|
+
deletes
|
|
5809
|
+
);
|
|
5810
|
+
const next = await refresh(
|
|
5811
|
+
sync,
|
|
5812
|
+
dir,
|
|
5813
|
+
manifest,
|
|
5814
|
+
new Map(writes.map((w) => [w.path, w.hash]))
|
|
5815
|
+
);
|
|
5816
|
+
return { ...result, rev: next.rev, versionId: next.versionId };
|
|
5817
|
+
}
|
|
5818
|
+
function formatWorkflowPush(result) {
|
|
5819
|
+
if (result.written.length === 0 && result.deleted.length === 0) {
|
|
5820
|
+
return `Nothing to push: ${result.dir}/ matches the head you pulled (rev ${result.rev}).`;
|
|
5821
|
+
}
|
|
5822
|
+
return [
|
|
5823
|
+
`Pushed to workflow ${result.workflowId}: ${result.written.length} written, ${result.deleted.length} deleted.`,
|
|
5824
|
+
...result.written.map((path) => ` + ${path}`),
|
|
5825
|
+
...result.deleted.map((path) => ` - ${path}`),
|
|
5826
|
+
`Head is now rev ${result.rev} (version ${result.versionId}).`
|
|
5827
|
+
].join("\n");
|
|
5828
|
+
}
|
|
5829
|
+
|
|
5830
|
+
// src/sync/run.ts
|
|
5831
|
+
function flag(parsed, name) {
|
|
5832
|
+
return parsed.flags.get(name)?.at(-1);
|
|
5833
|
+
}
|
|
5834
|
+
function integer(parsed, name) {
|
|
5835
|
+
const raw = flag(parsed, name);
|
|
5836
|
+
if (raw === void 0) return void 0;
|
|
5837
|
+
if (!/^\d+$/.test(raw))
|
|
5838
|
+
throw new UsageError(`--${name} must be a whole number`);
|
|
5839
|
+
return Number(raw);
|
|
5840
|
+
}
|
|
5841
|
+
function executionOptions(parsed) {
|
|
5842
|
+
const summaryOnly = flag(parsed, "summary-only") === "true";
|
|
5843
|
+
const glob = flag(parsed, "files");
|
|
5844
|
+
const dir = flag(parsed, "dir");
|
|
5845
|
+
return {
|
|
5846
|
+
executionId: parsed.positionals[0],
|
|
5847
|
+
activities: !summaryOnly,
|
|
5848
|
+
files: !summaryOnly && glob !== "false",
|
|
5849
|
+
glob: glob === "false" ? void 0 : glob,
|
|
5850
|
+
maxFileBytes: integer(parsed, "max-file-size"),
|
|
5851
|
+
dir: dir ? () => dir : void 0
|
|
5852
|
+
};
|
|
5853
|
+
}
|
|
5854
|
+
function withoutFlag(parsed, name) {
|
|
5855
|
+
const flags = new Map(parsed.flags);
|
|
5856
|
+
flags.delete(name);
|
|
5857
|
+
return { ...parsed, flags };
|
|
5858
|
+
}
|
|
5859
|
+
var PARTIAL = ["paths", "variants"];
|
|
5860
|
+
function rejectPartial(parsed) {
|
|
5861
|
+
const used = PARTIAL.filter((name) => parsed.flags.has(name));
|
|
5862
|
+
if (used.length === 0) return;
|
|
5863
|
+
throw new UsageError(
|
|
5864
|
+
`pull writes the whole tree, so it cannot take ${used.map((name) => `--${name}`).join(" or ")}. Add --json to list those files instead.`
|
|
5865
|
+
);
|
|
5866
|
+
}
|
|
5867
|
+
async function pull(command, parsed, sync, deps) {
|
|
5868
|
+
rejectPartial(parsed);
|
|
5869
|
+
const result = await pullWorkflow(sync, {
|
|
5870
|
+
workflowId: parsed.positionals[0],
|
|
5871
|
+
request: await buildRequest(command, withoutFlag(parsed, "out"), {
|
|
5872
|
+
io: deps.io
|
|
5873
|
+
}),
|
|
5874
|
+
dir: flag(parsed, "out"),
|
|
5875
|
+
root: deps.root ?? "",
|
|
5876
|
+
...deps.workflowPull
|
|
5877
|
+
});
|
|
5878
|
+
return { result, text: formatWorkflowPull(result) };
|
|
5879
|
+
}
|
|
5880
|
+
async function execute(command, parsed, sync, deps) {
|
|
5881
|
+
if (command.files === "pull") return pull(command, parsed, sync, deps);
|
|
5882
|
+
if (command.files === "push") {
|
|
5883
|
+
const result2 = await pushWorkflow(
|
|
5884
|
+
sync,
|
|
5885
|
+
flag(parsed, "from") ?? ".",
|
|
5886
|
+
parsed.positionals[0] || void 0
|
|
5887
|
+
);
|
|
5888
|
+
return { result: result2, text: formatWorkflowPush(result2) };
|
|
5889
|
+
}
|
|
5890
|
+
if (command.command !== "execution pull")
|
|
5891
|
+
throw new UsageError(`unknown command ${command.command}`);
|
|
5892
|
+
const result = await pullExecution(sync, {
|
|
5893
|
+
...executionOptions(parsed),
|
|
5894
|
+
root: deps.root ?? "",
|
|
5895
|
+
...deps.executionPull
|
|
5896
|
+
});
|
|
5897
|
+
return { result, text: formatExecutionPull(result) };
|
|
5898
|
+
}
|
|
5899
|
+
async function runSync(command, parsed, deps) {
|
|
5900
|
+
const sync = {
|
|
5901
|
+
remote: deps.remote,
|
|
5902
|
+
files: deps.files ?? nodeFiles(deps.cwd)
|
|
5903
|
+
};
|
|
5904
|
+
const { result, text: text2 } = await execute(command, parsed, sync, deps);
|
|
5905
|
+
deps.stdout(`${parsed.json ? JSON.stringify(result, null, 2) : text2}
|
|
5906
|
+
`);
|
|
5907
|
+
return 0;
|
|
5908
|
+
}
|
|
5108
5909
|
export {
|
|
5910
|
+
ALL_COMMANDS,
|
|
5109
5911
|
COMMANDS,
|
|
5912
|
+
MANIFEST,
|
|
5913
|
+
SYNC_COMMANDS,
|
|
5110
5914
|
UsageError,
|
|
5111
5915
|
buildRequest,
|
|
5916
|
+
formatActivitiesMarkdown,
|
|
5917
|
+
formatExecutionPull,
|
|
5918
|
+
formatWorkflowPull,
|
|
5919
|
+
formatWorkflowPush,
|
|
5112
5920
|
leadingWords,
|
|
5113
5921
|
matchCommand,
|
|
5922
|
+
matchesGlob,
|
|
5114
5923
|
parseInvocation,
|
|
5115
|
-
|
|
5924
|
+
pullExecution,
|
|
5925
|
+
pullWorkflow,
|
|
5926
|
+
pushWorkflow,
|
|
5927
|
+
render,
|
|
5928
|
+
runSync,
|
|
5929
|
+
withSyncCommands
|
|
5116
5930
|
};
|