@doist/automations-cli 1.22.2 → 1.23.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/dist/index.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  collectSkillStatus,
8
8
  getInstaller,
9
9
  updateAllInstalledSkills
10
- } from "./chunk-OEGAZVTZ.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.2",
6902
+ version: "1.23.0",
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
  },
@@ -27230,7 +27229,7 @@ var AutomationRunDtoSchema = external_exports.discriminatedUnion("status", [
27230
27229
  ...automationRunDtoBaseShape,
27231
27230
  status: external_exports.literal("skipped"),
27232
27231
  skipReason: external_exports.string().nullish(),
27233
- skipCategory: external_exports.enum(SKIP_CATEGORIES).nullish()
27232
+ skipCategory: external_exports.enum(SKIP_CATEGORIES).nullish().catch(null)
27234
27233
  })
27235
27234
  ]);
27236
27235
  var automationRunListResponseSchema = external_exports.object({
@@ -27332,7 +27331,7 @@ var AutomationRunDetailDtoSchema = external_exports.discriminatedUnion("status",
27332
27331
  ...automationRunDetailDtoBaseShape,
27333
27332
  status: external_exports.literal("skipped"),
27334
27333
  skipReason: external_exports.string().nullish(),
27335
- skipCategory: external_exports.enum(SKIP_CATEGORIES).nullish()
27334
+ skipCategory: external_exports.enum(SKIP_CATEGORIES).nullish().catch(null)
27336
27335
  })
27337
27336
  ]);
27338
27337
  function summarizeToolCallArgumentKeys(args) {
@@ -29157,7 +29156,7 @@ var runStatusSchema = external_exports.discriminatedUnion("status", [
29157
29156
  status: external_exports.literal("skipped"),
29158
29157
  lastRun: external_exports.iso.datetime(),
29159
29158
  skipReason: external_exports.string().optional(),
29160
- skipCategory: external_exports.enum(SKIP_CATEGORIES).optional()
29159
+ skipCategory: external_exports.enum(SKIP_CATEGORIES).optional().catch(void 0)
29161
29160
  })
29162
29161
  ]);
29163
29162
  var automationSchema = external_exports.object({
@@ -30080,7 +30079,7 @@ var DraftTestRunDetailDtoSchema = external_exports.discriminatedUnion("status",
30080
30079
  ...draftTestRunDetailDtoBaseShape,
30081
30080
  status: external_exports.literal("skipped"),
30082
30081
  skipReason: external_exports.string().nullish(),
30083
- skipCategory: external_exports.enum(SKIP_CATEGORIES).nullish()
30082
+ skipCategory: external_exports.enum(SKIP_CATEGORIES).nullish().catch(null)
30084
30083
  })
30085
30084
  ]);
30086
30085
 
@@ -31771,7 +31770,8 @@ var workspaceAutomationsEndpoints = {
31771
31770
  visibility: (workspaceId, id) => `${root8(workspaceId)}/${encodeURIComponent(id)}/visibility`,
31772
31771
  move: (workspaceId, id) => `${root8(workspaceId)}/${encodeURIComponent(id)}/move`,
31773
31772
  trigger: (workspaceId, id) => `${root8(workspaceId)}/${encodeURIComponent(id)}/trigger`,
31774
- draft: (workspaceId, id) => `${root8(workspaceId)}/${encodeURIComponent(id)}/draft`
31773
+ draft: (workspaceId, id) => `${root8(workspaceId)}/${encodeURIComponent(id)}/draft`,
31774
+ duplicate: (workspaceId, id) => `${root8(workspaceId)}/${encodeURIComponent(id)}/duplicate`
31775
31775
  };
31776
31776
  function root8(workspaceId) {
31777
31777
  return `/workspaces/${encodeURIComponent(workspaceId)}/automations`;
@@ -31884,6 +31884,15 @@ var WorkspaceAutomationsClient = class extends BaseClient {
31884
31884
  * Opens an edit draft seeded from the published manifest. Answers 409 when
31885
31885
  * one is already open; see `getOpenDraftConflictError`.
31886
31886
  */
31887
+ /** A draft copy of the automation, in the same workspace, for the caller to edit. */
31888
+ async duplicate(workspaceId, id, options) {
31889
+ const data = await this.request({
31890
+ method: "POST",
31891
+ path: workspaceAutomationsEndpoints.duplicate(IdSchema.parse(workspaceId), IdSchema.parse(id)),
31892
+ options
31893
+ });
31894
+ return automationDraftWithValidationSchema.parse(data);
31895
+ }
31887
31896
  async createDraft(workspaceId, id, options) {
31888
31897
  const data = await this.request({
31889
31898
  method: "POST",
@@ -34696,6 +34705,491 @@ function addWorkspaceScopeOptions(command) {
34696
34705
  ).option("--personal", "Act on your personal space, ignoring any pinned workspace");
34697
34706
  }
34698
34707
 
34708
+ // ../../packages/sdk/dist/esm/chat-turn/connection-requirements.js
34709
+ var CONNECTION_LOOKUP_CONCURRENCY = 4;
34710
+ function deduplicateConnectionRequirements(requirements) {
34711
+ const seen = /* @__PURE__ */ new Set();
34712
+ return requirements.filter((requirement) => {
34713
+ const key = JSON.stringify([requirement.connectorId, requirement.connectionType]);
34714
+ if (seen.has(key))
34715
+ return false;
34716
+ seen.add(key);
34717
+ return true;
34718
+ });
34719
+ }
34720
+ async function loadConnectionRequirements(connections, requirements, options) {
34721
+ return mapWithConcurrency(deduplicateConnectionRequirements(requirements), CONNECTION_LOOKUP_CONCURRENCY, async (requirement) => {
34722
+ const response = await connections.list({
34723
+ connectorId: requirement.connectorId,
34724
+ capability: requirement.connectionType,
34725
+ status: "authorized"
34726
+ }, options);
34727
+ return {
34728
+ ...requirement,
34729
+ availableConnections: response.connections.map((connection) => ({
34730
+ id: connection.id,
34731
+ label: connection.accountDisplayLabel ?? connection.name ?? connection.id
34732
+ }))
34733
+ };
34734
+ });
34735
+ }
34736
+ async function mapWithConcurrency(items, concurrency, map2) {
34737
+ const results = new Array(items.length);
34738
+ let next = 0;
34739
+ const workers = Array.from({ length: Math.min(concurrency, items.length) }, async () => {
34740
+ while (next < items.length) {
34741
+ const index = next;
34742
+ next += 1;
34743
+ results[index] = await map2(items[index]);
34744
+ }
34745
+ });
34746
+ await Promise.all(workers);
34747
+ return results;
34748
+ }
34749
+
34750
+ // ../../packages/sdk/dist/esm/chat-turn/connection-setup.js
34751
+ async function startConnectionSetup(connections, request2, options) {
34752
+ const created = await connections.create({
34753
+ connectorId: request2.connectorId,
34754
+ capability: request2.capability,
34755
+ ...request2.automationIds?.length ? { body: { automationIds: [...request2.automationIds] } } : {}
34756
+ }, options);
34757
+ return { connectionId: created.connectionId, setup: created.setup, resumed: false };
34758
+ }
34759
+ function summarizeConnectionSetup(setup) {
34760
+ if (setup?.kind === "oauth") {
34761
+ return { setupKind: "oauth", authorizationUrl: setup.authorizationUrl };
34762
+ }
34763
+ if (setup?.kind === "managed") {
34764
+ return {
34765
+ setupKind: "managed",
34766
+ instructions: setup.instructions,
34767
+ code: setup.code,
34768
+ deepLinkUrl: setup.deepLinkUrl,
34769
+ expiresAt: setup.expiresAt
34770
+ };
34771
+ }
34772
+ return { setupKind: "none" };
34773
+ }
34774
+ function getConnectionSetupUrl(setup) {
34775
+ if (setup?.kind === "oauth")
34776
+ return setup.authorizationUrl;
34777
+ if (setup?.kind === "managed")
34778
+ return setup.deepLinkUrl;
34779
+ return null;
34780
+ }
34781
+
34782
+ // ../../packages/core/dist/esm/chat-tools/client-tool-result.js
34783
+ var clientToolContinuationPartSchema = external_exports.discriminatedUnion("type", [
34784
+ toolPromptEnablePushResultSchema.omit({ input: true }).strict(),
34785
+ toolSelectConnectionsResultSchema.omit({ input: true }).strict()
34786
+ ]);
34787
+
34788
+ // ../../packages/core/dist/esm/chat-tools/update-memory.js
34789
+ var UpdateMemoryInputSchema = external_exports.object({
34790
+ append: external_exports.string().min(1).optional(),
34791
+ replace: external_exports.object({
34792
+ old: external_exports.string().min(1),
34793
+ new: external_exports.string()
34794
+ }).optional()
34795
+ });
34796
+ var UpdateMemoryOutputSchema = external_exports.object({
34797
+ memory: external_exports.string().nullable(),
34798
+ // What the memory held before this write — the chat UI's Undo restores it.
34799
+ previousMemory: external_exports.string().nullable()
34800
+ });
34801
+ var toolUpdateMemorySchema = external_exports.object({
34802
+ type: external_exports.literal("tool-update-memory"),
34803
+ toolCallId: external_exports.string().min(1),
34804
+ state: external_exports.literal("input-available"),
34805
+ input: UpdateMemoryInputSchema
34806
+ });
34807
+ var toolUpdateMemoryResultSchema = external_exports.object({
34808
+ type: external_exports.literal("tool-update-memory"),
34809
+ toolCallId: external_exports.string().min(1),
34810
+ state: external_exports.literal("output-available"),
34811
+ input: UpdateMemoryInputSchema,
34812
+ output: UpdateMemoryOutputSchema
34813
+ });
34814
+
34815
+ // ../../packages/sdk/dist/esm/chat-turn/messages.js
34816
+ var persistedToolPartSchema = external_exports.object({
34817
+ type: external_exports.string().trim().min(6).refine((type) => type.startsWith("tool-")),
34818
+ toolCallId: external_exports.string().trim().min(1),
34819
+ state: external_exports.enum(["input-streaming", "input-available", "output-available", "output-error"])
34820
+ }).passthrough();
34821
+ function createUserMessage(text2) {
34822
+ return {
34823
+ id: globalThis.crypto.randomUUID(),
34824
+ role: "user",
34825
+ parts: [{ type: "text", text: text2 }]
34826
+ };
34827
+ }
34828
+ function toUiMessage(message) {
34829
+ if (message.role !== "user" && message.role !== "assistant" && message.role !== "system") {
34830
+ throw new Error(`Unsupported persisted chat role: ${message.role}`);
34831
+ }
34832
+ if (!Array.isArray(message.parts)) {
34833
+ throw new Error(`Persisted chat message ${message.messageId} has invalid parts.`);
34834
+ }
34835
+ return {
34836
+ id: message.messageId,
34837
+ role: message.role,
34838
+ parts: normalizePersistedMessageParts(message.parts)
34839
+ };
34840
+ }
34841
+ function normalizePersistedMessageParts(parts) {
34842
+ return parts.filter((part) => isTextPart(part) || isStepStartPart(part) || isReasoningPart(part) || persistedToolPartSchema.safeParse(part).success);
34843
+ }
34844
+ function isTextPart(part) {
34845
+ if (typeof part !== "object" || part === null) {
34846
+ return false;
34847
+ }
34848
+ const candidate = part;
34849
+ return candidate.type === "text" && typeof candidate.text === "string";
34850
+ }
34851
+ function isStepStartPart(part) {
34852
+ if (typeof part !== "object" || part === null) {
34853
+ return false;
34854
+ }
34855
+ return part.type === "step-start";
34856
+ }
34857
+ function isReasoningPart(part) {
34858
+ if (typeof part !== "object" || part === null) {
34859
+ return false;
34860
+ }
34861
+ const candidate = part;
34862
+ return candidate.type === "reasoning" && typeof candidate.text === "string" && (candidate.state === void 0 || candidate.state === "streaming" || candidate.state === "done");
34863
+ }
34864
+ function getVisibleText(message) {
34865
+ return message.parts.filter((part) => part.type === "text").map((part) => part.text).join("");
34866
+ }
34867
+ function getPendingClientAction(message) {
34868
+ for (let index = message.parts.length - 1; index >= 0; index -= 1) {
34869
+ const part = message.parts[index];
34870
+ if (isSelectConnections(part)) {
34871
+ return {
34872
+ kind: "select_connections",
34873
+ message,
34874
+ toolCallId: part.toolCallId,
34875
+ draftId: part.input.draftId,
34876
+ connections: part.input.connections
34877
+ };
34878
+ }
34879
+ if (isPromptEnablePush(part)) {
34880
+ return {
34881
+ kind: "enable_push",
34882
+ message,
34883
+ toolCallId: part.toolCallId,
34884
+ draftId: part.input.draftId
34885
+ };
34886
+ }
34887
+ }
34888
+ return null;
34889
+ }
34890
+ function getPendingActionFromMessages(messages) {
34891
+ const latestMessage = messages.at(-1);
34892
+ if (!latestMessage || latestMessage.role !== "assistant")
34893
+ return null;
34894
+ return getPendingClientAction(toUiMessage(latestMessage));
34895
+ }
34896
+ function getPublishedAutomationId(message) {
34897
+ for (let index = message.parts.length - 1; index >= 0; index -= 1) {
34898
+ const part = message.parts[index];
34899
+ if (isPublishDraftResult(part) && part.output.success) {
34900
+ return part.output.automationId;
34901
+ }
34902
+ }
34903
+ return void 0;
34904
+ }
34905
+ function createEnablePushContinuation(action, output2) {
34906
+ return {
34907
+ id: action.message.id,
34908
+ role: "assistant",
34909
+ parts: [
34910
+ {
34911
+ type: "tool-prompt-enable-push-notifications",
34912
+ toolCallId: action.toolCallId,
34913
+ state: "output-available",
34914
+ output: output2
34915
+ }
34916
+ ]
34917
+ };
34918
+ }
34919
+ function createSelectConnectionsContinuation(action, output2) {
34920
+ return {
34921
+ id: action.message.id,
34922
+ role: "assistant",
34923
+ parts: [
34924
+ {
34925
+ type: "tool-select-connections",
34926
+ toolCallId: action.toolCallId,
34927
+ state: "output-available",
34928
+ output: output2
34929
+ }
34930
+ ]
34931
+ };
34932
+ }
34933
+
34934
+ // ../../packages/sdk/dist/esm/chat-turn/types.js
34935
+ var ChatTurnInputError = class extends Error {
34936
+ code;
34937
+ constructor(code, message) {
34938
+ super(message);
34939
+ this.name = "ChatTurnInputError";
34940
+ this.code = code;
34941
+ }
34942
+ };
34943
+
34944
+ // ../../packages/sdk/dist/esm/chat-turn/run-conversation-turn.js
34945
+ async function runConversationTurn(options) {
34946
+ const { api, transport, conversationId, signal } = options;
34947
+ const workspaceId = options.workspaceId ?? null;
34948
+ const requestOptions = { signal };
34949
+ const pending = await readPendingAction(options);
34950
+ let nextMessage;
34951
+ if (pending?.kind === "enable_push") {
34952
+ if ("message" in options.input || options.input.selections.length > 0) {
34953
+ throw new ChatTurnInputError("awaiting_push", "The conversation is waiting for the push notification prompt to be answered.");
34954
+ }
34955
+ nextMessage = await answerEnablePush(options, pending);
34956
+ } else if ("message" in options.input) {
34957
+ if (pending?.kind === "select_connections") {
34958
+ throw new ChatTurnInputError("awaiting_selection", "The conversation is waiting for connection selection.");
34959
+ }
34960
+ nextMessage = createUserMessage(options.input.message);
34961
+ } else {
34962
+ const selections = options.input.selections;
34963
+ if (pending?.kind !== "select_connections") {
34964
+ 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.");
34965
+ }
34966
+ const resolution = await resolveSelectConnections(options, pending, selections);
34967
+ if (resolution.kind === "required") {
34968
+ return actionRequired(conversationId, getVisibleText(pending.message), resolution.pendingAction);
34969
+ }
34970
+ nextMessage = resolution.continuation;
34971
+ }
34972
+ while (true) {
34973
+ const response = await transport.streamTurn({
34974
+ conversationId,
34975
+ workspaceId,
34976
+ message: nextMessage,
34977
+ signal,
34978
+ onText: options.onText,
34979
+ onToolActivity: options.onToolActivity
34980
+ });
34981
+ const assistantText = getVisibleText(response);
34982
+ const automationId = getPublishedAutomationId(response);
34983
+ if (automationId) {
34984
+ options.onPublished?.(automationId);
34985
+ const archive = await archiveConversation(api, conversationId, requestOptions);
34986
+ return {
34987
+ conversationId,
34988
+ outcome: "published",
34989
+ assistantText,
34990
+ automationId,
34991
+ ...archive
34992
+ };
34993
+ }
34994
+ const responseAction = getPendingClientAction(response);
34995
+ if (responseAction?.kind === "select_connections") {
34996
+ const resolution = await resolveSelectConnections(options, responseAction, []);
34997
+ if (resolution.kind === "resolved") {
34998
+ nextMessage = resolution.continuation;
34999
+ continue;
35000
+ }
35001
+ return actionRequired(conversationId, assistantText, resolution.pendingAction);
35002
+ }
35003
+ if (responseAction?.kind === "enable_push") {
35004
+ return actionRequired(conversationId, assistantText, {
35005
+ kind: "enable_push",
35006
+ toolCallId: responseAction.toolCallId,
35007
+ draftId: responseAction.draftId
35008
+ });
35009
+ }
35010
+ return { conversationId, outcome: "awaiting_input", assistantText };
35011
+ }
35012
+ }
35013
+ async function answerEnablePush(options, action) {
35014
+ const { subscriptions } = await options.api.notifications.push.list({
35015
+ signal: options.signal
35016
+ });
35017
+ const deviceCount = subscriptions.length;
35018
+ return createEnablePushContinuation(action, {
35019
+ enabled: deviceCount > 0,
35020
+ deviceCount,
35021
+ message: deviceCount > 0 ? `Push notifications are enabled on ${deviceCount} device${deviceCount === 1 ? "" : "s"}.` : "The user has not enabled push notifications."
35022
+ });
35023
+ }
35024
+ async function readPendingAction(options) {
35025
+ if (options.pendingAction !== void 0)
35026
+ return options.pendingAction;
35027
+ const messages = await options.api.chat.getMessages(options.conversationId, {
35028
+ signal: options.signal
35029
+ });
35030
+ return getPendingActionFromMessages(messages);
35031
+ }
35032
+ function actionRequired(conversationId, assistantText, pendingAction) {
35033
+ return { conversationId, outcome: "action_required", assistantText, pendingAction };
35034
+ }
35035
+ async function archiveConversation(api, conversationId, requestOptions) {
35036
+ try {
35037
+ const conversation = await api.chat.updateConversation(conversationId, { isArchived: true }, requestOptions);
35038
+ if (!conversation.isArchived) {
35039
+ throw new Error("The Automations API returned an active conversation.");
35040
+ }
35041
+ return { conversationArchived: true };
35042
+ } catch (error62) {
35043
+ return {
35044
+ conversationArchived: false,
35045
+ conversationArchiveError: error62 instanceof Error ? error62.message : String(error62)
35046
+ };
35047
+ }
35048
+ }
35049
+ async function resolveSelectConnections(options, action, selections) {
35050
+ const { api, signal } = options;
35051
+ const requestOptions = { signal };
35052
+ const requirements = await loadConnectionRequirements(api.connections, action.connections, requestOptions);
35053
+ assertSelectionsUnambiguous(requirements, selections);
35054
+ const resolved = [];
35055
+ for (const requirement of requirements) {
35056
+ const explicit = selections.find((selection) => selection.connectorId === requirement.connectorId && (!selection.connectionType || selection.connectionType === requirement.connectionType));
35057
+ let connectionId = explicit?.connectionId;
35058
+ let setup;
35059
+ if (connectionId) {
35060
+ const exists = requirement.availableConnections.some((connection) => connection.id === connectionId);
35061
+ if (!exists) {
35062
+ throw new ChatTurnInputError("unknown_connection", `Connection ${connectionId} is not an authorized ${requirement.serviceName} connection.`);
35063
+ }
35064
+ } else if (requirement.availableConnections.length === 1) {
35065
+ connectionId = requirement.availableConnections[0]?.id;
35066
+ } else if (requirement.availableConnections.length > 1) {
35067
+ connectionId = await options.chooseConnection?.(requirement);
35068
+ } else if (options.authorizeConnection) {
35069
+ connectionId = await options.authorizeConnection(requirement);
35070
+ } else {
35071
+ setup = await startConnectionSetup(api.connections, { connectorId: requirement.connectorId, capability: requirement.connectionType }, requestOptions);
35072
+ }
35073
+ if (!connectionId) {
35074
+ return {
35075
+ kind: "required",
35076
+ pendingAction: {
35077
+ kind: "select_connections",
35078
+ toolCallId: action.toolCallId,
35079
+ draftId: action.draftId,
35080
+ requirements: requirements.map((item) => item === requirement && setup ? { ...item, setup } : item)
35081
+ }
35082
+ };
35083
+ }
35084
+ resolved.push({ requirement, connectionId });
35085
+ }
35086
+ const output2 = [];
35087
+ for (const item of resolved) {
35088
+ const body = {
35089
+ connectorId: item.requirement.connectorId,
35090
+ connectionType: item.requirement.connectionType,
35091
+ connectionId: item.connectionId
35092
+ };
35093
+ const workspaceId = options.workspaceId ?? null;
35094
+ if (workspaceId === null) {
35095
+ await api.automationDraftConnections.upsert(action.draftId, body, requestOptions);
35096
+ } else {
35097
+ await api.workspaceAutomationDraftConnections.upsert(workspaceId, action.draftId, body, requestOptions);
35098
+ }
35099
+ output2.push({ type: "success", ...body });
35100
+ }
35101
+ return { kind: "resolved", continuation: createSelectConnectionsContinuation(action, output2) };
35102
+ }
35103
+ function assertSelectionsUnambiguous(requirements, selections) {
35104
+ for (const selection of selections) {
35105
+ if (selection.connectionType)
35106
+ continue;
35107
+ const matchingTypes = new Set(requirements.filter((requirement) => requirement.connectorId === selection.connectorId).map((requirement) => requirement.connectionType));
35108
+ if (matchingTypes.size > 1) {
35109
+ throw new ChatTurnInputError("ambiguous_selection", `Connection selection for ${selection.connectorId} is ambiguous. Specify ${selection.connectorId}:mcp or ${selection.connectorId}:trigger.`);
35110
+ }
35111
+ }
35112
+ }
35113
+
35114
+ // ../../packages/sdk/dist/esm/chat-turn/transport.js
35115
+ import { DefaultChatTransport, readUIMessageStream } from "ai";
35116
+ var ChatTurnTransport = class {
35117
+ transport;
35118
+ constructor({ token, baseUrl, headers, fetch: fetch2 }) {
35119
+ const apiRoot = `${baseUrl.replace(/\/+$/, "")}/v1`;
35120
+ this.transport = new DefaultChatTransport({
35121
+ api: `${apiRoot}/chat`,
35122
+ headers: {
35123
+ Authorization: `Bearer ${token}`,
35124
+ ...headers
35125
+ },
35126
+ credentials: "include",
35127
+ fetch: fetch2,
35128
+ prepareSendMessagesRequest({ messages, body, api }) {
35129
+ const { workspaceId, ...rest } = body ?? {};
35130
+ return {
35131
+ api: workspaceId ? `${apiRoot}/workspaces/${encodeURIComponent(workspaceId)}/chat` : api,
35132
+ body: {
35133
+ ...rest,
35134
+ message: messages.at(-1)
35135
+ }
35136
+ };
35137
+ }
35138
+ });
35139
+ }
35140
+ /** Streams one server turn, reporting incremental text and deduplicated tool state changes. */
35141
+ async streamTurn({ conversationId, workspaceId = null, message, signal, onText, onToolActivity }) {
35142
+ const stream = await this.transport.sendMessages({
35143
+ trigger: "submit-message",
35144
+ chatId: conversationId,
35145
+ messageId: void 0,
35146
+ messages: [message],
35147
+ abortSignal: signal,
35148
+ body: { conversationId, workspaceId }
35149
+ });
35150
+ let finalMessage;
35151
+ let emittedText = "";
35152
+ const emittedToolStates = /* @__PURE__ */ new Set();
35153
+ for await (const currentMessage of readUIMessageStream({
35154
+ stream,
35155
+ terminateOnError: true
35156
+ })) {
35157
+ finalMessage = currentMessage;
35158
+ const visibleText = getVisibleText(currentMessage);
35159
+ if (visibleText.startsWith(emittedText)) {
35160
+ const delta = visibleText.slice(emittedText.length);
35161
+ if (delta)
35162
+ onText?.(delta);
35163
+ emittedText = visibleText;
35164
+ }
35165
+ for (const part of currentMessage.parts) {
35166
+ const activity = toToolActivity(part);
35167
+ if (!activity)
35168
+ continue;
35169
+ const key = `${activity.toolCallId}:${activity.state}`;
35170
+ if (emittedToolStates.has(key))
35171
+ continue;
35172
+ emittedToolStates.add(key);
35173
+ onToolActivity?.(activity);
35174
+ }
35175
+ }
35176
+ if (!finalMessage) {
35177
+ throw new Error("The Automations chat returned an empty response.");
35178
+ }
35179
+ return finalMessage;
35180
+ }
35181
+ };
35182
+ function toToolActivity(part) {
35183
+ if (!part.type.startsWith("tool-") || !("toolCallId" in part) || !("state" in part)) {
35184
+ return null;
35185
+ }
35186
+ return {
35187
+ tool: part.type.slice("tool-".length),
35188
+ toolCallId: String(part.toolCallId),
35189
+ state: String(part.state)
35190
+ };
35191
+ }
35192
+
34699
35193
  // src/commands/read/format.ts
34700
35194
  function formatAutomationList(page) {
34701
35195
  if (page.data.length === 0) return "No automations found.";
@@ -34864,9 +35358,6 @@ function getMessageSummary(message) {
34864
35358
  const partTypes = message.parts.filter(isRecord2).map((part) => typeof part.type === "string" ? part.type : "unknown");
34865
35359
  return partTypes.length > 0 ? `[${partTypes.join(", ")}]` : "(no visible text)";
34866
35360
  }
34867
- function isTextPart(value) {
34868
- return isRecord2(value) && value.type === "text" && typeof value.text === "string";
34869
- }
34870
35361
  function isRecord2(value) {
34871
35362
  return typeof value === "object" && value !== null && !Array.isArray(value);
34872
35363
  }
@@ -35245,463 +35736,6 @@ function collectOption(value, previous) {
35245
35736
  return [...previous, value];
35246
35737
  }
35247
35738
 
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
35739
  // src/commands/chat/chat-service.ts
35706
35740
  import { createInterface } from "node:readline/promises";
35707
35741