thepopebot 1.2.75-beta.6 → 1.2.75-beta.8
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/bin/cli.js +10 -1
- package/config/instrumentation.js +1 -1
- package/lib/ai/tools.js +0 -4
- package/lib/code/actions.js +0 -15
- package/lib/db/api-keys.js +22 -61
- package/lib/maintenance.js +29 -13
- package/lib/tools/create-agent-job.js +0 -4
- package/lib/tools/docker.js +19 -15
- package/package.json +1 -1
- package/templates/skills/agent-job-secrets/agent-job-secrets.js +18 -9
package/bin/cli.js
CHANGED
|
@@ -248,6 +248,7 @@ async function init() {
|
|
|
248
248
|
const pkg = {
|
|
249
249
|
name: dirName,
|
|
250
250
|
private: true,
|
|
251
|
+
type: 'module',
|
|
251
252
|
scripts: {
|
|
252
253
|
setup: 'thepopebot setup',
|
|
253
254
|
'setup-telegram': 'thepopebot setup-telegram',
|
|
@@ -260,7 +261,15 @@ async function init() {
|
|
|
260
261
|
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
261
262
|
console.log(' Created package.json');
|
|
262
263
|
} else {
|
|
263
|
-
|
|
264
|
+
// Ensure "type": "module" is set for ESM support
|
|
265
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
266
|
+
if (!pkg.type) {
|
|
267
|
+
pkg.type = 'module';
|
|
268
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
269
|
+
console.log(' Added "type": "module" to package.json');
|
|
270
|
+
} else {
|
|
271
|
+
console.log(' Skipped package.json (already exists)');
|
|
272
|
+
}
|
|
264
273
|
}
|
|
265
274
|
|
|
266
275
|
// Create default skill activation symlinks
|
|
@@ -70,7 +70,7 @@ export async function register() {
|
|
|
70
70
|
const { startClusterRuntime } = await import('../lib/cluster/runtime.js');
|
|
71
71
|
startClusterRuntime();
|
|
72
72
|
|
|
73
|
-
// Start internal maintenance cron (cleanup
|
|
73
|
+
// Start internal maintenance cron (cleanup orphaned agent job keys, etc.)
|
|
74
74
|
const { startMaintenanceCron } = await import('../lib/maintenance.js');
|
|
75
75
|
startMaintenanceCron();
|
|
76
76
|
|
package/lib/ai/tools.js
CHANGED
|
@@ -54,9 +54,6 @@ 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
|
-
|
|
60
57
|
const { runHeadlessContainer, tailContainerLogs, waitForContainer, removeContainer } = await import('../tools/docker.js');
|
|
61
58
|
await runHeadlessContainer({
|
|
62
59
|
containerName,
|
|
@@ -67,7 +64,6 @@ const agentChatCodingTool = tool(
|
|
|
67
64
|
taskPrompt: prompt,
|
|
68
65
|
mode,
|
|
69
66
|
injectSecrets: true,
|
|
70
|
-
agentJobToken,
|
|
71
67
|
});
|
|
72
68
|
|
|
73
69
|
const streamCallback = runtime.configurable.streamCallback;
|
package/lib/code/actions.js
CHANGED
|
@@ -138,12 +138,6 @@ 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
|
-
|
|
147
141
|
try {
|
|
148
142
|
const { inspectContainer, startContainer, removeContainer, runInteractiveContainer } =
|
|
149
143
|
await import('../tools/docker.js');
|
|
@@ -159,7 +153,6 @@ export async function ensureCodeWorkspaceContainer(id) {
|
|
|
159
153
|
featureBranch: workspace.featureBranch,
|
|
160
154
|
workspaceId: id,
|
|
161
155
|
injectSecrets,
|
|
162
|
-
agentJobToken,
|
|
163
156
|
});
|
|
164
157
|
return { status: 'created' };
|
|
165
158
|
}
|
|
@@ -188,7 +181,6 @@ export async function ensureCodeWorkspaceContainer(id) {
|
|
|
188
181
|
featureBranch: workspace.featureBranch,
|
|
189
182
|
workspaceId: id,
|
|
190
183
|
injectSecrets,
|
|
191
|
-
agentJobToken,
|
|
192
184
|
});
|
|
193
185
|
return { status: 'created' };
|
|
194
186
|
} catch (err) {
|
|
@@ -217,12 +209,6 @@ export async function startInteractiveMode(id) {
|
|
|
217
209
|
const chat = getChatByWorkspaceId(id);
|
|
218
210
|
const injectSecrets = chat?.chatMode === 'agent';
|
|
219
211
|
|
|
220
|
-
let agentJobToken;
|
|
221
|
-
if (injectSecrets) {
|
|
222
|
-
const { createAgentJobApiKey } = await import('../db/api-keys.js');
|
|
223
|
-
({ key: agentJobToken } = createAgentJobApiKey(id));
|
|
224
|
-
}
|
|
225
|
-
|
|
226
212
|
try {
|
|
227
213
|
const { getConfig } = await import('../config.js');
|
|
228
214
|
const agent = getConfig('CODING_AGENT') || 'claude-code';
|
|
@@ -237,7 +223,6 @@ export async function startInteractiveMode(id) {
|
|
|
237
223
|
featureBranch: workspace.featureBranch,
|
|
238
224
|
workspaceId: id,
|
|
239
225
|
injectSecrets,
|
|
240
|
-
agentJobToken,
|
|
241
226
|
});
|
|
242
227
|
|
|
243
228
|
updateContainerName(id, containerName);
|
package/lib/db/api-keys.js
CHANGED
|
@@ -5,9 +5,6 @@ import { settings } from './schema.js';
|
|
|
5
5
|
|
|
6
6
|
const KEY_PREFIX = 'tpb_';
|
|
7
7
|
|
|
8
|
-
// In-memory cache: array of { id, keyHash } or null (not loaded)
|
|
9
|
-
let _cache = null;
|
|
10
|
-
|
|
11
8
|
/**
|
|
12
9
|
* Generate a new API key: tpb_ + 64 hex chars (32 random bytes).
|
|
13
10
|
* @returns {string}
|
|
@@ -25,44 +22,6 @@ export function hashApiKey(key) {
|
|
|
25
22
|
return createHash('sha256').update(key).digest('hex');
|
|
26
23
|
}
|
|
27
24
|
|
|
28
|
-
/**
|
|
29
|
-
* Lazy-load all API key hashes into the in-memory cache.
|
|
30
|
-
*/
|
|
31
|
-
function _ensureCache() {
|
|
32
|
-
if (_cache !== null) return _cache;
|
|
33
|
-
|
|
34
|
-
const db = getDb();
|
|
35
|
-
const ONE_HOUR = 60 * 60 * 1000;
|
|
36
|
-
const cutoff = Date.now() - ONE_HOUR;
|
|
37
|
-
|
|
38
|
-
const rows = db
|
|
39
|
-
.select()
|
|
40
|
-
.from(settings)
|
|
41
|
-
.where(eq(settings.type, 'api_key'))
|
|
42
|
-
.all();
|
|
43
|
-
|
|
44
|
-
const jobKeyRows = db
|
|
45
|
-
.select()
|
|
46
|
-
.from(settings)
|
|
47
|
-
.where(eq(settings.type, 'agent_job_api_key'))
|
|
48
|
-
.all()
|
|
49
|
-
.filter(r => r.lastUsedAt !== null ? r.lastUsedAt > cutoff : r.createdAt > cutoff);
|
|
50
|
-
|
|
51
|
-
_cache = [...rows, ...jobKeyRows].map((row) => {
|
|
52
|
-
const parsed = JSON.parse(row.value);
|
|
53
|
-
return { id: row.id, keyHash: parsed.key_hash, type: row.type };
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
return _cache;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Clear the in-memory cache (call after create/delete).
|
|
61
|
-
*/
|
|
62
|
-
export function invalidateApiKeyCache() {
|
|
63
|
-
_cache = null;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
25
|
/**
|
|
67
26
|
* Create a new named API key.
|
|
68
27
|
* @param {string} name - Human-readable name for the key
|
|
@@ -89,7 +48,6 @@ export function createApiKeyRecord(name, createdBy) {
|
|
|
89
48
|
};
|
|
90
49
|
|
|
91
50
|
db.insert(settings).values(record).run();
|
|
92
|
-
invalidateApiKeyCache();
|
|
93
51
|
|
|
94
52
|
return {
|
|
95
53
|
key,
|
|
@@ -143,7 +101,6 @@ export function getApiKey() {
|
|
|
143
101
|
export function deleteApiKeyById(id) {
|
|
144
102
|
const db = getDb();
|
|
145
103
|
db.delete(settings).where(eq(settings.id, id)).run();
|
|
146
|
-
invalidateApiKeyCache();
|
|
147
104
|
}
|
|
148
105
|
|
|
149
106
|
/**
|
|
@@ -152,11 +109,11 @@ export function deleteApiKeyById(id) {
|
|
|
152
109
|
export function deleteApiKey() {
|
|
153
110
|
const db = getDb();
|
|
154
111
|
db.delete(settings).where(eq(settings.type, 'api_key')).run();
|
|
155
|
-
invalidateApiKeyCache();
|
|
156
112
|
}
|
|
157
113
|
|
|
158
114
|
/**
|
|
159
|
-
* Verify a raw API key against
|
|
115
|
+
* Verify a raw API key against stored hashes.
|
|
116
|
+
* Queries the database directly on each call (SQLite is in-process, no caching needed).
|
|
160
117
|
* @param {string} rawKey - Raw API key from request header
|
|
161
118
|
* @returns {object|null} Record if valid, null otherwise
|
|
162
119
|
*/
|
|
@@ -164,27 +121,32 @@ export function verifyApiKey(rawKey) {
|
|
|
164
121
|
if (!rawKey || !rawKey.startsWith(KEY_PREFIX)) return null;
|
|
165
122
|
|
|
166
123
|
const keyHash = hashApiKey(rawKey);
|
|
167
|
-
const
|
|
124
|
+
const db = getDb();
|
|
168
125
|
|
|
169
|
-
|
|
126
|
+
const rows = [
|
|
127
|
+
...db.select().from(settings).where(eq(settings.type, 'api_key')).all(),
|
|
128
|
+
...db.select().from(settings).where(eq(settings.type, 'agent_job_api_key')).all(),
|
|
129
|
+
];
|
|
130
|
+
|
|
131
|
+
if (rows.length === 0) return null;
|
|
170
132
|
|
|
171
133
|
const b = Buffer.from(keyHash, 'hex');
|
|
172
134
|
|
|
173
|
-
for (const
|
|
174
|
-
const
|
|
135
|
+
for (const row of rows) {
|
|
136
|
+
const parsed = JSON.parse(row.value);
|
|
137
|
+
const a = Buffer.from(parsed.key_hash, 'hex');
|
|
175
138
|
if (a.length === b.length && timingSafeEqual(a, b)) {
|
|
176
|
-
// Update last_used_at
|
|
139
|
+
// Update last_used_at
|
|
177
140
|
try {
|
|
178
|
-
const db = getDb();
|
|
179
141
|
const now = Date.now();
|
|
180
142
|
db.update(settings)
|
|
181
143
|
.set({ lastUsedAt: now, updatedAt: now })
|
|
182
|
-
.where(eq(settings.id,
|
|
144
|
+
.where(eq(settings.id, row.id))
|
|
183
145
|
.run();
|
|
184
|
-
} catch {
|
|
185
|
-
|
|
146
|
+
} catch (err) {
|
|
147
|
+
console.error('[api-keys] Failed to update last_used_at:', err.message);
|
|
186
148
|
}
|
|
187
|
-
return
|
|
149
|
+
return { id: row.id, keyHash: parsed.key_hash, type: row.type };
|
|
188
150
|
}
|
|
189
151
|
}
|
|
190
152
|
|
|
@@ -192,12 +154,12 @@ export function verifyApiKey(rawKey) {
|
|
|
192
154
|
}
|
|
193
155
|
|
|
194
156
|
/**
|
|
195
|
-
* Create a per-
|
|
196
|
-
* Stored as type 'agent_job_api_key'
|
|
197
|
-
* @param {string}
|
|
157
|
+
* Create a per-container API key for agent secret access.
|
|
158
|
+
* Stored as type 'agent_job_api_key' with the container name in the key column.
|
|
159
|
+
* @param {string} containerName - Docker container name (used for cleanup)
|
|
198
160
|
* @returns {{ key: string }} The raw API key to inject into the container
|
|
199
161
|
*/
|
|
200
|
-
export function createAgentJobApiKey(
|
|
162
|
+
export function createAgentJobApiKey(containerName) {
|
|
201
163
|
const db = getDb();
|
|
202
164
|
const key = generateApiKey();
|
|
203
165
|
const keyHash = hashApiKey(key);
|
|
@@ -205,12 +167,11 @@ export function createAgentJobApiKey(jobId) {
|
|
|
205
167
|
db.insert(settings).values({
|
|
206
168
|
id: randomUUID(),
|
|
207
169
|
type: 'agent_job_api_key',
|
|
208
|
-
key:
|
|
170
|
+
key: containerName,
|
|
209
171
|
value: JSON.stringify({ key_hash: keyHash }),
|
|
210
172
|
createdAt: now,
|
|
211
173
|
updatedAt: now,
|
|
212
174
|
}).run();
|
|
213
|
-
invalidateApiKeyCache();
|
|
214
175
|
return { key };
|
|
215
176
|
}
|
|
216
177
|
|
package/lib/maintenance.js
CHANGED
|
@@ -2,39 +2,55 @@ import cron from 'node-cron';
|
|
|
2
2
|
import { eq } from 'drizzle-orm';
|
|
3
3
|
import { getDb } from './db/index.js';
|
|
4
4
|
import { settings } from './db/schema.js';
|
|
5
|
-
import { invalidateApiKeyCache } from './db/api-keys.js';
|
|
6
5
|
|
|
7
|
-
const
|
|
6
|
+
const TWENTY_FOUR_HOURS = 24 * 60 * 60 * 1000;
|
|
8
7
|
|
|
9
|
-
function cleanExpiredAgentJobKeys() {
|
|
8
|
+
async function cleanExpiredAgentJobKeys() {
|
|
10
9
|
try {
|
|
11
10
|
const db = getDb();
|
|
12
|
-
const cutoff = Date.now() -
|
|
11
|
+
const cutoff = Date.now() - TWENTY_FOUR_HOURS;
|
|
13
12
|
const rows = db
|
|
14
|
-
.select({ id: settings.id, lastUsedAt: settings.lastUsedAt, createdAt: settings.createdAt })
|
|
13
|
+
.select({ id: settings.id, key: settings.key, lastUsedAt: settings.lastUsedAt, createdAt: settings.createdAt })
|
|
15
14
|
.from(settings)
|
|
16
15
|
.where(eq(settings.type, 'agent_job_api_key'))
|
|
17
16
|
.all();
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
|
|
18
|
+
// Filter to candidates not used in the last 24 hours
|
|
19
|
+
const candidates = rows.filter(r =>
|
|
20
|
+
(r.lastUsedAt !== null ? r.lastUsedAt : r.createdAt) < cutoff
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
if (candidates.length === 0) {
|
|
24
|
+
console.log(`[maintenance] No expired agent job keys (${rows.length} active)`);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Check if the container still exists for each candidate
|
|
29
|
+
const { inspectContainer } = await import('./tools/docker.js');
|
|
30
|
+
const expiredIds = [];
|
|
31
|
+
for (const r of candidates) {
|
|
32
|
+
const info = await inspectContainer(r.key);
|
|
33
|
+
if (!info) {
|
|
34
|
+
expiredIds.push(r.id);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
21
38
|
if (expiredIds.length > 0) {
|
|
22
39
|
for (const id of expiredIds) {
|
|
23
40
|
db.delete(settings).where(eq(settings.id, id)).run();
|
|
24
41
|
}
|
|
25
|
-
|
|
26
|
-
console.log(`[maintenance] Deleted ${expiredIds.length} expired agent job key(s)`);
|
|
42
|
+
console.log(`[maintenance] Deleted ${expiredIds.length} orphaned agent job key(s)`);
|
|
27
43
|
} else {
|
|
28
|
-
console.log(`[maintenance]
|
|
44
|
+
console.log(`[maintenance] ${candidates.length} candidate(s) checked, all containers still running`);
|
|
29
45
|
}
|
|
30
46
|
} catch (err) {
|
|
31
47
|
console.error('[maintenance] cleanExpiredAgentJobKeys failed:', err);
|
|
32
48
|
}
|
|
33
49
|
}
|
|
34
50
|
|
|
35
|
-
function runMaintenance() {
|
|
51
|
+
async function runMaintenance() {
|
|
36
52
|
console.log('[maintenance] Running maintenance...');
|
|
37
|
-
cleanExpiredAgentJobKeys();
|
|
53
|
+
await cleanExpiredAgentJobKeys();
|
|
38
54
|
}
|
|
39
55
|
|
|
40
56
|
export function startMaintenanceCron() {
|
|
@@ -3,8 +3,6 @@ import { z } from 'zod';
|
|
|
3
3
|
import { githubApi } from './github.js';
|
|
4
4
|
import { createModel } from '../ai/model.js';
|
|
5
5
|
import { getConfig } from '../config.js';
|
|
6
|
-
import { createAgentJobApiKey } from '../db/api-keys.js';
|
|
7
|
-
|
|
8
6
|
/**
|
|
9
7
|
* Generate a short descriptive title for an agent job using the LLM.
|
|
10
8
|
* Uses structured output to avoid thinking-token leaks with extended-thinking models.
|
|
@@ -93,7 +91,6 @@ async function createAgentJob(agentJobDescription, options = {}) {
|
|
|
93
91
|
|
|
94
92
|
// 6. Launch Docker container locally (fire-and-forget with async cleanup)
|
|
95
93
|
const repoSlug = `${GH_OWNER}/${GH_REPO}`;
|
|
96
|
-
const { key: agentJobToken } = createAgentJobApiKey(agentJobId);
|
|
97
94
|
launchAgentJobContainer({
|
|
98
95
|
agentJobId,
|
|
99
96
|
repo: repoSlug,
|
|
@@ -102,7 +99,6 @@ async function createAgentJob(agentJobDescription, options = {}) {
|
|
|
102
99
|
description: agentJobDescription,
|
|
103
100
|
codingAgent: options.agentBackend,
|
|
104
101
|
llmModel: options.llmModel,
|
|
105
|
-
agentJobToken,
|
|
106
102
|
}).catch(err => {
|
|
107
103
|
console.error(`[agent-job] Failed to launch container for ${agentJobId}:`, err.message);
|
|
108
104
|
});
|
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,
|
|
199
|
+
async function runInteractiveContainer({ containerName, repo, branch, codingAgent, featureBranch, workspaceId, injectSecrets, 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,11 +234,12 @@ 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
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
237
|
+
// Create per-container API key for agent-secrets access
|
|
238
|
+
const { createAgentJobApiKey } = await import('../db/api-keys.js');
|
|
239
|
+
const { key: agentJobToken } = createAgentJobApiKey(containerName);
|
|
240
|
+
env.push(`AGENT_JOB_TOKEN=${agentJobToken}`);
|
|
241
|
+
const appUrl = getConfig('APP_URL');
|
|
242
|
+
if (appUrl) env.push(`APP_URL=${appUrl}`);
|
|
242
243
|
}
|
|
243
244
|
|
|
244
245
|
const hostConfig = {};
|
|
@@ -399,7 +400,7 @@ function buildAgentAuthEnv(agent) {
|
|
|
399
400
|
* @param {boolean} [options.injectSecrets] - Inject agent job secrets into container env
|
|
400
401
|
* @returns {Promise<{containerId: string, containerName: string}>}
|
|
401
402
|
*/
|
|
402
|
-
async function runHeadlessContainer({ containerName, repo, branch, featureBranch, workspaceId, taskPrompt, mode = 'plan', codingAgent, systemPrompt, continueSession = true, injectSecrets
|
|
403
|
+
async function runHeadlessContainer({ containerName, repo, branch, featureBranch, workspaceId, taskPrompt, mode = 'plan', codingAgent, systemPrompt, continueSession = true, injectSecrets }) {
|
|
403
404
|
const agent = codingAgent || getConfig('CODING_AGENT') || 'claude-code';
|
|
404
405
|
const version = process.env.THEPOPEBOT_VERSION;
|
|
405
406
|
const image = `stephengpope/thepopebot:coding-agent-${agent}-${version}`;
|
|
@@ -446,11 +447,12 @@ async function runHeadlessContainer({ containerName, repo, branch, featureBranch
|
|
|
446
447
|
if (jobSecrets.length > 0) {
|
|
447
448
|
env.push(`AGENT_JOB_SECRETS=${JSON.stringify(Object.fromEntries(jobSecrets.map(s => [s.key, s.value])))}`);
|
|
448
449
|
}
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
450
|
+
// Create per-container API key for agent-secrets access
|
|
451
|
+
const { createAgentJobApiKey } = await import('../db/api-keys.js');
|
|
452
|
+
const { key: agentJobToken } = createAgentJobApiKey(containerName);
|
|
453
|
+
env.push(`AGENT_JOB_TOKEN=${agentJobToken}`);
|
|
454
|
+
const appUrl = getConfig('APP_URL');
|
|
455
|
+
if (appUrl) env.push(`APP_URL=${appUrl}`);
|
|
454
456
|
}
|
|
455
457
|
|
|
456
458
|
const hostConfig = {};
|
|
@@ -861,7 +863,7 @@ async function removeVolume(name) {
|
|
|
861
863
|
* @param {string} [options.llmModel] - Model override
|
|
862
864
|
* @returns {Promise<{containerId: string, containerName: string, volumeName: string}>}
|
|
863
865
|
*/
|
|
864
|
-
async function runAgentJobContainer({ agentJobId, repo, branch, title, description, codingAgent, llmModel
|
|
866
|
+
async function runAgentJobContainer({ agentJobId, repo, branch, title, description, codingAgent, llmModel }) {
|
|
865
867
|
const agent = codingAgent || getConfig('CODING_AGENT') || 'claude-code';
|
|
866
868
|
const version = process.env.THEPOPEBOT_VERSION;
|
|
867
869
|
const image = `stephengpope/thepopebot:coding-agent-${agent}-${version}`;
|
|
@@ -899,8 +901,10 @@ async function runAgentJobContainer({ agentJobId, repo, branch, title, descripti
|
|
|
899
901
|
const appUrl = getConfig('APP_URL');
|
|
900
902
|
if (appUrl) env.push(`APP_URL=${appUrl}`);
|
|
901
903
|
|
|
902
|
-
//
|
|
903
|
-
|
|
904
|
+
// Create per-container API key for agent-secrets access
|
|
905
|
+
const { createAgentJobApiKey } = await import('../db/api-keys.js');
|
|
906
|
+
const { key: agentJobToken } = createAgentJobApiKey(containerName);
|
|
907
|
+
env.push(`AGENT_JOB_TOKEN=${agentJobToken}`);
|
|
904
908
|
|
|
905
909
|
// Inject agent job secrets (plain secrets as env vars; oauth types are null — agent must fetch via get)
|
|
906
910
|
const { getAllAgentJobSecrets } = await import('../db/config.js');
|
package/package.json
CHANGED
|
@@ -18,9 +18,8 @@ if (!cmd || cmd === 'list') {
|
|
|
18
18
|
console.log('Available secrets:');
|
|
19
19
|
keys.forEach(k => {
|
|
20
20
|
const fetchRequired = secrets[k] === null;
|
|
21
|
-
console.log(` - ${k}${fetchRequired ? ' (
|
|
21
|
+
console.log(` - ${k}${fetchRequired ? ' (use agent-job-secrets skill to fetch)' : ''}`);
|
|
22
22
|
});
|
|
23
|
-
console.log('\nTo get a value: agent-job-secrets.js get KEY_NAME');
|
|
24
23
|
}
|
|
25
24
|
process.exit(0);
|
|
26
25
|
}
|
|
@@ -31,33 +30,43 @@ if (!apiKey) { console.error('AGENT_JOB_TOKEN not available'); process.exit(1);
|
|
|
31
30
|
if (!appUrl) { console.error('APP_URL not available'); process.exit(1); }
|
|
32
31
|
|
|
33
32
|
if (cmd === 'get') {
|
|
34
|
-
if (!key) { console.error('Usage: agent-job-secrets
|
|
35
|
-
const
|
|
33
|
+
if (!key) { console.error('Usage: agent-job-secrets get KEY_NAME'); process.exit(1); }
|
|
34
|
+
const url = `${appUrl}/api/get-agent-job-secret?key=${encodeURIComponent(key)}`;
|
|
35
|
+
const res = await fetch(url, {
|
|
36
36
|
headers: { 'x-api-key': apiKey },
|
|
37
37
|
});
|
|
38
|
+
if (!res.ok) {
|
|
39
|
+
const body = await res.text();
|
|
40
|
+
console.error(`GET ${url} → ${res.status} ${body}`);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
38
43
|
const json = await res.json();
|
|
39
|
-
if (!res.ok || json.error) { console.error('Failed:', json.error || res.status); process.exit(1); }
|
|
40
44
|
console.log(json.value);
|
|
41
45
|
process.exit(0);
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
if (cmd === 'set') {
|
|
45
49
|
if (!key) {
|
|
46
|
-
console.error('Usage: agent-job-secrets
|
|
47
|
-
console.error(' echo "value" | agent-job-secrets
|
|
50
|
+
console.error('Usage: agent-job-secrets set KEY_NAME [value]');
|
|
51
|
+
console.error(' echo "value" | agent-job-secrets set KEY_NAME');
|
|
48
52
|
process.exit(1);
|
|
49
53
|
}
|
|
50
54
|
let value = inlineValue;
|
|
51
55
|
if (value === undefined) {
|
|
52
56
|
value = readFileSync('/dev/stdin', 'utf8').trim();
|
|
53
57
|
}
|
|
54
|
-
const
|
|
58
|
+
const url = `${appUrl}/api/set-agent-job-secret`;
|
|
59
|
+
const res = await fetch(url, {
|
|
55
60
|
method: 'POST',
|
|
56
61
|
headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey },
|
|
57
62
|
body: JSON.stringify({ key, value }),
|
|
58
63
|
});
|
|
64
|
+
if (!res.ok) {
|
|
65
|
+
const body = await res.text();
|
|
66
|
+
console.error(`POST ${url} → ${res.status} ${body}`);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
59
69
|
const json = await res.json();
|
|
60
|
-
if (!res.ok || json.error) { console.error('Failed:', json.error || res.status); process.exit(1); }
|
|
61
70
|
console.log(`Secret "${key}" updated.`);
|
|
62
71
|
process.exit(0);
|
|
63
72
|
}
|