agent-orchestrator-mcp-server 0.8.10 → 0.8.11
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/README.md +19 -18
- package/package.json +1 -1
- package/shared/orchestrator-client/orchestrator-client.d.ts +9 -1
- package/shared/orchestrator-client/orchestrator-client.integration-mock.js +25 -0
- package/shared/orchestrator-client/orchestrator-client.js +8 -0
- package/shared/resources.js +1 -1
- package/shared/tools/action-session.d.ts +37 -9
- package/shared/tools/action-session.js +105 -7
- package/shared/tools.js +1 -1
- package/shared/types.d.ts +7 -0
package/README.md
CHANGED
|
@@ -21,24 +21,24 @@ MCP server for PulseMCP's agent-orchestrator: a Claude Code + MCP-powered agent-
|
|
|
21
21
|
|
|
22
22
|
### Tools
|
|
23
23
|
|
|
24
|
-
| Tool | Tool Group | Read/Write | Description
|
|
25
|
-
| -------------------------- | ------------- | ---------- |
|
|
26
|
-
| `quick_search_sessions` | sessions | read | Quick title-based search/list sessions with optional ID lookup, title query, and status filter
|
|
27
|
-
| `get_session` | sessions | read | Get detailed session info with optional logs, transcripts, and transcript format (text/json)
|
|
28
|
-
| `get_configs` | sessions | read | Fetch all static configuration (MCP servers, agent roots, goals)
|
|
29
|
-
| `get_transcript_archive` | sessions | read | Get download URL and metadata for the transcript archive zip file
|
|
30
|
-
| `start_session` | sessions | write | Create and start a new agent session
|
|
31
|
-
| `action_session` | sessions | write | Perform actions: follow_up, pause, restart, archive, unarchive, change_mcp_servers, change_model, fork, refresh, refresh_all, update_notes, update_title, toggle_favorite, bulk_archive |
|
|
32
|
-
| `manage_enqueued_messages` | sessions | write | Manage session message queue: list, get, create, update, delete, reorder, interrupt
|
|
33
|
-
| `manage_categories` | sessions | write | Manage dashboard categories that organize sessions: list, create, update, delete, reorder, set_session_category
|
|
34
|
-
| `respond_to_elicitation` | sessions | write | Accept or decline a pending elicitation by its request_id, with optional structured content on accept
|
|
35
|
-
| `get_notifications` | notifications | read | Get/list notifications and badge count
|
|
36
|
-
| `send_push_notification` | notifications | write | Send a push notification about a session needing human attention
|
|
37
|
-
| `action_notification` | notifications | write | Mark read, mark all read, dismiss, dismiss all read notifications
|
|
38
|
-
| `search_triggers` | triggers | read | Search/list automation triggers with optional channel info
|
|
39
|
-
| `action_trigger` | triggers | write | Create, update, delete, toggle automation triggers
|
|
40
|
-
| `get_system_health` | health | read | Get system health report and optional CLI status
|
|
41
|
-
| `action_health` | health | write | System maintenance: cleanup_processes, retry_sessions, archive_old, cli_refresh, cli_clear_cache
|
|
24
|
+
| Tool | Tool Group | Read/Write | Description |
|
|
25
|
+
| -------------------------- | ------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
26
|
+
| `quick_search_sessions` | sessions | read | Quick title-based search/list sessions with optional ID lookup, title query, and status filter |
|
|
27
|
+
| `get_session` | sessions | read | Get detailed session info with optional logs, transcripts, and transcript format (text/json) |
|
|
28
|
+
| `get_configs` | sessions | read | Fetch all static configuration (MCP servers, agent roots, goals) |
|
|
29
|
+
| `get_transcript_archive` | sessions | read | Get download URL and metadata for the transcript archive zip file |
|
|
30
|
+
| `start_session` | sessions | write | Create and start a new agent session |
|
|
31
|
+
| `action_session` | sessions | write | Perform actions: follow_up, pause, restart, archive, unarchive, change_mcp_servers, change_model, set_heartbeat, fork, refresh, refresh_all, update_notes, update_title, toggle_favorite, bulk_archive |
|
|
32
|
+
| `manage_enqueued_messages` | sessions | write | Manage session message queue: list, get, create, update, delete, reorder, interrupt |
|
|
33
|
+
| `manage_categories` | sessions | write | Manage dashboard categories that organize sessions: list, create, update, delete, reorder, set_session_category |
|
|
34
|
+
| `respond_to_elicitation` | sessions | write | Accept or decline a pending elicitation by its request_id, with optional structured content on accept |
|
|
35
|
+
| `get_notifications` | notifications | read | Get/list notifications and badge count |
|
|
36
|
+
| `send_push_notification` | notifications | write | Send a push notification about a session needing human attention |
|
|
37
|
+
| `action_notification` | notifications | write | Mark read, mark all read, dismiss, dismiss all read notifications |
|
|
38
|
+
| `search_triggers` | triggers | read | Search/list automation triggers with optional channel info |
|
|
39
|
+
| `action_trigger` | triggers | write | Create, update, delete, toggle automation triggers |
|
|
40
|
+
| `get_system_health` | health | read | Get system health report and optional CLI status |
|
|
41
|
+
| `action_health` | health | write | System maintenance: cleanup_processes, retry_sessions, archive_old, cli_refresh, cli_clear_cache |
|
|
42
42
|
|
|
43
43
|
### Resources
|
|
44
44
|
|
|
@@ -213,6 +213,7 @@ This MCP server provides tools for the following Agent Orchestrator REST API end
|
|
|
213
213
|
- `POST /api/v1/sessions/:id/refresh` - Refresh session status
|
|
214
214
|
- `POST /api/v1/sessions/refresh_all` - Refresh all active sessions
|
|
215
215
|
- `POST /api/v1/sessions/:id/toggle_favorite` - Toggle favorite
|
|
216
|
+
- `PATCH /api/v1/sessions/:id/heartbeat` - Toggle heartbeat and/or set interval (set_heartbeat)
|
|
216
217
|
- `POST /api/v1/sessions/bulk_archive` - Bulk archive sessions
|
|
217
218
|
|
|
218
219
|
### Transcript Archive
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* A client for interacting with the Agent Orchestrator REST API.
|
|
5
5
|
*/
|
|
6
|
-
import type { Session, Log, SubagentTranscript, SessionsResponse, SearchSessionsResponse, SessionActionResponse, LogsResponse, SubagentTranscriptsResponse, CreateSessionRequest, UpdateSessionRequest, CreateLogRequest, UpdateLogRequest, CreateSubagentTranscriptRequest, UpdateSubagentTranscriptRequest, SessionStatus, LogLevel, SubagentStatus, MCPServerInfo, AgentRootInfo, ConfigsResponse, SendPushNotificationResponse, EnqueuedMessage, EnqueuedMessagesResponse, EnqueuedMessageInterruptResponse, EnqueuedMessageStatus, Category, CategoriesResponse, CreateCategoryRequest, UpdateCategoryRequest, SetSessionCategoryResponse, Trigger, TriggerType, TriggerStatus, TriggersResponse, TriggerResponse, TriggerChannelsResponse, TriggerConditionAttributes, TriggerConditionType, CreateTriggerRequest, UpdateTriggerRequest, Notification, NotificationsResponse, NotificationBadgeResponse, NotificationMarkAllReadResponse, NotificationDismissAllReadResponse, HealthReport, HealthActionResponse, CliStatusResponse, CliActionResponse, ForkSessionResponse, RefreshSessionResponse, RefreshAllSessionsResponse, BulkArchiveResponse, TranscriptResponse, TranscriptArchiveStatusResponse, ElicitationActionType, ElicitationResponse } from '../types.js';
|
|
6
|
+
import type { Session, Log, SubagentTranscript, SessionsResponse, SearchSessionsResponse, SessionActionResponse, SetHeartbeatResponse, LogsResponse, SubagentTranscriptsResponse, CreateSessionRequest, UpdateSessionRequest, CreateLogRequest, UpdateLogRequest, CreateSubagentTranscriptRequest, UpdateSubagentTranscriptRequest, SessionStatus, LogLevel, SubagentStatus, MCPServerInfo, AgentRootInfo, ConfigsResponse, SendPushNotificationResponse, EnqueuedMessage, EnqueuedMessagesResponse, EnqueuedMessageInterruptResponse, EnqueuedMessageStatus, Category, CategoriesResponse, CreateCategoryRequest, UpdateCategoryRequest, SetSessionCategoryResponse, Trigger, TriggerType, TriggerStatus, TriggersResponse, TriggerResponse, TriggerChannelsResponse, TriggerConditionAttributes, TriggerConditionType, CreateTriggerRequest, UpdateTriggerRequest, Notification, NotificationsResponse, NotificationBadgeResponse, NotificationMarkAllReadResponse, NotificationDismissAllReadResponse, HealthReport, HealthActionResponse, CliStatusResponse, CliActionResponse, ForkSessionResponse, RefreshSessionResponse, RefreshAllSessionsResponse, BulkArchiveResponse, TranscriptResponse, TranscriptArchiveStatusResponse, ElicitationActionType, ElicitationResponse } from '../types.js';
|
|
7
7
|
/** Raw agent root shape as returned by the Rails API */
|
|
8
8
|
export interface RawAgentRoot {
|
|
9
9
|
name: string;
|
|
@@ -67,6 +67,10 @@ export interface IAgentOrchestratorClient {
|
|
|
67
67
|
restartSession(id: string | number): Promise<SessionActionResponse>;
|
|
68
68
|
changeMcpServers(id: string | number, mcp_servers: string[]): Promise<Session>;
|
|
69
69
|
changeModel(id: string | number, model: string): Promise<Session>;
|
|
70
|
+
setHeartbeat(id: string | number, options: {
|
|
71
|
+
enabled?: boolean;
|
|
72
|
+
interval_seconds?: number;
|
|
73
|
+
}): Promise<SetHeartbeatResponse>;
|
|
70
74
|
listLogs(sessionId: string | number, options?: {
|
|
71
75
|
level?: LogLevel;
|
|
72
76
|
page?: number;
|
|
@@ -201,6 +205,10 @@ export declare class AgentOrchestratorClient implements IAgentOrchestratorClient
|
|
|
201
205
|
restartSession(id: string | number): Promise<SessionActionResponse>;
|
|
202
206
|
changeMcpServers(id: string | number, mcp_servers: string[]): Promise<Session>;
|
|
203
207
|
changeModel(id: string | number, model: string): Promise<Session>;
|
|
208
|
+
setHeartbeat(id: string | number, options: {
|
|
209
|
+
enabled?: boolean;
|
|
210
|
+
interval_seconds?: number;
|
|
211
|
+
}): Promise<SetHeartbeatResponse>;
|
|
204
212
|
listLogs(sessionId: string | number, options?: {
|
|
205
213
|
level?: LogLevel;
|
|
206
214
|
page?: number;
|
|
@@ -309,6 +309,31 @@ export function createIntegrationMockOrchestratorClient(initialMockData) {
|
|
|
309
309
|
session.updated_at = new Date().toISOString();
|
|
310
310
|
return session;
|
|
311
311
|
},
|
|
312
|
+
async setHeartbeat(id, options) {
|
|
313
|
+
const session = mockData.sessions?.find((s) => s.id === Number(id) || s.slug === String(id));
|
|
314
|
+
if (!session) {
|
|
315
|
+
throw new Error(`API Error (404): Session not found`);
|
|
316
|
+
}
|
|
317
|
+
if (options.enabled === undefined && options.interval_seconds === undefined) {
|
|
318
|
+
throw new Error(`API Error (422): At least one of "enabled" or "interval_seconds" must be provided`);
|
|
319
|
+
}
|
|
320
|
+
if (options.interval_seconds !== undefined &&
|
|
321
|
+
(options.interval_seconds < 30 || options.interval_seconds > 86400)) {
|
|
322
|
+
throw new Error(`API Error (422): interval_seconds must be between 30 and 86400`);
|
|
323
|
+
}
|
|
324
|
+
if (options.enabled !== undefined) {
|
|
325
|
+
session.heartbeat_enabled = options.enabled;
|
|
326
|
+
}
|
|
327
|
+
if (options.interval_seconds !== undefined) {
|
|
328
|
+
session.heartbeat_interval_seconds = options.interval_seconds;
|
|
329
|
+
}
|
|
330
|
+
session.updated_at = new Date().toISOString();
|
|
331
|
+
return {
|
|
332
|
+
session,
|
|
333
|
+
heartbeat_enabled: session.heartbeat_enabled ?? false,
|
|
334
|
+
heartbeat_interval_seconds: session.heartbeat_interval_seconds ?? 300,
|
|
335
|
+
};
|
|
336
|
+
},
|
|
312
337
|
async listLogs(sessionId, options) {
|
|
313
338
|
let logs = (mockData.logs || []).filter((l) => l.session_id === Number(sessionId));
|
|
314
339
|
if (options?.level) {
|
|
@@ -246,6 +246,14 @@ export class AgentOrchestratorClient {
|
|
|
246
246
|
});
|
|
247
247
|
return response.session;
|
|
248
248
|
}
|
|
249
|
+
async setHeartbeat(id, options) {
|
|
250
|
+
const body = {};
|
|
251
|
+
if (options.enabled !== undefined)
|
|
252
|
+
body.enabled = options.enabled;
|
|
253
|
+
if (options.interval_seconds !== undefined)
|
|
254
|
+
body.interval_seconds = options.interval_seconds;
|
|
255
|
+
return this.request('PATCH', `/sessions/${id}/heartbeat`, body);
|
|
256
|
+
}
|
|
249
257
|
// Logs
|
|
250
258
|
async listLogs(sessionId, options) {
|
|
251
259
|
return this.request('GET', `/sessions/${sessionId}/logs`, undefined, options);
|
package/shared/resources.js
CHANGED
|
@@ -74,7 +74,7 @@ export function createRegisterResources(clientFactory) {
|
|
|
74
74
|
triggers_readonly: 'Trigger tools (read only): search_triggers',
|
|
75
75
|
health: 'All health tools (read + write): health report, CLI status, maintenance actions',
|
|
76
76
|
health_readonly: 'Health tools (read only): get_system_health',
|
|
77
|
-
self_session: 'Self-management tools for auto-injected servers: get_session, get_configs (read), action_session (filtered: update_notes, update_title, archive), send_push_notification',
|
|
77
|
+
self_session: 'Self-management tools for auto-injected servers: get_session, get_configs (read), action_session (filtered: update_notes, update_title, set_heartbeat, archive), send_push_notification',
|
|
78
78
|
},
|
|
79
79
|
};
|
|
80
80
|
return {
|
|
@@ -3,34 +3,40 @@ import { z } from 'zod';
|
|
|
3
3
|
import type { IAgentOrchestratorClient } from '../orchestrator-client/orchestrator-client.js';
|
|
4
4
|
export declare const ActionSessionSchema: z.ZodObject<{
|
|
5
5
|
session_id: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
|
|
6
|
-
action: z.ZodEnum<["follow_up", "pause", "restart", "archive", "unarchive", "change_mcp_servers", "change_model", "fork", "refresh", "refresh_all", "update_notes", "update_title", "toggle_favorite", "bulk_archive"]>;
|
|
6
|
+
action: z.ZodEnum<["follow_up", "pause", "restart", "archive", "unarchive", "change_mcp_servers", "change_model", "set_heartbeat", "fork", "refresh", "refresh_all", "update_notes", "update_title", "toggle_favorite", "bulk_archive"]>;
|
|
7
7
|
prompt: z.ZodOptional<z.ZodString>;
|
|
8
8
|
force_immediate: z.ZodOptional<z.ZodBoolean>;
|
|
9
9
|
mcp_servers: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
10
10
|
model: z.ZodOptional<z.ZodString>;
|
|
11
|
+
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
12
|
+
interval_seconds: z.ZodOptional<z.ZodNumber>;
|
|
11
13
|
message_index: z.ZodOptional<z.ZodNumber>;
|
|
12
14
|
session_notes: z.ZodOptional<z.ZodString>;
|
|
13
15
|
session_ids: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
|
|
14
16
|
title: z.ZodOptional<z.ZodString>;
|
|
15
17
|
}, "strip", z.ZodTypeAny, {
|
|
16
|
-
action: "follow_up" | "pause" | "restart" | "archive" | "unarchive" | "change_mcp_servers" | "change_model" | "fork" | "refresh" | "refresh_all" | "update_notes" | "update_title" | "toggle_favorite" | "bulk_archive";
|
|
18
|
+
action: "follow_up" | "pause" | "restart" | "archive" | "unarchive" | "change_mcp_servers" | "change_model" | "set_heartbeat" | "fork" | "refresh" | "refresh_all" | "update_notes" | "update_title" | "toggle_favorite" | "bulk_archive";
|
|
19
|
+
enabled?: boolean | undefined;
|
|
17
20
|
prompt?: string | undefined;
|
|
18
21
|
title?: string | undefined;
|
|
19
22
|
mcp_servers?: string[] | undefined;
|
|
20
23
|
session_id?: string | number | undefined;
|
|
21
24
|
force_immediate?: boolean | undefined;
|
|
22
25
|
model?: string | undefined;
|
|
26
|
+
interval_seconds?: number | undefined;
|
|
23
27
|
message_index?: number | undefined;
|
|
24
28
|
session_notes?: string | undefined;
|
|
25
29
|
session_ids?: number[] | undefined;
|
|
26
30
|
}, {
|
|
27
|
-
action: "follow_up" | "pause" | "restart" | "archive" | "unarchive" | "change_mcp_servers" | "change_model" | "fork" | "refresh" | "refresh_all" | "update_notes" | "update_title" | "toggle_favorite" | "bulk_archive";
|
|
31
|
+
action: "follow_up" | "pause" | "restart" | "archive" | "unarchive" | "change_mcp_servers" | "change_model" | "set_heartbeat" | "fork" | "refresh" | "refresh_all" | "update_notes" | "update_title" | "toggle_favorite" | "bulk_archive";
|
|
32
|
+
enabled?: boolean | undefined;
|
|
28
33
|
prompt?: string | undefined;
|
|
29
34
|
title?: string | undefined;
|
|
30
35
|
mcp_servers?: string[] | undefined;
|
|
31
36
|
session_id?: string | number | undefined;
|
|
32
37
|
force_immediate?: boolean | undefined;
|
|
33
38
|
model?: string | undefined;
|
|
39
|
+
interval_seconds?: number | undefined;
|
|
34
40
|
message_index?: number | undefined;
|
|
35
41
|
session_notes?: string | undefined;
|
|
36
42
|
session_ids?: number[] | undefined;
|
|
@@ -49,8 +55,8 @@ export declare function actionSessionTool(_server: Server, clientFactory: () =>
|
|
|
49
55
|
};
|
|
50
56
|
action: {
|
|
51
57
|
type: string;
|
|
52
|
-
enum: readonly ["follow_up", "pause", "restart", "archive", "unarchive", "change_mcp_servers", "change_model", "fork", "refresh", "refresh_all", "update_notes", "update_title", "toggle_favorite", "bulk_archive"];
|
|
53
|
-
description: "Action to perform: \"follow_up\", \"pause\", \"restart\", \"archive\", \"unarchive\", \"change_mcp_servers\", \"change_model\", \"fork\", \"refresh\", \"refresh_all\", \"update_notes\", \"update_title\", \"toggle_favorite\", \"bulk_archive\"";
|
|
58
|
+
enum: readonly ["follow_up", "pause", "restart", "archive", "unarchive", "change_mcp_servers", "change_model", "set_heartbeat", "fork", "refresh", "refresh_all", "update_notes", "update_title", "toggle_favorite", "bulk_archive"];
|
|
59
|
+
description: "Action to perform: \"follow_up\", \"pause\", \"restart\", \"archive\", \"unarchive\", \"change_mcp_servers\", \"change_model\", \"set_heartbeat\", \"fork\", \"refresh\", \"refresh_all\", \"update_notes\", \"update_title\", \"toggle_favorite\", \"bulk_archive\"";
|
|
54
60
|
};
|
|
55
61
|
prompt: {
|
|
56
62
|
type: string;
|
|
@@ -71,6 +77,14 @@ export declare function actionSessionTool(_server: Server, clientFactory: () =>
|
|
|
71
77
|
type: string;
|
|
72
78
|
description: "Required for \"change_model\" action. The model identifier to use (e.g., \"opus\", \"sonnet\").";
|
|
73
79
|
};
|
|
80
|
+
enabled: {
|
|
81
|
+
type: string;
|
|
82
|
+
description: "Optional for \"set_heartbeat\" action. When true, enables the session heartbeat; when false, disables it. Omit to leave the enabled state unchanged (at least one of \"enabled\" or \"interval_seconds\" must be provided).";
|
|
83
|
+
};
|
|
84
|
+
interval_seconds: {
|
|
85
|
+
type: string;
|
|
86
|
+
description: "Optional for \"set_heartbeat\" action. Heartbeat cadence in seconds (30–86400). Omit to leave the interval unchanged (at least one of \"enabled\" or \"interval_seconds\" must be provided).";
|
|
87
|
+
};
|
|
74
88
|
message_index: {
|
|
75
89
|
type: string;
|
|
76
90
|
description: "Required for \"fork\" action. The transcript message index to fork from.";
|
|
@@ -109,18 +123,24 @@ export declare function actionSessionTool(_server: Server, clientFactory: () =>
|
|
|
109
123
|
};
|
|
110
124
|
export declare const SelfSessionActionSessionSchema: z.ZodObject<{
|
|
111
125
|
session_id: z.ZodUnion<[z.ZodString, z.ZodNumber]>;
|
|
112
|
-
action: z.ZodEnum<["update_notes", "update_title", "archive"]>;
|
|
126
|
+
action: z.ZodEnum<["update_notes", "update_title", "set_heartbeat", "archive"]>;
|
|
113
127
|
session_notes: z.ZodOptional<z.ZodString>;
|
|
114
128
|
title: z.ZodOptional<z.ZodString>;
|
|
129
|
+
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
130
|
+
interval_seconds: z.ZodOptional<z.ZodNumber>;
|
|
115
131
|
}, "strip", z.ZodTypeAny, {
|
|
116
132
|
session_id: string | number;
|
|
117
|
-
action: "archive" | "update_notes" | "update_title";
|
|
133
|
+
action: "archive" | "set_heartbeat" | "update_notes" | "update_title";
|
|
134
|
+
enabled?: boolean | undefined;
|
|
118
135
|
title?: string | undefined;
|
|
136
|
+
interval_seconds?: number | undefined;
|
|
119
137
|
session_notes?: string | undefined;
|
|
120
138
|
}, {
|
|
121
139
|
session_id: string | number;
|
|
122
|
-
action: "archive" | "update_notes" | "update_title";
|
|
140
|
+
action: "archive" | "set_heartbeat" | "update_notes" | "update_title";
|
|
141
|
+
enabled?: boolean | undefined;
|
|
123
142
|
title?: string | undefined;
|
|
143
|
+
interval_seconds?: number | undefined;
|
|
124
144
|
session_notes?: string | undefined;
|
|
125
145
|
}>;
|
|
126
146
|
export declare function selfSessionActionSessionTool(_server: Server, clientFactory: () => IAgentOrchestratorClient): {
|
|
@@ -137,7 +157,7 @@ export declare function selfSessionActionSessionTool(_server: Server, clientFact
|
|
|
137
157
|
};
|
|
138
158
|
action: {
|
|
139
159
|
type: string;
|
|
140
|
-
enum: readonly ["update_notes", "update_title", "archive"];
|
|
160
|
+
enum: readonly ["update_notes", "update_title", "set_heartbeat", "archive"];
|
|
141
161
|
description: string;
|
|
142
162
|
};
|
|
143
163
|
session_notes: {
|
|
@@ -148,6 +168,14 @@ export declare function selfSessionActionSessionTool(_server: Server, clientFact
|
|
|
148
168
|
type: string;
|
|
149
169
|
description: "Required for \"update_title\" action. The new title for the session.";
|
|
150
170
|
};
|
|
171
|
+
enabled: {
|
|
172
|
+
type: string;
|
|
173
|
+
description: "Optional for \"set_heartbeat\" action. When true, enables the session heartbeat; when false, disables it. Omit to leave the enabled state unchanged (at least one of \"enabled\" or \"interval_seconds\" must be provided).";
|
|
174
|
+
};
|
|
175
|
+
interval_seconds: {
|
|
176
|
+
type: string;
|
|
177
|
+
description: "Optional for \"set_heartbeat\" action. Heartbeat cadence in seconds (30–86400). Omit to leave the interval unchanged (at least one of \"enabled\" or \"interval_seconds\" must be provided).";
|
|
178
|
+
};
|
|
151
179
|
};
|
|
152
180
|
required: string[];
|
|
153
181
|
};
|
|
@@ -1,12 +1,44 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { parseAllowedAgentRoots } from '../allowed-agent-roots.js';
|
|
3
|
+
// Heartbeat interval bounds enforced by the Rails API (PATCH /sessions/:id/heartbeat).
|
|
4
|
+
const HEARTBEAT_MIN_INTERVAL_SECONDS = 30;
|
|
5
|
+
const HEARTBEAT_MAX_INTERVAL_SECONDS = 86400;
|
|
6
|
+
/**
|
|
7
|
+
* Validates set_heartbeat arguments. Returns an error message string if the
|
|
8
|
+
* arguments are invalid, or null if they are valid. Mirrors the Rails API's
|
|
9
|
+
* 422 conditions so callers get a clear error before the request is sent.
|
|
10
|
+
*/
|
|
11
|
+
function validateHeartbeatArgs(enabled, interval_seconds) {
|
|
12
|
+
if (enabled === undefined && interval_seconds === undefined) {
|
|
13
|
+
return 'Error: The "set_heartbeat" action requires at least one of "enabled" or "interval_seconds".';
|
|
14
|
+
}
|
|
15
|
+
if (interval_seconds !== undefined &&
|
|
16
|
+
(interval_seconds < HEARTBEAT_MIN_INTERVAL_SECONDS ||
|
|
17
|
+
interval_seconds > HEARTBEAT_MAX_INTERVAL_SECONDS)) {
|
|
18
|
+
return `Error: "interval_seconds" must be between ${HEARTBEAT_MIN_INTERVAL_SECONDS} and ${HEARTBEAT_MAX_INTERVAL_SECONDS}.`;
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
/** Formats a set_heartbeat response into the standard markdown result block. */
|
|
23
|
+
function formatHeartbeatResult(response) {
|
|
24
|
+
return [
|
|
25
|
+
`## Heartbeat Updated`,
|
|
26
|
+
'',
|
|
27
|
+
`- **Session ID:** ${response.session.id}`,
|
|
28
|
+
`- **Title:** ${response.session.title}`,
|
|
29
|
+
`- **Heartbeat Enabled:** ${response.heartbeat_enabled ? 'Yes' : 'No'}`,
|
|
30
|
+
`- **Interval:** ${response.heartbeat_interval_seconds} seconds`,
|
|
31
|
+
].join('\n');
|
|
32
|
+
}
|
|
3
33
|
const PARAM_DESCRIPTIONS = {
|
|
4
34
|
session_id: 'Session ID (numeric) or slug (string). Required for most actions. Not required for "refresh_all" and "bulk_archive".',
|
|
5
|
-
action: 'Action to perform: "follow_up", "pause", "restart", "archive", "unarchive", "change_mcp_servers", "change_model", "fork", "refresh", "refresh_all", "update_notes", "update_title", "toggle_favorite", "bulk_archive"',
|
|
35
|
+
action: 'Action to perform: "follow_up", "pause", "restart", "archive", "unarchive", "change_mcp_servers", "change_model", "set_heartbeat", "fork", "refresh", "refresh_all", "update_notes", "update_title", "toggle_favorite", "bulk_archive"',
|
|
6
36
|
prompt: 'Required for "follow_up" action. The prompt to send to the agent. Not used for other actions.',
|
|
7
37
|
force_immediate: 'Optional for "follow_up" action. When true, interrupts a running session to deliver the prompt immediately instead of queuing it. Not used for other actions.',
|
|
8
38
|
mcp_servers: 'Required for "change_mcp_servers" action. Array of MCP server names to set for the session.',
|
|
9
39
|
model: 'Required for "change_model" action. The model identifier to use (e.g., "opus", "sonnet").',
|
|
40
|
+
enabled: 'Optional for "set_heartbeat" action. When true, enables the session heartbeat; when false, disables it. Omit to leave the enabled state unchanged (at least one of "enabled" or "interval_seconds" must be provided).',
|
|
41
|
+
interval_seconds: 'Optional for "set_heartbeat" action. Heartbeat cadence in seconds (30–86400). Omit to leave the interval unchanged (at least one of "enabled" or "interval_seconds" must be provided).',
|
|
10
42
|
message_index: 'Required for "fork" action. The transcript message index to fork from.',
|
|
11
43
|
session_notes: 'Required for "update_notes" action. The notes text to set on the session.',
|
|
12
44
|
session_ids: 'Required for "bulk_archive" action. Array of session IDs to archive.',
|
|
@@ -20,6 +52,7 @@ const ACTION_ENUM = [
|
|
|
20
52
|
'unarchive',
|
|
21
53
|
'change_mcp_servers',
|
|
22
54
|
'change_model',
|
|
55
|
+
'set_heartbeat',
|
|
23
56
|
'fork',
|
|
24
57
|
'refresh',
|
|
25
58
|
'refresh_all',
|
|
@@ -35,6 +68,8 @@ export const ActionSessionSchema = z.object({
|
|
|
35
68
|
force_immediate: z.boolean().optional().describe(PARAM_DESCRIPTIONS.force_immediate),
|
|
36
69
|
mcp_servers: z.array(z.string()).optional().describe(PARAM_DESCRIPTIONS.mcp_servers),
|
|
37
70
|
model: z.string().optional().describe(PARAM_DESCRIPTIONS.model),
|
|
71
|
+
enabled: z.boolean().optional().describe(PARAM_DESCRIPTIONS.enabled),
|
|
72
|
+
interval_seconds: z.number().int().optional().describe(PARAM_DESCRIPTIONS.interval_seconds),
|
|
38
73
|
message_index: z.number().optional().describe(PARAM_DESCRIPTIONS.message_index),
|
|
39
74
|
session_notes: z.string().optional().describe(PARAM_DESCRIPTIONS.session_notes),
|
|
40
75
|
session_ids: z.array(z.number()).optional().describe(PARAM_DESCRIPTIONS.session_ids),
|
|
@@ -50,6 +85,7 @@ const TOOL_DESCRIPTION = `Perform an action on an agent session.
|
|
|
50
85
|
- **unarchive**: Restore an archived session to idle "needs_input" status
|
|
51
86
|
- **change_mcp_servers**: Update the MCP servers for a session (requires "mcp_servers" parameter)
|
|
52
87
|
- **change_model**: Update the model for a session (requires "model" parameter, e.g., "opus", "sonnet")
|
|
88
|
+
- **set_heartbeat**: Toggle a session's heartbeat and/or set its interval (provide "enabled" and/or "interval_seconds"). When enabled and the session sits in needs_input, a recurring nudge prompts it to keep working toward its goal; set "enabled" to false to stop the nudges.
|
|
53
89
|
- **fork**: Fork a session from a specific transcript message (requires "message_index")
|
|
54
90
|
- **refresh**: Refresh a single session's status from the execution provider
|
|
55
91
|
- **refresh_all**: Refresh all active sessions (no session_id needed)
|
|
@@ -97,6 +133,14 @@ export function actionSessionTool(_server, clientFactory) {
|
|
|
97
133
|
type: 'string',
|
|
98
134
|
description: PARAM_DESCRIPTIONS.model,
|
|
99
135
|
},
|
|
136
|
+
enabled: {
|
|
137
|
+
type: 'boolean',
|
|
138
|
+
description: PARAM_DESCRIPTIONS.enabled,
|
|
139
|
+
},
|
|
140
|
+
interval_seconds: {
|
|
141
|
+
type: 'number',
|
|
142
|
+
description: PARAM_DESCRIPTIONS.interval_seconds,
|
|
143
|
+
},
|
|
100
144
|
message_index: {
|
|
101
145
|
type: 'number',
|
|
102
146
|
description: PARAM_DESCRIPTIONS.message_index,
|
|
@@ -121,7 +165,7 @@ export function actionSessionTool(_server, clientFactory) {
|
|
|
121
165
|
try {
|
|
122
166
|
const validatedArgs = ActionSessionSchema.parse(args);
|
|
123
167
|
const client = clientFactory();
|
|
124
|
-
const { session_id, action, prompt, force_immediate, mcp_servers, model, message_index, session_notes, session_ids, title, } = validatedArgs;
|
|
168
|
+
const { session_id, action, prompt, force_immediate, mcp_servers, model, enabled, interval_seconds, message_index, session_notes, session_ids, title, } = validatedArgs;
|
|
125
169
|
// Actions that require session_id
|
|
126
170
|
const requiresSessionId = [
|
|
127
171
|
'follow_up',
|
|
@@ -131,6 +175,7 @@ export function actionSessionTool(_server, clientFactory) {
|
|
|
131
175
|
'unarchive',
|
|
132
176
|
'change_mcp_servers',
|
|
133
177
|
'change_model',
|
|
178
|
+
'set_heartbeat',
|
|
134
179
|
'fork',
|
|
135
180
|
'refresh',
|
|
136
181
|
'update_notes',
|
|
@@ -184,6 +229,16 @@ export function actionSessionTool(_server, clientFactory) {
|
|
|
184
229
|
isError: true,
|
|
185
230
|
};
|
|
186
231
|
}
|
|
232
|
+
// Validate set_heartbeat arguments
|
|
233
|
+
if (action === 'set_heartbeat') {
|
|
234
|
+
const heartbeatError = validateHeartbeatArgs(enabled, interval_seconds);
|
|
235
|
+
if (heartbeatError) {
|
|
236
|
+
return {
|
|
237
|
+
content: [{ type: 'text', text: heartbeatError }],
|
|
238
|
+
isError: true,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
}
|
|
187
242
|
// Block change_mcp_servers when ALLOWED_AGENT_ROOTS is active
|
|
188
243
|
if (action === 'change_mcp_servers' && parseAllowedAgentRoots() !== null) {
|
|
189
244
|
return {
|
|
@@ -375,6 +430,14 @@ export function actionSessionTool(_server, clientFactory) {
|
|
|
375
430
|
result = lines.join('\n');
|
|
376
431
|
break;
|
|
377
432
|
}
|
|
433
|
+
case 'set_heartbeat': {
|
|
434
|
+
const response = await client.setHeartbeat(session_id, {
|
|
435
|
+
enabled,
|
|
436
|
+
interval_seconds,
|
|
437
|
+
});
|
|
438
|
+
result = formatHeartbeatResult(response);
|
|
439
|
+
break;
|
|
440
|
+
}
|
|
378
441
|
case 'fork': {
|
|
379
442
|
const response = await client.forkSession(session_id, message_index);
|
|
380
443
|
const lines = [
|
|
@@ -503,27 +566,36 @@ export function actionSessionTool(_server, clientFactory) {
|
|
|
503
566
|
// SELF-SESSION VARIANT
|
|
504
567
|
// =============================================================================
|
|
505
568
|
// Restricted version of action_session for the self_session composite group.
|
|
506
|
-
// Only allows self-management actions: update_notes, update_title, archive.
|
|
569
|
+
// Only allows self-management actions: update_notes, update_title, set_heartbeat, archive.
|
|
507
570
|
// =============================================================================
|
|
508
|
-
const SELF_SESSION_ACTION_ENUM = [
|
|
571
|
+
const SELF_SESSION_ACTION_ENUM = [
|
|
572
|
+
'update_notes',
|
|
573
|
+
'update_title',
|
|
574
|
+
'set_heartbeat',
|
|
575
|
+
'archive',
|
|
576
|
+
];
|
|
509
577
|
export const SelfSessionActionSessionSchema = z.object({
|
|
510
578
|
session_id: z.union([z.string(), z.number()]).describe(PARAM_DESCRIPTIONS.session_id),
|
|
511
579
|
action: z
|
|
512
580
|
.enum(SELF_SESSION_ACTION_ENUM)
|
|
513
|
-
.describe('Action to perform: "update_notes", "update_title", "archive"'),
|
|
581
|
+
.describe('Action to perform: "update_notes", "update_title", "set_heartbeat", "archive"'),
|
|
514
582
|
session_notes: z.string().optional().describe(PARAM_DESCRIPTIONS.session_notes),
|
|
515
583
|
title: z.string().optional().describe(PARAM_DESCRIPTIONS.title),
|
|
584
|
+
enabled: z.boolean().optional().describe(PARAM_DESCRIPTIONS.enabled),
|
|
585
|
+
interval_seconds: z.number().int().optional().describe(PARAM_DESCRIPTIONS.interval_seconds),
|
|
516
586
|
});
|
|
517
587
|
const SELF_SESSION_TOOL_DESCRIPTION = `Perform a self-management action on a session.
|
|
518
588
|
|
|
519
589
|
**Actions (limited to self-management):**
|
|
520
590
|
- **update_notes**: Update the notes on a session (requires "session_notes")
|
|
521
591
|
- **update_title**: Update the title of a session (requires "title")
|
|
592
|
+
- **set_heartbeat**: Toggle this session's own heartbeat and/or set its interval (provide "enabled" and/or "interval_seconds"). When the heartbeat is enabled and this session sits in needs_input, a recurring nudge prompts it to keep working toward its goal. If you are genuinely blocked or done, set "enabled" to false to stop the nudges.
|
|
522
593
|
- **archive**: Archive a session (marks as completed)
|
|
523
594
|
|
|
524
595
|
**Use cases:**
|
|
525
596
|
- Update session notes to record progress or context
|
|
526
597
|
- Set a meaningful session title
|
|
598
|
+
- Turn off this session's heartbeat when blocked or finished (set_heartbeat with enabled=false)
|
|
527
599
|
- Archive the session when work is complete
|
|
528
600
|
|
|
529
601
|
**Archive guidelines:**
|
|
@@ -544,7 +616,7 @@ export function selfSessionActionSessionTool(_server, clientFactory) {
|
|
|
544
616
|
action: {
|
|
545
617
|
type: 'string',
|
|
546
618
|
enum: SELF_SESSION_ACTION_ENUM,
|
|
547
|
-
description: 'Action to perform: "update_notes", "update_title", "archive"',
|
|
619
|
+
description: 'Action to perform: "update_notes", "update_title", "set_heartbeat", "archive"',
|
|
548
620
|
},
|
|
549
621
|
session_notes: {
|
|
550
622
|
type: 'string',
|
|
@@ -554,6 +626,14 @@ export function selfSessionActionSessionTool(_server, clientFactory) {
|
|
|
554
626
|
type: 'string',
|
|
555
627
|
description: PARAM_DESCRIPTIONS.title,
|
|
556
628
|
},
|
|
629
|
+
enabled: {
|
|
630
|
+
type: 'boolean',
|
|
631
|
+
description: PARAM_DESCRIPTIONS.enabled,
|
|
632
|
+
},
|
|
633
|
+
interval_seconds: {
|
|
634
|
+
type: 'number',
|
|
635
|
+
description: PARAM_DESCRIPTIONS.interval_seconds,
|
|
636
|
+
},
|
|
557
637
|
},
|
|
558
638
|
required: ['session_id', 'action'],
|
|
559
639
|
},
|
|
@@ -561,7 +641,7 @@ export function selfSessionActionSessionTool(_server, clientFactory) {
|
|
|
561
641
|
try {
|
|
562
642
|
const validatedArgs = SelfSessionActionSessionSchema.parse(args);
|
|
563
643
|
const client = clientFactory();
|
|
564
|
-
const { session_id, action, session_notes, title } = validatedArgs;
|
|
644
|
+
const { session_id, action, session_notes, title, enabled, interval_seconds } = validatedArgs;
|
|
565
645
|
// Validate update_notes requires session_notes
|
|
566
646
|
if (action === 'update_notes' && session_notes === undefined) {
|
|
567
647
|
return {
|
|
@@ -586,6 +666,16 @@ export function selfSessionActionSessionTool(_server, clientFactory) {
|
|
|
586
666
|
isError: true,
|
|
587
667
|
};
|
|
588
668
|
}
|
|
669
|
+
// Validate set_heartbeat arguments
|
|
670
|
+
if (action === 'set_heartbeat') {
|
|
671
|
+
const heartbeatError = validateHeartbeatArgs(enabled, interval_seconds);
|
|
672
|
+
if (heartbeatError) {
|
|
673
|
+
return {
|
|
674
|
+
content: [{ type: 'text', text: heartbeatError }],
|
|
675
|
+
isError: true,
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
}
|
|
589
679
|
let result;
|
|
590
680
|
switch (action) {
|
|
591
681
|
case 'archive': {
|
|
@@ -601,6 +691,14 @@ export function selfSessionActionSessionTool(_server, clientFactory) {
|
|
|
601
691
|
result = lines.join('\n');
|
|
602
692
|
break;
|
|
603
693
|
}
|
|
694
|
+
case 'set_heartbeat': {
|
|
695
|
+
const response = await client.setHeartbeat(session_id, {
|
|
696
|
+
enabled,
|
|
697
|
+
interval_seconds,
|
|
698
|
+
});
|
|
699
|
+
result = formatHeartbeatResult(response);
|
|
700
|
+
break;
|
|
701
|
+
}
|
|
604
702
|
case 'update_notes': {
|
|
605
703
|
const session = await client.updateSessionNotes(session_id, session_notes);
|
|
606
704
|
const lines = [
|
package/shared/tools.js
CHANGED
|
@@ -69,7 +69,7 @@ export function parseEnabledToolGroups(enabledGroupsParam) {
|
|
|
69
69
|
* - get_configs: Fetch all static configuration (sessions, read; self_session)
|
|
70
70
|
* - get_transcript_archive: Get transcript archive download URL and metadata (sessions, read)
|
|
71
71
|
* - start_session: Create a new session (sessions, write)
|
|
72
|
-
* - action_session: Perform session actions (sessions, write; self_session: filtered to update_notes, update_title, archive)
|
|
72
|
+
* - action_session: Perform session actions (sessions, write; self_session: filtered to update_notes, update_title, set_heartbeat, archive)
|
|
73
73
|
* - manage_enqueued_messages: Manage session message queue (sessions, write)
|
|
74
74
|
* - manage_categories: Manage dashboard categories and session assignment (sessions, write)
|
|
75
75
|
* - respond_to_elicitation: Accept or decline a pending elicitation by request_id (sessions, write)
|
package/shared/types.d.ts
CHANGED
|
@@ -34,6 +34,8 @@ export interface Session {
|
|
|
34
34
|
archived_at: string | null;
|
|
35
35
|
category_id?: number | null;
|
|
36
36
|
category?: CategorySummary | null;
|
|
37
|
+
heartbeat_enabled?: boolean;
|
|
38
|
+
heartbeat_interval_seconds?: number;
|
|
37
39
|
created_at: string;
|
|
38
40
|
updated_at: string;
|
|
39
41
|
transcript?: string;
|
|
@@ -82,6 +84,11 @@ export interface SessionActionResponse {
|
|
|
82
84
|
session: Session;
|
|
83
85
|
message?: string;
|
|
84
86
|
}
|
|
87
|
+
export interface SetHeartbeatResponse {
|
|
88
|
+
session: Session;
|
|
89
|
+
heartbeat_enabled: boolean;
|
|
90
|
+
heartbeat_interval_seconds: number;
|
|
91
|
+
}
|
|
85
92
|
export interface LogsResponse {
|
|
86
93
|
logs: Log[];
|
|
87
94
|
pagination: Pagination;
|