@doist/automations-cli 1.22.1 → 1.22.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  collectSkillStatus,
8
8
  getInstaller,
9
9
  updateAllInstalledSkills
10
- } from "./chunk-NB7HIIJZ.js";
10
+ } from "./chunk-DECO7WSK.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.22.1",
6902
+ version: "1.22.3",
6903
6903
  description: "Command-line client for Todoist Automations",
6904
6904
  type: "module",
6905
6905
  bin: {
@@ -6933,7 +6933,7 @@ var package_default = {
6933
6933
  build: "node scripts/build.mjs && node scripts/refresh-skills.mjs",
6934
6934
  dev: "node scripts/build.mjs --watch",
6935
6935
  "type-check": "tsc -p tsconfig.test.json --noEmit",
6936
- test: "vitest run",
6936
+ test: "vitest run && node --test '../../release/*.test.mjs'",
6937
6937
  check: "oxfmt src scripts --check && oxlint src scripts && npm run check:skill-sync",
6938
6938
  "check:fix": "oxfmt src scripts && oxlint src scripts --fix",
6939
6939
  "sync:skill": "node scripts/sync-skill.mjs",
@@ -6957,7 +6957,6 @@ var package_default = {
6957
6957
  "conventional-changelog-conventionalcommits": "9.3.1",
6958
6958
  esbuild: "0.28.2",
6959
6959
  "semantic-release": "25.0.9",
6960
- "semantic-release-monorepo": "8.0.2",
6961
6960
  typescript: "6.0.3",
6962
6961
  vitest: "5.0.5"
6963
6962
  },
@@ -34696,6 +34695,491 @@ function addWorkspaceScopeOptions(command) {
34696
34695
  ).option("--personal", "Act on your personal space, ignoring any pinned workspace");
34697
34696
  }
34698
34697
 
34698
+ // ../../packages/sdk/dist/esm/chat-turn/connection-requirements.js
34699
+ var CONNECTION_LOOKUP_CONCURRENCY = 4;
34700
+ function deduplicateConnectionRequirements(requirements) {
34701
+ const seen = /* @__PURE__ */ new Set();
34702
+ return requirements.filter((requirement) => {
34703
+ const key = JSON.stringify([requirement.connectorId, requirement.connectionType]);
34704
+ if (seen.has(key))
34705
+ return false;
34706
+ seen.add(key);
34707
+ return true;
34708
+ });
34709
+ }
34710
+ async function loadConnectionRequirements(connections, requirements, options) {
34711
+ return mapWithConcurrency(deduplicateConnectionRequirements(requirements), CONNECTION_LOOKUP_CONCURRENCY, async (requirement) => {
34712
+ const response = await connections.list({
34713
+ connectorId: requirement.connectorId,
34714
+ capability: requirement.connectionType,
34715
+ status: "authorized"
34716
+ }, options);
34717
+ return {
34718
+ ...requirement,
34719
+ availableConnections: response.connections.map((connection) => ({
34720
+ id: connection.id,
34721
+ label: connection.accountDisplayLabel ?? connection.name ?? connection.id
34722
+ }))
34723
+ };
34724
+ });
34725
+ }
34726
+ async function mapWithConcurrency(items, concurrency, map2) {
34727
+ const results = new Array(items.length);
34728
+ let next = 0;
34729
+ const workers = Array.from({ length: Math.min(concurrency, items.length) }, async () => {
34730
+ while (next < items.length) {
34731
+ const index = next;
34732
+ next += 1;
34733
+ results[index] = await map2(items[index]);
34734
+ }
34735
+ });
34736
+ await Promise.all(workers);
34737
+ return results;
34738
+ }
34739
+
34740
+ // ../../packages/sdk/dist/esm/chat-turn/connection-setup.js
34741
+ async function startConnectionSetup(connections, request2, options) {
34742
+ const created = await connections.create({
34743
+ connectorId: request2.connectorId,
34744
+ capability: request2.capability,
34745
+ ...request2.automationIds?.length ? { body: { automationIds: [...request2.automationIds] } } : {}
34746
+ }, options);
34747
+ return { connectionId: created.connectionId, setup: created.setup, resumed: false };
34748
+ }
34749
+ function summarizeConnectionSetup(setup) {
34750
+ if (setup?.kind === "oauth") {
34751
+ return { setupKind: "oauth", authorizationUrl: setup.authorizationUrl };
34752
+ }
34753
+ if (setup?.kind === "managed") {
34754
+ return {
34755
+ setupKind: "managed",
34756
+ instructions: setup.instructions,
34757
+ code: setup.code,
34758
+ deepLinkUrl: setup.deepLinkUrl,
34759
+ expiresAt: setup.expiresAt
34760
+ };
34761
+ }
34762
+ return { setupKind: "none" };
34763
+ }
34764
+ function getConnectionSetupUrl(setup) {
34765
+ if (setup?.kind === "oauth")
34766
+ return setup.authorizationUrl;
34767
+ if (setup?.kind === "managed")
34768
+ return setup.deepLinkUrl;
34769
+ return null;
34770
+ }
34771
+
34772
+ // ../../packages/core/dist/esm/chat-tools/client-tool-result.js
34773
+ var clientToolContinuationPartSchema = external_exports.discriminatedUnion("type", [
34774
+ toolPromptEnablePushResultSchema.omit({ input: true }).strict(),
34775
+ toolSelectConnectionsResultSchema.omit({ input: true }).strict()
34776
+ ]);
34777
+
34778
+ // ../../packages/core/dist/esm/chat-tools/update-memory.js
34779
+ var UpdateMemoryInputSchema = external_exports.object({
34780
+ append: external_exports.string().min(1).optional(),
34781
+ replace: external_exports.object({
34782
+ old: external_exports.string().min(1),
34783
+ new: external_exports.string()
34784
+ }).optional()
34785
+ });
34786
+ var UpdateMemoryOutputSchema = external_exports.object({
34787
+ memory: external_exports.string().nullable(),
34788
+ // What the memory held before this write — the chat UI's Undo restores it.
34789
+ previousMemory: external_exports.string().nullable()
34790
+ });
34791
+ var toolUpdateMemorySchema = external_exports.object({
34792
+ type: external_exports.literal("tool-update-memory"),
34793
+ toolCallId: external_exports.string().min(1),
34794
+ state: external_exports.literal("input-available"),
34795
+ input: UpdateMemoryInputSchema
34796
+ });
34797
+ var toolUpdateMemoryResultSchema = external_exports.object({
34798
+ type: external_exports.literal("tool-update-memory"),
34799
+ toolCallId: external_exports.string().min(1),
34800
+ state: external_exports.literal("output-available"),
34801
+ input: UpdateMemoryInputSchema,
34802
+ output: UpdateMemoryOutputSchema
34803
+ });
34804
+
34805
+ // ../../packages/sdk/dist/esm/chat-turn/messages.js
34806
+ var persistedToolPartSchema = external_exports.object({
34807
+ type: external_exports.string().trim().min(6).refine((type) => type.startsWith("tool-")),
34808
+ toolCallId: external_exports.string().trim().min(1),
34809
+ state: external_exports.enum(["input-streaming", "input-available", "output-available", "output-error"])
34810
+ }).passthrough();
34811
+ function createUserMessage(text2) {
34812
+ return {
34813
+ id: globalThis.crypto.randomUUID(),
34814
+ role: "user",
34815
+ parts: [{ type: "text", text: text2 }]
34816
+ };
34817
+ }
34818
+ function toUiMessage(message) {
34819
+ if (message.role !== "user" && message.role !== "assistant" && message.role !== "system") {
34820
+ throw new Error(`Unsupported persisted chat role: ${message.role}`);
34821
+ }
34822
+ if (!Array.isArray(message.parts)) {
34823
+ throw new Error(`Persisted chat message ${message.messageId} has invalid parts.`);
34824
+ }
34825
+ return {
34826
+ id: message.messageId,
34827
+ role: message.role,
34828
+ parts: normalizePersistedMessageParts(message.parts)
34829
+ };
34830
+ }
34831
+ function normalizePersistedMessageParts(parts) {
34832
+ return parts.filter((part) => isTextPart(part) || isStepStartPart(part) || isReasoningPart(part) || persistedToolPartSchema.safeParse(part).success);
34833
+ }
34834
+ function isTextPart(part) {
34835
+ if (typeof part !== "object" || part === null) {
34836
+ return false;
34837
+ }
34838
+ const candidate = part;
34839
+ return candidate.type === "text" && typeof candidate.text === "string";
34840
+ }
34841
+ function isStepStartPart(part) {
34842
+ if (typeof part !== "object" || part === null) {
34843
+ return false;
34844
+ }
34845
+ return part.type === "step-start";
34846
+ }
34847
+ function isReasoningPart(part) {
34848
+ if (typeof part !== "object" || part === null) {
34849
+ return false;
34850
+ }
34851
+ const candidate = part;
34852
+ return candidate.type === "reasoning" && typeof candidate.text === "string" && (candidate.state === void 0 || candidate.state === "streaming" || candidate.state === "done");
34853
+ }
34854
+ function getVisibleText(message) {
34855
+ return message.parts.filter((part) => part.type === "text").map((part) => part.text).join("");
34856
+ }
34857
+ function getPendingClientAction(message) {
34858
+ for (let index = message.parts.length - 1; index >= 0; index -= 1) {
34859
+ const part = message.parts[index];
34860
+ if (isSelectConnections(part)) {
34861
+ return {
34862
+ kind: "select_connections",
34863
+ message,
34864
+ toolCallId: part.toolCallId,
34865
+ draftId: part.input.draftId,
34866
+ connections: part.input.connections
34867
+ };
34868
+ }
34869
+ if (isPromptEnablePush(part)) {
34870
+ return {
34871
+ kind: "enable_push",
34872
+ message,
34873
+ toolCallId: part.toolCallId,
34874
+ draftId: part.input.draftId
34875
+ };
34876
+ }
34877
+ }
34878
+ return null;
34879
+ }
34880
+ function getPendingActionFromMessages(messages) {
34881
+ const latestMessage = messages.at(-1);
34882
+ if (!latestMessage || latestMessage.role !== "assistant")
34883
+ return null;
34884
+ return getPendingClientAction(toUiMessage(latestMessage));
34885
+ }
34886
+ function getPublishedAutomationId(message) {
34887
+ for (let index = message.parts.length - 1; index >= 0; index -= 1) {
34888
+ const part = message.parts[index];
34889
+ if (isPublishDraftResult(part) && part.output.success) {
34890
+ return part.output.automationId;
34891
+ }
34892
+ }
34893
+ return void 0;
34894
+ }
34895
+ function createEnablePushContinuation(action, output2) {
34896
+ return {
34897
+ id: action.message.id,
34898
+ role: "assistant",
34899
+ parts: [
34900
+ {
34901
+ type: "tool-prompt-enable-push-notifications",
34902
+ toolCallId: action.toolCallId,
34903
+ state: "output-available",
34904
+ output: output2
34905
+ }
34906
+ ]
34907
+ };
34908
+ }
34909
+ function createSelectConnectionsContinuation(action, output2) {
34910
+ return {
34911
+ id: action.message.id,
34912
+ role: "assistant",
34913
+ parts: [
34914
+ {
34915
+ type: "tool-select-connections",
34916
+ toolCallId: action.toolCallId,
34917
+ state: "output-available",
34918
+ output: output2
34919
+ }
34920
+ ]
34921
+ };
34922
+ }
34923
+
34924
+ // ../../packages/sdk/dist/esm/chat-turn/types.js
34925
+ var ChatTurnInputError = class extends Error {
34926
+ code;
34927
+ constructor(code, message) {
34928
+ super(message);
34929
+ this.name = "ChatTurnInputError";
34930
+ this.code = code;
34931
+ }
34932
+ };
34933
+
34934
+ // ../../packages/sdk/dist/esm/chat-turn/run-conversation-turn.js
34935
+ async function runConversationTurn(options) {
34936
+ const { api, transport, conversationId, signal } = options;
34937
+ const workspaceId = options.workspaceId ?? null;
34938
+ const requestOptions = { signal };
34939
+ const pending = await readPendingAction(options);
34940
+ let nextMessage;
34941
+ if (pending?.kind === "enable_push") {
34942
+ if ("message" in options.input || options.input.selections.length > 0) {
34943
+ throw new ChatTurnInputError("awaiting_push", "The conversation is waiting for the push notification prompt to be answered.");
34944
+ }
34945
+ nextMessage = await answerEnablePush(options, pending);
34946
+ } else if ("message" in options.input) {
34947
+ if (pending?.kind === "select_connections") {
34948
+ throw new ChatTurnInputError("awaiting_selection", "The conversation is waiting for connection selection.");
34949
+ }
34950
+ nextMessage = createUserMessage(options.input.message);
34951
+ } else {
34952
+ const selections = options.input.selections;
34953
+ if (pending?.kind !== "select_connections") {
34954
+ throw new ChatTurnInputError(selections.length > 0 ? "not_awaiting_selection" : "input_required", selections.length > 0 ? "The conversation is not waiting for connection selection." : "A message is required to continue.");
34955
+ }
34956
+ const resolution = await resolveSelectConnections(options, pending, selections);
34957
+ if (resolution.kind === "required") {
34958
+ return actionRequired(conversationId, getVisibleText(pending.message), resolution.pendingAction);
34959
+ }
34960
+ nextMessage = resolution.continuation;
34961
+ }
34962
+ while (true) {
34963
+ const response = await transport.streamTurn({
34964
+ conversationId,
34965
+ workspaceId,
34966
+ message: nextMessage,
34967
+ signal,
34968
+ onText: options.onText,
34969
+ onToolActivity: options.onToolActivity
34970
+ });
34971
+ const assistantText = getVisibleText(response);
34972
+ const automationId = getPublishedAutomationId(response);
34973
+ if (automationId) {
34974
+ options.onPublished?.(automationId);
34975
+ const archive = await archiveConversation(api, conversationId, requestOptions);
34976
+ return {
34977
+ conversationId,
34978
+ outcome: "published",
34979
+ assistantText,
34980
+ automationId,
34981
+ ...archive
34982
+ };
34983
+ }
34984
+ const responseAction = getPendingClientAction(response);
34985
+ if (responseAction?.kind === "select_connections") {
34986
+ const resolution = await resolveSelectConnections(options, responseAction, []);
34987
+ if (resolution.kind === "resolved") {
34988
+ nextMessage = resolution.continuation;
34989
+ continue;
34990
+ }
34991
+ return actionRequired(conversationId, assistantText, resolution.pendingAction);
34992
+ }
34993
+ if (responseAction?.kind === "enable_push") {
34994
+ return actionRequired(conversationId, assistantText, {
34995
+ kind: "enable_push",
34996
+ toolCallId: responseAction.toolCallId,
34997
+ draftId: responseAction.draftId
34998
+ });
34999
+ }
35000
+ return { conversationId, outcome: "awaiting_input", assistantText };
35001
+ }
35002
+ }
35003
+ async function answerEnablePush(options, action) {
35004
+ const { subscriptions } = await options.api.notifications.push.list({
35005
+ signal: options.signal
35006
+ });
35007
+ const deviceCount = subscriptions.length;
35008
+ return createEnablePushContinuation(action, {
35009
+ enabled: deviceCount > 0,
35010
+ deviceCount,
35011
+ message: deviceCount > 0 ? `Push notifications are enabled on ${deviceCount} device${deviceCount === 1 ? "" : "s"}.` : "The user has not enabled push notifications."
35012
+ });
35013
+ }
35014
+ async function readPendingAction(options) {
35015
+ if (options.pendingAction !== void 0)
35016
+ return options.pendingAction;
35017
+ const messages = await options.api.chat.getMessages(options.conversationId, {
35018
+ signal: options.signal
35019
+ });
35020
+ return getPendingActionFromMessages(messages);
35021
+ }
35022
+ function actionRequired(conversationId, assistantText, pendingAction) {
35023
+ return { conversationId, outcome: "action_required", assistantText, pendingAction };
35024
+ }
35025
+ async function archiveConversation(api, conversationId, requestOptions) {
35026
+ try {
35027
+ const conversation = await api.chat.updateConversation(conversationId, { isArchived: true }, requestOptions);
35028
+ if (!conversation.isArchived) {
35029
+ throw new Error("The Automations API returned an active conversation.");
35030
+ }
35031
+ return { conversationArchived: true };
35032
+ } catch (error62) {
35033
+ return {
35034
+ conversationArchived: false,
35035
+ conversationArchiveError: error62 instanceof Error ? error62.message : String(error62)
35036
+ };
35037
+ }
35038
+ }
35039
+ async function resolveSelectConnections(options, action, selections) {
35040
+ const { api, signal } = options;
35041
+ const requestOptions = { signal };
35042
+ const requirements = await loadConnectionRequirements(api.connections, action.connections, requestOptions);
35043
+ assertSelectionsUnambiguous(requirements, selections);
35044
+ const resolved = [];
35045
+ for (const requirement of requirements) {
35046
+ const explicit = selections.find((selection) => selection.connectorId === requirement.connectorId && (!selection.connectionType || selection.connectionType === requirement.connectionType));
35047
+ let connectionId = explicit?.connectionId;
35048
+ let setup;
35049
+ if (connectionId) {
35050
+ const exists = requirement.availableConnections.some((connection) => connection.id === connectionId);
35051
+ if (!exists) {
35052
+ throw new ChatTurnInputError("unknown_connection", `Connection ${connectionId} is not an authorized ${requirement.serviceName} connection.`);
35053
+ }
35054
+ } else if (requirement.availableConnections.length === 1) {
35055
+ connectionId = requirement.availableConnections[0]?.id;
35056
+ } else if (requirement.availableConnections.length > 1) {
35057
+ connectionId = await options.chooseConnection?.(requirement);
35058
+ } else if (options.authorizeConnection) {
35059
+ connectionId = await options.authorizeConnection(requirement);
35060
+ } else {
35061
+ setup = await startConnectionSetup(api.connections, { connectorId: requirement.connectorId, capability: requirement.connectionType }, requestOptions);
35062
+ }
35063
+ if (!connectionId) {
35064
+ return {
35065
+ kind: "required",
35066
+ pendingAction: {
35067
+ kind: "select_connections",
35068
+ toolCallId: action.toolCallId,
35069
+ draftId: action.draftId,
35070
+ requirements: requirements.map((item) => item === requirement && setup ? { ...item, setup } : item)
35071
+ }
35072
+ };
35073
+ }
35074
+ resolved.push({ requirement, connectionId });
35075
+ }
35076
+ const output2 = [];
35077
+ for (const item of resolved) {
35078
+ const body = {
35079
+ connectorId: item.requirement.connectorId,
35080
+ connectionType: item.requirement.connectionType,
35081
+ connectionId: item.connectionId
35082
+ };
35083
+ const workspaceId = options.workspaceId ?? null;
35084
+ if (workspaceId === null) {
35085
+ await api.automationDraftConnections.upsert(action.draftId, body, requestOptions);
35086
+ } else {
35087
+ await api.workspaceAutomationDraftConnections.upsert(workspaceId, action.draftId, body, requestOptions);
35088
+ }
35089
+ output2.push({ type: "success", ...body });
35090
+ }
35091
+ return { kind: "resolved", continuation: createSelectConnectionsContinuation(action, output2) };
35092
+ }
35093
+ function assertSelectionsUnambiguous(requirements, selections) {
35094
+ for (const selection of selections) {
35095
+ if (selection.connectionType)
35096
+ continue;
35097
+ const matchingTypes = new Set(requirements.filter((requirement) => requirement.connectorId === selection.connectorId).map((requirement) => requirement.connectionType));
35098
+ if (matchingTypes.size > 1) {
35099
+ throw new ChatTurnInputError("ambiguous_selection", `Connection selection for ${selection.connectorId} is ambiguous. Specify ${selection.connectorId}:mcp or ${selection.connectorId}:trigger.`);
35100
+ }
35101
+ }
35102
+ }
35103
+
35104
+ // ../../packages/sdk/dist/esm/chat-turn/transport.js
35105
+ import { DefaultChatTransport, readUIMessageStream } from "ai";
35106
+ var ChatTurnTransport = class {
35107
+ transport;
35108
+ constructor({ token, baseUrl, headers, fetch: fetch2 }) {
35109
+ const apiRoot = `${baseUrl.replace(/\/+$/, "")}/v1`;
35110
+ this.transport = new DefaultChatTransport({
35111
+ api: `${apiRoot}/chat`,
35112
+ headers: {
35113
+ Authorization: `Bearer ${token}`,
35114
+ ...headers
35115
+ },
35116
+ credentials: "include",
35117
+ fetch: fetch2,
35118
+ prepareSendMessagesRequest({ messages, body, api }) {
35119
+ const { workspaceId, ...rest } = body ?? {};
35120
+ return {
35121
+ api: workspaceId ? `${apiRoot}/workspaces/${encodeURIComponent(workspaceId)}/chat` : api,
35122
+ body: {
35123
+ ...rest,
35124
+ message: messages.at(-1)
35125
+ }
35126
+ };
35127
+ }
35128
+ });
35129
+ }
35130
+ /** Streams one server turn, reporting incremental text and deduplicated tool state changes. */
35131
+ async streamTurn({ conversationId, workspaceId = null, message, signal, onText, onToolActivity }) {
35132
+ const stream = await this.transport.sendMessages({
35133
+ trigger: "submit-message",
35134
+ chatId: conversationId,
35135
+ messageId: void 0,
35136
+ messages: [message],
35137
+ abortSignal: signal,
35138
+ body: { conversationId, workspaceId }
35139
+ });
35140
+ let finalMessage;
35141
+ let emittedText = "";
35142
+ const emittedToolStates = /* @__PURE__ */ new Set();
35143
+ for await (const currentMessage of readUIMessageStream({
35144
+ stream,
35145
+ terminateOnError: true
35146
+ })) {
35147
+ finalMessage = currentMessage;
35148
+ const visibleText = getVisibleText(currentMessage);
35149
+ if (visibleText.startsWith(emittedText)) {
35150
+ const delta = visibleText.slice(emittedText.length);
35151
+ if (delta)
35152
+ onText?.(delta);
35153
+ emittedText = visibleText;
35154
+ }
35155
+ for (const part of currentMessage.parts) {
35156
+ const activity = toToolActivity(part);
35157
+ if (!activity)
35158
+ continue;
35159
+ const key = `${activity.toolCallId}:${activity.state}`;
35160
+ if (emittedToolStates.has(key))
35161
+ continue;
35162
+ emittedToolStates.add(key);
35163
+ onToolActivity?.(activity);
35164
+ }
35165
+ }
35166
+ if (!finalMessage) {
35167
+ throw new Error("The Automations chat returned an empty response.");
35168
+ }
35169
+ return finalMessage;
35170
+ }
35171
+ };
35172
+ function toToolActivity(part) {
35173
+ if (!part.type.startsWith("tool-") || !("toolCallId" in part) || !("state" in part)) {
35174
+ return null;
35175
+ }
35176
+ return {
35177
+ tool: part.type.slice("tool-".length),
35178
+ toolCallId: String(part.toolCallId),
35179
+ state: String(part.state)
35180
+ };
35181
+ }
35182
+
34699
35183
  // src/commands/read/format.ts
34700
35184
  function formatAutomationList(page) {
34701
35185
  if (page.data.length === 0) return "No automations found.";
@@ -34864,9 +35348,6 @@ function getMessageSummary(message) {
34864
35348
  const partTypes = message.parts.filter(isRecord2).map((part) => typeof part.type === "string" ? part.type : "unknown");
34865
35349
  return partTypes.length > 0 ? `[${partTypes.join(", ")}]` : "(no visible text)";
34866
35350
  }
34867
- function isTextPart(value) {
34868
- return isRecord2(value) && value.type === "text" && typeof value.text === "string";
34869
- }
34870
35351
  function isRecord2(value) {
34871
35352
  return typeof value === "object" && value !== null && !Array.isArray(value);
34872
35353
  }
@@ -35245,463 +35726,6 @@ function collectOption(value, previous) {
35245
35726
  return [...previous, value];
35246
35727
  }
35247
35728
 
35248
- // ../../packages/sdk/dist/esm/chat-turn/connection-requirements.js
35249
- var CONNECTION_LOOKUP_CONCURRENCY = 4;
35250
- function deduplicateConnectionRequirements(requirements) {
35251
- const seen = /* @__PURE__ */ new Set();
35252
- return requirements.filter((requirement) => {
35253
- const key = JSON.stringify([requirement.connectorId, requirement.connectionType]);
35254
- if (seen.has(key))
35255
- return false;
35256
- seen.add(key);
35257
- return true;
35258
- });
35259
- }
35260
- async function loadConnectionRequirements(connections, requirements, options) {
35261
- return mapWithConcurrency(deduplicateConnectionRequirements(requirements), CONNECTION_LOOKUP_CONCURRENCY, async (requirement) => {
35262
- const response = await connections.list({
35263
- connectorId: requirement.connectorId,
35264
- capability: requirement.connectionType,
35265
- status: "authorized"
35266
- }, options);
35267
- return {
35268
- ...requirement,
35269
- availableConnections: response.connections.map((connection) => ({
35270
- id: connection.id,
35271
- label: connection.accountDisplayLabel ?? connection.name ?? connection.id
35272
- }))
35273
- };
35274
- });
35275
- }
35276
- async function mapWithConcurrency(items, concurrency, map2) {
35277
- const results = new Array(items.length);
35278
- let next = 0;
35279
- const workers = Array.from({ length: Math.min(concurrency, items.length) }, async () => {
35280
- while (next < items.length) {
35281
- const index = next;
35282
- next += 1;
35283
- results[index] = await map2(items[index]);
35284
- }
35285
- });
35286
- await Promise.all(workers);
35287
- return results;
35288
- }
35289
-
35290
- // ../../packages/sdk/dist/esm/chat-turn/connection-setup.js
35291
- async function startConnectionSetup(connections, request2, options) {
35292
- const created = await connections.create({
35293
- connectorId: request2.connectorId,
35294
- capability: request2.capability,
35295
- ...request2.automationIds?.length ? { body: { automationIds: [...request2.automationIds] } } : {}
35296
- }, options);
35297
- return { connectionId: created.connectionId, setup: created.setup, resumed: false };
35298
- }
35299
- function summarizeConnectionSetup(setup) {
35300
- if (setup?.kind === "oauth") {
35301
- return { setupKind: "oauth", authorizationUrl: setup.authorizationUrl };
35302
- }
35303
- if (setup?.kind === "managed") {
35304
- return {
35305
- setupKind: "managed",
35306
- instructions: setup.instructions,
35307
- code: setup.code,
35308
- deepLinkUrl: setup.deepLinkUrl,
35309
- expiresAt: setup.expiresAt
35310
- };
35311
- }
35312
- return { setupKind: "none" };
35313
- }
35314
- function getConnectionSetupUrl(setup) {
35315
- if (setup?.kind === "oauth")
35316
- return setup.authorizationUrl;
35317
- if (setup?.kind === "managed")
35318
- return setup.deepLinkUrl;
35319
- return null;
35320
- }
35321
-
35322
- // ../../packages/core/dist/esm/chat-tools/client-tool-result.js
35323
- var clientToolContinuationPartSchema = external_exports.discriminatedUnion("type", [
35324
- toolPromptEnablePushResultSchema.omit({ input: true }).strict(),
35325
- toolSelectConnectionsResultSchema.omit({ input: true }).strict()
35326
- ]);
35327
-
35328
- // ../../packages/core/dist/esm/chat-tools/update-memory.js
35329
- var UpdateMemoryInputSchema = external_exports.object({
35330
- append: external_exports.string().min(1).optional(),
35331
- replace: external_exports.object({
35332
- old: external_exports.string().min(1),
35333
- new: external_exports.string()
35334
- }).optional()
35335
- });
35336
- var UpdateMemoryOutputSchema = external_exports.object({
35337
- memory: external_exports.string().nullable(),
35338
- // What the memory held before this write — the chat UI's Undo restores it.
35339
- previousMemory: external_exports.string().nullable()
35340
- });
35341
- var toolUpdateMemorySchema = external_exports.object({
35342
- type: external_exports.literal("tool-update-memory"),
35343
- toolCallId: external_exports.string().min(1),
35344
- state: external_exports.literal("input-available"),
35345
- input: UpdateMemoryInputSchema
35346
- });
35347
- var toolUpdateMemoryResultSchema = external_exports.object({
35348
- type: external_exports.literal("tool-update-memory"),
35349
- toolCallId: external_exports.string().min(1),
35350
- state: external_exports.literal("output-available"),
35351
- input: UpdateMemoryInputSchema,
35352
- output: UpdateMemoryOutputSchema
35353
- });
35354
-
35355
- // ../../packages/sdk/dist/esm/chat-turn/messages.js
35356
- function createUserMessage(text2) {
35357
- return {
35358
- id: globalThis.crypto.randomUUID(),
35359
- role: "user",
35360
- parts: [{ type: "text", text: text2 }]
35361
- };
35362
- }
35363
- function toUiMessage(message) {
35364
- if (message.role !== "user" && message.role !== "assistant" && message.role !== "system") {
35365
- throw new Error(`Unsupported persisted chat role: ${message.role}`);
35366
- }
35367
- if (!Array.isArray(message.parts)) {
35368
- throw new Error(`Persisted chat message ${message.messageId} has invalid parts.`);
35369
- }
35370
- return {
35371
- id: message.messageId,
35372
- role: message.role,
35373
- parts: message.parts
35374
- };
35375
- }
35376
- function getVisibleText(message) {
35377
- return message.parts.filter((part) => part.type === "text").map((part) => part.text).join("");
35378
- }
35379
- function getPendingClientAction(message) {
35380
- for (let index = message.parts.length - 1; index >= 0; index -= 1) {
35381
- const part = message.parts[index];
35382
- if (isSelectConnections(part)) {
35383
- return {
35384
- kind: "select_connections",
35385
- message,
35386
- toolCallId: part.toolCallId,
35387
- draftId: part.input.draftId,
35388
- connections: part.input.connections
35389
- };
35390
- }
35391
- if (isPromptEnablePush(part)) {
35392
- return {
35393
- kind: "enable_push",
35394
- message,
35395
- toolCallId: part.toolCallId,
35396
- draftId: part.input.draftId
35397
- };
35398
- }
35399
- }
35400
- return null;
35401
- }
35402
- function getPendingActionFromMessages(messages) {
35403
- const latestMessage = messages.at(-1);
35404
- if (!latestMessage || latestMessage.role !== "assistant")
35405
- return null;
35406
- return getPendingClientAction(toUiMessage(latestMessage));
35407
- }
35408
- function getPublishedAutomationId(message) {
35409
- for (let index = message.parts.length - 1; index >= 0; index -= 1) {
35410
- const part = message.parts[index];
35411
- if (isPublishDraftResult(part) && part.output.success) {
35412
- return part.output.automationId;
35413
- }
35414
- }
35415
- return void 0;
35416
- }
35417
- function createEnablePushContinuation(action, output2) {
35418
- return {
35419
- id: action.message.id,
35420
- role: "assistant",
35421
- parts: [
35422
- {
35423
- type: "tool-prompt-enable-push-notifications",
35424
- toolCallId: action.toolCallId,
35425
- state: "output-available",
35426
- output: output2
35427
- }
35428
- ]
35429
- };
35430
- }
35431
- function createSelectConnectionsContinuation(action, output2) {
35432
- return {
35433
- id: action.message.id,
35434
- role: "assistant",
35435
- parts: [
35436
- {
35437
- type: "tool-select-connections",
35438
- toolCallId: action.toolCallId,
35439
- state: "output-available",
35440
- output: output2
35441
- }
35442
- ]
35443
- };
35444
- }
35445
-
35446
- // ../../packages/sdk/dist/esm/chat-turn/types.js
35447
- var ChatTurnInputError = class extends Error {
35448
- code;
35449
- constructor(code, message) {
35450
- super(message);
35451
- this.name = "ChatTurnInputError";
35452
- this.code = code;
35453
- }
35454
- };
35455
-
35456
- // ../../packages/sdk/dist/esm/chat-turn/run-conversation-turn.js
35457
- async function runConversationTurn(options) {
35458
- const { api, transport, conversationId, signal } = options;
35459
- const workspaceId = options.workspaceId ?? null;
35460
- const requestOptions = { signal };
35461
- const pending = await readPendingAction(options);
35462
- let nextMessage;
35463
- if (pending?.kind === "enable_push") {
35464
- if ("message" in options.input || options.input.selections.length > 0) {
35465
- throw new ChatTurnInputError("awaiting_push", "The conversation is waiting for the push notification prompt to be answered.");
35466
- }
35467
- nextMessage = await answerEnablePush(options, pending);
35468
- } else if ("message" in options.input) {
35469
- if (pending?.kind === "select_connections") {
35470
- throw new ChatTurnInputError("awaiting_selection", "The conversation is waiting for connection selection.");
35471
- }
35472
- nextMessage = createUserMessage(options.input.message);
35473
- } else {
35474
- const selections = options.input.selections;
35475
- if (pending?.kind !== "select_connections") {
35476
- throw new ChatTurnInputError(selections.length > 0 ? "not_awaiting_selection" : "input_required", selections.length > 0 ? "The conversation is not waiting for connection selection." : "A message is required to continue.");
35477
- }
35478
- const resolution = await resolveSelectConnections(options, pending, selections);
35479
- if (resolution.kind === "required") {
35480
- return actionRequired(conversationId, getVisibleText(pending.message), resolution.pendingAction);
35481
- }
35482
- nextMessage = resolution.continuation;
35483
- }
35484
- while (true) {
35485
- const response = await transport.streamTurn({
35486
- conversationId,
35487
- workspaceId,
35488
- message: nextMessage,
35489
- signal,
35490
- onText: options.onText,
35491
- onToolActivity: options.onToolActivity
35492
- });
35493
- const assistantText = getVisibleText(response);
35494
- const automationId = getPublishedAutomationId(response);
35495
- if (automationId) {
35496
- options.onPublished?.(automationId);
35497
- const archive = await archiveConversation(api, conversationId, requestOptions);
35498
- return {
35499
- conversationId,
35500
- outcome: "published",
35501
- assistantText,
35502
- automationId,
35503
- ...archive
35504
- };
35505
- }
35506
- const responseAction = getPendingClientAction(response);
35507
- if (responseAction?.kind === "select_connections") {
35508
- const resolution = await resolveSelectConnections(options, responseAction, []);
35509
- if (resolution.kind === "resolved") {
35510
- nextMessage = resolution.continuation;
35511
- continue;
35512
- }
35513
- return actionRequired(conversationId, assistantText, resolution.pendingAction);
35514
- }
35515
- if (responseAction?.kind === "enable_push") {
35516
- return actionRequired(conversationId, assistantText, {
35517
- kind: "enable_push",
35518
- toolCallId: responseAction.toolCallId,
35519
- draftId: responseAction.draftId
35520
- });
35521
- }
35522
- return { conversationId, outcome: "awaiting_input", assistantText };
35523
- }
35524
- }
35525
- async function answerEnablePush(options, action) {
35526
- const { subscriptions } = await options.api.notifications.push.list({
35527
- signal: options.signal
35528
- });
35529
- const deviceCount = subscriptions.length;
35530
- return createEnablePushContinuation(action, {
35531
- enabled: deviceCount > 0,
35532
- deviceCount,
35533
- message: deviceCount > 0 ? `Push notifications are enabled on ${deviceCount} device${deviceCount === 1 ? "" : "s"}.` : "The user has not enabled push notifications."
35534
- });
35535
- }
35536
- async function readPendingAction(options) {
35537
- if (options.pendingAction !== void 0)
35538
- return options.pendingAction;
35539
- const messages = await options.api.chat.getMessages(options.conversationId, {
35540
- signal: options.signal
35541
- });
35542
- return getPendingActionFromMessages(messages);
35543
- }
35544
- function actionRequired(conversationId, assistantText, pendingAction) {
35545
- return { conversationId, outcome: "action_required", assistantText, pendingAction };
35546
- }
35547
- async function archiveConversation(api, conversationId, requestOptions) {
35548
- try {
35549
- const conversation = await api.chat.updateConversation(conversationId, { isArchived: true }, requestOptions);
35550
- if (!conversation.isArchived) {
35551
- throw new Error("The Automations API returned an active conversation.");
35552
- }
35553
- return { conversationArchived: true };
35554
- } catch (error62) {
35555
- return {
35556
- conversationArchived: false,
35557
- conversationArchiveError: error62 instanceof Error ? error62.message : String(error62)
35558
- };
35559
- }
35560
- }
35561
- async function resolveSelectConnections(options, action, selections) {
35562
- const { api, signal } = options;
35563
- const requestOptions = { signal };
35564
- const requirements = await loadConnectionRequirements(api.connections, action.connections, requestOptions);
35565
- assertSelectionsUnambiguous(requirements, selections);
35566
- const resolved = [];
35567
- for (const requirement of requirements) {
35568
- const explicit = selections.find((selection) => selection.connectorId === requirement.connectorId && (!selection.connectionType || selection.connectionType === requirement.connectionType));
35569
- let connectionId = explicit?.connectionId;
35570
- let setup;
35571
- if (connectionId) {
35572
- const exists = requirement.availableConnections.some((connection) => connection.id === connectionId);
35573
- if (!exists) {
35574
- throw new ChatTurnInputError("unknown_connection", `Connection ${connectionId} is not an authorized ${requirement.serviceName} connection.`);
35575
- }
35576
- } else if (requirement.availableConnections.length === 1) {
35577
- connectionId = requirement.availableConnections[0]?.id;
35578
- } else if (requirement.availableConnections.length > 1) {
35579
- connectionId = await options.chooseConnection?.(requirement);
35580
- } else if (options.authorizeConnection) {
35581
- connectionId = await options.authorizeConnection(requirement);
35582
- } else {
35583
- setup = await startConnectionSetup(api.connections, { connectorId: requirement.connectorId, capability: requirement.connectionType }, requestOptions);
35584
- }
35585
- if (!connectionId) {
35586
- return {
35587
- kind: "required",
35588
- pendingAction: {
35589
- kind: "select_connections",
35590
- toolCallId: action.toolCallId,
35591
- draftId: action.draftId,
35592
- requirements: requirements.map((item) => item === requirement && setup ? { ...item, setup } : item)
35593
- }
35594
- };
35595
- }
35596
- resolved.push({ requirement, connectionId });
35597
- }
35598
- const output2 = [];
35599
- for (const item of resolved) {
35600
- const body = {
35601
- connectorId: item.requirement.connectorId,
35602
- connectionType: item.requirement.connectionType,
35603
- connectionId: item.connectionId
35604
- };
35605
- const workspaceId = options.workspaceId ?? null;
35606
- if (workspaceId === null) {
35607
- await api.automationDraftConnections.upsert(action.draftId, body, requestOptions);
35608
- } else {
35609
- await api.workspaceAutomationDraftConnections.upsert(workspaceId, action.draftId, body, requestOptions);
35610
- }
35611
- output2.push({ type: "success", ...body });
35612
- }
35613
- return { kind: "resolved", continuation: createSelectConnectionsContinuation(action, output2) };
35614
- }
35615
- function assertSelectionsUnambiguous(requirements, selections) {
35616
- for (const selection of selections) {
35617
- if (selection.connectionType)
35618
- continue;
35619
- const matchingTypes = new Set(requirements.filter((requirement) => requirement.connectorId === selection.connectorId).map((requirement) => requirement.connectionType));
35620
- if (matchingTypes.size > 1) {
35621
- throw new ChatTurnInputError("ambiguous_selection", `Connection selection for ${selection.connectorId} is ambiguous. Specify ${selection.connectorId}:mcp or ${selection.connectorId}:trigger.`);
35622
- }
35623
- }
35624
- }
35625
-
35626
- // ../../packages/sdk/dist/esm/chat-turn/transport.js
35627
- import { DefaultChatTransport, readUIMessageStream } from "ai";
35628
- var ChatTurnTransport = class {
35629
- transport;
35630
- constructor({ token, baseUrl, headers, fetch: fetch2 }) {
35631
- const apiRoot = `${baseUrl.replace(/\/+$/, "")}/v1`;
35632
- this.transport = new DefaultChatTransport({
35633
- api: `${apiRoot}/chat`,
35634
- headers: {
35635
- Authorization: `Bearer ${token}`,
35636
- ...headers
35637
- },
35638
- credentials: "include",
35639
- fetch: fetch2,
35640
- prepareSendMessagesRequest({ messages, body, api }) {
35641
- const { workspaceId, ...rest } = body ?? {};
35642
- return {
35643
- api: workspaceId ? `${apiRoot}/workspaces/${encodeURIComponent(workspaceId)}/chat` : api,
35644
- body: {
35645
- ...rest,
35646
- message: messages.at(-1)
35647
- }
35648
- };
35649
- }
35650
- });
35651
- }
35652
- /** Streams one server turn, reporting incremental text and deduplicated tool state changes. */
35653
- async streamTurn({ conversationId, workspaceId = null, message, signal, onText, onToolActivity }) {
35654
- const stream = await this.transport.sendMessages({
35655
- trigger: "submit-message",
35656
- chatId: conversationId,
35657
- messageId: void 0,
35658
- messages: [message],
35659
- abortSignal: signal,
35660
- body: { conversationId, workspaceId }
35661
- });
35662
- let finalMessage;
35663
- let emittedText = "";
35664
- const emittedToolStates = /* @__PURE__ */ new Set();
35665
- for await (const currentMessage of readUIMessageStream({
35666
- stream,
35667
- terminateOnError: true
35668
- })) {
35669
- finalMessage = currentMessage;
35670
- const visibleText = getVisibleText(currentMessage);
35671
- if (visibleText.startsWith(emittedText)) {
35672
- const delta = visibleText.slice(emittedText.length);
35673
- if (delta)
35674
- onText?.(delta);
35675
- emittedText = visibleText;
35676
- }
35677
- for (const part of currentMessage.parts) {
35678
- const activity = toToolActivity(part);
35679
- if (!activity)
35680
- continue;
35681
- const key = `${activity.toolCallId}:${activity.state}`;
35682
- if (emittedToolStates.has(key))
35683
- continue;
35684
- emittedToolStates.add(key);
35685
- onToolActivity?.(activity);
35686
- }
35687
- }
35688
- if (!finalMessage) {
35689
- throw new Error("The Automations chat returned an empty response.");
35690
- }
35691
- return finalMessage;
35692
- }
35693
- };
35694
- function toToolActivity(part) {
35695
- if (!part.type.startsWith("tool-") || !("toolCallId" in part) || !("state" in part)) {
35696
- return null;
35697
- }
35698
- return {
35699
- tool: part.type.slice("tool-".length),
35700
- toolCallId: String(part.toolCallId),
35701
- state: String(part.state)
35702
- };
35703
- }
35704
-
35705
35729
  // src/commands/chat/chat-service.ts
35706
35730
  import { createInterface } from "node:readline/promises";
35707
35731
 
@@ -38203,6 +38227,9 @@ function registerUpdateCommand(program) {
38203
38227
  });
38204
38228
  }
38205
38229
 
38230
+ // src/commands/workspace/index.ts
38231
+ import { CliError as CliError9 } from "@doist/cli-core";
38232
+
38206
38233
  // src/commands/workspace/format.ts
38207
38234
  function formatWorkspaceList(workspaces) {
38208
38235
  if (workspaces.length === 0) {
@@ -38237,7 +38264,25 @@ function registerWorkspaceCommand(program) {
38237
38264
  workspace.command("list").description("List the workspaces you can use").action(async (_options, command) => {
38238
38265
  await runApiCommand(
38239
38266
  command,
38240
- ({ api, signal }) => api.workspaces.list({ signal }),
38267
+ async ({ api, signal }) => {
38268
+ try {
38269
+ return await api.workspaces.list({ signal });
38270
+ } catch (error62) {
38271
+ if (!(error62 instanceof AutomationsRequestError) || error62.httpStatusCode !== 404) {
38272
+ throw error62;
38273
+ }
38274
+ throw new CliError9(
38275
+ "WORKSPACE_ACCESS_UNAVAILABLE",
38276
+ "Workspace access is not available for this account or environment.",
38277
+ {
38278
+ hints: [
38279
+ "Use your personal space, for example: tda automation list --personal",
38280
+ "Contact support if you expect workspace access."
38281
+ ]
38282
+ }
38283
+ );
38284
+ }
38285
+ },
38241
38286
  {
38242
38287
  human: formatWorkspaceList,
38243
38288
  json: summarizeWorkspaceList,