thepopebot 1.2.76-beta.25 → 1.2.76-beta.27
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/api/index.js +3 -2
- package/bin/docker-build.js +80 -53
- package/bin/sync.js +18 -1
- package/lib/chat/components/chat-input.js +35 -4
- package/lib/chat/components/chat-input.jsx +30 -2
- package/lib/chat/components/chat.js +50 -89
- package/lib/chat/components/chat.jsx +6 -47
- package/lib/chat/components/index.js +1 -1
- package/lib/chat/components/settings-jobs-page.js +17 -3
- package/lib/chat/components/settings-jobs-page.jsx +23 -7
- package/lib/chat/components/settings-shared.js +2 -2
- package/lib/chat/components/settings-shared.jsx +2 -2
- package/lib/code/code-page.js +1 -1
- package/lib/code/code-page.jsx +3 -1
- package/package.json +1 -1
- package/templates/skills/agent-job-tools/SKILL.md +1 -1
package/api/index.js
CHANGED
|
@@ -116,8 +116,9 @@ async function handleGetAgentSecret(request) {
|
|
|
116
116
|
return Response.json({ error: 'Forbidden' }, { status: 403 });
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
const
|
|
120
|
-
if (!
|
|
119
|
+
const rawKey = new URL(request.url).searchParams.get('key');
|
|
120
|
+
if (!rawKey) return Response.json({ error: 'Missing key' }, { status: 400 });
|
|
121
|
+
const key = rawKey.toUpperCase();
|
|
121
122
|
|
|
122
123
|
const { getAgentJobSecretRaw, setAgentJobSecret: saveSecret } = await import('../lib/db/config.js');
|
|
123
124
|
const raw = getAgentJobSecretRaw(key);
|
package/bin/docker-build.js
CHANGED
|
@@ -1,19 +1,32 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Build all Docker images locally
|
|
4
|
+
* Build all Docker images locally.
|
|
5
5
|
*
|
|
6
6
|
* Usage:
|
|
7
|
-
* npm run docker:build # build
|
|
8
|
-
* npm run docker:build -- --image event-handler # build one
|
|
7
|
+
* npm run docker:build # build everything
|
|
8
|
+
* npm run docker:build -- --image event-handler # build one (deps built first)
|
|
9
9
|
*
|
|
10
10
|
* Reads the version from package.json and tags each image as:
|
|
11
11
|
* stephengpope/thepopebot:{image}-{version}
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
13
|
+
* Image hierarchy:
|
|
14
|
+
*
|
|
15
|
+
* thepopebot-base ← Ubuntu + Node + locale + Chromium + playwright + user
|
|
16
|
+
* ├── coding-agent-base ← + tmux, ttyd, scripts, entrypoint
|
|
17
|
+
* │ ├── coding-agent-claude-code ← + per-agent CLI
|
|
18
|
+
* │ ├── coding-agent-pi-coding-agent
|
|
19
|
+
* │ └── ... (one per agent)
|
|
20
|
+
* └── event-handler ← + pm2, gosu, Next.js, server.js
|
|
21
|
+
*
|
|
22
|
+
* Build order:
|
|
23
|
+
* 1. thepopebot-base
|
|
24
|
+
* 2. coding-agent-base + event-handler in parallel
|
|
25
|
+
* 3. all coding-agent variants in parallel
|
|
26
|
+
*
|
|
27
|
+
* Base images are tagged both versioned and unversioned (no version) so child
|
|
28
|
+
* Dockerfiles can `FROM thepopebot-base` / `FROM coding-agent-base` without a
|
|
29
|
+
* build-arg for local development.
|
|
17
30
|
*/
|
|
18
31
|
|
|
19
32
|
import { spawn } from 'child_process';
|
|
@@ -28,14 +41,21 @@ const pkg = JSON.parse(readFileSync(path.join(ROOT, 'package.json'), 'utf8'));
|
|
|
28
41
|
const VERSION = pkg.version;
|
|
29
42
|
const REPO = 'stephengpope/thepopebot';
|
|
30
43
|
|
|
31
|
-
//
|
|
32
|
-
const
|
|
44
|
+
// Built first — everything depends on this.
|
|
45
|
+
const THEPOPEBOT_BASE = {
|
|
46
|
+
name: 'thepopebot-base',
|
|
47
|
+
context: 'docker/base',
|
|
48
|
+
dockerfile: 'docker/base/Dockerfile',
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Built second — depends on thepopebot-base.
|
|
52
|
+
const CODING_AGENT_BASE = {
|
|
33
53
|
name: 'coding-agent-base',
|
|
34
54
|
context: 'docker/coding-agent',
|
|
35
55
|
dockerfile: 'docker/coding-agent/Dockerfile',
|
|
36
56
|
};
|
|
37
57
|
|
|
38
|
-
//
|
|
58
|
+
// Built third — depend on coding-agent-base.
|
|
39
59
|
const CODING_AGENTS = [
|
|
40
60
|
{
|
|
41
61
|
name: 'coding-agent-claude-code',
|
|
@@ -69,16 +89,14 @@ const CODING_AGENTS = [
|
|
|
69
89
|
},
|
|
70
90
|
];
|
|
71
91
|
|
|
72
|
-
//
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
},
|
|
79
|
-
];
|
|
92
|
+
// Built second — depends on thepopebot-base. Built in parallel with coding-agent-base.
|
|
93
|
+
const EVENT_HANDLER = {
|
|
94
|
+
name: 'event-handler',
|
|
95
|
+
context: '.',
|
|
96
|
+
dockerfile: 'docker/event-handler/Dockerfile',
|
|
97
|
+
};
|
|
80
98
|
|
|
81
|
-
const ALL_IMAGES = [
|
|
99
|
+
const ALL_IMAGES = [THEPOPEBOT_BASE, CODING_AGENT_BASE, ...CODING_AGENTS, EVENT_HANDLER];
|
|
82
100
|
|
|
83
101
|
// Parse --image flag
|
|
84
102
|
const filterArg = process.argv.find((_, i, a) => a[i - 1] === '--image');
|
|
@@ -101,11 +119,14 @@ function buildImage(img) {
|
|
|
101
119
|
console.log(` ${label} building — ${tag}`);
|
|
102
120
|
|
|
103
121
|
return new Promise((resolve, reject) => {
|
|
104
|
-
// Tag base image as both versioned and unversioned (agent Dockerfiles use FROM coding-agent-base)
|
|
105
122
|
const args = ['build', '-t', tag, '-f', dockerfile];
|
|
106
|
-
|
|
107
|
-
|
|
123
|
+
|
|
124
|
+
// Base images get an unversioned tag too so child Dockerfiles can
|
|
125
|
+
// `FROM thepopebot-base` / `FROM coding-agent-base` without a build-arg.
|
|
126
|
+
if (img.name === 'thepopebot-base' || img.name === 'coding-agent-base') {
|
|
127
|
+
args.push('-t', img.name);
|
|
108
128
|
}
|
|
129
|
+
|
|
109
130
|
args.push(context);
|
|
110
131
|
|
|
111
132
|
const proc = spawn(
|
|
@@ -172,50 +193,56 @@ function buildImage(img) {
|
|
|
172
193
|
});
|
|
173
194
|
}
|
|
174
195
|
|
|
175
|
-
// Build logic: base first, then agents + others in parallel
|
|
176
196
|
async function run() {
|
|
177
197
|
if (filterArg) {
|
|
178
|
-
// Single image build
|
|
179
|
-
if (filterArg ===
|
|
198
|
+
// Single image build — build dependency chain too.
|
|
199
|
+
if (filterArg === THEPOPEBOT_BASE.name) {
|
|
180
200
|
console.log(`Building 1 image — version ${VERSION}\n`);
|
|
181
|
-
await buildImage(
|
|
182
|
-
} else {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
201
|
+
await buildImage(THEPOPEBOT_BASE);
|
|
202
|
+
} else if (filterArg === CODING_AGENT_BASE.name) {
|
|
203
|
+
console.log(`Building 2 images — version ${VERSION}\n`);
|
|
204
|
+
await buildImage(THEPOPEBOT_BASE);
|
|
205
|
+
await buildImage(CODING_AGENT_BASE);
|
|
206
|
+
} else if (filterArg === EVENT_HANDLER.name) {
|
|
207
|
+
console.log(`Building 2 images — version ${VERSION}\n`);
|
|
208
|
+
await buildImage(THEPOPEBOT_BASE);
|
|
209
|
+
await buildImage(EVENT_HANDLER);
|
|
210
|
+
} else if (CODING_AGENTS.some(img => img.name === filterArg)) {
|
|
211
|
+
console.log(`Building 3 images — version ${VERSION}\n`);
|
|
212
|
+
await buildImage(THEPOPEBOT_BASE);
|
|
213
|
+
await buildImage(CODING_AGENT_BASE);
|
|
214
|
+
const agent = CODING_AGENTS.find(img => img.name === filterArg);
|
|
215
|
+
await buildImage(agent);
|
|
195
216
|
}
|
|
196
|
-
console.log('\
|
|
217
|
+
console.log('\ndone.');
|
|
197
218
|
return;
|
|
198
219
|
}
|
|
199
220
|
|
|
200
|
-
// Full build: base
|
|
221
|
+
// Full build: thepopebot-base → (coding-agent-base + event-handler in parallel) → variants
|
|
201
222
|
const totalCount = ALL_IMAGES.length;
|
|
202
|
-
console.log(`Building ${totalCount} images
|
|
223
|
+
console.log(`Building ${totalCount} images — version ${VERSION}\n`);
|
|
203
224
|
|
|
204
|
-
// Step 1:
|
|
205
|
-
await buildImage(
|
|
225
|
+
// Step 1: thepopebot-base
|
|
226
|
+
await buildImage(THEPOPEBOT_BASE);
|
|
206
227
|
|
|
207
|
-
// Step 2:
|
|
208
|
-
const
|
|
209
|
-
const
|
|
228
|
+
// Step 2: coding-agent-base and event-handler in parallel (both extend thepopebot-base)
|
|
229
|
+
const tier2 = await Promise.allSettled([CODING_AGENT_BASE, EVENT_HANDLER].map(buildImage));
|
|
230
|
+
const tier2Failed = tier2.filter(r => r.status === 'rejected');
|
|
231
|
+
if (tier2Failed.length > 0) {
|
|
232
|
+
console.error(`Tier 2 failed: ${tier2Failed.map(r => r.reason.message).join(', ')}`);
|
|
233
|
+
process.exit(1);
|
|
234
|
+
}
|
|
210
235
|
|
|
211
|
-
|
|
212
|
-
const
|
|
236
|
+
// Step 3: all coding-agent variants in parallel
|
|
237
|
+
const tier3 = await Promise.allSettled(CODING_AGENTS.map(buildImage));
|
|
238
|
+
const tier3Failed = tier3.filter(r => r.status === 'rejected');
|
|
239
|
+
const tier3Succeeded = tier3.filter(r => r.status === 'fulfilled');
|
|
213
240
|
|
|
214
|
-
|
|
215
|
-
console.log(`\n${
|
|
241
|
+
const succeededCount = 1 + 2 + tier3Succeeded.length;
|
|
242
|
+
console.log(`\n${succeededCount}/${totalCount} images built successfully.`);
|
|
216
243
|
|
|
217
|
-
if (
|
|
218
|
-
console.error(`${
|
|
244
|
+
if (tier3Failed.length > 0) {
|
|
245
|
+
console.error(`${tier3Failed.length} failed: ${tier3Failed.map(r => r.reason.message).join(', ')}`);
|
|
219
246
|
process.exit(1);
|
|
220
247
|
}
|
|
221
248
|
}
|
package/bin/sync.js
CHANGED
|
@@ -248,6 +248,19 @@ function mirrorTemplates(projectPath) {
|
|
|
248
248
|
}
|
|
249
249
|
}
|
|
250
250
|
|
|
251
|
+
/**
|
|
252
|
+
* Build thepopebot-base locally so the event-handler Dockerfile's
|
|
253
|
+
* `FROM ${BASE_IMAGE}` (default: thepopebot-base) resolves. Cached layers
|
|
254
|
+
* make this near-instant when nothing in docker/base changed.
|
|
255
|
+
*/
|
|
256
|
+
function buildBaseImage() {
|
|
257
|
+
console.log('\n Building thepopebot-base image...');
|
|
258
|
+
const baseContext = path.join(PACKAGE_DIR, 'docker', 'base');
|
|
259
|
+
execSync(`docker build -t thepopebot-base -f ${path.join(baseContext, 'Dockerfile')} ${baseContext}`, {
|
|
260
|
+
stdio: 'inherit',
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
251
264
|
function buildDockerImage(projectPath) {
|
|
252
265
|
console.log('\n Building Docker event handler image...');
|
|
253
266
|
|
|
@@ -431,7 +444,11 @@ export async function sync(projectPath) {
|
|
|
431
444
|
console.log('\n Installing package on host...');
|
|
432
445
|
execSync(`npm install --no-save ${tarballDest}`, { stdio: 'inherit', cwd: projectPath });
|
|
433
446
|
|
|
434
|
-
// 5. Build
|
|
447
|
+
// 5. Build thepopebot-base (event-handler Dockerfile FROMs it).
|
|
448
|
+
// Cached layers — fast unless docker/base/Dockerfile changed.
|
|
449
|
+
buildBaseImage();
|
|
450
|
+
|
|
451
|
+
// 6. Build event-handler image with patched Dockerfile (includes Next.js build)
|
|
435
452
|
buildDockerImage(projectPath);
|
|
436
453
|
|
|
437
454
|
// 6. Restart container with new image
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useRef, useEffect, useCallback, useState } from "react";
|
|
4
|
-
import { SendIcon, StopIcon, PaperclipIcon, XIcon, FileTextIcon, MicIcon } from "./icons.js";
|
|
4
|
+
import { SendIcon, StopIcon, PaperclipIcon, XIcon, FileTextIcon, MicIcon, KeyIcon } from "./icons.js";
|
|
5
5
|
import { useVoiceInput } from "../../voice/use-voice-input.js";
|
|
6
6
|
const getVoiceTokenFetch = () => fetch("/chat/voice-token").then((r) => r.json()).catch(() => ({ error: "Failed to get voice token" }));
|
|
7
7
|
import { VoiceBars } from "./voice-bars.jsx";
|
|
8
|
+
import { Dialog } from "./settings-shared.js";
|
|
9
|
+
import { JobSecretsManager } from "./settings-jobs-page.js";
|
|
8
10
|
import { cn } from "../utils.js";
|
|
9
11
|
const ACCEPTED_TYPES = [
|
|
10
12
|
"image/jpeg",
|
|
@@ -67,6 +69,7 @@ function ChatInput({ input, setInput, onSubmit, status, stop, files, setFiles, d
|
|
|
67
69
|
const [modeDropdownOpen, setModeDropdownOpen] = useState(false);
|
|
68
70
|
const [agentPickerOpen, setAgentPickerOpen] = useState(false);
|
|
69
71
|
const [partialText, setPartialText] = useState("");
|
|
72
|
+
const [secretsOpen, setSecretsOpen] = useState(false);
|
|
70
73
|
const dropdownRef = useRef(null);
|
|
71
74
|
const agentPickerRef = useRef(null);
|
|
72
75
|
const isStreaming = status === "streaming" || status === "submitted";
|
|
@@ -301,6 +304,18 @@ function ChatInput({ input, setInput, onSubmit, status, stop, files, setFiles, d
|
|
|
301
304
|
)
|
|
302
305
|
] })
|
|
303
306
|
] }),
|
|
307
|
+
!codeMode && /* @__PURE__ */ jsx(
|
|
308
|
+
"button",
|
|
309
|
+
{
|
|
310
|
+
type: "button",
|
|
311
|
+
onClick: () => setSecretsOpen(true),
|
|
312
|
+
className: "inline-flex items-center justify-center rounded-lg p-2.5 text-muted-foreground hover:text-foreground",
|
|
313
|
+
"aria-label": "Manage agent job secrets",
|
|
314
|
+
title: "Manage agent job secrets",
|
|
315
|
+
disabled: isStreaming,
|
|
316
|
+
children: /* @__PURE__ */ jsx(KeyIcon, { size: 16 })
|
|
317
|
+
}
|
|
318
|
+
),
|
|
304
319
|
codeModeSettings && !codeModeSettings.isInteractiveActive && /* @__PURE__ */ jsxs("div", { className: "relative", ref: agentPickerRef, children: [
|
|
305
320
|
agentPickerOpen && codeModeSettings.availableAgents?.length > 1 && /* @__PURE__ */ jsxs("div", { className: "absolute bottom-full left-0 mb-1.5 z-50 min-w-[140px] rounded-md border border-border bg-background shadow-md py-1 overflow-hidden", children: [
|
|
306
321
|
/* @__PURE__ */ jsx("p", { className: "px-3 pt-0.5 pb-1 text-[10px] font-medium text-muted-foreground uppercase tracking-wide", children: "Launch with" }),
|
|
@@ -417,8 +432,24 @@ function ChatInput({ input, setInput, onSubmit, status, stop, files, setFiles, d
|
|
|
417
432
|
]
|
|
418
433
|
}
|
|
419
434
|
) });
|
|
420
|
-
|
|
421
|
-
|
|
435
|
+
const secretsDialog = !codeMode ? /* @__PURE__ */ jsx(
|
|
436
|
+
Dialog,
|
|
437
|
+
{
|
|
438
|
+
open: secretsOpen,
|
|
439
|
+
onClose: () => setSecretsOpen(false),
|
|
440
|
+
title: "Agent Job Secrets",
|
|
441
|
+
maxWidth: "max-w-2xl",
|
|
442
|
+
children: /* @__PURE__ */ jsx(JobSecretsManager, { showHeader: false })
|
|
443
|
+
}
|
|
444
|
+
) : null;
|
|
445
|
+
if (bare) return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
446
|
+
formContent,
|
|
447
|
+
secretsDialog
|
|
448
|
+
] });
|
|
449
|
+
return /* @__PURE__ */ jsxs("div", { className: "mx-auto w-full max-w-4xl px-1.5 pb-[max(1rem,var(--safe-area-bottom))] md:px-6", children: [
|
|
450
|
+
formContent,
|
|
451
|
+
secretsDialog
|
|
452
|
+
] });
|
|
422
453
|
}
|
|
423
454
|
export {
|
|
424
455
|
ChatInput
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useRef, useEffect, useCallback, useState } from 'react';
|
|
4
|
-
import { SendIcon, StopIcon, PaperclipIcon, XIcon, FileTextIcon, MicIcon } from './icons.js';
|
|
4
|
+
import { SendIcon, StopIcon, PaperclipIcon, XIcon, FileTextIcon, MicIcon, KeyIcon } from './icons.js';
|
|
5
5
|
import { useVoiceInput } from '../../voice/use-voice-input.js';
|
|
6
6
|
const getVoiceTokenFetch = () =>
|
|
7
7
|
fetch('/chat/voice-token').then(r => r.json()).catch(() => ({ error: 'Failed to get voice token' }));
|
|
8
8
|
import { VoiceBars } from './voice-bars.jsx';
|
|
9
|
+
import { Dialog } from './settings-shared.js';
|
|
10
|
+
import { JobSecretsManager } from './settings-jobs-page.js';
|
|
9
11
|
import { cn } from '../utils.js';
|
|
10
12
|
|
|
11
13
|
const ACCEPTED_TYPES = [
|
|
@@ -48,6 +50,7 @@ export function ChatInput({ input, setInput, onSubmit, status, stop, files, setF
|
|
|
48
50
|
const [modeDropdownOpen, setModeDropdownOpen] = useState(false);
|
|
49
51
|
const [agentPickerOpen, setAgentPickerOpen] = useState(false);
|
|
50
52
|
const [partialText, setPartialText] = useState('');
|
|
53
|
+
const [secretsOpen, setSecretsOpen] = useState(false);
|
|
51
54
|
const dropdownRef = useRef(null);
|
|
52
55
|
const agentPickerRef = useRef(null);
|
|
53
56
|
const isStreaming = status === 'streaming' || status === 'submitted';
|
|
@@ -311,6 +314,19 @@ export function ChatInput({ input, setInput, onSubmit, status, stop, files, setF
|
|
|
311
314
|
</div>
|
|
312
315
|
)}
|
|
313
316
|
|
|
317
|
+
{!codeMode && (
|
|
318
|
+
<button
|
|
319
|
+
type="button"
|
|
320
|
+
onClick={() => setSecretsOpen(true)}
|
|
321
|
+
className="inline-flex items-center justify-center rounded-lg p-2.5 text-muted-foreground hover:text-foreground"
|
|
322
|
+
aria-label="Manage agent job secrets"
|
|
323
|
+
title="Manage agent job secrets"
|
|
324
|
+
disabled={isStreaming}
|
|
325
|
+
>
|
|
326
|
+
<KeyIcon size={16} />
|
|
327
|
+
</button>
|
|
328
|
+
)}
|
|
329
|
+
|
|
314
330
|
{/* Interactive toggle — left-click to launch with default agent,
|
|
315
331
|
right-click to pick a specific agent (when multiple are available) */}
|
|
316
332
|
{codeModeSettings && !codeModeSettings.isInteractiveActive && (
|
|
@@ -433,10 +449,22 @@ export function ChatInput({ input, setInput, onSubmit, status, stop, files, setF
|
|
|
433
449
|
</form>
|
|
434
450
|
);
|
|
435
451
|
|
|
436
|
-
|
|
452
|
+
const secretsDialog = !codeMode ? (
|
|
453
|
+
<Dialog
|
|
454
|
+
open={secretsOpen}
|
|
455
|
+
onClose={() => setSecretsOpen(false)}
|
|
456
|
+
title="Agent Job Secrets"
|
|
457
|
+
maxWidth="max-w-2xl"
|
|
458
|
+
>
|
|
459
|
+
<JobSecretsManager showHeader={false} />
|
|
460
|
+
</Dialog>
|
|
461
|
+
) : null;
|
|
462
|
+
|
|
463
|
+
if (bare) return <>{formContent}{secretsDialog}</>;
|
|
437
464
|
return (
|
|
438
465
|
<div className="mx-auto w-full max-w-4xl px-1.5 pb-[max(1rem,var(--safe-area-bottom))] md:px-6">
|
|
439
466
|
{formContent}
|
|
467
|
+
{secretsDialog}
|
|
440
468
|
</div>
|
|
441
469
|
);
|
|
442
470
|
}
|
|
@@ -344,100 +344,61 @@ function Chat({ chatId, initialMessages = [], workspace = null, chatMode = null
|
|
|
344
344
|
}
|
|
345
345
|
) })
|
|
346
346
|
] })
|
|
347
|
-
] }) }) : /* @__PURE__ */
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
347
|
+
] }) }) : /* @__PURE__ */ jsx("div", { className: "flex flex-1 flex-col min-h-0 overflow-hidden", children: showDiff && workspaceState?.id ? /* @__PURE__ */ jsx(
|
|
348
|
+
DiffViewer,
|
|
349
|
+
{
|
|
350
|
+
workspaceId: workspaceState.id,
|
|
351
|
+
diffStats,
|
|
352
|
+
onClose: () => setShowDiff(false)
|
|
353
|
+
}
|
|
354
|
+
) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
355
|
+
/* @__PURE__ */ jsx(Messages, { messages, status, onRetry: handleRetry, onEdit: handleEdit }),
|
|
356
|
+
error && /* @__PURE__ */ jsx("div", { className: "mx-auto w-full max-w-4xl px-2 md:px-4", children: /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-destructive/50 bg-destructive/10 px-4 py-2 text-sm text-destructive", children: error.message || "Something went wrong. Please try again." }) }),
|
|
357
|
+
/* @__PURE__ */ jsxs("div", { className: "mx-auto w-full max-w-4xl px-4 pb-4 md:px-6", children: [
|
|
358
|
+
isInteractiveActive && /* @__PURE__ */ jsxs(
|
|
359
|
+
"a",
|
|
360
|
+
{
|
|
361
|
+
href: `/code/${workspaceState?.id}`,
|
|
362
|
+
className: "flex items-center justify-center gap-2 rounded-xl border border-primary/20 bg-primary/5 px-4 py-3 mb-2 text-sm font-medium text-primary hover:bg-primary/10 transition-colors",
|
|
363
|
+
children: [
|
|
364
|
+
/* @__PURE__ */ jsx("span", { className: "h-2 w-2 rounded-full bg-green-500 animate-pulse" }),
|
|
365
|
+
"Click here to access Interactive Mode"
|
|
366
|
+
]
|
|
367
|
+
}
|
|
368
|
+
),
|
|
369
|
+
workspaceState && /* @__PURE__ */ jsx("div", { className: "rounded-t-xl border border-b-0 border-border px-3 py-2.5", children: /* @__PURE__ */ jsx(
|
|
370
|
+
WorkspaceBar,
|
|
352
371
|
{
|
|
353
|
-
|
|
372
|
+
repo,
|
|
373
|
+
branch,
|
|
374
|
+
onBranchChange: handleBranchChange,
|
|
375
|
+
getBranches: fetchBranches,
|
|
376
|
+
workspace: workspaceState,
|
|
354
377
|
diffStats,
|
|
355
|
-
|
|
378
|
+
onDiffStatsRefresh: handleDiffStatsRefresh,
|
|
379
|
+
onShowDiff: () => setShowDiff(true)
|
|
356
380
|
}
|
|
357
381
|
) }),
|
|
358
|
-
/* @__PURE__ */ jsx(
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
bare: true,
|
|
377
|
-
input,
|
|
378
|
-
setInput,
|
|
379
|
-
onSubmit: handleSend,
|
|
380
|
-
status,
|
|
381
|
-
stop,
|
|
382
|
-
files,
|
|
383
|
-
setFiles,
|
|
384
|
-
disabled: isInteractiveActive,
|
|
385
|
-
placeholder: isInteractiveActive ? "Interactive mode is active." : defaultPlaceholder,
|
|
386
|
-
className: workspaceState ? "rounded-t-none" : void 0,
|
|
387
|
-
codeMode,
|
|
388
|
-
codeModeSettings
|
|
389
|
-
}
|
|
390
|
-
)
|
|
391
|
-
] }) })
|
|
392
|
-
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
393
|
-
/* @__PURE__ */ jsx(Messages, { messages, status, onRetry: handleRetry, onEdit: handleEdit }),
|
|
394
|
-
error && /* @__PURE__ */ jsx("div", { className: "mx-auto w-full max-w-4xl px-2 md:px-4", children: /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-destructive/50 bg-destructive/10 px-4 py-2 text-sm text-destructive", children: error.message || "Something went wrong. Please try again." }) }),
|
|
395
|
-
/* @__PURE__ */ jsxs("div", { className: "mx-auto w-full max-w-4xl px-4 pb-4 md:px-6", children: [
|
|
396
|
-
isInteractiveActive && /* @__PURE__ */ jsxs(
|
|
397
|
-
"a",
|
|
398
|
-
{
|
|
399
|
-
href: `/code/${workspaceState?.id}`,
|
|
400
|
-
className: "flex items-center justify-center gap-2 rounded-xl border border-primary/20 bg-primary/5 px-4 py-3 mb-2 text-sm font-medium text-primary hover:bg-primary/10 transition-colors",
|
|
401
|
-
children: [
|
|
402
|
-
/* @__PURE__ */ jsx("span", { className: "h-2 w-2 rounded-full bg-green-500 animate-pulse" }),
|
|
403
|
-
"Click here to access Interactive Mode"
|
|
404
|
-
]
|
|
405
|
-
}
|
|
406
|
-
),
|
|
407
|
-
workspaceState && /* @__PURE__ */ jsx("div", { className: "rounded-t-xl border border-b-0 border-border px-3 py-2.5", children: /* @__PURE__ */ jsx(
|
|
408
|
-
WorkspaceBar,
|
|
409
|
-
{
|
|
410
|
-
repo,
|
|
411
|
-
branch,
|
|
412
|
-
onBranchChange: handleBranchChange,
|
|
413
|
-
getBranches: fetchBranches,
|
|
414
|
-
workspace: workspaceState,
|
|
415
|
-
diffStats,
|
|
416
|
-
onDiffStatsRefresh: handleDiffStatsRefresh,
|
|
417
|
-
onShowDiff: () => setShowDiff(true)
|
|
418
|
-
}
|
|
419
|
-
) }),
|
|
420
|
-
/* @__PURE__ */ jsx(
|
|
421
|
-
ChatInput,
|
|
422
|
-
{
|
|
423
|
-
bare: true,
|
|
424
|
-
input,
|
|
425
|
-
setInput,
|
|
426
|
-
onSubmit: handleSend,
|
|
427
|
-
status,
|
|
428
|
-
stop,
|
|
429
|
-
files,
|
|
430
|
-
setFiles,
|
|
431
|
-
disabled: isInteractiveActive,
|
|
432
|
-
placeholder: isInteractiveActive ? "Interactive mode is active." : defaultPlaceholder,
|
|
433
|
-
className: workspaceState ? "rounded-t-none" : void 0,
|
|
434
|
-
codeMode,
|
|
435
|
-
codeModeSettings
|
|
436
|
-
}
|
|
437
|
-
)
|
|
438
|
-
] })
|
|
382
|
+
/* @__PURE__ */ jsx(
|
|
383
|
+
ChatInput,
|
|
384
|
+
{
|
|
385
|
+
bare: true,
|
|
386
|
+
input,
|
|
387
|
+
setInput,
|
|
388
|
+
onSubmit: handleSend,
|
|
389
|
+
status,
|
|
390
|
+
stop,
|
|
391
|
+
files,
|
|
392
|
+
setFiles,
|
|
393
|
+
disabled: isInteractiveActive,
|
|
394
|
+
placeholder: isInteractiveActive ? "Interactive mode is active." : defaultPlaceholder,
|
|
395
|
+
className: workspaceState ? "rounded-t-none" : void 0,
|
|
396
|
+
codeMode,
|
|
397
|
+
codeModeSettings
|
|
398
|
+
}
|
|
399
|
+
)
|
|
439
400
|
] })
|
|
440
|
-
] })
|
|
401
|
+
] }) })
|
|
441
402
|
] });
|
|
442
403
|
}
|
|
443
404
|
export {
|
|
@@ -409,54 +409,13 @@ export function Chat({ chatId, initialMessages = [], workspace = null, chatMode
|
|
|
409
409
|
</div>
|
|
410
410
|
</div>
|
|
411
411
|
) : (
|
|
412
|
-
<div className="flex flex-1 flex-col min-h-0 overflow-hidden
|
|
413
|
-
{showDiff && workspaceState?.id && (
|
|
414
|
-
<div className="absolute inset-0 z-10 bg-black/50" />
|
|
415
|
-
)}
|
|
412
|
+
<div className="flex flex-1 flex-col min-h-0 overflow-hidden">
|
|
416
413
|
{showDiff && workspaceState?.id ? (
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
onClose={() => setShowDiff(false)}
|
|
423
|
-
/>
|
|
424
|
-
</div>
|
|
425
|
-
<div className="z-20 px-4 pb-4">
|
|
426
|
-
<div className="mx-auto w-full max-w-4xl">
|
|
427
|
-
{workspaceState && (
|
|
428
|
-
<div className="rounded-t-xl border border-b-0 border-border px-3 py-2.5 bg-background">
|
|
429
|
-
<WorkspaceBar
|
|
430
|
-
repo={repo}
|
|
431
|
-
branch={branch}
|
|
432
|
-
onBranchChange={handleBranchChange}
|
|
433
|
-
getBranches={fetchBranches}
|
|
434
|
-
workspace={workspaceState}
|
|
435
|
-
diffStats={diffStats}
|
|
436
|
-
onDiffStatsRefresh={handleDiffStatsRefresh}
|
|
437
|
-
onShowDiff={() => setShowDiff(true)}
|
|
438
|
-
chatMode={codeMode ? 'code' : 'agent'}
|
|
439
|
-
/>
|
|
440
|
-
</div>
|
|
441
|
-
)}
|
|
442
|
-
<ChatInput
|
|
443
|
-
bare
|
|
444
|
-
input={input}
|
|
445
|
-
setInput={setInput}
|
|
446
|
-
onSubmit={handleSend}
|
|
447
|
-
status={status}
|
|
448
|
-
stop={stop}
|
|
449
|
-
files={files}
|
|
450
|
-
setFiles={setFiles}
|
|
451
|
-
disabled={isInteractiveActive}
|
|
452
|
-
placeholder={isInteractiveActive ? 'Interactive mode is active.' : defaultPlaceholder}
|
|
453
|
-
className={workspaceState ? "rounded-t-none" : undefined}
|
|
454
|
-
codeMode={codeMode}
|
|
455
|
-
codeModeSettings={codeModeSettings}
|
|
456
|
-
/>
|
|
457
|
-
</div>
|
|
458
|
-
</div>
|
|
459
|
-
</>
|
|
414
|
+
<DiffViewer
|
|
415
|
+
workspaceId={workspaceState.id}
|
|
416
|
+
diffStats={diffStats}
|
|
417
|
+
onClose={() => setShowDiff(false)}
|
|
418
|
+
/>
|
|
460
419
|
) : (
|
|
461
420
|
<>
|
|
462
421
|
<Messages messages={messages} status={status} onRetry={handleRetry} onEdit={handleEdit} />
|
|
@@ -13,7 +13,7 @@ export { SettingsUsersPage } from './settings-users-page.js';
|
|
|
13
13
|
export { HelperLlmPage, ChatConfigPage, ChatProvidersPage, SettingsChatPage } from './settings-chat-page.js';
|
|
14
14
|
export { ChatProvidersPage as LlmsPage } from './settings-chat-page.js';
|
|
15
15
|
export { CodingAgentsPage } from './settings-coding-agents-page.js';
|
|
16
|
-
export { JobsPage } from './settings-jobs-page.js';
|
|
16
|
+
export { JobsPage, JobSecretsManager } from './settings-jobs-page.js';
|
|
17
17
|
export { GitHubTokensPage, GitHubSecretsPage, GitHubVariablesPage, SettingsGitHubPage } from './settings-github-page.js';
|
|
18
18
|
export { SettingsGeneralPage } from './settings-general-page.js';
|
|
19
19
|
export { ProfileLayout, ProfileLoginPage, ProfileTelegramPage } from './profile-page.js';
|
|
@@ -489,7 +489,7 @@ function OAuthSecretRow({ secret, onReauthorize, onDelete }) {
|
|
|
489
489
|
] })
|
|
490
490
|
] });
|
|
491
491
|
}
|
|
492
|
-
function
|
|
492
|
+
function JobSecretsManager({ showHeader = true }) {
|
|
493
493
|
const [secrets, setSecrets] = useState([]);
|
|
494
494
|
const [loading, setLoading] = useState(true);
|
|
495
495
|
const [showAdd, setShowAdd] = useState(false);
|
|
@@ -526,7 +526,7 @@ function JobsPage() {
|
|
|
526
526
|
return /* @__PURE__ */ jsx("div", { className: "h-48 animate-pulse rounded-md bg-border/50" });
|
|
527
527
|
}
|
|
528
528
|
return /* @__PURE__ */ jsxs("div", { children: [
|
|
529
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3 mb-4", children: [
|
|
529
|
+
showHeader ? /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3 mb-4", children: [
|
|
530
530
|
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
531
531
|
/* @__PURE__ */ jsx("h2", { className: "text-base font-medium", children: "Job Secrets" }),
|
|
532
532
|
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Custom environment variables passed to agent job containers. These are merged with built-in auth credentials when launching jobs." })
|
|
@@ -542,7 +542,17 @@ function JobsPage() {
|
|
|
542
542
|
]
|
|
543
543
|
}
|
|
544
544
|
) })
|
|
545
|
-
] }),
|
|
545
|
+
] }) : /* @__PURE__ */ jsx("div", { className: "flex items-center justify-end mb-4", children: /* @__PURE__ */ jsxs(
|
|
546
|
+
"button",
|
|
547
|
+
{
|
|
548
|
+
onClick: () => setShowAdd(true),
|
|
549
|
+
className: "inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium bg-foreground text-background hover:bg-foreground/90 shrink-0 transition-colors",
|
|
550
|
+
children: [
|
|
551
|
+
/* @__PURE__ */ jsx(PlusIcon, { size: 14 }),
|
|
552
|
+
"Add secret"
|
|
553
|
+
]
|
|
554
|
+
}
|
|
555
|
+
) }),
|
|
546
556
|
/* @__PURE__ */ jsx(
|
|
547
557
|
AddSecretDialog,
|
|
548
558
|
{
|
|
@@ -591,6 +601,10 @@ function JobsPage() {
|
|
|
591
601
|
)) }) })
|
|
592
602
|
] });
|
|
593
603
|
}
|
|
604
|
+
function JobsPage() {
|
|
605
|
+
return /* @__PURE__ */ jsx(JobSecretsManager, {});
|
|
606
|
+
}
|
|
594
607
|
export {
|
|
608
|
+
JobSecretsManager,
|
|
595
609
|
JobsPage
|
|
596
610
|
};
|
|
@@ -590,7 +590,7 @@ function OAuthSecretRow({ secret, onReauthorize, onDelete }) {
|
|
|
590
590
|
// Jobs page
|
|
591
591
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
592
592
|
|
|
593
|
-
export function
|
|
593
|
+
export function JobSecretsManager({ showHeader = true }) {
|
|
594
594
|
const [secrets, setSecrets] = useState([]);
|
|
595
595
|
const [loading, setLoading] = useState(true);
|
|
596
596
|
const [showAdd, setShowAdd] = useState(false);
|
|
@@ -635,12 +635,24 @@ export function JobsPage() {
|
|
|
635
635
|
|
|
636
636
|
return (
|
|
637
637
|
<div>
|
|
638
|
-
|
|
639
|
-
<div className="
|
|
640
|
-
<
|
|
641
|
-
|
|
638
|
+
{showHeader ? (
|
|
639
|
+
<div className="flex items-start justify-between gap-3 mb-4">
|
|
640
|
+
<div className="min-w-0">
|
|
641
|
+
<h2 className="text-base font-medium">Job Secrets</h2>
|
|
642
|
+
<p className="text-sm text-muted-foreground">Custom environment variables passed to agent job containers. These are merged with built-in auth credentials when launching jobs.</p>
|
|
643
|
+
</div>
|
|
644
|
+
<div className="flex items-center gap-2 shrink-0 pt-0.5">
|
|
645
|
+
<button
|
|
646
|
+
onClick={() => setShowAdd(true)}
|
|
647
|
+
className="inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium bg-foreground text-background hover:bg-foreground/90 shrink-0 transition-colors"
|
|
648
|
+
>
|
|
649
|
+
<PlusIcon size={14} />
|
|
650
|
+
Add secret
|
|
651
|
+
</button>
|
|
652
|
+
</div>
|
|
642
653
|
</div>
|
|
643
|
-
|
|
654
|
+
) : (
|
|
655
|
+
<div className="flex items-center justify-end mb-4">
|
|
644
656
|
<button
|
|
645
657
|
onClick={() => setShowAdd(true)}
|
|
646
658
|
className="inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium bg-foreground text-background hover:bg-foreground/90 shrink-0 transition-colors"
|
|
@@ -649,7 +661,7 @@ export function JobsPage() {
|
|
|
649
661
|
Add secret
|
|
650
662
|
</button>
|
|
651
663
|
</div>
|
|
652
|
-
|
|
664
|
+
)}
|
|
653
665
|
<AddSecretDialog
|
|
654
666
|
open={showAdd}
|
|
655
667
|
onAdd={handleAdd}
|
|
@@ -696,3 +708,7 @@ export function JobsPage() {
|
|
|
696
708
|
</div>
|
|
697
709
|
);
|
|
698
710
|
}
|
|
711
|
+
|
|
712
|
+
export function JobsPage() {
|
|
713
|
+
return <JobSecretsManager />;
|
|
714
|
+
}
|
|
@@ -271,7 +271,7 @@ function VariableRow({ name, isSet, currentValue, onUpdate, onDelete }) {
|
|
|
271
271
|
] })
|
|
272
272
|
] });
|
|
273
273
|
}
|
|
274
|
-
function Dialog({ open, onClose, title, children }) {
|
|
274
|
+
function Dialog({ open, onClose, title, children, maxWidth = "max-w-md" }) {
|
|
275
275
|
const ref = useRef(null);
|
|
276
276
|
useEffect(() => {
|
|
277
277
|
if (!open) return;
|
|
@@ -289,7 +289,7 @@ function Dialog({ open, onClose, title, children }) {
|
|
|
289
289
|
"div",
|
|
290
290
|
{
|
|
291
291
|
ref,
|
|
292
|
-
className:
|
|
292
|
+
className: `relative z-50 w-full ${maxWidth} mx-4 rounded-lg border border-border bg-background p-6 shadow-lg max-h-[90vh] overflow-y-auto`,
|
|
293
293
|
onClick: (e) => e.stopPropagation(),
|
|
294
294
|
children: [
|
|
295
295
|
/* @__PURE__ */ jsx("h3", { className: "text-base font-semibold mb-4", children: title }),
|
|
@@ -289,7 +289,7 @@ export function VariableRow({ name, isSet, currentValue, onUpdate, onDelete }) {
|
|
|
289
289
|
// Dialog — standardized modal
|
|
290
290
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
291
291
|
|
|
292
|
-
export function Dialog({ open, onClose, title, children }) {
|
|
292
|
+
export function Dialog({ open, onClose, title, children, maxWidth = 'max-w-md' }) {
|
|
293
293
|
const ref = useRef(null);
|
|
294
294
|
|
|
295
295
|
useEffect(() => {
|
|
@@ -306,7 +306,7 @@ export function Dialog({ open, onClose, title, children }) {
|
|
|
306
306
|
<div className="fixed inset-0 bg-black/50" onClick={onClose} />
|
|
307
307
|
<div
|
|
308
308
|
ref={ref}
|
|
309
|
-
className=
|
|
309
|
+
className={`relative z-50 w-full ${maxWidth} mx-4 rounded-lg border border-border bg-background p-6 shadow-lg max-h-[90vh] overflow-y-auto`}
|
|
310
310
|
onClick={(e) => e.stopPropagation()}
|
|
311
311
|
>
|
|
312
312
|
<h3 className="text-base font-semibold mb-4">{title}</h3>
|
package/lib/code/code-page.js
CHANGED
|
@@ -266,7 +266,7 @@ function CodePage({ session, codeWorkspaceId }) {
|
|
|
266
266
|
/* @__PURE__ */ jsxs(SidebarInset, { children: [
|
|
267
267
|
/* @__PURE__ */ jsxs("div", { className: "flex h-svh flex-col overflow-hidden", children: [
|
|
268
268
|
/* @__PURE__ */ jsx(ChatHeader, { workspaceId: codeWorkspaceId }),
|
|
269
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-end gap-0 px-4 bg-muted/30 border-b border-border shrink-0 overflow-x-auto scrollbar-hide", children: [
|
|
269
|
+
!showDiff && /* @__PURE__ */ jsxs("div", { className: "flex items-end gap-0 px-4 bg-muted/30 border-b border-border shrink-0 overflow-x-auto scrollbar-hide", children: [
|
|
270
270
|
/* @__PURE__ */ jsx(
|
|
271
271
|
PinnedTab,
|
|
272
272
|
{
|
package/lib/code/code-page.jsx
CHANGED
|
@@ -310,7 +310,8 @@ export default function CodePage({ session, codeWorkspaceId }) {
|
|
|
310
310
|
<div className="flex h-svh flex-col overflow-hidden">
|
|
311
311
|
<ChatHeader workspaceId={codeWorkspaceId} />
|
|
312
312
|
|
|
313
|
-
{/* Tab bar */}
|
|
313
|
+
{/* Tab bar — hidden while diff viewer is open so it fills the content area */}
|
|
314
|
+
{!showDiff && (
|
|
314
315
|
<div className="flex items-end gap-0 px-4 bg-muted/30 border-b border-border shrink-0 overflow-x-auto scrollbar-hide">
|
|
315
316
|
{/* Primary Code tab — pinned, not draggable */}
|
|
316
317
|
<PinnedTab
|
|
@@ -429,6 +430,7 @@ export default function CodePage({ session, codeWorkspaceId }) {
|
|
|
429
430
|
+ Port
|
|
430
431
|
</button>
|
|
431
432
|
</div>
|
|
433
|
+
)}
|
|
432
434
|
|
|
433
435
|
{/* Tab content panels — all mounted, hidden via display */}
|
|
434
436
|
<div style={{ position: 'relative', flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column' }}>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: agent-job-tools
|
|
3
|
-
description: Use when you need to access agent secrets, API keys, or create and manage background jobs. Supports listing keys
|
|
3
|
+
description: Use when you need to access agent secrets, API keys, or create and manage background jobs. Supports listing api keys, OAuth credentials (auto-refreshed). Also handles requests to "create a background job," "spawn a job," or "kick off an agent job."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
## Usage
|