thepopebot 1.2.76-beta.6 → 1.2.76-beta.7
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 +0 -7
- package/lib/ai/index.js +2 -0
- package/lib/ai/sdk-adapters/claude-code.js +33 -3
- package/lib/chat/components/chat-input.js +3 -2
- package/lib/chat/components/chat-input.jsx +3 -2
- package/lib/chat/components/chat.js +2 -1
- package/lib/chat/components/chat.jsx +1 -0
- package/lib/chat/components/code-mode-toggle.js +0 -8
- package/lib/chat/components/code-mode-toggle.jsx +0 -10
- package/lib/chat/components/message.js +1 -30
- package/lib/chat/components/message.jsx +1 -35
- package/lib/code/terminal-view.js +0 -8
- package/lib/code/terminal-view.jsx +0 -9
- package/lib/maintenance.js +31 -21
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -329,13 +329,6 @@ async function init() {
|
|
|
329
329
|
console.log(' Created .kimi/skills → ../skills/active');
|
|
330
330
|
}
|
|
331
331
|
|
|
332
|
-
// Create .agents/skills → ../skills/active symlink
|
|
333
|
-
const agentsSkillsLink = path.join(cwd, '.agents', 'skills');
|
|
334
|
-
if (!fs.existsSync(agentsSkillsLink)) {
|
|
335
|
-
fs.mkdirSync(path.dirname(agentsSkillsLink), { recursive: true });
|
|
336
|
-
createDirLink('../skills/active', agentsSkillsLink);
|
|
337
|
-
console.log(' Created .agents/skills → ../skills/active');
|
|
338
|
-
}
|
|
339
332
|
|
|
340
333
|
// Report backed-up files
|
|
341
334
|
if (backedUp.length > 0) {
|
package/lib/ai/index.js
CHANGED
|
@@ -221,6 +221,8 @@ async function* chatStream(threadId, message, attachments = [], options = {}) {
|
|
|
221
221
|
sessionId,
|
|
222
222
|
permissionMode: codeModeType,
|
|
223
223
|
attachments,
|
|
224
|
+
workspaceId,
|
|
225
|
+
chatMode: isCodeMode ? 'code' : 'agent',
|
|
224
226
|
})) {
|
|
225
227
|
// Write session ID on first meta chunk
|
|
226
228
|
if (chunk.type === 'meta' && chunk.sessionId) {
|
|
@@ -3,6 +3,8 @@ import fs from 'fs';
|
|
|
3
3
|
import { query } from '@anthropic-ai/claude-agent-sdk';
|
|
4
4
|
import { getConfig } from '../../config.js';
|
|
5
5
|
import { buildAgentAuthEnv } from '../../tools/docker.js';
|
|
6
|
+
import { createAgentJobApiKey } from '../../db/api-keys.js';
|
|
7
|
+
import { getAllAgentJobSecrets } from '../../db/config.js';
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
10
|
* Claude Agent SDK adapter. Wraps the SDK's query() and yields
|
|
@@ -62,7 +64,7 @@ function ensureSessionSymlink(wsBaseDir, workspaceDir) {
|
|
|
62
64
|
} catch {}
|
|
63
65
|
}
|
|
64
66
|
|
|
65
|
-
export async function* claudeCodeStream({ prompt, workspaceDir, systemPrompt, sessionId, permissionMode, attachments }) {
|
|
67
|
+
export async function* claudeCodeStream({ prompt, workspaceDir, systemPrompt, sessionId, permissionMode, attachments, workspaceId, chatMode }) {
|
|
66
68
|
// Point HOME at the workspace volume so the SDK stores session data on the
|
|
67
69
|
// shared volume (not the EH container's ephemeral filesystem).
|
|
68
70
|
const wsBaseDir = path.dirname(workspaceDir);
|
|
@@ -94,6 +96,23 @@ export async function* claudeCodeStream({ prompt, workspaceDir, systemPrompt, se
|
|
|
94
96
|
// Fall through — env may already have the right vars from process.env
|
|
95
97
|
}
|
|
96
98
|
|
|
99
|
+
// Inject agent job secrets when in agent chat mode
|
|
100
|
+
if (chatMode === 'agent') {
|
|
101
|
+
const shortId = (workspaceId || '').replace(/-/g, '').slice(0, 8);
|
|
102
|
+
const { key: agentJobToken } = createAgentJobApiKey(`claude-code-sdk-${shortId}`);
|
|
103
|
+
env.AGENT_JOB_TOKEN = agentJobToken;
|
|
104
|
+
const appUrl = getConfig('APP_URL');
|
|
105
|
+
if (appUrl) env.APP_URL = appUrl;
|
|
106
|
+
|
|
107
|
+
// Inject plain secrets as env vars (oauth types are null — agent fetches via skill)
|
|
108
|
+
const jobSecrets = getAllAgentJobSecrets();
|
|
109
|
+
for (const { key, value } of jobSecrets) {
|
|
110
|
+
if (value !== null && !env[key]) {
|
|
111
|
+
env[key] = value;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
97
116
|
const options = {
|
|
98
117
|
cwd: workspaceDir,
|
|
99
118
|
env,
|
|
@@ -134,6 +153,7 @@ export async function* claudeCodeStream({ prompt, workspaceDir, systemPrompt, se
|
|
|
134
153
|
|
|
135
154
|
// Track tool call state for mapping stream events
|
|
136
155
|
const activeToolCalls = new Map(); // index → { id, name, argsJson }
|
|
156
|
+
const toolNamesById = new Map(); // toolCallId → toolName (persists for tool-result lookup)
|
|
137
157
|
const activeThinkingBlocks = new Set(); // indices of active thinking blocks
|
|
138
158
|
|
|
139
159
|
try {
|
|
@@ -157,6 +177,7 @@ export async function* claudeCodeStream({ prompt, workspaceDir, systemPrompt, se
|
|
|
157
177
|
const block = event.content_block;
|
|
158
178
|
if (block.type === 'tool_use') {
|
|
159
179
|
activeToolCalls.set(event.index, { id: block.id, name: block.name, argsJson: '' });
|
|
180
|
+
toolNamesById.set(block.id, block.name);
|
|
160
181
|
yield { type: 'tool-call', toolCallId: block.id, toolName: block.name, args: {} };
|
|
161
182
|
} else if (block.type === 'thinking') {
|
|
162
183
|
activeThinkingBlocks.add(event.index);
|
|
@@ -208,14 +229,23 @@ export async function* claudeCodeStream({ prompt, workspaceDir, systemPrompt, se
|
|
|
208
229
|
: Array.isArray(block.content)
|
|
209
230
|
? block.content.map(b => b.type === 'text' ? b.text : JSON.stringify(b)).join('\n')
|
|
210
231
|
: JSON.stringify(block.content);
|
|
211
|
-
yield { type: 'tool-result', toolCallId: block.tool_use_id, result: content };
|
|
232
|
+
yield { type: 'tool-result', toolCallId: block.tool_use_id, toolName: toolNamesById.get(block.tool_use_id), result: content };
|
|
212
233
|
}
|
|
213
234
|
}
|
|
214
235
|
continue;
|
|
215
236
|
}
|
|
216
237
|
|
|
217
238
|
// ── assistant messages — redundant with streaming, skip ──
|
|
218
|
-
|
|
239
|
+
// But extract tool names so resumed tool-results can carry them.
|
|
240
|
+
if (message.type === 'assistant') {
|
|
241
|
+
const blocks = message.message?.content || [];
|
|
242
|
+
for (const block of blocks) {
|
|
243
|
+
if (block.type === 'tool_use' && block.id && block.name) {
|
|
244
|
+
toolNamesById.set(block.id, block.name);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
219
249
|
|
|
220
250
|
// ── result ──
|
|
221
251
|
if (message.type === 'result') {
|
|
@@ -146,6 +146,7 @@ function ChatInput({ input, setInput, onSubmit, status, stop, files, setFiles, d
|
|
|
146
146
|
if (e) e.preventDefault();
|
|
147
147
|
if (disabled || !input.trim() && !partialText.trim() && files.length === 0 || isStreaming) return;
|
|
148
148
|
if (canSendOverride !== void 0 && !canSendOverride) return;
|
|
149
|
+
if (isRecording) stopRecording();
|
|
149
150
|
if (partialText) {
|
|
150
151
|
const needsSpace = input && !input.endsWith(" ");
|
|
151
152
|
setInput(input + (needsSpace ? " " : "") + partialText);
|
|
@@ -339,12 +340,12 @@ function ChatInput({ input, setInput, onSubmit, status, stop, files, setFiles, d
|
|
|
339
340
|
onClick: () => codeModeSettings.onInteractiveToggle(),
|
|
340
341
|
onContextMenu: (e) => {
|
|
341
342
|
e.preventDefault();
|
|
342
|
-
if (!codeModeSettings.togglingMode && codeModeSettings.availableAgents?.length > 1) {
|
|
343
|
+
if (!codeModeSettings.togglingMode && codeModeSettings.availableAgents?.length > 1 && codeModeSettings.hasMessages) {
|
|
343
344
|
setAgentPickerOpen((prev) => !prev);
|
|
344
345
|
}
|
|
345
346
|
},
|
|
346
347
|
disabled: codeModeSettings.togglingMode || codeModeSettings.isInteractiveActive,
|
|
347
|
-
title: codeModeSettings.availableAgents?.length > 1 ? "Left-click to launch \xB7 Right-click to pick agent" : void 0,
|
|
348
|
+
title: codeModeSettings.availableAgents?.length > 1 && codeModeSettings.hasMessages ? "Left-click to launch \xB7 Right-click to pick agent" : void 0,
|
|
348
349
|
className: "inline-flex items-center gap-1.5 rounded-md px-2 py-1 text-xs text-muted-foreground hover:text-foreground transition-colors",
|
|
349
350
|
children: [
|
|
350
351
|
codeModeSettings.togglingMode && /* @__PURE__ */ jsxs("svg", { className: "animate-spin h-3 w-3", xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", children: [
|
|
@@ -140,6 +140,7 @@ export function ChatInput({ input, setInput, onSubmit, status, stop, files, setF
|
|
|
140
140
|
if (e) e.preventDefault();
|
|
141
141
|
if (disabled || (!input.trim() && !partialText.trim() && files.length === 0) || isStreaming) return;
|
|
142
142
|
if (canSendOverride !== undefined && !canSendOverride) return;
|
|
143
|
+
if (isRecording) stopRecording();
|
|
143
144
|
if (partialText) {
|
|
144
145
|
const needsSpace = input && !input.endsWith(' ');
|
|
145
146
|
setInput(input + (needsSpace ? ' ' : '') + partialText);
|
|
@@ -352,12 +353,12 @@ export function ChatInput({ input, setInput, onSubmit, status, stop, files, setF
|
|
|
352
353
|
onClick={() => codeModeSettings.onInteractiveToggle()}
|
|
353
354
|
onContextMenu={(e) => {
|
|
354
355
|
e.preventDefault();
|
|
355
|
-
if (!codeModeSettings.togglingMode && codeModeSettings.availableAgents?.length > 1) {
|
|
356
|
+
if (!codeModeSettings.togglingMode && codeModeSettings.availableAgents?.length > 1 && codeModeSettings.hasMessages) {
|
|
356
357
|
setAgentPickerOpen(prev => !prev);
|
|
357
358
|
}
|
|
358
359
|
}}
|
|
359
360
|
disabled={codeModeSettings.togglingMode || codeModeSettings.isInteractiveActive}
|
|
360
|
-
title={codeModeSettings.availableAgents?.length > 1 ? 'Left-click to launch · Right-click to pick agent' : undefined}
|
|
361
|
+
title={codeModeSettings.availableAgents?.length > 1 && codeModeSettings.hasMessages ? 'Left-click to launch · Right-click to pick agent' : undefined}
|
|
361
362
|
className="inline-flex items-center gap-1.5 rounded-md px-2 py-1 text-xs text-muted-foreground hover:text-foreground transition-colors"
|
|
362
363
|
>
|
|
363
364
|
{codeModeSettings.togglingMode && (
|
|
@@ -204,7 +204,8 @@ function Chat({ chatId, initialMessages = [], workspace = null, chatMode = null
|
|
|
204
204
|
isInteractiveActive,
|
|
205
205
|
onInteractiveToggle: handleInteractiveToggle,
|
|
206
206
|
togglingMode,
|
|
207
|
-
availableAgents
|
|
207
|
+
availableAgents,
|
|
208
|
+
hasMessages: messages.length > 0
|
|
208
209
|
};
|
|
209
210
|
const handleBranchChange = useCallback((newBranch) => {
|
|
210
211
|
setBranch(newBranch);
|
|
@@ -255,6 +255,7 @@ export function Chat({ chatId, initialMessages = [], workspace = null, chatMode
|
|
|
255
255
|
onInteractiveToggle: handleInteractiveToggle,
|
|
256
256
|
togglingMode,
|
|
257
257
|
availableAgents,
|
|
258
|
+
hasMessages: messages.length > 0,
|
|
258
259
|
};
|
|
259
260
|
|
|
260
261
|
const handleBranchChange = useCallback((newBranch) => {
|
|
@@ -327,14 +327,6 @@ function WorkspaceCommandButton({ workspaceId, diffStats, onDiffStatsRefresh, on
|
|
|
327
327
|
}, []);
|
|
328
328
|
const handleRun = useCallback(async () => {
|
|
329
329
|
if (commandRunning) return;
|
|
330
|
-
const fresh = await onDiffStatsRefresh?.();
|
|
331
|
-
const stats = fresh || diffStats;
|
|
332
|
-
if (!(stats?.insertions || 0) && !(stats?.deletions || 0)) {
|
|
333
|
-
setDialogOpen(true);
|
|
334
|
-
setCommandLogs([{ stream: "stderr", raw: "You have no changes.", parsed: [{ type: "text", text: "You have no changes." }] }]);
|
|
335
|
-
setCommandExitCode(1);
|
|
336
|
-
return;
|
|
337
|
-
}
|
|
338
330
|
setCommandRunning(true);
|
|
339
331
|
setDialogOpen(true);
|
|
340
332
|
setCommandLogs([]);
|
|
@@ -370,16 +370,6 @@ function WorkspaceCommandButton({ workspaceId, diffStats, onDiffStatsRefresh, on
|
|
|
370
370
|
const handleRun = useCallback(async () => {
|
|
371
371
|
if (commandRunning) return;
|
|
372
372
|
|
|
373
|
-
// Refresh diff stats and check for changes before running
|
|
374
|
-
const fresh = await onDiffStatsRefresh?.();
|
|
375
|
-
const stats = fresh || diffStats;
|
|
376
|
-
if (!(stats?.insertions || 0) && !(stats?.deletions || 0)) {
|
|
377
|
-
setDialogOpen(true);
|
|
378
|
-
setCommandLogs([{ stream: 'stderr', raw: 'You have no changes.', parsed: [{ type: 'text', text: 'You have no changes.' }] }]);
|
|
379
|
-
setCommandExitCode(1);
|
|
380
|
-
return;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
373
|
setCommandRunning(true);
|
|
384
374
|
setDialogOpen(true);
|
|
385
375
|
setCommandLogs([]);
|
|
@@ -83,32 +83,6 @@ function formatContent(content) {
|
|
|
83
83
|
}
|
|
84
84
|
return JSON.stringify(content, null, 2);
|
|
85
85
|
}
|
|
86
|
-
function ThinkingBlock({ part }) {
|
|
87
|
-
const [expanded, setExpanded] = useState(false);
|
|
88
|
-
const isStreaming = part.state === "input-streaming" || part.state === "input-available";
|
|
89
|
-
const content = typeof part.input === "string" ? part.input : "";
|
|
90
|
-
return /* @__PURE__ */ jsxs("div", { className: "my-1", children: [
|
|
91
|
-
/* @__PURE__ */ jsxs(
|
|
92
|
-
"button",
|
|
93
|
-
{
|
|
94
|
-
onClick: () => setExpanded(!expanded),
|
|
95
|
-
className: "flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors py-0.5",
|
|
96
|
-
children: [
|
|
97
|
-
/* @__PURE__ */ jsx(
|
|
98
|
-
ChevronDownIcon,
|
|
99
|
-
{
|
|
100
|
-
size: 12,
|
|
101
|
-
className: cn("transition-transform shrink-0", !expanded && "-rotate-90")
|
|
102
|
-
}
|
|
103
|
-
),
|
|
104
|
-
isStreaming && /* @__PURE__ */ jsx(SpinnerIcon, { size: 12, className: "shrink-0" }),
|
|
105
|
-
isStreaming ? /* @__PURE__ */ jsx("span", { className: "thinking-shimmer", children: "Thinking..." }) : /* @__PURE__ */ jsx("span", { children: "Thoughts" })
|
|
106
|
-
]
|
|
107
|
-
}
|
|
108
|
-
),
|
|
109
|
-
expanded && /* @__PURE__ */ jsx("div", { className: "mt-1.5 ml-4 pl-3 border-l border-border/50 text-xs text-muted-foreground/80 whitespace-pre-wrap leading-relaxed max-h-64 overflow-y-auto", children: content || /* @__PURE__ */ jsx("span", { className: "italic opacity-50", children: "..." }) })
|
|
110
|
-
] });
|
|
111
|
-
}
|
|
112
86
|
function ToolCall({ part, className }) {
|
|
113
87
|
const [expanded, setExpanded] = useState(false);
|
|
114
88
|
const toolName = part.toolName || (part.type?.startsWith("tool-") ? part.type.slice(5) : "tool");
|
|
@@ -260,7 +234,7 @@ function PreviewMessage({ message, isLoading, onRetry, onEdit }) {
|
|
|
260
234
|
return;
|
|
261
235
|
}
|
|
262
236
|
setShowWorking(false);
|
|
263
|
-
const timer = setTimeout(() => setShowWorking(true),
|
|
237
|
+
const timer = setTimeout(() => setShowWorking(true), 500);
|
|
264
238
|
return () => clearTimeout(timer);
|
|
265
239
|
}, [isLoading, partsLength, textLength, hasRunningTool]);
|
|
266
240
|
const fileParts = message.parts?.filter((p) => p.type === "file") || [];
|
|
@@ -399,9 +373,6 @@ function PreviewMessage({ message, isLoading, onRetry, onEdit }) {
|
|
|
399
373
|
] }, i);
|
|
400
374
|
}
|
|
401
375
|
if (part.type?.startsWith("tool-")) {
|
|
402
|
-
if (part.toolName === "__thinking__") {
|
|
403
|
-
return /* @__PURE__ */ jsx(ThinkingBlock, { part }, part.toolCallId || i);
|
|
404
|
-
}
|
|
405
376
|
const prevPart = message.parts[i - 1];
|
|
406
377
|
const afterText = prevPart?.type === "text";
|
|
407
378
|
return /* @__PURE__ */ jsx(ToolCall, { part, className: afterText ? "mt-3" : void 0 }, part.toolCallId || i);
|
|
@@ -77,37 +77,6 @@ function formatContent(content) {
|
|
|
77
77
|
return JSON.stringify(content, null, 2);
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
function ThinkingBlock({ part }) {
|
|
81
|
-
const [expanded, setExpanded] = useState(false);
|
|
82
|
-
const isStreaming = part.state === 'input-streaming' || part.state === 'input-available';
|
|
83
|
-
const content = typeof part.input === 'string' ? part.input : '';
|
|
84
|
-
|
|
85
|
-
return (
|
|
86
|
-
<div className="my-1">
|
|
87
|
-
<button
|
|
88
|
-
onClick={() => setExpanded(!expanded)}
|
|
89
|
-
className="flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors py-0.5"
|
|
90
|
-
>
|
|
91
|
-
<ChevronDownIcon
|
|
92
|
-
size={12}
|
|
93
|
-
className={cn('transition-transform shrink-0', !expanded && '-rotate-90')}
|
|
94
|
-
/>
|
|
95
|
-
{isStreaming && <SpinnerIcon size={12} className="shrink-0" />}
|
|
96
|
-
{isStreaming ? (
|
|
97
|
-
<span className="thinking-shimmer">Thinking...</span>
|
|
98
|
-
) : (
|
|
99
|
-
<span>Thoughts</span>
|
|
100
|
-
)}
|
|
101
|
-
</button>
|
|
102
|
-
{expanded && (
|
|
103
|
-
<div className="mt-1.5 ml-4 pl-3 border-l border-border/50 text-xs text-muted-foreground/80 whitespace-pre-wrap leading-relaxed max-h-64 overflow-y-auto">
|
|
104
|
-
{content || <span className="italic opacity-50">...</span>}
|
|
105
|
-
</div>
|
|
106
|
-
)}
|
|
107
|
-
</div>
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
80
|
function ToolCall({ part, className }) {
|
|
112
81
|
const [expanded, setExpanded] = useState(false);
|
|
113
82
|
|
|
@@ -310,7 +279,7 @@ export function PreviewMessage({ message, isLoading, onRetry, onEdit }) {
|
|
|
310
279
|
return;
|
|
311
280
|
}
|
|
312
281
|
setShowWorking(false);
|
|
313
|
-
const timer = setTimeout(() => setShowWorking(true),
|
|
282
|
+
const timer = setTimeout(() => setShowWorking(true), 500);
|
|
314
283
|
return () => clearTimeout(timer);
|
|
315
284
|
}, [isLoading, partsLength, textLength, hasRunningTool]);
|
|
316
285
|
|
|
@@ -482,9 +451,6 @@ export function PreviewMessage({ message, isLoading, onRetry, onEdit }) {
|
|
|
482
451
|
);
|
|
483
452
|
}
|
|
484
453
|
if (part.type?.startsWith('tool-')) {
|
|
485
|
-
if (part.toolName === '__thinking__') {
|
|
486
|
-
return <ThinkingBlock key={part.toolCallId || i} part={part} />;
|
|
487
|
-
}
|
|
488
454
|
const prevPart = message.parts[i - 1];
|
|
489
455
|
const afterText = prevPart?.type === 'text';
|
|
490
456
|
return <ToolCall key={part.toolCallId || i} part={part} className={afterText ? 'mt-3' : undefined} />;
|
|
@@ -812,14 +812,6 @@ function ToolbarCommandButton({ codeWorkspaceId, diffStats, onDiffStatsRefresh,
|
|
|
812
812
|
}, [dropupOpen]);
|
|
813
813
|
const handleRun = useCallback(async () => {
|
|
814
814
|
if (commandRunning) return;
|
|
815
|
-
const fresh = await onDiffStatsRefresh?.();
|
|
816
|
-
const stats = fresh || diffStats;
|
|
817
|
-
if (!(stats?.insertions || 0) && !(stats?.deletions || 0)) {
|
|
818
|
-
setDialogOpen(true);
|
|
819
|
-
setCommandOutput("You have no changes.");
|
|
820
|
-
setCommandExitCode(1);
|
|
821
|
-
return;
|
|
822
|
-
}
|
|
823
815
|
setCommandRunning(true);
|
|
824
816
|
setDialogOpen(true);
|
|
825
817
|
setCommandOutput("");
|
|
@@ -872,15 +872,6 @@ function ToolbarCommandButton({ codeWorkspaceId, diffStats, onDiffStatsRefresh,
|
|
|
872
872
|
const handleRun = useCallback(async () => {
|
|
873
873
|
if (commandRunning) return;
|
|
874
874
|
|
|
875
|
-
const fresh = await onDiffStatsRefresh?.();
|
|
876
|
-
const stats = fresh || diffStats;
|
|
877
|
-
if (!(stats?.insertions || 0) && !(stats?.deletions || 0)) {
|
|
878
|
-
setDialogOpen(true);
|
|
879
|
-
setCommandOutput('You have no changes.');
|
|
880
|
-
setCommandExitCode(1);
|
|
881
|
-
return;
|
|
882
|
-
}
|
|
883
|
-
|
|
884
875
|
setCommandRunning(true);
|
|
885
876
|
setDialogOpen(true);
|
|
886
877
|
setCommandOutput('');
|
package/lib/maintenance.js
CHANGED
|
@@ -4,44 +4,54 @@ import { getDb } from './db/index.js';
|
|
|
4
4
|
import { settings } from './db/schema.js';
|
|
5
5
|
|
|
6
6
|
const TWENTY_FOUR_HOURS = 24 * 60 * 60 * 1000;
|
|
7
|
+
const ONE_HOUR = 60 * 60 * 1000;
|
|
7
8
|
|
|
8
9
|
async function cleanExpiredAgentJobKeys() {
|
|
9
10
|
try {
|
|
10
11
|
const db = getDb();
|
|
11
|
-
const cutoff = Date.now() - TWENTY_FOUR_HOURS;
|
|
12
12
|
const rows = db
|
|
13
13
|
.select({ id: settings.id, key: settings.key, lastUsedAt: settings.lastUsedAt, createdAt: settings.createdAt })
|
|
14
14
|
.from(settings)
|
|
15
15
|
.where(eq(settings.type, 'agent_job_api_key'))
|
|
16
16
|
.all();
|
|
17
17
|
|
|
18
|
-
//
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
);
|
|
18
|
+
// Split into SDK keys (no container) vs container-backed keys
|
|
19
|
+
const sdkRows = rows.filter(r => r.key.includes('sdk'));
|
|
20
|
+
const containerRows = rows.filter(r => !r.key.includes('sdk'));
|
|
22
21
|
|
|
23
|
-
|
|
24
|
-
console.log(`[maintenance] No expired agent job keys (${rows.length} active)`);
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
22
|
+
let deleted = 0;
|
|
27
23
|
|
|
28
|
-
//
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
24
|
+
// SDK keys: delete any older than 1 hour (no container to inspect)
|
|
25
|
+
const sdkCutoff = Date.now() - ONE_HOUR;
|
|
26
|
+
for (const r of sdkRows) {
|
|
27
|
+
const age = r.lastUsedAt !== null ? r.lastUsedAt : r.createdAt;
|
|
28
|
+
if (age < sdkCutoff) {
|
|
29
|
+
db.delete(settings).where(eq(settings.id, r.id)).run();
|
|
30
|
+
deleted++;
|
|
35
31
|
}
|
|
36
32
|
}
|
|
37
33
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
// Container-backed keys: existing logic — 24h expiry + container inspect
|
|
35
|
+
const containerCutoff = Date.now() - TWENTY_FOUR_HOURS;
|
|
36
|
+
const candidates = containerRows.filter(r =>
|
|
37
|
+
(r.lastUsedAt !== null ? r.lastUsedAt : r.createdAt) < containerCutoff
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
if (candidates.length > 0) {
|
|
41
|
+
const { inspectContainer } = await import('./tools/docker.js');
|
|
42
|
+
for (const r of candidates) {
|
|
43
|
+
const info = await inspectContainer(r.key);
|
|
44
|
+
if (!info) {
|
|
45
|
+
db.delete(settings).where(eq(settings.id, r.id)).run();
|
|
46
|
+
deleted++;
|
|
47
|
+
}
|
|
41
48
|
}
|
|
42
|
-
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (deleted > 0) {
|
|
52
|
+
console.log(`[maintenance] Deleted ${deleted} expired agent job key(s)`);
|
|
43
53
|
} else {
|
|
44
|
-
console.log(`[maintenance] ${
|
|
54
|
+
console.log(`[maintenance] No expired agent job keys (${rows.length} active)`);
|
|
45
55
|
}
|
|
46
56
|
} catch (err) {
|
|
47
57
|
console.error('[maintenance] cleanExpiredAgentJobKeys failed:', err);
|