agent-relay-server 0.35.2 → 0.35.3

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/docs/openapi.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "openapi": "3.1.0",
3
3
  "info": {
4
4
  "title": "Agent Relay API",
5
- "version": "0.35.2",
5
+ "version": "0.35.3",
6
6
  "description": "Real-time message bus for inter-agent communication. Agent-first: this spec is designed for machine consumption — agents can self-discover the full API surface via GET /api/spec.",
7
7
  "license": {
8
8
  "name": "MIT",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-relay-server",
3
- "version": "0.35.2",
3
+ "version": "0.35.3",
4
4
  "description": "Lightweight HTTP message relay for inter-agent communication across machines",
5
5
  "module": "src/index.ts",
6
6
  "type": "module",
@@ -154,7 +154,9 @@ export interface ProviderAdapter {
154
154
  probeActivity?(process: ManagedProcess): Promise<"busy" | "idle" | "unknown">;
155
155
  terminalAttachSpec?(process: ManagedProcess): Promise<TerminalAttachSpec>;
156
156
  respondToPermissionDecision?(process: ManagedProcess, input: ProviderPermissionDecisionInput): Promise<Record<string, unknown> | void>;
157
- deliverInitialPrompt?(process: ManagedProcess, prompt: string): Promise<void>;
157
+ // `options.readyTimeoutMs` lets the runner widen the provider-ready wait for the
158
+ // first (cold-start) delivery vs. a fast re-attempt after a ready signal (#329).
159
+ deliverInitialPrompt?(process: ManagedProcess, prompt: string, options?: { readyTimeoutMs?: number }): Promise<void>;
158
160
  deliver(process: ManagedProcess, messages: Message[]): Promise<void>;
159
161
  onStatusChange(cb: (status: ProviderStatusUpdate) => void): void;
160
162
  // Subscribe to session-mirror events from providers that emit them directly
package/src/mcp.ts CHANGED
@@ -50,7 +50,7 @@ import type { ActivityKind, AgentCard, ArtifactKind, ArtifactSensitivity, Attach
50
50
  import { LAND_STRATEGIES, applyWorkspaceAction, waitForWorkspaceStatus, type WorkspaceAction } from "./workspace-actions";
51
51
  import { describeWorkspacePhase, landReceipt, readyContract, worktreeMcpInstructions } from "./workspace-phase";
52
52
  import { type ProviderEffort } from "agent-relay-sdk/provider-catalog";
53
- import { errMessage, isRecord, SPAWN_PROVIDERS, APPROVAL_MODES, VALID_EFFORTS, VALID_WORKSPACE_MODES } from "agent-relay-sdk";
53
+ import { errMessage, isRecord, stringValue, SPAWN_PROVIDERS, APPROVAL_MODES, VALID_EFFORTS, VALID_WORKSPACE_MODES } from "agent-relay-sdk";
54
54
  import { runnerRuntimeTokenEnv } from "./runtime-tokens";
55
55
 
56
56
  type JsonRpcId = string | number | null;
@@ -257,11 +257,11 @@ const TOOLS: ToolDefinition[] = [
257
257
  properties: {
258
258
  provider: { type: "string", enum: SPAWN_PROVIDERS },
259
259
  orchestratorId: { type: "string", description: "Target host. Defaults to the host that owns cwd, else YOUR OWN host — only set it to spawn onto a different machine." },
260
- cwd: { type: "string", description: "Working directory for the agent. Must resolve within the target orchestrator's base directory (enforced server-side)." },
260
+ cwd: { type: "string", description: "Working directory for the agent. Defaults to YOUR OWN cwd (the repo you're working in) when omitted, else the orchestrator's base dir. Must resolve within the target orchestrator's base directory (enforced server-side). NOTE: workspaceMode `isolated` requires a git repo — the resolved cwd must be inside one or the spawn is rejected." },
261
261
  label: { type: "string" },
262
262
  model: { type: "string" },
263
263
  effort: { type: "string", enum: VALID_EFFORTS },
264
- approvalMode: { type: "string", enum: APPROVAL_MODES },
264
+ approvalMode: { type: "string", enum: APPROVAL_MODES, description: "Permission posture for the worker. Defaults to YOUR OWN approval mode when omitted (a headless `guarded` worker wedges on the first approval prompt), so a coordinator running `open` spawns workers that can act autonomously. Pass an explicit value to narrow a child (e.g. `read-only` reviewer)." },
265
265
  prompt: { type: "string", description: "Initial task/message delivered to the agent on launch — spawn and hand it its first instruction in one call (no separate follow-up message needed)." },
266
266
  systemPromptAppend: { type: "string" },
267
267
  profile: { type: "string", description: "Agent profile name to apply (env, instructions, permissions, MCP/skills, spawn quota)." },
@@ -791,16 +791,37 @@ async function relaySpawnAgent(auth: McpAuthContext, args: Record<string, unknow
791
791
  const provider = enumField(args.provider, "provider", SPAWN_PROVIDERS) as SpawnProvider;
792
792
  const cwd = optionalString(args.cwd, "cwd", 500);
793
793
  const callerId = callerAgentId(auth);
794
- const preferHost = callerId ? getAgent(callerId)?.machine : undefined;
794
+ // One caller-record lookup, reused for host preference (#221), the cwd default (#328) and the
795
+ // approvalMode default (#331) — an agent spawning a helper inherits its own context instead of
796
+ // falling back to hardcoded values.
797
+ const caller = callerId ? getAgent(callerId) : undefined;
798
+ const preferHost = caller?.machine;
795
799
  const orchestrator = selectSpawnOrchestrator(provider, optionalString(args.orchestratorId, "orchestratorId", 200), cwd, preferHost);
796
- const resolvedCwd = cwd || orchestrator.baseDir;
800
+ // #328 default cwd to the caller's OWN cwd (the repo it's working in), not the orchestrator
801
+ // base dir, so "agent spawns a helper for its current task" Just Works — especially isolated mode,
802
+ // which needs a git repo (the base dir usually isn't one, so it silently downgraded to shared).
803
+ // Only adopt the caller's cwd when it resolves within the TARGET host's base dir (preferHost
804
+ // already biases the target to the caller's host; a cross-host path may not exist there). Non-agent
805
+ // callers (no caller record) keep the base-dir fallback. Precedence: explicit cwd > caller cwd > base dir.
806
+ const callerCwd = stringValue(caller?.meta?.cwd);
807
+ const inheritedCwd = callerCwd && isPathWithinBase(callerCwd, orchestrator.baseDir) ? callerCwd : undefined;
808
+ const resolvedCwd = cwd || inheritedCwd || orchestrator.baseDir;
797
809
  // #308 §3 — cwd must resolve within the TARGET host's base dir. A path valid on your own host
798
810
  // may not exist on a different orchestrator, so validate against the chosen host and say which.
799
811
  if (cwd && !isPathWithinBase(cwd, orchestrator.baseDir)) {
800
812
  throw new ValidationError(`cwd '${cwd}' is not within ${orchestrator.id} (host ${orchestrator.hostname})'s base dir '${orchestrator.baseDir}' — a path valid on your host may not exist on the target. Pass a cwd under that base dir, or omit cwd to default to it.`);
801
813
  }
802
814
  const selection = providerSelection(provider, args);
803
- const approvalMode = optionalEnum(args.approvalMode, "approvalMode", APPROVAL_MODES) as SpawnApprovalMode | undefined ?? "guarded";
815
+ // #331 default the child's approval mode to the CALLER's, not a hardcoded `guarded`. A headless
816
+ // `guarded` child wedges on the first tool-call approval prompt (no human at the TUI — it can't even
817
+ // read its own spawn message). A trusted coordinator running `open` spawns workers that can actually
818
+ // work in their isolated worktrees; an explicit arg always wins and can NARROW a child (e.g. a
819
+ // read-only reviewer); non-agent/admin callers (no caller record) keep the safe `guarded` default.
820
+ // Precedence: explicit approvalMode > caller mode > guarded.
821
+ const callerApprovalMode = optionalEnum(stringValue(caller?.meta?.approvalMode), "approvalMode", APPROVAL_MODES) as SpawnApprovalMode | undefined;
822
+ const approvalMode = (optionalEnum(args.approvalMode, "approvalMode", APPROVAL_MODES) as SpawnApprovalMode | undefined)
823
+ ?? callerApprovalMode
824
+ ?? "guarded";
804
825
  const spawnRequestId = optionalString(args.spawnRequestId, "spawnRequestId", 160) ?? generateSpawnRequestId();
805
826
  const label = optionalString(args.label, "label", 120);
806
827
  const policyName = optionalString(args.policyName, "policyName", 120);
@@ -815,8 +836,7 @@ async function relaySpawnAgent(auth: McpAuthContext, args: Record<string, unknow
815
836
  // granted only to agents whose profile sets maxSpawnedAgents>0 and never to children).
816
837
  // Server/admin tokens have no caller identity → unrestricted by design.
817
838
  if (callerId) {
818
- const me = getAgent(callerId);
819
- if (me?.spawnedBy) {
839
+ if (caller?.spawnedBy) {
820
840
  throw new McpAuthError("spawned agents cannot spawn further agents (no grandchildren)");
821
841
  }
822
842
  const quota = auth.component?.constraints?.maxSpawnedAgents ?? 0;