agent-orchestrator-mcp-server 0.7.15 → 0.7.17

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-orchestrator-mcp-server",
3
- "version": "0.7.15",
3
+ "version": "0.7.17",
4
4
  "description": "Local implementation of agent-orchestrator MCP server",
5
5
  "main": "build/index.js",
6
6
  "type": "module",
@@ -222,7 +222,7 @@ export class AgentOrchestratorClient {
222
222
  return this.request('POST', `/sessions/${id}/restart`);
223
223
  }
224
224
  async changeMcpServers(id, mcp_servers) {
225
- const response = await this.request('PATCH', `/sessions/${id}`, {
225
+ const response = await this.request('PATCH', `/sessions/${id}/mcp_servers`, {
226
226
  mcp_servers,
227
227
  });
228
228
  return response.session;
@@ -328,6 +328,30 @@ export function actionSessionTool(_server, clientFactory) {
328
328
  }
329
329
  case 'change_mcp_servers': {
330
330
  const session = await client.changeMcpServers(session_id, mcp_servers);
331
+ const requested = [...mcp_servers].sort();
332
+ const actual = [...(session.mcp_servers ?? [])].sort();
333
+ const diverged = requested.length !== actual.length || requested.some((name, i) => name !== actual[i]);
334
+ if (diverged) {
335
+ const formatList = (list) => (list.length > 0 ? list.join(', ') : '(none)');
336
+ return {
337
+ content: [
338
+ {
339
+ type: 'text',
340
+ text: [
341
+ `## MCP Servers Update FAILED — server list unchanged`,
342
+ '',
343
+ `The API accepted the request but the session's MCP servers do not match what was requested. This usually indicates the request hit a backend endpoint that silently dropped the parameter.`,
344
+ '',
345
+ `- **Session ID:** ${session.id}`,
346
+ `- **Title:** ${session.title}`,
347
+ `- **Requested:** ${formatList(mcp_servers)}`,
348
+ `- **Actual:** ${formatList(session.mcp_servers ?? [])}`,
349
+ ].join('\n'),
350
+ },
351
+ ],
352
+ isError: true,
353
+ };
354
+ }
331
355
  const lines = [
332
356
  `## MCP Servers Updated`,
333
357
  '',
@@ -4,18 +4,18 @@ import type { IAgentOrchestratorClient } from '../orchestrator-client/orchestrat
4
4
  export declare const WakeMeUpWhenSessionChangesStateSchema: z.ZodObject<{
5
5
  session_id: z.ZodUnion<[z.ZodString, z.ZodNumber]>;
6
6
  watched_session_id: z.ZodNumber;
7
- event_name: z.ZodEnum<["session_needs_input", "session_failed"]>;
7
+ event_name: z.ZodEnum<["session_needs_input", "session_failed", "session_archived"]>;
8
8
  prompt: z.ZodString;
9
9
  }, "strip", z.ZodTypeAny, {
10
10
  prompt: string;
11
11
  session_id: string | number;
12
12
  watched_session_id: number;
13
- event_name: "session_needs_input" | "session_failed";
13
+ event_name: "session_needs_input" | "session_failed" | "session_archived";
14
14
  }, {
15
15
  prompt: string;
16
16
  session_id: string | number;
17
17
  watched_session_id: number;
18
- event_name: "session_needs_input" | "session_failed";
18
+ event_name: "session_needs_input" | "session_failed" | "session_archived";
19
19
  }>;
20
20
  export declare function wakeMeUpWhenSessionChangesStateTool(_server: Server, clientFactory: () => IAgentOrchestratorClient): {
21
21
  name: string;
@@ -35,7 +35,7 @@ export declare function wakeMeUpWhenSessionChangesStateTool(_server: Server, cli
35
35
  };
36
36
  event_name: {
37
37
  type: string;
38
- enum: ("session_needs_input" | "session_failed")[];
38
+ enum: ("session_needs_input" | "session_failed" | "session_archived")[];
39
39
  description: string;
40
40
  };
41
41
  prompt: {
@@ -1,45 +1,53 @@
1
1
  import { z } from 'zod';
2
2
  import { parseAllowedAgentRoots } from '../allowed-agent-roots.js';
3
- const AO_EVENT_NAMES = ['session_needs_input', 'session_failed'];
3
+ const AO_EVENT_NAMES = ['session_needs_input', 'session_failed', 'session_archived'];
4
4
  export const WakeMeUpWhenSessionChangesStateSchema = z.object({
5
5
  session_id: z.union([z.string(), z.number()]),
6
6
  watched_session_id: z.number().int().positive(),
7
7
  event_name: z.enum(AO_EVENT_NAMES),
8
8
  prompt: z.string(),
9
9
  });
10
- const TOOL_DESCRIPTION = `Schedule this session to be woken up when another session transitions to \`needs_input\` or \`failed\`. The requester session is put to sleep (waiting status) and a one-time trigger fires when the watched session enters the matching state. If the requester is manually resumed before the watched session transitions, the trigger is silently consumed and won't re-fire.
10
+ const TOOL_DESCRIPTION = `Schedule this session to be woken up when another session transitions to \`needs_input\`, \`failed\`, or \`archived\`. The requester session is put to sleep (waiting status) and a one-time trigger fires when the watched session enters the matching state. If the requester is manually resumed before the watched session transitions, the trigger is silently consumed and won't re-fire.
11
11
 
12
- This is the **state-based analog of \`wake_me_up_later\`**. Use \`wake_me_up_later\` when you know *when* to wake up (a clock time). Use this tool when you know *what event* to wake up on but not when it will happen — e.g., a subagent session you spawned will eventually need input or might fail, and you want to be the first to handle it without polling.
12
+ This is the **state-based analog of \`wake_me_up_later\`**. Use \`wake_me_up_later\` when you know *when* to wake up (a clock time). Use this tool when you know *what event* to wake up on but not when it will happen — e.g., a subagent session you spawned will eventually finish (self-archive), pause for input, or crash, and you want to be the first to handle it without polling.
13
+
14
+ **Triple-wake pattern (typical use).** When you spawn a downstream session and want to wake up on whatever outcome it produces, you'll TYPICALLY want to schedule THREE state-change triggers — one for each terminal/idle event — so you wake on whichever happens first:
15
+ - \`session_archived\` — the watched session self-archived on success (common for closed-loop tasks like "open a PR and self-archive when CI is green"). A downstream session that self-archives goes \`running\` → \`archived\` directly, skipping \`needs_input\`, so a trigger on \`session_needs_input\` alone would NEVER fire for these tasks.
16
+ - \`session_needs_input\` — the watched session paused for user input or finished a turn awaiting follow-up.
17
+ - \`session_failed\` — the watched session crashed.
18
+
19
+ Pair these three triggers with ONE \`wake_me_up_later\` deadline backstop so a hung watched session can't keep you sleeping forever. The first trigger to fire wins; the others are silently consumed when the requester resumes. **Picking only ONE of the three is a footgun** — if you only schedule \`session_needs_input\` and the downstream session self-archives directly, the only thing that wakes you is the deadline (long, wasteful). Schedule all three unless you have a specific reason to wake only on one outcome.
13
20
 
14
21
  **IMPORTANT — Use this tool instead of polling.** When this tool is available, it is the correct way to wait on another session's state. Do NOT use these alternatives:
15
22
  - **Repeated \`get_session\` calls in a poll loop**: wastes compute, racks up tool-call overhead, and either polls too often (waste) or too rarely (latency). The trigger fires immediately on transition with no polling latency.
16
- - **\`wake_me_up_later\` with a guessed duration**: time-based wake-ups are wrong here — you don't know when the watched session will transition, and a guess is either too short (you wake up early and have to re-sleep) or too long (the watched session has been sitting in \`needs_input\` while you sleep).
23
+ - **\`wake_me_up_later\` with a guessed duration**: time-based wake-ups are wrong as the *primary* signal here — you don't know when the watched session will transition, and a guess is either too short (you wake up early and have to re-sleep) or too long (the watched session has been sitting in \`needs_input\` / archived while you sleep). Use \`wake_me_up_later\` only as a deadline backstop alongside the state-change triggers.
17
24
 
18
25
  **The watched session can be ANY session**, not just one the requester spawned. You can watch a peer session, a session a different agent created, or even a session run by a different user — as long as the requester knows the watched session's id.
19
26
 
20
- **One-shot semantics.** The trigger auto-disables after firing. If the watched session transitions, the requester wakes up exactly once, then the trigger is gone. To wake on the next transition too, schedule another trigger from the woken-up turn.
27
+ **One-shot semantics.** Each trigger auto-disables after firing. If the watched session transitions, the requester wakes up exactly once and only the first-firing trigger's prompt is delivered; any companion triggers (the other two state events plus the deadline backstop) are silently consumed and gone. To wake on a future transition too, schedule another trigger from the woken-up turn.
21
28
 
22
- **Important — fires on transitions, not on current state.** The trigger fires when the watched session *moves into* the target state, not when it is *already in* it. If the watched session is already \`needs_input\` when you create the trigger, the trigger waits for the next transition out and back in (which only happens if someone resumes the watched session). For \`session_failed\` on a session that is already \`failed\`, the trigger never fires \`failed\` is terminal. This tool rejects those terminal/no-fire-possible cases up front (already-failed watched session, archived watched session, self-watch). For non-terminal "already in target state" cases, the call succeeds but is a no-op until the watched session is resumed.
29
+ **Important — fires on transitions, not on current state.** The trigger fires when the watched session *moves into* the target state, not when it is *already in* it. \`failed\` and \`archived\` are both terminal under typical flow — a session that is already \`failed\` will not transition to \`failed\` again, and a session that is already \`archived\` will not transition to \`archived\` again unless someone unarchives it and re-archives it (rare and surprising). \`needs_input\` is non-terminal: if the watched session is already \`needs_input\` when you create the trigger, it waits for the next transition out and back in. This tool rejects up front any case where the trigger could never fire — already-failed and already-archived watched sessions (terminal states), plus the self-watch case (requester == watched) so the requester doesn't sleep on a trigger that can never fire.
23
30
 
24
- **Deadline backstop pattern.** If you need to wake on the watched session's transition *or* after a max wait time (whichever comes first), create a second \`wake_me_up_later\` trigger alongside this one. First trigger to fire wins; the AO firing path resumes the requester once and the other trigger is silently dropped.
31
+ **Deadline backstop pattern.** Always pair the state-change triggers with one \`wake_me_up_later\` trigger so the requester eventually wakes even if the watched session hangs. First trigger to fire wins; the AO firing path resumes the requester once and the others are silently dropped.
25
32
 
26
33
  **Parameters:**
27
34
  - **session_id**: The session to wake up (the requester). Works from either \`needs_input\` or \`running\` state — if you call this tool from within your own currently-running session, the sleep transition is recorded and takes effect after the current turn ends.
28
35
  - **watched_session_id**: The session to watch. Must be a positive integer. The Rails API rejects unknown ids with a clear 422.
29
36
  - **event_name**: Which transition to wake on:
30
- - \`session_needs_input\`: watched session moves to \`needs_input\` (typically: it finished a turn and is waiting for the user, OR it asked a clarifying question)
31
- - \`session_failed\`: watched session moves to \`failed\` (a hard error — the session crashed or was killed)
37
+ - \`session_needs_input\`: watched session moves to \`needs_input\` (typically: it finished a turn, or it asked a clarifying question).
38
+ - \`session_failed\`: watched session moves to \`failed\` (a hard error — the session crashed or was killed).
39
+ - \`session_archived\`: watched session moves to \`archived\` (typically: it self-archived after completing closed-loop work, OR a user manually archived it).
32
40
 
33
- Pick the one that matches what you're actually waiting for. If you want to wake on either, create two triggers (one per event) — the first to fire wins, and the other is silently dropped on the requester's resume.
34
- - **prompt**: The prompt to send when waking up the session. Include enough context that the woken-up turn knows what to do (e.g., "session #N you were watching just transitioned — check its output and decide next steps").
41
+ When in doubt, schedule all three (see the triple-wake pattern above) — the first to fire wins.
42
+ - **prompt**: The prompt to send when waking up the session. Include enough context that the woken-up turn knows what to do (e.g., "session #N you were watching just transitioned — check its output and decide next steps"). If you scheduled multiple triggers (the typical case), each trigger's prompt should make clear which event fired so the woken-up turn knows the outcome without re-checking.
35
43
 
36
44
  **What happens:**
37
45
  1. Creates a one-time \`ao_event\` trigger bound to the requester (\`reuse_session: true\`, \`last_session_id: session_id\`) with a single condition scoped to \`watched_session_id\` and \`event_name\`.
38
46
  2. As a side effect of creating the trigger, the AO API transitions the requester to sleeping (waiting) status — immediately if currently \`needs_input\`, or after the current turn ends if currently \`running\`.
39
47
  3. When the watched session transitions to the matching state, the trigger fires and resumes the requester with the provided prompt. The trigger then auto-deletes (one-shot).
40
- 4. If the requester is manually resumed first, the pending trigger is consumed (won't fire). If the watched session is archived without ever transitioning, the trigger is cleaned up.
48
+ 4. If the requester is manually resumed first, the pending trigger is consumed (won't fire). If the watched session is archived without ever transitioning to the matching state (e.g., you only scheduled \`session_needs_input\` and it went straight to \`archived\`), the trigger is cleaned up — and you'll only wake when your deadline backstop fires.
41
49
 
42
- **You must end your conversation turn after calling this tool** so the auto-sleep can take effect. If your turn keeps running, the requester will not be in a wakeable state when the watched session transitions, and the wake-up will be silently dropped.`;
50
+ **You must end your conversation turn after calling this tool** so the auto-sleep can take effect. If your turn keeps running, the requester will not be in a wakeable state when the watched session transitions, and the wake-up will be silently dropped. When scheduling multiple triggers (the typical triple-wake + deadline pattern), call this tool repeatedly within the same turn — the auto-sleep is idempotent and only takes effect once the turn ends.`;
43
51
  export function wakeMeUpWhenSessionChangesStateTool(_server, clientFactory) {
44
52
  return {
45
53
  name: 'wake_me_up_when_session_changes_state',
@@ -58,7 +66,7 @@ export function wakeMeUpWhenSessionChangesStateTool(_server, clientFactory) {
58
66
  event_name: {
59
67
  type: 'string',
60
68
  enum: [...AO_EVENT_NAMES],
61
- description: 'Which transition to wake on: "session_needs_input" (watched session is waiting for input) or "session_failed" (watched session crashed).',
69
+ description: 'Which transition to wake on: "session_needs_input" (watched session is waiting for input), "session_failed" (watched session crashed), or "session_archived" (watched session self-archived or was archived by a user). Typically schedule all three (plus a wake_me_up_later deadline backstop) for a downstream session you spawned — see the triple-wake pattern in the tool description.',
62
70
  },
63
71
  prompt: {
64
72
  type: 'string',
@@ -170,6 +178,17 @@ export function wakeMeUpWhenSessionChangesStateTool(_server, clientFactory) {
170
178
  isError: true,
171
179
  };
172
180
  }
181
+ if (event_name === 'session_archived' && watchedSession.status === 'archived') {
182
+ return {
183
+ content: [
184
+ {
185
+ type: 'text',
186
+ text: `Error: Watched session ${watched_session_id} is already in "archived" state. The trigger fires on transitions only — a session that is already archived will not transition to archived again (barring an unarchive + re-archive, which is rare), so the requester would sleep forever. Pass an active session id, or inspect the archived session directly.`,
187
+ },
188
+ ],
189
+ isError: true,
190
+ };
191
+ }
173
192
  if (watchedSession.status === 'archived') {
174
193
  return {
175
194
  content: [