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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-orchestrator-mcp-server",
3
- "version": "0.8.10",
3
+ "version": "0.8.11",
4
4
  "description": "Local implementation of agent-orchestrator MCP server",
5
5
  "main": "build/index.js",
6
6
  "type": "module",
@@ -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);
@@ -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 = ['update_notes', 'update_title', 'archive'];
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;