@doist/automations-cli 1.27.0 → 1.28.1
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 +24 -3
- package/dist/{chunk-ZTBPLXQS.js → chunk-3JSM3SAL.js} +62 -16
- package/dist/chunk-3JSM3SAL.js.map +7 -0
- package/dist/index.js +852 -383
- 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-3JSM3SAL.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.1",
|
|
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
|
-
const delta = visibleText.slice(emittedText.length);
|
|
35217
|
-
if (delta)
|
|
35218
|
-
onText?.(delta);
|
|
35219
|
-
emittedText = visibleText;
|
|
35220
|
-
}
|
|
35221
|
-
for (const part of currentMessage.parts) {
|
|
35222
|
-
const activity = toToolActivity(part);
|
|
35223
|
-
if (!activity)
|
|
35224
|
-
continue;
|
|
35225
|
-
const key = `${activity.toolCallId}:${activity.state}`;
|
|
35226
|
-
if (emittedToolStates.has(key))
|
|
35227
|
-
continue;
|
|
35228
|
-
emittedToolStates.add(key);
|
|
35229
|
-
onToolActivity?.(activity);
|
|
35194
|
+
let callbackError;
|
|
35195
|
+
const report = (callback) => {
|
|
35196
|
+
try {
|
|
35197
|
+
callback();
|
|
35198
|
+
} catch (error62) {
|
|
35199
|
+
callbackError = { error: error62 };
|
|
35200
|
+
throw error62;
|
|
35230
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
|
+
}
|
|
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,255 @@ 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, ...failure2.code ? { code: failure2.code } : {}, 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
|
+
import { CliError as CliError4, getErrorMessage as getErrorMessage7 } from "@doist/cli-core";
|
|
35409
|
+
var DEFAULT_WAIT = "10m";
|
|
35410
|
+
var TOKEN_LIFETIME_MARGIN_MS = 2 * 60 * 1e3;
|
|
35411
|
+
function addWaitOptions(command, waitDescription) {
|
|
35412
|
+
return command.option("--wait", waitDescription).option(
|
|
35413
|
+
"--timeout <duration>",
|
|
35414
|
+
`How long --wait waits, such as 90s or 15m (default ${DEFAULT_WAIT})`
|
|
35415
|
+
);
|
|
35416
|
+
}
|
|
35417
|
+
function resolveWait(options) {
|
|
35418
|
+
if (options.timeout !== void 0 && options.wait !== true) {
|
|
35419
|
+
throw new Error("--timeout only applies with --wait.");
|
|
35420
|
+
}
|
|
35421
|
+
if (options.wait !== true) {
|
|
35422
|
+
return null;
|
|
35423
|
+
}
|
|
35424
|
+
return { timeoutMs: parseTimeout(options.timeout ?? DEFAULT_WAIT) };
|
|
35425
|
+
}
|
|
35426
|
+
function waitTokenLifetimeMs(options) {
|
|
35427
|
+
if (options.wait !== true) {
|
|
35428
|
+
return void 0;
|
|
35429
|
+
}
|
|
35430
|
+
try {
|
|
35431
|
+
return parseTimeout(options.timeout ?? DEFAULT_WAIT) + TOKEN_LIFETIME_MARGIN_MS;
|
|
35432
|
+
} catch {
|
|
35433
|
+
return TOKEN_LIFETIME_MARGIN_MS;
|
|
35434
|
+
}
|
|
35435
|
+
}
|
|
35436
|
+
function parseTimeout(value) {
|
|
35437
|
+
try {
|
|
35438
|
+
return durationStringToMilliseconds(value);
|
|
35439
|
+
} catch {
|
|
35440
|
+
throw new Error(`Invalid --timeout ${value}; use a duration such as 90s, 15m or 1h.`);
|
|
35441
|
+
}
|
|
35442
|
+
}
|
|
35443
|
+
function reportRunProgress(report) {
|
|
35444
|
+
let last;
|
|
35445
|
+
return (run) => {
|
|
35446
|
+
const step = run.steps.find((candidate) => candidate.status === "running")?.stepId;
|
|
35447
|
+
const key = `${run.status}:${step ?? ""}`;
|
|
35448
|
+
if (key === last) return;
|
|
35449
|
+
last = key;
|
|
35450
|
+
report({ status: run.status, ...step ? { step } : {} });
|
|
35451
|
+
};
|
|
35452
|
+
}
|
|
35453
|
+
function toRunWaitError(error62, {
|
|
35454
|
+
run,
|
|
35455
|
+
command,
|
|
35456
|
+
codes,
|
|
35457
|
+
signal
|
|
35458
|
+
}) {
|
|
35459
|
+
const hints = [`See how it ends: ${command}`];
|
|
35460
|
+
if (signal.aborted) {
|
|
35461
|
+
return new CliError4(codes.interrupted, `Stopped waiting for ${run}. It keeps running.`, {
|
|
35462
|
+
hints
|
|
35463
|
+
});
|
|
35464
|
+
}
|
|
35465
|
+
return new CliError4(
|
|
35466
|
+
codes.failed,
|
|
35467
|
+
`Checking on ${run} failed: ${getErrorMessage7(error62).replace(/\.?$/, ".")} It keeps running.`,
|
|
35468
|
+
{ hints }
|
|
35469
|
+
);
|
|
35470
|
+
}
|
|
35471
|
+
function toTerminalRecords(result) {
|
|
35472
|
+
return [{ type: result.action, ...result, terminal: true }];
|
|
35473
|
+
}
|
|
35474
|
+
|
|
35475
|
+
// src/lib/workspace-scope.ts
|
|
35476
|
+
import { Option } from "commander";
|
|
35477
|
+
async function resolveWorkspaceScope(options) {
|
|
35478
|
+
if (options.workspace !== void 0) {
|
|
35479
|
+
return options.workspace;
|
|
35480
|
+
}
|
|
35481
|
+
if (options.personal) {
|
|
35482
|
+
return null;
|
|
35483
|
+
}
|
|
35484
|
+
return getDefaultWorkspaceId();
|
|
35485
|
+
}
|
|
35486
|
+
function scopeFlag(scope) {
|
|
35487
|
+
return scope === null ? "--personal" : `--workspace ${scope}`;
|
|
35488
|
+
}
|
|
35489
|
+
function addWorkspaceScopeOptions(command) {
|
|
35490
|
+
return command.addOption(
|
|
35491
|
+
new Option(
|
|
35492
|
+
"--workspace <workspace-id>",
|
|
35493
|
+
"Act on this workspace instead of your personal space"
|
|
35494
|
+
).conflicts("personal")
|
|
35495
|
+
).option("--personal", "Act on your personal space, ignoring any pinned workspace");
|
|
35496
|
+
}
|
|
35497
|
+
|
|
35249
35498
|
// src/commands/read/format.ts
|
|
35250
35499
|
function formatAutomationList(page) {
|
|
35251
35500
|
if (page.data.length === 0) return "No automations found.";
|
|
@@ -35457,8 +35706,35 @@ function describeVisibility(automation) {
|
|
|
35457
35706
|
function summarizeAutomationChange(result) {
|
|
35458
35707
|
return { ...result, automation: summarizeAutomation(result.automation) };
|
|
35459
35708
|
}
|
|
35709
|
+
var RUN_OUTCOME_LABELS = {
|
|
35710
|
+
completed: "completed",
|
|
35711
|
+
failed: "failed",
|
|
35712
|
+
skipped: "was skipped",
|
|
35713
|
+
cancelled: "was cancelled",
|
|
35714
|
+
timed_out: "did not finish before the timeout"
|
|
35715
|
+
};
|
|
35460
35716
|
function formatAutomationTrigger(result) {
|
|
35461
|
-
|
|
35717
|
+
if (result.action === "triggered") {
|
|
35718
|
+
if (result.runId === null) {
|
|
35719
|
+
return `Triggered automation ${result.automationId}.`;
|
|
35720
|
+
}
|
|
35721
|
+
return [
|
|
35722
|
+
`Started run ${result.runId} of automation ${result.automationId}.`,
|
|
35723
|
+
`Details: ${result.command}`
|
|
35724
|
+
].join("\n");
|
|
35725
|
+
}
|
|
35726
|
+
const lines = [
|
|
35727
|
+
`Run ${result.runId} of automation ${result.automationId} ${RUN_OUTCOME_LABELS[result.outcome]}.`
|
|
35728
|
+
];
|
|
35729
|
+
if (result.outcome === "timed_out") lines.push(`Status: ${result.status}`);
|
|
35730
|
+
if (result.failedStep) {
|
|
35731
|
+
lines.push(`Failed step: ${result.failedStep.stepId} (${result.failedStep.description})`);
|
|
35732
|
+
}
|
|
35733
|
+
if (result.error)
|
|
35734
|
+
lines.push(`${result.outcome === "skipped" ? "Reason" : "Error"}: ${result.error}`);
|
|
35735
|
+
if (result.result) lines.push(`Result: ${result.result.text}`);
|
|
35736
|
+
lines.push(`Details: ${result.command}`);
|
|
35737
|
+
return lines.join("\n");
|
|
35462
35738
|
}
|
|
35463
35739
|
function formatAutomationDelete(result) {
|
|
35464
35740
|
if (result.action === "delete_preview") {
|
|
@@ -35473,6 +35749,66 @@ function capitalize(value) {
|
|
|
35473
35749
|
return `${value.charAt(0).toUpperCase()}${value.slice(1)}`;
|
|
35474
35750
|
}
|
|
35475
35751
|
|
|
35752
|
+
// src/commands/automation/trigger-service.ts
|
|
35753
|
+
import { CliError as CliError5 } from "@doist/cli-core";
|
|
35754
|
+
async function triggerAutomation(runApi, {
|
|
35755
|
+
automationId,
|
|
35756
|
+
wait: wait2,
|
|
35757
|
+
signal,
|
|
35758
|
+
onProgress
|
|
35759
|
+
}) {
|
|
35760
|
+
const triggerType = readPrimaryTriggerType(await runApi.getAutomation(automationId));
|
|
35761
|
+
if (triggerType !== "schedule") {
|
|
35762
|
+
throw new Error(
|
|
35763
|
+
"This automation can only be triggered via its configured mechanism, not manually."
|
|
35764
|
+
);
|
|
35765
|
+
}
|
|
35766
|
+
const started = await runApi.trigger(automationId);
|
|
35767
|
+
const flag = scopeFlag(runApi.workspaceId);
|
|
35768
|
+
if (!started) {
|
|
35769
|
+
if (wait2) {
|
|
35770
|
+
throw new CliError5(
|
|
35771
|
+
"TRIGGER_RUN_UNREPORTED",
|
|
35772
|
+
"The run started, but the API did not say which one it was, so it cannot be waited for.",
|
|
35773
|
+
{ hints: [`See the latest run: tda run list ${automationId} --limit 1 ${flag}`] }
|
|
35774
|
+
);
|
|
35775
|
+
}
|
|
35776
|
+
return { action: "triggered", automationId, runId: null, status: null, command: null };
|
|
35777
|
+
}
|
|
35778
|
+
const identity = {
|
|
35779
|
+
automationId,
|
|
35780
|
+
runId: started.runId,
|
|
35781
|
+
command: `tda run view ${automationId} ${started.runId} ${flag}`
|
|
35782
|
+
};
|
|
35783
|
+
if (!wait2) {
|
|
35784
|
+
return { action: "triggered", ...identity, status: started.status };
|
|
35785
|
+
}
|
|
35786
|
+
let finished;
|
|
35787
|
+
try {
|
|
35788
|
+
finished = await waitForRun(runApi, identity, {
|
|
35789
|
+
...wait2,
|
|
35790
|
+
signal,
|
|
35791
|
+
...onProgress ? { onPoll: reportRunProgress(onProgress) } : {}
|
|
35792
|
+
});
|
|
35793
|
+
} catch (error62) {
|
|
35794
|
+
throw toRunWaitError(error62, {
|
|
35795
|
+
run: `run ${identity.runId} of automation ${automationId}`,
|
|
35796
|
+
command: identity.command,
|
|
35797
|
+
codes: { interrupted: "TRIGGER_WAIT_INTERRUPTED", failed: "TRIGGER_WAIT_FAILED" },
|
|
35798
|
+
signal
|
|
35799
|
+
});
|
|
35800
|
+
}
|
|
35801
|
+
return {
|
|
35802
|
+
action: "run_finished",
|
|
35803
|
+
...identity,
|
|
35804
|
+
outcome: finished.outcome,
|
|
35805
|
+
status: finished.status,
|
|
35806
|
+
failedStep: finished.failedStep,
|
|
35807
|
+
error: finished.error,
|
|
35808
|
+
result: finished.result
|
|
35809
|
+
};
|
|
35810
|
+
}
|
|
35811
|
+
|
|
35476
35812
|
// src/commands/automation/index.ts
|
|
35477
35813
|
function registerAutomationCommand(program) {
|
|
35478
35814
|
const automation = program.command("automation").description("Inspect and manage published automations");
|
|
@@ -35522,30 +35858,29 @@ function registerAutomationCommand(program) {
|
|
|
35522
35858
|
registerEnabledStateCommand(automation, "enable", true);
|
|
35523
35859
|
registerEnabledStateCommand(automation, "disable", false);
|
|
35524
35860
|
addWorkspaceScopeOptions(
|
|
35525
|
-
|
|
35861
|
+
addWaitOptions(
|
|
35862
|
+
automation.command("trigger").description("Start a live automation run and print its run ID").argument("<automation-id>", "Automation ID"),
|
|
35863
|
+
"Wait for the run to end and exit 1 unless it completed"
|
|
35864
|
+
)
|
|
35526
35865
|
).action(async (automationId, options, command) => {
|
|
35527
35866
|
await runApiCommand(
|
|
35528
35867
|
command,
|
|
35529
|
-
async ({ api, signal }) => {
|
|
35530
|
-
const
|
|
35531
|
-
const
|
|
35532
|
-
|
|
35868
|
+
async ({ api, signal, progress }) => {
|
|
35869
|
+
const wait2 = resolveWait(options);
|
|
35870
|
+
const runApi = bindRunApi(api, await resolveWorkspaceScope(options), { signal });
|
|
35871
|
+
return triggerAutomation(runApi, {
|
|
35872
|
+
automationId,
|
|
35873
|
+
wait: wait2,
|
|
35874
|
+
signal,
|
|
35875
|
+
onProgress: (state) => progress({ type: "run_progress", automationId, ...state })
|
|
35533
35876
|
});
|
|
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
35877
|
},
|
|
35547
|
-
{ human: formatAutomationTrigger },
|
|
35548
|
-
{
|
|
35878
|
+
{ human: formatAutomationTrigger, ndjson: toTerminalRecords },
|
|
35879
|
+
{
|
|
35880
|
+
spinner: options.wait === true ? "Running automation..." : "Triggering automation...",
|
|
35881
|
+
minimumTokenLifetimeMs: waitTokenLifetimeMs(options),
|
|
35882
|
+
exitCode: (result) => result.action === "run_finished" && result.outcome !== "completed" ? 1 : void 0
|
|
35883
|
+
}
|
|
35549
35884
|
);
|
|
35550
35885
|
});
|
|
35551
35886
|
addWorkspaceScopeOptions(
|
|
@@ -35802,6 +36137,7 @@ function collectOption(value, previous) {
|
|
|
35802
36137
|
}
|
|
35803
36138
|
|
|
35804
36139
|
// src/commands/chat/chat-service.ts
|
|
36140
|
+
import { CliError as CliError7 } from "@doist/cli-core";
|
|
35805
36141
|
import { createInterface } from "node:readline/promises";
|
|
35806
36142
|
|
|
35807
36143
|
// src/commands/chat/connection-authorizer.ts
|
|
@@ -35816,6 +36152,13 @@ var FINAL_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
|
35816
36152
|
"session_status",
|
|
35817
36153
|
"turn_completed"
|
|
35818
36154
|
]);
|
|
36155
|
+
var UNRECORDED_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
36156
|
+
"assistant_text",
|
|
36157
|
+
"connection_authorization_waiting",
|
|
36158
|
+
"turn_progress"
|
|
36159
|
+
]);
|
|
36160
|
+
var INTERRUPTING_EVENT_TYPES = /* @__PURE__ */ new Set(["error", "interrupted"]);
|
|
36161
|
+
var HEARTBEAT_EVENT_TYPES = /* @__PURE__ */ new Set(["connection_authorization_waiting", "turn_progress"]);
|
|
35819
36162
|
var SPINNER_TEXT = {
|
|
35820
36163
|
connection_authorization_opened: "Waiting for authorization...",
|
|
35821
36164
|
connection_authorization_started: "Waiting for authorization...",
|
|
@@ -35826,16 +36169,47 @@ var OutputWriter = class {
|
|
|
35826
36169
|
stream;
|
|
35827
36170
|
diagnosticStream;
|
|
35828
36171
|
events = [];
|
|
36172
|
+
streamText;
|
|
35829
36173
|
assistantLineOpen = false;
|
|
35830
36174
|
currentConversationId;
|
|
35831
|
-
|
|
36175
|
+
/** The current turn's text, one entry per step. */
|
|
36176
|
+
turnSteps = [];
|
|
36177
|
+
stepOpen = false;
|
|
36178
|
+
bufferedText = "";
|
|
36179
|
+
bufferedTextConversationId;
|
|
36180
|
+
constructor(mode, stream = process.stdout, diagnosticStream = process.stderr, { streamText = false } = {}) {
|
|
35832
36181
|
this.mode = mode;
|
|
35833
36182
|
this.stream = stream;
|
|
35834
36183
|
this.diagnosticStream = diagnosticStream;
|
|
36184
|
+
this.streamText = streamText;
|
|
36185
|
+
}
|
|
36186
|
+
/**
|
|
36187
|
+
* Assistant text as it arrives. A human reader, or an NDJSON caller that
|
|
36188
|
+
* asked with `--stream-text`, gets each fragment as `assistant_text`;
|
|
36189
|
+
* otherwise the text of a step comes out whole as one `assistant_message`
|
|
36190
|
+
* before whatever follows it.
|
|
36191
|
+
*/
|
|
36192
|
+
appendAssistantText(conversationId, text2) {
|
|
36193
|
+
if (!this.stepOpen) this.turnSteps.push("");
|
|
36194
|
+
this.stepOpen = true;
|
|
36195
|
+
this.turnSteps[this.turnSteps.length - 1] += text2;
|
|
36196
|
+
if (this.mode === "human" || this.mode === "ndjson" && this.streamText) {
|
|
36197
|
+
this.emit({ type: "assistant_text", conversationId, text: text2 });
|
|
36198
|
+
return;
|
|
36199
|
+
}
|
|
36200
|
+
this.bufferedText += text2;
|
|
36201
|
+
this.bufferedTextConversationId = conversationId;
|
|
35835
36202
|
}
|
|
35836
36203
|
/** Records and renders an event; JSON mode retains structured output until `finish`. */
|
|
35837
36204
|
emit(event) {
|
|
35838
|
-
|
|
36205
|
+
const endsStep = event.type !== "assistant_message" && event.type !== "assistant_text" && !HEARTBEAT_EVENT_TYPES.has(event.type);
|
|
36206
|
+
if (endsStep) {
|
|
36207
|
+
this.flushAssistantMessage(INTERRUPTING_EVENT_TYPES.has(event.type));
|
|
36208
|
+
this.stepOpen = false;
|
|
36209
|
+
}
|
|
36210
|
+
if (this.mode === "json" && !UNRECORDED_EVENT_TYPES.has(event.type)) {
|
|
36211
|
+
this.events.push(event);
|
|
36212
|
+
}
|
|
35839
36213
|
if (event.conversationId) this.currentConversationId = event.conversationId;
|
|
35840
36214
|
if (this.mode === "ndjson") {
|
|
35841
36215
|
this.stream.write(`${formatNdjson5([event])}
|
|
@@ -35851,17 +36225,28 @@ var OutputWriter = class {
|
|
|
35851
36225
|
get conversationId() {
|
|
35852
36226
|
return this.currentConversationId;
|
|
35853
36227
|
}
|
|
36228
|
+
/** The current turn's text so far, its steps separated by blank lines. */
|
|
36229
|
+
get assistantText() {
|
|
36230
|
+
return this.turnSteps.join("\n\n");
|
|
36231
|
+
}
|
|
36232
|
+
/** Starts collecting the text of a new turn. */
|
|
36233
|
+
startTurn() {
|
|
36234
|
+
this.turnSteps = [];
|
|
36235
|
+
this.stepOpen = false;
|
|
36236
|
+
}
|
|
35854
36237
|
/** Emits structured conversation status with human formatting owned by this output layer. */
|
|
35855
36238
|
emitConversationStatus(snapshot, pendingActionKind) {
|
|
35856
36239
|
this.emit({
|
|
35857
36240
|
type: "session_status",
|
|
35858
36241
|
conversationId: snapshot.conversation.id,
|
|
35859
36242
|
...snapshot,
|
|
35860
|
-
humanText: formatConversationStatus(snapshot, pendingActionKind)
|
|
36243
|
+
humanText: formatConversationStatus(snapshot, pendingActionKind),
|
|
36244
|
+
terminal: true
|
|
35861
36245
|
});
|
|
35862
36246
|
}
|
|
35863
36247
|
/** Finalizes output, including the accumulated event list in JSON mode. */
|
|
35864
36248
|
finish(summary) {
|
|
36249
|
+
this.flushAssistantMessage(false);
|
|
35865
36250
|
stopEarlySpinner();
|
|
35866
36251
|
if (this.mode === "json") {
|
|
35867
36252
|
this.stream.write(`${formatJson5({ ...summary, events: this.events })}
|
|
@@ -35873,6 +36258,17 @@ var OutputWriter = class {
|
|
|
35873
36258
|
this.assistantLineOpen = false;
|
|
35874
36259
|
}
|
|
35875
36260
|
}
|
|
36261
|
+
flushAssistantMessage(partial2) {
|
|
36262
|
+
if (!this.bufferedText) return;
|
|
36263
|
+
const text2 = this.bufferedText;
|
|
36264
|
+
this.bufferedText = "";
|
|
36265
|
+
this.emit({
|
|
36266
|
+
type: "assistant_message",
|
|
36267
|
+
conversationId: this.bufferedTextConversationId,
|
|
36268
|
+
text: text2,
|
|
36269
|
+
...partial2 ? { partial: true } : {}
|
|
36270
|
+
});
|
|
36271
|
+
}
|
|
35876
36272
|
/**
|
|
35877
36273
|
* The agent goes quiet for long stretches between events, so a spinner
|
|
35878
36274
|
* fills the gap. Taking it down before every write keeps its next frame
|
|
@@ -36096,6 +36492,7 @@ function getMessageRoleLabel(role) {
|
|
|
36096
36492
|
|
|
36097
36493
|
// src/commands/chat/connection-authorizer.ts
|
|
36098
36494
|
var CONNECTION_POLL_INTERVAL_MS = 2e3;
|
|
36495
|
+
var CONNECTION_WAITING_REPORT_INTERVAL_MS = 15e3;
|
|
36099
36496
|
var CONNECTION_AUTHORIZATION_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
36100
36497
|
var ApiConnectionAuthorizer = class {
|
|
36101
36498
|
connections;
|
|
@@ -36187,6 +36584,7 @@ var ApiConnectionAuthorizer = class {
|
|
|
36187
36584
|
}
|
|
36188
36585
|
async waitUntilAuthorized(request2, connectionId, signal) {
|
|
36189
36586
|
const startedAt = Date.now();
|
|
36587
|
+
let reportedAt;
|
|
36190
36588
|
while (true) {
|
|
36191
36589
|
if (signal?.aborted) throw new Error("Connection authorization interrupted.");
|
|
36192
36590
|
const connection = await this.connections.get(connectionId, { signal });
|
|
@@ -36214,16 +36612,20 @@ var ApiConnectionAuthorizer = class {
|
|
|
36214
36612
|
`Timed out waiting for ${request2.serviceName} connection authorization.`
|
|
36215
36613
|
);
|
|
36216
36614
|
}
|
|
36217
|
-
|
|
36218
|
-
|
|
36219
|
-
|
|
36220
|
-
|
|
36221
|
-
|
|
36222
|
-
|
|
36223
|
-
|
|
36224
|
-
|
|
36225
|
-
|
|
36226
|
-
|
|
36615
|
+
const now = Date.now();
|
|
36616
|
+
if (reportedAt === void 0 || now - reportedAt >= CONNECTION_WAITING_REPORT_INTERVAL_MS) {
|
|
36617
|
+
reportedAt = now;
|
|
36618
|
+
this.output.emit({
|
|
36619
|
+
type: "connection_authorization_waiting",
|
|
36620
|
+
conversationId: request2.conversationId,
|
|
36621
|
+
connectionId,
|
|
36622
|
+
connectorId: request2.connectorId,
|
|
36623
|
+
serviceName: request2.serviceName,
|
|
36624
|
+
elapsedSeconds: Math.floor(elapsedMs / 1e3),
|
|
36625
|
+
state: "waiting_for_user",
|
|
36626
|
+
terminal: false
|
|
36627
|
+
});
|
|
36628
|
+
}
|
|
36227
36629
|
await wait(Math.min(this.pollIntervalMs, this.timeoutMs - elapsedMs), void 0, {
|
|
36228
36630
|
signal
|
|
36229
36631
|
});
|
|
@@ -36238,11 +36640,48 @@ function isSafeSetupUrl(value) {
|
|
|
36238
36640
|
}
|
|
36239
36641
|
}
|
|
36240
36642
|
|
|
36643
|
+
// src/commands/chat/chat-failure.ts
|
|
36644
|
+
var SELECTION_CODES = /* @__PURE__ */ new Set([
|
|
36645
|
+
"CHAT_AWAITING_SELECTION",
|
|
36646
|
+
"CHAT_AMBIGUOUS_SELECTION",
|
|
36647
|
+
"CHAT_UNKNOWN_CONNECTION"
|
|
36648
|
+
]);
|
|
36649
|
+
function chatFailureCode(error62) {
|
|
36650
|
+
switch (classifyChatTurnFailure(error62)) {
|
|
36651
|
+
case "broken":
|
|
36652
|
+
return "CHAT_TURN_FAILED";
|
|
36653
|
+
case "refused":
|
|
36654
|
+
return "CHAT_TURN_REJECTED";
|
|
36655
|
+
default:
|
|
36656
|
+
return FALLBACK_FAILURE_CODE;
|
|
36657
|
+
}
|
|
36658
|
+
}
|
|
36659
|
+
function messageResumeCommand(conversationId) {
|
|
36660
|
+
return `tda chat continue ${conversationId} "<message>"`;
|
|
36661
|
+
}
|
|
36662
|
+
function resumeCommandFor(conversationId, code) {
|
|
36663
|
+
if (code === "CHAT_TURN_ACTIVE") return void 0;
|
|
36664
|
+
if (code === "CHAT_AWAITING_PUSH") return `tda chat continue ${conversationId}`;
|
|
36665
|
+
if (SELECTION_CODES.has(code)) {
|
|
36666
|
+
return `tda chat continue ${conversationId} --connection <connector[:type]=connection-id>`;
|
|
36667
|
+
}
|
|
36668
|
+
return messageResumeCommand(conversationId);
|
|
36669
|
+
}
|
|
36670
|
+
function formatChatFailureDetails(details) {
|
|
36671
|
+
return [
|
|
36672
|
+
`Conversation: ${details.conversationId}`,
|
|
36673
|
+
...details.requestId ? [`Request ID: ${details.requestId}`] : [],
|
|
36674
|
+
...details.draftCommand ? [`Its draft is saved: ${details.draftCommand}`] : [],
|
|
36675
|
+
...details.resumeCommand ? [`Resume with: ${details.resumeCommand}`] : []
|
|
36676
|
+
];
|
|
36677
|
+
}
|
|
36678
|
+
|
|
36241
36679
|
// src/commands/chat/conversation-turn-lock.ts
|
|
36242
36680
|
import { link, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
36243
36681
|
import { tmpdir } from "node:os";
|
|
36244
36682
|
import { join } from "node:path";
|
|
36245
36683
|
import { randomUUID } from "node:crypto";
|
|
36684
|
+
import { CliError as CliError6 } from "@doist/cli-core";
|
|
36246
36685
|
var ConversationTurnLock = class {
|
|
36247
36686
|
directory;
|
|
36248
36687
|
pid;
|
|
@@ -36279,7 +36718,8 @@ var ConversationTurnLock = class {
|
|
|
36279
36718
|
}
|
|
36280
36719
|
const current = await readLock(path);
|
|
36281
36720
|
if (current && this.isProcessRunning(current.pid)) {
|
|
36282
|
-
throw new
|
|
36721
|
+
throw new CliError6(
|
|
36722
|
+
"CHAT_TURN_ACTIVE",
|
|
36283
36723
|
`Conversation ${conversationId} already has an active turn in process ${current.pid}. Continue monitoring that process; do not invoke continue until it exits.`
|
|
36284
36724
|
);
|
|
36285
36725
|
}
|
|
@@ -36326,7 +36766,8 @@ function isNoSuchProcessError(error62) {
|
|
|
36326
36766
|
|
|
36327
36767
|
// src/commands/chat/chat-service.ts
|
|
36328
36768
|
var TURN_HEARTBEAT_INTERVAL_MS = 15e3;
|
|
36329
|
-
var
|
|
36769
|
+
var DRAFT_LOOKUP_TIMEOUT_MS = 5e3;
|
|
36770
|
+
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
36771
|
var ChatService = class {
|
|
36331
36772
|
api;
|
|
36332
36773
|
transport;
|
|
@@ -36336,6 +36777,8 @@ var ChatService = class {
|
|
|
36336
36777
|
turnLock;
|
|
36337
36778
|
heartbeatIntervalMs;
|
|
36338
36779
|
readline;
|
|
36780
|
+
/** The conversation a start or continue is working on, once it is known. */
|
|
36781
|
+
failureScope;
|
|
36339
36782
|
constructor({
|
|
36340
36783
|
token,
|
|
36341
36784
|
baseUrl,
|
|
@@ -36366,6 +36809,10 @@ var ChatService = class {
|
|
|
36366
36809
|
*/
|
|
36367
36810
|
async startConversation(message, signal, workspaceId = null) {
|
|
36368
36811
|
const conversation = workspaceId === null ? await this.api.chat.createConversation({}, { signal }) : await this.api.workspaceChat.createConversation(workspaceId, {}, { signal });
|
|
36812
|
+
this.failureScope = {
|
|
36813
|
+
conversationId: conversation.id,
|
|
36814
|
+
workspaceId: conversation.workspaceId
|
|
36815
|
+
};
|
|
36369
36816
|
this.output.emit({ type: "session_started", conversationId: conversation.id });
|
|
36370
36817
|
return this.withActiveTurn(
|
|
36371
36818
|
conversation.id,
|
|
@@ -36384,8 +36831,10 @@ var ChatService = class {
|
|
|
36384
36831
|
selections,
|
|
36385
36832
|
signal
|
|
36386
36833
|
}) {
|
|
36834
|
+
this.failureScope = { conversationId };
|
|
36387
36835
|
const conversation = await this.api.chat.getConversation(conversationId, { signal });
|
|
36388
36836
|
const scope = { conversationId, workspaceId: conversation.workspaceId };
|
|
36837
|
+
this.failureScope = scope;
|
|
36389
36838
|
return this.withActiveTurn(
|
|
36390
36839
|
conversationId,
|
|
36391
36840
|
async () => {
|
|
@@ -36398,7 +36847,8 @@ var ChatService = class {
|
|
|
36398
36847
|
} else {
|
|
36399
36848
|
const nextMessage = this.interactive ? await this.promptForMessage(signal) : "";
|
|
36400
36849
|
if (!nextMessage) {
|
|
36401
|
-
throw new
|
|
36850
|
+
throw new CliError7(
|
|
36851
|
+
"CHAT_INPUT_REQUIRED",
|
|
36402
36852
|
"A message or --connection selection is required to continue."
|
|
36403
36853
|
);
|
|
36404
36854
|
}
|
|
@@ -36504,6 +36954,7 @@ var ChatService = class {
|
|
|
36504
36954
|
}
|
|
36505
36955
|
}
|
|
36506
36956
|
async runTurn({ conversationId, workspaceId }, input2, signal, pendingAction, { markActivity }) {
|
|
36957
|
+
this.output.startTurn();
|
|
36507
36958
|
try {
|
|
36508
36959
|
return await runConversationTurn({
|
|
36509
36960
|
api: this.api,
|
|
@@ -36515,7 +36966,7 @@ var ChatService = class {
|
|
|
36515
36966
|
signal,
|
|
36516
36967
|
onText: (text2) => {
|
|
36517
36968
|
markActivity();
|
|
36518
|
-
this.output.
|
|
36969
|
+
this.output.appendAssistantText(conversationId, text2);
|
|
36519
36970
|
},
|
|
36520
36971
|
onToolActivity: (activity) => {
|
|
36521
36972
|
markActivity();
|
|
@@ -36537,7 +36988,7 @@ var ChatService = class {
|
|
|
36537
36988
|
});
|
|
36538
36989
|
} catch (error62) {
|
|
36539
36990
|
if (error62 instanceof ChatTurnInputError) {
|
|
36540
|
-
throw new
|
|
36991
|
+
throw new CliError7(`CHAT_${error62.code.toUpperCase()}`, describeInputError(error62));
|
|
36541
36992
|
}
|
|
36542
36993
|
throw error62;
|
|
36543
36994
|
}
|
|
@@ -36561,13 +37012,17 @@ var ChatService = class {
|
|
|
36561
37012
|
terminal: false
|
|
36562
37013
|
});
|
|
36563
37014
|
}
|
|
36564
|
-
completeTurn(
|
|
37015
|
+
completeTurn(turn) {
|
|
37016
|
+
const summary = { ...turn, assistantText: this.output.assistantText || turn.assistantText };
|
|
36565
37017
|
const resumeCommand = getResumeCommand(summary);
|
|
36566
37018
|
this.output.emit({
|
|
36567
37019
|
type: "turn_completed",
|
|
36568
37020
|
conversationId: summary.conversationId,
|
|
36569
37021
|
status: summary.status,
|
|
36570
37022
|
outcome: summary.status,
|
|
37023
|
+
assistantText: summary.assistantText,
|
|
37024
|
+
...summary.automationId ? { automationId: summary.automationId } : {},
|
|
37025
|
+
...summary.action ? { action: summary.action } : {},
|
|
36571
37026
|
state: "completed",
|
|
36572
37027
|
terminal: true,
|
|
36573
37028
|
...summary.conversationArchived !== void 0 ? { conversationArchived: summary.conversationArchived } : {},
|
|
@@ -36608,6 +37063,32 @@ var ChatService = class {
|
|
|
36608
37063
|
await release();
|
|
36609
37064
|
}
|
|
36610
37065
|
}
|
|
37066
|
+
/** Whether a start or continue has a conversation its failure can name. */
|
|
37067
|
+
get hasFailureScope() {
|
|
37068
|
+
return this.failureScope !== void 0;
|
|
37069
|
+
}
|
|
37070
|
+
/**
|
|
37071
|
+
* What a caller needs to pick up a conversation whose start or continue
|
|
37072
|
+
* failed, or undefined before one was known. The draft is looked up only for
|
|
37073
|
+
* a turn that broke, not one refused for its input, credentials or balance.
|
|
37074
|
+
*/
|
|
37075
|
+
async describeFailure(error62, code) {
|
|
37076
|
+
const scope = this.failureScope;
|
|
37077
|
+
if (!scope) return void 0;
|
|
37078
|
+
const { conversationId, workspaceId } = scope;
|
|
37079
|
+
const draftId = workspaceId !== void 0 && classifyChatTurnFailure(error62) === "broken" ? await findConversationDraftId(this.api, {
|
|
37080
|
+
conversationId,
|
|
37081
|
+
workspaceId,
|
|
37082
|
+
signal: AbortSignal.timeout(DRAFT_LOOKUP_TIMEOUT_MS)
|
|
37083
|
+
}).catch(() => null) : null;
|
|
37084
|
+
const resumeCommand = resumeCommandFor(conversationId, code);
|
|
37085
|
+
return {
|
|
37086
|
+
conversationId,
|
|
37087
|
+
...readFailedRequest(error62),
|
|
37088
|
+
...draftId && workspaceId !== void 0 ? { draftId, draftCommand: `tda draft view ${draftId} ${scopeFlag(workspaceId)}` } : {},
|
|
37089
|
+
...resumeCommand ? { resumeCommand } : {}
|
|
37090
|
+
};
|
|
37091
|
+
}
|
|
36611
37092
|
async getPersistedPendingAction(conversationId, signal) {
|
|
36612
37093
|
const messages = await this.api.chat.getMessages(conversationId, { signal });
|
|
36613
37094
|
return getPendingActionFromMessages(messages);
|
|
@@ -36694,21 +37175,25 @@ function getResumeCommand(summary) {
|
|
|
36694
37175
|
if (typeof actionResume === "string") {
|
|
36695
37176
|
return actionResume.replace("<conversation-id>", summary.conversationId);
|
|
36696
37177
|
}
|
|
36697
|
-
return
|
|
37178
|
+
return messageResumeCommand(summary.conversationId);
|
|
36698
37179
|
}
|
|
36699
37180
|
|
|
36700
37181
|
// src/commands/chat/index.ts
|
|
36701
37182
|
function registerChatCommand(program) {
|
|
36702
37183
|
const chat = program.command("chat").description("Talk to the Todoist Automations agent");
|
|
36703
37184
|
addWorkspaceScopeOptions(
|
|
36704
|
-
|
|
37185
|
+
addStreamTextOption(
|
|
37186
|
+
chat.command("start").description("Start a new conversation").argument("<message...>", "Initial message")
|
|
37187
|
+
)
|
|
36705
37188
|
).action(async (messageParts, options, command) => {
|
|
36706
37189
|
await runCommand(command, async ({ service, signal }) => {
|
|
36707
37190
|
const workspaceId = await resolveWorkspaceScope(options);
|
|
36708
37191
|
return service.startConversation(messageParts.join(" "), signal, workspaceId);
|
|
36709
37192
|
});
|
|
36710
37193
|
});
|
|
36711
|
-
|
|
37194
|
+
addStreamTextOption(
|
|
37195
|
+
chat.command("continue").description("Continue a persisted conversation").argument("<conversation-id>", "Conversation ID emitted by start").argument("[message...]", "Answer or follow-up message")
|
|
37196
|
+
).option(
|
|
36712
37197
|
"--connection <connector[:type]=connection-id>",
|
|
36713
37198
|
"Resolve a pending connection selection; repeat for multiple connectors",
|
|
36714
37199
|
collectOption,
|
|
@@ -36733,11 +37218,19 @@ function registerChatCommand(program) {
|
|
|
36733
37218
|
);
|
|
36734
37219
|
});
|
|
36735
37220
|
}
|
|
37221
|
+
function addStreamTextOption(command) {
|
|
37222
|
+
return command.option(
|
|
37223
|
+
"--stream-text",
|
|
37224
|
+
"Under --ndjson, emit the assistant text as it arrives (assistant_text) rather than one assistant_message per step"
|
|
37225
|
+
);
|
|
37226
|
+
}
|
|
36736
37227
|
async function runCommand(command, operation) {
|
|
36737
37228
|
let service;
|
|
36738
37229
|
let source;
|
|
36739
37230
|
await runStreamingCommand(command, {
|
|
36740
|
-
createOutput: (mode) => new OutputWriter(mode
|
|
37231
|
+
createOutput: (mode) => new OutputWriter(mode, process.stdout, process.stderr, {
|
|
37232
|
+
streamText: command.opts().streamText === true
|
|
37233
|
+
}),
|
|
36741
37234
|
operation: async ({ options, mode, output: output2, signal }) => {
|
|
36742
37235
|
const apiConfiguration = await withSpinner(
|
|
36743
37236
|
{ text: "Connecting..." },
|
|
@@ -36760,33 +37253,33 @@ async function runCommand(command, operation) {
|
|
|
36760
37253
|
onSuccess: (result, { output: output2 }) => output2.finish(result),
|
|
36761
37254
|
onInterrupted: ({ output: output2 }) => {
|
|
36762
37255
|
const conversationId = output2.conversationId;
|
|
36763
|
-
|
|
37256
|
+
const resume = conversationId ? { resumeCommand: messageResumeCommand(conversationId) } : {};
|
|
36764
37257
|
output2.emit({
|
|
36765
|
-
type: "
|
|
37258
|
+
type: "interrupted",
|
|
36766
37259
|
conversationId,
|
|
36767
37260
|
status: "interrupted",
|
|
36768
|
-
|
|
36769
|
-
|
|
36770
|
-
terminal: true
|
|
36771
|
-
...conversationId ? { resumeCommand: `tda chat continue ${conversationId} "<message>"` } : {}
|
|
37261
|
+
message: "Interrupted.",
|
|
37262
|
+
...resume,
|
|
37263
|
+
terminal: true
|
|
36772
37264
|
});
|
|
36773
37265
|
output2.finish({
|
|
36774
37266
|
conversationId,
|
|
36775
37267
|
status: "interrupted",
|
|
36776
|
-
assistantText:
|
|
37268
|
+
assistantText: output2.assistantText,
|
|
37269
|
+
...resume
|
|
36777
37270
|
});
|
|
36778
37271
|
},
|
|
36779
|
-
onError: (error62, { mode, output: output2 }) => {
|
|
37272
|
+
onError: async (error62, { mode, output: output2 }) => {
|
|
36780
37273
|
const failure2 = renderFailure(error62, { mode, source });
|
|
36781
|
-
const
|
|
36782
|
-
|
|
36783
|
-
|
|
36784
|
-
|
|
36785
|
-
|
|
36786
|
-
|
|
36787
|
-
|
|
36788
|
-
});
|
|
36789
|
-
output2.finish(
|
|
37274
|
+
const code = failure2.code ?? (service?.hasFailureScope ? chatFailureCode(error62) : FALLBACK_FAILURE_CODE);
|
|
37275
|
+
const details = await service?.describeFailure(error62, code);
|
|
37276
|
+
const message = mode === "human" ? [
|
|
37277
|
+
failure2.humanMessage,
|
|
37278
|
+
...details ? formatChatFailureDetails(details) : []
|
|
37279
|
+
].join("\n") : failure2.message;
|
|
37280
|
+
const record2 = toFailureRecord({ code, message }, details);
|
|
37281
|
+
output2.emit({ type: "error", ...record2, state: "failed", terminal: true });
|
|
37282
|
+
output2.finish(record2);
|
|
36790
37283
|
},
|
|
36791
37284
|
onFinally: () => {
|
|
36792
37285
|
stopEarlySpinner();
|
|
@@ -36972,7 +37465,12 @@ async function runAuthorizationCommand(command, operation) {
|
|
|
36972
37465
|
onSuccess: (result, { mode, output: output2 }) => {
|
|
36973
37466
|
if (mode === "json") return output2.finish(result);
|
|
36974
37467
|
stopEarlySpinner();
|
|
36975
|
-
writeResult(mode, result, {
|
|
37468
|
+
writeResult(mode, result, {
|
|
37469
|
+
human: formatConnectionChange,
|
|
37470
|
+
ndjson: (completed) => [
|
|
37471
|
+
{ type: "connection_completed", ...completed, terminal: true }
|
|
37472
|
+
]
|
|
37473
|
+
});
|
|
36976
37474
|
},
|
|
36977
37475
|
onInterrupted: ({ output: output2 }) => {
|
|
36978
37476
|
output2.emit({
|
|
@@ -36986,14 +37484,9 @@ async function runAuthorizationCommand(command, operation) {
|
|
|
36986
37484
|
onError: (error62, { mode, output: output2 }) => {
|
|
36987
37485
|
const failure2 = renderFailure(error62, { mode, source });
|
|
36988
37486
|
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 });
|
|
37487
|
+
const record2 = toFailureRecord({ code: failure2.code, message });
|
|
37488
|
+
output2.emit({ type: "error", ...record2, terminal: true });
|
|
37489
|
+
output2.finish(record2);
|
|
36997
37490
|
},
|
|
36998
37491
|
onFinally: () => stopEarlySpinner()
|
|
36999
37492
|
});
|
|
@@ -37366,7 +37859,7 @@ function registerConnectorCommand(program) {
|
|
|
37366
37859
|
}
|
|
37367
37860
|
|
|
37368
37861
|
// src/lib/manifest-input.ts
|
|
37369
|
-
import { CliError as
|
|
37862
|
+
import { CliError as CliError8, getErrorMessage as getErrorMessage8 } from "@doist/cli-core";
|
|
37370
37863
|
import { Option as Option6 } from "commander";
|
|
37371
37864
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
37372
37865
|
import { text } from "node:stream/consumers";
|
|
@@ -37380,9 +37873,9 @@ async function readManifestInput(options, input2 = process.stdin) {
|
|
|
37380
37873
|
try {
|
|
37381
37874
|
return JSON.parse(source.content);
|
|
37382
37875
|
} catch (error62) {
|
|
37383
|
-
throw new
|
|
37876
|
+
throw new CliError8(
|
|
37384
37877
|
"MANIFEST_UNREADABLE",
|
|
37385
|
-
`${source.label} is not valid JSON: ${
|
|
37878
|
+
`${source.label} is not valid JSON: ${getErrorMessage8(error62)}`
|
|
37386
37879
|
);
|
|
37387
37880
|
}
|
|
37388
37881
|
}
|
|
@@ -37391,9 +37884,9 @@ async function readSource(options, input2) {
|
|
|
37391
37884
|
try {
|
|
37392
37885
|
return { label: options.file, content: await readFile2(options.file, "utf8") };
|
|
37393
37886
|
} catch (error62) {
|
|
37394
|
-
throw new
|
|
37887
|
+
throw new CliError8(
|
|
37395
37888
|
"MANIFEST_UNREADABLE",
|
|
37396
|
-
`Could not read ${options.file}: ${
|
|
37889
|
+
`Could not read ${options.file}: ${getErrorMessage8(error62)}`
|
|
37397
37890
|
);
|
|
37398
37891
|
}
|
|
37399
37892
|
}
|
|
@@ -37401,17 +37894,17 @@ async function readSource(options, input2) {
|
|
|
37401
37894
|
try {
|
|
37402
37895
|
return { label: "Standard input", content: await text(input2) };
|
|
37403
37896
|
} catch (error62) {
|
|
37404
|
-
throw new
|
|
37897
|
+
throw new CliError8(
|
|
37405
37898
|
"MANIFEST_UNREADABLE",
|
|
37406
|
-
`Could not read standard input: ${
|
|
37899
|
+
`Could not read standard input: ${getErrorMessage8(error62)}`
|
|
37407
37900
|
);
|
|
37408
37901
|
}
|
|
37409
37902
|
}
|
|
37410
|
-
throw new
|
|
37903
|
+
throw new CliError8("MANIFEST_UNREADABLE", "Provide the manifest with --file <path> or --stdin.");
|
|
37411
37904
|
}
|
|
37412
37905
|
|
|
37413
37906
|
// src/commands/draft/draft-service.ts
|
|
37414
|
-
import { CliError as
|
|
37907
|
+
import { CliError as CliError9, getErrorMessage as getErrorMessage9 } from "@doist/cli-core";
|
|
37415
37908
|
import { readFile as readFile3 } from "node:fs/promises";
|
|
37416
37909
|
function createDraft2(draftApi, input2) {
|
|
37417
37910
|
return withRecoveryHints(draftApi, createDraft(draftApi, input2));
|
|
@@ -37444,13 +37937,13 @@ async function withRecoveryHints(draftApi, operation) {
|
|
|
37444
37937
|
const draftId = error62.draftId ?? "<draft-id>";
|
|
37445
37938
|
switch (error62.code) {
|
|
37446
37939
|
case "manifest_invalid":
|
|
37447
|
-
throw new
|
|
37940
|
+
throw new CliError9("MANIFEST_INVALID", error62.message);
|
|
37448
37941
|
case "draft_conflict":
|
|
37449
|
-
throw new
|
|
37942
|
+
throw new CliError9("DRAFT_CONFLICT", error62.message, {
|
|
37450
37943
|
hints: draftHints(scope, draftId)
|
|
37451
37944
|
});
|
|
37452
37945
|
case "draft_manifest_rejected":
|
|
37453
|
-
throw new
|
|
37946
|
+
throw new CliError9("DRAFT_MANIFEST_REJECTED", error62.message, {
|
|
37454
37947
|
hints: draftHints(scope, draftId)
|
|
37455
37948
|
});
|
|
37456
37949
|
case "draft_not_ready":
|
|
@@ -37468,14 +37961,12 @@ function draftHints(scope, draftId) {
|
|
|
37468
37961
|
`Or discard it first: tda draft delete ${draftId} --yes ${scopeFlag(scope)}`
|
|
37469
37962
|
];
|
|
37470
37963
|
}
|
|
37471
|
-
function scopeFlag(scope) {
|
|
37472
|
-
return scope === null ? "--personal" : `--workspace ${scope}`;
|
|
37473
|
-
}
|
|
37474
37964
|
async function startDraftTest2(draftApi, {
|
|
37475
37965
|
draftId,
|
|
37476
37966
|
request: request2,
|
|
37477
37967
|
wait: waitFor,
|
|
37478
|
-
signal
|
|
37968
|
+
signal,
|
|
37969
|
+
onProgress
|
|
37479
37970
|
}) {
|
|
37480
37971
|
let started;
|
|
37481
37972
|
try {
|
|
@@ -37497,9 +37988,21 @@ async function startDraftTest2(draftApi, {
|
|
|
37497
37988
|
}
|
|
37498
37989
|
let finished;
|
|
37499
37990
|
try {
|
|
37500
|
-
finished = await waitForDraftTest(draftApi, started, {
|
|
37991
|
+
finished = await waitForDraftTest(draftApi, started, {
|
|
37992
|
+
...waitFor,
|
|
37993
|
+
signal,
|
|
37994
|
+
...onProgress ? { onPoll: reportRunProgress(onProgress) } : {}
|
|
37995
|
+
});
|
|
37501
37996
|
} catch (error62) {
|
|
37502
|
-
throw
|
|
37997
|
+
throw toRunWaitError(error62, {
|
|
37998
|
+
run: `test run ${identity.testRunId} of draft ${identity.draftId}`,
|
|
37999
|
+
command: identity.command,
|
|
38000
|
+
codes: {
|
|
38001
|
+
interrupted: "DRAFT_TEST_WAIT_INTERRUPTED",
|
|
38002
|
+
failed: "DRAFT_TEST_WAIT_FAILED"
|
|
38003
|
+
},
|
|
38004
|
+
signal
|
|
38005
|
+
});
|
|
37503
38006
|
}
|
|
37504
38007
|
return {
|
|
37505
38008
|
action: "test_finished",
|
|
@@ -37511,25 +38014,9 @@ async function startDraftTest2(draftApi, {
|
|
|
37511
38014
|
result: finished.result
|
|
37512
38015
|
};
|
|
37513
38016
|
}
|
|
37514
|
-
function toDraftTestWaitError(error62, identity, signal) {
|
|
37515
|
-
const run = `test run ${identity.testRunId} of draft ${identity.draftId}`;
|
|
37516
|
-
const hints = [`See how it ends: ${identity.command}`];
|
|
37517
|
-
if (signal.aborted) {
|
|
37518
|
-
return new CliError5(
|
|
37519
|
-
"DRAFT_TEST_WAIT_INTERRUPTED",
|
|
37520
|
-
`Stopped waiting for ${run}. It keeps running.`,
|
|
37521
|
-
{ hints }
|
|
37522
|
-
);
|
|
37523
|
-
}
|
|
37524
|
-
return new CliError5(
|
|
37525
|
-
"DRAFT_TEST_WAIT_FAILED",
|
|
37526
|
-
`Checking on ${run} failed: ${getErrorMessage8(error62).replace(/\.?$/, ".")} It keeps running.`,
|
|
37527
|
-
{ hints }
|
|
37528
|
-
);
|
|
37529
|
-
}
|
|
37530
38017
|
function draftNotReadyError(message, draftId, scope) {
|
|
37531
38018
|
const flag = scopeFlag(scope);
|
|
37532
|
-
return new
|
|
38019
|
+
return new CliError9("DRAFT_NOT_READY", message, {
|
|
37533
38020
|
hints: [
|
|
37534
38021
|
`See the issues: tda draft view ${draftId} ${flag}`,
|
|
37535
38022
|
`Bind connections: tda draft connect ${draftId} --auto ${flag}`
|
|
@@ -37543,13 +38030,13 @@ function toDraftTestCliError(refusal, draftId, scope) {
|
|
|
37543
38030
|
return draftNotReadyError(refusal.message, draftId, scope);
|
|
37544
38031
|
case "draft_test_payload_required":
|
|
37545
38032
|
case "draft_test_payload_filtered":
|
|
37546
|
-
return new
|
|
38033
|
+
return new CliError9(refusal.code.toUpperCase(), refusal.message, {
|
|
37547
38034
|
hints: [
|
|
37548
38035
|
`Test with a real event: tda draft test ${draftId} --trigger <event> --payload-file <path> ${flag}`
|
|
37549
38036
|
]
|
|
37550
38037
|
});
|
|
37551
38038
|
default:
|
|
37552
|
-
return new
|
|
38039
|
+
return new CliError9(refusal.code.toUpperCase(), refusal.message);
|
|
37553
38040
|
}
|
|
37554
38041
|
}
|
|
37555
38042
|
async function readTestPayloadFile(path) {
|
|
@@ -37557,17 +38044,17 @@ async function readTestPayloadFile(path) {
|
|
|
37557
38044
|
try {
|
|
37558
38045
|
content = await readFile3(path, "utf8");
|
|
37559
38046
|
} catch (error62) {
|
|
37560
|
-
throw new
|
|
38047
|
+
throw new CliError9(
|
|
37561
38048
|
"PAYLOAD_UNREADABLE",
|
|
37562
|
-
`Could not read ${path}: ${
|
|
38049
|
+
`Could not read ${path}: ${getErrorMessage9(error62)}`
|
|
37563
38050
|
);
|
|
37564
38051
|
}
|
|
37565
38052
|
try {
|
|
37566
38053
|
return JSON.parse(content);
|
|
37567
38054
|
} catch (error62) {
|
|
37568
|
-
throw new
|
|
38055
|
+
throw new CliError9(
|
|
37569
38056
|
"PAYLOAD_UNREADABLE",
|
|
37570
|
-
`${path} is not valid JSON: ${
|
|
38057
|
+
`${path} is not valid JSON: ${getErrorMessage9(error62)}`
|
|
37571
38058
|
);
|
|
37572
38059
|
}
|
|
37573
38060
|
}
|
|
@@ -37754,7 +38241,7 @@ function formatDraftTest(result) {
|
|
|
37754
38241
|
}
|
|
37755
38242
|
|
|
37756
38243
|
// src/commands/draft/validate-command.ts
|
|
37757
|
-
import { CliError as
|
|
38244
|
+
import { CliError as CliError10 } from "@doist/cli-core";
|
|
37758
38245
|
|
|
37759
38246
|
// src/lib/local-command.ts
|
|
37760
38247
|
async function runLocalCommand(command, operation, presentation, execution = {}) {
|
|
@@ -37786,7 +38273,7 @@ function registerDraftValidateCommand(draft) {
|
|
|
37786
38273
|
function validateManifestInput(manifest) {
|
|
37787
38274
|
const validation = validateManifest(manifest);
|
|
37788
38275
|
if (!validation.isValid) {
|
|
37789
|
-
throw new
|
|
38276
|
+
throw new CliError10(
|
|
37790
38277
|
"MANIFEST_INVALID",
|
|
37791
38278
|
`Manifest is invalid: ${validation.errors.join("; ")}`
|
|
37792
38279
|
);
|
|
@@ -37806,8 +38293,6 @@ function formatManifestValidation(result) {
|
|
|
37806
38293
|
}
|
|
37807
38294
|
|
|
37808
38295
|
// src/commands/draft/index.ts
|
|
37809
|
-
var DEFAULT_TEST_WAIT = "10m";
|
|
37810
|
-
var TOKEN_LIFETIME_MARGIN_MS = 2 * 60 * 1e3;
|
|
37811
38296
|
function registerDraftCommand(program) {
|
|
37812
38297
|
const draft = program.command("draft").description("Author, test and inspect automation drafts");
|
|
37813
38298
|
registerDraftValidateCommand(draft);
|
|
@@ -37934,9 +38419,9 @@ function registerDraftCommand(program) {
|
|
|
37934
38419
|
);
|
|
37935
38420
|
});
|
|
37936
38421
|
addWorkspaceScopeOptions(
|
|
37937
|
-
|
|
37938
|
-
"
|
|
37939
|
-
|
|
38422
|
+
addWaitOptions(
|
|
38423
|
+
draft.command("test").description("Start a test run of a draft, with mocked connector calls").argument("<draft-id>", "Draft ID"),
|
|
38424
|
+
"Wait for the run to end and exit 1 unless it passed"
|
|
37940
38425
|
).option(
|
|
37941
38426
|
"--trigger <index|event>",
|
|
37942
38427
|
"The trigger to test: its position in the manifest's triggers, or a webhook event"
|
|
@@ -37947,11 +38432,8 @@ function registerDraftCommand(program) {
|
|
|
37947
38432
|
).action(async (draftId, options, command) => {
|
|
37948
38433
|
await runApiCommand(
|
|
37949
38434
|
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);
|
|
38435
|
+
async ({ api, signal, progress }) => {
|
|
38436
|
+
const wait2 = resolveWait(options);
|
|
37955
38437
|
const draftApi = bindDraftApi(api, await resolveWorkspaceScope(options), { signal });
|
|
37956
38438
|
return startDraftTest2(draftApi, {
|
|
37957
38439
|
draftId,
|
|
@@ -37959,14 +38441,15 @@ function registerDraftCommand(program) {
|
|
|
37959
38441
|
...options.trigger !== void 0 ? { trigger: toDraftTestTriggerSelector(options.trigger) } : {},
|
|
37960
38442
|
...options.payloadFile !== void 0 ? { payload: await readTestPayloadFile(options.payloadFile) } : {}
|
|
37961
38443
|
},
|
|
37962
|
-
wait:
|
|
37963
|
-
signal
|
|
38444
|
+
wait: wait2,
|
|
38445
|
+
signal,
|
|
38446
|
+
onProgress: (state) => progress({ type: "test_progress", draftId, ...state })
|
|
37964
38447
|
});
|
|
37965
38448
|
},
|
|
37966
|
-
{ human: formatDraftTest },
|
|
38449
|
+
{ human: formatDraftTest, ndjson: toTerminalRecords },
|
|
37967
38450
|
{
|
|
37968
38451
|
spinner: options.wait === true ? "Testing draft..." : "Starting test run...",
|
|
37969
|
-
minimumTokenLifetimeMs:
|
|
38452
|
+
minimumTokenLifetimeMs: waitTokenLifetimeMs(options),
|
|
37970
38453
|
exitCode: (result) => result.action === "test_finished" && result.outcome !== "passed" ? 1 : void 0
|
|
37971
38454
|
}
|
|
37972
38455
|
);
|
|
@@ -38026,20 +38509,6 @@ function streamDraftList(options, fetchPage) {
|
|
|
38026
38509
|
async (args) => summarizeDraftListPage(await fetchPage(args))
|
|
38027
38510
|
);
|
|
38028
38511
|
}
|
|
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
38512
|
|
|
38044
38513
|
// src/commands/run/index.ts
|
|
38045
38514
|
import { Option as Option7 } from "commander";
|
|
@@ -38135,11 +38604,11 @@ function registerRunCommand(program) {
|
|
|
38135
38604
|
}
|
|
38136
38605
|
|
|
38137
38606
|
// src/commands/skill/shared.ts
|
|
38138
|
-
import { CliError as
|
|
38607
|
+
import { CliError as CliError11 } from "@doist/cli-core";
|
|
38139
38608
|
function requireInstaller(agent) {
|
|
38140
38609
|
const installer = getInstaller(agent);
|
|
38141
38610
|
if (!installer) {
|
|
38142
|
-
throw new
|
|
38611
|
+
throw new CliError11("UNKNOWN_AGENT", `Unknown agent: ${agent}.`, {
|
|
38143
38612
|
hints: [`Available agents: ${AGENT_NAMES.join(", ")}.`]
|
|
38144
38613
|
});
|
|
38145
38614
|
}
|
|
@@ -38217,7 +38686,7 @@ async function uninstallSkill(agent, options, command) {
|
|
|
38217
38686
|
}
|
|
38218
38687
|
|
|
38219
38688
|
// src/commands/skill/update.ts
|
|
38220
|
-
import { CliError as
|
|
38689
|
+
import { CliError as CliError12 } from "@doist/cli-core";
|
|
38221
38690
|
async function updateSkill(agent, options, command) {
|
|
38222
38691
|
await runLocalCommand(
|
|
38223
38692
|
command,
|
|
@@ -38243,7 +38712,7 @@ async function updateAllSkills(options, command) {
|
|
|
38243
38712
|
async () => {
|
|
38244
38713
|
const result = await updateAllInstalledSkills(options.local === true);
|
|
38245
38714
|
if (result.errors.length > 0) {
|
|
38246
|
-
throw new
|
|
38715
|
+
throw new CliError12(
|
|
38247
38716
|
"UPDATE_FAILED",
|
|
38248
38717
|
`Could not update ${result.errors.length} skill file(s).`,
|
|
38249
38718
|
{ hints: result.errors.map((error62) => `${error62.agent}: ${error62.message}`) }
|
|
@@ -38303,7 +38772,7 @@ function registerUpdateCommand(program) {
|
|
|
38303
38772
|
}
|
|
38304
38773
|
|
|
38305
38774
|
// src/commands/workspace/index.ts
|
|
38306
|
-
import { CliError as
|
|
38775
|
+
import { CliError as CliError13 } from "@doist/cli-core";
|
|
38307
38776
|
|
|
38308
38777
|
// src/commands/workspace/format.ts
|
|
38309
38778
|
function formatWorkspaceList(workspaces) {
|
|
@@ -38346,7 +38815,7 @@ function registerWorkspaceCommand(program) {
|
|
|
38346
38815
|
if (!(error62 instanceof AutomationsRequestError) || error62.httpStatusCode !== 404) {
|
|
38347
38816
|
throw error62;
|
|
38348
38817
|
}
|
|
38349
|
-
throw new
|
|
38818
|
+
throw new CliError13(
|
|
38350
38819
|
"WORKSPACE_ACCESS_UNAVAILABLE",
|
|
38351
38820
|
"Workspace access is not available for this account or environment.",
|
|
38352
38821
|
{
|