minionsai 0.1.13 → 0.1.14
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/dist/server/client/dist/assets/{highlighted-body-TPN3WLV5-K6mK4CnF.js → highlighted-body-TPN3WLV5-BGAwsMbg.js} +1 -1
- package/dist/server/client/dist/assets/index-CAVFyzK8.css +1 -0
- package/dist/server/client/dist/assets/index-DdbCvYan.js +776 -0
- package/dist/server/client/dist/index.html +2 -2
- package/dist/server/server/adapters/hermes-worker.d.ts +1 -0
- package/dist/server/server/adapters/hermes-worker.js +14 -4
- package/dist/server/server/adapters/types.d.ts +2 -0
- package/dist/server/server/adapters/worker-protocol.d.ts +9 -0
- package/dist/server/server/index.js +6 -2
- package/dist/server/server/live-chat.d.ts +1 -1
- package/dist/server/server/live-chat.js +1 -1
- package/dist/server/server/paths.d.ts +1 -0
- package/dist/server/server/paths.js +4 -0
- package/dist/server/server/prompts/task-agent.d.ts +1 -1
- package/dist/server/server/prompts/task-agent.js +2 -2
- package/dist/server/server/routes/chat.js +39 -3
- package/dist/server/server/routes/skills.d.ts +1 -0
- package/dist/server/server/routes/skills.js +815 -7
- package/dist/server/server/workers/hermes_worker.py +58 -0
- package/dist/server/shared/types.d.ts +39 -1
- package/package.json +4 -2
- package/dist/server/client/dist/assets/index-BP1qiwyD.css +0 -1
- package/dist/server/client/dist/assets/index-o62nNllV.js +0 -751
- package/dist/server/server/skills/catalog.d.ts +0 -21
- package/dist/server/server/skills/catalog.js +0 -156
- package/skills/lead-generation/SKILL.md +0 -41
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
<script>
|
|
9
9
|
(function(){var t=localStorage.getItem('theme');var d=t==='dark'||(!t||t==='system')&&window.matchMedia('(prefers-color-scheme: dark)').matches;if(d)document.documentElement.classList.add('dark')})();
|
|
10
10
|
</script>
|
|
11
|
-
<script type="module" crossorigin src="/assets/index-
|
|
12
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
11
|
+
<script type="module" crossorigin src="/assets/index-DdbCvYan.js"></script>
|
|
12
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CAVFyzK8.css">
|
|
13
13
|
</head>
|
|
14
14
|
<body class="bg-gray-50 text-gray-900 dark:bg-gray-950 dark:text-gray-100">
|
|
15
15
|
<div id="root"></div>
|
|
@@ -9,6 +9,7 @@ export declare class HermesWorkerAdapter implements AgentAdapter {
|
|
|
9
9
|
sessionId: string;
|
|
10
10
|
}>;
|
|
11
11
|
chatStream(sessionId: string, message: string, options?: AgentRunOptions): AsyncIterable<StreamEvent>;
|
|
12
|
+
interruptChat(sessionId: string, reason?: string): Promise<boolean>;
|
|
12
13
|
healthCheck(): Promise<boolean>;
|
|
13
14
|
getMessages(sessionId: string, taskId: string): Promise<TaskMessage[]>;
|
|
14
15
|
getSessionMetadata(sessionId: string): Promise<SessionMetadata | null>;
|
|
@@ -6,6 +6,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
6
6
|
import { randomUUID } from 'node:crypto';
|
|
7
7
|
import { expandHomePrefix, resolveHermesHome, resolveMinionsWorkspaceDir } from '../paths.js';
|
|
8
8
|
const WORKER_READY_TIMEOUT_MS = 10_000;
|
|
9
|
+
const WORKER_INTERRUPT_TIMEOUT_MS = 10_000;
|
|
9
10
|
function resolveAgentDirFromHermesCli() {
|
|
10
11
|
try {
|
|
11
12
|
const hermesBin = execFileSync('which', ['hermes'], { encoding: 'utf8' }).trim();
|
|
@@ -191,13 +192,13 @@ class HermesWorkerClient {
|
|
|
191
192
|
child.kill(signal);
|
|
192
193
|
});
|
|
193
194
|
}
|
|
194
|
-
async request(input) {
|
|
195
|
+
async request(input, timeoutMs) {
|
|
195
196
|
await this.start();
|
|
196
197
|
const id = randomUUID();
|
|
197
198
|
const request = typeof input === 'string'
|
|
198
199
|
? { id, type: input }
|
|
199
200
|
: { ...input, id };
|
|
200
|
-
return await this.sendRequest(request);
|
|
201
|
+
return await this.sendRequest(request, timeoutMs);
|
|
201
202
|
}
|
|
202
203
|
async *stream(request) {
|
|
203
204
|
await this.start();
|
|
@@ -243,7 +244,7 @@ class HermesWorkerClient {
|
|
|
243
244
|
if (timeoutMs) {
|
|
244
245
|
timeout = setTimeout(() => {
|
|
245
246
|
this.pending.delete(request.id);
|
|
246
|
-
reject(new Error(`Hermes worker did not
|
|
247
|
+
reject(new Error(`Hermes worker did not respond within ${timeoutMs}ms`));
|
|
247
248
|
}, timeoutMs);
|
|
248
249
|
}
|
|
249
250
|
try {
|
|
@@ -398,13 +399,22 @@ export class HermesWorkerAdapter {
|
|
|
398
399
|
yield { type: 'error', error: formatWorkerError(event.error), code: workerErrorCode(event.error) };
|
|
399
400
|
break;
|
|
400
401
|
case 'done':
|
|
401
|
-
yield { type: 'done', sessionId: event.sessionId ?? sessionId, context: event.context };
|
|
402
|
+
yield { type: 'done', sessionId: event.sessionId ?? sessionId, context: event.context, interrupted: event.interrupted };
|
|
402
403
|
break;
|
|
403
404
|
case 'result':
|
|
404
405
|
break;
|
|
405
406
|
}
|
|
406
407
|
}
|
|
407
408
|
}
|
|
409
|
+
async interruptChat(sessionId, reason) {
|
|
410
|
+
const result = await this.client.request({
|
|
411
|
+
type: 'chat.interrupt',
|
|
412
|
+
sessionId,
|
|
413
|
+
taskId: sessionId,
|
|
414
|
+
reason,
|
|
415
|
+
}, WORKER_INTERRUPT_TIMEOUT_MS);
|
|
416
|
+
return result.interrupted;
|
|
417
|
+
}
|
|
408
418
|
async healthCheck() {
|
|
409
419
|
try {
|
|
410
420
|
await this.client.start();
|
|
@@ -19,6 +19,7 @@ export interface StreamEvent {
|
|
|
19
19
|
duration?: number;
|
|
20
20
|
label?: string;
|
|
21
21
|
context?: ContextUsage | null;
|
|
22
|
+
interrupted?: boolean;
|
|
22
23
|
}
|
|
23
24
|
export interface AgentAdapter {
|
|
24
25
|
chat(sessionId: string, message: string, options?: AgentRunOptions): Promise<{
|
|
@@ -26,6 +27,7 @@ export interface AgentAdapter {
|
|
|
26
27
|
sessionId: string;
|
|
27
28
|
}>;
|
|
28
29
|
chatStream(sessionId: string, message: string, options?: AgentRunOptions): AsyncIterable<StreamEvent>;
|
|
30
|
+
interruptChat(sessionId: string, reason?: string): Promise<boolean>;
|
|
29
31
|
healthCheck(): Promise<boolean>;
|
|
30
32
|
getMessages(sessionId: string, taskId: string): Promise<TaskMessage[]>;
|
|
31
33
|
getSessionMetadata(sessionId: string): Promise<SessionMetadata | null>;
|
|
@@ -87,6 +87,12 @@ export type WorkerRequest = {
|
|
|
87
87
|
type: 'goal.evaluate';
|
|
88
88
|
sessionId: string;
|
|
89
89
|
responseText: string;
|
|
90
|
+
} | {
|
|
91
|
+
id: string;
|
|
92
|
+
type: 'chat.interrupt';
|
|
93
|
+
taskId?: string;
|
|
94
|
+
sessionId?: string;
|
|
95
|
+
reason?: string;
|
|
90
96
|
} | {
|
|
91
97
|
id: string;
|
|
92
98
|
type: 'chat';
|
|
@@ -132,6 +138,8 @@ export type WorkerResult = {
|
|
|
132
138
|
goal: GoalStateSnapshot | null;
|
|
133
139
|
} | {
|
|
134
140
|
cleared: boolean;
|
|
141
|
+
} | {
|
|
142
|
+
interrupted: boolean;
|
|
135
143
|
} | GoalDecision | {
|
|
136
144
|
title: string;
|
|
137
145
|
} | {
|
|
@@ -165,6 +173,7 @@ export type WorkerEvent = {
|
|
|
165
173
|
type: 'done';
|
|
166
174
|
sessionId?: string;
|
|
167
175
|
context?: ContextUsage | null;
|
|
176
|
+
interrupted?: boolean;
|
|
168
177
|
} | {
|
|
169
178
|
id: string;
|
|
170
179
|
type: 'error';
|
|
@@ -4,13 +4,17 @@ import { once } from 'node:events';
|
|
|
4
4
|
import { createServer } from 'node:http';
|
|
5
5
|
import app, { adapter } from './app.js';
|
|
6
6
|
import { mountFrontend } from './frontend.js';
|
|
7
|
-
import {
|
|
7
|
+
import { ensureHermesExternalSkillsDir } from './routes/skills.js';
|
|
8
8
|
const PORT = parseInt(process.env.PORT || '6969', 10);
|
|
9
9
|
const httpServer = createServer(app);
|
|
10
10
|
let closeFrontend = () => { };
|
|
11
11
|
let shuttingDown = false;
|
|
12
12
|
async function main() {
|
|
13
|
-
|
|
13
|
+
// Re-register the skills dir with Hermes every boot, so installed skills stay
|
|
14
|
+
// visible even if config.yaml was regenerated or skills were restored on disk.
|
|
15
|
+
await ensureHermesExternalSkillsDir().catch((error) => {
|
|
16
|
+
console.error('Failed to register skills directory with Hermes — installed skills may not load:', error instanceof Error ? error.message : error);
|
|
17
|
+
});
|
|
14
18
|
closeFrontend = await mountFrontend(app, httpServer);
|
|
15
19
|
try {
|
|
16
20
|
await adapter.start();
|
|
@@ -22,7 +22,7 @@ export declare function getRun(taskId: string): LiveChatRun | undefined;
|
|
|
22
22
|
export declare function getRunContext(taskId: string): LiveChatRun['context'] | undefined;
|
|
23
23
|
export declare function getRunStatus(taskId: string): TaskRunState | undefined;
|
|
24
24
|
export declare function getRunStatuses(): TaskRunState[];
|
|
25
|
-
export declare function updateRunStatus(taskId: string, status: Extract<LiveChatRunStatus, 'done' | 'error'>, options?: {
|
|
25
|
+
export declare function updateRunStatus(taskId: string, status: Extract<LiveChatRunStatus, 'done' | 'error' | 'stopped'>, options?: {
|
|
26
26
|
context?: LiveChatRun['context'];
|
|
27
27
|
error?: string;
|
|
28
28
|
}): TaskRunState | undefined;
|
|
@@ -201,7 +201,7 @@ export function applyEvent(taskId, event) {
|
|
|
201
201
|
}
|
|
202
202
|
else if (event.type === 'done') {
|
|
203
203
|
if (run.status !== 'error')
|
|
204
|
-
run.status = 'done';
|
|
204
|
+
run.status = event.interrupted ? 'stopped' : 'done';
|
|
205
205
|
if (event.sessionId)
|
|
206
206
|
run.sessionId = event.sessionId;
|
|
207
207
|
if (event.context !== undefined) {
|
|
@@ -4,5 +4,6 @@ export declare function resolveMinionsHome(): string;
|
|
|
4
4
|
export declare function resolveMinionsDataDir(): string;
|
|
5
5
|
export declare function resolveMinionsLogsDir(): string;
|
|
6
6
|
export declare function resolveMinionsWorkspaceDir(): string;
|
|
7
|
+
export declare function resolveMinionsSkillsDir(): string;
|
|
7
8
|
export declare function resolveMinionsDbPath(): string;
|
|
8
9
|
export declare function ensureMinionsStateDirs(): void;
|
|
@@ -28,6 +28,9 @@ export function resolveMinionsLogsDir() {
|
|
|
28
28
|
export function resolveMinionsWorkspaceDir() {
|
|
29
29
|
return join(resolveMinionsHome(), 'workspace');
|
|
30
30
|
}
|
|
31
|
+
export function resolveMinionsSkillsDir() {
|
|
32
|
+
return join(resolveMinionsHome(), 'skills');
|
|
33
|
+
}
|
|
31
34
|
export function resolveMinionsDbPath() {
|
|
32
35
|
const configured = process.env.DB_PATH?.trim();
|
|
33
36
|
if (configured)
|
|
@@ -39,5 +42,6 @@ export function ensureMinionsStateDirs() {
|
|
|
39
42
|
mkdirSync(resolveMinionsDataDir(), { recursive: true });
|
|
40
43
|
mkdirSync(resolveMinionsLogsDir(), { recursive: true });
|
|
41
44
|
mkdirSync(resolveMinionsWorkspaceDir(), { recursive: true });
|
|
45
|
+
mkdirSync(resolveMinionsSkillsDir(), { recursive: true });
|
|
42
46
|
mkdirSync(dirname(dbPath), { recursive: true });
|
|
43
47
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const TASK_AGENT_SYSTEM_PROMPT = "<task_agent>\n <role>\n You are an autonomous task agent. A user has given you a task to accomplish.\n </role>\n\n <responsibilities>\n <responsibility name=\"understand\">\n Read the task carefully. Identify anything unclear, ambiguous, or underspecified.\n </responsibility>\n <responsibility name=\"clarify\">\n Before doing work, make sure you fully understand what the user wants. Ask focused clarifying questions about scope, constraints, expected outcomes, edge cases, or anything else you are uncertain about. Do not assume when the uncertainty matters. The user is available to answer. Keep asking until you are confident you understand the task correctly.\n </responsibility>\n <responsibility name=\"execute\">\n Once you and the user are aligned, choose the best execution strategy. Do the work yourself in this session if it is straightforward. Create a child session if you need a dedicated sub-agent for complex sub-work. Set up a cron job when the work is recurring, periodic, scheduled, or better handled as durable batches over time. You have full autonomy to use the tools and approach that best accomplish the task.\n </responsibility>\n </responsibilities>\n\n <guidelines>\n <guideline>Understand first, act second. Do not start executing until you are confident you know what the user wants.</guideline>\n <guideline>When clarifying, ask focused questions rather than a long wall of questions. A natural back-and-forth conversation is ideal.</guideline>\n <guideline>When the user asks for a cron job, schedule, scheduled task, recurring task, monitor, daily/weekly task, or similar repeated work, default to a Hermes cron job using the available cronjob tooling. Do not use Linux cron, systemd timers, or host OS schedulers unless the user explicitly asks for them.</guideline>\n <guideline>For lead generation, prospecting, and large list
|
|
1
|
+
export declare const TASK_AGENT_SYSTEM_PROMPT = "<task_agent>\n <role>\n You are an autonomous task agent. A user has given you a task to accomplish.\n </role>\n\n <responsibilities>\n <responsibility name=\"understand\">\n Read the task carefully. Identify anything unclear, ambiguous, or underspecified.\n </responsibility>\n <responsibility name=\"clarify\">\n Before doing work, make sure you fully understand what the user wants. Ask focused clarifying questions about scope, constraints, expected outcomes, edge cases, or anything else you are uncertain about. Do not assume when the uncertainty matters. The user is available to answer. Keep asking until you are confident you understand the task correctly.\n </responsibility>\n <responsibility name=\"execute\">\n Once you and the user are aligned, choose the best execution strategy. Do the work yourself in this session if it is straightforward. Create a child session if you need a dedicated sub-agent for complex sub-work. Set up a cron job when the work is recurring, periodic, scheduled, or better handled as durable batches over time. You have full autonomy to use the tools and approach that best accomplish the task.\n </responsibility>\n </responsibilities>\n\n <guidelines>\n <guideline>Understand first, act second. Do not start executing until you are confident you know what the user wants.</guideline>\n <guideline>When clarifying, ask focused questions rather than a long wall of questions. A natural back-and-forth conversation is ideal.</guideline>\n <guideline>When the user asks for a cron job, schedule, scheduled task, recurring task, monitor, daily/weekly task, or similar repeated work, default to a Hermes cron job using the available cronjob tooling. Do not use Linux cron, systemd timers, or host OS schedulers unless the user explicitly asks for them.</guideline>\n <guideline>For lead generation, prospecting, data collection, and other large list-processing work, start with a small sample or validation run. Choose useful columns, write results to a local CSV file when tabular output is valuable, and continue from the same file instead of starting over.</guideline>\n <guideline>If list-processing work is larger than a small one-off result, prefer a Hermes cron job with a self-contained prompt, sensible batch size, CSV/checkpoint path, and schedule so the work can continue durably over time.</guideline>\n <guideline>Keep the user informed of meaningful progress in your responses.</guideline>\n </guidelines>\n</task_agent>";
|
|
@@ -19,8 +19,8 @@ export const TASK_AGENT_SYSTEM_PROMPT = `<task_agent>
|
|
|
19
19
|
<guideline>Understand first, act second. Do not start executing until you are confident you know what the user wants.</guideline>
|
|
20
20
|
<guideline>When clarifying, ask focused questions rather than a long wall of questions. A natural back-and-forth conversation is ideal.</guideline>
|
|
21
21
|
<guideline>When the user asks for a cron job, schedule, scheduled task, recurring task, monitor, daily/weekly task, or similar repeated work, default to a Hermes cron job using the available cronjob tooling. Do not use Linux cron, systemd timers, or host OS schedulers unless the user explicitly asks for them.</guideline>
|
|
22
|
-
<guideline>For lead generation, prospecting, and large list
|
|
22
|
+
<guideline>For lead generation, prospecting, data collection, and other large list-processing work, start with a small sample or validation run. Choose useful columns, write results to a local CSV file when tabular output is valuable, and continue from the same file instead of starting over.</guideline>
|
|
23
|
+
<guideline>If list-processing work is larger than a small one-off result, prefer a Hermes cron job with a self-contained prompt, sensible batch size, CSV/checkpoint path, and schedule so the work can continue durably over time.</guideline>
|
|
23
24
|
<guideline>Keep the user informed of meaningful progress in your responses.</guideline>
|
|
24
|
-
<guideline>You have project-specific skills under the "minions" category in your skills index. Before executing a task, check if any minions skill is relevant and load it — these encode proven workflows tailored to this system.</guideline>
|
|
25
25
|
</guidelines>
|
|
26
26
|
</task_agent>`;
|
|
@@ -16,6 +16,9 @@ function hasNoSession(task) {
|
|
|
16
16
|
function isTaskRunActive(status) {
|
|
17
17
|
return status?.status === 'streaming' || status?.status === 'compacting';
|
|
18
18
|
}
|
|
19
|
+
function isInterruptibleRun(status) {
|
|
20
|
+
return status?.status === 'streaming' && (status.kind === 'chat' || status.kind === 'goal');
|
|
21
|
+
}
|
|
19
22
|
function completeTaskRun(taskId, runId, status, ttlMs, options) {
|
|
20
23
|
const updated = updateRunStatus(taskId, status, options);
|
|
21
24
|
if (updated) {
|
|
@@ -96,6 +99,7 @@ async function streamChatTurn(runTask, sessionId, content, options) {
|
|
|
96
99
|
let doneContext;
|
|
97
100
|
let responseText = '';
|
|
98
101
|
let hadError = false;
|
|
102
|
+
let interrupted = false;
|
|
99
103
|
try {
|
|
100
104
|
const stream = adapter.chatStream(sessionId, content, {
|
|
101
105
|
systemMessage: TASK_AGENT_SYSTEM_PROMPT,
|
|
@@ -109,6 +113,8 @@ async function streamChatTurn(runTask, sessionId, content, options) {
|
|
|
109
113
|
if (event.type === 'done') {
|
|
110
114
|
sawDone = true;
|
|
111
115
|
doneContext = event.context;
|
|
116
|
+
if (event.interrupted)
|
|
117
|
+
interrupted = true;
|
|
112
118
|
if (!options.completeOnDone) {
|
|
113
119
|
updateRunContext(runTask.id, event.context, event.sessionId);
|
|
114
120
|
continue;
|
|
@@ -142,7 +148,7 @@ async function streamChatTurn(runTask, sessionId, content, options) {
|
|
|
142
148
|
broadcastLive(runTask.id, event);
|
|
143
149
|
}
|
|
144
150
|
}
|
|
145
|
-
return { responseText, sawDone, context: doneContext, hadError };
|
|
151
|
+
return { responseText, sawDone, context: doneContext, hadError, interrupted };
|
|
146
152
|
}
|
|
147
153
|
async function consumeChatRun(runTask, sessionId, content, runId) {
|
|
148
154
|
const result = await streamChatTurn(runTask, sessionId, content, { completeOnDone: true });
|
|
@@ -156,13 +162,13 @@ async function consumeChatRun(runTask, sessionId, content, runId) {
|
|
|
156
162
|
async function consumeGoalRun(runTask, sessionId, initialContent, runId) {
|
|
157
163
|
let finalContext;
|
|
158
164
|
let hadError = false;
|
|
165
|
+
let wasInterrupted = false;
|
|
159
166
|
let turnContent = initialContent;
|
|
160
167
|
let turnCount = 0;
|
|
161
168
|
try {
|
|
162
169
|
while (turnContent) {
|
|
163
170
|
if (++turnCount > MINIONS_GOAL_MAX_TURNS) {
|
|
164
171
|
appendSystemMessage(runTask.id, 'Goal turn limit reached');
|
|
165
|
-
broadcastRunSnapshot(runTask.id);
|
|
166
172
|
break;
|
|
167
173
|
}
|
|
168
174
|
appendUserMessage(runTask.id, turnContent);
|
|
@@ -178,6 +184,10 @@ async function consumeGoalRun(runTask, sessionId, initialContent, runId) {
|
|
|
178
184
|
hadError = true;
|
|
179
185
|
break;
|
|
180
186
|
}
|
|
187
|
+
if (turn.interrupted) {
|
|
188
|
+
wasInterrupted = true;
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
181
191
|
const decision = await adapter.evaluateGoal(sessionId, turn.responseText);
|
|
182
192
|
let shouldBroadcastSnapshot = false;
|
|
183
193
|
if (decision.state) {
|
|
@@ -205,8 +215,13 @@ async function consumeGoalRun(runTask, sessionId, initialContent, runId) {
|
|
|
205
215
|
}
|
|
206
216
|
finally {
|
|
207
217
|
if (!hadError && getRunStatus(runTask.id)?.status === 'streaming') {
|
|
208
|
-
updateRunStatus(runTask.id, 'done', { context: finalContext ?? null });
|
|
218
|
+
updateRunStatus(runTask.id, wasInterrupted ? 'stopped' : 'done', { context: finalContext ?? null });
|
|
209
219
|
}
|
|
220
|
+
// Goal-turn `done` events are swallowed (completeOnDone=false), so the live
|
|
221
|
+
// channel never sees the terminal status — push a final snapshot for it. The
|
|
222
|
+
// error path already delivered a terminal `error` event, so skip it there.
|
|
223
|
+
if (!hadError)
|
|
224
|
+
broadcastRunSnapshot(runTask.id);
|
|
210
225
|
settleRun(runTask.id, runId, finalContext ?? null);
|
|
211
226
|
}
|
|
212
227
|
}
|
|
@@ -273,6 +288,27 @@ chatRouter.post('/:id/messages', async (req, res) => {
|
|
|
273
288
|
void consumeChatRun(runTask, sessionId, content, snapshot.runId);
|
|
274
289
|
res.status(202).json({ runId: snapshot.runId });
|
|
275
290
|
});
|
|
291
|
+
chatRouter.post('/:id/interrupt', async (req, res) => {
|
|
292
|
+
const task = getTask(req.params.id);
|
|
293
|
+
if (!task)
|
|
294
|
+
return res.status(404).json({ error: 'Task not found' });
|
|
295
|
+
if (!isInterruptibleRun(getRunStatus(task.id))) {
|
|
296
|
+
return res.status(409).json({ error: 'This task has no active message to stop' });
|
|
297
|
+
}
|
|
298
|
+
const reason = typeof req.body?.reason === 'string' && req.body.reason.trim()
|
|
299
|
+
? req.body.reason.trim()
|
|
300
|
+
: undefined;
|
|
301
|
+
try {
|
|
302
|
+
const interrupted = await adapter.interruptChat(task.id, reason);
|
|
303
|
+
if (!interrupted) {
|
|
304
|
+
return res.status(409).json({ error: 'Hermes had no active agent to stop for this task' });
|
|
305
|
+
}
|
|
306
|
+
res.json({ interrupted: true });
|
|
307
|
+
}
|
|
308
|
+
catch (error) {
|
|
309
|
+
res.status(503).json({ error: toErrorMessage(error, 'Could not stop Hermes run') });
|
|
310
|
+
}
|
|
311
|
+
});
|
|
276
312
|
chatRouter.post('/:id/compact', async (req, res) => {
|
|
277
313
|
const task = getTask(req.params.id);
|
|
278
314
|
if (!task)
|