@asteroidai/cli 1.9.115 → 1.9.123
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 +378 -34
- package/dist/core.js +385 -33
- package/package.json +3 -3
package/dist/asteroid.js
CHANGED
|
@@ -1946,6 +1946,34 @@ var COMMANDS = [
|
|
|
1946
1946
|
destructive: false,
|
|
1947
1947
|
capability: "run_execution"
|
|
1948
1948
|
},
|
|
1949
|
+
{
|
|
1950
|
+
command: "execution sync",
|
|
1951
|
+
tool: "execution_sync",
|
|
1952
|
+
title: "Wait for an execution to finish",
|
|
1953
|
+
description: "Wait up to `timeoutSeconds` (1-25, default 25) for a running execution to finish. Returns the terminal `status` and `executionResult` when it finishes in time. Otherwise returns the current status: call it again to keep waiting. A paused or `awaiting_confirmation` execution returns at once, since it needs a person. Use it after `workflow_execute_post`, or after `workflow_execute_sync_post` timed out, instead of polling `execution_get`.",
|
|
1954
|
+
method: "GET",
|
|
1955
|
+
path: "/agents/v2/executions/{executionId}/sync",
|
|
1956
|
+
args: [
|
|
1957
|
+
{
|
|
1958
|
+
name: "executionId",
|
|
1959
|
+
flag: "execution-id",
|
|
1960
|
+
type: "string",
|
|
1961
|
+
required: true
|
|
1962
|
+
}
|
|
1963
|
+
],
|
|
1964
|
+
query: [
|
|
1965
|
+
{
|
|
1966
|
+
name: "timeoutSeconds",
|
|
1967
|
+
flag: "timeout-seconds",
|
|
1968
|
+
type: "integer",
|
|
1969
|
+
required: false,
|
|
1970
|
+
description: "Seconds to wait for the execution to finish before returning 202. Defaults to 25."
|
|
1971
|
+
}
|
|
1972
|
+
],
|
|
1973
|
+
binary: false,
|
|
1974
|
+
readOnly: true,
|
|
1975
|
+
destructive: false
|
|
1976
|
+
},
|
|
1949
1977
|
{
|
|
1950
1978
|
command: "insight get",
|
|
1951
1979
|
tool: "insight_get",
|
|
@@ -4886,6 +4914,26 @@ var COMMANDS = [
|
|
|
4886
4914
|
readOnly: false,
|
|
4887
4915
|
destructive: false
|
|
4888
4916
|
},
|
|
4917
|
+
{
|
|
4918
|
+
command: "workflow head validate",
|
|
4919
|
+
tool: "workflow_head_validate",
|
|
4920
|
+
title: "Validate workflow head",
|
|
4921
|
+
description: 'Check the workflow\'s editable head the way publish and run do, without changing it. Returns blocking errors and advisory warnings, each with the path of the field it names, such as ["nodes", "Login", "model"]. Use after `workflow_files_patch` and before `workflow_head_publish`; an empty `issues` list means the head can publish and run. The same list comes back on every `workflow_files_patch` response.',
|
|
4922
|
+
method: "POST",
|
|
4923
|
+
path: "/agents/v2/workflows/{workflowId}/head/validate",
|
|
4924
|
+
args: [
|
|
4925
|
+
{
|
|
4926
|
+
name: "workflowId",
|
|
4927
|
+
flag: "workflow-id",
|
|
4928
|
+
type: "string",
|
|
4929
|
+
required: true
|
|
4930
|
+
}
|
|
4931
|
+
],
|
|
4932
|
+
query: [],
|
|
4933
|
+
binary: false,
|
|
4934
|
+
readOnly: true,
|
|
4935
|
+
destructive: false
|
|
4936
|
+
},
|
|
4889
4937
|
{
|
|
4890
4938
|
command: "workflow list",
|
|
4891
4939
|
tool: "workflow_list",
|
|
@@ -6200,6 +6248,85 @@ var COMMANDS = [
|
|
|
6200
6248
|
}
|
|
6201
6249
|
];
|
|
6202
6250
|
|
|
6251
|
+
// src/sync/types.ts
|
|
6252
|
+
var MANIFEST = ".asteroid.json";
|
|
6253
|
+
function get(path, query = {}) {
|
|
6254
|
+
return { method: "GET", path, query: new URLSearchParams(query) };
|
|
6255
|
+
}
|
|
6256
|
+
function statusOf(error) {
|
|
6257
|
+
const status = error?.status;
|
|
6258
|
+
return typeof status === "number" ? status : void 0;
|
|
6259
|
+
}
|
|
6260
|
+
|
|
6261
|
+
// src/sync/execution-wait.ts
|
|
6262
|
+
var WAIT_TIMEOUT_SECONDS = 600;
|
|
6263
|
+
var WAIT_INTERVAL_SECONDS = 15;
|
|
6264
|
+
var MAX_WAIT_TIMEOUT_SECONDS = 3600;
|
|
6265
|
+
var MAX_WAIT_INTERVAL_SECONDS = 120;
|
|
6266
|
+
var TERMINAL = /* @__PURE__ */ new Set(["completed", "cancelled", "failed"]);
|
|
6267
|
+
function isTerminalExecutionStatus(status) {
|
|
6268
|
+
return TERMINAL.has(status);
|
|
6269
|
+
}
|
|
6270
|
+
var SYNC_TIMEOUT_SECONDS = 25;
|
|
6271
|
+
function pause(ms) {
|
|
6272
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
6273
|
+
}
|
|
6274
|
+
async function readStatus(sync, executionId2, timeoutMs) {
|
|
6275
|
+
if (timeoutMs <= 0)
|
|
6276
|
+
return await sync.remote.json(
|
|
6277
|
+
get(`/agents/v2/executions/${executionId2}`)
|
|
6278
|
+
);
|
|
6279
|
+
const timeoutSeconds = Math.min(
|
|
6280
|
+
SYNC_TIMEOUT_SECONDS,
|
|
6281
|
+
Math.max(1, Math.ceil(timeoutMs / 1e3))
|
|
6282
|
+
);
|
|
6283
|
+
const response = await sync.remote.json(
|
|
6284
|
+
get(`/agents/v2/executions/${executionId2}/sync`, {
|
|
6285
|
+
timeoutSeconds: String(timeoutSeconds)
|
|
6286
|
+
})
|
|
6287
|
+
);
|
|
6288
|
+
return {
|
|
6289
|
+
id: response.executionId,
|
|
6290
|
+
status: response.status,
|
|
6291
|
+
executionResult: response.executionResult
|
|
6292
|
+
};
|
|
6293
|
+
}
|
|
6294
|
+
async function waitForExecution(sync, options) {
|
|
6295
|
+
const timeoutMs = (options.timeoutSeconds ?? WAIT_TIMEOUT_SECONDS) * 1e3;
|
|
6296
|
+
const intervalMs = (options.intervalSeconds ?? WAIT_INTERVAL_SECONDS) * 1e3;
|
|
6297
|
+
const now = options.now ?? Date.now;
|
|
6298
|
+
const sleep = options.sleep ?? pause;
|
|
6299
|
+
const started = now();
|
|
6300
|
+
let polls = 0;
|
|
6301
|
+
for (; ; ) {
|
|
6302
|
+
const called = now();
|
|
6303
|
+
const execution = await readStatus(
|
|
6304
|
+
sync,
|
|
6305
|
+
options.executionId,
|
|
6306
|
+
timeoutMs - (called - started)
|
|
6307
|
+
);
|
|
6308
|
+
polls++;
|
|
6309
|
+
const elapsed = now() - started;
|
|
6310
|
+
const waitedSeconds = Math.round(elapsed / 1e3);
|
|
6311
|
+
if (isTerminalExecutionStatus(execution.status))
|
|
6312
|
+
return { execution, terminal: true, waitedSeconds, polls };
|
|
6313
|
+
const remaining = timeoutMs - elapsed;
|
|
6314
|
+
if (remaining <= 0)
|
|
6315
|
+
return { execution, terminal: false, waitedSeconds, polls };
|
|
6316
|
+
const early = intervalMs - (now() - called);
|
|
6317
|
+
if (early > 0) await sleep(Math.min(early, remaining));
|
|
6318
|
+
}
|
|
6319
|
+
}
|
|
6320
|
+
function formatExecutionWait(result) {
|
|
6321
|
+
const { execution } = result;
|
|
6322
|
+
if (!result.terminal) {
|
|
6323
|
+
return `Execution ${execution.id} still ${execution.status} after ${result.waitedSeconds}s. Wait again, or read what it has so far with \`asteroid execution pull ${execution.id}\`.`;
|
|
6324
|
+
}
|
|
6325
|
+
const line2 = `Execution ${execution.id} ${execution.status}.`;
|
|
6326
|
+
const outcome = execution.executionResult;
|
|
6327
|
+
return outcome ? `${line2} Result: ${outcome.outcome}: ${outcome.reasoning}` : line2;
|
|
6328
|
+
}
|
|
6329
|
+
|
|
6203
6330
|
// src/sync/commands.ts
|
|
6204
6331
|
function option(name, type, description, extra = {}) {
|
|
6205
6332
|
return { name, flag: name, type, required: false, description, ...extra };
|
|
@@ -6211,6 +6338,12 @@ var local = {
|
|
|
6211
6338
|
destructive: false,
|
|
6212
6339
|
local: true
|
|
6213
6340
|
};
|
|
6341
|
+
var executionId = {
|
|
6342
|
+
name: "executionId",
|
|
6343
|
+
flag: "execution-id",
|
|
6344
|
+
type: "string",
|
|
6345
|
+
required: true
|
|
6346
|
+
};
|
|
6214
6347
|
var SYNC_COMMANDS = [
|
|
6215
6348
|
{
|
|
6216
6349
|
...local,
|
|
@@ -6219,14 +6352,7 @@ var SYNC_COMMANDS = [
|
|
|
6219
6352
|
title: "Pull an execution to disk",
|
|
6220
6353
|
description: "Write an execution to `<workflow-name>/executions/<id>/`: summary.json, activities.md (the step-by-step log), and the files the run produced.",
|
|
6221
6354
|
readOnly: true,
|
|
6222
|
-
args: [
|
|
6223
|
-
{
|
|
6224
|
-
name: "executionId",
|
|
6225
|
-
flag: "execution-id",
|
|
6226
|
-
type: "string",
|
|
6227
|
-
required: true
|
|
6228
|
-
}
|
|
6229
|
-
],
|
|
6355
|
+
args: [executionId],
|
|
6230
6356
|
query: [
|
|
6231
6357
|
option(
|
|
6232
6358
|
"files",
|
|
@@ -6252,6 +6378,54 @@ var SYNC_COMMANDS = [
|
|
|
6252
6378
|
"Write here instead of `<workflow-name>/executions/<id>/` under the current directory."
|
|
6253
6379
|
)
|
|
6254
6380
|
]
|
|
6381
|
+
},
|
|
6382
|
+
{
|
|
6383
|
+
...local,
|
|
6384
|
+
command: "execution wait",
|
|
6385
|
+
tool: "execution_wait",
|
|
6386
|
+
title: "Wait for an execution to end",
|
|
6387
|
+
description: "Poll an execution until it is completed, failed or cancelled, then print its status and, when the run produced one, its result. When the timeout passes first, print the status it still has, so you can wait again.",
|
|
6388
|
+
readOnly: true,
|
|
6389
|
+
args: [executionId],
|
|
6390
|
+
query: [
|
|
6391
|
+
option(
|
|
6392
|
+
"timeout",
|
|
6393
|
+
"integer",
|
|
6394
|
+
`Give up after this many seconds, ${WAIT_TIMEOUT_SECONDS} unless set, ${MAX_WAIT_TIMEOUT_SECONDS} at most.`
|
|
6395
|
+
),
|
|
6396
|
+
option(
|
|
6397
|
+
"interval",
|
|
6398
|
+
"integer",
|
|
6399
|
+
`Poll every this many seconds, ${WAIT_INTERVAL_SECONDS} unless set, ${MAX_WAIT_INTERVAL_SECONDS} at most.`
|
|
6400
|
+
)
|
|
6401
|
+
]
|
|
6402
|
+
},
|
|
6403
|
+
{
|
|
6404
|
+
...local,
|
|
6405
|
+
command: "insight pull",
|
|
6406
|
+
tool: "insight_pull",
|
|
6407
|
+
title: "Pull insights to disk",
|
|
6408
|
+
description: "Write insights to `insights/<name>.md`, one markdown file each with YAML frontmatter (id, type, status, name, next_steps). Name an insight to pull that one. A file another insight or an unpushed draft already holds keeps its name; the pulled one gets a -2, -3 suffix.",
|
|
6409
|
+
readOnly: true,
|
|
6410
|
+
args: [
|
|
6411
|
+
{
|
|
6412
|
+
name: "insightId",
|
|
6413
|
+
flag: "insight-id",
|
|
6414
|
+
type: "string",
|
|
6415
|
+
required: false,
|
|
6416
|
+
description: "One insight to pull. Without it, every insight, or one workflow's with --workflow-id."
|
|
6417
|
+
}
|
|
6418
|
+
],
|
|
6419
|
+
query: [
|
|
6420
|
+
option("workflowId", "string", "Only this workflow's insights.", {
|
|
6421
|
+
flag: "workflow-id"
|
|
6422
|
+
}),
|
|
6423
|
+
option(
|
|
6424
|
+
"dir",
|
|
6425
|
+
"string",
|
|
6426
|
+
"Write here instead of `insights/` under the current directory."
|
|
6427
|
+
)
|
|
6428
|
+
]
|
|
6255
6429
|
}
|
|
6256
6430
|
];
|
|
6257
6431
|
|
|
@@ -6710,15 +6884,16 @@ function defineCommands(root, options, dotted2 = /* @__PURE__ */ new Map(), onIn
|
|
|
6710
6884
|
(arg) => options.defaultArg?.(command, arg)
|
|
6711
6885
|
);
|
|
6712
6886
|
const defaulted = defaults[0] !== void 0;
|
|
6887
|
+
const optional = (arg, i) => i === 0 && defaulted || !arg.required;
|
|
6713
6888
|
command.args.forEach((arg) => {
|
|
6714
6889
|
leaf.argument(
|
|
6715
|
-
defaulted ? `[${arg.flag}]` : `<${arg.flag}>`,
|
|
6890
|
+
defaulted || !arg.required ? `[${arg.flag}]` : `<${arg.flag}>`,
|
|
6716
6891
|
arg.description ?? ""
|
|
6717
6892
|
);
|
|
6718
6893
|
});
|
|
6719
6894
|
if (defaulted) {
|
|
6720
6895
|
const names = command.args.map(
|
|
6721
|
-
(arg, i) => i
|
|
6896
|
+
(arg, i) => optional(arg, i) ? `[${arg.flag}]` : `<${arg.flag}>`
|
|
6722
6897
|
);
|
|
6723
6898
|
leaf.usage(`[options] ${names.join(" ")}`);
|
|
6724
6899
|
}
|
|
@@ -6735,8 +6910,9 @@ ${notes.join("\n")}`);
|
|
|
6735
6910
|
if (positionals.length === command.args.length - 1 && defaults[0] !== void 0) {
|
|
6736
6911
|
positionals = [defaults[0], ...positionals];
|
|
6737
6912
|
}
|
|
6738
|
-
|
|
6739
|
-
|
|
6913
|
+
const required = command.args.filter((arg) => arg.required).length;
|
|
6914
|
+
if (positionals.length < required || positionals.length > command.args.length) {
|
|
6915
|
+
const expected = command.args.map((a, i) => optional(a, i) ? `[${a.flag}]` : `<${a.flag}>`).join(" ");
|
|
6740
6916
|
throw new UsageError(
|
|
6741
6917
|
`expected ${expected}, got ${cmd.args.length} argument(s)`
|
|
6742
6918
|
);
|
|
@@ -7258,7 +7434,7 @@ var ApiError = class extends Error {
|
|
|
7258
7434
|
status;
|
|
7259
7435
|
body;
|
|
7260
7436
|
};
|
|
7261
|
-
var VERSION = "1.9.
|
|
7437
|
+
var VERSION = "1.9.123";
|
|
7262
7438
|
function problem(status, body, fallback) {
|
|
7263
7439
|
if (body && typeof body === "object") {
|
|
7264
7440
|
const record2 = body;
|
|
@@ -8070,6 +8246,13 @@ function normalizeNodeName(name) {
|
|
|
8070
8246
|
}
|
|
8071
8247
|
return result;
|
|
8072
8248
|
}
|
|
8249
|
+
function lossySlugOr(name, fallback) {
|
|
8250
|
+
try {
|
|
8251
|
+
return normalizeNodeName(name);
|
|
8252
|
+
} catch {
|
|
8253
|
+
return fallback;
|
|
8254
|
+
}
|
|
8255
|
+
}
|
|
8073
8256
|
function nodeDirName(name) {
|
|
8074
8257
|
if (isNameCharsetConforming(name)) {
|
|
8075
8258
|
return name.replaceAll(" ", "_");
|
|
@@ -8083,8 +8266,8 @@ function agentDirName(name) {
|
|
|
8083
8266
|
return "agent";
|
|
8084
8267
|
}
|
|
8085
8268
|
}
|
|
8086
|
-
function executionDirName(
|
|
8087
|
-
return
|
|
8269
|
+
function executionDirName(executionId2) {
|
|
8270
|
+
return executionId2.slice(0, 8);
|
|
8088
8271
|
}
|
|
8089
8272
|
function workflowDirName(workflowId, version, parentId, parentVersion) {
|
|
8090
8273
|
const short = workflowId.slice(0, 8);
|
|
@@ -8132,16 +8315,6 @@ function matchesGlob(path, pattern) {
|
|
|
8132
8315
|
return new RegExp(`^${source}$`).test(path);
|
|
8133
8316
|
}
|
|
8134
8317
|
|
|
8135
|
-
// src/sync/types.ts
|
|
8136
|
-
var MANIFEST = ".asteroid.json";
|
|
8137
|
-
function get(path, query = {}) {
|
|
8138
|
-
return { method: "GET", path, query: new URLSearchParams(query) };
|
|
8139
|
-
}
|
|
8140
|
-
function statusOf(error) {
|
|
8141
|
-
const status = error?.status;
|
|
8142
|
-
return typeof status === "number" ? status : void 0;
|
|
8143
|
-
}
|
|
8144
|
-
|
|
8145
8318
|
// src/sync/execution-pull.ts
|
|
8146
8319
|
var DIRECTORIES = ["workspace", "downloads", "shared"];
|
|
8147
8320
|
function relativeFilePath(file) {
|
|
@@ -8179,14 +8352,14 @@ async function writeActivities(sync, dir, execution) {
|
|
|
8179
8352
|
return false;
|
|
8180
8353
|
}
|
|
8181
8354
|
}
|
|
8182
|
-
async function listFiles(sync,
|
|
8355
|
+
async function listFiles(sync, executionId2) {
|
|
8183
8356
|
const listing = await sync.remote.json(
|
|
8184
|
-
get(`/agents/v2/executions/${
|
|
8357
|
+
get(`/agents/v2/executions/${executionId2}/files`)
|
|
8185
8358
|
);
|
|
8186
8359
|
return DIRECTORIES.flatMap((name) => listing.directories[name] ?? []);
|
|
8187
8360
|
}
|
|
8188
|
-
async function download(sync,
|
|
8189
|
-
const path = `/agents/v2/executions/${
|
|
8361
|
+
async function download(sync, executionId2, file) {
|
|
8362
|
+
const path = `/agents/v2/executions/${executionId2}/files/${file.id}/signed-url`;
|
|
8190
8363
|
const { url } = await sync.remote.json(get(path));
|
|
8191
8364
|
return sync.remote.download(url);
|
|
8192
8365
|
}
|
|
@@ -8314,6 +8487,114 @@ ${result.skipped.map((s) => ` - ${s}`).join("\n")}` : null,
|
|
|
8314
8487
|
].filter(Boolean).join("\n");
|
|
8315
8488
|
}
|
|
8316
8489
|
|
|
8490
|
+
// src/sync/insight-file.ts
|
|
8491
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
8492
|
+
var FRONTMATTER = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)(?:\r?\n)?/;
|
|
8493
|
+
function serializeInsightFile(meta, body) {
|
|
8494
|
+
const front = {};
|
|
8495
|
+
if (meta.id) front.id = meta.id;
|
|
8496
|
+
front.type = meta.type;
|
|
8497
|
+
if (meta.status) front.status = meta.status;
|
|
8498
|
+
front.name = meta.name;
|
|
8499
|
+
if (meta.nextSteps) front.next_steps = meta.nextSteps;
|
|
8500
|
+
const yaml = stringifyYaml(front).trimEnd();
|
|
8501
|
+
return `---
|
|
8502
|
+
${yaml}
|
|
8503
|
+
---
|
|
8504
|
+
|
|
8505
|
+
${body}`;
|
|
8506
|
+
}
|
|
8507
|
+
function insightFileId(text3) {
|
|
8508
|
+
const match = FRONTMATTER.exec(text3);
|
|
8509
|
+
if (!match) return void 0;
|
|
8510
|
+
try {
|
|
8511
|
+
const id = parseYaml(match[1])?.id;
|
|
8512
|
+
return typeof id === "string" || typeof id === "number" ? String(id) : void 0;
|
|
8513
|
+
} catch {
|
|
8514
|
+
return void 0;
|
|
8515
|
+
}
|
|
8516
|
+
}
|
|
8517
|
+
|
|
8518
|
+
// src/sync/insight-pull.ts
|
|
8519
|
+
var INSIGHTS_DIR = "insights";
|
|
8520
|
+
var PAGE_SIZE = 100;
|
|
8521
|
+
async function listInsights(sync, workflowId) {
|
|
8522
|
+
const all = [];
|
|
8523
|
+
for (let page = 1; ; page++) {
|
|
8524
|
+
const query = {
|
|
8525
|
+
page: String(page),
|
|
8526
|
+
pageSize: String(PAGE_SIZE)
|
|
8527
|
+
};
|
|
8528
|
+
if (workflowId) query.workflowId = workflowId;
|
|
8529
|
+
const { items, total } = await sync.remote.json(
|
|
8530
|
+
get("/astro/v2/insights", query)
|
|
8531
|
+
);
|
|
8532
|
+
all.push(...items);
|
|
8533
|
+
if (items.length === 0 || all.length >= total) return all;
|
|
8534
|
+
}
|
|
8535
|
+
}
|
|
8536
|
+
async function stemOwners(sync, dir) {
|
|
8537
|
+
const owners = /* @__PURE__ */ new Map();
|
|
8538
|
+
for (const name of await sync.files.list(dir)) {
|
|
8539
|
+
if (!name.endsWith(".md") || name.includes("/")) continue;
|
|
8540
|
+
const bytes = await sync.files.read(join3(dir, name));
|
|
8541
|
+
const text3 = bytes ? Buffer.from(bytes).toString("utf8") : "";
|
|
8542
|
+
owners.set(name.slice(0, -".md".length).toLowerCase(), insightFileId(text3));
|
|
8543
|
+
}
|
|
8544
|
+
return owners;
|
|
8545
|
+
}
|
|
8546
|
+
function assignStems(owners, insights) {
|
|
8547
|
+
const used = /* @__PURE__ */ new Set();
|
|
8548
|
+
const heldByOther = (stem, id) => owners.has(stem) && owners.get(stem) !== id;
|
|
8549
|
+
return insights.map((insight) => {
|
|
8550
|
+
const base = lossySlugOr(insight.name, "insight");
|
|
8551
|
+
let stem = base;
|
|
8552
|
+
for (let n = 2; used.has(stem) || heldByOther(stem, insight.id); n++) {
|
|
8553
|
+
stem = `${base}-${n}`;
|
|
8554
|
+
}
|
|
8555
|
+
used.add(stem);
|
|
8556
|
+
return [insight, stem];
|
|
8557
|
+
});
|
|
8558
|
+
}
|
|
8559
|
+
async function pullInsights(sync, options = {}) {
|
|
8560
|
+
const dir = options.dir ?? INSIGHTS_DIR;
|
|
8561
|
+
const insights = options.insightId ? [
|
|
8562
|
+
await sync.remote.json(
|
|
8563
|
+
get(`/astro/v2/insights/${options.insightId}`)
|
|
8564
|
+
)
|
|
8565
|
+
] : await listInsights(sync, options.workflowId);
|
|
8566
|
+
const pulled = [];
|
|
8567
|
+
for (const [insight, stem] of assignStems(
|
|
8568
|
+
await stemOwners(sync, dir),
|
|
8569
|
+
insights
|
|
8570
|
+
)) {
|
|
8571
|
+
const path = join3(dir, `${stem}.md`);
|
|
8572
|
+
const text3 = serializeInsightFile(
|
|
8573
|
+
{
|
|
8574
|
+
id: insight.id,
|
|
8575
|
+
type: insight.type,
|
|
8576
|
+
status: insight.status,
|
|
8577
|
+
name: insight.name,
|
|
8578
|
+
nextSteps: insight.nextSteps ?? void 0
|
|
8579
|
+
},
|
|
8580
|
+
insight.content
|
|
8581
|
+
);
|
|
8582
|
+
await sync.files.write(path, Buffer.from(text3, "utf8"));
|
|
8583
|
+
const { id, name, type, status } = insight;
|
|
8584
|
+
pulled.push({ id, name, type, status, path });
|
|
8585
|
+
}
|
|
8586
|
+
return { dir, insights: pulled };
|
|
8587
|
+
}
|
|
8588
|
+
function formatInsightPull(result) {
|
|
8589
|
+
const count = result.insights.length;
|
|
8590
|
+
return [
|
|
8591
|
+
`Pulled ${count} insight${count === 1 ? "" : "s"} into ${result.dir}/.`,
|
|
8592
|
+
...result.insights.map(
|
|
8593
|
+
(insight) => ` - ${insight.path}: ${insight.name} (${insight.type}, ${insight.status})`
|
|
8594
|
+
)
|
|
8595
|
+
].join("\n");
|
|
8596
|
+
}
|
|
8597
|
+
|
|
8317
8598
|
// src/sync/node-files.ts
|
|
8318
8599
|
import { mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
8319
8600
|
import { dirname as dirname2, relative, resolve, sep } from "node:path";
|
|
@@ -8605,7 +8886,7 @@ function conflict(manifest) {
|
|
|
8605
8886
|
}
|
|
8606
8887
|
async function patch(sync, manifest, writes, deletes) {
|
|
8607
8888
|
try {
|
|
8608
|
-
await sync.remote.json({
|
|
8889
|
+
const snapshot = await sync.remote.json({
|
|
8609
8890
|
method: "PATCH",
|
|
8610
8891
|
path: `/agents/v2/workflows/${manifest.workflowId}/head/files`,
|
|
8611
8892
|
query: new URLSearchParams(),
|
|
@@ -8618,7 +8899,7 @@ async function patch(sync, manifest, writes, deletes) {
|
|
|
8618
8899
|
deletes
|
|
8619
8900
|
}
|
|
8620
8901
|
});
|
|
8621
|
-
return
|
|
8902
|
+
return { issues: snapshot?.issues ?? [] };
|
|
8622
8903
|
} catch (error) {
|
|
8623
8904
|
if (statusOf(error) !== 409) throw error;
|
|
8624
8905
|
return "conflict";
|
|
@@ -8688,14 +8969,20 @@ async function pushWorkflow(sync, dir, workflowId, options = {}) {
|
|
|
8688
8969
|
if (writes.length === 0 && deletes.length === 0) {
|
|
8689
8970
|
return { ...result, rev: manifest.rev, versionId: manifest.versionId };
|
|
8690
8971
|
}
|
|
8691
|
-
|
|
8972
|
+
const applied = await patch(sync, manifest, writes, deletes);
|
|
8973
|
+
if (applied !== "conflict") {
|
|
8692
8974
|
const next = await refresh2(
|
|
8693
8975
|
sync,
|
|
8694
8976
|
dir,
|
|
8695
8977
|
manifest,
|
|
8696
8978
|
new Map(writes.map((w) => [w.path, w.hash]))
|
|
8697
8979
|
);
|
|
8698
|
-
return {
|
|
8980
|
+
return {
|
|
8981
|
+
...result,
|
|
8982
|
+
rev: next.rev,
|
|
8983
|
+
versionId: next.versionId,
|
|
8984
|
+
issues: applied.issues
|
|
8985
|
+
};
|
|
8699
8986
|
}
|
|
8700
8987
|
if (!options.rebase || attempt >= REBASE_ATTEMPTS) throw conflict(manifest);
|
|
8701
8988
|
rebasedFrom ??= manifest.rev;
|
|
@@ -8711,9 +8998,23 @@ function formatWorkflowPush(result) {
|
|
|
8711
8998
|
`Pushed to workflow ${result.workflowId}: ${result.written.length} written, ${result.deleted.length} deleted.${rebased}`,
|
|
8712
8999
|
...result.written.map((path) => ` + ${path}`),
|
|
8713
9000
|
...result.deleted.map((path) => ` - ${path}`),
|
|
8714
|
-
`Head is now rev ${result.rev} (version ${result.versionId})
|
|
9001
|
+
`Head is now rev ${result.rev} (version ${result.versionId}).`,
|
|
9002
|
+
...formatIssues(result.issues ?? [])
|
|
8715
9003
|
].join("\n");
|
|
8716
9004
|
}
|
|
9005
|
+
function formatIssues(issues) {
|
|
9006
|
+
if (issues.length === 0) return [];
|
|
9007
|
+
const errors = issues.filter((issue) => issue.severity === "error").length;
|
|
9008
|
+
const warnings = issues.length - errors;
|
|
9009
|
+
const count = (n, noun) => `${n} ${noun}${n === 1 ? "" : "s"}`;
|
|
9010
|
+
const summary2 = errors === 0 ? `Validation: ${count(warnings, "warning")}, nothing blocks a publish.` : `Validation: ${count(errors, "error")}${warnings > 0 ? `, ${count(warnings, "warning")}` : ""}. Fix the errors before you publish.`;
|
|
9011
|
+
return [
|
|
9012
|
+
summary2,
|
|
9013
|
+
...issues.map(
|
|
9014
|
+
(issue) => ` ${issue.severity === "error" ? "!" : "?"} ${issue.path.join("/") || "(workflow)"}: ${issue.message}`
|
|
9015
|
+
)
|
|
9016
|
+
];
|
|
9017
|
+
}
|
|
8717
9018
|
|
|
8718
9019
|
// src/sync/run.ts
|
|
8719
9020
|
function flag(parsed, name) {
|
|
@@ -8726,6 +9027,35 @@ function integer(parsed, name) {
|
|
|
8726
9027
|
throw new UsageError(`--${name} must be a whole number`);
|
|
8727
9028
|
return Number(raw);
|
|
8728
9029
|
}
|
|
9030
|
+
function bounded(parsed, name, min, max) {
|
|
9031
|
+
const value2 = integer(parsed, name);
|
|
9032
|
+
if (value2 === void 0) return void 0;
|
|
9033
|
+
if (value2 < min || value2 > max)
|
|
9034
|
+
throw new UsageError(`--${name} must be between ${min} and ${max}`);
|
|
9035
|
+
return value2;
|
|
9036
|
+
}
|
|
9037
|
+
function executionWaitOptions(parsed) {
|
|
9038
|
+
return {
|
|
9039
|
+
executionId: parsed.positionals[0],
|
|
9040
|
+
timeoutSeconds: bounded(parsed, "timeout", 0, MAX_WAIT_TIMEOUT_SECONDS),
|
|
9041
|
+
intervalSeconds: bounded(parsed, "interval", 1, MAX_WAIT_INTERVAL_SECONDS)
|
|
9042
|
+
};
|
|
9043
|
+
}
|
|
9044
|
+
function given(value2, name) {
|
|
9045
|
+
if (value2 === void 0) return void 0;
|
|
9046
|
+
if (value2 === "") throw new UsageError(`${name} must not be empty`);
|
|
9047
|
+
return value2;
|
|
9048
|
+
}
|
|
9049
|
+
function insightPullOptions(parsed) {
|
|
9050
|
+
const options = {};
|
|
9051
|
+
const insightId = given(parsed.positionals[0], "<insight-id>");
|
|
9052
|
+
if (insightId) options.insightId = insightId;
|
|
9053
|
+
const workflowId = given(flag(parsed, "workflow-id"), "--workflow-id");
|
|
9054
|
+
if (workflowId) options.workflowId = workflowId;
|
|
9055
|
+
const dir = given(flag(parsed, "dir"), "--dir");
|
|
9056
|
+
if (dir) options.dir = dir;
|
|
9057
|
+
return options;
|
|
9058
|
+
}
|
|
8729
9059
|
function executionOptions(parsed) {
|
|
8730
9060
|
const summaryOnly = flag(parsed, "summary-only") === "true";
|
|
8731
9061
|
const glob = flag(parsed, "files");
|
|
@@ -8787,6 +9117,20 @@ async function execute(command, parsed, sync, deps) {
|
|
|
8787
9117
|
);
|
|
8788
9118
|
return { result: result2, text: formatWorkflowPush(result2) };
|
|
8789
9119
|
}
|
|
9120
|
+
if (command.command === "execution wait") {
|
|
9121
|
+
const result2 = await waitForExecution(sync, {
|
|
9122
|
+
...executionWaitOptions(parsed),
|
|
9123
|
+
...deps.executionWait
|
|
9124
|
+
});
|
|
9125
|
+
return { result: result2, text: formatExecutionWait(result2) };
|
|
9126
|
+
}
|
|
9127
|
+
if (command.command === "insight pull") {
|
|
9128
|
+
const result2 = await pullInsights(sync, {
|
|
9129
|
+
...deps.insightPull,
|
|
9130
|
+
...insightPullOptions(parsed)
|
|
9131
|
+
});
|
|
9132
|
+
return { result: result2, text: formatInsightPull(result2) };
|
|
9133
|
+
}
|
|
8790
9134
|
if (command.command !== "execution pull")
|
|
8791
9135
|
throw new UsageError(`unknown command ${command.command}`);
|
|
8792
9136
|
const result = await pullExecution(sync, {
|
package/dist/core.js
CHANGED
|
@@ -1967,6 +1967,34 @@ var COMMANDS = [
|
|
|
1967
1967
|
destructive: false,
|
|
1968
1968
|
capability: "run_execution"
|
|
1969
1969
|
},
|
|
1970
|
+
{
|
|
1971
|
+
command: "execution sync",
|
|
1972
|
+
tool: "execution_sync",
|
|
1973
|
+
title: "Wait for an execution to finish",
|
|
1974
|
+
description: "Wait up to `timeoutSeconds` (1-25, default 25) for a running execution to finish. Returns the terminal `status` and `executionResult` when it finishes in time. Otherwise returns the current status: call it again to keep waiting. A paused or `awaiting_confirmation` execution returns at once, since it needs a person. Use it after `workflow_execute_post`, or after `workflow_execute_sync_post` timed out, instead of polling `execution_get`.",
|
|
1975
|
+
method: "GET",
|
|
1976
|
+
path: "/agents/v2/executions/{executionId}/sync",
|
|
1977
|
+
args: [
|
|
1978
|
+
{
|
|
1979
|
+
name: "executionId",
|
|
1980
|
+
flag: "execution-id",
|
|
1981
|
+
type: "string",
|
|
1982
|
+
required: true
|
|
1983
|
+
}
|
|
1984
|
+
],
|
|
1985
|
+
query: [
|
|
1986
|
+
{
|
|
1987
|
+
name: "timeoutSeconds",
|
|
1988
|
+
flag: "timeout-seconds",
|
|
1989
|
+
type: "integer",
|
|
1990
|
+
required: false,
|
|
1991
|
+
description: "Seconds to wait for the execution to finish before returning 202. Defaults to 25."
|
|
1992
|
+
}
|
|
1993
|
+
],
|
|
1994
|
+
binary: false,
|
|
1995
|
+
readOnly: true,
|
|
1996
|
+
destructive: false
|
|
1997
|
+
},
|
|
1970
1998
|
{
|
|
1971
1999
|
command: "insight get",
|
|
1972
2000
|
tool: "insight_get",
|
|
@@ -4907,6 +4935,26 @@ var COMMANDS = [
|
|
|
4907
4935
|
readOnly: false,
|
|
4908
4936
|
destructive: false
|
|
4909
4937
|
},
|
|
4938
|
+
{
|
|
4939
|
+
command: "workflow head validate",
|
|
4940
|
+
tool: "workflow_head_validate",
|
|
4941
|
+
title: "Validate workflow head",
|
|
4942
|
+
description: 'Check the workflow\'s editable head the way publish and run do, without changing it. Returns blocking errors and advisory warnings, each with the path of the field it names, such as ["nodes", "Login", "model"]. Use after `workflow_files_patch` and before `workflow_head_publish`; an empty `issues` list means the head can publish and run. The same list comes back on every `workflow_files_patch` response.',
|
|
4943
|
+
method: "POST",
|
|
4944
|
+
path: "/agents/v2/workflows/{workflowId}/head/validate",
|
|
4945
|
+
args: [
|
|
4946
|
+
{
|
|
4947
|
+
name: "workflowId",
|
|
4948
|
+
flag: "workflow-id",
|
|
4949
|
+
type: "string",
|
|
4950
|
+
required: true
|
|
4951
|
+
}
|
|
4952
|
+
],
|
|
4953
|
+
query: [],
|
|
4954
|
+
binary: false,
|
|
4955
|
+
readOnly: true,
|
|
4956
|
+
destructive: false
|
|
4957
|
+
},
|
|
4910
4958
|
{
|
|
4911
4959
|
command: "workflow list",
|
|
4912
4960
|
tool: "workflow_list",
|
|
@@ -6580,15 +6628,16 @@ function defineCommands(root, options, dotted2 = /* @__PURE__ */ new Map(), onIn
|
|
|
6580
6628
|
(arg) => options.defaultArg?.(command, arg)
|
|
6581
6629
|
);
|
|
6582
6630
|
const defaulted = defaults[0] !== void 0;
|
|
6631
|
+
const optional = (arg, i) => i === 0 && defaulted || !arg.required;
|
|
6583
6632
|
command.args.forEach((arg) => {
|
|
6584
6633
|
leaf.argument(
|
|
6585
|
-
defaulted ? `[${arg.flag}]` : `<${arg.flag}>`,
|
|
6634
|
+
defaulted || !arg.required ? `[${arg.flag}]` : `<${arg.flag}>`,
|
|
6586
6635
|
arg.description ?? ""
|
|
6587
6636
|
);
|
|
6588
6637
|
});
|
|
6589
6638
|
if (defaulted) {
|
|
6590
6639
|
const names = command.args.map(
|
|
6591
|
-
(arg, i) => i
|
|
6640
|
+
(arg, i) => optional(arg, i) ? `[${arg.flag}]` : `<${arg.flag}>`
|
|
6592
6641
|
);
|
|
6593
6642
|
leaf.usage(`[options] ${names.join(" ")}`);
|
|
6594
6643
|
}
|
|
@@ -6605,8 +6654,9 @@ ${notes.join("\n")}`);
|
|
|
6605
6654
|
if (positionals.length === command.args.length - 1 && defaults[0] !== void 0) {
|
|
6606
6655
|
positionals = [defaults[0], ...positionals];
|
|
6607
6656
|
}
|
|
6608
|
-
|
|
6609
|
-
|
|
6657
|
+
const required = command.args.filter((arg) => arg.required).length;
|
|
6658
|
+
if (positionals.length < required || positionals.length > command.args.length) {
|
|
6659
|
+
const expected = command.args.map((a, i) => optional(a, i) ? `[${a.flag}]` : `<${a.flag}>`).join(" ");
|
|
6610
6660
|
throw new UsageError(
|
|
6611
6661
|
`expected ${expected}, got ${cmd.args.length} argument(s)`
|
|
6612
6662
|
);
|
|
@@ -6904,6 +6954,85 @@ function rejectUnknown(flags) {
|
|
|
6904
6954
|
}
|
|
6905
6955
|
}
|
|
6906
6956
|
|
|
6957
|
+
// src/sync/types.ts
|
|
6958
|
+
var MANIFEST = ".asteroid.json";
|
|
6959
|
+
function get(path, query = {}) {
|
|
6960
|
+
return { method: "GET", path, query: new URLSearchParams(query) };
|
|
6961
|
+
}
|
|
6962
|
+
function statusOf(error) {
|
|
6963
|
+
const status = error?.status;
|
|
6964
|
+
return typeof status === "number" ? status : void 0;
|
|
6965
|
+
}
|
|
6966
|
+
|
|
6967
|
+
// src/sync/execution-wait.ts
|
|
6968
|
+
var WAIT_TIMEOUT_SECONDS = 600;
|
|
6969
|
+
var WAIT_INTERVAL_SECONDS = 15;
|
|
6970
|
+
var MAX_WAIT_TIMEOUT_SECONDS = 3600;
|
|
6971
|
+
var MAX_WAIT_INTERVAL_SECONDS = 120;
|
|
6972
|
+
var TERMINAL = /* @__PURE__ */ new Set(["completed", "cancelled", "failed"]);
|
|
6973
|
+
function isTerminalExecutionStatus(status) {
|
|
6974
|
+
return TERMINAL.has(status);
|
|
6975
|
+
}
|
|
6976
|
+
var SYNC_TIMEOUT_SECONDS = 25;
|
|
6977
|
+
function pause(ms) {
|
|
6978
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
6979
|
+
}
|
|
6980
|
+
async function readStatus(sync, executionId2, timeoutMs) {
|
|
6981
|
+
if (timeoutMs <= 0)
|
|
6982
|
+
return await sync.remote.json(
|
|
6983
|
+
get(`/agents/v2/executions/${executionId2}`)
|
|
6984
|
+
);
|
|
6985
|
+
const timeoutSeconds = Math.min(
|
|
6986
|
+
SYNC_TIMEOUT_SECONDS,
|
|
6987
|
+
Math.max(1, Math.ceil(timeoutMs / 1e3))
|
|
6988
|
+
);
|
|
6989
|
+
const response = await sync.remote.json(
|
|
6990
|
+
get(`/agents/v2/executions/${executionId2}/sync`, {
|
|
6991
|
+
timeoutSeconds: String(timeoutSeconds)
|
|
6992
|
+
})
|
|
6993
|
+
);
|
|
6994
|
+
return {
|
|
6995
|
+
id: response.executionId,
|
|
6996
|
+
status: response.status,
|
|
6997
|
+
executionResult: response.executionResult
|
|
6998
|
+
};
|
|
6999
|
+
}
|
|
7000
|
+
async function waitForExecution(sync, options) {
|
|
7001
|
+
const timeoutMs = (options.timeoutSeconds ?? WAIT_TIMEOUT_SECONDS) * 1e3;
|
|
7002
|
+
const intervalMs = (options.intervalSeconds ?? WAIT_INTERVAL_SECONDS) * 1e3;
|
|
7003
|
+
const now = options.now ?? Date.now;
|
|
7004
|
+
const sleep = options.sleep ?? pause;
|
|
7005
|
+
const started = now();
|
|
7006
|
+
let polls = 0;
|
|
7007
|
+
for (; ; ) {
|
|
7008
|
+
const called = now();
|
|
7009
|
+
const execution = await readStatus(
|
|
7010
|
+
sync,
|
|
7011
|
+
options.executionId,
|
|
7012
|
+
timeoutMs - (called - started)
|
|
7013
|
+
);
|
|
7014
|
+
polls++;
|
|
7015
|
+
const elapsed = now() - started;
|
|
7016
|
+
const waitedSeconds = Math.round(elapsed / 1e3);
|
|
7017
|
+
if (isTerminalExecutionStatus(execution.status))
|
|
7018
|
+
return { execution, terminal: true, waitedSeconds, polls };
|
|
7019
|
+
const remaining = timeoutMs - elapsed;
|
|
7020
|
+
if (remaining <= 0)
|
|
7021
|
+
return { execution, terminal: false, waitedSeconds, polls };
|
|
7022
|
+
const early = intervalMs - (now() - called);
|
|
7023
|
+
if (early > 0) await sleep(Math.min(early, remaining));
|
|
7024
|
+
}
|
|
7025
|
+
}
|
|
7026
|
+
function formatExecutionWait(result) {
|
|
7027
|
+
const { execution } = result;
|
|
7028
|
+
if (!result.terminal) {
|
|
7029
|
+
return `Execution ${execution.id} still ${execution.status} after ${result.waitedSeconds}s. Wait again, or read what it has so far with \`asteroid execution pull ${execution.id}\`.`;
|
|
7030
|
+
}
|
|
7031
|
+
const line2 = `Execution ${execution.id} ${execution.status}.`;
|
|
7032
|
+
const outcome = execution.executionResult;
|
|
7033
|
+
return outcome ? `${line2} Result: ${outcome.outcome}: ${outcome.reasoning}` : line2;
|
|
7034
|
+
}
|
|
7035
|
+
|
|
6907
7036
|
// src/sync/commands.ts
|
|
6908
7037
|
function option(name, type, description, extra = {}) {
|
|
6909
7038
|
return { name, flag: name, type, required: false, description, ...extra };
|
|
@@ -6915,6 +7044,12 @@ var local = {
|
|
|
6915
7044
|
destructive: false,
|
|
6916
7045
|
local: true
|
|
6917
7046
|
};
|
|
7047
|
+
var executionId = {
|
|
7048
|
+
name: "executionId",
|
|
7049
|
+
flag: "execution-id",
|
|
7050
|
+
type: "string",
|
|
7051
|
+
required: true
|
|
7052
|
+
};
|
|
6918
7053
|
var SYNC_COMMANDS = [
|
|
6919
7054
|
{
|
|
6920
7055
|
...local,
|
|
@@ -6923,14 +7058,7 @@ var SYNC_COMMANDS = [
|
|
|
6923
7058
|
title: "Pull an execution to disk",
|
|
6924
7059
|
description: "Write an execution to `<workflow-name>/executions/<id>/`: summary.json, activities.md (the step-by-step log), and the files the run produced.",
|
|
6925
7060
|
readOnly: true,
|
|
6926
|
-
args: [
|
|
6927
|
-
{
|
|
6928
|
-
name: "executionId",
|
|
6929
|
-
flag: "execution-id",
|
|
6930
|
-
type: "string",
|
|
6931
|
-
required: true
|
|
6932
|
-
}
|
|
6933
|
-
],
|
|
7061
|
+
args: [executionId],
|
|
6934
7062
|
query: [
|
|
6935
7063
|
option(
|
|
6936
7064
|
"files",
|
|
@@ -6956,6 +7084,54 @@ var SYNC_COMMANDS = [
|
|
|
6956
7084
|
"Write here instead of `<workflow-name>/executions/<id>/` under the current directory."
|
|
6957
7085
|
)
|
|
6958
7086
|
]
|
|
7087
|
+
},
|
|
7088
|
+
{
|
|
7089
|
+
...local,
|
|
7090
|
+
command: "execution wait",
|
|
7091
|
+
tool: "execution_wait",
|
|
7092
|
+
title: "Wait for an execution to end",
|
|
7093
|
+
description: "Poll an execution until it is completed, failed or cancelled, then print its status and, when the run produced one, its result. When the timeout passes first, print the status it still has, so you can wait again.",
|
|
7094
|
+
readOnly: true,
|
|
7095
|
+
args: [executionId],
|
|
7096
|
+
query: [
|
|
7097
|
+
option(
|
|
7098
|
+
"timeout",
|
|
7099
|
+
"integer",
|
|
7100
|
+
`Give up after this many seconds, ${WAIT_TIMEOUT_SECONDS} unless set, ${MAX_WAIT_TIMEOUT_SECONDS} at most.`
|
|
7101
|
+
),
|
|
7102
|
+
option(
|
|
7103
|
+
"interval",
|
|
7104
|
+
"integer",
|
|
7105
|
+
`Poll every this many seconds, ${WAIT_INTERVAL_SECONDS} unless set, ${MAX_WAIT_INTERVAL_SECONDS} at most.`
|
|
7106
|
+
)
|
|
7107
|
+
]
|
|
7108
|
+
},
|
|
7109
|
+
{
|
|
7110
|
+
...local,
|
|
7111
|
+
command: "insight pull",
|
|
7112
|
+
tool: "insight_pull",
|
|
7113
|
+
title: "Pull insights to disk",
|
|
7114
|
+
description: "Write insights to `insights/<name>.md`, one markdown file each with YAML frontmatter (id, type, status, name, next_steps). Name an insight to pull that one. A file another insight or an unpushed draft already holds keeps its name; the pulled one gets a -2, -3 suffix.",
|
|
7115
|
+
readOnly: true,
|
|
7116
|
+
args: [
|
|
7117
|
+
{
|
|
7118
|
+
name: "insightId",
|
|
7119
|
+
flag: "insight-id",
|
|
7120
|
+
type: "string",
|
|
7121
|
+
required: false,
|
|
7122
|
+
description: "One insight to pull. Without it, every insight, or one workflow's with --workflow-id."
|
|
7123
|
+
}
|
|
7124
|
+
],
|
|
7125
|
+
query: [
|
|
7126
|
+
option("workflowId", "string", "Only this workflow's insights.", {
|
|
7127
|
+
flag: "workflow-id"
|
|
7128
|
+
}),
|
|
7129
|
+
option(
|
|
7130
|
+
"dir",
|
|
7131
|
+
"string",
|
|
7132
|
+
"Write here instead of `insights/` under the current directory."
|
|
7133
|
+
)
|
|
7134
|
+
]
|
|
6959
7135
|
}
|
|
6960
7136
|
];
|
|
6961
7137
|
|
|
@@ -7068,6 +7244,13 @@ function normalizeNodeName(name) {
|
|
|
7068
7244
|
}
|
|
7069
7245
|
return result;
|
|
7070
7246
|
}
|
|
7247
|
+
function lossySlugOr(name, fallback) {
|
|
7248
|
+
try {
|
|
7249
|
+
return normalizeNodeName(name);
|
|
7250
|
+
} catch {
|
|
7251
|
+
return fallback;
|
|
7252
|
+
}
|
|
7253
|
+
}
|
|
7071
7254
|
function nodeDirName(name) {
|
|
7072
7255
|
if (isNameCharsetConforming(name)) {
|
|
7073
7256
|
return name.replaceAll(" ", "_");
|
|
@@ -7081,8 +7264,8 @@ function agentDirName(name) {
|
|
|
7081
7264
|
return "agent";
|
|
7082
7265
|
}
|
|
7083
7266
|
}
|
|
7084
|
-
function executionDirName(
|
|
7085
|
-
return
|
|
7267
|
+
function executionDirName(executionId2) {
|
|
7268
|
+
return executionId2.slice(0, 8);
|
|
7086
7269
|
}
|
|
7087
7270
|
function workflowDirName(workflowId, version, parentId, parentVersion) {
|
|
7088
7271
|
const short = workflowId.slice(0, 8);
|
|
@@ -7130,16 +7313,6 @@ function matchesGlob(path, pattern) {
|
|
|
7130
7313
|
return new RegExp(`^${source}$`).test(path);
|
|
7131
7314
|
}
|
|
7132
7315
|
|
|
7133
|
-
// src/sync/types.ts
|
|
7134
|
-
var MANIFEST = ".asteroid.json";
|
|
7135
|
-
function get(path, query = {}) {
|
|
7136
|
-
return { method: "GET", path, query: new URLSearchParams(query) };
|
|
7137
|
-
}
|
|
7138
|
-
function statusOf(error) {
|
|
7139
|
-
const status = error?.status;
|
|
7140
|
-
return typeof status === "number" ? status : void 0;
|
|
7141
|
-
}
|
|
7142
|
-
|
|
7143
7316
|
// src/sync/execution-pull.ts
|
|
7144
7317
|
var DIRECTORIES = ["workspace", "downloads", "shared"];
|
|
7145
7318
|
function relativeFilePath(file) {
|
|
@@ -7177,14 +7350,14 @@ async function writeActivities(sync, dir, execution) {
|
|
|
7177
7350
|
return false;
|
|
7178
7351
|
}
|
|
7179
7352
|
}
|
|
7180
|
-
async function listFiles(sync,
|
|
7353
|
+
async function listFiles(sync, executionId2) {
|
|
7181
7354
|
const listing = await sync.remote.json(
|
|
7182
|
-
get(`/agents/v2/executions/${
|
|
7355
|
+
get(`/agents/v2/executions/${executionId2}/files`)
|
|
7183
7356
|
);
|
|
7184
7357
|
return DIRECTORIES.flatMap((name) => listing.directories[name] ?? []);
|
|
7185
7358
|
}
|
|
7186
|
-
async function download(sync,
|
|
7187
|
-
const path = `/agents/v2/executions/${
|
|
7359
|
+
async function download(sync, executionId2, file) {
|
|
7360
|
+
const path = `/agents/v2/executions/${executionId2}/files/${file.id}/signed-url`;
|
|
7188
7361
|
const { url } = await sync.remote.json(get(path));
|
|
7189
7362
|
return sync.remote.download(url);
|
|
7190
7363
|
}
|
|
@@ -7312,6 +7485,114 @@ ${result.skipped.map((s) => ` - ${s}`).join("\n")}` : null,
|
|
|
7312
7485
|
].filter(Boolean).join("\n");
|
|
7313
7486
|
}
|
|
7314
7487
|
|
|
7488
|
+
// src/sync/insight-file.ts
|
|
7489
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
7490
|
+
var FRONTMATTER = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)(?:\r?\n)?/;
|
|
7491
|
+
function serializeInsightFile(meta, body) {
|
|
7492
|
+
const front = {};
|
|
7493
|
+
if (meta.id) front.id = meta.id;
|
|
7494
|
+
front.type = meta.type;
|
|
7495
|
+
if (meta.status) front.status = meta.status;
|
|
7496
|
+
front.name = meta.name;
|
|
7497
|
+
if (meta.nextSteps) front.next_steps = meta.nextSteps;
|
|
7498
|
+
const yaml = stringifyYaml(front).trimEnd();
|
|
7499
|
+
return `---
|
|
7500
|
+
${yaml}
|
|
7501
|
+
---
|
|
7502
|
+
|
|
7503
|
+
${body}`;
|
|
7504
|
+
}
|
|
7505
|
+
function insightFileId(text2) {
|
|
7506
|
+
const match = FRONTMATTER.exec(text2);
|
|
7507
|
+
if (!match) return void 0;
|
|
7508
|
+
try {
|
|
7509
|
+
const id = parseYaml(match[1])?.id;
|
|
7510
|
+
return typeof id === "string" || typeof id === "number" ? String(id) : void 0;
|
|
7511
|
+
} catch {
|
|
7512
|
+
return void 0;
|
|
7513
|
+
}
|
|
7514
|
+
}
|
|
7515
|
+
|
|
7516
|
+
// src/sync/insight-pull.ts
|
|
7517
|
+
var INSIGHTS_DIR = "insights";
|
|
7518
|
+
var PAGE_SIZE = 100;
|
|
7519
|
+
async function listInsights(sync, workflowId) {
|
|
7520
|
+
const all = [];
|
|
7521
|
+
for (let page = 1; ; page++) {
|
|
7522
|
+
const query = {
|
|
7523
|
+
page: String(page),
|
|
7524
|
+
pageSize: String(PAGE_SIZE)
|
|
7525
|
+
};
|
|
7526
|
+
if (workflowId) query.workflowId = workflowId;
|
|
7527
|
+
const { items, total } = await sync.remote.json(
|
|
7528
|
+
get("/astro/v2/insights", query)
|
|
7529
|
+
);
|
|
7530
|
+
all.push(...items);
|
|
7531
|
+
if (items.length === 0 || all.length >= total) return all;
|
|
7532
|
+
}
|
|
7533
|
+
}
|
|
7534
|
+
async function stemOwners(sync, dir) {
|
|
7535
|
+
const owners = /* @__PURE__ */ new Map();
|
|
7536
|
+
for (const name of await sync.files.list(dir)) {
|
|
7537
|
+
if (!name.endsWith(".md") || name.includes("/")) continue;
|
|
7538
|
+
const bytes = await sync.files.read(join(dir, name));
|
|
7539
|
+
const text2 = bytes ? Buffer.from(bytes).toString("utf8") : "";
|
|
7540
|
+
owners.set(name.slice(0, -".md".length).toLowerCase(), insightFileId(text2));
|
|
7541
|
+
}
|
|
7542
|
+
return owners;
|
|
7543
|
+
}
|
|
7544
|
+
function assignStems(owners, insights) {
|
|
7545
|
+
const used = /* @__PURE__ */ new Set();
|
|
7546
|
+
const heldByOther = (stem, id) => owners.has(stem) && owners.get(stem) !== id;
|
|
7547
|
+
return insights.map((insight) => {
|
|
7548
|
+
const base = lossySlugOr(insight.name, "insight");
|
|
7549
|
+
let stem = base;
|
|
7550
|
+
for (let n = 2; used.has(stem) || heldByOther(stem, insight.id); n++) {
|
|
7551
|
+
stem = `${base}-${n}`;
|
|
7552
|
+
}
|
|
7553
|
+
used.add(stem);
|
|
7554
|
+
return [insight, stem];
|
|
7555
|
+
});
|
|
7556
|
+
}
|
|
7557
|
+
async function pullInsights(sync, options = {}) {
|
|
7558
|
+
const dir = options.dir ?? INSIGHTS_DIR;
|
|
7559
|
+
const insights = options.insightId ? [
|
|
7560
|
+
await sync.remote.json(
|
|
7561
|
+
get(`/astro/v2/insights/${options.insightId}`)
|
|
7562
|
+
)
|
|
7563
|
+
] : await listInsights(sync, options.workflowId);
|
|
7564
|
+
const pulled = [];
|
|
7565
|
+
for (const [insight, stem] of assignStems(
|
|
7566
|
+
await stemOwners(sync, dir),
|
|
7567
|
+
insights
|
|
7568
|
+
)) {
|
|
7569
|
+
const path = join(dir, `${stem}.md`);
|
|
7570
|
+
const text2 = serializeInsightFile(
|
|
7571
|
+
{
|
|
7572
|
+
id: insight.id,
|
|
7573
|
+
type: insight.type,
|
|
7574
|
+
status: insight.status,
|
|
7575
|
+
name: insight.name,
|
|
7576
|
+
nextSteps: insight.nextSteps ?? void 0
|
|
7577
|
+
},
|
|
7578
|
+
insight.content
|
|
7579
|
+
);
|
|
7580
|
+
await sync.files.write(path, Buffer.from(text2, "utf8"));
|
|
7581
|
+
const { id, name, type, status } = insight;
|
|
7582
|
+
pulled.push({ id, name, type, status, path });
|
|
7583
|
+
}
|
|
7584
|
+
return { dir, insights: pulled };
|
|
7585
|
+
}
|
|
7586
|
+
function formatInsightPull(result) {
|
|
7587
|
+
const count = result.insights.length;
|
|
7588
|
+
return [
|
|
7589
|
+
`Pulled ${count} insight${count === 1 ? "" : "s"} into ${result.dir}/.`,
|
|
7590
|
+
...result.insights.map(
|
|
7591
|
+
(insight) => ` - ${insight.path}: ${insight.name} (${insight.type}, ${insight.status})`
|
|
7592
|
+
)
|
|
7593
|
+
].join("\n");
|
|
7594
|
+
}
|
|
7595
|
+
|
|
7315
7596
|
// src/sync/manifest.ts
|
|
7316
7597
|
async function loadManifest(sync, dir) {
|
|
7317
7598
|
const bytes = await sync.files.read(join(dir, MANIFEST));
|
|
@@ -7603,7 +7884,7 @@ function conflict(manifest) {
|
|
|
7603
7884
|
}
|
|
7604
7885
|
async function patch(sync, manifest, writes, deletes) {
|
|
7605
7886
|
try {
|
|
7606
|
-
await sync.remote.json({
|
|
7887
|
+
const snapshot = await sync.remote.json({
|
|
7607
7888
|
method: "PATCH",
|
|
7608
7889
|
path: `/agents/v2/workflows/${manifest.workflowId}/head/files`,
|
|
7609
7890
|
query: new URLSearchParams(),
|
|
@@ -7616,7 +7897,7 @@ async function patch(sync, manifest, writes, deletes) {
|
|
|
7616
7897
|
deletes
|
|
7617
7898
|
}
|
|
7618
7899
|
});
|
|
7619
|
-
return
|
|
7900
|
+
return { issues: snapshot?.issues ?? [] };
|
|
7620
7901
|
} catch (error) {
|
|
7621
7902
|
if (statusOf(error) !== 409) throw error;
|
|
7622
7903
|
return "conflict";
|
|
@@ -7686,14 +7967,20 @@ async function pushWorkflow(sync, dir, workflowId, options = {}) {
|
|
|
7686
7967
|
if (writes.length === 0 && deletes.length === 0) {
|
|
7687
7968
|
return { ...result, rev: manifest.rev, versionId: manifest.versionId };
|
|
7688
7969
|
}
|
|
7689
|
-
|
|
7970
|
+
const applied = await patch(sync, manifest, writes, deletes);
|
|
7971
|
+
if (applied !== "conflict") {
|
|
7690
7972
|
const next = await refresh(
|
|
7691
7973
|
sync,
|
|
7692
7974
|
dir,
|
|
7693
7975
|
manifest,
|
|
7694
7976
|
new Map(writes.map((w) => [w.path, w.hash]))
|
|
7695
7977
|
);
|
|
7696
|
-
return {
|
|
7978
|
+
return {
|
|
7979
|
+
...result,
|
|
7980
|
+
rev: next.rev,
|
|
7981
|
+
versionId: next.versionId,
|
|
7982
|
+
issues: applied.issues
|
|
7983
|
+
};
|
|
7697
7984
|
}
|
|
7698
7985
|
if (!options.rebase || attempt >= REBASE_ATTEMPTS) throw conflict(manifest);
|
|
7699
7986
|
rebasedFrom ??= manifest.rev;
|
|
@@ -7709,9 +7996,23 @@ function formatWorkflowPush(result) {
|
|
|
7709
7996
|
`Pushed to workflow ${result.workflowId}: ${result.written.length} written, ${result.deleted.length} deleted.${rebased}`,
|
|
7710
7997
|
...result.written.map((path) => ` + ${path}`),
|
|
7711
7998
|
...result.deleted.map((path) => ` - ${path}`),
|
|
7712
|
-
`Head is now rev ${result.rev} (version ${result.versionId})
|
|
7999
|
+
`Head is now rev ${result.rev} (version ${result.versionId}).`,
|
|
8000
|
+
...formatIssues(result.issues ?? [])
|
|
7713
8001
|
].join("\n");
|
|
7714
8002
|
}
|
|
8003
|
+
function formatIssues(issues) {
|
|
8004
|
+
if (issues.length === 0) return [];
|
|
8005
|
+
const errors = issues.filter((issue) => issue.severity === "error").length;
|
|
8006
|
+
const warnings = issues.length - errors;
|
|
8007
|
+
const count = (n, noun) => `${n} ${noun}${n === 1 ? "" : "s"}`;
|
|
8008
|
+
const summary2 = errors === 0 ? `Validation: ${count(warnings, "warning")}, nothing blocks a publish.` : `Validation: ${count(errors, "error")}${warnings > 0 ? `, ${count(warnings, "warning")}` : ""}. Fix the errors before you publish.`;
|
|
8009
|
+
return [
|
|
8010
|
+
summary2,
|
|
8011
|
+
...issues.map(
|
|
8012
|
+
(issue) => ` ${issue.severity === "error" ? "!" : "?"} ${issue.path.join("/") || "(workflow)"}: ${issue.message}`
|
|
8013
|
+
)
|
|
8014
|
+
];
|
|
8015
|
+
}
|
|
7715
8016
|
|
|
7716
8017
|
// src/sync/run.ts
|
|
7717
8018
|
function flag(parsed, name) {
|
|
@@ -7724,6 +8025,35 @@ function integer(parsed, name) {
|
|
|
7724
8025
|
throw new UsageError(`--${name} must be a whole number`);
|
|
7725
8026
|
return Number(raw);
|
|
7726
8027
|
}
|
|
8028
|
+
function bounded(parsed, name, min, max) {
|
|
8029
|
+
const value2 = integer(parsed, name);
|
|
8030
|
+
if (value2 === void 0) return void 0;
|
|
8031
|
+
if (value2 < min || value2 > max)
|
|
8032
|
+
throw new UsageError(`--${name} must be between ${min} and ${max}`);
|
|
8033
|
+
return value2;
|
|
8034
|
+
}
|
|
8035
|
+
function executionWaitOptions(parsed) {
|
|
8036
|
+
return {
|
|
8037
|
+
executionId: parsed.positionals[0],
|
|
8038
|
+
timeoutSeconds: bounded(parsed, "timeout", 0, MAX_WAIT_TIMEOUT_SECONDS),
|
|
8039
|
+
intervalSeconds: bounded(parsed, "interval", 1, MAX_WAIT_INTERVAL_SECONDS)
|
|
8040
|
+
};
|
|
8041
|
+
}
|
|
8042
|
+
function given(value2, name) {
|
|
8043
|
+
if (value2 === void 0) return void 0;
|
|
8044
|
+
if (value2 === "") throw new UsageError(`${name} must not be empty`);
|
|
8045
|
+
return value2;
|
|
8046
|
+
}
|
|
8047
|
+
function insightPullOptions(parsed) {
|
|
8048
|
+
const options = {};
|
|
8049
|
+
const insightId = given(parsed.positionals[0], "<insight-id>");
|
|
8050
|
+
if (insightId) options.insightId = insightId;
|
|
8051
|
+
const workflowId = given(flag(parsed, "workflow-id"), "--workflow-id");
|
|
8052
|
+
if (workflowId) options.workflowId = workflowId;
|
|
8053
|
+
const dir = given(flag(parsed, "dir"), "--dir");
|
|
8054
|
+
if (dir) options.dir = dir;
|
|
8055
|
+
return options;
|
|
8056
|
+
}
|
|
7727
8057
|
function executionOptions(parsed) {
|
|
7728
8058
|
const summaryOnly = flag(parsed, "summary-only") === "true";
|
|
7729
8059
|
const glob = flag(parsed, "files");
|
|
@@ -7785,6 +8115,20 @@ async function execute(command, parsed, sync, deps) {
|
|
|
7785
8115
|
);
|
|
7786
8116
|
return { result: result2, text: formatWorkflowPush(result2) };
|
|
7787
8117
|
}
|
|
8118
|
+
if (command.command === "execution wait") {
|
|
8119
|
+
const result2 = await waitForExecution(sync, {
|
|
8120
|
+
...executionWaitOptions(parsed),
|
|
8121
|
+
...deps.executionWait
|
|
8122
|
+
});
|
|
8123
|
+
return { result: result2, text: formatExecutionWait(result2) };
|
|
8124
|
+
}
|
|
8125
|
+
if (command.command === "insight pull") {
|
|
8126
|
+
const result2 = await pullInsights(sync, {
|
|
8127
|
+
...deps.insightPull,
|
|
8128
|
+
...insightPullOptions(parsed)
|
|
8129
|
+
});
|
|
8130
|
+
return { result: result2, text: formatInsightPull(result2) };
|
|
8131
|
+
}
|
|
7788
8132
|
if (command.command !== "execution pull")
|
|
7789
8133
|
throw new UsageError(`unknown command ${command.command}`);
|
|
7790
8134
|
const result = await pullExecution(sync, {
|
|
@@ -7807,15 +8151,20 @@ async function runSync(command, parsed, deps) {
|
|
|
7807
8151
|
export {
|
|
7808
8152
|
ALL_COMMANDS,
|
|
7809
8153
|
COMMANDS,
|
|
8154
|
+
INSIGHTS_DIR,
|
|
7810
8155
|
MANIFEST,
|
|
7811
8156
|
SYNC_COMMANDS,
|
|
7812
8157
|
UsageError,
|
|
7813
8158
|
buildRequest,
|
|
7814
8159
|
formatActivitiesMarkdown,
|
|
7815
8160
|
formatExecutionPull,
|
|
8161
|
+
formatExecutionWait,
|
|
8162
|
+
formatInsightPull,
|
|
7816
8163
|
formatWorkflowPull,
|
|
7817
8164
|
formatWorkflowPush,
|
|
7818
8165
|
headFilesRequest,
|
|
8166
|
+
insightFileId,
|
|
8167
|
+
isTerminalExecutionStatus,
|
|
7819
8168
|
leadingWords,
|
|
7820
8169
|
loadManifest,
|
|
7821
8170
|
matchCommand,
|
|
@@ -7823,12 +8172,15 @@ export {
|
|
|
7823
8172
|
parseInvocation,
|
|
7824
8173
|
pullDir,
|
|
7825
8174
|
pullExecution,
|
|
8175
|
+
pullInsights,
|
|
7826
8176
|
pullWorkflow,
|
|
7827
8177
|
pushWorkflow,
|
|
7828
8178
|
readManifest,
|
|
7829
8179
|
render,
|
|
7830
8180
|
runSync,
|
|
7831
8181
|
runsLocally,
|
|
8182
|
+
serializeInsightFile,
|
|
8183
|
+
waitForExecution,
|
|
7832
8184
|
withSyncCommands,
|
|
7833
8185
|
writeManifest
|
|
7834
8186
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@asteroidai/cli",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.123",
|
|
4
4
|
"description": "The asteroid command-line interface for the Asteroid platform.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
"cli-table3": "^0.6.5",
|
|
24
24
|
"commander": "^15.0.0",
|
|
25
25
|
"env-paths": "^4.0.0",
|
|
26
|
-
"proper-lockfile": "^4.1.2"
|
|
26
|
+
"proper-lockfile": "^4.1.2",
|
|
27
|
+
"yaml": "^2.9.0"
|
|
27
28
|
},
|
|
28
29
|
"devDependencies": {
|
|
29
30
|
"@types/node": "^22.20.1",
|
|
@@ -33,7 +34,6 @@
|
|
|
33
34
|
"tsx": "^4.23.12",
|
|
34
35
|
"typescript": "5.9.3",
|
|
35
36
|
"vitest": "^4.1.11",
|
|
36
|
-
"yaml": "^2.9.0",
|
|
37
37
|
"@asteroid/workflow-files": "0.1.0"
|
|
38
38
|
},
|
|
39
39
|
"scripts": {
|