thepopebot 1.2.75-beta.4 → 1.2.75-beta.6
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/lib/ai/tools.js +4 -0
- package/lib/chat/actions.js +16 -1
- package/lib/chat/components/chat-header.js +4 -0
- package/lib/chat/components/chat-header.jsx +4 -0
- package/lib/chat/components/settings-jobs-page.js +13 -2
- package/lib/chat/components/settings-jobs-page.jsx +15 -1
- package/lib/chat/components/settings-secrets-layout.js +1 -1
- package/lib/chat/components/settings-secrets-layout.jsx +1 -1
- package/lib/code/actions.js +41 -10
- package/lib/code/port-forwards.js +3 -0
- package/lib/db/config.js +23 -0
- package/lib/maintenance.js +8 -1
- package/lib/tools/docker.js +12 -2
- package/package.json +1 -1
- package/setup/setup-ssl.mjs +13 -6
- package/templates/.gitignore.template +1 -0
- package/templates/docker-compose.custom.yml +6 -2
package/lib/ai/tools.js
CHANGED
|
@@ -54,6 +54,9 @@ const agentChatCodingTool = tool(
|
|
|
54
54
|
const codingAgent = getConfig('CODING_AGENT') || 'claude-code';
|
|
55
55
|
const containerName = `${codingAgent}-headless-${randomUUID().slice(0, 8)}`;
|
|
56
56
|
|
|
57
|
+
const { createAgentJobApiKey } = await import('../db/api-keys.js');
|
|
58
|
+
const { key: agentJobToken } = createAgentJobApiKey(randomUUID());
|
|
59
|
+
|
|
57
60
|
const { runHeadlessContainer, tailContainerLogs, waitForContainer, removeContainer } = await import('../tools/docker.js');
|
|
58
61
|
await runHeadlessContainer({
|
|
59
62
|
containerName,
|
|
@@ -64,6 +67,7 @@ const agentChatCodingTool = tool(
|
|
|
64
67
|
taskPrompt: prompt,
|
|
65
68
|
mode,
|
|
66
69
|
injectSecrets: true,
|
|
70
|
+
agentJobToken,
|
|
67
71
|
});
|
|
68
72
|
|
|
69
73
|
const streamCallback = runtime.configurable.streamCallback;
|
package/lib/chat/actions.js
CHANGED
|
@@ -1270,7 +1270,7 @@ export async function initiateOAuthFlow({ secretName, clientId, clientSecret, to
|
|
|
1270
1270
|
try {
|
|
1271
1271
|
const { createOAuthState } = await import('../oauth/helper.js');
|
|
1272
1272
|
const redirectUri = `${process.env.AUTH_URL}/api/oauth/callback`;
|
|
1273
|
-
const state = createOAuthState({ secretName, clientId, clientSecret, tokenUrl, secretType: secretType || 'agent_job_secret', returnPath: returnPath || '/admin/event-handler/agent-
|
|
1273
|
+
const state = createOAuthState({ secretName, clientId, clientSecret, tokenUrl, secretType: secretType || 'agent_job_secret', returnPath: returnPath || '/admin/event-handler/agent-secrets' });
|
|
1274
1274
|
return { state, redirectUri };
|
|
1275
1275
|
} catch (err) {
|
|
1276
1276
|
console.error('Failed to initiate OAuth flow:', err);
|
|
@@ -1278,6 +1278,21 @@ export async function initiateOAuthFlow({ secretName, clientId, clientSecret, to
|
|
|
1278
1278
|
}
|
|
1279
1279
|
}
|
|
1280
1280
|
|
|
1281
|
+
/**
|
|
1282
|
+
* Get stored OAuth credentials for an agent job secret (for re-authorization pre-fill).
|
|
1283
|
+
* @param {string} name
|
|
1284
|
+
*/
|
|
1285
|
+
export async function getOAuthSecretCredentials(name) {
|
|
1286
|
+
await requireAuth();
|
|
1287
|
+
try {
|
|
1288
|
+
const { getAgentJobSecretOAuthCredentials } = await import('../db/config.js');
|
|
1289
|
+
return getAgentJobSecretOAuthCredentials(name) || { error: 'Not an OAuth secret' };
|
|
1290
|
+
} catch (err) {
|
|
1291
|
+
console.error('Failed to get OAuth credentials:', err);
|
|
1292
|
+
return { error: 'Failed to get credentials' };
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1281
1296
|
/**
|
|
1282
1297
|
* Delete an agent job secret.
|
|
1283
1298
|
* @param {string} name
|
|
@@ -30,6 +30,7 @@ function ChatHeader({ chatId: chatIdProp, workspaceId }) {
|
|
|
30
30
|
setStarred(data.starred || 0);
|
|
31
31
|
setResolvedChatId(data.chatId);
|
|
32
32
|
if (data.chatMode) setChatMode(data.chatMode);
|
|
33
|
+
document.title = data.title;
|
|
33
34
|
}
|
|
34
35
|
}).catch(() => {
|
|
35
36
|
});
|
|
@@ -41,6 +42,7 @@ function ChatHeader({ chatId: chatIdProp, workspaceId }) {
|
|
|
41
42
|
setTitle(data.title);
|
|
42
43
|
setStarred(data.starred || 0);
|
|
43
44
|
if (data.chatMode) setChatMode(data.chatMode);
|
|
45
|
+
document.title = data.title;
|
|
44
46
|
}
|
|
45
47
|
}).catch(() => {
|
|
46
48
|
});
|
|
@@ -51,6 +53,7 @@ function ChatHeader({ chatId: chatIdProp, workspaceId }) {
|
|
|
51
53
|
if (e.detail.chatId === chatId) {
|
|
52
54
|
setTitle(e.detail.title);
|
|
53
55
|
if (e.detail.chatMode) setChatMode(e.detail.chatMode);
|
|
56
|
+
document.title = e.detail.title;
|
|
54
57
|
}
|
|
55
58
|
};
|
|
56
59
|
const starHandler = (e) => {
|
|
@@ -63,6 +66,7 @@ function ChatHeader({ chatId: chatIdProp, workspaceId }) {
|
|
|
63
66
|
return () => {
|
|
64
67
|
window.removeEventListener("chatTitleUpdated", titleHandler);
|
|
65
68
|
window.removeEventListener("chatStarUpdated", starHandler);
|
|
69
|
+
document.title = "ThePopeBot";
|
|
66
70
|
};
|
|
67
71
|
}, [fetchMeta, chatId]);
|
|
68
72
|
useEffect(() => {
|
|
@@ -38,6 +38,7 @@ export function ChatHeader({ chatId: chatIdProp, workspaceId }) {
|
|
|
38
38
|
setStarred(data.starred || 0);
|
|
39
39
|
setResolvedChatId(data.chatId);
|
|
40
40
|
if (data.chatMode) setChatMode(data.chatMode);
|
|
41
|
+
document.title = data.title;
|
|
41
42
|
}
|
|
42
43
|
})
|
|
43
44
|
.catch(() => {});
|
|
@@ -51,6 +52,7 @@ export function ChatHeader({ chatId: chatIdProp, workspaceId }) {
|
|
|
51
52
|
setTitle(data.title);
|
|
52
53
|
setStarred(data.starred || 0);
|
|
53
54
|
if (data.chatMode) setChatMode(data.chatMode);
|
|
55
|
+
document.title = data.title;
|
|
54
56
|
}
|
|
55
57
|
})
|
|
56
58
|
.catch(() => {});
|
|
@@ -62,6 +64,7 @@ export function ChatHeader({ chatId: chatIdProp, workspaceId }) {
|
|
|
62
64
|
if (e.detail.chatId === chatId) {
|
|
63
65
|
setTitle(e.detail.title);
|
|
64
66
|
if (e.detail.chatMode) setChatMode(e.detail.chatMode);
|
|
67
|
+
document.title = e.detail.title;
|
|
65
68
|
}
|
|
66
69
|
};
|
|
67
70
|
const starHandler = (e) => {
|
|
@@ -74,6 +77,7 @@ export function ChatHeader({ chatId: chatIdProp, workspaceId }) {
|
|
|
74
77
|
return () => {
|
|
75
78
|
window.removeEventListener('chatTitleUpdated', titleHandler);
|
|
76
79
|
window.removeEventListener('chatStarUpdated', starHandler);
|
|
80
|
+
document.title = 'ThePopeBot';
|
|
77
81
|
};
|
|
78
82
|
}, [fetchMeta, chatId]);
|
|
79
83
|
|
|
@@ -8,7 +8,8 @@ import {
|
|
|
8
8
|
getAgentJobSecrets,
|
|
9
9
|
updateAgentJobSecret,
|
|
10
10
|
deleteAgentJobSecretAction,
|
|
11
|
-
initiateOAuthFlow
|
|
11
|
+
initiateOAuthFlow,
|
|
12
|
+
getOAuthSecretCredentials
|
|
12
13
|
} from "../actions.js";
|
|
13
14
|
function buildProviderOptions() {
|
|
14
15
|
const options = [];
|
|
@@ -170,6 +171,16 @@ function AddSecretDialog({ open, onAdd, onCancel, onOAuthSuccess, editingSecret
|
|
|
170
171
|
setCopied(false);
|
|
171
172
|
setRedirectUri(`${window.location.origin}/api/oauth/callback`);
|
|
172
173
|
if (!editingSecret) setTimeout(() => nameRef.current?.focus(), 50);
|
|
174
|
+
if (editingSecret?.key) {
|
|
175
|
+
getOAuthSecretCredentials(editingSecret.key).then((creds) => {
|
|
176
|
+
if (creds && !creds.error) {
|
|
177
|
+
setClientId(creds.clientId);
|
|
178
|
+
setClientSecret(creds.clientSecret);
|
|
179
|
+
const match = PROVIDER_OPTIONS.find((o) => o.tokenUrl === creds.tokenUrl);
|
|
180
|
+
if (match) setSelectedOption(match.id);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
}
|
|
173
184
|
}
|
|
174
185
|
return () => {
|
|
175
186
|
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
|
@@ -236,7 +247,7 @@ function AddSecretDialog({ open, onAdd, onCancel, onOAuthSuccess, editingSecret
|
|
|
236
247
|
tokenUrl: opt.tokenUrl,
|
|
237
248
|
scopes,
|
|
238
249
|
secretType: "agent_job_secret",
|
|
239
|
-
returnPath: "/admin/event-handler/agent-
|
|
250
|
+
returnPath: "/admin/event-handler/agent-secrets"
|
|
240
251
|
});
|
|
241
252
|
if (result?.error) {
|
|
242
253
|
setError(result.error);
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
updateAgentJobSecret,
|
|
10
10
|
deleteAgentJobSecretAction,
|
|
11
11
|
initiateOAuthFlow,
|
|
12
|
+
getOAuthSecretCredentials,
|
|
12
13
|
} from '../actions.js';
|
|
13
14
|
|
|
14
15
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -225,6 +226,19 @@ function AddSecretDialog({ open, onAdd, onCancel, onOAuthSuccess, editingSecret
|
|
|
225
226
|
setCopied(false);
|
|
226
227
|
setRedirectUri(`${window.location.origin}/api/oauth/callback`);
|
|
227
228
|
if (!editingSecret) setTimeout(() => nameRef.current?.focus(), 50);
|
|
229
|
+
|
|
230
|
+
// Pre-fill OAuth credentials from stored secret
|
|
231
|
+
if (editingSecret?.key) {
|
|
232
|
+
getOAuthSecretCredentials(editingSecret.key).then((creds) => {
|
|
233
|
+
if (creds && !creds.error) {
|
|
234
|
+
setClientId(creds.clientId);
|
|
235
|
+
setClientSecret(creds.clientSecret);
|
|
236
|
+
// Auto-select provider by matching tokenUrl
|
|
237
|
+
const match = PROVIDER_OPTIONS.find((o) => o.tokenUrl === creds.tokenUrl);
|
|
238
|
+
if (match) setSelectedOption(match.id);
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
}
|
|
228
242
|
}
|
|
229
243
|
return () => {
|
|
230
244
|
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
|
@@ -306,7 +320,7 @@ function AddSecretDialog({ open, onAdd, onCancel, onOAuthSuccess, editingSecret
|
|
|
306
320
|
tokenUrl: opt.tokenUrl,
|
|
307
321
|
scopes,
|
|
308
322
|
secretType: 'agent_job_secret',
|
|
309
|
-
returnPath: '/admin/event-handler/agent-
|
|
323
|
+
returnPath: '/admin/event-handler/agent-secrets',
|
|
310
324
|
});
|
|
311
325
|
|
|
312
326
|
if (result?.error) {
|
|
@@ -30,7 +30,7 @@ const EVENT_HANDLER_TABS = [
|
|
|
30
30
|
{ id: "llms", label: "LLMs", href: "/admin/event-handler/llms" },
|
|
31
31
|
{ id: "chat", label: "Chat", href: "/admin/event-handler/chat" },
|
|
32
32
|
{ id: "coding-agents", label: "Coding Agents", href: "/admin/event-handler/coding-agents" },
|
|
33
|
-
{ id: "agent-
|
|
33
|
+
{ id: "agent-secrets", label: "Agent Secrets", href: "/admin/event-handler/agent-secrets" },
|
|
34
34
|
{ id: "webhooks", label: "Webhooks", href: "/admin/event-handler/webhooks" },
|
|
35
35
|
{ id: "telegram", label: "Telegram", href: "/admin/event-handler/telegram" },
|
|
36
36
|
{ id: "voice", label: "Voice", href: "/admin/event-handler/voice" }
|
|
@@ -52,7 +52,7 @@ const EVENT_HANDLER_TABS = [
|
|
|
52
52
|
{ id: 'llms', label: 'LLMs', href: '/admin/event-handler/llms' },
|
|
53
53
|
{ id: 'chat', label: 'Chat', href: '/admin/event-handler/chat' },
|
|
54
54
|
{ id: 'coding-agents', label: 'Coding Agents', href: '/admin/event-handler/coding-agents' },
|
|
55
|
-
{ id: 'agent-
|
|
55
|
+
{ id: 'agent-secrets', label: 'Agent Secrets', href: '/admin/event-handler/agent-secrets' },
|
|
56
56
|
{ id: 'webhooks', label: 'Webhooks', href: '/admin/event-handler/webhooks' },
|
|
57
57
|
{ id: 'telegram', label: 'Telegram', href: '/admin/event-handler/telegram' },
|
|
58
58
|
{ id: 'voice', label: 'Voice', href: '/admin/event-handler/voice' },
|
package/lib/code/actions.js
CHANGED
|
@@ -138,6 +138,12 @@ export async function ensureCodeWorkspaceContainer(id) {
|
|
|
138
138
|
const chat = getChatByWorkspaceId(id);
|
|
139
139
|
const injectSecrets = chat?.chatMode === 'agent';
|
|
140
140
|
|
|
141
|
+
let agentJobToken;
|
|
142
|
+
if (injectSecrets) {
|
|
143
|
+
const { createAgentJobApiKey } = await import('../db/api-keys.js');
|
|
144
|
+
({ key: agentJobToken } = createAgentJobApiKey(id));
|
|
145
|
+
}
|
|
146
|
+
|
|
141
147
|
try {
|
|
142
148
|
const { inspectContainer, startContainer, removeContainer, runInteractiveContainer } =
|
|
143
149
|
await import('../tools/docker.js');
|
|
@@ -153,6 +159,7 @@ export async function ensureCodeWorkspaceContainer(id) {
|
|
|
153
159
|
featureBranch: workspace.featureBranch,
|
|
154
160
|
workspaceId: id,
|
|
155
161
|
injectSecrets,
|
|
162
|
+
agentJobToken,
|
|
156
163
|
});
|
|
157
164
|
return { status: 'created' };
|
|
158
165
|
}
|
|
@@ -181,6 +188,7 @@ export async function ensureCodeWorkspaceContainer(id) {
|
|
|
181
188
|
featureBranch: workspace.featureBranch,
|
|
182
189
|
workspaceId: id,
|
|
183
190
|
injectSecrets,
|
|
191
|
+
agentJobToken,
|
|
184
192
|
});
|
|
185
193
|
return { status: 'created' };
|
|
186
194
|
} catch (err) {
|
|
@@ -209,6 +217,12 @@ export async function startInteractiveMode(id) {
|
|
|
209
217
|
const chat = getChatByWorkspaceId(id);
|
|
210
218
|
const injectSecrets = chat?.chatMode === 'agent';
|
|
211
219
|
|
|
220
|
+
let agentJobToken;
|
|
221
|
+
if (injectSecrets) {
|
|
222
|
+
const { createAgentJobApiKey } = await import('../db/api-keys.js');
|
|
223
|
+
({ key: agentJobToken } = createAgentJobApiKey(id));
|
|
224
|
+
}
|
|
225
|
+
|
|
212
226
|
try {
|
|
213
227
|
const { getConfig } = await import('../config.js');
|
|
214
228
|
const agent = getConfig('CODING_AGENT') || 'claude-code';
|
|
@@ -223,6 +237,7 @@ export async function startInteractiveMode(id) {
|
|
|
223
237
|
featureBranch: workspace.featureBranch,
|
|
224
238
|
workspaceId: id,
|
|
225
239
|
injectSecrets,
|
|
240
|
+
agentJobToken,
|
|
226
241
|
});
|
|
227
242
|
|
|
228
243
|
updateContainerName(id, containerName);
|
|
@@ -543,13 +558,21 @@ export async function getWorkspaceDiffStats(id, authenticatedUser) {
|
|
|
543
558
|
const { execSync } = await import('child_process');
|
|
544
559
|
const opts = { cwd: repoPath, encoding: 'utf8', timeout: 5000 };
|
|
545
560
|
|
|
546
|
-
const featureBranch = workspace.featureBranch || workspace.branch || 'main';
|
|
547
561
|
const baseBranch = workspace.branch || 'main';
|
|
548
|
-
let
|
|
562
|
+
let currentBranch;
|
|
549
563
|
try {
|
|
550
|
-
execSync(`git -c safe.directory='*' rev-parse --
|
|
551
|
-
|
|
552
|
-
} catch {
|
|
564
|
+
const detected = execSync(`git -c safe.directory='*' rev-parse --abbrev-ref HEAD`, opts).trim();
|
|
565
|
+
currentBranch = (detected && detected !== 'HEAD') ? detected : null;
|
|
566
|
+
} catch { /* leave null */ }
|
|
567
|
+
let diffRef;
|
|
568
|
+
if (currentBranch) {
|
|
569
|
+
try {
|
|
570
|
+
execSync(`git -c safe.directory='*' rev-parse --verify origin/${currentBranch} 2>/dev/null`, opts);
|
|
571
|
+
diffRef = `origin/${currentBranch}`;
|
|
572
|
+
} catch {
|
|
573
|
+
diffRef = `origin/${baseBranch}`;
|
|
574
|
+
}
|
|
575
|
+
} else {
|
|
553
576
|
diffRef = `origin/${baseBranch}`;
|
|
554
577
|
}
|
|
555
578
|
|
|
@@ -618,13 +641,21 @@ export async function getWorkspaceDiffFull(id, authenticatedUser) {
|
|
|
618
641
|
const { execSync } = await import('child_process');
|
|
619
642
|
const opts = { cwd: repoPath, encoding: 'utf8', timeout: 10000 };
|
|
620
643
|
|
|
621
|
-
const featureBranch = workspace.featureBranch || workspace.branch || 'main';
|
|
622
644
|
const baseBranch = workspace.branch || 'main';
|
|
623
|
-
let
|
|
645
|
+
let currentBranch;
|
|
624
646
|
try {
|
|
625
|
-
execSync(`git -c safe.directory='*' rev-parse --
|
|
626
|
-
|
|
627
|
-
} catch {
|
|
647
|
+
const detected = execSync(`git -c safe.directory='*' rev-parse --abbrev-ref HEAD`, opts).trim();
|
|
648
|
+
currentBranch = (detected && detected !== 'HEAD') ? detected : null;
|
|
649
|
+
} catch { /* leave null */ }
|
|
650
|
+
let diffRef;
|
|
651
|
+
if (currentBranch) {
|
|
652
|
+
try {
|
|
653
|
+
execSync(`git -c safe.directory='*' rev-parse --verify origin/${currentBranch} 2>/dev/null`, opts);
|
|
654
|
+
diffRef = `origin/${currentBranch}`;
|
|
655
|
+
} catch {
|
|
656
|
+
diffRef = `origin/${baseBranch}`;
|
|
657
|
+
}
|
|
658
|
+
} else {
|
|
628
659
|
diffRef = `origin/${baseBranch}`;
|
|
629
660
|
}
|
|
630
661
|
|
|
@@ -143,6 +143,9 @@ function writeTraefikConfig() {
|
|
|
143
143
|
if (sslDomain) {
|
|
144
144
|
lines.push(' tls:');
|
|
145
145
|
lines.push(' certResolver: letsencrypt');
|
|
146
|
+
lines.push(' domains:');
|
|
147
|
+
lines.push(` - main: ${sslDomain}`);
|
|
148
|
+
lines.push(` sans: "*.${sslDomain}"`);
|
|
146
149
|
}
|
|
147
150
|
lines.push(` service: ${key}`);
|
|
148
151
|
}
|
package/lib/db/config.js
CHANGED
|
@@ -254,6 +254,29 @@ export function setAgentJobSecret(key, value, userId) {
|
|
|
254
254
|
.run();
|
|
255
255
|
}
|
|
256
256
|
|
|
257
|
+
/**
|
|
258
|
+
* Get OAuth credentials stored with an agent job secret.
|
|
259
|
+
* Returns { clientId, clientSecret, tokenUrl } if the secret is oauth2, null otherwise.
|
|
260
|
+
* @param {string} key
|
|
261
|
+
*/
|
|
262
|
+
export function getAgentJobSecretOAuthCredentials(key) {
|
|
263
|
+
const db = getDb();
|
|
264
|
+
const row = db
|
|
265
|
+
.select({ value: settings.value })
|
|
266
|
+
.from(settings)
|
|
267
|
+
.where(and(eq(settings.type, 'agent_job_secret'), eq(settings.key, key)))
|
|
268
|
+
.get();
|
|
269
|
+
if (!row) return null;
|
|
270
|
+
try {
|
|
271
|
+
const decrypted = decrypt(JSON.parse(row.value));
|
|
272
|
+
const parsed = JSON.parse(decrypted);
|
|
273
|
+
if (parsed.type === 'oauth2' && parsed.clientId && parsed.clientSecret) {
|
|
274
|
+
return { clientId: parsed.clientId, clientSecret: parsed.clientSecret, tokenUrl: parsed.tokenUrl };
|
|
275
|
+
}
|
|
276
|
+
} catch {}
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
|
|
257
280
|
/**
|
|
258
281
|
* Delete an agent job secret.
|
|
259
282
|
* @param {string} key
|
package/lib/maintenance.js
CHANGED
|
@@ -24,12 +24,19 @@ function cleanExpiredAgentJobKeys() {
|
|
|
24
24
|
}
|
|
25
25
|
invalidateApiKeyCache();
|
|
26
26
|
console.log(`[maintenance] Deleted ${expiredIds.length} expired agent job key(s)`);
|
|
27
|
+
} else {
|
|
28
|
+
console.log(`[maintenance] No expired agent job keys (${rows.length} active)`);
|
|
27
29
|
}
|
|
28
30
|
} catch (err) {
|
|
29
31
|
console.error('[maintenance] cleanExpiredAgentJobKeys failed:', err);
|
|
30
32
|
}
|
|
31
33
|
}
|
|
32
34
|
|
|
35
|
+
function runMaintenance() {
|
|
36
|
+
console.log('[maintenance] Running maintenance...');
|
|
37
|
+
cleanExpiredAgentJobKeys();
|
|
38
|
+
}
|
|
39
|
+
|
|
33
40
|
export function startMaintenanceCron() {
|
|
34
|
-
cron.schedule('0 * * * *',
|
|
41
|
+
cron.schedule('0 * * * *', runMaintenance);
|
|
35
42
|
}
|
package/lib/tools/docker.js
CHANGED
|
@@ -196,7 +196,7 @@ async function runContainer({ containerName, image, env = [], workingDir, hostCo
|
|
|
196
196
|
* @param {boolean} [options.injectSecrets] - Inject agent job secrets into container env
|
|
197
197
|
* @returns {Promise<{containerId: string, containerName: string}>}
|
|
198
198
|
*/
|
|
199
|
-
async function runInteractiveContainer({ containerName, repo, branch, codingAgent, featureBranch, workspaceId, injectSecrets, continueSession = true }) {
|
|
199
|
+
async function runInteractiveContainer({ containerName, repo, branch, codingAgent, featureBranch, workspaceId, injectSecrets, agentJobToken, continueSession = true }) {
|
|
200
200
|
const agent = codingAgent || getConfig('CODING_AGENT') || 'claude-code';
|
|
201
201
|
const version = process.env.THEPOPEBOT_VERSION;
|
|
202
202
|
const image = `stephengpope/thepopebot:coding-agent-${agent}-${version}`;
|
|
@@ -234,6 +234,11 @@ async function runInteractiveContainer({ containerName, repo, branch, codingAgen
|
|
|
234
234
|
if (jobSecrets.length > 0) {
|
|
235
235
|
env.push(`AGENT_JOB_SECRETS=${JSON.stringify(Object.fromEntries(jobSecrets.map(s => [s.key, s.value])))}`);
|
|
236
236
|
}
|
|
237
|
+
if (agentJobToken) {
|
|
238
|
+
env.push(`AGENT_JOB_TOKEN=${agentJobToken}`);
|
|
239
|
+
const appUrl = getConfig('APP_URL');
|
|
240
|
+
if (appUrl) env.push(`APP_URL=${appUrl}`);
|
|
241
|
+
}
|
|
237
242
|
}
|
|
238
243
|
|
|
239
244
|
const hostConfig = {};
|
|
@@ -394,7 +399,7 @@ function buildAgentAuthEnv(agent) {
|
|
|
394
399
|
* @param {boolean} [options.injectSecrets] - Inject agent job secrets into container env
|
|
395
400
|
* @returns {Promise<{containerId: string, containerName: string}>}
|
|
396
401
|
*/
|
|
397
|
-
async function runHeadlessContainer({ containerName, repo, branch, featureBranch, workspaceId, taskPrompt, mode = 'plan', codingAgent, systemPrompt, continueSession = true, injectSecrets }) {
|
|
402
|
+
async function runHeadlessContainer({ containerName, repo, branch, featureBranch, workspaceId, taskPrompt, mode = 'plan', codingAgent, systemPrompt, continueSession = true, injectSecrets, agentJobToken }) {
|
|
398
403
|
const agent = codingAgent || getConfig('CODING_AGENT') || 'claude-code';
|
|
399
404
|
const version = process.env.THEPOPEBOT_VERSION;
|
|
400
405
|
const image = `stephengpope/thepopebot:coding-agent-${agent}-${version}`;
|
|
@@ -441,6 +446,11 @@ async function runHeadlessContainer({ containerName, repo, branch, featureBranch
|
|
|
441
446
|
if (jobSecrets.length > 0) {
|
|
442
447
|
env.push(`AGENT_JOB_SECRETS=${JSON.stringify(Object.fromEntries(jobSecrets.map(s => [s.key, s.value])))}`);
|
|
443
448
|
}
|
|
449
|
+
if (agentJobToken) {
|
|
450
|
+
env.push(`AGENT_JOB_TOKEN=${agentJobToken}`);
|
|
451
|
+
const appUrl = getConfig('APP_URL');
|
|
452
|
+
if (appUrl) env.push(`APP_URL=${appUrl}`);
|
|
453
|
+
}
|
|
444
454
|
}
|
|
445
455
|
|
|
446
456
|
const hostConfig = {};
|
package/package.json
CHANGED
package/setup/setup-ssl.mjs
CHANGED
|
@@ -5,9 +5,12 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Prompts for domain, DNS provider, API credentials, and server address.
|
|
7
7
|
* Creates the wildcard DNS record via the provider's API.
|
|
8
|
-
* Writes
|
|
8
|
+
* Writes SSL config to .env, DNS provider credentials to .env.traefik,
|
|
9
|
+
* and switches COMPOSE_FILE to docker-compose.custom.yml.
|
|
9
10
|
*/
|
|
10
11
|
|
|
12
|
+
import fs from 'fs';
|
|
13
|
+
import path from 'path';
|
|
11
14
|
import * as clack from '@clack/prompts';
|
|
12
15
|
import { updateEnvVariable } from './lib/auth.mjs';
|
|
13
16
|
import { loadEnvFile } from './lib/env.mjs';
|
|
@@ -299,17 +302,21 @@ async function main() {
|
|
|
299
302
|
updateEnvVariable('SSL_DOMAIN', domain);
|
|
300
303
|
updateEnvVariable('SSL_EMAIL', email);
|
|
301
304
|
updateEnvVariable('SSL_DNS_PROVIDER', provider.traefikName);
|
|
302
|
-
updateEnvVariable('APP_HOSTNAME', domain);
|
|
303
305
|
|
|
304
|
-
//
|
|
305
|
-
|
|
306
|
-
updateEnvVariable(
|
|
306
|
+
// Set APP_HOSTNAME only if not already set (don't overwrite existing webhook hostname)
|
|
307
|
+
if (!env.APP_HOSTNAME) {
|
|
308
|
+
updateEnvVariable('APP_HOSTNAME', domain);
|
|
307
309
|
}
|
|
308
310
|
|
|
311
|
+
// Write DNS provider credentials to .env.traefik (Traefik-only, not leaked to other services)
|
|
312
|
+
const traefikEnvPath = path.join(process.cwd(), '.env.traefik');
|
|
313
|
+
const traefikLines = provider.envVars.map((v) => `${v.key}=${credentials[v.key]}`);
|
|
314
|
+
fs.writeFileSync(traefikEnvPath, traefikLines.join('\n') + '\n');
|
|
315
|
+
|
|
309
316
|
// Switch to custom compose file
|
|
310
317
|
updateEnvVariable('COMPOSE_FILE', 'docker-compose.custom.yml');
|
|
311
318
|
|
|
312
|
-
s.stop('Configuration saved to .env');
|
|
319
|
+
s.stop('Configuration saved to .env and .env.traefik');
|
|
313
320
|
|
|
314
321
|
// ── Summary ────────────────────────────────────────────────────────
|
|
315
322
|
clack.log.success(
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
# SSL_DOMAIN — your domain (e.g., bot.example.com)
|
|
9
9
|
# SSL_EMAIL — email for Let's Encrypt notifications
|
|
10
10
|
# SSL_DNS_PROVIDER — Traefik DNS provider name (e.g., cloudflare)
|
|
11
|
-
#
|
|
11
|
+
#
|
|
12
|
+
# DNS provider API credentials are stored in .env.traefik (created by setup-ssl).
|
|
13
|
+
# Only this file is passed to the Traefik container — not the main .env.
|
|
12
14
|
#
|
|
13
15
|
# DNS: *.${SSL_DOMAIN} must resolve to this server (A record or CNAME).
|
|
14
16
|
# The setup-ssl command can create this record for you.
|
|
@@ -28,7 +30,7 @@ services:
|
|
|
28
30
|
- --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json
|
|
29
31
|
- --certificatesresolvers.letsencrypt.acme.dnschallenge.provider=${SSL_DNS_PROVIDER}
|
|
30
32
|
- --certificatesresolvers.letsencrypt.acme.dnschallenge.resolvers=8.8.8.8:53,8.8.4.4:53
|
|
31
|
-
env_file: .env
|
|
33
|
+
env_file: .env.traefik
|
|
32
34
|
ports:
|
|
33
35
|
- "80:80"
|
|
34
36
|
- "443:443"
|
|
@@ -60,6 +62,8 @@ services:
|
|
|
60
62
|
- traefik.http.routers.event-handler.rule=Host(`${APP_HOSTNAME}`)
|
|
61
63
|
- traefik.http.routers.event-handler.entrypoints=websecure
|
|
62
64
|
- traefik.http.routers.event-handler.tls.certresolver=letsencrypt
|
|
65
|
+
- traefik.http.routers.event-handler.tls.domains[0].main=${SSL_DOMAIN}
|
|
66
|
+
- traefik.http.routers.event-handler.tls.domains[0].sans=*.${SSL_DOMAIN}
|
|
63
67
|
- traefik.http.services.event-handler.loadbalancer.server.port=80
|
|
64
68
|
stop_grace_period: 120s
|
|
65
69
|
healthcheck:
|