@getpaseo/server 0.1.88 → 0.1.90

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 (94) hide show
  1. package/dist/server/server/agent/agent-manager.js +4 -1
  2. package/dist/server/server/agent/agent-prompt.js +4 -1
  3. package/dist/server/server/agent/agent-sdk-types.d.ts +1 -0
  4. package/dist/server/server/agent/agent-storage.d.ts +22 -22
  5. package/dist/server/server/agent/agent-storage.js +2 -9
  6. package/dist/server/server/agent/create-agent/create.d.ts +2 -0
  7. package/dist/server/server/agent/create-agent/create.js +26 -7
  8. package/dist/server/server/agent/create-agent-lifecycle-dispatch.d.ts +1 -0
  9. package/dist/server/server/agent/create-agent-lifecycle-dispatch.js +4 -0
  10. package/dist/server/server/agent/create-agent-mode.d.ts +3 -8
  11. package/dist/server/server/agent/create-agent-mode.js +16 -2
  12. package/dist/server/server/agent/import-sessions.js +1 -1
  13. package/dist/server/server/agent/mcp-server.d.ts +1 -0
  14. package/dist/server/server/agent/mcp-server.js +113 -70
  15. package/dist/server/server/agent/provider-snapshot-manager.d.ts +2 -1
  16. package/dist/server/server/agent/provider-snapshot-manager.js +18 -2
  17. package/dist/server/server/agent/providers/acp-agent.d.ts +3 -3
  18. package/dist/server/server/agent/providers/acp-agent.js +18 -13
  19. package/dist/server/server/agent/providers/codex-app-server-agent.js +16 -22
  20. package/dist/server/server/agent/providers/mock-load-test-agent.d.ts +2 -0
  21. package/dist/server/server/agent/providers/mock-load-test-agent.js +69 -2
  22. package/dist/server/server/agent/providers/opencode-agent.js +19 -8
  23. package/dist/server/server/agent/providers/pi/agent.js +13 -0
  24. package/dist/server/server/agent/providers/pi/rpc-types.d.ts +3 -0
  25. package/dist/server/server/agent/timeline-projection.js +30 -1
  26. package/dist/server/server/atomic-file.d.ts +3 -0
  27. package/dist/server/server/atomic-file.js +19 -0
  28. package/dist/server/server/auto-archive-on-merge/archive-if-safe.d.ts +1 -0
  29. package/dist/server/server/auto-archive-on-merge/archive-if-safe.js +10 -2
  30. package/dist/server/server/bootstrap.d.ts +7 -2
  31. package/dist/server/server/bootstrap.js +154 -115
  32. package/dist/server/server/chat/chat-service.js +2 -4
  33. package/dist/server/server/config.js +41 -0
  34. package/dist/server/server/daemon-keypair.js +2 -2
  35. package/dist/server/server/loop-service.d.ts +26 -22
  36. package/dist/server/server/loop-service.js +27 -9
  37. package/dist/server/server/package-version.d.ts +2 -2
  38. package/dist/server/server/paseo-worktree-archive-service.d.ts +2 -0
  39. package/dist/server/server/paseo-worktree-archive-service.js +28 -9
  40. package/dist/server/server/persisted-config.d.ts +84 -28
  41. package/dist/server/server/persisted-config.js +20 -3
  42. package/dist/server/server/pid-lock.d.ts +2 -2
  43. package/dist/server/server/private-files.d.ts +0 -1
  44. package/dist/server/server/private-files.js +0 -5
  45. package/dist/server/server/schedule/service.d.ts +6 -0
  46. package/dist/server/server/schedule/service.js +41 -18
  47. package/dist/server/server/schedule/store.js +3 -2
  48. package/dist/server/server/script-health-monitor.d.ts +4 -4
  49. package/dist/server/server/script-health-monitor.js +6 -6
  50. package/dist/server/server/script-proxy.d.ts +2 -39
  51. package/dist/server/server/script-proxy.js +1 -244
  52. package/dist/server/server/script-route-branch-handler.d.ts +2 -2
  53. package/dist/server/server/script-route-branch-handler.js +3 -37
  54. package/dist/server/server/script-status-projection.d.ts +6 -4
  55. package/dist/server/server/script-status-projection.js +85 -37
  56. package/dist/server/server/server-id.js +3 -3
  57. package/dist/server/server/service-proxy.d.ts +237 -0
  58. package/dist/server/server/service-proxy.js +714 -0
  59. package/dist/server/server/session.d.ts +12 -18
  60. package/dist/server/server/session.js +206 -117
  61. package/dist/server/server/speech/providers/local/worker-client.js +1 -11
  62. package/dist/server/server/websocket-server.d.ts +7 -4
  63. package/dist/server/server/websocket-server.js +9 -4
  64. package/dist/server/server/workspace-bootstrap-dedupe.d.ts +34 -0
  65. package/dist/server/server/workspace-bootstrap-dedupe.js +23 -0
  66. package/dist/server/server/workspace-directory.d.ts +8 -0
  67. package/dist/server/server/workspace-directory.js +141 -11
  68. package/dist/server/server/workspace-git-service.d.ts +3 -0
  69. package/dist/server/server/workspace-git-service.js +53 -12
  70. package/dist/server/server/workspace-registry.d.ts +2 -2
  71. package/dist/server/server/workspace-registry.js +2 -6
  72. package/dist/server/server/workspace-service-env.d.ts +1 -0
  73. package/dist/server/server/workspace-service-env.js +23 -18
  74. package/dist/server/server/worktree/commands.d.ts +2 -0
  75. package/dist/server/server/worktree/commands.js +4 -1
  76. package/dist/server/server/worktree-bootstrap.d.ts +4 -3
  77. package/dist/server/server/worktree-bootstrap.js +14 -13
  78. package/dist/server/server/worktree-core.d.ts +1 -0
  79. package/dist/server/server/worktree-core.js +2 -0
  80. package/dist/server/server/worktree-session.d.ts +6 -2
  81. package/dist/server/server/worktree-session.js +3 -0
  82. package/dist/server/services/github-service.d.ts +1 -0
  83. package/dist/server/services/github-service.js +7 -1
  84. package/dist/server/utils/checkout-git.d.ts +6 -3
  85. package/dist/server/utils/checkout-git.js +40 -38
  86. package/dist/server/utils/worktree.d.ts +17 -12
  87. package/dist/server/utils/worktree.js +39 -22
  88. package/dist/src/server/persisted-config.js +20 -3
  89. package/dist/src/server/private-files.js +0 -5
  90. package/package.json +9 -7
  91. package/dist/server/server/editor-targets.d.ts +0 -18
  92. package/dist/server/server/editor-targets.js +0 -109
  93. package/dist/server/utils/script-hostname.d.ts +0 -8
  94. package/dist/server/utils/script-hostname.js +0 -14
@@ -300,6 +300,23 @@ export async function createAgentMcpServer(options) {
300
300
  });
301
301
  const registerRawTool = server.registerTool.bind(server);
302
302
  const registerTool = (name, config, handler) => registerRawTool(name, relaxMcpToolOutputSchema(config), (async (args, extra) => addModelVisibleStructuredContent(await handler(args, extra))));
303
+ const buildCronScheduleCadence = (input) => {
304
+ const expression = input.cron?.trim() ?? "";
305
+ if (!expression) {
306
+ throw new Error("cron is required");
307
+ }
308
+ const timezone = normalizeScheduleTimeZoneArg(input.timezone);
309
+ return {
310
+ type: "cron",
311
+ expression,
312
+ ...(timezone !== undefined ? { timezone } : {}),
313
+ };
314
+ };
315
+ const buildScheduleExpiry = (expiresIn) => {
316
+ return expiresIn === undefined
317
+ ? undefined
318
+ : new Date(Date.now() + parseDurationString(expiresIn)).toISOString();
319
+ };
303
320
  const resolveCallerAgent = () => {
304
321
  if (!callerAgentId) {
305
322
  return null;
@@ -325,7 +342,7 @@ export async function createAgentMcpServer(options) {
325
342
  if (opts?.required) {
326
343
  throw new Error("cwd is required");
327
344
  }
328
- throw new Error("cwd is required when no caller agent is available");
345
+ throw new Error("cwd is required outside an agent-scoped session");
329
346
  }
330
347
  return expandUserPath(trimmedCwd);
331
348
  };
@@ -469,7 +486,7 @@ export async function createAgentMcpServer(options) {
469
486
  cwd: z
470
487
  .string()
471
488
  .optional()
472
- .describe("Optional working directory. Defaults to the caller agent working directory."),
489
+ .describe("Optional working directory. Defaults to your current working directory."),
473
490
  title: z
474
491
  .string()
475
492
  .trim()
@@ -484,16 +501,16 @@ export async function createAgentMcpServer(options) {
484
501
  .trim()
485
502
  .min(1, "initialPrompt is required")
486
503
  .describe("Required first task to run immediately after creation."),
487
- background: z
504
+ detached: z
488
505
  .boolean()
489
506
  .optional()
490
507
  .default(false)
491
- .describe("Run agent in background. If false (default), waits for completion or permission request. If true, returns immediately."),
508
+ .describe("If true, the created agent stands on its own: it does not appear in your subagent track and is not archived with you."),
492
509
  notifyOnFinish: z
493
510
  .boolean()
494
511
  .optional()
495
- .default(false)
496
- .describe("Send a notification prompt to the caller agent when this agent finishes, errors, or needs permission. Requires a caller agent context."),
512
+ .default(true)
513
+ .describe("Get notified when the created agent finishes, errors, or needs permission. Set false only for truly fire-and-forget agents."),
497
514
  };
498
515
  const topLevelInputSchema = {
499
516
  cwd: z
@@ -541,7 +558,7 @@ export async function createAgentMcpServer(options) {
541
558
  .boolean()
542
559
  .optional()
543
560
  .default(false)
544
- .describe("Send a notification prompt to the caller agent when this agent finishes, errors, or needs permission. Requires a caller agent context."),
561
+ .describe("Agent-scoped only: get notified when the created agent finishes, errors, or needs permission."),
545
562
  };
546
563
  const createAgentInputSchema = callerAgentId ? agentToAgentInputSchema : topLevelInputSchema;
547
564
  const agentToAgentCreateAgentArgsSchema = z.object(agentToAgentInputSchema).strict();
@@ -574,7 +591,7 @@ export async function createAgentMcpServer(options) {
574
591
  }
575
592
  const handler = resolveSpeakHandler?.(callerAgentId) ?? null;
576
593
  if (!handler) {
577
- throw new Error(`No speak handler registered for caller agent '${callerAgentId}'`);
594
+ throw new Error(`No speak handler registered for your session '${callerAgentId}'`);
578
595
  }
579
596
  await handler({
580
597
  text: args.text,
@@ -603,14 +620,30 @@ export async function createAgentMcpServer(options) {
603
620
  availableModes: z.array(ProviderModeSchema),
604
621
  lastMessage: z.string().nullable().optional(),
605
622
  permission: AgentPermissionRequestPayloadSchema.nullable().optional(),
623
+ guidance: z.string().optional(),
606
624
  },
607
625
  }, async (args) => {
608
- const { parsedArgs, worktree } = resolveCreateAgentToolArgs(args);
609
- const { snapshot, background, initialPromptStarted } = await createAgentCommand({
626
+ const resolvedArgs = resolveCreateAgentToolArgs(args);
627
+ const { parsedArgs, worktree } = resolvedArgs;
628
+ let requestedBackground;
629
+ let notifyOnFinish;
630
+ let detached;
631
+ if (resolvedArgs.kind === "agent-scoped") {
632
+ requestedBackground = true;
633
+ notifyOnFinish = resolvedArgs.parsedArgs.notifyOnFinish;
634
+ detached = resolvedArgs.parsedArgs.detached;
635
+ }
636
+ else {
637
+ requestedBackground = resolvedArgs.parsedArgs.background;
638
+ notifyOnFinish = resolvedArgs.parsedArgs.notifyOnFinish ?? false;
639
+ detached = false;
640
+ }
641
+ const { snapshot, background: createdInBackground, initialPromptStarted, } = await createAgentCommand({
610
642
  agentManager,
611
643
  agentStorage,
612
644
  logger: childLogger,
613
645
  paseoHome: options.paseoHome,
646
+ worktreesRoot: options.worktreesRoot,
614
647
  workspaceGitService: options.workspaceGitService,
615
648
  terminalManager,
616
649
  providerSnapshotManager,
@@ -625,14 +658,15 @@ export async function createAgentMcpServer(options) {
625
658
  features: parsedArgs.settings?.features,
626
659
  labels: parsedArgs.labels,
627
660
  mode: parsedArgs.settings?.modeId,
628
- background: parsedArgs.background ?? false,
629
- notifyOnFinish: parsedArgs.notifyOnFinish ?? false,
661
+ background: requestedBackground,
662
+ notifyOnFinish,
663
+ detached,
630
664
  callerAgentId,
631
665
  callerContext,
632
666
  worktree,
633
667
  });
634
668
  try {
635
- if (!background && initialPromptStarted) {
669
+ if (!createdInBackground && initialPromptStarted) {
636
670
  const result = await waitForAgentWithTimeout(agentManager, snapshot.id, {
637
671
  waitForActive: true,
638
672
  });
@@ -659,8 +693,11 @@ export async function createAgentMcpServer(options) {
659
693
  childLogger.error({ err: error, agentId: snapshot.id }, "Failed to run initial prompt");
660
694
  throw error;
661
695
  }
662
- // Return immediately if background=true
696
+ // Return immediately for async creation.
663
697
  const currentSnapshot = agentManager.getAgent(snapshot.id) ?? snapshot;
698
+ const guidance = callerAgentId && notifyOnFinish && initialPromptStarted
699
+ ? "You will get notified when the created agent finishes, errors, or needs permission. Do not call wait_for_agent or poll for status; continue with other work until the notification arrives."
700
+ : undefined;
664
701
  const response = {
665
702
  content: [],
666
703
  structuredContent: ensureValidJson({
@@ -672,6 +709,7 @@ export async function createAgentMcpServer(options) {
672
709
  availableModes: currentSnapshot.availableModes,
673
710
  lastMessage: null,
674
711
  permission: null,
712
+ ...(guidance ? { guidance } : {}),
675
713
  }),
676
714
  };
677
715
  return response;
@@ -679,12 +717,14 @@ export async function createAgentMcpServer(options) {
679
717
  function resolveCreateAgentToolArgs(args) {
680
718
  if (callerAgentId) {
681
719
  return {
720
+ kind: "agent-scoped",
682
721
  parsedArgs: agentToAgentCreateAgentArgsSchema.parse(args),
683
722
  worktree: undefined,
684
723
  };
685
724
  }
686
725
  const parsedArgs = topLevelCreateAgentArgsSchema.parse(args);
687
726
  return {
727
+ kind: "top-level",
688
728
  parsedArgs,
689
729
  worktree: resolveTopLevelCreateAgentWorktree(parsedArgs),
690
730
  };
@@ -784,7 +824,7 @@ export async function createAgentMcpServer(options) {
784
824
  .boolean()
785
825
  .optional()
786
826
  .default(false)
787
- .describe("Send a notification prompt to the caller agent when this agent finishes, errors, or needs permission."),
827
+ .describe("Agent-scoped only: get notified when this run finishes, errors, or needs permission."),
788
828
  },
789
829
  outputSchema: {
790
830
  success: z.boolean(),
@@ -1025,7 +1065,7 @@ export async function createAgentMcpServer(options) {
1025
1065
  cwd: z
1026
1066
  .string()
1027
1067
  .optional()
1028
- .describe("Optional working directory. Defaults to the caller agent cwd."),
1068
+ .describe("Optional working directory. Defaults to your current working directory."),
1029
1069
  all: z.boolean().optional().describe("List terminals across all working directories."),
1030
1070
  },
1031
1071
  outputSchema: {
@@ -1058,7 +1098,7 @@ export async function createAgentMcpServer(options) {
1058
1098
  cwd: z
1059
1099
  .string()
1060
1100
  .optional()
1061
- .describe("Optional working directory. Defaults to the caller agent cwd."),
1101
+ .describe("Optional working directory. Defaults to your current working directory."),
1062
1102
  name: z.string().optional().describe("Optional terminal name."),
1063
1103
  },
1064
1104
  outputSchema: TerminalSummarySchema.shape,
@@ -1168,79 +1208,80 @@ export async function createAgentMcpServer(options) {
1168
1208
  });
1169
1209
  registerTool("create_schedule", {
1170
1210
  title: "Create schedule",
1171
- description: "Create a recurring schedule that runs on an agent or a new agent.",
1211
+ description: "Create a recurring schedule that starts a new agent on a cron cadence.",
1172
1212
  inputSchema: {
1173
1213
  prompt: z.string().trim().min(1, "prompt is required"),
1174
- every: z.string().optional(),
1175
- cron: z.string().optional(),
1214
+ cron: z.string().trim().min(1, "cron is required"),
1176
1215
  timezone: z
1177
1216
  .string()
1178
1217
  .trim()
1179
1218
  .min(1)
1180
1219
  .optional()
1181
- .describe("IANA time zone for cron cadence; requires cron. For example: America/New_York."),
1220
+ .describe("IANA time zone for the cron cadence. For example: America/New_York."),
1182
1221
  name: z.string().optional(),
1183
- target: z.enum(["self", "new-agent"]).optional(),
1184
- provider: AgentProviderEnum.optional().describe("Provider, or provider/model (for example: codex or codex/gpt-5.4)."),
1222
+ provider: AgentProviderEnum.describe("Provider, or provider/model (for example: codex or codex/gpt-5.4)."),
1185
1223
  cwd: z.string().optional(),
1186
1224
  maxRuns: z.number().int().positive().optional(),
1187
1225
  expiresIn: z.string().optional(),
1188
1226
  },
1189
1227
  outputSchema: ScheduleSummarySchema.shape,
1190
- }, async ({ prompt, every, cron, timezone, name, target, provider, cwd, maxRuns, expiresIn }) => {
1228
+ }, async ({ prompt, cron, timezone, name, provider, cwd, maxRuns, expiresIn }) => {
1191
1229
  if (!scheduleService) {
1192
1230
  throw new Error("Schedule service is not configured");
1193
1231
  }
1194
- const normalizedEvery = normalizeScheduleCadenceArg(every);
1195
- const normalizedCron = normalizeScheduleCadenceArg(cron);
1196
- const normalizedTimeZone = normalizeScheduleTimeZoneArg(timezone);
1197
- const cadenceCount = Number(normalizedEvery !== undefined) + Number(normalizedCron !== undefined);
1198
- if (cadenceCount !== 1) {
1199
- throw new Error("Specify exactly one of every or cron");
1232
+ const expiresAt = buildScheduleExpiry(expiresIn);
1233
+ const schedule = await scheduleService.create({
1234
+ prompt: prompt.trim(),
1235
+ cadence: buildCronScheduleCadence({
1236
+ cron,
1237
+ ...(timezone !== undefined ? { timezone } : {}),
1238
+ }),
1239
+ target: resolveNewAgentScheduleTarget({ provider, cwd }),
1240
+ ...(name?.trim() ? { name: name.trim() } : {}),
1241
+ ...(maxRuns === undefined ? {} : { maxRuns }),
1242
+ ...(expiresAt === undefined ? {} : { expiresAt }),
1243
+ });
1244
+ return {
1245
+ content: [],
1246
+ structuredContent: ensureValidJson(toScheduleSummary(schedule)),
1247
+ };
1248
+ });
1249
+ registerTool("create_heartbeat", {
1250
+ title: "Create heartbeat",
1251
+ description: "Create a recurring heartbeat that sends you a prompt on a cron cadence.",
1252
+ inputSchema: {
1253
+ prompt: z.string().trim().min(1, "prompt is required"),
1254
+ cron: z.string().trim().min(1, "cron is required"),
1255
+ timezone: z
1256
+ .string()
1257
+ .trim()
1258
+ .min(1)
1259
+ .optional()
1260
+ .describe("IANA time zone for the cron cadence. For example: America/New_York."),
1261
+ name: z.string().optional(),
1262
+ maxRuns: z.number().int().positive().optional(),
1263
+ expiresIn: z.string().optional(),
1264
+ },
1265
+ outputSchema: ScheduleSummarySchema.shape,
1266
+ }, async ({ prompt, cron, timezone, name, maxRuns, expiresIn }) => {
1267
+ if (!scheduleService) {
1268
+ throw new Error("Schedule service is not configured");
1200
1269
  }
1201
- if (normalizedTimeZone !== undefined && normalizedCron === undefined) {
1202
- throw new Error("timezone can only be used with cron");
1270
+ if (!callerAgentId) {
1271
+ throw new Error("create_heartbeat requires an agent-scoped session");
1203
1272
  }
1204
- const scheduleTarget = target === "self"
1205
- ? (() => {
1206
- const callerAgent = resolveCallerAgent();
1207
- if (!callerAgentId || !callerAgent) {
1208
- throw new Error("target=self requires a caller agent");
1209
- }
1210
- const trimmedCwd = cwd?.trim();
1211
- if (trimmedCwd && expandUserPath(trimmedCwd) !== callerAgent.cwd) {
1212
- throw new Error("cwd can only differ from the caller agent when target=new-agent");
1213
- }
1214
- if (provider !== undefined) {
1215
- const resolved = resolveScheduleProviderAndModel({
1216
- provider,
1217
- defaultProvider: callerAgent.provider,
1218
- });
1219
- if (resolved.provider !== callerAgent.provider ||
1220
- (resolved.model !== undefined && resolved.model !== callerAgent.config.model)) {
1221
- throw new Error("provider can only differ from the caller agent when target=new-agent");
1222
- }
1223
- }
1224
- return { type: "agent", agentId: callerAgentId };
1225
- })()
1226
- : (() => {
1227
- return resolveNewAgentScheduleTarget({ provider, cwd });
1228
- })();
1273
+ resolveCallerAgent();
1274
+ const expiresAt = buildScheduleExpiry(expiresIn);
1229
1275
  const schedule = await scheduleService.create({
1230
1276
  prompt: prompt.trim(),
1231
- cadence: normalizedEvery !== undefined
1232
- ? { type: "every", everyMs: parseDurationString(normalizedEvery) }
1233
- : {
1234
- type: "cron",
1235
- expression: normalizedCron,
1236
- ...(normalizedTimeZone !== undefined ? { timezone: normalizedTimeZone } : {}),
1237
- },
1238
- target: scheduleTarget,
1277
+ cadence: buildCronScheduleCadence({
1278
+ cron,
1279
+ ...(timezone !== undefined ? { timezone } : {}),
1280
+ }),
1281
+ target: { type: "agent", agentId: callerAgentId },
1239
1282
  ...(name?.trim() ? { name: name.trim() } : {}),
1240
1283
  ...(maxRuns === undefined ? {} : { maxRuns }),
1241
- ...(expiresIn === undefined
1242
- ? {}
1243
- : { expiresAt: new Date(Date.now() + parseDurationString(expiresIn)).toISOString() }),
1284
+ ...(expiresAt === undefined ? {} : { expiresAt }),
1244
1285
  });
1245
1286
  return {
1246
1287
  content: [],
@@ -1518,7 +1559,7 @@ export async function createAgentMcpServer(options) {
1518
1559
  cwd: z
1519
1560
  .string()
1520
1561
  .optional()
1521
- .describe("Optional repository cwd. Defaults to the caller agent cwd."),
1562
+ .describe("Optional repository cwd. Defaults to your current working directory."),
1522
1563
  },
1523
1564
  outputSchema: {
1524
1565
  worktrees: z.array(WorktreeSummarySchema),
@@ -1578,6 +1619,7 @@ export async function createAgentMcpServer(options) {
1578
1619
  const repoRoot = resolveScopedCwd(cwd, { required: true });
1579
1620
  const commandResult = await createPaseoWorktreeCommand({
1580
1621
  paseoHome: options.paseoHome,
1622
+ worktreesRoot: options.worktreesRoot,
1581
1623
  createPaseoWorktreeWorkflow: options.createPaseoWorktree,
1582
1624
  }, createMcpWorktreeCommandInput(repoRoot, target));
1583
1625
  if (!commandResult.ok) {
@@ -1603,7 +1645,7 @@ export async function createAgentMcpServer(options) {
1603
1645
  cwd: z
1604
1646
  .string()
1605
1647
  .optional()
1606
- .describe("Optional repository cwd. Defaults to the caller agent cwd."),
1648
+ .describe("Optional repository cwd. Defaults to your current working directory."),
1607
1649
  worktreePath: z.string().optional(),
1608
1650
  worktreeSlug: z.string().optional(),
1609
1651
  },
@@ -1778,6 +1820,7 @@ function archiveWorktreeDependencies(options, context) {
1778
1820
  }
1779
1821
  return {
1780
1822
  paseoHome: options.paseoHome,
1823
+ worktreesRoot: options.worktreesRoot,
1781
1824
  github: options.github,
1782
1825
  workspaceGitService: options.workspaceGitService,
1783
1826
  agentManager: context.agentManager,
@@ -28,12 +28,13 @@ interface ProviderSnapshotProviderOptions {
28
28
  provider: AgentProvider;
29
29
  wait?: boolean;
30
30
  }
31
- interface ResolveProviderCreateConfigOptions {
31
+ export interface ResolveProviderCreateConfigOptions {
32
32
  cwd?: string | null;
33
33
  provider: AgentProvider;
34
34
  requestedMode: string | undefined;
35
35
  featureValues: Record<string, unknown> | undefined;
36
36
  parent: ManagedAgent | null;
37
+ unattended: boolean;
37
38
  }
38
39
  export interface ResolvedProviderCreateConfig {
39
40
  modeId: string | undefined;
@@ -175,11 +175,13 @@ export class ProviderSnapshotManager {
175
175
  wait: true,
176
176
  });
177
177
  const definition = this.requireProvider(input.provider);
178
+ const parent = input.parent ? this.resolveParent(input.parent) : null;
178
179
  return definition.resolveCreateConfig({
179
180
  provider: input.provider,
180
181
  requestedMode: input.requestedMode,
181
182
  featureValues: input.featureValues,
182
- parent: input.parent ? this.resolveParent(input.parent) : null,
183
+ parent,
184
+ unattended: input.unattended || parent?.isUnattended === true,
183
185
  availableModes: entry.modes ?? [],
184
186
  });
185
187
  }
@@ -227,12 +229,26 @@ export class ProviderSnapshotManager {
227
229
  this.providerLoads.clear();
228
230
  }
229
231
  buildRegistry() {
230
- return buildProviderRegistry(this.logger, {
232
+ const registry = buildProviderRegistry(this.logger, {
231
233
  runtimeSettings: this.runtimeSettings,
232
234
  providerOverrides: this.providerOverrides,
233
235
  workspaceGitService: this.workspaceGitService,
234
236
  isDev: this.isDev,
235
237
  });
238
+ for (const [provider, client] of Object.entries(this.extraClients)) {
239
+ const definition = registry[provider];
240
+ if (!definition)
241
+ continue;
242
+ registry[provider] = {
243
+ ...definition,
244
+ createClient: () => client,
245
+ resolveCreateConfig: client.resolveCreateConfig?.bind(client) ?? definition.resolveCreateConfig,
246
+ isCreateConfigUnattended: client.isCreateConfigUnattended?.bind(client) ?? definition.isCreateConfigUnattended,
247
+ fetchModels: client.listModels.bind(client),
248
+ fetchModes: client.listModes?.bind(client) ?? definition.fetchModes,
249
+ };
250
+ }
251
+ return registry;
236
252
  }
237
253
  resolveParent(parent) {
238
254
  const definition = this.requireProvider(parent.provider);
@@ -181,6 +181,7 @@ export declare class ACPAgentSession implements AgentSession, ACPClient {
181
181
  private readonly subscribers;
182
182
  private readonly pendingPermissions;
183
183
  private readonly messageAssemblies;
184
+ private readonly submittedUserMessageIds;
184
185
  private readonly toolCalls;
185
186
  private readonly terminalEntries;
186
187
  private readonly persistedHistory;
@@ -208,15 +209,13 @@ export declare class ACPAgentSession implements AgentSession, ACPClient {
208
209
  private closed;
209
210
  private historyPending;
210
211
  private replayingHistory;
211
- private suppressUserEchoMessageId;
212
- private suppressUserEchoText;
213
212
  private bootstrapThreadEventPending;
214
213
  constructor(config: AgentSessionConfig, options: ACPAgentSessionOptions);
215
214
  get id(): string | null;
216
215
  initializeNewSession(): Promise<void>;
217
216
  initializeResumedSession(): Promise<void>;
218
217
  run(prompt: AgentPromptInput, options?: AgentRunOptions): Promise<AgentRunResult>;
219
- startTurn(prompt: AgentPromptInput, _options?: AgentRunOptions): Promise<{
218
+ startTurn(prompt: AgentPromptInput, options?: AgentRunOptions): Promise<{
220
219
  turnId: string;
221
220
  }>;
222
221
  subscribe(callback: (event: AgentStreamEvent) => void): () => void;
@@ -275,6 +274,7 @@ export declare class ACPAgentSession implements AgentSession, ACPClient {
275
274
  private handlePromptResponse;
276
275
  private wrapTimeline;
277
276
  private pushEvent;
277
+ private emitSubmittedUserMessage;
278
278
  private runtimeInfo;
279
279
  private finishTurn;
280
280
  private emitBootstrapThreadEvent;
@@ -518,6 +518,7 @@ export class ACPAgentSession {
518
518
  this.subscribers = new Set();
519
519
  this.pendingPermissions = new Map();
520
520
  this.messageAssemblies = new Map();
521
+ this.submittedUserMessageIds = new Set();
521
522
  this.toolCalls = new Map();
522
523
  this.terminalEntries = new Map();
523
524
  this.persistedHistory = [];
@@ -539,8 +540,6 @@ export class ACPAgentSession {
539
540
  this.closed = false;
540
541
  this.historyPending = false;
541
542
  this.replayingHistory = false;
542
- this.suppressUserEchoMessageId = null;
543
- this.suppressUserEchoText = null;
544
543
  this.bootstrapThreadEventPending = false;
545
544
  this.provider = options.provider;
546
545
  this.capabilities = options.capabilities;
@@ -635,7 +634,7 @@ export class ACPAgentSession {
635
634
  }
636
635
  return result;
637
636
  }
638
- async startTurn(prompt, _options) {
637
+ async startTurn(prompt, options) {
639
638
  if (this.closed) {
640
639
  throw new Error(`${this.provider} session is closed`);
641
640
  }
@@ -646,12 +645,11 @@ export class ACPAgentSession {
646
645
  throw new Error("A foreground turn is already active");
647
646
  }
648
647
  const turnId = randomUUID();
649
- const messageId = randomUUID();
648
+ const messageId = options?.messageId ?? randomUUID();
650
649
  this.activeForegroundTurnId = turnId;
651
- this.suppressUserEchoMessageId = messageId;
652
- this.suppressUserEchoText = extractPromptText(prompt);
653
650
  this.emitBootstrapThreadEvent();
654
651
  this.pushEvent({ type: "turn_started", provider: this.provider, turnId });
652
+ this.emitSubmittedUserMessage(prompt, messageId, turnId);
655
653
  void this.connection
656
654
  .prompt({
657
655
  sessionId: this.sessionId,
@@ -1360,11 +1358,7 @@ export class ACPAgentSession {
1360
1358
  if (!item) {
1361
1359
  return [];
1362
1360
  }
1363
- const shouldSuppress = this.suppressUserEchoMessageId &&
1364
- update.messageId === this.suppressUserEchoMessageId &&
1365
- this.suppressUserEchoText &&
1366
- item.text === this.suppressUserEchoText;
1367
- if (shouldSuppress) {
1361
+ if (update.messageId && this.submittedUserMessageIds.has(update.messageId)) {
1368
1362
  return [];
1369
1363
  }
1370
1364
  return [this.wrapTimeline(item)];
@@ -1533,6 +1527,19 @@ export class ACPAgentSession {
1533
1527
  subscriber(event);
1534
1528
  }
1535
1529
  }
1530
+ emitSubmittedUserMessage(prompt, messageId, turnId) {
1531
+ const text = extractPromptText(prompt);
1532
+ if (text.trim().length === 0) {
1533
+ return;
1534
+ }
1535
+ this.submittedUserMessageIds.add(messageId);
1536
+ this.pushEvent({
1537
+ type: "timeline",
1538
+ provider: this.provider,
1539
+ turnId,
1540
+ item: { type: "user_message", text, messageId },
1541
+ });
1542
+ }
1536
1543
  runtimeInfo() {
1537
1544
  return {
1538
1545
  provider: this.provider,
@@ -1548,8 +1555,6 @@ export class ACPAgentSession {
1548
1555
  }
1549
1556
  finishTurn(event) {
1550
1557
  this.activeForegroundTurnId = null;
1551
- this.suppressUserEchoMessageId = null;
1552
- this.suppressUserEchoText = null;
1553
1558
  this.pushEvent(event);
1554
1559
  }
1555
1560
  emitBootstrapThreadEvent() {
@@ -572,6 +572,10 @@ function filterCodexThreadsByCwd(threads, cwd) {
572
572
  const matchesCwd = createPathEquivalenceMatcher(cwd);
573
573
  return threads.filter((thread) => typeof thread.cwd === "string" && matchesCwd(thread.cwd));
574
574
  }
575
+ function buildCodexThreadListTimeline(thread) {
576
+ const preview = typeof thread.preview === "string" ? thread.preview.trim() : "";
577
+ return preview ? [{ type: "user_message", text: preview }] : [];
578
+ }
575
579
  export function toAgentUsage(tokenUsage) {
576
580
  const usage = toObjectRecord(tokenUsage);
577
581
  if (!usage)
@@ -4315,31 +4319,21 @@ export class CodexAppServerAgentClient {
4315
4319
  await client.request("initialize", buildCodexAppServerInitializeParams());
4316
4320
  client.notify("initialized", {});
4317
4321
  const limit = options?.limit ?? 20;
4318
- // thread/list returns the cheap `cwd` field. When the caller supplied
4319
- // a cwd hint we filter here so the per-thread `thread/read includeTurns`
4320
- // hydration below only runs for matching threads. Fetch a wider window
4321
- // when filtering since most threads will be from other cwds.
4322
+ // thread/list returns the cheap `cwd` field. Fetch a wider window when
4323
+ // filtering since most threads will be from other cwds, then keep the
4324
+ // local realpath-aware filter for symlink-equivalent workspace paths.
4322
4325
  const listLimit = options?.cwd ? Math.max(limit, 50) : limit;
4323
- const response = toObjectRecord(await client.request("thread/list", { limit: listLimit }));
4326
+ const response = toObjectRecord(await client.request("thread/list", {
4327
+ limit: listLimit,
4328
+ ...(options?.cwd ? { cwd: options.cwd } : {}),
4329
+ }));
4324
4330
  const allThreads = Array.isArray(response?.data) ? response.data.filter(isRecord) : [];
4325
4331
  const threads = filterCodexThreadsByCwd(allThreads, options?.cwd);
4326
- const descriptors = await Promise.all(threads.slice(0, limit).map(async (thread) => {
4332
+ const descriptors = threads.slice(0, limit).map((thread) => {
4327
4333
  const threadId = typeof thread.id === "string" ? thread.id : "";
4328
4334
  const cwd = typeof thread.cwd === "string" ? thread.cwd : process.cwd();
4329
- const title = typeof thread.preview === "string" ? thread.preview : null;
4330
- let timeline = [];
4331
- try {
4332
- timeline = await loadCodexThreadHistoryTimeline({
4333
- threadId,
4334
- cwd,
4335
- requestThread: (threadIdToRead) => {
4336
- return readCodexThread(client, threadIdToRead);
4337
- },
4338
- });
4339
- }
4340
- catch {
4341
- timeline = [];
4342
- }
4335
+ const preview = typeof thread.preview === "string" ? thread.preview : null;
4336
+ const title = typeof thread.name === "string" && thread.name.trim() ? thread.name : preview;
4343
4337
  return {
4344
4338
  provider: CODEX_PROVIDER,
4345
4339
  sessionId: threadId,
@@ -4359,9 +4353,9 @@ export class CodexAppServerAgentClient {
4359
4353
  threadId,
4360
4354
  },
4361
4355
  },
4362
- timeline: timeline.map((entry) => entry.item),
4356
+ timeline: buildCodexThreadListTimeline(thread),
4363
4357
  };
4364
- }));
4358
+ });
4365
4359
  return descriptors;
4366
4360
  }
4367
4361
  finally {
@@ -65,7 +65,9 @@ export declare class MockLoadTestAgentSession implements AgentSession {
65
65
  private scheduleLargePayloadTurn;
66
66
  private scheduleStressTurn;
67
67
  private schedulePlanApprovalTurn;
68
+ private scheduleQuestionPromptTurn;
68
69
  private emitPlanApprovalTurn;
70
+ private emitQuestionPromptTurn;
69
71
  private emitStressTurn;
70
72
  private emitLargePayloadTurn;
71
73
  private tick;