@dench.com/cli 3.6.4-staging.377 → 3.6.6-staging.386
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 +16 -0
- package/agent-config.ts +10 -16
- package/app-sdk.d.ts +234 -6
- package/crm.ts +107 -21
- package/dench.ts +71 -34
- package/email.ts +207 -2
- package/lib/api-schemas.ts +127 -9
- package/lib/app-sdk-catalog.generated.ts +17 -0
- package/lib/app-sdk-surface.ts +49 -0
- package/lib/command-registry.ts +115 -3
- package/lib/native-providers.ts +44 -0
- package/package.json +1 -1
- package/tools.ts +106 -61
package/lib/app-sdk-surface.ts
CHANGED
|
@@ -908,6 +908,55 @@ export const APP_SDK_SURFACE: AppSdkSurface = {
|
|
|
908
908
|
},
|
|
909
909
|
|
|
910
910
|
email: {
|
|
911
|
+
createDraft: {
|
|
912
|
+
op: "email.drafts.create",
|
|
913
|
+
doc: "Create a native mailbox draft with a stable request receipt.",
|
|
914
|
+
args: `draft: ${A("email.drafts.create")}`,
|
|
915
|
+
returns: R("email.drafts.create"),
|
|
916
|
+
},
|
|
917
|
+
draft: {
|
|
918
|
+
op: "email.drafts.get",
|
|
919
|
+
doc: "Read a native email draft and sync status.",
|
|
920
|
+
args: "draftId: string",
|
|
921
|
+
returns: R("email.drafts.get"),
|
|
922
|
+
},
|
|
923
|
+
updateDraft: {
|
|
924
|
+
op: "email.drafts.update",
|
|
925
|
+
doc: "Save the next email draft revision.",
|
|
926
|
+
args: `input: ${A("email.drafts.update")}`,
|
|
927
|
+
returns: R("email.drafts.update"),
|
|
928
|
+
},
|
|
929
|
+
sendDraft: {
|
|
930
|
+
op: "email.drafts.send",
|
|
931
|
+
doc: "Send the frozen native email draft revision.",
|
|
932
|
+
args: `input: ${A("email.drafts.send")}`,
|
|
933
|
+
returns: R("email.drafts.send"),
|
|
934
|
+
},
|
|
935
|
+
senders: {
|
|
936
|
+
op: "email.drafts.senders",
|
|
937
|
+
doc: "List eligible native mailboxes.",
|
|
938
|
+
args: `opts?: ${A("email.drafts.senders")}`,
|
|
939
|
+
returns: R("email.drafts.senders"),
|
|
940
|
+
},
|
|
941
|
+
uploadAttachment: {
|
|
942
|
+
op: "email.drafts.upload",
|
|
943
|
+
doc: "Upload attachment bytes for a native draft.",
|
|
944
|
+
args: `input: ${A("email.drafts.upload")}`,
|
|
945
|
+
returns: R("email.drafts.upload"),
|
|
946
|
+
},
|
|
947
|
+
signature: {
|
|
948
|
+
op: "email.drafts.signature",
|
|
949
|
+
doc: "Load the selected mailbox signature for visible insertion.",
|
|
950
|
+
args: "draftId: string",
|
|
951
|
+
returns: R("email.drafts.signature"),
|
|
952
|
+
},
|
|
953
|
+
sendFromMailbox: {
|
|
954
|
+
op: "email.native.send",
|
|
955
|
+
doc: "Explicitly send through a native Gmail or Outlook mailbox.",
|
|
956
|
+
args: `message: ${A("email.native.send")}`,
|
|
957
|
+
returns: R("email.native.send"),
|
|
958
|
+
},
|
|
959
|
+
|
|
911
960
|
send: {
|
|
912
961
|
op: "email.send",
|
|
913
962
|
doc: "Send one email from a verified identity.",
|
package/lib/command-registry.ts
CHANGED
|
@@ -34,6 +34,9 @@ export type CustomOperation = {
|
|
|
34
34
|
| "cronRunNow"
|
|
35
35
|
| "connectionsList"
|
|
36
36
|
| "toolDisconnect"
|
|
37
|
+
| "nativeToolSearch"
|
|
38
|
+
| "nativeToolRun"
|
|
39
|
+
| "nativeToolConnect"
|
|
37
40
|
| "billingTopup"
|
|
38
41
|
| "upgrade"
|
|
39
42
|
| "filesMove"
|
|
@@ -468,6 +471,18 @@ const rawApiOperations = [
|
|
|
468
471
|
backend: local("Backend selection is local CLI state."),
|
|
469
472
|
}),
|
|
470
473
|
|
|
474
|
+
op({
|
|
475
|
+
id: "workspace.context.sections",
|
|
476
|
+
group: "workspace",
|
|
477
|
+
summary:
|
|
478
|
+
"Read selected scoped workspace documents and a compact context index without writes.",
|
|
479
|
+
cli: "dench context --sections",
|
|
480
|
+
method: "GET",
|
|
481
|
+
path: "/context/sections",
|
|
482
|
+
requestSchema: anyObject,
|
|
483
|
+
responseSchema: anyResponse,
|
|
484
|
+
backend: convex("query", "functions/agentConfig:workspaceContext"),
|
|
485
|
+
}),
|
|
471
486
|
// Workspace orientation.
|
|
472
487
|
op({
|
|
473
488
|
id: "workspace.context",
|
|
@@ -3332,6 +3347,103 @@ const rawApiOperations = [
|
|
|
3332
3347
|
responseSchema: anyResponse,
|
|
3333
3348
|
backend: convex("mutation", "functions/emailCampaigns:deleteIdentity"),
|
|
3334
3349
|
}),
|
|
3350
|
+
op({
|
|
3351
|
+
id: "email.drafts.create",
|
|
3352
|
+
group: "email",
|
|
3353
|
+
summary:
|
|
3354
|
+
"Create an editable Dench email draft and synchronize its selected native mailbox asynchronously. Use a stable requestId for retries.",
|
|
3355
|
+
cli: "dench email draft create",
|
|
3356
|
+
method: "POST",
|
|
3357
|
+
path: "/email/drafts",
|
|
3358
|
+
requestSchema: anyObject,
|
|
3359
|
+
responseSchema: anyResponse,
|
|
3360
|
+
backend: convex("mutation", "functions/emailDrafts:create"),
|
|
3361
|
+
}),
|
|
3362
|
+
op({
|
|
3363
|
+
id: "email.drafts.get",
|
|
3364
|
+
group: "email",
|
|
3365
|
+
summary: "Read an email draft, eligible senders and provider sync status.",
|
|
3366
|
+
cli: "dench email draft show",
|
|
3367
|
+
method: "GET",
|
|
3368
|
+
path: "/email/drafts/{draftId}",
|
|
3369
|
+
requestSchema: anyObject,
|
|
3370
|
+
responseSchema: anyResponse,
|
|
3371
|
+
backend: convex("query", "functions/emailDrafts:get"),
|
|
3372
|
+
}),
|
|
3373
|
+
op({
|
|
3374
|
+
id: "email.drafts.senders",
|
|
3375
|
+
group: "email",
|
|
3376
|
+
summary: "List native mailboxes this caller may use.",
|
|
3377
|
+
cli: "dench email draft senders",
|
|
3378
|
+
method: "GET",
|
|
3379
|
+
path: "/email/drafts/senders",
|
|
3380
|
+
requestSchema: anyObject,
|
|
3381
|
+
responseSchema: anyResponse,
|
|
3382
|
+
backend: convex("query", "functions/emailDrafts:listSenders"),
|
|
3383
|
+
}),
|
|
3384
|
+
op({
|
|
3385
|
+
id: "email.drafts.update",
|
|
3386
|
+
group: "email",
|
|
3387
|
+
summary:
|
|
3388
|
+
"Save draft content using expectedRevision; sender changes require a new draft after provider binding.",
|
|
3389
|
+
cli: "dench email draft update",
|
|
3390
|
+
method: "PATCH",
|
|
3391
|
+
path: "/email/drafts/{draftId}",
|
|
3392
|
+
requestSchema: anyObject,
|
|
3393
|
+
responseSchema: anyResponse,
|
|
3394
|
+
backend: convex("mutation", "functions/emailDrafts:update"),
|
|
3395
|
+
}),
|
|
3396
|
+
op({
|
|
3397
|
+
id: "email.drafts.send",
|
|
3398
|
+
group: "email",
|
|
3399
|
+
summary:
|
|
3400
|
+
"Send the current Dench draft revision through its native mailbox. Repeated requests share a durable receipt.",
|
|
3401
|
+
cli: "dench email draft send",
|
|
3402
|
+
method: "POST",
|
|
3403
|
+
path: "/email/drafts/{draftId}/send",
|
|
3404
|
+
requestSchema: anyObject,
|
|
3405
|
+
responseSchema: anyResponse,
|
|
3406
|
+
backend: convex("mutation", "functions/emailDrafts:send"),
|
|
3407
|
+
}),
|
|
3408
|
+
op({
|
|
3409
|
+
id: "email.drafts.upload",
|
|
3410
|
+
group: "email",
|
|
3411
|
+
summary:
|
|
3412
|
+
"Upload attachment bytes authorized against this draft before adding its storage reference.",
|
|
3413
|
+
cli: "dench email draft attach",
|
|
3414
|
+
method: "POST",
|
|
3415
|
+
path: "/email/drafts/{draftId}/attachments",
|
|
3416
|
+
requestSchema: anyObject,
|
|
3417
|
+
responseSchema: anyResponse,
|
|
3418
|
+
backend: convex("action", "functions/emailDraftsNode:uploadAttachment"),
|
|
3419
|
+
}),
|
|
3420
|
+
op({
|
|
3421
|
+
id: "email.drafts.signature",
|
|
3422
|
+
group: "email",
|
|
3423
|
+
summary:
|
|
3424
|
+
"Load the selected mailbox signature for explicit visible insertion.",
|
|
3425
|
+
cli: "dench email draft signature",
|
|
3426
|
+
method: "GET",
|
|
3427
|
+
path: "/email/drafts/{draftId}/signature",
|
|
3428
|
+
requestSchema: anyObject,
|
|
3429
|
+
responseSchema: anyResponse,
|
|
3430
|
+
backend: convex("action", "functions/emailDraftsNode:loadSignature"),
|
|
3431
|
+
}),
|
|
3432
|
+
op({
|
|
3433
|
+
id: "email.native.send",
|
|
3434
|
+
group: "email",
|
|
3435
|
+
summary:
|
|
3436
|
+
"Send through a native Gmail or Outlook mailbox with a durable request receipt. Only use for an explicit send request.",
|
|
3437
|
+
cli: "dench email send --mailbox",
|
|
3438
|
+
method: "POST",
|
|
3439
|
+
path: "/email/native/send",
|
|
3440
|
+
requestSchema: anyObject,
|
|
3441
|
+
responseSchema: anyResponse,
|
|
3442
|
+
backend: {
|
|
3443
|
+
...convex("mutation", "functions/emailDrafts:create"),
|
|
3444
|
+
fixedArgs: { mode: "send" },
|
|
3445
|
+
},
|
|
3446
|
+
}),
|
|
3335
3447
|
op({
|
|
3336
3448
|
id: "email.send",
|
|
3337
3449
|
group: "email",
|
|
@@ -4825,7 +4937,7 @@ const rawApiOperations = [
|
|
|
4825
4937
|
path: "/tools/connections",
|
|
4826
4938
|
requestSchema: anyObject,
|
|
4827
4939
|
responseSchema: anyResponse,
|
|
4828
|
-
backend:
|
|
4940
|
+
backend: custom("nativeToolConnect", "bearer"),
|
|
4829
4941
|
}),
|
|
4830
4942
|
op({
|
|
4831
4943
|
id: "tool.search",
|
|
@@ -4836,7 +4948,7 @@ const rawApiOperations = [
|
|
|
4836
4948
|
path: "/tools/search",
|
|
4837
4949
|
requestSchema: anyObject,
|
|
4838
4950
|
responseSchema: anyResponse,
|
|
4839
|
-
backend:
|
|
4951
|
+
backend: custom("nativeToolSearch", "bearer"),
|
|
4840
4952
|
}),
|
|
4841
4953
|
op({
|
|
4842
4954
|
id: "tool.run",
|
|
@@ -4852,7 +4964,7 @@ const rawApiOperations = [
|
|
|
4852
4964
|
path: "/tools/run",
|
|
4853
4965
|
requestSchema: anyObject,
|
|
4854
4966
|
responseSchema: anyResponse,
|
|
4855
|
-
backend:
|
|
4967
|
+
backend: custom("nativeToolRun", "bearer"),
|
|
4856
4968
|
}),
|
|
4857
4969
|
op({
|
|
4858
4970
|
id: "tool.disconnect",
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { normalizeUnipileToolkitSlug } from "./unipile-toolkits";
|
|
2
|
+
|
|
3
|
+
/** Provider ownership is independent of connection health and search results. */
|
|
4
|
+
export function nativeToolkit(slug: string): string | null {
|
|
5
|
+
const normalized = slug
|
|
6
|
+
.trim()
|
|
7
|
+
.toLowerCase()
|
|
8
|
+
.replace(/[\s_-]+/g, "");
|
|
9
|
+
if (normalized === "slack" || normalized === "slackbot") return "slack";
|
|
10
|
+
return normalizeUnipileToolkitSlug(slug);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function nativeToolkitForTool(slug: string): string | null {
|
|
14
|
+
const parts = slug.trim().split(/[_-]+/);
|
|
15
|
+
// Providers with separator aliases (GOOGLE_CALENDAR, MICROSOFT_OUTLOOK)
|
|
16
|
+
// are resolved before considering the remainder as an action name.
|
|
17
|
+
for (let length = Math.min(3, parts.length - 1); length > 0; length--) {
|
|
18
|
+
const toolkit = nativeToolkit(parts.slice(0, length).join(""));
|
|
19
|
+
if (toolkit) return toolkit;
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function isNativeToolRecord(value: {
|
|
25
|
+
slug?: unknown;
|
|
26
|
+
tool_slug?: unknown;
|
|
27
|
+
toolkit_slug?: unknown;
|
|
28
|
+
toolkit?: unknown;
|
|
29
|
+
}): boolean {
|
|
30
|
+
const toolkit =
|
|
31
|
+
value.toolkit && typeof value.toolkit === "object"
|
|
32
|
+
? (value.toolkit as { slug?: unknown; name?: unknown })
|
|
33
|
+
: {};
|
|
34
|
+
const name =
|
|
35
|
+
value.toolkit_slug ??
|
|
36
|
+
(typeof value.toolkit === "string"
|
|
37
|
+
? value.toolkit
|
|
38
|
+
: (toolkit.slug ?? toolkit.name));
|
|
39
|
+
const slug = value.slug ?? value.tool_slug;
|
|
40
|
+
return (
|
|
41
|
+
(typeof name === "string" && nativeToolkit(name) !== null) ||
|
|
42
|
+
(typeof slug === "string" && nativeToolkitForTool(slug) !== null)
|
|
43
|
+
);
|
|
44
|
+
}
|
package/package.json
CHANGED
package/tools.ts
CHANGED
|
@@ -57,6 +57,11 @@ import {
|
|
|
57
57
|
type ConnectionSource,
|
|
58
58
|
connectionSource,
|
|
59
59
|
} from "./lib/connection-source";
|
|
60
|
+
import {
|
|
61
|
+
isNativeToolRecord,
|
|
62
|
+
nativeToolkit,
|
|
63
|
+
nativeToolkitForTool,
|
|
64
|
+
} from "./lib/native-providers";
|
|
60
65
|
import {
|
|
61
66
|
normalizeUnipileToolkitSlug,
|
|
62
67
|
unipileConnectKindFromToolkitSlug,
|
|
@@ -75,6 +80,8 @@ export type ToolCliContext = {
|
|
|
75
80
|
gatewayBaseUrl?: string;
|
|
76
81
|
gatewayHeaders?: Record<string, string>;
|
|
77
82
|
gatewayAuthError?: string;
|
|
83
|
+
/** Resolve the gateway only when a third-party operation actually needs it. */
|
|
84
|
+
resolveGatewayAuth?: () => Promise<GatewayAuthContext>;
|
|
78
85
|
/**
|
|
79
86
|
* Convex client + bearer for the Unipile half of the list. Absent on the
|
|
80
87
|
* help path and whenever the caller could not be authenticated, in which
|
|
@@ -93,7 +100,11 @@ export type ToolCliContext = {
|
|
|
93
100
|
|
|
94
101
|
type GatewayAuthContext = Pick<
|
|
95
102
|
ToolCliContext,
|
|
96
|
-
|
|
103
|
+
| "bearerToken"
|
|
104
|
+
| "gatewayBaseUrl"
|
|
105
|
+
| "gatewayHeaders"
|
|
106
|
+
| "gatewayAuthError"
|
|
107
|
+
| "resolveGatewayAuth"
|
|
97
108
|
>;
|
|
98
109
|
|
|
99
110
|
type UnipileAuthContext = Pick<ToolCliContext, "convex" | "sessionToken">;
|
|
@@ -152,43 +163,9 @@ const connectCalendarRef = makeFunctionReference<"action">(
|
|
|
152
163
|
"functions/calendarUnipileNode:createCalendarHostedAuthLink",
|
|
153
164
|
);
|
|
154
165
|
|
|
155
|
-
/**
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
* never execute, and showing them next to the ones that work just teaches an
|
|
159
|
-
* agent to pick a tool that always answers `composio_not_connected`.
|
|
160
|
-
*
|
|
161
|
-
* The set comes from the server with the tools themselves rather than being
|
|
162
|
-
* hardcoded here, so a fifth platform needs no CLI change.
|
|
163
|
-
*/
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Composio spells some toolkits without a separator (`googlecalendar`), while
|
|
167
|
-
* our registry uses the hyphenated form. Normalize through this map before the
|
|
168
|
-
* membership test, or the dead Composio calendar entries survive the filter
|
|
169
|
-
* and sit next to the working ones under two names for one product.
|
|
170
|
-
*/
|
|
171
|
-
const COMPOSIO_TOOLKIT_ALIASES: Record<string, string> = {
|
|
172
|
-
googlecalendar: "google-calendar",
|
|
173
|
-
outlookcalendar: "outlook-calendar",
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
function withoutShadowedComposioTools(
|
|
177
|
-
items: JsonRecord[],
|
|
178
|
-
ownedToolkits: Set<string>,
|
|
179
|
-
): JsonRecord[] {
|
|
180
|
-
if (ownedToolkits.size === 0) return items;
|
|
181
|
-
return items.filter((item) => {
|
|
182
|
-
const slug = (
|
|
183
|
-
stringField(item, "toolkit_slug") ??
|
|
184
|
-
stringField(asRecord(item.toolkit), "slug", "name") ??
|
|
185
|
-
""
|
|
186
|
-
)
|
|
187
|
-
.toLowerCase()
|
|
188
|
-
.replaceAll("_", "-");
|
|
189
|
-
const canonical = COMPOSIO_TOOLKIT_ALIASES[slug] ?? slug;
|
|
190
|
-
return !canonical || !ownedToolkits.has(canonical);
|
|
191
|
-
});
|
|
166
|
+
/** Native ownership survives empty searches, disconnections and registry failures. */
|
|
167
|
+
function withoutShadowedComposioTools(items: JsonRecord[]): JsonRecord[] {
|
|
168
|
+
return items.filter((item) => !isNativeToolRecord(item));
|
|
192
169
|
}
|
|
193
170
|
|
|
194
171
|
/**
|
|
@@ -277,14 +254,16 @@ export async function fetchConnectedAppsSummary(
|
|
|
277
254
|
connections: unipile.connections,
|
|
278
255
|
...unipileFields,
|
|
279
256
|
};
|
|
280
|
-
return
|
|
281
|
-
? { ...failure, reason:
|
|
282
|
-
:
|
|
257
|
+
return ctx.gatewayAuthError
|
|
258
|
+
? { ...failure, reason: ctx.gatewayAuthError }
|
|
259
|
+
: error instanceof MissingApiKeyError
|
|
260
|
+
? { ...failure, reason: error.message }
|
|
261
|
+
: { ...failure, error: errorMessage(error) };
|
|
283
262
|
}
|
|
284
263
|
const connections = [
|
|
285
264
|
...extractConnections(payload)
|
|
286
265
|
.map(compactConnection)
|
|
287
|
-
.filter((connection) => !
|
|
266
|
+
.filter((connection) => !nativeToolkit(connection.toolkit)),
|
|
288
267
|
...unipile.connections,
|
|
289
268
|
];
|
|
290
269
|
return {
|
|
@@ -305,7 +284,7 @@ async function runStatusSubcommand(ctx: ToolCliContext): Promise<void> {
|
|
|
305
284
|
: ctx.args[0];
|
|
306
285
|
if (requestedToolkit) ctx.args.shift();
|
|
307
286
|
const unipileToolkit = requestedToolkit
|
|
308
|
-
?
|
|
287
|
+
? nativeToolkit(requestedToolkit)
|
|
309
288
|
: null;
|
|
310
289
|
const filterToolkit = unipileToolkit ?? requestedToolkit;
|
|
311
290
|
|
|
@@ -369,7 +348,11 @@ async function runStatusSubcommand(ctx: ToolCliContext): Promise<void> {
|
|
|
369
348
|
}
|
|
370
349
|
|
|
371
350
|
async function runSearchSubcommand(ctx: ToolCliContext): Promise<void> {
|
|
372
|
-
const
|
|
351
|
+
const requestedToolkit = getFlag(ctx.args, "--toolkit");
|
|
352
|
+
const ownedToolkit = requestedToolkit
|
|
353
|
+
? nativeToolkit(requestedToolkit)
|
|
354
|
+
: null;
|
|
355
|
+
const toolkit = ownedToolkit ?? requestedToolkit;
|
|
373
356
|
const limitRaw = getFlag(ctx.args, "--limit");
|
|
374
357
|
const limit = limitRaw ? parsePositiveInt("--limit", limitRaw) : undefined;
|
|
375
358
|
const compact = hasFlag(ctx.args, "--compact");
|
|
@@ -395,28 +378,43 @@ async function runSearchSubcommand(ctx: ToolCliContext): Promise<void> {
|
|
|
395
378
|
if (toolkit) body.toolkit_slug = toolkit;
|
|
396
379
|
if (limit) body.limit = limit;
|
|
397
380
|
|
|
398
|
-
const [
|
|
399
|
-
|
|
381
|
+
const [gateway, unipile] = await Promise.all([
|
|
382
|
+
ownedToolkit
|
|
383
|
+
? Promise.resolve({
|
|
384
|
+
payload: {} as unknown,
|
|
385
|
+
error: undefined as string | undefined,
|
|
386
|
+
})
|
|
387
|
+
: callGateway(ctx, "POST", "/v1/composio/tools/search", body)
|
|
388
|
+
.then((payload) => ({
|
|
389
|
+
payload,
|
|
390
|
+
error: undefined as string | undefined,
|
|
391
|
+
}))
|
|
392
|
+
.catch((error) => ({
|
|
393
|
+
payload: {} as unknown,
|
|
394
|
+
error: errorMessage(error),
|
|
395
|
+
})),
|
|
400
396
|
fetchUnipileTools(ctx, { query, ...(toolkit ? { toolkit } : {}) }),
|
|
401
397
|
]);
|
|
402
398
|
|
|
403
|
-
const record = asRecord(payload) ?? {};
|
|
404
|
-
const ownedToolkits = new Set(
|
|
405
|
-
unipile.tools
|
|
406
|
-
.map((tool) => stringField(asRecord(tool.toolkit), "slug") ?? "")
|
|
407
|
-
.filter(Boolean),
|
|
408
|
-
);
|
|
399
|
+
const record = asRecord(gateway.payload) ?? {};
|
|
409
400
|
const merged = {
|
|
410
|
-
|
|
401
|
+
query,
|
|
411
402
|
items: [
|
|
403
|
+
...unipile.tools,
|
|
412
404
|
...withoutShadowedComposioTools(
|
|
413
|
-
extractSearchItems(record),
|
|
414
|
-
|
|
405
|
+
extractSearchItems(asRecord(record.data) ?? record),
|
|
406
|
+
),
|
|
407
|
+
].slice(0, limit ?? 100),
|
|
408
|
+
// Do not retain unfiltered nested tool_schemas/data from the gateway.
|
|
409
|
+
connected_toolkits: [
|
|
410
|
+
...[...connectedToolkitSet(asRecord(record.data) ?? record)].filter(
|
|
411
|
+
(slug) => !nativeToolkit(slug),
|
|
415
412
|
),
|
|
416
|
-
...unipile.tools,
|
|
417
413
|
],
|
|
418
414
|
nativeAvailable: unipile.available,
|
|
419
415
|
...(unipile.error ? { nativeError: unipile.error } : {}),
|
|
416
|
+
...(gateway.error ? { gatewayError: gateway.error } : {}),
|
|
417
|
+
...(ownedToolkit ? { toolkit: ownedToolkit } : {}),
|
|
420
418
|
};
|
|
421
419
|
|
|
422
420
|
if (ctx.jsonOutput) {
|
|
@@ -427,20 +425,27 @@ async function runSearchSubcommand(ctx: ToolCliContext): Promise<void> {
|
|
|
427
425
|
}
|
|
428
426
|
|
|
429
427
|
/**
|
|
430
|
-
*
|
|
431
|
-
* deployment routes everything to Composio exactly as it did before.
|
|
428
|
+
* Resolve a native action without interpreting a broken registry as another provider.
|
|
432
429
|
*/
|
|
433
430
|
async function resolveUnipileTool(
|
|
434
431
|
ctx: UnipileAuthContext,
|
|
435
432
|
slug: string,
|
|
436
433
|
): Promise<JsonRecord | null> {
|
|
437
|
-
if (!ctx.convex)
|
|
434
|
+
if (!ctx.convex) {
|
|
435
|
+
if (nativeToolkitForTool(slug))
|
|
436
|
+
throw new ToolCliError(
|
|
437
|
+
"native_unavailable: Sign in to Dench to use this integration.",
|
|
438
|
+
);
|
|
439
|
+
return null;
|
|
440
|
+
}
|
|
438
441
|
try {
|
|
439
442
|
return (
|
|
440
443
|
asRecord(await ctx.convex.query(getUnipileToolRef, { slug } as never)) ??
|
|
441
444
|
null
|
|
442
445
|
);
|
|
443
|
-
} catch {
|
|
446
|
+
} catch (error) {
|
|
447
|
+
if (nativeToolkitForTool(slug))
|
|
448
|
+
throw new ToolCliError(`native_unavailable: ${errorMessage(error)}`);
|
|
444
449
|
return null;
|
|
445
450
|
}
|
|
446
451
|
}
|
|
@@ -528,7 +533,11 @@ async function fetchUnipileTools(
|
|
|
528
533
|
.filter((row): row is JsonRecord => row !== undefined)
|
|
529
534
|
.map((row) =>
|
|
530
535
|
typeof row.source === "string"
|
|
531
|
-
? {
|
|
536
|
+
? {
|
|
537
|
+
...row,
|
|
538
|
+
source: connectionSource(row.source, "dench"),
|
|
539
|
+
connection_status: { is_connected: true },
|
|
540
|
+
}
|
|
532
541
|
: row,
|
|
533
542
|
),
|
|
534
543
|
};
|
|
@@ -565,9 +574,18 @@ async function runRunSubcommand(ctx: ToolCliContext): Promise<void> {
|
|
|
565
574
|
// routed to Composio, which 404s.
|
|
566
575
|
const unipileTool = await resolveUnipileTool(ctx, toolSlug);
|
|
567
576
|
if (unipileTool) {
|
|
577
|
+
if (account)
|
|
578
|
+
throw new ToolCliError(
|
|
579
|
+
"--account is for third-party connections. Pass the native mailbox/account field in --args.",
|
|
580
|
+
);
|
|
568
581
|
await runUnipileTool(ctx, toolSlug, parsedArgs, approval);
|
|
569
582
|
return;
|
|
570
583
|
}
|
|
584
|
+
if (nativeToolkitForTool(toolSlug)) {
|
|
585
|
+
throw new ToolCliError(
|
|
586
|
+
`unsupported_native_capability: ${toolSlug} is not supported by Dench's native integration. For email drafts use dench email draft create; do not reconnect or try another provider.`,
|
|
587
|
+
);
|
|
588
|
+
}
|
|
571
589
|
|
|
572
590
|
const body: Record<string, unknown> = {
|
|
573
591
|
tool_slug: toolSlug,
|
|
@@ -697,6 +715,21 @@ async function runConnectSubcommand(ctx: ToolCliContext): Promise<void> {
|
|
|
697
715
|
ctx.args.shift();
|
|
698
716
|
|
|
699
717
|
const normalizedToolkit = toolkit.trim().toLowerCase();
|
|
718
|
+
// Hosted chat owns the human connection UI. Do not mint OAuth credentials
|
|
719
|
+
// in a model command or journal them in a tool result. External CLI clients
|
|
720
|
+
// retain their ordinary link-based flow.
|
|
721
|
+
if (process.env.DENCH_CHAT_ARTIFACTS === "1") {
|
|
722
|
+
console.log(
|
|
723
|
+
JSON.stringify({
|
|
724
|
+
ok: true,
|
|
725
|
+
kind: "connect",
|
|
726
|
+
toolkit: nativeToolkit(toolkit) ?? normalizedToolkit,
|
|
727
|
+
action: "connect",
|
|
728
|
+
}),
|
|
729
|
+
);
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
|
|
700
733
|
if (normalizedToolkit === "slack" || normalizedToolkit === "slackbot") {
|
|
701
734
|
await runSlackConnectSubcommand(ctx, normalizedToolkit);
|
|
702
735
|
return;
|
|
@@ -920,6 +953,11 @@ async function callGateway(
|
|
|
920
953
|
path: string,
|
|
921
954
|
body?: Record<string, unknown>,
|
|
922
955
|
): Promise<unknown> {
|
|
956
|
+
if (ctx.resolveGatewayAuth && !ctx.bearerToken) {
|
|
957
|
+
const resolve = ctx.resolveGatewayAuth;
|
|
958
|
+
ctx.resolveGatewayAuth = undefined;
|
|
959
|
+
Object.assign(ctx, await resolve());
|
|
960
|
+
}
|
|
923
961
|
const bearerToken = requireBearerToken(ctx);
|
|
924
962
|
const baseUrl = resolveGatewayBaseUrl(ctx);
|
|
925
963
|
const init: RequestInit = {
|
|
@@ -1239,6 +1277,13 @@ function formatSearchResult(searchResult: unknown, compact = false): string {
|
|
|
1239
1277
|
const items = extractSearchItems(data);
|
|
1240
1278
|
const connectedSet = connectedToolkitSet(data);
|
|
1241
1279
|
|
|
1280
|
+
if (
|
|
1281
|
+
items.length === 0 &&
|
|
1282
|
+
typeof data?.toolkit === "string" &&
|
|
1283
|
+
nativeToolkit(data.toolkit)
|
|
1284
|
+
) {
|
|
1285
|
+
return `No matching native action for ${data.toolkit}. For email drafts use dench email draft create; otherwise inspect dench tool status ${data.toolkit} --json. Native apps never fall back to Composio.`;
|
|
1286
|
+
}
|
|
1242
1287
|
if (items.length === 0) {
|
|
1243
1288
|
return [
|
|
1244
1289
|
"No matching tools found.",
|