@doist/automations-cli 1.26.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 +813 -364
- 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()
|
|
@@ -29962,7 +29967,8 @@ var FRONTEND_COHORT_EVENTS = [
|
|
|
29962
29967
|
"template:used",
|
|
29963
29968
|
"top-up:completed",
|
|
29964
29969
|
"guided-creation:started",
|
|
29965
|
-
"guided-creation:completed"
|
|
29970
|
+
"guided-creation:completed",
|
|
29971
|
+
"workspace:switched"
|
|
29966
29972
|
];
|
|
29967
29973
|
var BACKEND_COHORT_EVENTS = [
|
|
29968
29974
|
// API-process (publish lifecycle)
|
|
@@ -31056,13 +31062,19 @@ var AutomationsRequestError = class extends Error {
|
|
|
31056
31062
|
* scope: a challenge can say the grant falls short without saying of what.
|
|
31057
31063
|
*/
|
|
31058
31064
|
requiredScope;
|
|
31059
|
-
|
|
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) {
|
|
31060
31071
|
super(message);
|
|
31061
31072
|
this.name = "AutomationsRequestError";
|
|
31062
31073
|
this.httpStatusCode = httpStatusCode;
|
|
31063
31074
|
this.responseData = responseData;
|
|
31064
31075
|
this.authChallenge = authChallenge2;
|
|
31065
31076
|
this.requiredScope = parseChallengeScope(authChallenge2);
|
|
31077
|
+
this.requestId = requestId;
|
|
31066
31078
|
}
|
|
31067
31079
|
isAuthError() {
|
|
31068
31080
|
return this.httpStatusCode === 401 || this.httpStatusCode === 403;
|
|
@@ -31133,7 +31145,7 @@ async function request(args) {
|
|
|
31133
31145
|
const data = await readResponseBody(response);
|
|
31134
31146
|
if (!response.ok) {
|
|
31135
31147
|
const message = typeof data === "object" && data !== null && "message" in data ? String(data.message) : response.statusText || `Request failed with status ${response.status}`;
|
|
31136
|
-
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);
|
|
31137
31149
|
}
|
|
31138
31150
|
return data;
|
|
31139
31151
|
}
|
|
@@ -31544,13 +31556,15 @@ var AutomationsClient = class extends BaseClient {
|
|
|
31544
31556
|
});
|
|
31545
31557
|
return createDraftResponseSchema.parse(data);
|
|
31546
31558
|
}
|
|
31559
|
+
/** Starts a run now. Undefined from an API that predates returning the run. */
|
|
31547
31560
|
async trigger(id, options) {
|
|
31548
31561
|
const parsedId = IdSchema.parse(id);
|
|
31549
|
-
await this.request({
|
|
31562
|
+
const data = await this.request({
|
|
31550
31563
|
method: "POST",
|
|
31551
31564
|
path: automationsEndpoints.trigger(parsedId),
|
|
31552
31565
|
options
|
|
31553
31566
|
});
|
|
31567
|
+
return triggerAutomationResponseSchema.optional().parse(data);
|
|
31554
31568
|
}
|
|
31555
31569
|
async test(id, options) {
|
|
31556
31570
|
const parsedId = IdSchema.parse(id);
|
|
@@ -31885,13 +31899,17 @@ var WorkspaceAutomationsClient = class extends BaseClient {
|
|
|
31885
31899
|
});
|
|
31886
31900
|
return automationSchema.parse(data);
|
|
31887
31901
|
}
|
|
31888
|
-
/**
|
|
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
|
+
*/
|
|
31889
31906
|
async trigger(workspaceId, id, options) {
|
|
31890
|
-
await this.request({
|
|
31907
|
+
const data = await this.request({
|
|
31891
31908
|
method: "POST",
|
|
31892
31909
|
path: workspaceAutomationsEndpoints.trigger(IdSchema.parse(workspaceId), IdSchema.parse(id)),
|
|
31893
31910
|
options
|
|
31894
31911
|
});
|
|
31912
|
+
return triggerAutomationResponseSchema.optional().parse(data);
|
|
31895
31913
|
}
|
|
31896
31914
|
/**
|
|
31897
31915
|
* Deletes the automation. Workspace admins may delete another member's
|
|
@@ -32801,6 +32819,9 @@ function readManifestMetadata(dto) {
|
|
|
32801
32819
|
...typeof dto.manifest.description === "string" ? { description: dto.manifest.description } : {}
|
|
32802
32820
|
};
|
|
32803
32821
|
}
|
|
32822
|
+
function readPrimaryTriggerType(dto) {
|
|
32823
|
+
return hasTriggerType(dto.manifest) ? getManifestTriggers(dto.manifest)[0]?.type : void 0;
|
|
32824
|
+
}
|
|
32804
32825
|
function summarizeTrigger(trigger) {
|
|
32805
32826
|
if (!isManifestRecord(trigger))
|
|
32806
32827
|
return void 0;
|
|
@@ -33300,24 +33321,11 @@ function readManifestName(manifest) {
|
|
|
33300
33321
|
return typeof name === "string" && name.trim() ? name : "(unnamed)";
|
|
33301
33322
|
}
|
|
33302
33323
|
|
|
33303
|
-
// ../../packages/sdk/dist/esm/
|
|
33324
|
+
// ../../packages/sdk/dist/esm/runs/poll-until-closed.js
|
|
33304
33325
|
var DEFAULT_POLL_INTERVALS_MS = { first: 2e3, max: 1e4 };
|
|
33305
33326
|
var STATUS_REQUEST_GRACE_MS = 1e4;
|
|
33306
|
-
var OPEN_RUN_STATUSES = /* @__PURE__ */ new Set([
|
|
33307
|
-
|
|
33308
|
-
"running"
|
|
33309
|
-
]);
|
|
33310
|
-
async function startDraftTest(draftApi, { draftId, request: request2 }) {
|
|
33311
|
-
const started = await draftApi.test(draftId, request2);
|
|
33312
|
-
return {
|
|
33313
|
-
draftId,
|
|
33314
|
-
testRunId: started.testRunId,
|
|
33315
|
-
trigger: started.trigger,
|
|
33316
|
-
connectorCalls: started.connectorCalls,
|
|
33317
|
-
status: started.status
|
|
33318
|
-
};
|
|
33319
|
-
}
|
|
33320
|
-
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 }) {
|
|
33321
33329
|
const intervals = pollIntervalsMs ?? DEFAULT_POLL_INTERVALS_MS;
|
|
33322
33330
|
const deadline = Date.now() + timeoutMs;
|
|
33323
33331
|
let intervalMs = intervals.first;
|
|
@@ -33326,24 +33334,57 @@ async function waitForDraftTest(draftApi, { draftId, testRunId }, { timeoutMs, p
|
|
|
33326
33334
|
let last = null;
|
|
33327
33335
|
while (true) {
|
|
33328
33336
|
try {
|
|
33329
|
-
last = await
|
|
33337
|
+
last = await read2(requestSignal);
|
|
33330
33338
|
} catch (error62) {
|
|
33331
33339
|
if (signal.aborted || !requestTimeout.aborted)
|
|
33332
33340
|
throw error62;
|
|
33333
|
-
return
|
|
33341
|
+
return { timedOut: true, run: last };
|
|
33334
33342
|
}
|
|
33335
33343
|
if (!OPEN_RUN_STATUSES.has(last.status)) {
|
|
33336
|
-
return
|
|
33344
|
+
return { timedOut: false, run: last };
|
|
33337
33345
|
}
|
|
33338
33346
|
onPoll?.(last);
|
|
33339
33347
|
const remainingMs = deadline - Date.now();
|
|
33340
33348
|
if (remainingMs <= 0) {
|
|
33341
|
-
return
|
|
33349
|
+
return { timedOut: true, run: last };
|
|
33342
33350
|
}
|
|
33343
33351
|
await delay(Math.min(intervalMs, remainingMs), signal);
|
|
33344
33352
|
intervalMs = Math.min(intervalMs * 2, intervals.max);
|
|
33345
33353
|
}
|
|
33346
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
|
+
}
|
|
33347
33388
|
function toDraftTestTriggerSelector(value) {
|
|
33348
33389
|
if (typeof value === "number")
|
|
33349
33390
|
return { index: value };
|
|
@@ -33370,22 +33411,64 @@ function toOutcome(status) {
|
|
|
33370
33411
|
return "failed";
|
|
33371
33412
|
}
|
|
33372
33413
|
}
|
|
33373
|
-
|
|
33374
|
-
|
|
33375
|
-
|
|
33376
|
-
|
|
33377
|
-
|
|
33378
|
-
|
|
33379
|
-
|
|
33380
|
-
|
|
33381
|
-
|
|
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
|
+
})
|
|
33382
33425
|
};
|
|
33383
|
-
|
|
33384
|
-
|
|
33385
|
-
|
|
33386
|
-
|
|
33387
|
-
|
|
33388
|
-
|
|
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;
|
|
33389
33472
|
}
|
|
33390
33473
|
|
|
33391
33474
|
// src/lib/auth-environment.ts
|
|
@@ -34594,172 +34677,6 @@ import { CliError as CliError3 } from "@doist/cli-core";
|
|
|
34594
34677
|
// src/lib/command-error.ts
|
|
34595
34678
|
import { formatJson as formatJson4, formatNdjson as formatNdjson4, getErrorMessage as getErrorMessage6 } from "@doist/cli-core";
|
|
34596
34679
|
|
|
34597
|
-
// src/lib/billing-errors.ts
|
|
34598
|
-
import { CliError as CliError2 } from "@doist/cli-core";
|
|
34599
|
-
var PAYMENT_REQUIRED_CODE = "PAYMENT_REQUIRED";
|
|
34600
|
-
var TOP_UP_PATH = "/settings/top-up";
|
|
34601
|
-
var TODOIST_SUBSCRIPTION_PATH = "/app/settings/subscription";
|
|
34602
|
-
function topUpUrl() {
|
|
34603
|
-
return `${getEnvironment().appUrl}${TOP_UP_PATH}`;
|
|
34604
|
-
}
|
|
34605
|
-
function todoistSubscriptionUrl() {
|
|
34606
|
-
return `${getEnvironment().todoistAppUrl}${TODOIST_SUBSCRIPTION_PATH}`;
|
|
34607
|
-
}
|
|
34608
|
-
var topUpHint = () => `Top up at ${topUpUrl()}.`;
|
|
34609
|
-
var HINT_BY_DENIAL_CODE = {
|
|
34610
|
-
usage_billing_insufficient_credit: topUpHint,
|
|
34611
|
-
usage_billing_no_billing_account: topUpHint,
|
|
34612
|
-
usage_billing_plan_required: () => `Upgrade at ${todoistSubscriptionUrl()}.`,
|
|
34613
|
-
usage_billing_billing_paused: topUpHint
|
|
34614
|
-
};
|
|
34615
|
-
var PaymentRequiredError = class extends CliError2 {
|
|
34616
|
-
constructor(message, hints) {
|
|
34617
|
-
super(PAYMENT_REQUIRED_CODE, message, { hints });
|
|
34618
|
-
this.name = "PaymentRequiredError";
|
|
34619
|
-
}
|
|
34620
|
-
};
|
|
34621
|
-
function explainBillingFailure(error62) {
|
|
34622
|
-
const refusal = getChatBillingRefusal(error62);
|
|
34623
|
-
if (refusal === null) return error62;
|
|
34624
|
-
const hints = refusal.code === void 0 ? [] : [HINT_BY_DENIAL_CODE[refusal.code]()];
|
|
34625
|
-
return new PaymentRequiredError(refusal.message, hints);
|
|
34626
|
-
}
|
|
34627
|
-
|
|
34628
|
-
// src/lib/output.ts
|
|
34629
|
-
import { formatJson as formatJson3, formatNdjson as formatNdjson3 } from "@doist/cli-core";
|
|
34630
|
-
import { stripVTControlCharacters } from "node:util";
|
|
34631
|
-
function getOutputMode(args) {
|
|
34632
|
-
if (args.json && args.ndjson) {
|
|
34633
|
-
throw new Error("Choose either --json or --ndjson, not both.");
|
|
34634
|
-
}
|
|
34635
|
-
if (args.json) return "json";
|
|
34636
|
-
if (args.ndjson) return "ndjson";
|
|
34637
|
-
return "human";
|
|
34638
|
-
}
|
|
34639
|
-
function sanitizeTerminalText(value) {
|
|
34640
|
-
return stripVTControlCharacters(value).replace(
|
|
34641
|
-
/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/gu,
|
|
34642
|
-
""
|
|
34643
|
-
);
|
|
34644
|
-
}
|
|
34645
|
-
function writeResult(mode, result, presentation, stream = process.stdout) {
|
|
34646
|
-
if (mode === "json") {
|
|
34647
|
-
stream.write(`${formatJson3(presentation.json?.(result) ?? result)}
|
|
34648
|
-
`);
|
|
34649
|
-
return;
|
|
34650
|
-
}
|
|
34651
|
-
if (mode === "ndjson") {
|
|
34652
|
-
const output3 = formatNdjson3(
|
|
34653
|
-
presentation.ndjson?.(result) ?? [presentation.json?.(result) ?? result]
|
|
34654
|
-
);
|
|
34655
|
-
if (output3) stream.write(`${output3}
|
|
34656
|
-
`);
|
|
34657
|
-
return;
|
|
34658
|
-
}
|
|
34659
|
-
const output2 = sanitizeTerminalText(presentation.human(result));
|
|
34660
|
-
stream.write(output2.endsWith("\n") ? output2 : `${output2}
|
|
34661
|
-
`);
|
|
34662
|
-
}
|
|
34663
|
-
async function writeNdjsonItem(item, stream = process.stdout) {
|
|
34664
|
-
if (stream.write(`${formatNdjson3([item])}
|
|
34665
|
-
`) !== false) return;
|
|
34666
|
-
await new Promise((resolve) => stream.once("drain", resolve));
|
|
34667
|
-
}
|
|
34668
|
-
|
|
34669
|
-
// src/lib/command-error.ts
|
|
34670
|
-
function renderFailure(error62, context = {}) {
|
|
34671
|
-
const authExplained = context.source ? explainAuthFailure(error62, context.source) : error62;
|
|
34672
|
-
const explained = explainBillingFailure(authExplained);
|
|
34673
|
-
const code = getErrorCode(explained);
|
|
34674
|
-
return {
|
|
34675
|
-
message: getErrorMessage6(explained),
|
|
34676
|
-
humanMessage: formatError2(explained),
|
|
34677
|
-
...code ? { code } : {}
|
|
34678
|
-
};
|
|
34679
|
-
}
|
|
34680
|
-
function writeFailure(mode, status, failure2) {
|
|
34681
|
-
const { message, code } = failure2;
|
|
34682
|
-
const result = { status, message, ...code ? { code } : {} };
|
|
34683
|
-
if (mode === "json") {
|
|
34684
|
-
process.stdout.write(`${formatJson4(result)}
|
|
34685
|
-
`);
|
|
34686
|
-
} else if (mode === "ndjson") {
|
|
34687
|
-
process.stdout.write(`${formatNdjson4([{ type: status, ...result, terminal: true }])}
|
|
34688
|
-
`);
|
|
34689
|
-
} else {
|
|
34690
|
-
const prefix = status === "error" ? "Error: " : "";
|
|
34691
|
-
process.stdout.write(`${prefix}${sanitizeTerminalText(failure2.humanMessage)}
|
|
34692
|
-
`);
|
|
34693
|
-
}
|
|
34694
|
-
}
|
|
34695
|
-
|
|
34696
|
-
// src/lib/api-command.ts
|
|
34697
|
-
async function runApiCommand(command, operation, presentation, execution = {}) {
|
|
34698
|
-
let mode = "human";
|
|
34699
|
-
let source;
|
|
34700
|
-
const abortController = new AbortController();
|
|
34701
|
-
const onInterrupt = () => abortController.abort();
|
|
34702
|
-
process.once("SIGINT", onInterrupt);
|
|
34703
|
-
try {
|
|
34704
|
-
const globalOptions = getGlobalOptions(command);
|
|
34705
|
-
mode = getOutputMode(globalOptions);
|
|
34706
|
-
const apiConfiguration = await resolveApiConfiguration(globalOptions, {
|
|
34707
|
-
minimumTokenLifetimeMs: execution.minimumTokenLifetimeMs
|
|
34708
|
-
});
|
|
34709
|
-
source = apiConfiguration.source;
|
|
34710
|
-
const context = {
|
|
34711
|
-
api: createAutomationsApi(apiConfiguration),
|
|
34712
|
-
signal: abortController.signal
|
|
34713
|
-
};
|
|
34714
|
-
if (mode === "ndjson" && execution.streamNdjson) {
|
|
34715
|
-
for await (const item of execution.streamNdjson(context)) await writeNdjsonItem(item);
|
|
34716
|
-
return;
|
|
34717
|
-
}
|
|
34718
|
-
const result = await withSpinner(
|
|
34719
|
-
{ text: execution.spinner ?? "Loading..." },
|
|
34720
|
-
() => operation(context)
|
|
34721
|
-
);
|
|
34722
|
-
stopEarlySpinner();
|
|
34723
|
-
writeResult(mode, result, presentation);
|
|
34724
|
-
const exitCode = execution.exitCode?.(result);
|
|
34725
|
-
if (exitCode !== void 0) process.exitCode = exitCode;
|
|
34726
|
-
} catch (error62) {
|
|
34727
|
-
if (abortController.signal.aborted) {
|
|
34728
|
-
writeFailure(
|
|
34729
|
-
mode,
|
|
34730
|
-
"interrupted",
|
|
34731
|
-
error62 instanceof CliError3 ? renderFailure(error62, { mode, source }) : { message: "Interrupted.", humanMessage: "Interrupted." }
|
|
34732
|
-
);
|
|
34733
|
-
process.exitCode = 130;
|
|
34734
|
-
return;
|
|
34735
|
-
}
|
|
34736
|
-
writeFailure(mode, "error", renderFailure(error62, { mode, source }));
|
|
34737
|
-
process.exitCode = 1;
|
|
34738
|
-
} finally {
|
|
34739
|
-
process.removeListener("SIGINT", onInterrupt);
|
|
34740
|
-
}
|
|
34741
|
-
}
|
|
34742
|
-
|
|
34743
|
-
// src/lib/workspace-scope.ts
|
|
34744
|
-
import { Option } from "commander";
|
|
34745
|
-
async function resolveWorkspaceScope(options) {
|
|
34746
|
-
if (options.workspace !== void 0) {
|
|
34747
|
-
return options.workspace;
|
|
34748
|
-
}
|
|
34749
|
-
if (options.personal) {
|
|
34750
|
-
return null;
|
|
34751
|
-
}
|
|
34752
|
-
return getDefaultWorkspaceId();
|
|
34753
|
-
}
|
|
34754
|
-
function addWorkspaceScopeOptions(command) {
|
|
34755
|
-
return command.addOption(
|
|
34756
|
-
new Option(
|
|
34757
|
-
"--workspace <workspace-id>",
|
|
34758
|
-
"Act on this workspace instead of your personal space"
|
|
34759
|
-
).conflicts("personal")
|
|
34760
|
-
).option("--personal", "Act on your personal space, ignoring any pinned workspace");
|
|
34761
|
-
}
|
|
34762
|
-
|
|
34763
34680
|
// ../../packages/sdk/dist/esm/chat-turn/connection-requirements.js
|
|
34764
34681
|
var CONNECTION_LOOKUP_CONCURRENCY = 4;
|
|
34765
34682
|
function deduplicateConnectionRequirements(requirements) {
|
|
@@ -35168,6 +35085,67 @@ function assertSelectionsUnambiguous(requirements, selections) {
|
|
|
35168
35085
|
|
|
35169
35086
|
// ../../packages/sdk/dist/esm/chat-turn/transport.js
|
|
35170
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
|
|
35171
35149
|
var ChatTurnTransport = class {
|
|
35172
35150
|
transport;
|
|
35173
35151
|
constructor({ token, baseUrl, headers, fetch: fetch2 }) {
|
|
@@ -35194,39 +35172,62 @@ var ChatTurnTransport = class {
|
|
|
35194
35172
|
}
|
|
35195
35173
|
/** Streams one server turn, reporting incremental text and deduplicated tool state changes. */
|
|
35196
35174
|
async streamTurn({ conversationId, workspaceId = null, message, signal, onText, onToolActivity }) {
|
|
35197
|
-
const
|
|
35198
|
-
|
|
35199
|
-
|
|
35200
|
-
|
|
35201
|
-
|
|
35202
|
-
|
|
35203
|
-
|
|
35204
|
-
|
|
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
|
+
}
|
|
35205
35191
|
let finalMessage;
|
|
35206
35192
|
let emittedText = "";
|
|
35207
35193
|
const emittedToolStates = /* @__PURE__ */ new Set();
|
|
35208
|
-
|
|
35209
|
-
|
|
35210
|
-
|
|
35211
|
-
|
|
35212
|
-
|
|
35213
|
-
|
|
35214
|
-
|
|
35215
|
-
|
|
35216
|
-
|
|
35217
|
-
|
|
35218
|
-
|
|
35219
|
-
|
|
35220
|
-
|
|
35221
|
-
|
|
35222
|
-
|
|
35223
|
-
|
|
35224
|
-
|
|
35225
|
-
|
|
35226
|
-
|
|
35227
|
-
|
|
35228
|
-
|
|
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
|
+
}
|
|
35229
35226
|
}
|
|
35227
|
+
} catch (error62) {
|
|
35228
|
+
if (callbackError)
|
|
35229
|
+
throw callbackError.error;
|
|
35230
|
+
throw fail("stream", error62);
|
|
35230
35231
|
}
|
|
35231
35232
|
if (!finalMessage) {
|
|
35232
35233
|
throw new Error("The Automations chat returned an empty response.");
|
|
@@ -35245,6 +35246,236 @@ function toToolActivity(part) {
|
|
|
35245
35246
|
};
|
|
35246
35247
|
}
|
|
35247
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
|
+
|
|
35248
35479
|
// src/commands/read/format.ts
|
|
35249
35480
|
function formatAutomationList(page) {
|
|
35250
35481
|
if (page.data.length === 0) return "No automations found.";
|
|
@@ -35456,8 +35687,35 @@ function describeVisibility(automation) {
|
|
|
35456
35687
|
function summarizeAutomationChange(result) {
|
|
35457
35688
|
return { ...result, automation: summarizeAutomation(result.automation) };
|
|
35458
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
|
+
};
|
|
35459
35697
|
function formatAutomationTrigger(result) {
|
|
35460
|
-
|
|
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");
|
|
35461
35719
|
}
|
|
35462
35720
|
function formatAutomationDelete(result) {
|
|
35463
35721
|
if (result.action === "delete_preview") {
|
|
@@ -35472,6 +35730,56 @@ function capitalize(value) {
|
|
|
35472
35730
|
return `${value.charAt(0).toUpperCase()}${value.slice(1)}`;
|
|
35473
35731
|
}
|
|
35474
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
|
+
|
|
35475
35783
|
// src/commands/automation/index.ts
|
|
35476
35784
|
function registerAutomationCommand(program) {
|
|
35477
35785
|
const automation = program.command("automation").description("Inspect and manage published automations");
|
|
@@ -35521,30 +35829,29 @@ function registerAutomationCommand(program) {
|
|
|
35521
35829
|
registerEnabledStateCommand(automation, "enable", true);
|
|
35522
35830
|
registerEnabledStateCommand(automation, "disable", false);
|
|
35523
35831
|
addWorkspaceScopeOptions(
|
|
35524
|
-
|
|
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
|
+
)
|
|
35525
35836
|
).action(async (automationId, options, command) => {
|
|
35526
35837
|
await runApiCommand(
|
|
35527
35838
|
command,
|
|
35528
|
-
async ({ api, signal }) => {
|
|
35529
|
-
const
|
|
35530
|
-
const
|
|
35531
|
-
|
|
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 })
|
|
35532
35847
|
});
|
|
35533
|
-
const triggerType = hasTriggerType(automation2.manifest) ? getManifestTriggers(automation2.manifest)[0]?.type : void 0;
|
|
35534
|
-
if (triggerType !== "schedule") {
|
|
35535
|
-
throw new Error(
|
|
35536
|
-
"This automation can only be triggered via its configured mechanism, not manually."
|
|
35537
|
-
);
|
|
35538
|
-
}
|
|
35539
|
-
if (workspaceId === null) {
|
|
35540
|
-
await api.automations.trigger(automationId, { signal });
|
|
35541
|
-
} else {
|
|
35542
|
-
await api.workspaceAutomations.trigger(workspaceId, automationId, { signal });
|
|
35543
|
-
}
|
|
35544
|
-
return { action: "triggered", automationId };
|
|
35545
35848
|
},
|
|
35546
|
-
{ human: formatAutomationTrigger },
|
|
35547
|
-
{
|
|
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
|
+
}
|
|
35548
35855
|
);
|
|
35549
35856
|
});
|
|
35550
35857
|
addWorkspaceScopeOptions(
|
|
@@ -35801,6 +36108,7 @@ function collectOption(value, previous) {
|
|
|
35801
36108
|
}
|
|
35802
36109
|
|
|
35803
36110
|
// src/commands/chat/chat-service.ts
|
|
36111
|
+
import { CliError as CliError6 } from "@doist/cli-core";
|
|
35804
36112
|
import { createInterface } from "node:readline/promises";
|
|
35805
36113
|
|
|
35806
36114
|
// src/commands/chat/connection-authorizer.ts
|
|
@@ -35815,6 +36123,13 @@ var FINAL_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
|
35815
36123
|
"session_status",
|
|
35816
36124
|
"turn_completed"
|
|
35817
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"]);
|
|
35818
36133
|
var SPINNER_TEXT = {
|
|
35819
36134
|
connection_authorization_opened: "Waiting for authorization...",
|
|
35820
36135
|
connection_authorization_started: "Waiting for authorization...",
|
|
@@ -35825,16 +36140,47 @@ var OutputWriter = class {
|
|
|
35825
36140
|
stream;
|
|
35826
36141
|
diagnosticStream;
|
|
35827
36142
|
events = [];
|
|
36143
|
+
streamText;
|
|
35828
36144
|
assistantLineOpen = false;
|
|
35829
36145
|
currentConversationId;
|
|
35830
|
-
|
|
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 } = {}) {
|
|
35831
36152
|
this.mode = mode;
|
|
35832
36153
|
this.stream = stream;
|
|
35833
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;
|
|
35834
36173
|
}
|
|
35835
36174
|
/** Records and renders an event; JSON mode retains structured output until `finish`. */
|
|
35836
36175
|
emit(event) {
|
|
35837
|
-
|
|
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
|
+
}
|
|
35838
36184
|
if (event.conversationId) this.currentConversationId = event.conversationId;
|
|
35839
36185
|
if (this.mode === "ndjson") {
|
|
35840
36186
|
this.stream.write(`${formatNdjson5([event])}
|
|
@@ -35850,17 +36196,28 @@ var OutputWriter = class {
|
|
|
35850
36196
|
get conversationId() {
|
|
35851
36197
|
return this.currentConversationId;
|
|
35852
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
|
+
}
|
|
35853
36208
|
/** Emits structured conversation status with human formatting owned by this output layer. */
|
|
35854
36209
|
emitConversationStatus(snapshot, pendingActionKind) {
|
|
35855
36210
|
this.emit({
|
|
35856
36211
|
type: "session_status",
|
|
35857
36212
|
conversationId: snapshot.conversation.id,
|
|
35858
36213
|
...snapshot,
|
|
35859
|
-
humanText: formatConversationStatus(snapshot, pendingActionKind)
|
|
36214
|
+
humanText: formatConversationStatus(snapshot, pendingActionKind),
|
|
36215
|
+
terminal: true
|
|
35860
36216
|
});
|
|
35861
36217
|
}
|
|
35862
36218
|
/** Finalizes output, including the accumulated event list in JSON mode. */
|
|
35863
36219
|
finish(summary) {
|
|
36220
|
+
this.flushAssistantMessage(false);
|
|
35864
36221
|
stopEarlySpinner();
|
|
35865
36222
|
if (this.mode === "json") {
|
|
35866
36223
|
this.stream.write(`${formatJson5({ ...summary, events: this.events })}
|
|
@@ -35872,6 +36229,17 @@ var OutputWriter = class {
|
|
|
35872
36229
|
this.assistantLineOpen = false;
|
|
35873
36230
|
}
|
|
35874
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
|
+
}
|
|
35875
36243
|
/**
|
|
35876
36244
|
* The agent goes quiet for long stretches between events, so a spinner
|
|
35877
36245
|
* fills the gap. Taking it down before every write keeps its next frame
|
|
@@ -36095,6 +36463,7 @@ function getMessageRoleLabel(role) {
|
|
|
36095
36463
|
|
|
36096
36464
|
// src/commands/chat/connection-authorizer.ts
|
|
36097
36465
|
var CONNECTION_POLL_INTERVAL_MS = 2e3;
|
|
36466
|
+
var CONNECTION_WAITING_REPORT_INTERVAL_MS = 15e3;
|
|
36098
36467
|
var CONNECTION_AUTHORIZATION_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
36099
36468
|
var ApiConnectionAuthorizer = class {
|
|
36100
36469
|
connections;
|
|
@@ -36186,6 +36555,7 @@ var ApiConnectionAuthorizer = class {
|
|
|
36186
36555
|
}
|
|
36187
36556
|
async waitUntilAuthorized(request2, connectionId, signal) {
|
|
36188
36557
|
const startedAt = Date.now();
|
|
36558
|
+
let reportedAt;
|
|
36189
36559
|
while (true) {
|
|
36190
36560
|
if (signal?.aborted) throw new Error("Connection authorization interrupted.");
|
|
36191
36561
|
const connection = await this.connections.get(connectionId, { signal });
|
|
@@ -36213,16 +36583,20 @@ var ApiConnectionAuthorizer = class {
|
|
|
36213
36583
|
`Timed out waiting for ${request2.serviceName} connection authorization.`
|
|
36214
36584
|
);
|
|
36215
36585
|
}
|
|
36216
|
-
|
|
36217
|
-
|
|
36218
|
-
|
|
36219
|
-
|
|
36220
|
-
|
|
36221
|
-
|
|
36222
|
-
|
|
36223
|
-
|
|
36224
|
-
|
|
36225
|
-
|
|
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
|
+
}
|
|
36226
36600
|
await wait(Math.min(this.pollIntervalMs, this.timeoutMs - elapsedMs), void 0, {
|
|
36227
36601
|
signal
|
|
36228
36602
|
});
|
|
@@ -36237,11 +36611,48 @@ function isSafeSetupUrl(value) {
|
|
|
36237
36611
|
}
|
|
36238
36612
|
}
|
|
36239
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
|
+
|
|
36240
36650
|
// src/commands/chat/conversation-turn-lock.ts
|
|
36241
36651
|
import { link, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
36242
36652
|
import { tmpdir } from "node:os";
|
|
36243
36653
|
import { join } from "node:path";
|
|
36244
36654
|
import { randomUUID } from "node:crypto";
|
|
36655
|
+
import { CliError as CliError5 } from "@doist/cli-core";
|
|
36245
36656
|
var ConversationTurnLock = class {
|
|
36246
36657
|
directory;
|
|
36247
36658
|
pid;
|
|
@@ -36278,7 +36689,8 @@ var ConversationTurnLock = class {
|
|
|
36278
36689
|
}
|
|
36279
36690
|
const current = await readLock(path);
|
|
36280
36691
|
if (current && this.isProcessRunning(current.pid)) {
|
|
36281
|
-
throw new
|
|
36692
|
+
throw new CliError5(
|
|
36693
|
+
"CHAT_TURN_ACTIVE",
|
|
36282
36694
|
`Conversation ${conversationId} already has an active turn in process ${current.pid}. Continue monitoring that process; do not invoke continue until it exits.`
|
|
36283
36695
|
);
|
|
36284
36696
|
}
|
|
@@ -36325,7 +36737,8 @@ function isNoSuchProcessError(error62) {
|
|
|
36325
36737
|
|
|
36326
36738
|
// src/commands/chat/chat-service.ts
|
|
36327
36739
|
var TURN_HEARTBEAT_INTERVAL_MS = 15e3;
|
|
36328
|
-
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.";
|
|
36329
36742
|
var ChatService = class {
|
|
36330
36743
|
api;
|
|
36331
36744
|
transport;
|
|
@@ -36335,6 +36748,8 @@ var ChatService = class {
|
|
|
36335
36748
|
turnLock;
|
|
36336
36749
|
heartbeatIntervalMs;
|
|
36337
36750
|
readline;
|
|
36751
|
+
/** The conversation a start or continue is working on, once it is known. */
|
|
36752
|
+
failureScope;
|
|
36338
36753
|
constructor({
|
|
36339
36754
|
token,
|
|
36340
36755
|
baseUrl,
|
|
@@ -36365,6 +36780,10 @@ var ChatService = class {
|
|
|
36365
36780
|
*/
|
|
36366
36781
|
async startConversation(message, signal, workspaceId = null) {
|
|
36367
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
|
+
};
|
|
36368
36787
|
this.output.emit({ type: "session_started", conversationId: conversation.id });
|
|
36369
36788
|
return this.withActiveTurn(
|
|
36370
36789
|
conversation.id,
|
|
@@ -36383,8 +36802,10 @@ var ChatService = class {
|
|
|
36383
36802
|
selections,
|
|
36384
36803
|
signal
|
|
36385
36804
|
}) {
|
|
36805
|
+
this.failureScope = { conversationId };
|
|
36386
36806
|
const conversation = await this.api.chat.getConversation(conversationId, { signal });
|
|
36387
36807
|
const scope = { conversationId, workspaceId: conversation.workspaceId };
|
|
36808
|
+
this.failureScope = scope;
|
|
36388
36809
|
return this.withActiveTurn(
|
|
36389
36810
|
conversationId,
|
|
36390
36811
|
async () => {
|
|
@@ -36397,7 +36818,8 @@ var ChatService = class {
|
|
|
36397
36818
|
} else {
|
|
36398
36819
|
const nextMessage = this.interactive ? await this.promptForMessage(signal) : "";
|
|
36399
36820
|
if (!nextMessage) {
|
|
36400
|
-
throw new
|
|
36821
|
+
throw new CliError6(
|
|
36822
|
+
"CHAT_INPUT_REQUIRED",
|
|
36401
36823
|
"A message or --connection selection is required to continue."
|
|
36402
36824
|
);
|
|
36403
36825
|
}
|
|
@@ -36503,6 +36925,7 @@ var ChatService = class {
|
|
|
36503
36925
|
}
|
|
36504
36926
|
}
|
|
36505
36927
|
async runTurn({ conversationId, workspaceId }, input2, signal, pendingAction, { markActivity }) {
|
|
36928
|
+
this.output.startTurn();
|
|
36506
36929
|
try {
|
|
36507
36930
|
return await runConversationTurn({
|
|
36508
36931
|
api: this.api,
|
|
@@ -36514,7 +36937,7 @@ var ChatService = class {
|
|
|
36514
36937
|
signal,
|
|
36515
36938
|
onText: (text2) => {
|
|
36516
36939
|
markActivity();
|
|
36517
|
-
this.output.
|
|
36940
|
+
this.output.appendAssistantText(conversationId, text2);
|
|
36518
36941
|
},
|
|
36519
36942
|
onToolActivity: (activity) => {
|
|
36520
36943
|
markActivity();
|
|
@@ -36536,7 +36959,7 @@ var ChatService = class {
|
|
|
36536
36959
|
});
|
|
36537
36960
|
} catch (error62) {
|
|
36538
36961
|
if (error62 instanceof ChatTurnInputError) {
|
|
36539
|
-
throw new
|
|
36962
|
+
throw new CliError6(`CHAT_${error62.code.toUpperCase()}`, describeInputError(error62));
|
|
36540
36963
|
}
|
|
36541
36964
|
throw error62;
|
|
36542
36965
|
}
|
|
@@ -36560,13 +36983,17 @@ var ChatService = class {
|
|
|
36560
36983
|
terminal: false
|
|
36561
36984
|
});
|
|
36562
36985
|
}
|
|
36563
|
-
completeTurn(
|
|
36986
|
+
completeTurn(turn) {
|
|
36987
|
+
const summary = { ...turn, assistantText: this.output.assistantText || turn.assistantText };
|
|
36564
36988
|
const resumeCommand = getResumeCommand(summary);
|
|
36565
36989
|
this.output.emit({
|
|
36566
36990
|
type: "turn_completed",
|
|
36567
36991
|
conversationId: summary.conversationId,
|
|
36568
36992
|
status: summary.status,
|
|
36569
36993
|
outcome: summary.status,
|
|
36994
|
+
assistantText: summary.assistantText,
|
|
36995
|
+
...summary.automationId ? { automationId: summary.automationId } : {},
|
|
36996
|
+
...summary.action ? { action: summary.action } : {},
|
|
36570
36997
|
state: "completed",
|
|
36571
36998
|
terminal: true,
|
|
36572
36999
|
...summary.conversationArchived !== void 0 ? { conversationArchived: summary.conversationArchived } : {},
|
|
@@ -36607,6 +37034,32 @@ var ChatService = class {
|
|
|
36607
37034
|
await release();
|
|
36608
37035
|
}
|
|
36609
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
|
+
}
|
|
36610
37063
|
async getPersistedPendingAction(conversationId, signal) {
|
|
36611
37064
|
const messages = await this.api.chat.getMessages(conversationId, { signal });
|
|
36612
37065
|
return getPendingActionFromMessages(messages);
|
|
@@ -36693,21 +37146,25 @@ function getResumeCommand(summary) {
|
|
|
36693
37146
|
if (typeof actionResume === "string") {
|
|
36694
37147
|
return actionResume.replace("<conversation-id>", summary.conversationId);
|
|
36695
37148
|
}
|
|
36696
|
-
return
|
|
37149
|
+
return messageResumeCommand(summary.conversationId);
|
|
36697
37150
|
}
|
|
36698
37151
|
|
|
36699
37152
|
// src/commands/chat/index.ts
|
|
36700
37153
|
function registerChatCommand(program) {
|
|
36701
37154
|
const chat = program.command("chat").description("Talk to the Todoist Automations agent");
|
|
36702
37155
|
addWorkspaceScopeOptions(
|
|
36703
|
-
|
|
37156
|
+
addStreamTextOption(
|
|
37157
|
+
chat.command("start").description("Start a new conversation").argument("<message...>", "Initial message")
|
|
37158
|
+
)
|
|
36704
37159
|
).action(async (messageParts, options, command) => {
|
|
36705
37160
|
await runCommand(command, async ({ service, signal }) => {
|
|
36706
37161
|
const workspaceId = await resolveWorkspaceScope(options);
|
|
36707
37162
|
return service.startConversation(messageParts.join(" "), signal, workspaceId);
|
|
36708
37163
|
});
|
|
36709
37164
|
});
|
|
36710
|
-
|
|
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(
|
|
36711
37168
|
"--connection <connector[:type]=connection-id>",
|
|
36712
37169
|
"Resolve a pending connection selection; repeat for multiple connectors",
|
|
36713
37170
|
collectOption,
|
|
@@ -36732,11 +37189,19 @@ function registerChatCommand(program) {
|
|
|
36732
37189
|
);
|
|
36733
37190
|
});
|
|
36734
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
|
+
}
|
|
36735
37198
|
async function runCommand(command, operation) {
|
|
36736
37199
|
let service;
|
|
36737
37200
|
let source;
|
|
36738
37201
|
await runStreamingCommand(command, {
|
|
36739
|
-
createOutput: (mode) => new OutputWriter(mode
|
|
37202
|
+
createOutput: (mode) => new OutputWriter(mode, process.stdout, process.stderr, {
|
|
37203
|
+
streamText: command.opts().streamText === true
|
|
37204
|
+
}),
|
|
36740
37205
|
operation: async ({ options, mode, output: output2, signal }) => {
|
|
36741
37206
|
const apiConfiguration = await withSpinner(
|
|
36742
37207
|
{ text: "Connecting..." },
|
|
@@ -36759,33 +37224,33 @@ async function runCommand(command, operation) {
|
|
|
36759
37224
|
onSuccess: (result, { output: output2 }) => output2.finish(result),
|
|
36760
37225
|
onInterrupted: ({ output: output2 }) => {
|
|
36761
37226
|
const conversationId = output2.conversationId;
|
|
36762
|
-
|
|
37227
|
+
const resume = conversationId ? { resumeCommand: messageResumeCommand(conversationId) } : {};
|
|
36763
37228
|
output2.emit({
|
|
36764
|
-
type: "
|
|
37229
|
+
type: "interrupted",
|
|
36765
37230
|
conversationId,
|
|
36766
37231
|
status: "interrupted",
|
|
36767
|
-
|
|
36768
|
-
|
|
36769
|
-
terminal: true
|
|
36770
|
-
...conversationId ? { resumeCommand: `tda chat continue ${conversationId} "<message>"` } : {}
|
|
37232
|
+
message: "Interrupted.",
|
|
37233
|
+
...resume,
|
|
37234
|
+
terminal: true
|
|
36771
37235
|
});
|
|
36772
37236
|
output2.finish({
|
|
36773
37237
|
conversationId,
|
|
36774
37238
|
status: "interrupted",
|
|
36775
|
-
assistantText:
|
|
37239
|
+
assistantText: output2.assistantText,
|
|
37240
|
+
...resume
|
|
36776
37241
|
});
|
|
36777
37242
|
},
|
|
36778
|
-
onError: (error62, { mode, output: output2 }) => {
|
|
37243
|
+
onError: async (error62, { mode, output: output2 }) => {
|
|
36779
37244
|
const failure2 = renderFailure(error62, { mode, source });
|
|
36780
|
-
const
|
|
36781
|
-
|
|
36782
|
-
|
|
36783
|
-
|
|
36784
|
-
|
|
36785
|
-
|
|
36786
|
-
|
|
36787
|
-
});
|
|
36788
|
-
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);
|
|
36789
37254
|
},
|
|
36790
37255
|
onFinally: () => {
|
|
36791
37256
|
stopEarlySpinner();
|
|
@@ -36971,7 +37436,12 @@ async function runAuthorizationCommand(command, operation) {
|
|
|
36971
37436
|
onSuccess: (result, { mode, output: output2 }) => {
|
|
36972
37437
|
if (mode === "json") return output2.finish(result);
|
|
36973
37438
|
stopEarlySpinner();
|
|
36974
|
-
writeResult(mode, result, {
|
|
37439
|
+
writeResult(mode, result, {
|
|
37440
|
+
human: formatConnectionChange,
|
|
37441
|
+
ndjson: (completed) => [
|
|
37442
|
+
{ type: "connection_completed", ...completed, terminal: true }
|
|
37443
|
+
]
|
|
37444
|
+
});
|
|
36975
37445
|
},
|
|
36976
37446
|
onInterrupted: ({ output: output2 }) => {
|
|
36977
37447
|
output2.emit({
|
|
@@ -36985,14 +37455,9 @@ async function runAuthorizationCommand(command, operation) {
|
|
|
36985
37455
|
onError: (error62, { mode, output: output2 }) => {
|
|
36986
37456
|
const failure2 = renderFailure(error62, { mode, source });
|
|
36987
37457
|
const message = mode === "human" ? failure2.humanMessage : failure2.message;
|
|
36988
|
-
|
|
36989
|
-
|
|
36990
|
-
|
|
36991
|
-
message,
|
|
36992
|
-
...failure2.code ? { code: failure2.code } : {},
|
|
36993
|
-
terminal: true
|
|
36994
|
-
});
|
|
36995
|
-
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);
|
|
36996
37461
|
},
|
|
36997
37462
|
onFinally: () => stopEarlySpinner()
|
|
36998
37463
|
});
|
|
@@ -37365,7 +37830,7 @@ function registerConnectorCommand(program) {
|
|
|
37365
37830
|
}
|
|
37366
37831
|
|
|
37367
37832
|
// src/lib/manifest-input.ts
|
|
37368
|
-
import { CliError as
|
|
37833
|
+
import { CliError as CliError7, getErrorMessage as getErrorMessage7 } from "@doist/cli-core";
|
|
37369
37834
|
import { Option as Option6 } from "commander";
|
|
37370
37835
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
37371
37836
|
import { text } from "node:stream/consumers";
|
|
@@ -37379,7 +37844,7 @@ async function readManifestInput(options, input2 = process.stdin) {
|
|
|
37379
37844
|
try {
|
|
37380
37845
|
return JSON.parse(source.content);
|
|
37381
37846
|
} catch (error62) {
|
|
37382
|
-
throw new
|
|
37847
|
+
throw new CliError7(
|
|
37383
37848
|
"MANIFEST_UNREADABLE",
|
|
37384
37849
|
`${source.label} is not valid JSON: ${getErrorMessage7(error62)}`
|
|
37385
37850
|
);
|
|
@@ -37390,7 +37855,7 @@ async function readSource(options, input2) {
|
|
|
37390
37855
|
try {
|
|
37391
37856
|
return { label: options.file, content: await readFile2(options.file, "utf8") };
|
|
37392
37857
|
} catch (error62) {
|
|
37393
|
-
throw new
|
|
37858
|
+
throw new CliError7(
|
|
37394
37859
|
"MANIFEST_UNREADABLE",
|
|
37395
37860
|
`Could not read ${options.file}: ${getErrorMessage7(error62)}`
|
|
37396
37861
|
);
|
|
@@ -37400,17 +37865,17 @@ async function readSource(options, input2) {
|
|
|
37400
37865
|
try {
|
|
37401
37866
|
return { label: "Standard input", content: await text(input2) };
|
|
37402
37867
|
} catch (error62) {
|
|
37403
|
-
throw new
|
|
37868
|
+
throw new CliError7(
|
|
37404
37869
|
"MANIFEST_UNREADABLE",
|
|
37405
37870
|
`Could not read standard input: ${getErrorMessage7(error62)}`
|
|
37406
37871
|
);
|
|
37407
37872
|
}
|
|
37408
37873
|
}
|
|
37409
|
-
throw new
|
|
37874
|
+
throw new CliError7("MANIFEST_UNREADABLE", "Provide the manifest with --file <path> or --stdin.");
|
|
37410
37875
|
}
|
|
37411
37876
|
|
|
37412
37877
|
// src/commands/draft/draft-service.ts
|
|
37413
|
-
import { CliError as
|
|
37878
|
+
import { CliError as CliError8, getErrorMessage as getErrorMessage8 } from "@doist/cli-core";
|
|
37414
37879
|
import { readFile as readFile3 } from "node:fs/promises";
|
|
37415
37880
|
function createDraft2(draftApi, input2) {
|
|
37416
37881
|
return withRecoveryHints(draftApi, createDraft(draftApi, input2));
|
|
@@ -37443,13 +37908,13 @@ async function withRecoveryHints(draftApi, operation) {
|
|
|
37443
37908
|
const draftId = error62.draftId ?? "<draft-id>";
|
|
37444
37909
|
switch (error62.code) {
|
|
37445
37910
|
case "manifest_invalid":
|
|
37446
|
-
throw new
|
|
37911
|
+
throw new CliError8("MANIFEST_INVALID", error62.message);
|
|
37447
37912
|
case "draft_conflict":
|
|
37448
|
-
throw new
|
|
37913
|
+
throw new CliError8("DRAFT_CONFLICT", error62.message, {
|
|
37449
37914
|
hints: draftHints(scope, draftId)
|
|
37450
37915
|
});
|
|
37451
37916
|
case "draft_manifest_rejected":
|
|
37452
|
-
throw new
|
|
37917
|
+
throw new CliError8("DRAFT_MANIFEST_REJECTED", error62.message, {
|
|
37453
37918
|
hints: draftHints(scope, draftId)
|
|
37454
37919
|
});
|
|
37455
37920
|
case "draft_not_ready":
|
|
@@ -37467,14 +37932,12 @@ function draftHints(scope, draftId) {
|
|
|
37467
37932
|
`Or discard it first: tda draft delete ${draftId} --yes ${scopeFlag(scope)}`
|
|
37468
37933
|
];
|
|
37469
37934
|
}
|
|
37470
|
-
function scopeFlag(scope) {
|
|
37471
|
-
return scope === null ? "--personal" : `--workspace ${scope}`;
|
|
37472
|
-
}
|
|
37473
37935
|
async function startDraftTest2(draftApi, {
|
|
37474
37936
|
draftId,
|
|
37475
37937
|
request: request2,
|
|
37476
37938
|
wait: waitFor,
|
|
37477
|
-
signal
|
|
37939
|
+
signal,
|
|
37940
|
+
onProgress
|
|
37478
37941
|
}) {
|
|
37479
37942
|
let started;
|
|
37480
37943
|
try {
|
|
@@ -37496,7 +37959,11 @@ async function startDraftTest2(draftApi, {
|
|
|
37496
37959
|
}
|
|
37497
37960
|
let finished;
|
|
37498
37961
|
try {
|
|
37499
|
-
finished = await waitForDraftTest(draftApi, started, {
|
|
37962
|
+
finished = await waitForDraftTest(draftApi, started, {
|
|
37963
|
+
...waitFor,
|
|
37964
|
+
signal,
|
|
37965
|
+
...onProgress ? { onPoll: reportRunProgress(onProgress) } : {}
|
|
37966
|
+
});
|
|
37500
37967
|
} catch (error62) {
|
|
37501
37968
|
throw toDraftTestWaitError(error62, identity, signal);
|
|
37502
37969
|
}
|
|
@@ -37514,13 +37981,13 @@ function toDraftTestWaitError(error62, identity, signal) {
|
|
|
37514
37981
|
const run = `test run ${identity.testRunId} of draft ${identity.draftId}`;
|
|
37515
37982
|
const hints = [`See how it ends: ${identity.command}`];
|
|
37516
37983
|
if (signal.aborted) {
|
|
37517
|
-
return new
|
|
37984
|
+
return new CliError8(
|
|
37518
37985
|
"DRAFT_TEST_WAIT_INTERRUPTED",
|
|
37519
37986
|
`Stopped waiting for ${run}. It keeps running.`,
|
|
37520
37987
|
{ hints }
|
|
37521
37988
|
);
|
|
37522
37989
|
}
|
|
37523
|
-
return new
|
|
37990
|
+
return new CliError8(
|
|
37524
37991
|
"DRAFT_TEST_WAIT_FAILED",
|
|
37525
37992
|
`Checking on ${run} failed: ${getErrorMessage8(error62).replace(/\.?$/, ".")} It keeps running.`,
|
|
37526
37993
|
{ hints }
|
|
@@ -37528,7 +37995,7 @@ function toDraftTestWaitError(error62, identity, signal) {
|
|
|
37528
37995
|
}
|
|
37529
37996
|
function draftNotReadyError(message, draftId, scope) {
|
|
37530
37997
|
const flag = scopeFlag(scope);
|
|
37531
|
-
return new
|
|
37998
|
+
return new CliError8("DRAFT_NOT_READY", message, {
|
|
37532
37999
|
hints: [
|
|
37533
38000
|
`See the issues: tda draft view ${draftId} ${flag}`,
|
|
37534
38001
|
`Bind connections: tda draft connect ${draftId} --auto ${flag}`
|
|
@@ -37542,13 +38009,13 @@ function toDraftTestCliError(refusal, draftId, scope) {
|
|
|
37542
38009
|
return draftNotReadyError(refusal.message, draftId, scope);
|
|
37543
38010
|
case "draft_test_payload_required":
|
|
37544
38011
|
case "draft_test_payload_filtered":
|
|
37545
|
-
return new
|
|
38012
|
+
return new CliError8(refusal.code.toUpperCase(), refusal.message, {
|
|
37546
38013
|
hints: [
|
|
37547
38014
|
`Test with a real event: tda draft test ${draftId} --trigger <event> --payload-file <path> ${flag}`
|
|
37548
38015
|
]
|
|
37549
38016
|
});
|
|
37550
38017
|
default:
|
|
37551
|
-
return new
|
|
38018
|
+
return new CliError8(refusal.code.toUpperCase(), refusal.message);
|
|
37552
38019
|
}
|
|
37553
38020
|
}
|
|
37554
38021
|
async function readTestPayloadFile(path) {
|
|
@@ -37556,7 +38023,7 @@ async function readTestPayloadFile(path) {
|
|
|
37556
38023
|
try {
|
|
37557
38024
|
content = await readFile3(path, "utf8");
|
|
37558
38025
|
} catch (error62) {
|
|
37559
|
-
throw new
|
|
38026
|
+
throw new CliError8(
|
|
37560
38027
|
"PAYLOAD_UNREADABLE",
|
|
37561
38028
|
`Could not read ${path}: ${getErrorMessage8(error62)}`
|
|
37562
38029
|
);
|
|
@@ -37564,7 +38031,7 @@ async function readTestPayloadFile(path) {
|
|
|
37564
38031
|
try {
|
|
37565
38032
|
return JSON.parse(content);
|
|
37566
38033
|
} catch (error62) {
|
|
37567
|
-
throw new
|
|
38034
|
+
throw new CliError8(
|
|
37568
38035
|
"PAYLOAD_UNREADABLE",
|
|
37569
38036
|
`${path} is not valid JSON: ${getErrorMessage8(error62)}`
|
|
37570
38037
|
);
|
|
@@ -37753,7 +38220,7 @@ function formatDraftTest(result) {
|
|
|
37753
38220
|
}
|
|
37754
38221
|
|
|
37755
38222
|
// src/commands/draft/validate-command.ts
|
|
37756
|
-
import { CliError as
|
|
38223
|
+
import { CliError as CliError9 } from "@doist/cli-core";
|
|
37757
38224
|
|
|
37758
38225
|
// src/lib/local-command.ts
|
|
37759
38226
|
async function runLocalCommand(command, operation, presentation, execution = {}) {
|
|
@@ -37785,7 +38252,7 @@ function registerDraftValidateCommand(draft) {
|
|
|
37785
38252
|
function validateManifestInput(manifest) {
|
|
37786
38253
|
const validation = validateManifest(manifest);
|
|
37787
38254
|
if (!validation.isValid) {
|
|
37788
|
-
throw new
|
|
38255
|
+
throw new CliError9(
|
|
37789
38256
|
"MANIFEST_INVALID",
|
|
37790
38257
|
`Manifest is invalid: ${validation.errors.join("; ")}`
|
|
37791
38258
|
);
|
|
@@ -37805,8 +38272,6 @@ function formatManifestValidation(result) {
|
|
|
37805
38272
|
}
|
|
37806
38273
|
|
|
37807
38274
|
// src/commands/draft/index.ts
|
|
37808
|
-
var DEFAULT_TEST_WAIT = "10m";
|
|
37809
|
-
var TOKEN_LIFETIME_MARGIN_MS = 2 * 60 * 1e3;
|
|
37810
38275
|
function registerDraftCommand(program) {
|
|
37811
38276
|
const draft = program.command("draft").description("Author, test and inspect automation drafts");
|
|
37812
38277
|
registerDraftValidateCommand(draft);
|
|
@@ -37933,9 +38398,9 @@ function registerDraftCommand(program) {
|
|
|
37933
38398
|
);
|
|
37934
38399
|
});
|
|
37935
38400
|
addWorkspaceScopeOptions(
|
|
37936
|
-
|
|
37937
|
-
"
|
|
37938
|
-
|
|
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"
|
|
37939
38404
|
).option(
|
|
37940
38405
|
"--trigger <index|event>",
|
|
37941
38406
|
"The trigger to test: its position in the manifest's triggers, or a webhook event"
|
|
@@ -37946,11 +38411,8 @@ function registerDraftCommand(program) {
|
|
|
37946
38411
|
).action(async (draftId, options, command) => {
|
|
37947
38412
|
await runApiCommand(
|
|
37948
38413
|
command,
|
|
37949
|
-
async ({ api, signal }) => {
|
|
37950
|
-
|
|
37951
|
-
throw new Error("--timeout only applies with --wait.");
|
|
37952
|
-
}
|
|
37953
|
-
const timeoutMs = parseTestTimeout(options.timeout ?? DEFAULT_TEST_WAIT);
|
|
38414
|
+
async ({ api, signal, progress }) => {
|
|
38415
|
+
const wait2 = resolveWait(options);
|
|
37954
38416
|
const draftApi = bindDraftApi(api, await resolveWorkspaceScope(options), { signal });
|
|
37955
38417
|
return startDraftTest2(draftApi, {
|
|
37956
38418
|
draftId,
|
|
@@ -37958,14 +38420,15 @@ function registerDraftCommand(program) {
|
|
|
37958
38420
|
...options.trigger !== void 0 ? { trigger: toDraftTestTriggerSelector(options.trigger) } : {},
|
|
37959
38421
|
...options.payloadFile !== void 0 ? { payload: await readTestPayloadFile(options.payloadFile) } : {}
|
|
37960
38422
|
},
|
|
37961
|
-
wait:
|
|
37962
|
-
signal
|
|
38423
|
+
wait: wait2,
|
|
38424
|
+
signal,
|
|
38425
|
+
onProgress: (state) => progress({ type: "test_progress", draftId, ...state })
|
|
37963
38426
|
});
|
|
37964
38427
|
},
|
|
37965
|
-
{ human: formatDraftTest },
|
|
38428
|
+
{ human: formatDraftTest, ndjson: toTerminalRecords },
|
|
37966
38429
|
{
|
|
37967
38430
|
spinner: options.wait === true ? "Testing draft..." : "Starting test run...",
|
|
37968
|
-
minimumTokenLifetimeMs:
|
|
38431
|
+
minimumTokenLifetimeMs: waitTokenLifetimeMs(options),
|
|
37969
38432
|
exitCode: (result) => result.action === "test_finished" && result.outcome !== "passed" ? 1 : void 0
|
|
37970
38433
|
}
|
|
37971
38434
|
);
|
|
@@ -38025,20 +38488,6 @@ function streamDraftList(options, fetchPage) {
|
|
|
38025
38488
|
async (args) => summarizeDraftListPage(await fetchPage(args))
|
|
38026
38489
|
);
|
|
38027
38490
|
}
|
|
38028
|
-
function tryParseTestTimeout(value) {
|
|
38029
|
-
try {
|
|
38030
|
-
return durationStringToMilliseconds(value);
|
|
38031
|
-
} catch {
|
|
38032
|
-
return 0;
|
|
38033
|
-
}
|
|
38034
|
-
}
|
|
38035
|
-
function parseTestTimeout(value) {
|
|
38036
|
-
try {
|
|
38037
|
-
return durationStringToMilliseconds(value);
|
|
38038
|
-
} catch {
|
|
38039
|
-
throw new Error(`Invalid --timeout ${value}; use a duration such as 90s, 15m or 1h.`);
|
|
38040
|
-
}
|
|
38041
|
-
}
|
|
38042
38491
|
|
|
38043
38492
|
// src/commands/run/index.ts
|
|
38044
38493
|
import { Option as Option7 } from "commander";
|
|
@@ -38134,11 +38583,11 @@ function registerRunCommand(program) {
|
|
|
38134
38583
|
}
|
|
38135
38584
|
|
|
38136
38585
|
// src/commands/skill/shared.ts
|
|
38137
|
-
import { CliError as
|
|
38586
|
+
import { CliError as CliError10 } from "@doist/cli-core";
|
|
38138
38587
|
function requireInstaller(agent) {
|
|
38139
38588
|
const installer = getInstaller(agent);
|
|
38140
38589
|
if (!installer) {
|
|
38141
|
-
throw new
|
|
38590
|
+
throw new CliError10("UNKNOWN_AGENT", `Unknown agent: ${agent}.`, {
|
|
38142
38591
|
hints: [`Available agents: ${AGENT_NAMES.join(", ")}.`]
|
|
38143
38592
|
});
|
|
38144
38593
|
}
|
|
@@ -38216,7 +38665,7 @@ async function uninstallSkill(agent, options, command) {
|
|
|
38216
38665
|
}
|
|
38217
38666
|
|
|
38218
38667
|
// src/commands/skill/update.ts
|
|
38219
|
-
import { CliError as
|
|
38668
|
+
import { CliError as CliError11 } from "@doist/cli-core";
|
|
38220
38669
|
async function updateSkill(agent, options, command) {
|
|
38221
38670
|
await runLocalCommand(
|
|
38222
38671
|
command,
|
|
@@ -38242,7 +38691,7 @@ async function updateAllSkills(options, command) {
|
|
|
38242
38691
|
async () => {
|
|
38243
38692
|
const result = await updateAllInstalledSkills(options.local === true);
|
|
38244
38693
|
if (result.errors.length > 0) {
|
|
38245
|
-
throw new
|
|
38694
|
+
throw new CliError11(
|
|
38246
38695
|
"UPDATE_FAILED",
|
|
38247
38696
|
`Could not update ${result.errors.length} skill file(s).`,
|
|
38248
38697
|
{ hints: result.errors.map((error62) => `${error62.agent}: ${error62.message}`) }
|
|
@@ -38302,7 +38751,7 @@ function registerUpdateCommand(program) {
|
|
|
38302
38751
|
}
|
|
38303
38752
|
|
|
38304
38753
|
// src/commands/workspace/index.ts
|
|
38305
|
-
import { CliError as
|
|
38754
|
+
import { CliError as CliError12 } from "@doist/cli-core";
|
|
38306
38755
|
|
|
38307
38756
|
// src/commands/workspace/format.ts
|
|
38308
38757
|
function formatWorkspaceList(workspaces) {
|
|
@@ -38345,7 +38794,7 @@ function registerWorkspaceCommand(program) {
|
|
|
38345
38794
|
if (!(error62 instanceof AutomationsRequestError) || error62.httpStatusCode !== 404) {
|
|
38346
38795
|
throw error62;
|
|
38347
38796
|
}
|
|
38348
|
-
throw new
|
|
38797
|
+
throw new CliError12(
|
|
38349
38798
|
"WORKSPACE_ACCESS_UNAVAILABLE",
|
|
38350
38799
|
"Workspace access is not available for this account or environment.",
|
|
38351
38800
|
{
|