clementine-agent 1.10.1 → 1.10.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/cli/dashboard.js +6 -1
- package/dist/config.d.ts +6 -0
- package/dist/config.js +1 -1
- package/dist/integrations/composio/client.js +47 -15
- package/package.json +1 -1
package/dist/cli/dashboard.js
CHANGED
|
@@ -5217,11 +5217,16 @@ If the tool returns nothing or errors, return an empty array \`[]\`.`,
|
|
|
5217
5217
|
app.put('/api/settings/:key', async (req, res) => {
|
|
5218
5218
|
try {
|
|
5219
5219
|
const { key } = req.params;
|
|
5220
|
-
|
|
5220
|
+
let { value } = req.body;
|
|
5221
5221
|
if (typeof value !== 'string') {
|
|
5222
5222
|
res.status(400).json({ error: 'value must be a string' });
|
|
5223
5223
|
return;
|
|
5224
5224
|
}
|
|
5225
|
+
// Strip whitespace + invisible chars that commonly hitchhike when users
|
|
5226
|
+
// paste from a website. Without this, Composio's API rejects the key as
|
|
5227
|
+
// 401 even though the visible characters look right. Trim covers
|
|
5228
|
+
// newlines, tabs, regular spaces, and zero-width chars.
|
|
5229
|
+
value = value.trim().replace(/[-]/g, '');
|
|
5225
5230
|
// Allow known keys + any valid env var name (A-Z, 0-9, _)
|
|
5226
5231
|
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
|
|
5227
5232
|
res.status(400).json({ error: `Invalid key format: ${key}` });
|
package/dist/config.d.ts
CHANGED
|
@@ -11,6 +11,12 @@ export declare const PKG_DIR: string;
|
|
|
11
11
|
/** Data home — user data, vault, .env, logs, sessions. */
|
|
12
12
|
export declare const BASE_DIR: string;
|
|
13
13
|
import { shellEscape as _shellEscape } from './config/env-parser.js';
|
|
14
|
+
/**
|
|
15
|
+
* Look up a config value: local .env first, then process.env fallback.
|
|
16
|
+
* Keychain refs in either source are resolved lazily; failed resolution
|
|
17
|
+
* falls through to the fallback rather than returning the literal stub.
|
|
18
|
+
*/
|
|
19
|
+
export declare function getEnv(key: string, fallback?: string): string;
|
|
14
20
|
/** Merged view of process.env overlaid with .env. Use for classifyIntegrations / summarizeIntegrationStatus. */
|
|
15
21
|
export declare function envSnapshot(): Record<string, string | undefined>;
|
|
16
22
|
/** Test-only: clear the keychain ref cache so re-resolution can be tested. */
|
package/dist/config.js
CHANGED
|
@@ -84,7 +84,7 @@ function maybeResolveRef(value) {
|
|
|
84
84
|
* Keychain refs in either source are resolved lazily; failed resolution
|
|
85
85
|
* falls through to the fallback rather than returning the literal stub.
|
|
86
86
|
*/
|
|
87
|
-
function getEnv(key, fallback = '') {
|
|
87
|
+
export function getEnv(key, fallback = '') {
|
|
88
88
|
const fromLocal = maybeResolveRef(env[key]);
|
|
89
89
|
if (fromLocal !== undefined && fromLocal !== '')
|
|
90
90
|
return fromLocal;
|
|
@@ -15,7 +15,18 @@
|
|
|
15
15
|
import { Composio } from '@composio/core';
|
|
16
16
|
import { ClaudeAgentSDKProvider } from '@composio/claude-agent-sdk';
|
|
17
17
|
import pino from 'pino';
|
|
18
|
+
import { getEnv } from '../../config.js';
|
|
18
19
|
const logger = pino({ name: 'clementine.composio' });
|
|
20
|
+
// `process.env` is intentionally NOT populated from .env (config.ts keeps
|
|
21
|
+
// secrets out of the SDK subprocess). Reading process.env.COMPOSIO_API_KEY
|
|
22
|
+
// directly works during the dashboard's hot-reload (PUT handler mutates
|
|
23
|
+
// process.env), but is empty after a fresh daemon restart even if the key
|
|
24
|
+
// is in .env. Use this helper everywhere we read Composio env vars: it
|
|
25
|
+
// prefers process.env (hot-reload from dashboard) and falls back to the
|
|
26
|
+
// .env file via getEnv (survives restarts).
|
|
27
|
+
function readComposioEnv(key) {
|
|
28
|
+
return process.env[key] || getEnv(key, '');
|
|
29
|
+
}
|
|
19
30
|
// Curated set surfaced in the dashboard. Composio exposes 1000+ — rendering
|
|
20
31
|
// them all is noisy. Users can still connect anything by editing this list.
|
|
21
32
|
export const CURATED_TOOLKITS = [
|
|
@@ -50,7 +61,7 @@ let singleton = null;
|
|
|
50
61
|
export function getComposio() {
|
|
51
62
|
if (singleton)
|
|
52
63
|
return singleton;
|
|
53
|
-
const apiKey =
|
|
64
|
+
const apiKey = readComposioEnv('COMPOSIO_API_KEY');
|
|
54
65
|
if (!apiKey)
|
|
55
66
|
return null;
|
|
56
67
|
singleton = new Composio({
|
|
@@ -60,7 +71,7 @@ export function getComposio() {
|
|
|
60
71
|
return singleton;
|
|
61
72
|
}
|
|
62
73
|
export function isComposioEnabled() {
|
|
63
|
-
return Boolean(
|
|
74
|
+
return Boolean(readComposioEnv('COMPOSIO_API_KEY'));
|
|
64
75
|
}
|
|
65
76
|
/**
|
|
66
77
|
* Discard the cached client + identity cache so the next call to getComposio()
|
|
@@ -89,13 +100,14 @@ export async function getPreferredUserId() {
|
|
|
89
100
|
// with the most existing connections, falling back to this constant.
|
|
90
101
|
const DEFAULT_NEW_CONNECTION_USER_ID = 'default';
|
|
91
102
|
export function clementineUserId() {
|
|
92
|
-
return
|
|
103
|
+
return readComposioEnv('COMPOSIO_USER_ID') || DEFAULT_NEW_CONNECTION_USER_ID;
|
|
93
104
|
}
|
|
94
105
|
// Cached after first detection — avoids extra API calls per authorize.
|
|
95
106
|
let detectedPreferredUserId = null;
|
|
96
107
|
async function detectPreferredUserId(composio) {
|
|
97
|
-
|
|
98
|
-
|
|
108
|
+
const explicit = readComposioEnv('COMPOSIO_USER_ID');
|
|
109
|
+
if (explicit)
|
|
110
|
+
return explicit;
|
|
99
111
|
if (detectedPreferredUserId)
|
|
100
112
|
return detectedPreferredUserId;
|
|
101
113
|
try {
|
|
@@ -383,9 +395,17 @@ export async function authorizeToolkit(slug, opts) {
|
|
|
383
395
|
// 1. Find or create an auth config. session.authorize() doesn't auto-create
|
|
384
396
|
// so we have to pass authConfigId explicitly to connectedAccounts.initiate.
|
|
385
397
|
let authConfigId;
|
|
386
|
-
|
|
398
|
+
let existingConfig;
|
|
399
|
+
try {
|
|
400
|
+
existingConfig = (await composio.authConfigs.list({ toolkit: slug })).items[0];
|
|
401
|
+
}
|
|
402
|
+
catch (err) {
|
|
403
|
+
logger.error({ err, slug, step: 'authConfigs.list' }, 'Composio authorize failed');
|
|
404
|
+
throw err;
|
|
405
|
+
}
|
|
387
406
|
if (existingConfig) {
|
|
388
407
|
authConfigId = existingConfig.id;
|
|
408
|
+
logger.debug({ slug, authConfigId }, 'Reusing existing auth config');
|
|
389
409
|
}
|
|
390
410
|
else {
|
|
391
411
|
try {
|
|
@@ -394,12 +414,17 @@ export async function authorizeToolkit(slug, opts) {
|
|
|
394
414
|
name: `${displayNameFor(slug)} Auth Config`,
|
|
395
415
|
});
|
|
396
416
|
authConfigId = created.id;
|
|
417
|
+
logger.debug({ slug, authConfigId }, 'Created managed auth config');
|
|
397
418
|
}
|
|
398
419
|
catch (err) {
|
|
399
|
-
// 400 → Composio doesn't host a managed OAuth app for this toolkit;
|
|
400
|
-
// user must register their own via the Composio dashboard.
|
|
401
420
|
const status = err?.status;
|
|
402
|
-
|
|
421
|
+
logger.warn({ err, slug, status, step: 'authConfigs.create' }, 'authConfigs.create rejected — likely needs BYO setup');
|
|
422
|
+
// 400 → Composio doesn't host a managed OAuth app for this toolkit.
|
|
423
|
+
// 401/403 → key lacks permission to create managed auth configs (common
|
|
424
|
+
// for Google services where Composio's plan tier requires you to
|
|
425
|
+
// register your own Google OAuth project). Either way, the user fix
|
|
426
|
+
// is the same: set it up once in Composio's auth-configs dashboard.
|
|
427
|
+
if (status === 400 || status === 401 || status === 403) {
|
|
403
428
|
throw new ComposioNeedsAuthConfigError(slug, String(err));
|
|
404
429
|
}
|
|
405
430
|
throw err;
|
|
@@ -412,12 +437,19 @@ export async function authorizeToolkit(slug, opts) {
|
|
|
412
437
|
// freshly authorized Gmail lands next to the existing Outlook (etc.) under
|
|
413
438
|
// the same user_id. Falls back to env override or "default".
|
|
414
439
|
const userId = await detectPreferredUserId(composio);
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
440
|
+
try {
|
|
441
|
+
const conn = await composio.connectedAccounts.initiate(userId, authConfigId, {
|
|
442
|
+
...(existing.length > 0 ? { allowMultiple: true } : {}),
|
|
443
|
+
...(opts?.callbackUrl ? { callbackUrl: opts.callbackUrl } : {}),
|
|
444
|
+
...(opts?.alias ? { alias: opts.alias } : {}),
|
|
445
|
+
});
|
|
446
|
+
logger.debug({ slug, userId, connectionId: conn.id }, 'Initiated connection');
|
|
447
|
+
return { redirectUrl: conn.redirectUrl ?? null, connectionId: conn.id };
|
|
448
|
+
}
|
|
449
|
+
catch (err) {
|
|
450
|
+
logger.error({ err, slug, userId, authConfigId, step: 'connectedAccounts.initiate' }, 'Composio initiate failed');
|
|
451
|
+
throw err;
|
|
452
|
+
}
|
|
421
453
|
}
|
|
422
454
|
export async function disconnectToolkit(connectionId) {
|
|
423
455
|
const composio = getComposio();
|