@doist/automations-cli 1.27.0 → 1.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -3
- package/dist/{chunk-ZTBPLXQS.js → chunk-TGFCBT2H.js} +55 -15
- package/dist/chunk-TGFCBT2H.js.map +7 -0
- package/dist/index.js +811 -363
- package/dist/index.js.map +4 -4
- package/dist/lib/skills/update-installed.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-ZTBPLXQS.js.map +0 -7
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
collectSkillStatus,
|
|
8
8
|
getInstaller,
|
|
9
9
|
updateAllInstalledSkills
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-TGFCBT2H.js";
|
|
11
11
|
|
|
12
12
|
// ../../node_modules/ajv/dist/compile/codegen/code.js
|
|
13
13
|
var require_code = __commonJS({
|
|
@@ -6899,7 +6899,7 @@ import { fileURLToPath } from "node:url";
|
|
|
6899
6899
|
// package.json
|
|
6900
6900
|
var package_default = {
|
|
6901
6901
|
name: "@doist/automations-cli",
|
|
6902
|
-
version: "1.
|
|
6902
|
+
version: "1.28.0",
|
|
6903
6903
|
description: "Command-line client for Todoist Automations",
|
|
6904
6904
|
type: "module",
|
|
6905
6905
|
bin: {
|
|
@@ -29192,6 +29192,11 @@ var automationListResponseSchema = external_exports.object({
|
|
|
29192
29192
|
data: external_exports.array(automationSchema),
|
|
29193
29193
|
nextCursor: external_exports.string().nullable()
|
|
29194
29194
|
});
|
|
29195
|
+
var triggerAutomationResponseSchema = external_exports.object({
|
|
29196
|
+
automationId: external_exports.string(),
|
|
29197
|
+
runId: external_exports.string(),
|
|
29198
|
+
status: external_exports.literal("pending")
|
|
29199
|
+
});
|
|
29195
29200
|
var updateAutomationSchema = external_exports.object({
|
|
29196
29201
|
enabled: external_exports.boolean().optional(),
|
|
29197
29202
|
manifest: manifestSchema.optional()
|
|
@@ -31057,13 +31062,19 @@ var AutomationsRequestError = class extends Error {
|
|
|
31057
31062
|
* scope: a challenge can say the grant falls short without saying of what.
|
|
31058
31063
|
*/
|
|
31059
31064
|
requiredScope;
|
|
31060
|
-
|
|
31065
|
+
/**
|
|
31066
|
+
* The `X-Request-Id` the response carried, which the backend logs the request
|
|
31067
|
+
* under. Undefined in a browser unless the header is exposed by CORS.
|
|
31068
|
+
*/
|
|
31069
|
+
requestId;
|
|
31070
|
+
constructor(message, httpStatusCode, responseData, authChallenge2, requestId) {
|
|
31061
31071
|
super(message);
|
|
31062
31072
|
this.name = "AutomationsRequestError";
|
|
31063
31073
|
this.httpStatusCode = httpStatusCode;
|
|
31064
31074
|
this.responseData = responseData;
|
|
31065
31075
|
this.authChallenge = authChallenge2;
|
|
31066
31076
|
this.requiredScope = parseChallengeScope(authChallenge2);
|
|
31077
|
+
this.requestId = requestId;
|
|
31067
31078
|
}
|
|
31068
31079
|
isAuthError() {
|
|
31069
31080
|
return this.httpStatusCode === 401 || this.httpStatusCode === 403;
|
|
@@ -31134,7 +31145,7 @@ async function request(args) {
|
|
|
31134
31145
|
const data = await readResponseBody(response);
|
|
31135
31146
|
if (!response.ok) {
|
|
31136
31147
|
const message = typeof data === "object" && data !== null && "message" in data ? String(data.message) : response.statusText || `Request failed with status ${response.status}`;
|
|
31137
|
-
throw new AutomationsRequestError(message, response.status, data, response.headers.get("WWW-Authenticate") ?? void 0);
|
|
31148
|
+
throw new AutomationsRequestError(message, response.status, data, response.headers.get("WWW-Authenticate") ?? void 0, response.headers.get("X-Request-Id") ?? void 0);
|
|
31138
31149
|
}
|
|
31139
31150
|
return data;
|
|
31140
31151
|
}
|
|
@@ -31545,13 +31556,15 @@ var AutomationsClient = class extends BaseClient {
|
|
|
31545
31556
|
});
|
|
31546
31557
|
return createDraftResponseSchema.parse(data);
|
|
31547
31558
|
}
|
|
31559
|
+
/** Starts a run now. Undefined from an API that predates returning the run. */
|
|
31548
31560
|
async trigger(id, options) {
|
|
31549
31561
|
const parsedId = IdSchema.parse(id);
|
|
31550
|
-
await this.request({
|
|
31562
|
+
const data = await this.request({
|
|
31551
31563
|
method: "POST",
|
|
31552
31564
|
path: automationsEndpoints.trigger(parsedId),
|
|
31553
31565
|
options
|
|
31554
31566
|
});
|
|
31567
|
+
return triggerAutomationResponseSchema.optional().parse(data);
|
|
31555
31568
|
}
|
|
31556
31569
|
async test(id, options) {
|
|
31557
31570
|
const parsedId = IdSchema.parse(id);
|
|
@@ -31886,13 +31899,17 @@ var WorkspaceAutomationsClient = class extends BaseClient {
|
|
|
31886
31899
|
});
|
|
31887
31900
|
return automationSchema.parse(data);
|
|
31888
31901
|
}
|
|
31889
|
-
/**
|
|
31902
|
+
/**
|
|
31903
|
+
* Runs the automation now. The run executes as its creator, not as the caller.
|
|
31904
|
+
* Undefined from an API that predates returning the run.
|
|
31905
|
+
*/
|
|
31890
31906
|
async trigger(workspaceId, id, options) {
|
|
31891
|
-
await this.request({
|
|
31907
|
+
const data = await this.request({
|
|
31892
31908
|
method: "POST",
|
|
31893
31909
|
path: workspaceAutomationsEndpoints.trigger(IdSchema.parse(workspaceId), IdSchema.parse(id)),
|
|
31894
31910
|
options
|
|
31895
31911
|
});
|
|
31912
|
+
return triggerAutomationResponseSchema.optional().parse(data);
|
|
31896
31913
|
}
|
|
31897
31914
|
/**
|
|
31898
31915
|
* Deletes the automation. Workspace admins may delete another member's
|
|
@@ -32802,6 +32819,9 @@ function readManifestMetadata(dto) {
|
|
|
32802
32819
|
...typeof dto.manifest.description === "string" ? { description: dto.manifest.description } : {}
|
|
32803
32820
|
};
|
|
32804
32821
|
}
|
|
32822
|
+
function readPrimaryTriggerType(dto) {
|
|
32823
|
+
return hasTriggerType(dto.manifest) ? getManifestTriggers(dto.manifest)[0]?.type : void 0;
|
|
32824
|
+
}
|
|
32805
32825
|
function summarizeTrigger(trigger) {
|
|
32806
32826
|
if (!isManifestRecord(trigger))
|
|
32807
32827
|
return void 0;
|
|
@@ -33301,24 +33321,11 @@ function readManifestName(manifest) {
|
|
|
33301
33321
|
return typeof name === "string" && name.trim() ? name : "(unnamed)";
|
|
33302
33322
|
}
|
|
33303
33323
|
|
|
33304
|
-
// ../../packages/sdk/dist/esm/
|
|
33324
|
+
// ../../packages/sdk/dist/esm/runs/poll-until-closed.js
|
|
33305
33325
|
var DEFAULT_POLL_INTERVALS_MS = { first: 2e3, max: 1e4 };
|
|
33306
33326
|
var STATUS_REQUEST_GRACE_MS = 1e4;
|
|
33307
|
-
var OPEN_RUN_STATUSES = /* @__PURE__ */ new Set([
|
|
33308
|
-
|
|
33309
|
-
"running"
|
|
33310
|
-
]);
|
|
33311
|
-
async function startDraftTest(draftApi, { draftId, request: request2 }) {
|
|
33312
|
-
const started = await draftApi.test(draftId, request2);
|
|
33313
|
-
return {
|
|
33314
|
-
draftId,
|
|
33315
|
-
testRunId: started.testRunId,
|
|
33316
|
-
trigger: started.trigger,
|
|
33317
|
-
connectorCalls: started.connectorCalls,
|
|
33318
|
-
status: started.status
|
|
33319
|
-
};
|
|
33320
|
-
}
|
|
33321
|
-
async function waitForDraftTest(draftApi, { draftId, testRunId }, { timeoutMs, pollIntervalsMs, requestGraceMs, onPoll, signal }) {
|
|
33327
|
+
var OPEN_RUN_STATUSES = /* @__PURE__ */ new Set(["pending", "running"]);
|
|
33328
|
+
async function pollUntilClosed(read2, { timeoutMs, pollIntervalsMs, requestGraceMs, onPoll, signal }) {
|
|
33322
33329
|
const intervals = pollIntervalsMs ?? DEFAULT_POLL_INTERVALS_MS;
|
|
33323
33330
|
const deadline = Date.now() + timeoutMs;
|
|
33324
33331
|
let intervalMs = intervals.first;
|
|
@@ -33327,24 +33334,57 @@ async function waitForDraftTest(draftApi, { draftId, testRunId }, { timeoutMs, p
|
|
|
33327
33334
|
let last = null;
|
|
33328
33335
|
while (true) {
|
|
33329
33336
|
try {
|
|
33330
|
-
last = await
|
|
33337
|
+
last = await read2(requestSignal);
|
|
33331
33338
|
} catch (error62) {
|
|
33332
33339
|
if (signal.aborted || !requestTimeout.aborted)
|
|
33333
33340
|
throw error62;
|
|
33334
|
-
return
|
|
33341
|
+
return { timedOut: true, run: last };
|
|
33335
33342
|
}
|
|
33336
33343
|
if (!OPEN_RUN_STATUSES.has(last.status)) {
|
|
33337
|
-
return
|
|
33344
|
+
return { timedOut: false, run: last };
|
|
33338
33345
|
}
|
|
33339
33346
|
onPoll?.(last);
|
|
33340
33347
|
const remainingMs = deadline - Date.now();
|
|
33341
33348
|
if (remainingMs <= 0) {
|
|
33342
|
-
return
|
|
33349
|
+
return { timedOut: true, run: last };
|
|
33343
33350
|
}
|
|
33344
33351
|
await delay(Math.min(intervalMs, remainingMs), signal);
|
|
33345
33352
|
intervalMs = Math.min(intervalMs * 2, intervals.max);
|
|
33346
33353
|
}
|
|
33347
33354
|
}
|
|
33355
|
+
function delay(ms, signal) {
|
|
33356
|
+
return new Promise((resolve, reject) => {
|
|
33357
|
+
if (signal.aborted) {
|
|
33358
|
+
reject(signal.reason);
|
|
33359
|
+
return;
|
|
33360
|
+
}
|
|
33361
|
+
const onAbort = () => {
|
|
33362
|
+
clearTimeout(timer);
|
|
33363
|
+
reject(signal.reason);
|
|
33364
|
+
};
|
|
33365
|
+
const timer = setTimeout(() => {
|
|
33366
|
+
signal.removeEventListener("abort", onAbort);
|
|
33367
|
+
resolve();
|
|
33368
|
+
}, ms);
|
|
33369
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
33370
|
+
});
|
|
33371
|
+
}
|
|
33372
|
+
|
|
33373
|
+
// ../../packages/sdk/dist/esm/drafts/draft-test.js
|
|
33374
|
+
async function startDraftTest(draftApi, { draftId, request: request2 }) {
|
|
33375
|
+
const started = await draftApi.test(draftId, request2);
|
|
33376
|
+
return {
|
|
33377
|
+
draftId,
|
|
33378
|
+
testRunId: started.testRunId,
|
|
33379
|
+
trigger: started.trigger,
|
|
33380
|
+
connectorCalls: started.connectorCalls,
|
|
33381
|
+
status: started.status
|
|
33382
|
+
};
|
|
33383
|
+
}
|
|
33384
|
+
async function waitForDraftTest(draftApi, { draftId, testRunId }, options) {
|
|
33385
|
+
const polled = await pollUntilClosed((requestSignal) => draftApi.getTestRun(draftId, testRunId, void 0, requestSignal), options);
|
|
33386
|
+
return polled.timedOut ? toFinished(polled.run, "timed_out") : toFinished(polled.run, toOutcome(polled.run.status));
|
|
33387
|
+
}
|
|
33348
33388
|
function toDraftTestTriggerSelector(value) {
|
|
33349
33389
|
if (typeof value === "number")
|
|
33350
33390
|
return { index: value };
|
|
@@ -33371,22 +33411,64 @@ function toOutcome(status) {
|
|
|
33371
33411
|
return "failed";
|
|
33372
33412
|
}
|
|
33373
33413
|
}
|
|
33374
|
-
|
|
33375
|
-
|
|
33376
|
-
|
|
33377
|
-
|
|
33378
|
-
|
|
33379
|
-
|
|
33380
|
-
|
|
33381
|
-
|
|
33382
|
-
|
|
33414
|
+
|
|
33415
|
+
// ../../packages/sdk/dist/esm/runs/run-api.js
|
|
33416
|
+
function bindRunApi(api, workspaceId, { signal } = {}) {
|
|
33417
|
+
if (workspaceId === null) {
|
|
33418
|
+
return {
|
|
33419
|
+
workspaceId: null,
|
|
33420
|
+
getAutomation: (automationId) => api.automations.get(automationId, { signal }),
|
|
33421
|
+
trigger: (automationId) => api.automations.trigger(automationId, { signal }),
|
|
33422
|
+
getRun: (automationId, runId, args = {}, requestSignal) => api.automationRuns.get(automationId, runId, args, {
|
|
33423
|
+
signal: requestSignal ?? signal
|
|
33424
|
+
})
|
|
33383
33425
|
};
|
|
33384
|
-
|
|
33385
|
-
|
|
33386
|
-
|
|
33387
|
-
|
|
33388
|
-
|
|
33389
|
-
|
|
33426
|
+
}
|
|
33427
|
+
return {
|
|
33428
|
+
workspaceId,
|
|
33429
|
+
getAutomation: (automationId) => api.workspaceAutomations.get(workspaceId, automationId, { signal }),
|
|
33430
|
+
trigger: (automationId) => api.workspaceAutomations.trigger(workspaceId, automationId, { signal }),
|
|
33431
|
+
getRun: (automationId, runId, args = {}, requestSignal) => api.workspaceAutomationRuns.get(workspaceId, automationId, runId, args, {
|
|
33432
|
+
signal: requestSignal ?? signal
|
|
33433
|
+
})
|
|
33434
|
+
};
|
|
33435
|
+
}
|
|
33436
|
+
|
|
33437
|
+
// ../../packages/sdk/dist/esm/runs/run-wait.js
|
|
33438
|
+
async function waitForRun(runApi, { automationId, runId }, options) {
|
|
33439
|
+
const polled = await pollUntilClosed((requestSignal) => runApi.getRun(automationId, runId, void 0, requestSignal), options);
|
|
33440
|
+
return polled.timedOut ? toFinished2(polled.run, "timed_out") : toFinished2(polled.run, toOutcome2(polled.run.status));
|
|
33441
|
+
}
|
|
33442
|
+
function toFinished2(run, outcome) {
|
|
33443
|
+
const failedStep = run ? findFailedStep(run) : null;
|
|
33444
|
+
return {
|
|
33445
|
+
outcome,
|
|
33446
|
+
status: run?.status ?? "pending",
|
|
33447
|
+
failedStep,
|
|
33448
|
+
error: failedStep?.error ?? (run ? getRunOutcomeReason(run) : null) ?? (run?.status === "data-expired" ? "Temporal no longer holds this run's history, so its outcome is unknown" : null),
|
|
33449
|
+
result: run?.result ?? null
|
|
33450
|
+
};
|
|
33451
|
+
}
|
|
33452
|
+
function findFailedStep(run) {
|
|
33453
|
+
const step = run.steps.find((candidate) => candidate.status === "failed");
|
|
33454
|
+
return step ? { stepId: step.stepId, description: step.description, error: step.error ?? null } : null;
|
|
33455
|
+
}
|
|
33456
|
+
function toOutcome2(status) {
|
|
33457
|
+
switch (status) {
|
|
33458
|
+
case "completed":
|
|
33459
|
+
case "skipped":
|
|
33460
|
+
case "cancelled":
|
|
33461
|
+
return status;
|
|
33462
|
+
default:
|
|
33463
|
+
return "failed";
|
|
33464
|
+
}
|
|
33465
|
+
}
|
|
33466
|
+
|
|
33467
|
+
// ../../packages/sdk/dist/esm/drafts/conversation-draft.js
|
|
33468
|
+
async function findConversationDraftId(api, { conversationId, workspaceId, signal }) {
|
|
33469
|
+
const args = { limit: MAX_PAGINATION_LIMIT };
|
|
33470
|
+
const page = workspaceId === null ? await api.automationDrafts.list(args, { signal }) : await api.workspaceAutomationDrafts.list(workspaceId, args, { signal });
|
|
33471
|
+
return page.data.find((draft) => draft.conversationId === conversationId)?.id ?? null;
|
|
33390
33472
|
}
|
|
33391
33473
|
|
|
33392
33474
|
// src/lib/auth-environment.ts
|
|
@@ -34595,172 +34677,6 @@ import { CliError as CliError3 } from "@doist/cli-core";
|
|
|
34595
34677
|
// src/lib/command-error.ts
|
|
34596
34678
|
import { formatJson as formatJson4, formatNdjson as formatNdjson4, getErrorMessage as getErrorMessage6 } from "@doist/cli-core";
|
|
34597
34679
|
|
|
34598
|
-
// src/lib/billing-errors.ts
|
|
34599
|
-
import { CliError as CliError2 } from "@doist/cli-core";
|
|
34600
|
-
var PAYMENT_REQUIRED_CODE = "PAYMENT_REQUIRED";
|
|
34601
|
-
var TOP_UP_PATH = "/settings/top-up";
|
|
34602
|
-
var TODOIST_SUBSCRIPTION_PATH = "/app/settings/subscription";
|
|
34603
|
-
function topUpUrl() {
|
|
34604
|
-
return `${getEnvironment().appUrl}${TOP_UP_PATH}`;
|
|
34605
|
-
}
|
|
34606
|
-
function todoistSubscriptionUrl() {
|
|
34607
|
-
return `${getEnvironment().todoistAppUrl}${TODOIST_SUBSCRIPTION_PATH}`;
|
|
34608
|
-
}
|
|
34609
|
-
var topUpHint = () => `Top up at ${topUpUrl()}.`;
|
|
34610
|
-
var HINT_BY_DENIAL_CODE = {
|
|
34611
|
-
usage_billing_insufficient_credit: topUpHint,
|
|
34612
|
-
usage_billing_no_billing_account: topUpHint,
|
|
34613
|
-
usage_billing_plan_required: () => `Upgrade at ${todoistSubscriptionUrl()}.`,
|
|
34614
|
-
usage_billing_billing_paused: topUpHint
|
|
34615
|
-
};
|
|
34616
|
-
var PaymentRequiredError = class extends CliError2 {
|
|
34617
|
-
constructor(message, hints) {
|
|
34618
|
-
super(PAYMENT_REQUIRED_CODE, message, { hints });
|
|
34619
|
-
this.name = "PaymentRequiredError";
|
|
34620
|
-
}
|
|
34621
|
-
};
|
|
34622
|
-
function explainBillingFailure(error62) {
|
|
34623
|
-
const refusal = getChatBillingRefusal(error62);
|
|
34624
|
-
if (refusal === null) return error62;
|
|
34625
|
-
const hints = refusal.code === void 0 ? [] : [HINT_BY_DENIAL_CODE[refusal.code]()];
|
|
34626
|
-
return new PaymentRequiredError(refusal.message, hints);
|
|
34627
|
-
}
|
|
34628
|
-
|
|
34629
|
-
// src/lib/output.ts
|
|
34630
|
-
import { formatJson as formatJson3, formatNdjson as formatNdjson3 } from "@doist/cli-core";
|
|
34631
|
-
import { stripVTControlCharacters } from "node:util";
|
|
34632
|
-
function getOutputMode(args) {
|
|
34633
|
-
if (args.json && args.ndjson) {
|
|
34634
|
-
throw new Error("Choose either --json or --ndjson, not both.");
|
|
34635
|
-
}
|
|
34636
|
-
if (args.json) return "json";
|
|
34637
|
-
if (args.ndjson) return "ndjson";
|
|
34638
|
-
return "human";
|
|
34639
|
-
}
|
|
34640
|
-
function sanitizeTerminalText(value) {
|
|
34641
|
-
return stripVTControlCharacters(value).replace(
|
|
34642
|
-
/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/gu,
|
|
34643
|
-
""
|
|
34644
|
-
);
|
|
34645
|
-
}
|
|
34646
|
-
function writeResult(mode, result, presentation, stream = process.stdout) {
|
|
34647
|
-
if (mode === "json") {
|
|
34648
|
-
stream.write(`${formatJson3(presentation.json?.(result) ?? result)}
|
|
34649
|
-
`);
|
|
34650
|
-
return;
|
|
34651
|
-
}
|
|
34652
|
-
if (mode === "ndjson") {
|
|
34653
|
-
const output3 = formatNdjson3(
|
|
34654
|
-
presentation.ndjson?.(result) ?? [presentation.json?.(result) ?? result]
|
|
34655
|
-
);
|
|
34656
|
-
if (output3) stream.write(`${output3}
|
|
34657
|
-
`);
|
|
34658
|
-
return;
|
|
34659
|
-
}
|
|
34660
|
-
const output2 = sanitizeTerminalText(presentation.human(result));
|
|
34661
|
-
stream.write(output2.endsWith("\n") ? output2 : `${output2}
|
|
34662
|
-
`);
|
|
34663
|
-
}
|
|
34664
|
-
async function writeNdjsonItem(item, stream = process.stdout) {
|
|
34665
|
-
if (stream.write(`${formatNdjson3([item])}
|
|
34666
|
-
`) !== false) return;
|
|
34667
|
-
await new Promise((resolve) => stream.once("drain", resolve));
|
|
34668
|
-
}
|
|
34669
|
-
|
|
34670
|
-
// src/lib/command-error.ts
|
|
34671
|
-
function renderFailure(error62, context = {}) {
|
|
34672
|
-
const authExplained = context.source ? explainAuthFailure(error62, context.source) : error62;
|
|
34673
|
-
const explained = explainBillingFailure(authExplained);
|
|
34674
|
-
const code = getErrorCode(explained);
|
|
34675
|
-
return {
|
|
34676
|
-
message: getErrorMessage6(explained),
|
|
34677
|
-
humanMessage: formatError2(explained),
|
|
34678
|
-
...code ? { code } : {}
|
|
34679
|
-
};
|
|
34680
|
-
}
|
|
34681
|
-
function writeFailure(mode, status, failure2) {
|
|
34682
|
-
const { message, code } = failure2;
|
|
34683
|
-
const result = { status, message, ...code ? { code } : {} };
|
|
34684
|
-
if (mode === "json") {
|
|
34685
|
-
process.stdout.write(`${formatJson4(result)}
|
|
34686
|
-
`);
|
|
34687
|
-
} else if (mode === "ndjson") {
|
|
34688
|
-
process.stdout.write(`${formatNdjson4([{ type: status, ...result, terminal: true }])}
|
|
34689
|
-
`);
|
|
34690
|
-
} else {
|
|
34691
|
-
const prefix = status === "error" ? "Error: " : "";
|
|
34692
|
-
process.stdout.write(`${prefix}${sanitizeTerminalText(failure2.humanMessage)}
|
|
34693
|
-
`);
|
|
34694
|
-
}
|
|
34695
|
-
}
|
|
34696
|
-
|
|
34697
|
-
// src/lib/api-command.ts
|
|
34698
|
-
async function runApiCommand(command, operation, presentation, execution = {}) {
|
|
34699
|
-
let mode = "human";
|
|
34700
|
-
let source;
|
|
34701
|
-
const abortController = new AbortController();
|
|
34702
|
-
const onInterrupt = () => abortController.abort();
|
|
34703
|
-
process.once("SIGINT", onInterrupt);
|
|
34704
|
-
try {
|
|
34705
|
-
const globalOptions = getGlobalOptions(command);
|
|
34706
|
-
mode = getOutputMode(globalOptions);
|
|
34707
|
-
const apiConfiguration = await resolveApiConfiguration(globalOptions, {
|
|
34708
|
-
minimumTokenLifetimeMs: execution.minimumTokenLifetimeMs
|
|
34709
|
-
});
|
|
34710
|
-
source = apiConfiguration.source;
|
|
34711
|
-
const context = {
|
|
34712
|
-
api: createAutomationsApi(apiConfiguration),
|
|
34713
|
-
signal: abortController.signal
|
|
34714
|
-
};
|
|
34715
|
-
if (mode === "ndjson" && execution.streamNdjson) {
|
|
34716
|
-
for await (const item of execution.streamNdjson(context)) await writeNdjsonItem(item);
|
|
34717
|
-
return;
|
|
34718
|
-
}
|
|
34719
|
-
const result = await withSpinner(
|
|
34720
|
-
{ text: execution.spinner ?? "Loading..." },
|
|
34721
|
-
() => operation(context)
|
|
34722
|
-
);
|
|
34723
|
-
stopEarlySpinner();
|
|
34724
|
-
writeResult(mode, result, presentation);
|
|
34725
|
-
const exitCode = execution.exitCode?.(result);
|
|
34726
|
-
if (exitCode !== void 0) process.exitCode = exitCode;
|
|
34727
|
-
} catch (error62) {
|
|
34728
|
-
if (abortController.signal.aborted) {
|
|
34729
|
-
writeFailure(
|
|
34730
|
-
mode,
|
|
34731
|
-
"interrupted",
|
|
34732
|
-
error62 instanceof CliError3 ? renderFailure(error62, { mode, source }) : { message: "Interrupted.", humanMessage: "Interrupted." }
|
|
34733
|
-
);
|
|
34734
|
-
process.exitCode = 130;
|
|
34735
|
-
return;
|
|
34736
|
-
}
|
|
34737
|
-
writeFailure(mode, "error", renderFailure(error62, { mode, source }));
|
|
34738
|
-
process.exitCode = 1;
|
|
34739
|
-
} finally {
|
|
34740
|
-
process.removeListener("SIGINT", onInterrupt);
|
|
34741
|
-
}
|
|
34742
|
-
}
|
|
34743
|
-
|
|
34744
|
-
// src/lib/workspace-scope.ts
|
|
34745
|
-
import { Option } from "commander";
|
|
34746
|
-
async function resolveWorkspaceScope(options) {
|
|
34747
|
-
if (options.workspace !== void 0) {
|
|
34748
|
-
return options.workspace;
|
|
34749
|
-
}
|
|
34750
|
-
if (options.personal) {
|
|
34751
|
-
return null;
|
|
34752
|
-
}
|
|
34753
|
-
return getDefaultWorkspaceId();
|
|
34754
|
-
}
|
|
34755
|
-
function addWorkspaceScopeOptions(command) {
|
|
34756
|
-
return command.addOption(
|
|
34757
|
-
new Option(
|
|
34758
|
-
"--workspace <workspace-id>",
|
|
34759
|
-
"Act on this workspace instead of your personal space"
|
|
34760
|
-
).conflicts("personal")
|
|
34761
|
-
).option("--personal", "Act on your personal space, ignoring any pinned workspace");
|
|
34762
|
-
}
|
|
34763
|
-
|
|
34764
34680
|
// ../../packages/sdk/dist/esm/chat-turn/connection-requirements.js
|
|
34765
34681
|
var CONNECTION_LOOKUP_CONCURRENCY = 4;
|
|
34766
34682
|
function deduplicateConnectionRequirements(requirements) {
|
|
@@ -35169,6 +35085,67 @@ function assertSelectionsUnambiguous(requirements, selections) {
|
|
|
35169
35085
|
|
|
35170
35086
|
// ../../packages/sdk/dist/esm/chat-turn/transport.js
|
|
35171
35087
|
import { DefaultChatTransport, readUIMessageStream } from "ai";
|
|
35088
|
+
|
|
35089
|
+
// ../../packages/sdk/dist/esm/chat-turn/errors.js
|
|
35090
|
+
var ChatTurnError = class extends Error {
|
|
35091
|
+
conversationId;
|
|
35092
|
+
requestId;
|
|
35093
|
+
stage;
|
|
35094
|
+
/** The API's error body, when it refused the request with one. */
|
|
35095
|
+
response;
|
|
35096
|
+
constructor({ conversationId, requestId, stage, cause }) {
|
|
35097
|
+
const response = stage === "request" ? parseChatStreamRequestError(cause) : void 0;
|
|
35098
|
+
super(response?.message ?? (cause instanceof Error ? cause.message : String(cause)), {
|
|
35099
|
+
cause
|
|
35100
|
+
});
|
|
35101
|
+
this.name = "ChatTurnError";
|
|
35102
|
+
this.conversationId = conversationId;
|
|
35103
|
+
this.requestId = requestId;
|
|
35104
|
+
this.stage = stage;
|
|
35105
|
+
this.response = response;
|
|
35106
|
+
}
|
|
35107
|
+
get httpStatus() {
|
|
35108
|
+
return this.response?.statusCode;
|
|
35109
|
+
}
|
|
35110
|
+
};
|
|
35111
|
+
function parseChatStreamRequestError(error62) {
|
|
35112
|
+
if (!(error62 instanceof Error) || !error62.message.trim().startsWith("{"))
|
|
35113
|
+
return void 0;
|
|
35114
|
+
let body;
|
|
35115
|
+
try {
|
|
35116
|
+
body = JSON.parse(error62.message);
|
|
35117
|
+
} catch {
|
|
35118
|
+
return void 0;
|
|
35119
|
+
}
|
|
35120
|
+
const parsed = chatStreamRequestErrorResponseSchema.safeParse(body);
|
|
35121
|
+
return parsed.success ? parsed.data : void 0;
|
|
35122
|
+
}
|
|
35123
|
+
function getChatTurnBillingRefusal(error62) {
|
|
35124
|
+
return getChatBillingRefusal(error62 instanceof ChatTurnError ? error62.cause : error62);
|
|
35125
|
+
}
|
|
35126
|
+
function readFailedRequest(error62) {
|
|
35127
|
+
if (error62 instanceof ChatTurnError) {
|
|
35128
|
+
return {
|
|
35129
|
+
requestId: error62.requestId,
|
|
35130
|
+
...error62.httpStatus !== void 0 ? { httpStatus: error62.httpStatus } : {}
|
|
35131
|
+
};
|
|
35132
|
+
}
|
|
35133
|
+
if (error62 instanceof AutomationsRequestError) {
|
|
35134
|
+
return {
|
|
35135
|
+
...error62.requestId ? { requestId: error62.requestId } : {},
|
|
35136
|
+
httpStatus: error62.httpStatusCode
|
|
35137
|
+
};
|
|
35138
|
+
}
|
|
35139
|
+
return {};
|
|
35140
|
+
}
|
|
35141
|
+
function classifyChatTurnFailure(error62) {
|
|
35142
|
+
const { httpStatus: httpStatus2 } = readFailedRequest(error62);
|
|
35143
|
+
if (httpStatus2 !== void 0)
|
|
35144
|
+
return httpStatus2 < 500 ? "refused" : "broken";
|
|
35145
|
+
return error62 instanceof ChatTurnError || error62 instanceof AutomationsNetworkError ? "broken" : "other";
|
|
35146
|
+
}
|
|
35147
|
+
|
|
35148
|
+
// ../../packages/sdk/dist/esm/chat-turn/transport.js
|
|
35172
35149
|
var ChatTurnTransport = class {
|
|
35173
35150
|
transport;
|
|
35174
35151
|
constructor({ token, baseUrl, headers, fetch: fetch2 }) {
|
|
@@ -35195,39 +35172,62 @@ var ChatTurnTransport = class {
|
|
|
35195
35172
|
}
|
|
35196
35173
|
/** Streams one server turn, reporting incremental text and deduplicated tool state changes. */
|
|
35197
35174
|
async streamTurn({ conversationId, workspaceId = null, message, signal, onText, onToolActivity }) {
|
|
35198
|
-
const
|
|
35199
|
-
|
|
35200
|
-
|
|
35201
|
-
|
|
35202
|
-
|
|
35203
|
-
|
|
35204
|
-
|
|
35205
|
-
|
|
35175
|
+
const requestId = globalThis.crypto.randomUUID();
|
|
35176
|
+
const fail = (stage, cause) => signal?.aborted ? cause : new ChatTurnError({ conversationId, requestId, stage, cause });
|
|
35177
|
+
let stream;
|
|
35178
|
+
try {
|
|
35179
|
+
stream = await this.transport.sendMessages({
|
|
35180
|
+
trigger: "submit-message",
|
|
35181
|
+
chatId: conversationId,
|
|
35182
|
+
messageId: void 0,
|
|
35183
|
+
messages: [message],
|
|
35184
|
+
abortSignal: signal,
|
|
35185
|
+
headers: { "X-Request-Id": requestId },
|
|
35186
|
+
body: { conversationId, workspaceId }
|
|
35187
|
+
});
|
|
35188
|
+
} catch (error62) {
|
|
35189
|
+
throw fail("request", error62);
|
|
35190
|
+
}
|
|
35206
35191
|
let finalMessage;
|
|
35207
35192
|
let emittedText = "";
|
|
35208
35193
|
const emittedToolStates = /* @__PURE__ */ new Set();
|
|
35209
|
-
|
|
35210
|
-
|
|
35211
|
-
|
|
35212
|
-
|
|
35213
|
-
|
|
35214
|
-
|
|
35215
|
-
|
|
35216
|
-
|
|
35217
|
-
|
|
35218
|
-
|
|
35219
|
-
|
|
35220
|
-
|
|
35221
|
-
|
|
35222
|
-
|
|
35223
|
-
|
|
35224
|
-
|
|
35225
|
-
|
|
35226
|
-
|
|
35227
|
-
|
|
35228
|
-
|
|
35229
|
-
|
|
35194
|
+
let callbackError;
|
|
35195
|
+
const report = (callback) => {
|
|
35196
|
+
try {
|
|
35197
|
+
callback();
|
|
35198
|
+
} catch (error62) {
|
|
35199
|
+
callbackError = { error: error62 };
|
|
35200
|
+
throw error62;
|
|
35201
|
+
}
|
|
35202
|
+
};
|
|
35203
|
+
try {
|
|
35204
|
+
for await (const currentMessage of readUIMessageStream({
|
|
35205
|
+
stream,
|
|
35206
|
+
terminateOnError: true
|
|
35207
|
+
})) {
|
|
35208
|
+
finalMessage = currentMessage;
|
|
35209
|
+
const visibleText = getVisibleText(currentMessage);
|
|
35210
|
+
if (visibleText.startsWith(emittedText)) {
|
|
35211
|
+
const delta = visibleText.slice(emittedText.length);
|
|
35212
|
+
if (delta)
|
|
35213
|
+
report(() => onText?.(delta));
|
|
35214
|
+
emittedText = visibleText;
|
|
35215
|
+
}
|
|
35216
|
+
for (const part of currentMessage.parts) {
|
|
35217
|
+
const activity = toToolActivity(part);
|
|
35218
|
+
if (!activity)
|
|
35219
|
+
continue;
|
|
35220
|
+
const key = `${activity.toolCallId}:${activity.state}`;
|
|
35221
|
+
if (emittedToolStates.has(key))
|
|
35222
|
+
continue;
|
|
35223
|
+
emittedToolStates.add(key);
|
|
35224
|
+
report(() => onToolActivity?.(activity));
|
|
35225
|
+
}
|
|
35230
35226
|
}
|
|
35227
|
+
} catch (error62) {
|
|
35228
|
+
if (callbackError)
|
|
35229
|
+
throw callbackError.error;
|
|
35230
|
+
throw fail("stream", error62);
|
|
35231
35231
|
}
|
|
35232
35232
|
if (!finalMessage) {
|
|
35233
35233
|
throw new Error("The Automations chat returned an empty response.");
|
|
@@ -35246,6 +35246,236 @@ function toToolActivity(part) {
|
|
|
35246
35246
|
};
|
|
35247
35247
|
}
|
|
35248
35248
|
|
|
35249
|
+
// src/lib/billing-errors.ts
|
|
35250
|
+
import { CliError as CliError2 } from "@doist/cli-core";
|
|
35251
|
+
var PAYMENT_REQUIRED_CODE = "PAYMENT_REQUIRED";
|
|
35252
|
+
var TOP_UP_PATH = "/settings/top-up";
|
|
35253
|
+
var TODOIST_SUBSCRIPTION_PATH = "/app/settings/subscription";
|
|
35254
|
+
function topUpUrl() {
|
|
35255
|
+
return `${getEnvironment().appUrl}${TOP_UP_PATH}`;
|
|
35256
|
+
}
|
|
35257
|
+
function todoistSubscriptionUrl() {
|
|
35258
|
+
return `${getEnvironment().todoistAppUrl}${TODOIST_SUBSCRIPTION_PATH}`;
|
|
35259
|
+
}
|
|
35260
|
+
var topUpHint = () => `Top up at ${topUpUrl()}.`;
|
|
35261
|
+
var HINT_BY_DENIAL_CODE = {
|
|
35262
|
+
usage_billing_insufficient_credit: topUpHint,
|
|
35263
|
+
usage_billing_no_billing_account: topUpHint,
|
|
35264
|
+
usage_billing_plan_required: () => `Upgrade at ${todoistSubscriptionUrl()}.`,
|
|
35265
|
+
usage_billing_billing_paused: topUpHint
|
|
35266
|
+
};
|
|
35267
|
+
var PaymentRequiredError = class extends CliError2 {
|
|
35268
|
+
constructor(message, hints) {
|
|
35269
|
+
super(PAYMENT_REQUIRED_CODE, message, { hints });
|
|
35270
|
+
this.name = "PaymentRequiredError";
|
|
35271
|
+
}
|
|
35272
|
+
};
|
|
35273
|
+
function explainBillingFailure(error62) {
|
|
35274
|
+
const refusal = getChatTurnBillingRefusal(error62);
|
|
35275
|
+
if (refusal === null) return error62;
|
|
35276
|
+
const hints = refusal.code === void 0 ? [] : [HINT_BY_DENIAL_CODE[refusal.code]()];
|
|
35277
|
+
return new PaymentRequiredError(refusal.message, hints);
|
|
35278
|
+
}
|
|
35279
|
+
|
|
35280
|
+
// src/lib/output.ts
|
|
35281
|
+
import { formatJson as formatJson3, formatNdjson as formatNdjson3 } from "@doist/cli-core";
|
|
35282
|
+
import { stripVTControlCharacters } from "node:util";
|
|
35283
|
+
function getOutputMode(args) {
|
|
35284
|
+
if (args.json && args.ndjson) {
|
|
35285
|
+
throw new Error("Choose either --json or --ndjson, not both.");
|
|
35286
|
+
}
|
|
35287
|
+
if (args.json) return "json";
|
|
35288
|
+
if (args.ndjson) return "ndjson";
|
|
35289
|
+
return "human";
|
|
35290
|
+
}
|
|
35291
|
+
function sanitizeTerminalText(value) {
|
|
35292
|
+
return stripVTControlCharacters(value).replace(
|
|
35293
|
+
/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/gu,
|
|
35294
|
+
""
|
|
35295
|
+
);
|
|
35296
|
+
}
|
|
35297
|
+
function writeResult(mode, result, presentation, stream = process.stdout) {
|
|
35298
|
+
if (mode === "json") {
|
|
35299
|
+
stream.write(`${formatJson3(presentation.json?.(result) ?? result)}
|
|
35300
|
+
`);
|
|
35301
|
+
return;
|
|
35302
|
+
}
|
|
35303
|
+
if (mode === "ndjson") {
|
|
35304
|
+
const output3 = formatNdjson3(
|
|
35305
|
+
presentation.ndjson?.(result) ?? [presentation.json?.(result) ?? result]
|
|
35306
|
+
);
|
|
35307
|
+
if (output3) stream.write(`${output3}
|
|
35308
|
+
`);
|
|
35309
|
+
return;
|
|
35310
|
+
}
|
|
35311
|
+
const output2 = sanitizeTerminalText(presentation.human(result));
|
|
35312
|
+
stream.write(output2.endsWith("\n") ? output2 : `${output2}
|
|
35313
|
+
`);
|
|
35314
|
+
}
|
|
35315
|
+
async function writeNdjsonItem(item, stream = process.stdout) {
|
|
35316
|
+
if (stream.write(`${formatNdjson3([item])}
|
|
35317
|
+
`) !== false) return;
|
|
35318
|
+
await new Promise((resolve) => stream.once("drain", resolve));
|
|
35319
|
+
}
|
|
35320
|
+
|
|
35321
|
+
// src/lib/command-error.ts
|
|
35322
|
+
function renderFailure(error62, context = {}) {
|
|
35323
|
+
const authExplained = context.source ? explainAuthFailure(error62, context.source) : error62;
|
|
35324
|
+
const explained = explainBillingFailure(authExplained);
|
|
35325
|
+
const code = getErrorCode(explained);
|
|
35326
|
+
return {
|
|
35327
|
+
message: getErrorMessage6(explained),
|
|
35328
|
+
humanMessage: formatError2(explained),
|
|
35329
|
+
...code ? { code } : {}
|
|
35330
|
+
};
|
|
35331
|
+
}
|
|
35332
|
+
var FALLBACK_FAILURE_CODE = "TDA_COMMAND_FAILED";
|
|
35333
|
+
function toFailureRecord(failure2, details) {
|
|
35334
|
+
return {
|
|
35335
|
+
...details,
|
|
35336
|
+
status: "error",
|
|
35337
|
+
code: failure2.code ?? FALLBACK_FAILURE_CODE,
|
|
35338
|
+
message: failure2.message
|
|
35339
|
+
};
|
|
35340
|
+
}
|
|
35341
|
+
function writeFailure(mode, status, failure2) {
|
|
35342
|
+
const result = status === "error" ? toFailureRecord(failure2) : { status, message: failure2.message };
|
|
35343
|
+
if (mode === "json") {
|
|
35344
|
+
process.stdout.write(`${formatJson4(result)}
|
|
35345
|
+
`);
|
|
35346
|
+
} else if (mode === "ndjson") {
|
|
35347
|
+
process.stdout.write(`${formatNdjson4([{ type: status, ...result, terminal: true }])}
|
|
35348
|
+
`);
|
|
35349
|
+
} else {
|
|
35350
|
+
const prefix = status === "error" ? "Error: " : "";
|
|
35351
|
+
process.stdout.write(`${prefix}${sanitizeTerminalText(failure2.humanMessage)}
|
|
35352
|
+
`);
|
|
35353
|
+
}
|
|
35354
|
+
}
|
|
35355
|
+
|
|
35356
|
+
// src/lib/api-command.ts
|
|
35357
|
+
async function runApiCommand(command, operation, presentation, execution = {}) {
|
|
35358
|
+
let mode = "human";
|
|
35359
|
+
let source;
|
|
35360
|
+
const abortController = new AbortController();
|
|
35361
|
+
const onInterrupt = () => abortController.abort();
|
|
35362
|
+
process.once("SIGINT", onInterrupt);
|
|
35363
|
+
try {
|
|
35364
|
+
const globalOptions = getGlobalOptions(command);
|
|
35365
|
+
mode = getOutputMode(globalOptions);
|
|
35366
|
+
const apiConfiguration = await resolveApiConfiguration(globalOptions, {
|
|
35367
|
+
minimumTokenLifetimeMs: execution.minimumTokenLifetimeMs
|
|
35368
|
+
});
|
|
35369
|
+
source = apiConfiguration.source;
|
|
35370
|
+
const outputMode = mode;
|
|
35371
|
+
const context = {
|
|
35372
|
+
api: createAutomationsApi(apiConfiguration),
|
|
35373
|
+
signal: abortController.signal,
|
|
35374
|
+
progress: (record2) => {
|
|
35375
|
+
if (outputMode === "ndjson") void writeNdjsonItem({ ...record2, terminal: false });
|
|
35376
|
+
}
|
|
35377
|
+
};
|
|
35378
|
+
if (mode === "ndjson" && execution.streamNdjson) {
|
|
35379
|
+
for await (const item of execution.streamNdjson(context)) await writeNdjsonItem(item);
|
|
35380
|
+
return;
|
|
35381
|
+
}
|
|
35382
|
+
const result = await withSpinner(
|
|
35383
|
+
{ text: execution.spinner ?? "Loading..." },
|
|
35384
|
+
() => operation(context)
|
|
35385
|
+
);
|
|
35386
|
+
stopEarlySpinner();
|
|
35387
|
+
writeResult(mode, result, presentation);
|
|
35388
|
+
const exitCode = execution.exitCode?.(result);
|
|
35389
|
+
if (exitCode !== void 0) process.exitCode = exitCode;
|
|
35390
|
+
} catch (error62) {
|
|
35391
|
+
if (abortController.signal.aborted) {
|
|
35392
|
+
writeFailure(
|
|
35393
|
+
mode,
|
|
35394
|
+
"interrupted",
|
|
35395
|
+
error62 instanceof CliError3 ? renderFailure(error62, { mode, source }) : { message: "Interrupted.", humanMessage: "Interrupted." }
|
|
35396
|
+
);
|
|
35397
|
+
process.exitCode = 130;
|
|
35398
|
+
return;
|
|
35399
|
+
}
|
|
35400
|
+
writeFailure(mode, "error", renderFailure(error62, { mode, source }));
|
|
35401
|
+
process.exitCode = 1;
|
|
35402
|
+
} finally {
|
|
35403
|
+
process.removeListener("SIGINT", onInterrupt);
|
|
35404
|
+
}
|
|
35405
|
+
}
|
|
35406
|
+
|
|
35407
|
+
// src/lib/wait-options.ts
|
|
35408
|
+
var DEFAULT_WAIT = "10m";
|
|
35409
|
+
var TOKEN_LIFETIME_MARGIN_MS = 2 * 60 * 1e3;
|
|
35410
|
+
function addWaitOptions(command, waitDescription) {
|
|
35411
|
+
return command.option("--wait", waitDescription).option(
|
|
35412
|
+
"--timeout <duration>",
|
|
35413
|
+
`How long --wait waits, such as 90s or 15m (default ${DEFAULT_WAIT})`
|
|
35414
|
+
);
|
|
35415
|
+
}
|
|
35416
|
+
function resolveWait(options) {
|
|
35417
|
+
if (options.timeout !== void 0 && options.wait !== true) {
|
|
35418
|
+
throw new Error("--timeout only applies with --wait.");
|
|
35419
|
+
}
|
|
35420
|
+
if (options.wait !== true) {
|
|
35421
|
+
return null;
|
|
35422
|
+
}
|
|
35423
|
+
return { timeoutMs: parseTimeout(options.timeout ?? DEFAULT_WAIT) };
|
|
35424
|
+
}
|
|
35425
|
+
function waitTokenLifetimeMs(options) {
|
|
35426
|
+
if (options.wait !== true) {
|
|
35427
|
+
return void 0;
|
|
35428
|
+
}
|
|
35429
|
+
try {
|
|
35430
|
+
return parseTimeout(options.timeout ?? DEFAULT_WAIT) + TOKEN_LIFETIME_MARGIN_MS;
|
|
35431
|
+
} catch {
|
|
35432
|
+
return TOKEN_LIFETIME_MARGIN_MS;
|
|
35433
|
+
}
|
|
35434
|
+
}
|
|
35435
|
+
function parseTimeout(value) {
|
|
35436
|
+
try {
|
|
35437
|
+
return durationStringToMilliseconds(value);
|
|
35438
|
+
} catch {
|
|
35439
|
+
throw new Error(`Invalid --timeout ${value}; use a duration such as 90s, 15m or 1h.`);
|
|
35440
|
+
}
|
|
35441
|
+
}
|
|
35442
|
+
function reportRunProgress(report) {
|
|
35443
|
+
let last;
|
|
35444
|
+
return (run) => {
|
|
35445
|
+
const step = run.steps.find((candidate) => candidate.status === "running")?.stepId;
|
|
35446
|
+
const key = `${run.status}:${step ?? ""}`;
|
|
35447
|
+
if (key === last) return;
|
|
35448
|
+
last = key;
|
|
35449
|
+
report({ status: run.status, ...step ? { step } : {} });
|
|
35450
|
+
};
|
|
35451
|
+
}
|
|
35452
|
+
function toTerminalRecords(result) {
|
|
35453
|
+
return [{ type: result.action, ...result, terminal: true }];
|
|
35454
|
+
}
|
|
35455
|
+
|
|
35456
|
+
// src/lib/workspace-scope.ts
|
|
35457
|
+
import { Option } from "commander";
|
|
35458
|
+
async function resolveWorkspaceScope(options) {
|
|
35459
|
+
if (options.workspace !== void 0) {
|
|
35460
|
+
return options.workspace;
|
|
35461
|
+
}
|
|
35462
|
+
if (options.personal) {
|
|
35463
|
+
return null;
|
|
35464
|
+
}
|
|
35465
|
+
return getDefaultWorkspaceId();
|
|
35466
|
+
}
|
|
35467
|
+
function scopeFlag(scope) {
|
|
35468
|
+
return scope === null ? "--personal" : `--workspace ${scope}`;
|
|
35469
|
+
}
|
|
35470
|
+
function addWorkspaceScopeOptions(command) {
|
|
35471
|
+
return command.addOption(
|
|
35472
|
+
new Option(
|
|
35473
|
+
"--workspace <workspace-id>",
|
|
35474
|
+
"Act on this workspace instead of your personal space"
|
|
35475
|
+
).conflicts("personal")
|
|
35476
|
+
).option("--personal", "Act on your personal space, ignoring any pinned workspace");
|
|
35477
|
+
}
|
|
35478
|
+
|
|
35249
35479
|
// src/commands/read/format.ts
|
|
35250
35480
|
function formatAutomationList(page) {
|
|
35251
35481
|
if (page.data.length === 0) return "No automations found.";
|
|
@@ -35457,8 +35687,35 @@ function describeVisibility(automation) {
|
|
|
35457
35687
|
function summarizeAutomationChange(result) {
|
|
35458
35688
|
return { ...result, automation: summarizeAutomation(result.automation) };
|
|
35459
35689
|
}
|
|
35690
|
+
var RUN_OUTCOME_LABELS = {
|
|
35691
|
+
completed: "completed",
|
|
35692
|
+
failed: "failed",
|
|
35693
|
+
skipped: "was skipped",
|
|
35694
|
+
cancelled: "was cancelled",
|
|
35695
|
+
timed_out: "did not finish before the timeout"
|
|
35696
|
+
};
|
|
35460
35697
|
function formatAutomationTrigger(result) {
|
|
35461
|
-
|
|
35698
|
+
if (result.action === "triggered") {
|
|
35699
|
+
if (result.runId === null) {
|
|
35700
|
+
return `Triggered automation ${result.automationId}.`;
|
|
35701
|
+
}
|
|
35702
|
+
return [
|
|
35703
|
+
`Started run ${result.runId} of automation ${result.automationId}.`,
|
|
35704
|
+
`Details: ${result.command}`
|
|
35705
|
+
].join("\n");
|
|
35706
|
+
}
|
|
35707
|
+
const lines = [
|
|
35708
|
+
`Run ${result.runId} of automation ${result.automationId} ${RUN_OUTCOME_LABELS[result.outcome]}.`
|
|
35709
|
+
];
|
|
35710
|
+
if (result.outcome === "timed_out") lines.push(`Status: ${result.status}`);
|
|
35711
|
+
if (result.failedStep) {
|
|
35712
|
+
lines.push(`Failed step: ${result.failedStep.stepId} (${result.failedStep.description})`);
|
|
35713
|
+
}
|
|
35714
|
+
if (result.error)
|
|
35715
|
+
lines.push(`${result.outcome === "skipped" ? "Reason" : "Error"}: ${result.error}`);
|
|
35716
|
+
if (result.result) lines.push(`Result: ${result.result.text}`);
|
|
35717
|
+
lines.push(`Details: ${result.command}`);
|
|
35718
|
+
return lines.join("\n");
|
|
35462
35719
|
}
|
|
35463
35720
|
function formatAutomationDelete(result) {
|
|
35464
35721
|
if (result.action === "delete_preview") {
|
|
@@ -35473,6 +35730,56 @@ function capitalize(value) {
|
|
|
35473
35730
|
return `${value.charAt(0).toUpperCase()}${value.slice(1)}`;
|
|
35474
35731
|
}
|
|
35475
35732
|
|
|
35733
|
+
// src/commands/automation/trigger-service.ts
|
|
35734
|
+
import { CliError as CliError4 } from "@doist/cli-core";
|
|
35735
|
+
async function triggerAutomation(runApi, {
|
|
35736
|
+
automationId,
|
|
35737
|
+
wait: wait2,
|
|
35738
|
+
signal,
|
|
35739
|
+
onProgress
|
|
35740
|
+
}) {
|
|
35741
|
+
const triggerType = readPrimaryTriggerType(await runApi.getAutomation(automationId));
|
|
35742
|
+
if (triggerType !== "schedule") {
|
|
35743
|
+
throw new Error(
|
|
35744
|
+
"This automation can only be triggered via its configured mechanism, not manually."
|
|
35745
|
+
);
|
|
35746
|
+
}
|
|
35747
|
+
const started = await runApi.trigger(automationId);
|
|
35748
|
+
const flag = scopeFlag(runApi.workspaceId);
|
|
35749
|
+
if (!started) {
|
|
35750
|
+
if (wait2) {
|
|
35751
|
+
throw new CliError4(
|
|
35752
|
+
"TRIGGER_RUN_UNREPORTED",
|
|
35753
|
+
"The run started, but the API did not say which one it was, so it cannot be waited for.",
|
|
35754
|
+
{ hints: [`See the latest run: tda run list ${automationId} --limit 1 ${flag}`] }
|
|
35755
|
+
);
|
|
35756
|
+
}
|
|
35757
|
+
return { action: "triggered", automationId, runId: null, status: null, command: null };
|
|
35758
|
+
}
|
|
35759
|
+
const identity = {
|
|
35760
|
+
automationId,
|
|
35761
|
+
runId: started.runId,
|
|
35762
|
+
command: `tda run view ${automationId} ${started.runId} ${flag}`
|
|
35763
|
+
};
|
|
35764
|
+
if (!wait2) {
|
|
35765
|
+
return { action: "triggered", ...identity, status: started.status };
|
|
35766
|
+
}
|
|
35767
|
+
const finished = await waitForRun(runApi, identity, {
|
|
35768
|
+
...wait2,
|
|
35769
|
+
signal,
|
|
35770
|
+
...onProgress ? { onPoll: reportRunProgress(onProgress) } : {}
|
|
35771
|
+
});
|
|
35772
|
+
return {
|
|
35773
|
+
action: "run_finished",
|
|
35774
|
+
...identity,
|
|
35775
|
+
outcome: finished.outcome,
|
|
35776
|
+
status: finished.status,
|
|
35777
|
+
failedStep: finished.failedStep,
|
|
35778
|
+
error: finished.error,
|
|
35779
|
+
result: finished.result
|
|
35780
|
+
};
|
|
35781
|
+
}
|
|
35782
|
+
|
|
35476
35783
|
// src/commands/automation/index.ts
|
|
35477
35784
|
function registerAutomationCommand(program) {
|
|
35478
35785
|
const automation = program.command("automation").description("Inspect and manage published automations");
|
|
@@ -35522,30 +35829,29 @@ function registerAutomationCommand(program) {
|
|
|
35522
35829
|
registerEnabledStateCommand(automation, "enable", true);
|
|
35523
35830
|
registerEnabledStateCommand(automation, "disable", false);
|
|
35524
35831
|
addWorkspaceScopeOptions(
|
|
35525
|
-
|
|
35832
|
+
addWaitOptions(
|
|
35833
|
+
automation.command("trigger").description("Start a live automation run and print its run ID").argument("<automation-id>", "Automation ID"),
|
|
35834
|
+
"Wait for the run to end and exit 1 unless it completed"
|
|
35835
|
+
)
|
|
35526
35836
|
).action(async (automationId, options, command) => {
|
|
35527
35837
|
await runApiCommand(
|
|
35528
35838
|
command,
|
|
35529
|
-
async ({ api, signal }) => {
|
|
35530
|
-
const
|
|
35531
|
-
const
|
|
35532
|
-
|
|
35839
|
+
async ({ api, signal, progress }) => {
|
|
35840
|
+
const wait2 = resolveWait(options);
|
|
35841
|
+
const runApi = bindRunApi(api, await resolveWorkspaceScope(options), { signal });
|
|
35842
|
+
return triggerAutomation(runApi, {
|
|
35843
|
+
automationId,
|
|
35844
|
+
wait: wait2,
|
|
35845
|
+
signal,
|
|
35846
|
+
onProgress: (state) => progress({ type: "run_progress", automationId, ...state })
|
|
35533
35847
|
});
|
|
35534
|
-
const triggerType = hasTriggerType(automation2.manifest) ? getManifestTriggers(automation2.manifest)[0]?.type : void 0;
|
|
35535
|
-
if (triggerType !== "schedule") {
|
|
35536
|
-
throw new Error(
|
|
35537
|
-
"This automation can only be triggered via its configured mechanism, not manually."
|
|
35538
|
-
);
|
|
35539
|
-
}
|
|
35540
|
-
if (workspaceId === null) {
|
|
35541
|
-
await api.automations.trigger(automationId, { signal });
|
|
35542
|
-
} else {
|
|
35543
|
-
await api.workspaceAutomations.trigger(workspaceId, automationId, { signal });
|
|
35544
|
-
}
|
|
35545
|
-
return { action: "triggered", automationId };
|
|
35546
35848
|
},
|
|
35547
|
-
{ human: formatAutomationTrigger },
|
|
35548
|
-
{
|
|
35849
|
+
{ human: formatAutomationTrigger, ndjson: toTerminalRecords },
|
|
35850
|
+
{
|
|
35851
|
+
spinner: options.wait === true ? "Running automation..." : "Triggering automation...",
|
|
35852
|
+
minimumTokenLifetimeMs: waitTokenLifetimeMs(options),
|
|
35853
|
+
exitCode: (result) => result.action === "run_finished" && result.outcome !== "completed" ? 1 : void 0
|
|
35854
|
+
}
|
|
35549
35855
|
);
|
|
35550
35856
|
});
|
|
35551
35857
|
addWorkspaceScopeOptions(
|
|
@@ -35802,6 +36108,7 @@ function collectOption(value, previous) {
|
|
|
35802
36108
|
}
|
|
35803
36109
|
|
|
35804
36110
|
// src/commands/chat/chat-service.ts
|
|
36111
|
+
import { CliError as CliError6 } from "@doist/cli-core";
|
|
35805
36112
|
import { createInterface } from "node:readline/promises";
|
|
35806
36113
|
|
|
35807
36114
|
// src/commands/chat/connection-authorizer.ts
|
|
@@ -35816,6 +36123,13 @@ var FINAL_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
|
35816
36123
|
"session_status",
|
|
35817
36124
|
"turn_completed"
|
|
35818
36125
|
]);
|
|
36126
|
+
var UNRECORDED_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
36127
|
+
"assistant_text",
|
|
36128
|
+
"connection_authorization_waiting",
|
|
36129
|
+
"turn_progress"
|
|
36130
|
+
]);
|
|
36131
|
+
var INTERRUPTING_EVENT_TYPES = /* @__PURE__ */ new Set(["error", "interrupted"]);
|
|
36132
|
+
var HEARTBEAT_EVENT_TYPES = /* @__PURE__ */ new Set(["connection_authorization_waiting", "turn_progress"]);
|
|
35819
36133
|
var SPINNER_TEXT = {
|
|
35820
36134
|
connection_authorization_opened: "Waiting for authorization...",
|
|
35821
36135
|
connection_authorization_started: "Waiting for authorization...",
|
|
@@ -35826,16 +36140,47 @@ var OutputWriter = class {
|
|
|
35826
36140
|
stream;
|
|
35827
36141
|
diagnosticStream;
|
|
35828
36142
|
events = [];
|
|
36143
|
+
streamText;
|
|
35829
36144
|
assistantLineOpen = false;
|
|
35830
36145
|
currentConversationId;
|
|
35831
|
-
|
|
36146
|
+
/** The current turn's text, one entry per step. */
|
|
36147
|
+
turnSteps = [];
|
|
36148
|
+
stepOpen = false;
|
|
36149
|
+
bufferedText = "";
|
|
36150
|
+
bufferedTextConversationId;
|
|
36151
|
+
constructor(mode, stream = process.stdout, diagnosticStream = process.stderr, { streamText = false } = {}) {
|
|
35832
36152
|
this.mode = mode;
|
|
35833
36153
|
this.stream = stream;
|
|
35834
36154
|
this.diagnosticStream = diagnosticStream;
|
|
36155
|
+
this.streamText = streamText;
|
|
36156
|
+
}
|
|
36157
|
+
/**
|
|
36158
|
+
* Assistant text as it arrives. A human reader, or an NDJSON caller that
|
|
36159
|
+
* asked with `--stream-text`, gets each fragment as `assistant_text`;
|
|
36160
|
+
* otherwise the text of a step comes out whole as one `assistant_message`
|
|
36161
|
+
* before whatever follows it.
|
|
36162
|
+
*/
|
|
36163
|
+
appendAssistantText(conversationId, text2) {
|
|
36164
|
+
if (!this.stepOpen) this.turnSteps.push("");
|
|
36165
|
+
this.stepOpen = true;
|
|
36166
|
+
this.turnSteps[this.turnSteps.length - 1] += text2;
|
|
36167
|
+
if (this.mode === "human" || this.mode === "ndjson" && this.streamText) {
|
|
36168
|
+
this.emit({ type: "assistant_text", conversationId, text: text2 });
|
|
36169
|
+
return;
|
|
36170
|
+
}
|
|
36171
|
+
this.bufferedText += text2;
|
|
36172
|
+
this.bufferedTextConversationId = conversationId;
|
|
35835
36173
|
}
|
|
35836
36174
|
/** Records and renders an event; JSON mode retains structured output until `finish`. */
|
|
35837
36175
|
emit(event) {
|
|
35838
|
-
|
|
36176
|
+
const endsStep = event.type !== "assistant_message" && event.type !== "assistant_text" && !HEARTBEAT_EVENT_TYPES.has(event.type);
|
|
36177
|
+
if (endsStep) {
|
|
36178
|
+
this.flushAssistantMessage(INTERRUPTING_EVENT_TYPES.has(event.type));
|
|
36179
|
+
this.stepOpen = false;
|
|
36180
|
+
}
|
|
36181
|
+
if (this.mode === "json" && !UNRECORDED_EVENT_TYPES.has(event.type)) {
|
|
36182
|
+
this.events.push(event);
|
|
36183
|
+
}
|
|
35839
36184
|
if (event.conversationId) this.currentConversationId = event.conversationId;
|
|
35840
36185
|
if (this.mode === "ndjson") {
|
|
35841
36186
|
this.stream.write(`${formatNdjson5([event])}
|
|
@@ -35851,17 +36196,28 @@ var OutputWriter = class {
|
|
|
35851
36196
|
get conversationId() {
|
|
35852
36197
|
return this.currentConversationId;
|
|
35853
36198
|
}
|
|
36199
|
+
/** The current turn's text so far, its steps separated by blank lines. */
|
|
36200
|
+
get assistantText() {
|
|
36201
|
+
return this.turnSteps.join("\n\n");
|
|
36202
|
+
}
|
|
36203
|
+
/** Starts collecting the text of a new turn. */
|
|
36204
|
+
startTurn() {
|
|
36205
|
+
this.turnSteps = [];
|
|
36206
|
+
this.stepOpen = false;
|
|
36207
|
+
}
|
|
35854
36208
|
/** Emits structured conversation status with human formatting owned by this output layer. */
|
|
35855
36209
|
emitConversationStatus(snapshot, pendingActionKind) {
|
|
35856
36210
|
this.emit({
|
|
35857
36211
|
type: "session_status",
|
|
35858
36212
|
conversationId: snapshot.conversation.id,
|
|
35859
36213
|
...snapshot,
|
|
35860
|
-
humanText: formatConversationStatus(snapshot, pendingActionKind)
|
|
36214
|
+
humanText: formatConversationStatus(snapshot, pendingActionKind),
|
|
36215
|
+
terminal: true
|
|
35861
36216
|
});
|
|
35862
36217
|
}
|
|
35863
36218
|
/** Finalizes output, including the accumulated event list in JSON mode. */
|
|
35864
36219
|
finish(summary) {
|
|
36220
|
+
this.flushAssistantMessage(false);
|
|
35865
36221
|
stopEarlySpinner();
|
|
35866
36222
|
if (this.mode === "json") {
|
|
35867
36223
|
this.stream.write(`${formatJson5({ ...summary, events: this.events })}
|
|
@@ -35873,6 +36229,17 @@ var OutputWriter = class {
|
|
|
35873
36229
|
this.assistantLineOpen = false;
|
|
35874
36230
|
}
|
|
35875
36231
|
}
|
|
36232
|
+
flushAssistantMessage(partial2) {
|
|
36233
|
+
if (!this.bufferedText) return;
|
|
36234
|
+
const text2 = this.bufferedText;
|
|
36235
|
+
this.bufferedText = "";
|
|
36236
|
+
this.emit({
|
|
36237
|
+
type: "assistant_message",
|
|
36238
|
+
conversationId: this.bufferedTextConversationId,
|
|
36239
|
+
text: text2,
|
|
36240
|
+
...partial2 ? { partial: true } : {}
|
|
36241
|
+
});
|
|
36242
|
+
}
|
|
35876
36243
|
/**
|
|
35877
36244
|
* The agent goes quiet for long stretches between events, so a spinner
|
|
35878
36245
|
* fills the gap. Taking it down before every write keeps its next frame
|
|
@@ -36096,6 +36463,7 @@ function getMessageRoleLabel(role) {
|
|
|
36096
36463
|
|
|
36097
36464
|
// src/commands/chat/connection-authorizer.ts
|
|
36098
36465
|
var CONNECTION_POLL_INTERVAL_MS = 2e3;
|
|
36466
|
+
var CONNECTION_WAITING_REPORT_INTERVAL_MS = 15e3;
|
|
36099
36467
|
var CONNECTION_AUTHORIZATION_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
36100
36468
|
var ApiConnectionAuthorizer = class {
|
|
36101
36469
|
connections;
|
|
@@ -36187,6 +36555,7 @@ var ApiConnectionAuthorizer = class {
|
|
|
36187
36555
|
}
|
|
36188
36556
|
async waitUntilAuthorized(request2, connectionId, signal) {
|
|
36189
36557
|
const startedAt = Date.now();
|
|
36558
|
+
let reportedAt;
|
|
36190
36559
|
while (true) {
|
|
36191
36560
|
if (signal?.aborted) throw new Error("Connection authorization interrupted.");
|
|
36192
36561
|
const connection = await this.connections.get(connectionId, { signal });
|
|
@@ -36214,16 +36583,20 @@ var ApiConnectionAuthorizer = class {
|
|
|
36214
36583
|
`Timed out waiting for ${request2.serviceName} connection authorization.`
|
|
36215
36584
|
);
|
|
36216
36585
|
}
|
|
36217
|
-
|
|
36218
|
-
|
|
36219
|
-
|
|
36220
|
-
|
|
36221
|
-
|
|
36222
|
-
|
|
36223
|
-
|
|
36224
|
-
|
|
36225
|
-
|
|
36226
|
-
|
|
36586
|
+
const now = Date.now();
|
|
36587
|
+
if (reportedAt === void 0 || now - reportedAt >= CONNECTION_WAITING_REPORT_INTERVAL_MS) {
|
|
36588
|
+
reportedAt = now;
|
|
36589
|
+
this.output.emit({
|
|
36590
|
+
type: "connection_authorization_waiting",
|
|
36591
|
+
conversationId: request2.conversationId,
|
|
36592
|
+
connectionId,
|
|
36593
|
+
connectorId: request2.connectorId,
|
|
36594
|
+
serviceName: request2.serviceName,
|
|
36595
|
+
elapsedSeconds: Math.floor(elapsedMs / 1e3),
|
|
36596
|
+
state: "waiting_for_user",
|
|
36597
|
+
terminal: false
|
|
36598
|
+
});
|
|
36599
|
+
}
|
|
36227
36600
|
await wait(Math.min(this.pollIntervalMs, this.timeoutMs - elapsedMs), void 0, {
|
|
36228
36601
|
signal
|
|
36229
36602
|
});
|
|
@@ -36238,11 +36611,48 @@ function isSafeSetupUrl(value) {
|
|
|
36238
36611
|
}
|
|
36239
36612
|
}
|
|
36240
36613
|
|
|
36614
|
+
// src/commands/chat/chat-failure.ts
|
|
36615
|
+
var SELECTION_CODES = /* @__PURE__ */ new Set([
|
|
36616
|
+
"CHAT_AWAITING_SELECTION",
|
|
36617
|
+
"CHAT_AMBIGUOUS_SELECTION",
|
|
36618
|
+
"CHAT_UNKNOWN_CONNECTION"
|
|
36619
|
+
]);
|
|
36620
|
+
function chatFailureCode(error62) {
|
|
36621
|
+
switch (classifyChatTurnFailure(error62)) {
|
|
36622
|
+
case "broken":
|
|
36623
|
+
return "CHAT_TURN_FAILED";
|
|
36624
|
+
case "refused":
|
|
36625
|
+
return "CHAT_TURN_REJECTED";
|
|
36626
|
+
default:
|
|
36627
|
+
return FALLBACK_FAILURE_CODE;
|
|
36628
|
+
}
|
|
36629
|
+
}
|
|
36630
|
+
function messageResumeCommand(conversationId) {
|
|
36631
|
+
return `tda chat continue ${conversationId} "<message>"`;
|
|
36632
|
+
}
|
|
36633
|
+
function resumeCommandFor(conversationId, code) {
|
|
36634
|
+
if (code === "CHAT_TURN_ACTIVE") return void 0;
|
|
36635
|
+
if (code === "CHAT_AWAITING_PUSH") return `tda chat continue ${conversationId}`;
|
|
36636
|
+
if (SELECTION_CODES.has(code)) {
|
|
36637
|
+
return `tda chat continue ${conversationId} --connection <connector[:type]=connection-id>`;
|
|
36638
|
+
}
|
|
36639
|
+
return messageResumeCommand(conversationId);
|
|
36640
|
+
}
|
|
36641
|
+
function formatChatFailureDetails(details) {
|
|
36642
|
+
return [
|
|
36643
|
+
`Conversation: ${details.conversationId}`,
|
|
36644
|
+
...details.requestId ? [`Request ID: ${details.requestId}`] : [],
|
|
36645
|
+
...details.draftCommand ? [`Its draft is saved: ${details.draftCommand}`] : [],
|
|
36646
|
+
...details.resumeCommand ? [`Resume with: ${details.resumeCommand}`] : []
|
|
36647
|
+
];
|
|
36648
|
+
}
|
|
36649
|
+
|
|
36241
36650
|
// src/commands/chat/conversation-turn-lock.ts
|
|
36242
36651
|
import { link, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
36243
36652
|
import { tmpdir } from "node:os";
|
|
36244
36653
|
import { join } from "node:path";
|
|
36245
36654
|
import { randomUUID } from "node:crypto";
|
|
36655
|
+
import { CliError as CliError5 } from "@doist/cli-core";
|
|
36246
36656
|
var ConversationTurnLock = class {
|
|
36247
36657
|
directory;
|
|
36248
36658
|
pid;
|
|
@@ -36279,7 +36689,8 @@ var ConversationTurnLock = class {
|
|
|
36279
36689
|
}
|
|
36280
36690
|
const current = await readLock(path);
|
|
36281
36691
|
if (current && this.isProcessRunning(current.pid)) {
|
|
36282
|
-
throw new
|
|
36692
|
+
throw new CliError5(
|
|
36693
|
+
"CHAT_TURN_ACTIVE",
|
|
36283
36694
|
`Conversation ${conversationId} already has an active turn in process ${current.pid}. Continue monitoring that process; do not invoke continue until it exits.`
|
|
36284
36695
|
);
|
|
36285
36696
|
}
|
|
@@ -36326,7 +36737,8 @@ function isNoSuchProcessError(error62) {
|
|
|
36326
36737
|
|
|
36327
36738
|
// src/commands/chat/chat-service.ts
|
|
36328
36739
|
var TURN_HEARTBEAT_INTERVAL_MS = 15e3;
|
|
36329
|
-
var
|
|
36740
|
+
var DRAFT_LOOKUP_TIMEOUT_MS = 5e3;
|
|
36741
|
+
var TURN_RUNNING_MESSAGE = "Keep monitoring this process; do not invoke continue until a record with terminal=true is emitted and the process exits.";
|
|
36330
36742
|
var ChatService = class {
|
|
36331
36743
|
api;
|
|
36332
36744
|
transport;
|
|
@@ -36336,6 +36748,8 @@ var ChatService = class {
|
|
|
36336
36748
|
turnLock;
|
|
36337
36749
|
heartbeatIntervalMs;
|
|
36338
36750
|
readline;
|
|
36751
|
+
/** The conversation a start or continue is working on, once it is known. */
|
|
36752
|
+
failureScope;
|
|
36339
36753
|
constructor({
|
|
36340
36754
|
token,
|
|
36341
36755
|
baseUrl,
|
|
@@ -36366,6 +36780,10 @@ var ChatService = class {
|
|
|
36366
36780
|
*/
|
|
36367
36781
|
async startConversation(message, signal, workspaceId = null) {
|
|
36368
36782
|
const conversation = workspaceId === null ? await this.api.chat.createConversation({}, { signal }) : await this.api.workspaceChat.createConversation(workspaceId, {}, { signal });
|
|
36783
|
+
this.failureScope = {
|
|
36784
|
+
conversationId: conversation.id,
|
|
36785
|
+
workspaceId: conversation.workspaceId
|
|
36786
|
+
};
|
|
36369
36787
|
this.output.emit({ type: "session_started", conversationId: conversation.id });
|
|
36370
36788
|
return this.withActiveTurn(
|
|
36371
36789
|
conversation.id,
|
|
@@ -36384,8 +36802,10 @@ var ChatService = class {
|
|
|
36384
36802
|
selections,
|
|
36385
36803
|
signal
|
|
36386
36804
|
}) {
|
|
36805
|
+
this.failureScope = { conversationId };
|
|
36387
36806
|
const conversation = await this.api.chat.getConversation(conversationId, { signal });
|
|
36388
36807
|
const scope = { conversationId, workspaceId: conversation.workspaceId };
|
|
36808
|
+
this.failureScope = scope;
|
|
36389
36809
|
return this.withActiveTurn(
|
|
36390
36810
|
conversationId,
|
|
36391
36811
|
async () => {
|
|
@@ -36398,7 +36818,8 @@ var ChatService = class {
|
|
|
36398
36818
|
} else {
|
|
36399
36819
|
const nextMessage = this.interactive ? await this.promptForMessage(signal) : "";
|
|
36400
36820
|
if (!nextMessage) {
|
|
36401
|
-
throw new
|
|
36821
|
+
throw new CliError6(
|
|
36822
|
+
"CHAT_INPUT_REQUIRED",
|
|
36402
36823
|
"A message or --connection selection is required to continue."
|
|
36403
36824
|
);
|
|
36404
36825
|
}
|
|
@@ -36504,6 +36925,7 @@ var ChatService = class {
|
|
|
36504
36925
|
}
|
|
36505
36926
|
}
|
|
36506
36927
|
async runTurn({ conversationId, workspaceId }, input2, signal, pendingAction, { markActivity }) {
|
|
36928
|
+
this.output.startTurn();
|
|
36507
36929
|
try {
|
|
36508
36930
|
return await runConversationTurn({
|
|
36509
36931
|
api: this.api,
|
|
@@ -36515,7 +36937,7 @@ var ChatService = class {
|
|
|
36515
36937
|
signal,
|
|
36516
36938
|
onText: (text2) => {
|
|
36517
36939
|
markActivity();
|
|
36518
|
-
this.output.
|
|
36940
|
+
this.output.appendAssistantText(conversationId, text2);
|
|
36519
36941
|
},
|
|
36520
36942
|
onToolActivity: (activity) => {
|
|
36521
36943
|
markActivity();
|
|
@@ -36537,7 +36959,7 @@ var ChatService = class {
|
|
|
36537
36959
|
});
|
|
36538
36960
|
} catch (error62) {
|
|
36539
36961
|
if (error62 instanceof ChatTurnInputError) {
|
|
36540
|
-
throw new
|
|
36962
|
+
throw new CliError6(`CHAT_${error62.code.toUpperCase()}`, describeInputError(error62));
|
|
36541
36963
|
}
|
|
36542
36964
|
throw error62;
|
|
36543
36965
|
}
|
|
@@ -36561,13 +36983,17 @@ var ChatService = class {
|
|
|
36561
36983
|
terminal: false
|
|
36562
36984
|
});
|
|
36563
36985
|
}
|
|
36564
|
-
completeTurn(
|
|
36986
|
+
completeTurn(turn) {
|
|
36987
|
+
const summary = { ...turn, assistantText: this.output.assistantText || turn.assistantText };
|
|
36565
36988
|
const resumeCommand = getResumeCommand(summary);
|
|
36566
36989
|
this.output.emit({
|
|
36567
36990
|
type: "turn_completed",
|
|
36568
36991
|
conversationId: summary.conversationId,
|
|
36569
36992
|
status: summary.status,
|
|
36570
36993
|
outcome: summary.status,
|
|
36994
|
+
assistantText: summary.assistantText,
|
|
36995
|
+
...summary.automationId ? { automationId: summary.automationId } : {},
|
|
36996
|
+
...summary.action ? { action: summary.action } : {},
|
|
36571
36997
|
state: "completed",
|
|
36572
36998
|
terminal: true,
|
|
36573
36999
|
...summary.conversationArchived !== void 0 ? { conversationArchived: summary.conversationArchived } : {},
|
|
@@ -36608,6 +37034,32 @@ var ChatService = class {
|
|
|
36608
37034
|
await release();
|
|
36609
37035
|
}
|
|
36610
37036
|
}
|
|
37037
|
+
/** Whether a start or continue has a conversation its failure can name. */
|
|
37038
|
+
get hasFailureScope() {
|
|
37039
|
+
return this.failureScope !== void 0;
|
|
37040
|
+
}
|
|
37041
|
+
/**
|
|
37042
|
+
* What a caller needs to pick up a conversation whose start or continue
|
|
37043
|
+
* failed, or undefined before one was known. The draft is looked up only for
|
|
37044
|
+
* a turn that broke, not one refused for its input, credentials or balance.
|
|
37045
|
+
*/
|
|
37046
|
+
async describeFailure(error62, code) {
|
|
37047
|
+
const scope = this.failureScope;
|
|
37048
|
+
if (!scope) return void 0;
|
|
37049
|
+
const { conversationId, workspaceId } = scope;
|
|
37050
|
+
const draftId = workspaceId !== void 0 && classifyChatTurnFailure(error62) === "broken" ? await findConversationDraftId(this.api, {
|
|
37051
|
+
conversationId,
|
|
37052
|
+
workspaceId,
|
|
37053
|
+
signal: AbortSignal.timeout(DRAFT_LOOKUP_TIMEOUT_MS)
|
|
37054
|
+
}).catch(() => null) : null;
|
|
37055
|
+
const resumeCommand = resumeCommandFor(conversationId, code);
|
|
37056
|
+
return {
|
|
37057
|
+
conversationId,
|
|
37058
|
+
...readFailedRequest(error62),
|
|
37059
|
+
...draftId && workspaceId !== void 0 ? { draftId, draftCommand: `tda draft view ${draftId} ${scopeFlag(workspaceId)}` } : {},
|
|
37060
|
+
...resumeCommand ? { resumeCommand } : {}
|
|
37061
|
+
};
|
|
37062
|
+
}
|
|
36611
37063
|
async getPersistedPendingAction(conversationId, signal) {
|
|
36612
37064
|
const messages = await this.api.chat.getMessages(conversationId, { signal });
|
|
36613
37065
|
return getPendingActionFromMessages(messages);
|
|
@@ -36694,21 +37146,25 @@ function getResumeCommand(summary) {
|
|
|
36694
37146
|
if (typeof actionResume === "string") {
|
|
36695
37147
|
return actionResume.replace("<conversation-id>", summary.conversationId);
|
|
36696
37148
|
}
|
|
36697
|
-
return
|
|
37149
|
+
return messageResumeCommand(summary.conversationId);
|
|
36698
37150
|
}
|
|
36699
37151
|
|
|
36700
37152
|
// src/commands/chat/index.ts
|
|
36701
37153
|
function registerChatCommand(program) {
|
|
36702
37154
|
const chat = program.command("chat").description("Talk to the Todoist Automations agent");
|
|
36703
37155
|
addWorkspaceScopeOptions(
|
|
36704
|
-
|
|
37156
|
+
addStreamTextOption(
|
|
37157
|
+
chat.command("start").description("Start a new conversation").argument("<message...>", "Initial message")
|
|
37158
|
+
)
|
|
36705
37159
|
).action(async (messageParts, options, command) => {
|
|
36706
37160
|
await runCommand(command, async ({ service, signal }) => {
|
|
36707
37161
|
const workspaceId = await resolveWorkspaceScope(options);
|
|
36708
37162
|
return service.startConversation(messageParts.join(" "), signal, workspaceId);
|
|
36709
37163
|
});
|
|
36710
37164
|
});
|
|
36711
|
-
|
|
37165
|
+
addStreamTextOption(
|
|
37166
|
+
chat.command("continue").description("Continue a persisted conversation").argument("<conversation-id>", "Conversation ID emitted by start").argument("[message...]", "Answer or follow-up message")
|
|
37167
|
+
).option(
|
|
36712
37168
|
"--connection <connector[:type]=connection-id>",
|
|
36713
37169
|
"Resolve a pending connection selection; repeat for multiple connectors",
|
|
36714
37170
|
collectOption,
|
|
@@ -36733,11 +37189,19 @@ function registerChatCommand(program) {
|
|
|
36733
37189
|
);
|
|
36734
37190
|
});
|
|
36735
37191
|
}
|
|
37192
|
+
function addStreamTextOption(command) {
|
|
37193
|
+
return command.option(
|
|
37194
|
+
"--stream-text",
|
|
37195
|
+
"Under --ndjson, emit the assistant text as it arrives (assistant_text) rather than one assistant_message per step"
|
|
37196
|
+
);
|
|
37197
|
+
}
|
|
36736
37198
|
async function runCommand(command, operation) {
|
|
36737
37199
|
let service;
|
|
36738
37200
|
let source;
|
|
36739
37201
|
await runStreamingCommand(command, {
|
|
36740
|
-
createOutput: (mode) => new OutputWriter(mode
|
|
37202
|
+
createOutput: (mode) => new OutputWriter(mode, process.stdout, process.stderr, {
|
|
37203
|
+
streamText: command.opts().streamText === true
|
|
37204
|
+
}),
|
|
36741
37205
|
operation: async ({ options, mode, output: output2, signal }) => {
|
|
36742
37206
|
const apiConfiguration = await withSpinner(
|
|
36743
37207
|
{ text: "Connecting..." },
|
|
@@ -36760,33 +37224,33 @@ async function runCommand(command, operation) {
|
|
|
36760
37224
|
onSuccess: (result, { output: output2 }) => output2.finish(result),
|
|
36761
37225
|
onInterrupted: ({ output: output2 }) => {
|
|
36762
37226
|
const conversationId = output2.conversationId;
|
|
36763
|
-
|
|
37227
|
+
const resume = conversationId ? { resumeCommand: messageResumeCommand(conversationId) } : {};
|
|
36764
37228
|
output2.emit({
|
|
36765
|
-
type: "
|
|
37229
|
+
type: "interrupted",
|
|
36766
37230
|
conversationId,
|
|
36767
37231
|
status: "interrupted",
|
|
36768
|
-
|
|
36769
|
-
|
|
36770
|
-
terminal: true
|
|
36771
|
-
...conversationId ? { resumeCommand: `tda chat continue ${conversationId} "<message>"` } : {}
|
|
37232
|
+
message: "Interrupted.",
|
|
37233
|
+
...resume,
|
|
37234
|
+
terminal: true
|
|
36772
37235
|
});
|
|
36773
37236
|
output2.finish({
|
|
36774
37237
|
conversationId,
|
|
36775
37238
|
status: "interrupted",
|
|
36776
|
-
assistantText:
|
|
37239
|
+
assistantText: output2.assistantText,
|
|
37240
|
+
...resume
|
|
36777
37241
|
});
|
|
36778
37242
|
},
|
|
36779
|
-
onError: (error62, { mode, output: output2 }) => {
|
|
37243
|
+
onError: async (error62, { mode, output: output2 }) => {
|
|
36780
37244
|
const failure2 = renderFailure(error62, { mode, source });
|
|
36781
|
-
const
|
|
36782
|
-
|
|
36783
|
-
|
|
36784
|
-
|
|
36785
|
-
|
|
36786
|
-
|
|
36787
|
-
|
|
36788
|
-
});
|
|
36789
|
-
output2.finish(
|
|
37245
|
+
const code = failure2.code ?? (service?.hasFailureScope ? chatFailureCode(error62) : FALLBACK_FAILURE_CODE);
|
|
37246
|
+
const details = await service?.describeFailure(error62, code);
|
|
37247
|
+
const message = mode === "human" ? [
|
|
37248
|
+
failure2.humanMessage,
|
|
37249
|
+
...details ? formatChatFailureDetails(details) : []
|
|
37250
|
+
].join("\n") : failure2.message;
|
|
37251
|
+
const record2 = toFailureRecord({ code, message }, details);
|
|
37252
|
+
output2.emit({ type: "error", ...record2, state: "failed", terminal: true });
|
|
37253
|
+
output2.finish(record2);
|
|
36790
37254
|
},
|
|
36791
37255
|
onFinally: () => {
|
|
36792
37256
|
stopEarlySpinner();
|
|
@@ -36972,7 +37436,12 @@ async function runAuthorizationCommand(command, operation) {
|
|
|
36972
37436
|
onSuccess: (result, { mode, output: output2 }) => {
|
|
36973
37437
|
if (mode === "json") return output2.finish(result);
|
|
36974
37438
|
stopEarlySpinner();
|
|
36975
|
-
writeResult(mode, result, {
|
|
37439
|
+
writeResult(mode, result, {
|
|
37440
|
+
human: formatConnectionChange,
|
|
37441
|
+
ndjson: (completed) => [
|
|
37442
|
+
{ type: "connection_completed", ...completed, terminal: true }
|
|
37443
|
+
]
|
|
37444
|
+
});
|
|
36976
37445
|
},
|
|
36977
37446
|
onInterrupted: ({ output: output2 }) => {
|
|
36978
37447
|
output2.emit({
|
|
@@ -36986,14 +37455,9 @@ async function runAuthorizationCommand(command, operation) {
|
|
|
36986
37455
|
onError: (error62, { mode, output: output2 }) => {
|
|
36987
37456
|
const failure2 = renderFailure(error62, { mode, source });
|
|
36988
37457
|
const message = mode === "human" ? failure2.humanMessage : failure2.message;
|
|
36989
|
-
|
|
36990
|
-
|
|
36991
|
-
|
|
36992
|
-
message,
|
|
36993
|
-
...failure2.code ? { code: failure2.code } : {},
|
|
36994
|
-
terminal: true
|
|
36995
|
-
});
|
|
36996
|
-
output2.finish({ status: "error", message });
|
|
37458
|
+
const record2 = toFailureRecord({ code: failure2.code, message });
|
|
37459
|
+
output2.emit({ type: "error", ...record2, terminal: true });
|
|
37460
|
+
output2.finish(record2);
|
|
36997
37461
|
},
|
|
36998
37462
|
onFinally: () => stopEarlySpinner()
|
|
36999
37463
|
});
|
|
@@ -37366,7 +37830,7 @@ function registerConnectorCommand(program) {
|
|
|
37366
37830
|
}
|
|
37367
37831
|
|
|
37368
37832
|
// src/lib/manifest-input.ts
|
|
37369
|
-
import { CliError as
|
|
37833
|
+
import { CliError as CliError7, getErrorMessage as getErrorMessage7 } from "@doist/cli-core";
|
|
37370
37834
|
import { Option as Option6 } from "commander";
|
|
37371
37835
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
37372
37836
|
import { text } from "node:stream/consumers";
|
|
@@ -37380,7 +37844,7 @@ async function readManifestInput(options, input2 = process.stdin) {
|
|
|
37380
37844
|
try {
|
|
37381
37845
|
return JSON.parse(source.content);
|
|
37382
37846
|
} catch (error62) {
|
|
37383
|
-
throw new
|
|
37847
|
+
throw new CliError7(
|
|
37384
37848
|
"MANIFEST_UNREADABLE",
|
|
37385
37849
|
`${source.label} is not valid JSON: ${getErrorMessage7(error62)}`
|
|
37386
37850
|
);
|
|
@@ -37391,7 +37855,7 @@ async function readSource(options, input2) {
|
|
|
37391
37855
|
try {
|
|
37392
37856
|
return { label: options.file, content: await readFile2(options.file, "utf8") };
|
|
37393
37857
|
} catch (error62) {
|
|
37394
|
-
throw new
|
|
37858
|
+
throw new CliError7(
|
|
37395
37859
|
"MANIFEST_UNREADABLE",
|
|
37396
37860
|
`Could not read ${options.file}: ${getErrorMessage7(error62)}`
|
|
37397
37861
|
);
|
|
@@ -37401,17 +37865,17 @@ async function readSource(options, input2) {
|
|
|
37401
37865
|
try {
|
|
37402
37866
|
return { label: "Standard input", content: await text(input2) };
|
|
37403
37867
|
} catch (error62) {
|
|
37404
|
-
throw new
|
|
37868
|
+
throw new CliError7(
|
|
37405
37869
|
"MANIFEST_UNREADABLE",
|
|
37406
37870
|
`Could not read standard input: ${getErrorMessage7(error62)}`
|
|
37407
37871
|
);
|
|
37408
37872
|
}
|
|
37409
37873
|
}
|
|
37410
|
-
throw new
|
|
37874
|
+
throw new CliError7("MANIFEST_UNREADABLE", "Provide the manifest with --file <path> or --stdin.");
|
|
37411
37875
|
}
|
|
37412
37876
|
|
|
37413
37877
|
// src/commands/draft/draft-service.ts
|
|
37414
|
-
import { CliError as
|
|
37878
|
+
import { CliError as CliError8, getErrorMessage as getErrorMessage8 } from "@doist/cli-core";
|
|
37415
37879
|
import { readFile as readFile3 } from "node:fs/promises";
|
|
37416
37880
|
function createDraft2(draftApi, input2) {
|
|
37417
37881
|
return withRecoveryHints(draftApi, createDraft(draftApi, input2));
|
|
@@ -37444,13 +37908,13 @@ async function withRecoveryHints(draftApi, operation) {
|
|
|
37444
37908
|
const draftId = error62.draftId ?? "<draft-id>";
|
|
37445
37909
|
switch (error62.code) {
|
|
37446
37910
|
case "manifest_invalid":
|
|
37447
|
-
throw new
|
|
37911
|
+
throw new CliError8("MANIFEST_INVALID", error62.message);
|
|
37448
37912
|
case "draft_conflict":
|
|
37449
|
-
throw new
|
|
37913
|
+
throw new CliError8("DRAFT_CONFLICT", error62.message, {
|
|
37450
37914
|
hints: draftHints(scope, draftId)
|
|
37451
37915
|
});
|
|
37452
37916
|
case "draft_manifest_rejected":
|
|
37453
|
-
throw new
|
|
37917
|
+
throw new CliError8("DRAFT_MANIFEST_REJECTED", error62.message, {
|
|
37454
37918
|
hints: draftHints(scope, draftId)
|
|
37455
37919
|
});
|
|
37456
37920
|
case "draft_not_ready":
|
|
@@ -37468,14 +37932,12 @@ function draftHints(scope, draftId) {
|
|
|
37468
37932
|
`Or discard it first: tda draft delete ${draftId} --yes ${scopeFlag(scope)}`
|
|
37469
37933
|
];
|
|
37470
37934
|
}
|
|
37471
|
-
function scopeFlag(scope) {
|
|
37472
|
-
return scope === null ? "--personal" : `--workspace ${scope}`;
|
|
37473
|
-
}
|
|
37474
37935
|
async function startDraftTest2(draftApi, {
|
|
37475
37936
|
draftId,
|
|
37476
37937
|
request: request2,
|
|
37477
37938
|
wait: waitFor,
|
|
37478
|
-
signal
|
|
37939
|
+
signal,
|
|
37940
|
+
onProgress
|
|
37479
37941
|
}) {
|
|
37480
37942
|
let started;
|
|
37481
37943
|
try {
|
|
@@ -37497,7 +37959,11 @@ async function startDraftTest2(draftApi, {
|
|
|
37497
37959
|
}
|
|
37498
37960
|
let finished;
|
|
37499
37961
|
try {
|
|
37500
|
-
finished = await waitForDraftTest(draftApi, started, {
|
|
37962
|
+
finished = await waitForDraftTest(draftApi, started, {
|
|
37963
|
+
...waitFor,
|
|
37964
|
+
signal,
|
|
37965
|
+
...onProgress ? { onPoll: reportRunProgress(onProgress) } : {}
|
|
37966
|
+
});
|
|
37501
37967
|
} catch (error62) {
|
|
37502
37968
|
throw toDraftTestWaitError(error62, identity, signal);
|
|
37503
37969
|
}
|
|
@@ -37515,13 +37981,13 @@ function toDraftTestWaitError(error62, identity, signal) {
|
|
|
37515
37981
|
const run = `test run ${identity.testRunId} of draft ${identity.draftId}`;
|
|
37516
37982
|
const hints = [`See how it ends: ${identity.command}`];
|
|
37517
37983
|
if (signal.aborted) {
|
|
37518
|
-
return new
|
|
37984
|
+
return new CliError8(
|
|
37519
37985
|
"DRAFT_TEST_WAIT_INTERRUPTED",
|
|
37520
37986
|
`Stopped waiting for ${run}. It keeps running.`,
|
|
37521
37987
|
{ hints }
|
|
37522
37988
|
);
|
|
37523
37989
|
}
|
|
37524
|
-
return new
|
|
37990
|
+
return new CliError8(
|
|
37525
37991
|
"DRAFT_TEST_WAIT_FAILED",
|
|
37526
37992
|
`Checking on ${run} failed: ${getErrorMessage8(error62).replace(/\.?$/, ".")} It keeps running.`,
|
|
37527
37993
|
{ hints }
|
|
@@ -37529,7 +37995,7 @@ function toDraftTestWaitError(error62, identity, signal) {
|
|
|
37529
37995
|
}
|
|
37530
37996
|
function draftNotReadyError(message, draftId, scope) {
|
|
37531
37997
|
const flag = scopeFlag(scope);
|
|
37532
|
-
return new
|
|
37998
|
+
return new CliError8("DRAFT_NOT_READY", message, {
|
|
37533
37999
|
hints: [
|
|
37534
38000
|
`See the issues: tda draft view ${draftId} ${flag}`,
|
|
37535
38001
|
`Bind connections: tda draft connect ${draftId} --auto ${flag}`
|
|
@@ -37543,13 +38009,13 @@ function toDraftTestCliError(refusal, draftId, scope) {
|
|
|
37543
38009
|
return draftNotReadyError(refusal.message, draftId, scope);
|
|
37544
38010
|
case "draft_test_payload_required":
|
|
37545
38011
|
case "draft_test_payload_filtered":
|
|
37546
|
-
return new
|
|
38012
|
+
return new CliError8(refusal.code.toUpperCase(), refusal.message, {
|
|
37547
38013
|
hints: [
|
|
37548
38014
|
`Test with a real event: tda draft test ${draftId} --trigger <event> --payload-file <path> ${flag}`
|
|
37549
38015
|
]
|
|
37550
38016
|
});
|
|
37551
38017
|
default:
|
|
37552
|
-
return new
|
|
38018
|
+
return new CliError8(refusal.code.toUpperCase(), refusal.message);
|
|
37553
38019
|
}
|
|
37554
38020
|
}
|
|
37555
38021
|
async function readTestPayloadFile(path) {
|
|
@@ -37557,7 +38023,7 @@ async function readTestPayloadFile(path) {
|
|
|
37557
38023
|
try {
|
|
37558
38024
|
content = await readFile3(path, "utf8");
|
|
37559
38025
|
} catch (error62) {
|
|
37560
|
-
throw new
|
|
38026
|
+
throw new CliError8(
|
|
37561
38027
|
"PAYLOAD_UNREADABLE",
|
|
37562
38028
|
`Could not read ${path}: ${getErrorMessage8(error62)}`
|
|
37563
38029
|
);
|
|
@@ -37565,7 +38031,7 @@ async function readTestPayloadFile(path) {
|
|
|
37565
38031
|
try {
|
|
37566
38032
|
return JSON.parse(content);
|
|
37567
38033
|
} catch (error62) {
|
|
37568
|
-
throw new
|
|
38034
|
+
throw new CliError8(
|
|
37569
38035
|
"PAYLOAD_UNREADABLE",
|
|
37570
38036
|
`${path} is not valid JSON: ${getErrorMessage8(error62)}`
|
|
37571
38037
|
);
|
|
@@ -37754,7 +38220,7 @@ function formatDraftTest(result) {
|
|
|
37754
38220
|
}
|
|
37755
38221
|
|
|
37756
38222
|
// src/commands/draft/validate-command.ts
|
|
37757
|
-
import { CliError as
|
|
38223
|
+
import { CliError as CliError9 } from "@doist/cli-core";
|
|
37758
38224
|
|
|
37759
38225
|
// src/lib/local-command.ts
|
|
37760
38226
|
async function runLocalCommand(command, operation, presentation, execution = {}) {
|
|
@@ -37786,7 +38252,7 @@ function registerDraftValidateCommand(draft) {
|
|
|
37786
38252
|
function validateManifestInput(manifest) {
|
|
37787
38253
|
const validation = validateManifest(manifest);
|
|
37788
38254
|
if (!validation.isValid) {
|
|
37789
|
-
throw new
|
|
38255
|
+
throw new CliError9(
|
|
37790
38256
|
"MANIFEST_INVALID",
|
|
37791
38257
|
`Manifest is invalid: ${validation.errors.join("; ")}`
|
|
37792
38258
|
);
|
|
@@ -37806,8 +38272,6 @@ function formatManifestValidation(result) {
|
|
|
37806
38272
|
}
|
|
37807
38273
|
|
|
37808
38274
|
// src/commands/draft/index.ts
|
|
37809
|
-
var DEFAULT_TEST_WAIT = "10m";
|
|
37810
|
-
var TOKEN_LIFETIME_MARGIN_MS = 2 * 60 * 1e3;
|
|
37811
38275
|
function registerDraftCommand(program) {
|
|
37812
38276
|
const draft = program.command("draft").description("Author, test and inspect automation drafts");
|
|
37813
38277
|
registerDraftValidateCommand(draft);
|
|
@@ -37934,9 +38398,9 @@ function registerDraftCommand(program) {
|
|
|
37934
38398
|
);
|
|
37935
38399
|
});
|
|
37936
38400
|
addWorkspaceScopeOptions(
|
|
37937
|
-
|
|
37938
|
-
"
|
|
37939
|
-
|
|
38401
|
+
addWaitOptions(
|
|
38402
|
+
draft.command("test").description("Start a test run of a draft, with mocked connector calls").argument("<draft-id>", "Draft ID"),
|
|
38403
|
+
"Wait for the run to end and exit 1 unless it passed"
|
|
37940
38404
|
).option(
|
|
37941
38405
|
"--trigger <index|event>",
|
|
37942
38406
|
"The trigger to test: its position in the manifest's triggers, or a webhook event"
|
|
@@ -37947,11 +38411,8 @@ function registerDraftCommand(program) {
|
|
|
37947
38411
|
).action(async (draftId, options, command) => {
|
|
37948
38412
|
await runApiCommand(
|
|
37949
38413
|
command,
|
|
37950
|
-
async ({ api, signal }) => {
|
|
37951
|
-
|
|
37952
|
-
throw new Error("--timeout only applies with --wait.");
|
|
37953
|
-
}
|
|
37954
|
-
const timeoutMs = parseTestTimeout(options.timeout ?? DEFAULT_TEST_WAIT);
|
|
38414
|
+
async ({ api, signal, progress }) => {
|
|
38415
|
+
const wait2 = resolveWait(options);
|
|
37955
38416
|
const draftApi = bindDraftApi(api, await resolveWorkspaceScope(options), { signal });
|
|
37956
38417
|
return startDraftTest2(draftApi, {
|
|
37957
38418
|
draftId,
|
|
@@ -37959,14 +38420,15 @@ function registerDraftCommand(program) {
|
|
|
37959
38420
|
...options.trigger !== void 0 ? { trigger: toDraftTestTriggerSelector(options.trigger) } : {},
|
|
37960
38421
|
...options.payloadFile !== void 0 ? { payload: await readTestPayloadFile(options.payloadFile) } : {}
|
|
37961
38422
|
},
|
|
37962
|
-
wait:
|
|
37963
|
-
signal
|
|
38423
|
+
wait: wait2,
|
|
38424
|
+
signal,
|
|
38425
|
+
onProgress: (state) => progress({ type: "test_progress", draftId, ...state })
|
|
37964
38426
|
});
|
|
37965
38427
|
},
|
|
37966
|
-
{ human: formatDraftTest },
|
|
38428
|
+
{ human: formatDraftTest, ndjson: toTerminalRecords },
|
|
37967
38429
|
{
|
|
37968
38430
|
spinner: options.wait === true ? "Testing draft..." : "Starting test run...",
|
|
37969
|
-
minimumTokenLifetimeMs:
|
|
38431
|
+
minimumTokenLifetimeMs: waitTokenLifetimeMs(options),
|
|
37970
38432
|
exitCode: (result) => result.action === "test_finished" && result.outcome !== "passed" ? 1 : void 0
|
|
37971
38433
|
}
|
|
37972
38434
|
);
|
|
@@ -38026,20 +38488,6 @@ function streamDraftList(options, fetchPage) {
|
|
|
38026
38488
|
async (args) => summarizeDraftListPage(await fetchPage(args))
|
|
38027
38489
|
);
|
|
38028
38490
|
}
|
|
38029
|
-
function tryParseTestTimeout(value) {
|
|
38030
|
-
try {
|
|
38031
|
-
return durationStringToMilliseconds(value);
|
|
38032
|
-
} catch {
|
|
38033
|
-
return 0;
|
|
38034
|
-
}
|
|
38035
|
-
}
|
|
38036
|
-
function parseTestTimeout(value) {
|
|
38037
|
-
try {
|
|
38038
|
-
return durationStringToMilliseconds(value);
|
|
38039
|
-
} catch {
|
|
38040
|
-
throw new Error(`Invalid --timeout ${value}; use a duration such as 90s, 15m or 1h.`);
|
|
38041
|
-
}
|
|
38042
|
-
}
|
|
38043
38491
|
|
|
38044
38492
|
// src/commands/run/index.ts
|
|
38045
38493
|
import { Option as Option7 } from "commander";
|
|
@@ -38135,11 +38583,11 @@ function registerRunCommand(program) {
|
|
|
38135
38583
|
}
|
|
38136
38584
|
|
|
38137
38585
|
// src/commands/skill/shared.ts
|
|
38138
|
-
import { CliError as
|
|
38586
|
+
import { CliError as CliError10 } from "@doist/cli-core";
|
|
38139
38587
|
function requireInstaller(agent) {
|
|
38140
38588
|
const installer = getInstaller(agent);
|
|
38141
38589
|
if (!installer) {
|
|
38142
|
-
throw new
|
|
38590
|
+
throw new CliError10("UNKNOWN_AGENT", `Unknown agent: ${agent}.`, {
|
|
38143
38591
|
hints: [`Available agents: ${AGENT_NAMES.join(", ")}.`]
|
|
38144
38592
|
});
|
|
38145
38593
|
}
|
|
@@ -38217,7 +38665,7 @@ async function uninstallSkill(agent, options, command) {
|
|
|
38217
38665
|
}
|
|
38218
38666
|
|
|
38219
38667
|
// src/commands/skill/update.ts
|
|
38220
|
-
import { CliError as
|
|
38668
|
+
import { CliError as CliError11 } from "@doist/cli-core";
|
|
38221
38669
|
async function updateSkill(agent, options, command) {
|
|
38222
38670
|
await runLocalCommand(
|
|
38223
38671
|
command,
|
|
@@ -38243,7 +38691,7 @@ async function updateAllSkills(options, command) {
|
|
|
38243
38691
|
async () => {
|
|
38244
38692
|
const result = await updateAllInstalledSkills(options.local === true);
|
|
38245
38693
|
if (result.errors.length > 0) {
|
|
38246
|
-
throw new
|
|
38694
|
+
throw new CliError11(
|
|
38247
38695
|
"UPDATE_FAILED",
|
|
38248
38696
|
`Could not update ${result.errors.length} skill file(s).`,
|
|
38249
38697
|
{ hints: result.errors.map((error62) => `${error62.agent}: ${error62.message}`) }
|
|
@@ -38303,7 +38751,7 @@ function registerUpdateCommand(program) {
|
|
|
38303
38751
|
}
|
|
38304
38752
|
|
|
38305
38753
|
// src/commands/workspace/index.ts
|
|
38306
|
-
import { CliError as
|
|
38754
|
+
import { CliError as CliError12 } from "@doist/cli-core";
|
|
38307
38755
|
|
|
38308
38756
|
// src/commands/workspace/format.ts
|
|
38309
38757
|
function formatWorkspaceList(workspaces) {
|
|
@@ -38346,7 +38794,7 @@ function registerWorkspaceCommand(program) {
|
|
|
38346
38794
|
if (!(error62 instanceof AutomationsRequestError) || error62.httpStatusCode !== 404) {
|
|
38347
38795
|
throw error62;
|
|
38348
38796
|
}
|
|
38349
|
-
throw new
|
|
38797
|
+
throw new CliError12(
|
|
38350
38798
|
"WORKSPACE_ACCESS_UNAVAILABLE",
|
|
38351
38799
|
"Workspace access is not available for this account or environment.",
|
|
38352
38800
|
{
|