@mduan/paseo-cli 0.11.0-fork.35 → 0.11.0-fork.37

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.
Files changed (35) hide show
  1. package/dist/commands/agent/send.d.ts +2 -0
  2. package/dist/commands/agent/send.js +8 -1
  3. package/node_modules/@getpaseo/client/dist/daemon-client.d.ts +2 -0
  4. package/node_modules/@getpaseo/client/dist/daemon-client.js +1 -0
  5. package/node_modules/@getpaseo/client/package.json +3 -3
  6. package/node_modules/@getpaseo/highlight/package.json +1 -1
  7. package/node_modules/@getpaseo/plugin/package.json +3 -3
  8. package/node_modules/@getpaseo/protocol/dist/messages.d.ts +4 -0
  9. package/node_modules/@getpaseo/protocol/dist/messages.js +2 -0
  10. package/node_modules/@getpaseo/protocol/package.json +1 -1
  11. package/node_modules/@getpaseo/relay/package.json +1 -1
  12. package/node_modules/@getpaseo/server/dist/server/builtin-plugins/package.json +1 -1
  13. package/node_modules/@getpaseo/server/dist/server/server/agent/agent-prompt.d.ts +7 -0
  14. package/node_modules/@getpaseo/server/dist/server/server/agent/agent-prompt.js +82 -16
  15. package/node_modules/@getpaseo/server/dist/server/server/agent/tools/paseo-tools.js +20 -8
  16. package/node_modules/@getpaseo/server/dist/server/server/session/daemon/npm-global-cli.js +7 -0
  17. package/node_modules/@getpaseo/server/dist/server/server/session.js +2 -1
  18. package/node_modules/@getpaseo/server/dist/server/skills/paseo/SKILL.md +1 -1
  19. package/node_modules/@getpaseo/server/dist/server/web-ui/_expo/static/css/xterm-3bb1704bf6cb0876640973dc0244b4cb.css.gz +0 -0
  20. package/node_modules/@getpaseo/server/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-e52f9cb2de89e698c51c18241d12cb99.js.gz +0 -0
  21. package/node_modules/@getpaseo/server/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-8cb998792f7a176d7612229dafb2c2d3.js.gz +0 -0
  22. package/node_modules/@getpaseo/server/dist/server/web-ui/_expo/static/js/web/{index-c5f0ae884c9004e9301096d186baee98.js → index-b8c45270998cdd560e3a40ec00a172ff.js} +3 -3
  23. package/node_modules/@getpaseo/server/dist/server/web-ui/_expo/static/js/web/index-b8c45270998cdd560e3a40ec00a172ff.js.br +0 -0
  24. package/node_modules/@getpaseo/server/dist/server/web-ui/_expo/static/js/web/index-b8c45270998cdd560e3a40ec00a172ff.js.gz +0 -0
  25. package/node_modules/@getpaseo/server/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-cf65163fc7d8cd4c646c36e83771e937.js.gz +0 -0
  26. package/node_modules/@getpaseo/server/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-da2a50627d6e788990a7f324838c24d2.js.gz +0 -0
  27. package/node_modules/@getpaseo/server/dist/server/web-ui/index.html +1 -1
  28. package/node_modules/@getpaseo/server/dist/server/web-ui/index.html.br +0 -0
  29. package/node_modules/@getpaseo/server/dist/server/web-ui/index.html.gz +0 -0
  30. package/node_modules/@getpaseo/server/dist/server/web-ui/manifest.json.gz +0 -0
  31. package/node_modules/@getpaseo/server/dist/src/server/session/daemon/npm-global-cli.js +7 -0
  32. package/node_modules/@getpaseo/server/package.json +6 -6
  33. package/package.json +7 -7
  34. package/node_modules/@getpaseo/server/dist/server/web-ui/_expo/static/js/web/index-c5f0ae884c9004e9301096d186baee98.js.br +0 -0
  35. package/node_modules/@getpaseo/server/dist/server/web-ui/_expo/static/js/web/index-c5f0ae884c9004e9301096d186baee98.js.gz +0 -0
@@ -1,4 +1,5 @@
1
1
  import { type Command } from "commander";
2
+ import type { ActiveTurnBehavior } from "@getpaseo/protocol/messages";
2
3
  import type { CommandOptions, SingleResult, OutputSchema } from "../../output/index.js";
3
4
  /** Result type for agent send command */
4
5
  export interface AgentSendResult {
@@ -9,6 +10,7 @@ export interface AgentSendResult {
9
10
  /** Schema for agent send output */
10
11
  export declare const agentSendSchema: OutputSchema<AgentSendResult>;
11
12
  export interface AgentSendOptions extends CommandOptions {
13
+ behavior?: ActiveTurnBehavior;
12
14
  wait?: boolean;
13
15
  image?: string[];
14
16
  prompt?: string;
@@ -1,3 +1,4 @@
1
+ import { Option } from "commander";
1
2
  import { collectMultiple } from "../../utils/command-options.js";
2
3
  import { connectToDaemon } from "../../utils/client.js";
3
4
  import { readFile } from "node:fs/promises";
@@ -19,6 +20,7 @@ export function addSendOptions(cmd) {
19
20
  .option("--prompt <text>", "Provide the message inline as a flag")
20
21
  .option("--prompt-file <path>", "Read the message from a UTF-8 text file")
21
22
  .option("--image <path>", "Attach image(s) to the message", collectMultiple, [])
23
+ .addOption(new Option("--behavior <behavior>", "How to send into a running turn (default: steer from an agent, interrupt otherwise)").choices(["interrupt", "steer"]))
22
24
  .option("--no-wait", "Return immediately without waiting for completion");
23
25
  }
24
26
  /**
@@ -139,7 +141,12 @@ export async function runSendCommand(agentIdArg, prompt, options, _command) {
139
141
  // Read image files if provided
140
142
  const images = options.image && options.image.length > 0 ? await readImageFiles(options.image) : undefined;
141
143
  // Send the message
142
- await client.sendAgentMessage(agentIdArg, promptInput, { images });
144
+ const sourceAgentId = process.env.PASEO_AGENT_ID?.trim() || undefined;
145
+ await client.sendAgentMessage(agentIdArg, promptInput, {
146
+ images,
147
+ sourceAgentId,
148
+ activeTurnBehavior: options.behavior ?? (sourceAgentId ? "steer" : "interrupt"),
149
+ });
143
150
  // If --no-wait, return immediately
144
151
  if (options.wait === false) {
145
152
  await client.close();
@@ -162,6 +162,8 @@ export interface SendMessageOptions {
162
162
  mimeType: string;
163
163
  }>;
164
164
  attachments?: SendAgentMessageRequest["attachments"];
165
+ /** The sending agent, when an agent rather than a person sends the message. */
166
+ sourceAgentId?: string;
165
167
  }
166
168
  export interface AgentAttentionRequiredNotification {
167
169
  agentId: string;
@@ -2276,6 +2276,7 @@ export class DaemonClient {
2276
2276
  ...(options?.activeTurnBehavior ? { activeTurnBehavior: options.activeTurnBehavior } : {}),
2277
2277
  ...(options?.images ? { images: options.images } : {}),
2278
2278
  ...(options?.attachments ? { attachments: options.attachments } : {}),
2279
+ ...(options?.sourceAgentId ? { sourceAgentId: options.sourceAgentId } : {}),
2279
2280
  });
2280
2281
  const payload = await this.sendRequest({
2281
2282
  requestId,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getpaseo/client",
3
- "version": "0.11.0-fork.35",
3
+ "version": "0.11.0-fork.37",
4
4
  "description": "Paseo client SDK package",
5
5
  "files": [
6
6
  "dist",
@@ -44,7 +44,7 @@
44
44
  "vitest": "^4.1.6"
45
45
  },
46
46
  "dependencies": {
47
- "@getpaseo/protocol": "0.11.0-fork.35",
48
- "@getpaseo/relay": "0.11.0-fork.35"
47
+ "@getpaseo/protocol": "0.11.0-fork.37",
48
+ "@getpaseo/relay": "0.11.0-fork.37"
49
49
  }
50
50
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getpaseo/highlight",
3
- "version": "0.11.0-fork.35",
3
+ "version": "0.11.0-fork.37",
4
4
  "files": [
5
5
  "dist",
6
6
  "!dist/**/*.map",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getpaseo/plugin",
3
- "version": "0.11.0-fork.35",
3
+ "version": "0.11.0-fork.37",
4
4
  "description": "Paseo plugin SDK package",
5
5
  "files": [
6
6
  "dist",
@@ -66,8 +66,8 @@
66
66
  "test": "vitest run"
67
67
  },
68
68
  "devDependencies": {
69
- "@getpaseo/client": "0.11.0-fork.35",
70
- "@getpaseo/protocol": "0.11.0-fork.35",
69
+ "@getpaseo/client": "0.11.0-fork.37",
70
+ "@getpaseo/protocol": "0.11.0-fork.37",
71
71
  "@types/node": "^20.9.0",
72
72
  "@types/react": "~19.2.0",
73
73
  "react": "19.1.0",
@@ -1586,6 +1586,7 @@ export declare const SendAgentMessageRequestSchema: z.ZodObject<{
1586
1586
  size: number;
1587
1587
  path: string;
1588
1588
  })[], unknown>>>;
1589
+ sourceAgentId: z.ZodOptional<z.ZodString>;
1589
1590
  }, z.core.$strip>;
1590
1591
  export declare const WaitForFinishRequestSchema: z.ZodObject<{
1591
1592
  type: z.ZodLiteral<"wait_for_finish_request">;
@@ -5592,6 +5593,7 @@ export declare const SessionInboundMessageSchema: z.ZodDiscriminatedUnion<[z.Zod
5592
5593
  size: number;
5593
5594
  path: string;
5594
5595
  })[], unknown>>>;
5596
+ sourceAgentId: z.ZodOptional<z.ZodString>;
5595
5597
  }, z.core.$strip>, z.ZodObject<{
5596
5598
  type: z.ZodLiteral<"wait_for_finish_request">;
5597
5599
  requestId: z.ZodString;
@@ -39179,6 +39181,7 @@ export declare const WSSessionInboundSchema: z.ZodObject<{
39179
39181
  size: number;
39180
39182
  path: string;
39181
39183
  })[], unknown>>>;
39184
+ sourceAgentId: z.ZodOptional<z.ZodString>;
39182
39185
  }, z.core.$strip>, z.ZodObject<{
39183
39186
  type: z.ZodLiteral<"wait_for_finish_request">;
39184
39187
  requestId: z.ZodString;
@@ -55890,6 +55893,7 @@ export declare const WSInboundMessageSchema: z.ZodDiscriminatedUnion<[z.ZodObjec
55890
55893
  size: number;
55891
55894
  path: string;
55892
55895
  })[], unknown>>>;
55896
+ sourceAgentId: z.ZodOptional<z.ZodString>;
55893
55897
  }, z.core.$strip>, z.ZodObject<{
55894
55898
  type: z.ZodLiteral<"wait_for_finish_request">;
55895
55899
  requestId: z.ZodString;
@@ -1265,6 +1265,8 @@ export const SendAgentMessageRequestSchema = z.object({
1265
1265
  activeTurnBehavior: ActiveTurnBehaviorSchema.optional(),
1266
1266
  images: z.array(ImageAttachmentSchema).optional(),
1267
1267
  attachments: AgentAttachmentsSchema,
1268
+ /** Set when an agent sends this, such as `paseo send` from an agent's shell; not a human prompt. */
1269
+ sourceAgentId: z.string().optional(),
1268
1270
  });
1269
1271
  export const WaitForFinishRequestSchema = z.object({
1270
1272
  type: z.literal("wait_for_finish_request"),
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getpaseo/protocol",
3
- "version": "0.11.0-fork.35",
3
+ "version": "0.11.0-fork.37",
4
4
  "description": "Paseo shared protocol schemas and wire types",
5
5
  "files": [
6
6
  "dist",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getpaseo/relay",
3
- "version": "0.11.0-fork.35",
3
+ "version": "0.11.0-fork.37",
4
4
  "description": "Paseo relay for bridging daemon and client connections",
5
5
  "files": [
6
6
  "dist",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getpaseo/builtin-plugins",
3
- "version": "0.11.0-fork.35",
3
+ "version": "0.11.0-fork.37",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "scripts": {
@@ -85,6 +85,13 @@ export interface SetupFinishNotificationParams {
85
85
  childAgentId: string;
86
86
  callerAgentId: string;
87
87
  requireParentOwnership?: boolean;
88
+ /**
89
+ * Dispatch this observation is armed ahead of — catches a turn finishing during steering
90
+ * admission. Notifications wait for it and are dropped if it fails.
91
+ */
92
+ dispatch?: Promise<unknown>;
93
+ /** How the armed prompt meets a running turn; a steer can be answered after that turn ends. */
94
+ activeTurnBehavior?: ActiveTurnBehavior;
88
95
  logger: Logger;
89
96
  }
90
97
  export declare function setupFinishNotification(params: SetupFinishNotificationParams): void;
@@ -213,6 +213,11 @@ export async function startCreatedAgentInitialPrompt(params) {
213
213
  }
214
214
  return refreshedSnapshot;
215
215
  }
216
+ /**
217
+ * Claude answers a steer that arrives after its last tool call in a follow-up turn, started
218
+ * once the steered turn ends. Keep observing that long so the follow-up's finish is reported.
219
+ */
220
+ const STEER_FOLLOW_UP_WINDOW_MS = 10000;
216
221
  const FINISH_NOTIFICATION_MESSAGE_LIMIT = 4000;
217
222
  function formatFinishNotificationBody(params) {
218
223
  const statusLine = `Agent ${params.childAgentId} (${params.title}) ${params.reason}.`;
@@ -232,34 +237,77 @@ function formatFinishNotificationBody(params) {
232
237
  }
233
238
  sections.push(`<agent-response>\n${lastAssistantMessage}\n</agent-response>`);
234
239
  }
240
+ if (params.followUpPossible) {
241
+ sections.push("If this response does not answer the message you steered into this agent's turn, the agent may still be answering it. A follow-up report will arrive when it finishes.");
242
+ }
235
243
  return sections.join("\n\n");
236
244
  }
237
- // A caller waits on a child through one armed notification. Arming again, such as a
238
- // follow-up prompt while the child still runs, replaces the earlier one so the child's
239
- // next finish reaches the caller once.
245
+ // One observation per caller/child pair. Re-arming (e.g. a follow-up while the child runs)
246
+ // joins it, so the next finish notifies once even when sends overlap or fail.
240
247
  const armedFinishNotifications = new WeakMap();
241
248
  export function setupFinishNotification(params) {
242
- const { agentManager, agentStorage, childAgentId, callerAgentId, requireParentOwnership = false, logger, } = params;
249
+ const { agentManager, agentStorage, childAgentId, callerAgentId, logger } = params;
250
+ const armedByManager = armedFinishNotifications.get(agentManager) ?? new Map();
251
+ armedFinishNotifications.set(agentManager, armedByManager);
252
+ const armedKey = JSON.stringify([childAgentId, callerAgentId]);
253
+ const armed = armedByManager.get(armedKey);
254
+ if (armed) {
255
+ armed(params);
256
+ return;
257
+ }
258
+ armedByManager.set(armedKey, arm);
243
259
  let hasSeenRunning = false;
244
260
  let stopped = false;
245
261
  const notifiedPermissionRequestIds = new Set();
246
262
  let unsubscribe = null;
247
263
  let notificationQueue = Promise.resolve();
248
- const armedByManager = armedFinishNotifications.get(agentManager) ?? new Map();
249
- armedFinishNotifications.set(agentManager, armedByManager);
250
- const armedKey = JSON.stringify([childAgentId, callerAgentId]);
251
- armedByManager.get(armedKey)?.();
252
- armedByManager.set(armedKey, stop);
264
+ let requireParentOwnership = false;
265
+ // Accepted once any arming is not tied to a dispatch, or any tracked dispatch succeeds.
266
+ let accepted = false;
267
+ let steeredIntoRunningTurn = false;
268
+ let followUpWindow;
269
+ const dispatches = [];
270
+ arm(params);
271
+ function arm(next) {
272
+ requireParentOwnership = next.requireParentOwnership ?? false;
273
+ if (next.activeTurnBehavior === "steer" &&
274
+ agentManager.getAgent(childAgentId)?.lifecycle === "running") {
275
+ steeredIntoRunningTurn = true;
276
+ }
277
+ const { dispatch } = next;
278
+ if (!dispatch) {
279
+ accepted = true;
280
+ return;
281
+ }
282
+ dispatches.push(dispatch);
283
+ void (async () => {
284
+ if (!(await dispatchAccepted()))
285
+ stop();
286
+ })();
287
+ }
288
+ /** Resolves once a tracked dispatch succeeds, or false when every one has failed. */
289
+ async function dispatchAccepted() {
290
+ let tracked = 0;
291
+ while (!accepted && tracked < dispatches.length) {
292
+ tracked = dispatches.length;
293
+ if (await Promise.any(dispatches).then(() => true, () => false)) {
294
+ accepted = true;
295
+ }
296
+ }
297
+ return accepted;
298
+ }
253
299
  function stop() {
254
300
  if (stopped)
255
301
  return;
256
302
  stopped = true;
303
+ clearTimeout(followUpWindow);
257
304
  unsubscribe?.();
258
- if (armedByManager.get(armedKey) === stop) {
259
- armedByManager.delete(armedKey);
260
- }
305
+ armedByManager.delete(armedKey);
261
306
  }
262
- async function notify(reason, permissionRequest) {
307
+ async function notify(reason, options) {
308
+ if (!(await dispatchAccepted())) {
309
+ return;
310
+ }
263
311
  const callerRecord = await agentStorage.get(callerAgentId);
264
312
  if (callerRecord?.archivedAt) {
265
313
  return;
@@ -275,7 +323,8 @@ export function setupFinishNotification(params) {
275
323
  title,
276
324
  reason,
277
325
  lastAssistantMessage,
278
- permissionRequest,
326
+ permissionRequest: options.permissionRequest,
327
+ followUpPossible: options.followUpPossible,
279
328
  });
280
329
  await sendPromptToAgent({
281
330
  agentManager,
@@ -293,11 +342,22 @@ export function setupFinishNotification(params) {
293
342
  if (options.terminal ?? true)
294
343
  stop();
295
344
  notificationQueue = notificationQueue
296
- .then(() => notify(reason, options.permissionRequest))
345
+ .then(() => notify(reason, options))
297
346
  .catch((error) => {
298
347
  logger.error({ err: error, childAgentId, callerAgentId, reason }, "Failed to notify caller agent");
299
348
  });
300
349
  }
350
+ function notifyFinished() {
351
+ if (!steeredIntoRunningTurn) {
352
+ notifySafely("finished");
353
+ return;
354
+ }
355
+ steeredIntoRunningTurn = false;
356
+ hasSeenRunning = false;
357
+ notifySafely("finished", { terminal: false, followUpPossible: true });
358
+ followUpWindow = setTimeout(stop, STEER_FOLLOW_UP_WINDOW_MS);
359
+ followUpWindow.unref?.();
360
+ }
301
361
  unsubscribe = agentManager.subscribe((event) => {
302
362
  if (stopped) {
303
363
  return;
@@ -309,6 +369,7 @@ export function setupFinishNotification(params) {
309
369
  }
310
370
  }
311
371
  if (event.agent.lifecycle === "running") {
372
+ clearTimeout(followUpWindow);
312
373
  if (event.agent.pendingPermissions.size === 0) {
313
374
  hasSeenRunning = true;
314
375
  }
@@ -319,7 +380,7 @@ export function setupFinishNotification(params) {
319
380
  return;
320
381
  }
321
382
  if (event.agent.lifecycle === "idle" && hasSeenRunning) {
322
- notifySafely("finished");
383
+ notifyFinished();
323
384
  return;
324
385
  }
325
386
  if (event.agent.lifecycle === "closed") {
@@ -359,6 +420,11 @@ export function setupFinishNotification(params) {
359
420
  // not have started yet (streamAgent sets a pending run before
360
421
  // transitioning to "running").
361
422
  const childSnapshot = agentManager.getAgent(childAgentId);
423
+ if (params.dispatch) {
424
+ // The dispatch may still load the child or start a turn that clears a stale error.
425
+ hasSeenRunning = childSnapshot?.lifecycle === "running";
426
+ return;
427
+ }
362
428
  if (!childSnapshot || childSnapshot.lifecycle === "closed") {
363
429
  stop();
364
430
  return;
@@ -1,7 +1,7 @@
1
1
  import { stat } from "node:fs/promises";
2
2
  import { z } from "zod";
3
3
  import { ensureValidJson } from "../../json-utils.js";
4
- import { AgentProfileSchema } from "@getpaseo/protocol/messages";
4
+ import { ActiveTurnBehaviorSchema, AgentProfileSchema } from "@getpaseo/protocol/messages";
5
5
  import { AgentFeatureSchema, AgentPermissionRequestPayloadSchema, AgentListItemPayloadSchema, AgentPermissionResponseSchema, AgentSnapshotPayloadSchema, WorkspaceScriptPayloadSchema, } from "../../messages.js";
6
6
  import { buildStoredAgentPayload, toAgentListItemPayload, toAgentPayload, } from "../agent-projections.js";
7
7
  import { curateAgentActivity } from "../activity-curator.js";
@@ -797,6 +797,7 @@ export function createPaseoToolCatalog(options) {
797
797
  .object(legacyTopLevelCreateAgentInputSchema)
798
798
  .strict();
799
799
  const commonSendAgentPromptInputSchema = {
800
+ activeTurnBehavior: ActiveTurnBehaviorSchema.optional().describe('How to send into a running turn: "steer" joins it, falling back to interrupt when steering is unavailable; "interrupt" replaces it. Defaults to steer for agent callers and interrupt otherwise.'),
800
801
  agentId: z.string(),
801
802
  prompt: z.string(),
802
803
  sessionMode: z.string().optional().describe("Optional mode to set before running the prompt."),
@@ -1394,10 +1395,11 @@ export function createPaseoToolCatalog(options) {
1394
1395
  throw new Error("unreachable");
1395
1396
  }
1396
1397
  }
1398
+ const STEERED_BLOCKING_GUIDANCE = "If lastMessage does not answer your message, the agent may still be answering it in a follow-up turn. Check get_agent_activity before acting on this result.";
1397
1399
  const PROMPTED_AGENT_NOTIFICATION_GUIDANCE = "You will get notified when the prompted agent finishes, errors, or needs permission. Do not poll for status; continue with other work until the notification arrives.";
1398
1400
  registerTool("send_agent_prompt", {
1399
1401
  title: "Send agent prompt",
1400
- description: "Send a task to a running agent. Agent-scoped callers run in background by default; top-level callers wait by default.",
1402
+ description: "Send a task to a running agent. Agent-scoped callers run in background and steer a running turn by default; top-level callers wait and interrupt by default.",
1401
1403
  inputSchema: sendAgentPromptInputSchema,
1402
1404
  outputSchema: {
1403
1405
  success: z.boolean(),
@@ -1406,8 +1408,8 @@ export function createPaseoToolCatalog(options) {
1406
1408
  permission: AgentPermissionRequestPayloadSchema.nullable().optional(),
1407
1409
  guidance: z.string().optional(),
1408
1410
  },
1409
- }, async ({ agentId, prompt, sessionMode, background = Boolean(callerAgentId), notifyOnFinish = Boolean(callerAgentId), }) => {
1410
- function armFinishNotification() {
1411
+ }, async ({ agentId, prompt, sessionMode, background = Boolean(callerAgentId), notifyOnFinish = Boolean(callerAgentId), activeTurnBehavior = callerAgentId ? "steer" : "interrupt", }) => {
1412
+ function armFinishNotification(dispatched) {
1411
1413
  if (!callerAgentId || !notifyOnFinish) {
1412
1414
  return false;
1413
1415
  }
@@ -1416,18 +1418,24 @@ export function createPaseoToolCatalog(options) {
1416
1418
  agentStorage,
1417
1419
  childAgentId: agentId,
1418
1420
  callerAgentId,
1421
+ ...dispatched,
1419
1422
  logger: childLogger,
1420
1423
  });
1421
1424
  return true;
1422
1425
  }
1423
- const { disposition } = await sendPromptToAgent({
1426
+ const steersRunningTurn = activeTurnBehavior === "steer" && agentManager.getAgent(agentId)?.lifecycle === "running";
1427
+ const dispatch = sendPromptToAgent({
1424
1428
  agentManager,
1425
1429
  agentStorage,
1426
1430
  agentId,
1427
1431
  prompt,
1428
1432
  sessionMode,
1433
+ activeTurnBehavior,
1429
1434
  logger: childLogger,
1430
1435
  });
1436
+ // Background callers observe before dispatch; blocking callers arm only if the wait runs out.
1437
+ const notifying = background && armFinishNotification({ dispatch, activeTurnBehavior });
1438
+ const { disposition } = await dispatch;
1431
1439
  // If not running in background, wait for completion
1432
1440
  if (!background) {
1433
1441
  const result = await waitForAgentWithTimeout(agentManager, agentId, {
@@ -1435,15 +1443,20 @@ export function createPaseoToolCatalog(options) {
1435
1443
  });
1436
1444
  // The wait ran out while the agent keeps working, so its result arrives as a
1437
1445
  // finish notification instead of in this response.
1438
- const notifying = result.timedOut &&
1446
+ const notifyingAfterWait = result.timedOut &&
1439
1447
  agentManager.getAgent(agentId)?.lifecycle === "running" &&
1440
1448
  armFinishNotification();
1449
+ let guidance;
1450
+ if (notifyingAfterWait)
1451
+ guidance = PROMPTED_AGENT_NOTIFICATION_GUIDANCE;
1452
+ else if (steersRunningTurn)
1453
+ guidance = STEERED_BLOCKING_GUIDANCE;
1441
1454
  const responseData = {
1442
1455
  success: true,
1443
1456
  status: result.status,
1444
1457
  lastMessage: result.lastMessage,
1445
1458
  permission: sanitizePermissionRequest(result.permission),
1446
- ...(notifying ? { guidance: PROMPTED_AGENT_NOTIFICATION_GUIDANCE } : {}),
1459
+ ...(guidance ? { guidance } : {}),
1447
1460
  };
1448
1461
  const validJson = ensureValidJson(responseData);
1449
1462
  const response = {
@@ -1452,7 +1465,6 @@ export function createPaseoToolCatalog(options) {
1452
1465
  };
1453
1466
  return response;
1454
1467
  }
1455
- const notifying = armFinishNotification();
1456
1468
  // Return once the provider has accepted the turn, so the status reports it running.
1457
1469
  if (disposition === "turn_started") {
1458
1470
  await waitForAgentRunStartWithTimeout(agentManager, agentId);
@@ -1,3 +1,4 @@
1
+ import path from "node:path";
1
2
  import { getErrorMessage } from "@getpaseo/protocol/error-utils";
2
3
  import { z } from "zod";
3
4
  import { execCommand } from "../../../utils/spawn.js";
@@ -31,6 +32,12 @@ async function runExternalCommand(command, args, options) {
31
32
  const { stdout, stderr } = await execCommand(command, args, {
32
33
  timeout: options?.timeout,
33
34
  maxBuffer: options?.maxBuffer,
35
+ // Version-manager PATH shims can switch Node installations while the daemon is running.
36
+ envOverlay: {
37
+ PATH: [path.dirname(process.execPath), process.env.PATH]
38
+ .filter(Boolean)
39
+ .join(path.delimiter),
40
+ },
34
41
  });
35
42
  return { exitCode: 0, stdout, stderr };
36
43
  }
@@ -6692,7 +6692,8 @@ export class Session {
6692
6692
  textPrefix: msg.text.slice(0, 80),
6693
6693
  }, "agent.session.send_agent_message");
6694
6694
  const send = async () => {
6695
- await this.recordHumanPrompt({ agentId });
6695
+ if (!msg.sourceAgentId)
6696
+ await this.recordHumanPrompt({ agentId });
6696
6697
  const result = await sendPromptToAgent({
6697
6698
  agentManager: this.agentManager,
6698
6699
  agentStorage: this.agentStorage,
@@ -61,7 +61,7 @@ Detach is an explicit user action in the subagents track, not an agent tool. A c
61
61
 
62
62
  Agent-scoped `create_agent` defaults `notifyOnFinish` to true. Set it to `false` only for truly fire-and-forget agents.
63
63
 
64
- **`send_agent_prompt`** — `{ agentId, prompt }`. Use for follow-ups to an existing agent. Agent-scoped prompt calls default to `background: true` and `notifyOnFinish: true`; top-level calls default to blocking with no callback. For a synchronous follow-up, pass `background: false` and use the returned result.
64
+ **`send_agent_prompt`** — `{ agentId, prompt, activeTurnBehavior? }`. Use for follow-ups to an existing agent. Agent-scoped prompt calls default to `background: true`, `notifyOnFinish: true`, and `activeTurnBehavior: "steer"`; top-level calls default to blocking, no callback, and `"interrupt"`. Steering falls back to interrupting when the provider can't steer. CLI sends from an agent use the same default and accept `--behavior steer|interrupt`. For a synchronous follow-up, pass `background: false` and use the returned result.
65
65
 
66
66
  **`update_agent`** — `{ agentId, name?, labels?, settings? }`. Use `settings` for runtime changes on an existing agent: `modeId`, `model`, `thinkingOptionId`, and provider-specific `features`. For Codex fast mode, pass `settings: { features: { "fast_mode": true } }`.
67
67