@link-assistant/hive-mind 1.45.1 ā 1.46.1
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/CHANGELOG.md +26 -0
- package/package.json +1 -1
- package/src/agent.lib.mjs +13 -0
- package/src/isolation-runner.lib.mjs +204 -0
- package/src/session-monitor.lib.mjs +253 -0
- package/src/telegram-bot.mjs +58 -57
- package/src/telegram-solve-queue.lib.mjs +33 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 1.46.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 84aacf7: fix: pass LINK_ASSISTANT_AGENT_VERBOSE env var to agent process for HTTP logging (#1521)
|
|
8
|
+
|
|
9
|
+
## 1.46.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- d9721c0: Add work session completion notifications and isolation mode to Telegram bot
|
|
14
|
+
|
|
15
|
+
Session notifications:
|
|
16
|
+
- Tracks sessions started by `/solve` and `/hive` commands
|
|
17
|
+
- Monitors sessions every 30 seconds and sends completion notifications
|
|
18
|
+
- Sends notification with session name, duration, URL, and exit status
|
|
19
|
+
- Persistent session tracking via ExecutionStore from start-command
|
|
20
|
+
|
|
21
|
+
Isolation mode (`--isolation` option, experimental):
|
|
22
|
+
- New `--isolation` flag for Telegram bot: `screen`, `tmux`, or `docker`
|
|
23
|
+
- Uses `$` CLI from link-foundation/start with GUID-based session tracking
|
|
24
|
+
- Tracks session completion via `$ --status <uuid>` for reliable detection
|
|
25
|
+
- Solve queue supports isolation-aware execution and process counting
|
|
26
|
+
- Each isolated session gets a unique UUID for unambiguous tracking
|
|
27
|
+
- Without `--isolation`, uses existing `start-screen` command (unchanged)
|
|
28
|
+
|
|
3
29
|
## 1.45.1
|
|
4
30
|
|
|
5
31
|
### Patch Changes
|
package/package.json
CHANGED
package/src/agent.lib.mjs
CHANGED
|
@@ -508,9 +508,22 @@ export const executeAgentCommand = async params => {
|
|
|
508
508
|
try {
|
|
509
509
|
// Pipe the prompt file to agent via stdin
|
|
510
510
|
// Use agentArgs which includes --model and optionally --verbose
|
|
511
|
+
|
|
512
|
+
// Issue #1521: Build environment for agent process
|
|
513
|
+
// Pass LINK_ASSISTANT_AGENT_VERBOSE env var when --verbose is enabled
|
|
514
|
+
// This ensures Flag.LINK_ASSISTANT_AGENT_VERBOSE is true at module load time inside the agent,
|
|
515
|
+
// which is required for HTTP request/response logging to work.
|
|
516
|
+
// The --verbose CLI flag alone is not sufficient because the agent's Flag module
|
|
517
|
+
// reads the env var at initialization, before yargs middleware calls Flag.setVerbose().
|
|
518
|
+
const agentEnv = { ...process.env };
|
|
519
|
+
if (argv.verbose) {
|
|
520
|
+
agentEnv.LINK_ASSISTANT_AGENT_VERBOSE = 'true';
|
|
521
|
+
}
|
|
522
|
+
|
|
511
523
|
execCommand = $({
|
|
512
524
|
cwd: tempDir,
|
|
513
525
|
mirror: false,
|
|
526
|
+
env: agentEnv,
|
|
514
527
|
})`cat ${promptFile} | ${agentPath} ${agentArgs}`;
|
|
515
528
|
|
|
516
529
|
await log(`${formatAligned('š', 'Command details:', '')}`);
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Isolation Runner for Telegram bot
|
|
3
|
+
*
|
|
4
|
+
* Executes commands using the `$` CLI from start-command with isolation backends
|
|
5
|
+
* (screen, tmux, docker). Uses GUIDs for unique session tracking and
|
|
6
|
+
* `$ --status <uuid>` for reliable completion detection.
|
|
7
|
+
*
|
|
8
|
+
* Uses command-stream library to invoke the globally-installed `$` CLI,
|
|
9
|
+
* following the same pattern as claude.lib.mjs, agent.lib.mjs, etc.
|
|
10
|
+
*
|
|
11
|
+
* @see https://github.com/link-foundation/start
|
|
12
|
+
* @see https://github.com/link-assistant/hive-mind/issues/380
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import crypto from 'crypto';
|
|
16
|
+
|
|
17
|
+
if (typeof use === 'undefined') {
|
|
18
|
+
globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const { $ } = await use('command-stream');
|
|
22
|
+
|
|
23
|
+
// Valid isolation backends
|
|
24
|
+
const VALID_ISOLATION_BACKENDS = ['screen', 'tmux', 'docker'];
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Generate a UUID v4 for unique session identification
|
|
28
|
+
* @returns {string} UUID v4 string
|
|
29
|
+
*/
|
|
30
|
+
export function generateSessionId() {
|
|
31
|
+
return crypto.randomUUID();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Find the `$` CLI binary path
|
|
36
|
+
* @returns {Promise<string|null>} Path to `$` binary or null
|
|
37
|
+
*/
|
|
38
|
+
async function findStartCommandBinary() {
|
|
39
|
+
try {
|
|
40
|
+
const result = await $`which $`;
|
|
41
|
+
const path = result.stdout?.toString().trim() || '';
|
|
42
|
+
return path || null;
|
|
43
|
+
} catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Execute a command with isolation via `$` from start-command
|
|
50
|
+
*
|
|
51
|
+
* @param {string} command - The command to run (e.g., 'solve')
|
|
52
|
+
* @param {string[]} args - Arguments for the command
|
|
53
|
+
* @param {Object} options - Isolation options
|
|
54
|
+
* @param {string} options.backend - Isolation backend: 'screen', 'tmux', or 'docker'
|
|
55
|
+
* @param {string} [options.sessionId] - UUID for session tracking (auto-generated if not provided)
|
|
56
|
+
* @param {boolean} [options.verbose] - Enable verbose logging
|
|
57
|
+
* @returns {Promise<{success: boolean, sessionId: string, output: string, error?: string, warning?: string}>}
|
|
58
|
+
*/
|
|
59
|
+
export async function executeWithIsolation(command, args, options = {}) {
|
|
60
|
+
const { backend, verbose = false } = options;
|
|
61
|
+
const sessionId = options.sessionId || generateSessionId();
|
|
62
|
+
|
|
63
|
+
if (!VALID_ISOLATION_BACKENDS.includes(backend)) {
|
|
64
|
+
return {
|
|
65
|
+
success: false,
|
|
66
|
+
sessionId,
|
|
67
|
+
output: '',
|
|
68
|
+
error: `Invalid isolation backend: '${backend}'. Must be one of: ${VALID_ISOLATION_BACKENDS.join(', ')}`,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const binPath = await findStartCommandBinary();
|
|
73
|
+
if (!binPath) {
|
|
74
|
+
return {
|
|
75
|
+
success: false,
|
|
76
|
+
sessionId,
|
|
77
|
+
output: '',
|
|
78
|
+
warning: 'ā ļø WARNING: start-command ($) not found in PATH\nPlease install: npm install -g start-command',
|
|
79
|
+
error: 'start-command ($) not found',
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (verbose) {
|
|
84
|
+
console.log(`[VERBOSE] isolation-runner: Using $ binary at: ${binPath}`);
|
|
85
|
+
console.log(`[VERBOSE] isolation-runner: Backend: ${backend}, Session ID: ${sessionId}`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Build arguments as array for the $ CLI:
|
|
89
|
+
// $ --isolated <backend> --detached --session <sessionId> -- <command> <args...>
|
|
90
|
+
const argsStr = args.join(' ');
|
|
91
|
+
|
|
92
|
+
if (verbose) {
|
|
93
|
+
console.log(`[VERBOSE] isolation-runner: $ --isolated ${backend} --detached --session ${sessionId} -- ${command} ${argsStr}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
const result = await $({ mirror: false })`${binPath} --isolated ${backend} --detached --session ${sessionId} -- ${command} ${argsStr}`;
|
|
98
|
+
|
|
99
|
+
const stdout = result.stdout?.toString() || '';
|
|
100
|
+
const stderr = result.stderr?.toString() || '';
|
|
101
|
+
const output = stdout + (stderr ? '\n' + stderr : '');
|
|
102
|
+
|
|
103
|
+
if (verbose) {
|
|
104
|
+
console.log(`[VERBOSE] isolation-runner: Output: ${output.substring(0, 500)}`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
success: true,
|
|
109
|
+
sessionId,
|
|
110
|
+
output: output.trim(),
|
|
111
|
+
};
|
|
112
|
+
} catch (error) {
|
|
113
|
+
const stdout = error.stdout?.toString() || '';
|
|
114
|
+
const stderr = error.stderr?.toString() || '';
|
|
115
|
+
const output = stdout + stderr;
|
|
116
|
+
|
|
117
|
+
if (verbose) {
|
|
118
|
+
console.error(`[VERBOSE] isolation-runner: Error: ${error.message}`);
|
|
119
|
+
console.error(`[VERBOSE] isolation-runner: Output: ${output.substring(0, 500)}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
success: false,
|
|
124
|
+
sessionId,
|
|
125
|
+
output: output.trim(),
|
|
126
|
+
error: error.message,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Query the status of an isolated session via `$ --status <uuid>`
|
|
133
|
+
*
|
|
134
|
+
* @param {string} sessionId - UUID of the session to check
|
|
135
|
+
* @param {boolean} [verbose] - Enable verbose logging
|
|
136
|
+
* @returns {Promise<{exists: boolean, status: string|null, exitCode: number|null, raw: string}>}
|
|
137
|
+
*/
|
|
138
|
+
export async function querySessionStatus(sessionId, verbose = false) {
|
|
139
|
+
const binPath = await findStartCommandBinary();
|
|
140
|
+
if (!binPath) {
|
|
141
|
+
if (verbose) {
|
|
142
|
+
console.log('[VERBOSE] isolation-runner: Cannot query status - $ binary not found');
|
|
143
|
+
}
|
|
144
|
+
return { exists: false, status: null, exitCode: null, raw: '' };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
const result = await $({ mirror: false })`${binPath} --status ${sessionId} --output-format json`;
|
|
149
|
+
|
|
150
|
+
const stdout = result.stdout?.toString().trim() || '';
|
|
151
|
+
|
|
152
|
+
if (verbose) {
|
|
153
|
+
console.log(`[VERBOSE] isolation-runner: Status query result: ${stdout.substring(0, 300)}`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
const data = JSON.parse(stdout);
|
|
158
|
+
return {
|
|
159
|
+
exists: true,
|
|
160
|
+
status: data.status || null,
|
|
161
|
+
exitCode: data.exitCode !== undefined ? data.exitCode : null,
|
|
162
|
+
raw: stdout,
|
|
163
|
+
};
|
|
164
|
+
} catch {
|
|
165
|
+
// If JSON parsing fails, try text-based detection
|
|
166
|
+
const isExecuting = stdout.includes('executing');
|
|
167
|
+
const isExecuted = stdout.includes('executed');
|
|
168
|
+
return {
|
|
169
|
+
exists: isExecuting || isExecuted,
|
|
170
|
+
status: isExecuting ? 'executing' : isExecuted ? 'executed' : null,
|
|
171
|
+
exitCode: null,
|
|
172
|
+
raw: stdout,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
} catch (error) {
|
|
176
|
+
if (verbose) {
|
|
177
|
+
console.log(`[VERBOSE] isolation-runner: Status query error: ${error.message}`);
|
|
178
|
+
}
|
|
179
|
+
return { exists: false, status: null, exitCode: null, raw: '' };
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Check if an isolated session is still running
|
|
185
|
+
*
|
|
186
|
+
* @param {string} sessionId - UUID of the session
|
|
187
|
+
* @param {boolean} [verbose] - Enable verbose logging
|
|
188
|
+
* @returns {Promise<boolean>} True if session is still executing
|
|
189
|
+
*/
|
|
190
|
+
export async function isSessionRunning(sessionId, verbose = false) {
|
|
191
|
+
const result = await querySessionStatus(sessionId, verbose);
|
|
192
|
+
return result.exists && result.status === 'executing';
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Validate that an isolation backend value is valid
|
|
197
|
+
* @param {string} backend - Backend value to validate
|
|
198
|
+
* @returns {boolean}
|
|
199
|
+
*/
|
|
200
|
+
export function isValidIsolationBackend(backend) {
|
|
201
|
+
return VALID_ISOLATION_BACKENDS.includes(backend);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export { VALID_ISOLATION_BACKENDS };
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session monitoring for Telegram bot
|
|
3
|
+
*
|
|
4
|
+
* Tracks active sessions (screen-based or isolation-based) and sends
|
|
5
|
+
* notifications when they complete.
|
|
6
|
+
*
|
|
7
|
+
* Two tracking modes:
|
|
8
|
+
* 1. Screen mode (default): Uses `screen -ls` to detect session completion
|
|
9
|
+
* 2. Isolation mode: Uses `$ --status <uuid>` from start-command CLI for reliable tracking
|
|
10
|
+
*
|
|
11
|
+
* Session state is stored in-memory. The `$` CLI (start-command) is accessed
|
|
12
|
+
* purely via its CLI interface, not as a library dependency.
|
|
13
|
+
*
|
|
14
|
+
* @see https://github.com/link-foundation/start
|
|
15
|
+
* @see https://github.com/link-assistant/hive-mind/issues/380
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { promisify } from 'util';
|
|
19
|
+
import { exec as execCallback } from 'child_process';
|
|
20
|
+
|
|
21
|
+
const exec = promisify(execCallback);
|
|
22
|
+
|
|
23
|
+
// Lazy import for isolation runner (only when needed)
|
|
24
|
+
let _querySessionStatus = null;
|
|
25
|
+
async function getQuerySessionStatus() {
|
|
26
|
+
if (!_querySessionStatus) {
|
|
27
|
+
const mod = await import('./isolation-runner.lib.mjs');
|
|
28
|
+
_querySessionStatus = mod.querySessionStatus;
|
|
29
|
+
}
|
|
30
|
+
return _querySessionStatus;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// In-memory session store
|
|
34
|
+
const activeSessions = new Map();
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Check if a screen session exists
|
|
38
|
+
* @param {string} sessionName - Name of the screen session to check
|
|
39
|
+
* @returns {Promise<boolean>} True if session exists, false otherwise
|
|
40
|
+
*/
|
|
41
|
+
export async function checkScreenSessionExists(sessionName) {
|
|
42
|
+
try {
|
|
43
|
+
const { stdout } = await exec('screen -ls');
|
|
44
|
+
return stdout.includes(sessionName);
|
|
45
|
+
} catch {
|
|
46
|
+
// screen -ls returns exit code 1 when no sessions exist
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Check if an isolated session is still running using $ --status
|
|
53
|
+
* @param {string} sessionId - UUID of the isolated session
|
|
54
|
+
* @param {boolean} verbose - Whether to log verbose output
|
|
55
|
+
* @returns {Promise<boolean>} True if session is still running
|
|
56
|
+
*/
|
|
57
|
+
async function checkIsolatedSessionRunning(sessionId, verbose = false) {
|
|
58
|
+
try {
|
|
59
|
+
const queryStatus = await getQuerySessionStatus();
|
|
60
|
+
const result = await queryStatus(sessionId, verbose);
|
|
61
|
+
return result.exists && result.status === 'executing';
|
|
62
|
+
} catch (error) {
|
|
63
|
+
if (verbose) {
|
|
64
|
+
console.error(`[VERBOSE] Error checking isolated session ${sessionId}: ${error.message}`);
|
|
65
|
+
}
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Get the exit code of a completed isolated session
|
|
72
|
+
* @param {string} sessionId - UUID of the isolated session
|
|
73
|
+
* @param {boolean} verbose - Whether to log verbose output
|
|
74
|
+
* @returns {Promise<number|null>} Exit code or null if unknown
|
|
75
|
+
*/
|
|
76
|
+
async function getIsolatedSessionExitCode(sessionId, verbose = false) {
|
|
77
|
+
try {
|
|
78
|
+
const queryStatus = await getQuerySessionStatus();
|
|
79
|
+
const result = await queryStatus(sessionId, verbose);
|
|
80
|
+
if (result.exists && result.status === 'executed') {
|
|
81
|
+
return result.exitCode;
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
} catch {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Track a new session for completion monitoring
|
|
91
|
+
*
|
|
92
|
+
* @param {string} sessionName - Name of the screen session or isolation session UUID
|
|
93
|
+
* @param {Object} sessionInfo - Session metadata
|
|
94
|
+
* @param {number} sessionInfo.chatId - Telegram chat ID to notify
|
|
95
|
+
* @param {number} [sessionInfo.messageId] - Telegram message ID to update on completion
|
|
96
|
+
* @param {Date} sessionInfo.startTime - When the session started
|
|
97
|
+
* @param {string} sessionInfo.url - GitHub URL being processed
|
|
98
|
+
* @param {string} sessionInfo.command - Command type (solve/hive)
|
|
99
|
+
* @param {string} [sessionInfo.isolationBackend] - Isolation backend if using isolation mode
|
|
100
|
+
* @param {string} [sessionInfo.sessionId] - UUID for isolation-based sessions
|
|
101
|
+
* @param {boolean} verbose - Whether to log verbose output
|
|
102
|
+
*/
|
|
103
|
+
export function trackSession(sessionName, sessionInfo, verbose = false) {
|
|
104
|
+
activeSessions.set(sessionName, sessionInfo);
|
|
105
|
+
if (verbose) {
|
|
106
|
+
const mode = sessionInfo.isolationBackend ? `isolation:${sessionInfo.isolationBackend}` : 'screen';
|
|
107
|
+
console.log(`[VERBOSE] Session ${sessionName} tracked in memory (mode: ${mode})`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Get the number of active sessions being tracked
|
|
113
|
+
* @param {boolean} verbose - Whether to log verbose output
|
|
114
|
+
* @returns {number} Number of active sessions
|
|
115
|
+
*/
|
|
116
|
+
export function getActiveSessionCount(verbose = false) {
|
|
117
|
+
if (verbose) {
|
|
118
|
+
console.log(`[VERBOSE] Active sessions: ${activeSessions.size}`);
|
|
119
|
+
}
|
|
120
|
+
return activeSessions.size;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Get all active sessions
|
|
125
|
+
* @param {boolean} verbose - Whether to log verbose output
|
|
126
|
+
* @returns {Array<{sessionName: string, sessionInfo: Object}>} Array of active sessions
|
|
127
|
+
*/
|
|
128
|
+
function getActiveSessions(verbose = false) {
|
|
129
|
+
const sessions = [];
|
|
130
|
+
for (const [sessionName, sessionInfo] of activeSessions.entries()) {
|
|
131
|
+
sessions.push({ sessionName, sessionInfo });
|
|
132
|
+
}
|
|
133
|
+
if (verbose) {
|
|
134
|
+
console.log(`[VERBOSE] Retrieved ${sessions.length} active session(s)`);
|
|
135
|
+
}
|
|
136
|
+
return sessions;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Remove a session from tracking
|
|
141
|
+
* @param {string} sessionName - Name of the session to remove
|
|
142
|
+
* @param {boolean} verbose - Whether to log verbose output
|
|
143
|
+
*/
|
|
144
|
+
function completeSession(sessionName, exitCode = 0, verbose = false) {
|
|
145
|
+
activeSessions.delete(sessionName);
|
|
146
|
+
if (verbose) {
|
|
147
|
+
console.log(`[VERBOSE] Session ${sessionName} removed from tracking (exit: ${exitCode})`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Monitor active sessions and send notifications when they complete
|
|
153
|
+
* @param {Object} bot - Telegraf bot instance for sending messages
|
|
154
|
+
* @param {boolean} verbose - Whether to log verbose output
|
|
155
|
+
*/
|
|
156
|
+
export async function monitorSessions(bot, verbose = false) {
|
|
157
|
+
const sessions = getActiveSessions(verbose);
|
|
158
|
+
|
|
159
|
+
if (sessions.length === 0) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (verbose) {
|
|
164
|
+
console.log(`[VERBOSE] Checking ${sessions.length} active session(s)...`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
for (const { sessionName, sessionInfo } of sessions) {
|
|
168
|
+
let stillRunning;
|
|
169
|
+
let exitCode = null;
|
|
170
|
+
|
|
171
|
+
if (sessionInfo.isolationBackend && sessionInfo.sessionId) {
|
|
172
|
+
// Isolation mode: use $ --status for reliable tracking
|
|
173
|
+
stillRunning = await checkIsolatedSessionRunning(sessionInfo.sessionId, verbose);
|
|
174
|
+
if (!stillRunning) {
|
|
175
|
+
exitCode = await getIsolatedSessionExitCode(sessionInfo.sessionId, verbose);
|
|
176
|
+
}
|
|
177
|
+
} else {
|
|
178
|
+
// Screen mode: use screen -ls for detection
|
|
179
|
+
stillRunning = await checkScreenSessionExists(sessionName);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (!stillRunning) {
|
|
183
|
+
console.log(`Session ${sessionName} has finished. Sending notification to chat ${sessionInfo.chatId}`);
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
const endTime = new Date();
|
|
187
|
+
const startTime = sessionInfo.startTime instanceof Date ? sessionInfo.startTime : new Date(sessionInfo.startTime);
|
|
188
|
+
const duration = Math.round((endTime - startTime) / 1000);
|
|
189
|
+
const minutes = Math.floor(duration / 60);
|
|
190
|
+
const seconds = duration % 60;
|
|
191
|
+
|
|
192
|
+
const statusEmoji = exitCode === null || exitCode === 0 ? 'ā
' : 'ā';
|
|
193
|
+
const statusText = exitCode === null || exitCode === 0 ? 'Completed' : `Failed (exit code: ${exitCode})`;
|
|
194
|
+
const isolationInfo = sessionInfo.isolationBackend ? `\nš Isolation: ${sessionInfo.isolationBackend}` : '';
|
|
195
|
+
|
|
196
|
+
let message = `${statusEmoji} *Work Session ${statusText}*\n\n`;
|
|
197
|
+
message += `š Session: \`${sessionName}\`\n`;
|
|
198
|
+
message += `ā±ļø Duration: ${minutes}m ${seconds}s\n`;
|
|
199
|
+
message += `š URL: ${sessionInfo.url}${isolationInfo}\n\n`;
|
|
200
|
+
message += `The work session has finished. You can now review the results.`;
|
|
201
|
+
|
|
202
|
+
// Update the original reply message if messageId is available, otherwise send new message
|
|
203
|
+
if (sessionInfo.messageId) {
|
|
204
|
+
await bot.telegram.editMessageText(sessionInfo.chatId, sessionInfo.messageId, undefined, message, { parse_mode: 'Markdown' });
|
|
205
|
+
} else {
|
|
206
|
+
await bot.telegram.sendMessage(sessionInfo.chatId, message, { parse_mode: 'Markdown' });
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
completeSession(sessionName, exitCode || 0, verbose);
|
|
210
|
+
} catch (error) {
|
|
211
|
+
console.error(`Failed to send completion notification for ${sessionName}:`, error);
|
|
212
|
+
completeSession(sessionName, 1, verbose);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Start the session monitoring interval
|
|
220
|
+
* @param {Object} bot - Telegraf bot instance for sending messages
|
|
221
|
+
* @param {boolean} verbose - Whether to log verbose output
|
|
222
|
+
* @param {number} intervalMs - Monitoring interval in milliseconds (default: 30000)
|
|
223
|
+
* @returns {NodeJS.Timer} The interval timer (can be cleared with clearInterval)
|
|
224
|
+
*/
|
|
225
|
+
export function startSessionMonitoring(bot, verbose = false, intervalMs = 30000) {
|
|
226
|
+
const timer = setInterval(() => monitorSessions(bot, verbose), intervalMs);
|
|
227
|
+
console.log(`š Session monitoring started (checking every ${intervalMs / 1000} seconds, storage: in-memory)`);
|
|
228
|
+
return timer;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Get statistics about session tracking
|
|
233
|
+
* @param {boolean} verbose - Whether to log verbose output
|
|
234
|
+
* @returns {Object} Statistics object
|
|
235
|
+
*/
|
|
236
|
+
export function getSessionStats(verbose = false) {
|
|
237
|
+
const sessions = Array.from(activeSessions.values());
|
|
238
|
+
const isolated = sessions.filter(s => s.isolationBackend);
|
|
239
|
+
|
|
240
|
+
if (verbose) {
|
|
241
|
+
console.log(`[VERBOSE] Session stats: ${sessions.length} total, ${isolated.length} isolated`);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
total: activeSessions.size,
|
|
246
|
+
executing: activeSessions.size,
|
|
247
|
+
executed: 0,
|
|
248
|
+
successful: 0,
|
|
249
|
+
failed: 0,
|
|
250
|
+
isolated: isolated.length,
|
|
251
|
+
storageType: 'in-memory',
|
|
252
|
+
};
|
|
253
|
+
}
|
package/src/telegram-bot.mjs
CHANGED
|
@@ -23,27 +23,18 @@ const { loadLenvConfig } = await import('./lenv-reader.lib.mjs');
|
|
|
23
23
|
|
|
24
24
|
const dotenvxModule = await use('@dotenvx/dotenvx');
|
|
25
25
|
const dotenvx = dotenvxModule.default || dotenvxModule;
|
|
26
|
-
|
|
27
26
|
const getenvModule = await use('getenv');
|
|
28
|
-
// Node 24 CJS/ESM interop may return the whole module object instead of the function directly
|
|
29
27
|
const getenv = typeof getenvModule === 'function' ? getenvModule : getenvModule.default || getenvModule;
|
|
30
28
|
|
|
31
|
-
// Load .env configuration
|
|
32
|
-
// quiet: true suppresses info messages, ignore: ['MISSING_ENV_FILE'] suppresses error when .env doesn't exist
|
|
33
|
-
// This makes .env file optional (issue #1318)
|
|
29
|
+
// Load .env/.lenv configuration (issue #1318)
|
|
34
30
|
dotenvx.config({ quiet: true, ignore: ['MISSING_ENV_FILE'] });
|
|
35
|
-
|
|
36
|
-
// Load .lenv configuration (if exists)
|
|
37
|
-
// .lenv overrides .env
|
|
38
31
|
loadLenvConfig({ override: true, quiet: true });
|
|
39
32
|
|
|
40
33
|
const yargsModule = await use('yargs@17.7.2');
|
|
41
34
|
const yargs = yargsModule.default || yargsModule;
|
|
42
35
|
const helpersModuleBot = await use('yargs@17.7.2/helpers');
|
|
43
|
-
// Node 24 CJS/ESM interop may return the whole module object instead of named exports directly
|
|
44
36
|
const _helpersBot = helpersModuleBot.default || helpersModuleBot;
|
|
45
37
|
const hideBin = _helpersBot.hideBin || (argv => argv.slice(2));
|
|
46
|
-
// Import yargs configurations, GitHub utilities, and telegram helpers
|
|
47
38
|
const { createYargsConfig: createSolveYargsConfig, detectMalformedFlags } = await import('./solve.config.lib.mjs');
|
|
48
39
|
const { createYargsConfig: createHiveYargsConfig } = await import('./hive.config.lib.mjs');
|
|
49
40
|
const { parseGitHubUrl } = await import('./github.lib.mjs');
|
|
@@ -55,8 +46,8 @@ const { escapeMarkdown, escapeMarkdownV2, cleanNonPrintableChars, makeSpecialCha
|
|
|
55
46
|
const { getSolveQueue, createQueueExecuteCallback } = await import('./telegram-solve-queue.lib.mjs');
|
|
56
47
|
const { isChatStopped, getChatStopInfo, getStoppedChatRejectMessage, DEFAULT_STOP_REASON } = await import('./telegram-start-stop-command.lib.mjs');
|
|
57
48
|
const { isOldMessage: _isOldMessage, isGroupChat: _isGroupChat, isChatAuthorized: _isChatAuthorized, isForwardedOrReply: _isForwardedOrReply, extractCommandFromText, extractGitHubUrl: _extractGitHubUrl } = await import('./telegram-message-filters.lib.mjs');
|
|
58
|
-
// Import bot launcher with exponential backoff retry (issue #1240)
|
|
59
49
|
const { launchBotWithRetry } = await import('./telegram-bot-launcher.lib.mjs');
|
|
50
|
+
const { trackSession, startSessionMonitoring } = await import('./session-monitor.lib.mjs');
|
|
60
51
|
|
|
61
52
|
const config = yargs(hideBin(process.argv))
|
|
62
53
|
.usage('Usage: hive-telegram-bot [options]')
|
|
@@ -118,6 +109,7 @@ const config = yargs(hideBin(process.argv))
|
|
|
118
109
|
alias: 'v',
|
|
119
110
|
default: getenv('TELEGRAM_BOT_VERBOSE', 'false') === 'true',
|
|
120
111
|
})
|
|
112
|
+
.option('isolation', { type: 'string', description: 'Experimental: isolation backend (screen/tmux/docker)', default: getenv('TELEGRAM_ISOLATION', '') })
|
|
121
113
|
.help('h')
|
|
122
114
|
.alias('h', 'help')
|
|
123
115
|
.parserConfiguration({
|
|
@@ -127,65 +119,52 @@ const config = yargs(hideBin(process.argv))
|
|
|
127
119
|
.strict() // Enable strict mode to reject unknown options (consistent with solve.mjs and hive.mjs)
|
|
128
120
|
.parse();
|
|
129
121
|
|
|
130
|
-
//
|
|
131
|
-
// This allows users to pass environment variables via command line
|
|
132
|
-
//
|
|
133
|
-
// Complete configuration priority order (highest priority last):
|
|
134
|
-
// 1. .env (base configuration, loaded first - already loaded above at line 24)
|
|
135
|
-
// 2. .lenv (overrides .env - already loaded above at line 28)
|
|
136
|
-
// 3. yargs CLI options parsed above (lines 41-102) use getenv() for defaults,
|
|
137
|
-
// which reads from process.env populated by .env and .lenv
|
|
138
|
-
// 4. --configuration option (overrides process.env, affecting getenv() calls below)
|
|
139
|
-
// 5. Final resolution (lines 116+): CLI option values > environment variables
|
|
140
|
-
// Pattern: config.X || getenv('VAR') means CLI options have highest priority
|
|
122
|
+
// Configuration priority: CLI option > --configuration LINO > .lenv > .env
|
|
141
123
|
if (config.configuration) {
|
|
142
124
|
loadLenvConfig({ configuration: config.configuration, override: true, quiet: true });
|
|
143
125
|
}
|
|
144
126
|
|
|
145
|
-
// After loading configuration, resolve final values
|
|
146
|
-
// Priority: CLI option > environment variable
|
|
147
127
|
const BOT_TOKEN = config.token || getenv('TELEGRAM_BOT_TOKEN', '');
|
|
148
128
|
const VERBOSE = config.verbose || getenv('TELEGRAM_BOT_VERBOSE', 'false') === 'true';
|
|
149
|
-
|
|
150
129
|
if (!BOT_TOKEN) {
|
|
151
|
-
console.error('Error: TELEGRAM_BOT_TOKEN
|
|
152
|
-
console.error('Please set it with: export TELEGRAM_BOT_TOKEN=your_bot_token');
|
|
153
|
-
console.error('Or use: hive-telegram-bot --token your_bot_token');
|
|
130
|
+
console.error('Error: TELEGRAM_BOT_TOKEN not set. Use --token or TELEGRAM_BOT_TOKEN env var.');
|
|
154
131
|
process.exit(1);
|
|
155
132
|
}
|
|
156
133
|
|
|
157
|
-
//
|
|
158
|
-
// Priority: CLI option > environment variable (from .lenv or .env)
|
|
159
|
-
// NOTE: This section moved BEFORE loading telegraf for faster dry-run mode (issue #801)
|
|
134
|
+
// Resolve final config values (CLI option > environment variable)
|
|
160
135
|
const resolvedAllowedChats = config.allowedChats || getenv('TELEGRAM_ALLOWED_CHATS', '');
|
|
161
136
|
const allowedChats = resolvedAllowedChats ? lino.parseNumericIds(resolvedAllowedChats) : null;
|
|
162
137
|
|
|
163
138
|
// Parse allowed topics (chatId:topicId pairs in Links Notation)
|
|
164
139
|
const resolvedAllowedTopics = config.allowedTopics || getenv('TELEGRAM_ALLOWED_TOPICS', '');
|
|
165
140
|
const allowedTopics = resolvedAllowedTopics ? lino.parseLinks(resolvedAllowedTopics) : null;
|
|
166
|
-
|
|
167
|
-
// Parse override options
|
|
168
141
|
const resolvedSolveOverrides = config.solveOverrides || getenv('TELEGRAM_SOLVE_OVERRIDES', '');
|
|
169
142
|
const solveOverrides = resolvedSolveOverrides
|
|
170
143
|
? lino
|
|
171
144
|
.parseStringValues(resolvedSolveOverrides)
|
|
172
|
-
.map(
|
|
173
|
-
.filter(
|
|
145
|
+
.map(l => l.trim())
|
|
146
|
+
.filter(l => l)
|
|
174
147
|
: [];
|
|
175
|
-
|
|
176
148
|
const resolvedHiveOverrides = config.hiveOverrides || getenv('TELEGRAM_HIVE_OVERRIDES', '');
|
|
177
149
|
const hiveOverrides = resolvedHiveOverrides
|
|
178
150
|
? lino
|
|
179
151
|
.parseStringValues(resolvedHiveOverrides)
|
|
180
|
-
.map(
|
|
181
|
-
.filter(
|
|
152
|
+
.map(l => l.trim())
|
|
153
|
+
.filter(l => l)
|
|
182
154
|
: [];
|
|
183
|
-
|
|
184
|
-
// Command enable/disable flags
|
|
185
|
-
// Note: yargs automatically supports --no-solve and --no-hive for negation
|
|
186
|
-
// Priority: CLI option > environment variable
|
|
187
155
|
const solveEnabled = config.solve;
|
|
188
156
|
const hiveEnabled = config.hive;
|
|
157
|
+
// Isolation mode (experimental): uses `$` from start-command with specified backend
|
|
158
|
+
const ISOLATION_BACKEND = (config.isolation || getenv('TELEGRAM_ISOLATION', '')).trim().toLowerCase();
|
|
159
|
+
let isolationRunner = null;
|
|
160
|
+
if (ISOLATION_BACKEND) {
|
|
161
|
+
if (!['screen', 'tmux', 'docker'].includes(ISOLATION_BACKEND)) {
|
|
162
|
+
console.error(`Error: Invalid --isolation value '${ISOLATION_BACKEND}'. Must be: screen, tmux, or docker`);
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
console.log(`š Isolation mode enabled: ${ISOLATION_BACKEND} (experimental)`);
|
|
166
|
+
isolationRunner = await import('./isolation-runner.lib.mjs');
|
|
167
|
+
}
|
|
189
168
|
|
|
190
169
|
// Validate solve overrides early using solve's yargs config
|
|
191
170
|
// Only validate if solve command is enabled
|
|
@@ -607,29 +586,35 @@ async function safeReply(ctx, text, options = {}) {
|
|
|
607
586
|
}
|
|
608
587
|
}
|
|
609
588
|
|
|
610
|
-
// Execute a start-
|
|
589
|
+
// Execute a command via isolation mode ($ from start-command) or start-screen, then update message
|
|
611
590
|
async function executeAndUpdateMessage(ctx, startingMessage, commandName, args, infoBlock) {
|
|
612
|
-
const
|
|
613
|
-
const { chat, message_id } = startingMessage;
|
|
614
|
-
|
|
615
|
-
// Safely edit message - catch errors to prevent stuck "Starting..." messages (issue #1062)
|
|
591
|
+
const { chat, message_id: msgId } = startingMessage;
|
|
616
592
|
const safeEdit = async text => {
|
|
617
593
|
try {
|
|
618
|
-
await ctx.telegram.editMessageText(chat.id,
|
|
594
|
+
await ctx.telegram.editMessageText(chat.id, msgId, undefined, text, { parse_mode: 'Markdown' });
|
|
619
595
|
} catch (e) {
|
|
620
596
|
console.error(`[telegram-bot] Failed to update message for ${commandName}: ${e.message}`);
|
|
621
597
|
}
|
|
622
598
|
};
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
if (
|
|
627
|
-
const
|
|
628
|
-
|
|
629
|
-
await
|
|
599
|
+
let result,
|
|
600
|
+
session,
|
|
601
|
+
extraInfo = '';
|
|
602
|
+
if (ISOLATION_BACKEND && isolationRunner) {
|
|
603
|
+
const sid = isolationRunner.generateSessionId();
|
|
604
|
+
VERBOSE && console.log(`[VERBOSE] Using isolation (${ISOLATION_BACKEND}), session: ${sid}`);
|
|
605
|
+
result = await isolationRunner.executeWithIsolation(commandName, args, { backend: ISOLATION_BACKEND, sessionId: sid, verbose: VERBOSE });
|
|
606
|
+
session = sid;
|
|
607
|
+
extraInfo = `\nš Isolation: \`${ISOLATION_BACKEND}\``;
|
|
608
|
+
if (result.success) trackSession(sid, { chatId: ctx.chat.id, messageId: msgId, startTime: new Date(), url: args[0], command: commandName, isolationBackend: ISOLATION_BACKEND, sessionId: sid }, VERBOSE);
|
|
630
609
|
} else {
|
|
631
|
-
await
|
|
610
|
+
result = await executeStartScreen(commandName, args);
|
|
611
|
+
const match = result.success && (result.output.match(/session:\s*(\S+)/i) || result.output.match(/screen -R\s+(\S+)/));
|
|
612
|
+
session = match ? match[1] : 'unknown';
|
|
613
|
+
if (result.success && session !== 'unknown') trackSession(session, { chatId: ctx.chat.id, messageId: msgId, startTime: new Date(), url: args[0], command: commandName }, VERBOSE);
|
|
632
614
|
}
|
|
615
|
+
if (result.warning) return safeEdit(`ā ļø ${result.warning}`);
|
|
616
|
+
if (result.success) await safeEdit(`ā
${commandName.charAt(0).toUpperCase() + commandName.slice(1)} command started successfully!\n\nš Session: \`${session}\`${extraInfo}\n\n${infoBlock}\n\nš You will receive a notification when the session finishes.`);
|
|
617
|
+
else await safeEdit(`ā Error executing ${commandName} command:\n\n\`\`\`\n${result.error || result.output}\n\`\`\``);
|
|
633
618
|
}
|
|
634
619
|
|
|
635
620
|
bot.command('help', async ctx => {
|
|
@@ -707,6 +692,9 @@ bot.command('help', async ctx => {
|
|
|
707
692
|
message += '*/help* - Show this help message\n';
|
|
708
693
|
message += '*/stop* - Stop accepting new tasks (owner only)\n';
|
|
709
694
|
message += '*/start* - Resume accepting tasks (owner only)\n\n';
|
|
695
|
+
message += 'š *Session Notifications:* The bot monitors sessions and notifies when they complete.\n';
|
|
696
|
+
if (ISOLATION_BACKEND) message += `š *Isolation Mode:* \`${ISOLATION_BACKEND}\` (experimental)\n`;
|
|
697
|
+
message += '\n';
|
|
710
698
|
message += 'ā ļø *Note:* /solve, /hive, /solve\\_queue, /limits, /version, /accept\\_invites, /merge, /stop and /start commands only work in group chats.\n\n';
|
|
711
699
|
message += 'š§ *Common Options:*\n';
|
|
712
700
|
message += `⢠\`--model <model>\` or \`-m\` - ${buildModelOptionDescription()}\n`;
|
|
@@ -1043,7 +1031,17 @@ async function handleSolveCommand(ctx) {
|
|
|
1043
1031
|
if (check.reason) queueMessage += `\n\nā³ Waiting: ${escapeMarkdown(check.reason)}`;
|
|
1044
1032
|
const queuedMessage = await safeReply(ctx, queueMessage, { reply_to_message_id: ctx.message.message_id });
|
|
1045
1033
|
queueItem.messageInfo = { chatId: queuedMessage.chat.id, messageId: queuedMessage.message_id };
|
|
1046
|
-
if (!solveQueue.executeCallback)
|
|
1034
|
+
if (!solveQueue.executeCallback) {
|
|
1035
|
+
solveQueue.executeCallback =
|
|
1036
|
+
ISOLATION_BACKEND && isolationRunner
|
|
1037
|
+
? async item => {
|
|
1038
|
+
const sid = isolationRunner.generateSessionId();
|
|
1039
|
+
const r = await isolationRunner.executeWithIsolation('solve', item.args, { backend: ISOLATION_BACKEND, sessionId: sid, verbose: VERBOSE });
|
|
1040
|
+
if (r.success) trackSession(sid, { chatId: item.ctx?.chat?.id, messageId: item.messageInfo?.messageId, startTime: new Date(), url: item.url, command: 'solve', isolationBackend: ISOLATION_BACKEND, sessionId: sid }, VERBOSE);
|
|
1041
|
+
return { ...r, output: r.output || `session: ${sid}` };
|
|
1042
|
+
}
|
|
1043
|
+
: createQueueExecuteCallback(executeStartScreen, (session, info) => trackSession(session, info, VERBOSE));
|
|
1044
|
+
}
|
|
1047
1045
|
}
|
|
1048
1046
|
}
|
|
1049
1047
|
|
|
@@ -1457,6 +1455,9 @@ launchBotWithRetry(
|
|
|
1457
1455
|
|
|
1458
1456
|
console.log('[VERBOSE] Send a message to the bot to test message reception');
|
|
1459
1457
|
}
|
|
1458
|
+
|
|
1459
|
+
// Start session monitoring - check for completed sessions every 30 seconds
|
|
1460
|
+
startSessionMonitoring(bot, VERBOSE);
|
|
1460
1461
|
})
|
|
1461
1462
|
.catch(error => {
|
|
1462
1463
|
console.error('ā Failed to start bot:', error);
|
|
@@ -1351,14 +1351,44 @@ export function resetSolveQueue() {
|
|
|
1351
1351
|
/**
|
|
1352
1352
|
* Create an execute callback for the queue
|
|
1353
1353
|
* @param {Function} executeStartScreen - Function to execute start-screen command
|
|
1354
|
+
* @param {Function} [trackSessionFn] - Optional function to track session for completion notifications
|
|
1354
1355
|
* @returns {Function} Execute callback for queue items
|
|
1355
1356
|
*/
|
|
1356
|
-
export function createQueueExecuteCallback(executeStartScreen) {
|
|
1357
|
+
export function createQueueExecuteCallback(executeStartScreen, trackSessionFn) {
|
|
1357
1358
|
return async item => {
|
|
1358
|
-
|
|
1359
|
+
const result = await executeStartScreen('solve', item.args);
|
|
1360
|
+
if (trackSessionFn && result.success) {
|
|
1361
|
+
const match = result.output && (result.output.match(/session:\s*(\S+)/i) || result.output.match(/screen -R\s+(\S+)/));
|
|
1362
|
+
const session = match ? match[1] : null;
|
|
1363
|
+
if (session) {
|
|
1364
|
+
trackSessionFn(session, { chatId: item.ctx?.chat?.id, messageId: item.messageInfo?.messageId, startTime: new Date(), url: item.url, command: 'solve' });
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
return result;
|
|
1359
1368
|
};
|
|
1360
1369
|
}
|
|
1361
1370
|
|
|
1371
|
+
/**
|
|
1372
|
+
* Get count of running isolated sessions tracked via ExecutionStore
|
|
1373
|
+
* When isolation mode is enabled, this replaces pgrep-based process detection
|
|
1374
|
+
* for more reliable task counting.
|
|
1375
|
+
*
|
|
1376
|
+
* @param {boolean} verbose - Whether to log verbose output
|
|
1377
|
+
* @returns {Promise<{count: number, sessions: string[]}>}
|
|
1378
|
+
*/
|
|
1379
|
+
export async function getRunningIsolatedSessions(verbose = false) {
|
|
1380
|
+
try {
|
|
1381
|
+
const { getActiveSessionCount } = await import('./session-monitor.lib.mjs');
|
|
1382
|
+
const count = getActiveSessionCount(verbose);
|
|
1383
|
+
return { count, sessions: [] };
|
|
1384
|
+
} catch (error) {
|
|
1385
|
+
if (verbose) {
|
|
1386
|
+
console.error(`[VERBOSE] /solve_queue error getting isolated sessions:`, error.message);
|
|
1387
|
+
}
|
|
1388
|
+
return { count: 0, sessions: [] };
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1362
1392
|
export default {
|
|
1363
1393
|
SolveQueue,
|
|
1364
1394
|
SolveQueueItem,
|
|
@@ -1367,6 +1397,7 @@ export default {
|
|
|
1367
1397
|
getRunningProcesses,
|
|
1368
1398
|
getRunningClaudeProcesses,
|
|
1369
1399
|
getRunningAgentProcesses,
|
|
1400
|
+
getRunningIsolatedSessions,
|
|
1370
1401
|
createQueueExecuteCallback,
|
|
1371
1402
|
formatDuration,
|
|
1372
1403
|
QUEUE_CONFIG,
|