@getpaseo/server 0.1.89 → 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 (46) hide show
  1. package/dist/server/server/agent/agent-prompt.js +4 -1
  2. package/dist/server/server/agent/agent-sdk-types.d.ts +1 -0
  3. package/dist/server/server/agent/agent-storage.js +2 -9
  4. package/dist/server/server/agent/create-agent/create.js +10 -2
  5. package/dist/server/server/agent/create-agent-mode.d.ts +3 -8
  6. package/dist/server/server/agent/create-agent-mode.js +16 -2
  7. package/dist/server/server/agent/import-sessions.js +1 -1
  8. package/dist/server/server/agent/provider-snapshot-manager.d.ts +2 -1
  9. package/dist/server/server/agent/provider-snapshot-manager.js +18 -2
  10. package/dist/server/server/agent/providers/acp-agent.d.ts +3 -3
  11. package/dist/server/server/agent/providers/acp-agent.js +18 -13
  12. package/dist/server/server/agent/providers/codex-app-server-agent.js +16 -22
  13. package/dist/server/server/agent/providers/mock-load-test-agent.d.ts +2 -0
  14. package/dist/server/server/agent/providers/mock-load-test-agent.js +69 -2
  15. package/dist/server/server/agent/providers/opencode-agent.js +19 -8
  16. package/dist/server/server/agent/timeline-projection.js +30 -1
  17. package/dist/server/server/atomic-file.d.ts +3 -0
  18. package/dist/server/server/atomic-file.js +19 -0
  19. package/dist/server/server/auto-archive-on-merge/archive-if-safe.js +4 -1
  20. package/dist/server/server/bootstrap.js +2 -0
  21. package/dist/server/server/chat/chat-service.js +2 -4
  22. package/dist/server/server/daemon-keypair.js +2 -2
  23. package/dist/server/server/loop-service.d.ts +4 -0
  24. package/dist/server/server/loop-service.js +27 -9
  25. package/dist/server/server/persisted-config.js +3 -3
  26. package/dist/server/server/private-files.d.ts +0 -1
  27. package/dist/server/server/private-files.js +0 -5
  28. package/dist/server/server/schedule/service.d.ts +6 -0
  29. package/dist/server/server/schedule/service.js +41 -18
  30. package/dist/server/server/schedule/store.js +3 -2
  31. package/dist/server/server/server-id.js +3 -3
  32. package/dist/server/server/session.d.ts +5 -15
  33. package/dist/server/server/session.js +184 -107
  34. package/dist/server/server/speech/providers/local/worker-client.js +1 -11
  35. package/dist/server/server/workspace-bootstrap-dedupe.d.ts +34 -0
  36. package/dist/server/server/workspace-bootstrap-dedupe.js +23 -0
  37. package/dist/server/server/workspace-directory.d.ts +8 -0
  38. package/dist/server/server/workspace-directory.js +141 -15
  39. package/dist/server/server/workspace-registry.js +2 -6
  40. package/dist/server/utils/checkout-git.d.ts +0 -1
  41. package/dist/server/utils/checkout-git.js +23 -31
  42. package/dist/src/server/persisted-config.js +3 -3
  43. package/dist/src/server/private-files.js +0 -5
  44. package/package.json +9 -7
  45. package/dist/server/server/editor-targets.d.ts +0 -18
  46. package/dist/server/server/editor-targets.js +0 -109
@@ -120,9 +120,12 @@ export async function sendPromptToAgent(params) {
120
120
  if (params.sessionMode) {
121
121
  await params.agentManager.setAgentMode(params.agentId, params.sessionMode);
122
122
  }
123
+ const runOptions = params.messageId
124
+ ? { ...params.runOptions, messageId: params.messageId }
125
+ : params.runOptions;
123
126
  return startAgentRun(params.agentManager, params.agentId, params.prompt, params.logger, {
124
127
  replaceRunning: true,
125
- runOptions: params.runOptions,
128
+ runOptions,
126
129
  });
127
130
  }
128
131
  export async function startCreatedAgentInitialPrompt(params) {
@@ -84,6 +84,7 @@ export interface ResolveAgentCreateConfigInput {
84
84
  requestedMode: string | undefined;
85
85
  featureValues: Record<string, unknown> | undefined;
86
86
  parent: AgentCreateConfigParent | null;
87
+ unattended: boolean;
87
88
  availableModes: AgentMode[] | undefined;
88
89
  }
89
90
  export interface ResolveAgentCreateConfigResult {
@@ -1,7 +1,7 @@
1
- import { randomUUID } from "node:crypto";
2
1
  import { promises as fs } from "node:fs";
3
2
  import path from "node:path";
4
3
  import { z } from "zod";
4
+ import { writeJsonFileAtomic } from "../atomic-file.js";
5
5
  import { AgentFeatureSchema, AgentStatusSchema } from "../messages.js";
6
6
  import { toStoredAgentRecord } from "./agent-projections.js";
7
7
  const SERIALIZABLE_CONFIG_SCHEMA = z
@@ -109,8 +109,7 @@ export class AgentStorage {
109
109
  const agentId = record.id;
110
110
  const nextPath = this.buildRecordPath(record);
111
111
  const previousPath = this.pathById.get(agentId);
112
- await fs.mkdir(path.dirname(nextPath), { recursive: true });
113
- await writeFileAtomically(nextPath, JSON.stringify(record, null, 2));
112
+ await writeJsonFileAtomic(nextPath, record);
114
113
  this.addIndexedPath(agentId, nextPath);
115
114
  if (previousPath && previousPath !== nextPath) {
116
115
  try {
@@ -317,10 +316,4 @@ function projectDirNameFromCwd(cwd) {
317
316
  }
318
317
  return prefix + withoutRoot.replace(/[\\/]+/g, "-");
319
318
  }
320
- async function writeFileAtomically(targetPath, payload) {
321
- const directory = path.dirname(targetPath);
322
- const tempPath = path.join(directory, `.agent.tmp-${process.pid}-${Date.now()}-${randomUUID()}`);
323
- await fs.writeFile(tempPath, payload, "utf8");
324
- await fs.rename(tempPath, targetPath);
325
- }
326
319
  //# sourceMappingURL=agent-storage.js.map
@@ -3,7 +3,7 @@ import { expandUserPath, resolvePathFromBase } from "../../path-utils.js";
3
3
  import { toWorktreeRequestError } from "../../worktree-errors.js";
4
4
  import { scheduleAgentMetadataGeneration } from "../agent-metadata-generator.js";
5
5
  import { setupFinishNotification, startCreatedAgentInitialPrompt } from "../agent-prompt.js";
6
- import { resolveClientMessageId } from "../../client-message-id.js";
6
+ import { normalizeClientMessageId, resolveClientMessageId } from "../../client-message-id.js";
7
7
  import { resolveRequiredProviderModel } from "../mcp-shared.js";
8
8
  import { appendTimelineItemIfAgentKnown, emitLiveTimelineItemIfAgentKnown, } from "../timeline-append.js";
9
9
  export async function createAgentCommand(dependencies, input) {
@@ -46,6 +46,13 @@ async function resolveSessionCreateAgent(dependencies, input) {
46
46
  });
47
47
  const prompt = buildAgentPrompt(trimmedPrompt ?? "", input.images, input.attachments);
48
48
  const hasPromptContent = Array.isArray(prompt) ? prompt.length > 0 : prompt.length > 0;
49
+ const clientMessageId = normalizeClientMessageId(input.clientMessageId);
50
+ const runOptions = input.outputSchema || clientMessageId
51
+ ? {
52
+ ...(input.outputSchema ? { outputSchema: input.outputSchema } : {}),
53
+ ...(clientMessageId ? { messageId: clientMessageId } : {}),
54
+ }
55
+ : undefined;
49
56
  return {
50
57
  config: sessionConfig,
51
58
  createOptions: {
@@ -57,7 +64,7 @@ async function resolveSessionCreateAgent(dependencies, input) {
57
64
  },
58
65
  metadataInitialPrompt: trimmedPrompt,
59
66
  prompt: hasPromptContent ? prompt : undefined,
60
- runOptions: input.outputSchema ? { outputSchema: input.outputSchema } : undefined,
67
+ runOptions,
61
68
  explicitTitle: input.explicitTitle,
62
69
  setupContinuation,
63
70
  background: true,
@@ -93,6 +100,7 @@ async function resolveMcpCreateAgent(dependencies, input) {
93
100
  requestedMode: input.mode,
94
101
  featureValues: input.features,
95
102
  parent: parentAgent,
103
+ unattended: false,
96
104
  });
97
105
  const labels = mergeLabels({
98
106
  callerAgentId: input.callerAgentId,
@@ -1,18 +1,13 @@
1
- import type { AgentCreateConfigUnattendedInput, AgentProvider, ResolveAgentCreateConfigInput, ResolveAgentCreateConfigResult } from "./agent-sdk-types.js";
2
- interface CreateAgentModeParent {
3
- provider: AgentProvider;
4
- modeId: string | null;
5
- isUnattended: boolean;
6
- }
1
+ import type { AgentCreateConfigParent, AgentCreateConfigUnattendedInput, AgentProvider, ResolveAgentCreateConfigInput, ResolveAgentCreateConfigResult } from "./agent-sdk-types.js";
7
2
  export interface ResolveCreateAgentModeInput {
8
3
  requestedMode: string | undefined;
9
4
  targetProvider: AgentProvider;
10
- parent: CreateAgentModeParent | null;
5
+ parent: AgentCreateConfigParent | null;
6
+ unattended: boolean;
11
7
  availableModes: string[] | undefined;
12
8
  targetUnattendedMode: string | undefined;
13
9
  }
14
10
  export declare function resolveAndValidateCreateAgentMode(input: ResolveCreateAgentModeInput): string | undefined;
15
11
  export declare function resolveDefaultAgentCreateConfig(input: ResolveAgentCreateConfigInput): ResolveAgentCreateConfigResult;
16
12
  export declare function isDefaultAgentCreateConfigUnattended(input: AgentCreateConfigUnattendedInput): boolean;
17
- export {};
18
13
  //# sourceMappingURL=create-agent-mode.d.ts.map
@@ -4,6 +4,15 @@ function listModes(modes) {
4
4
  }
5
5
  return modes.length > 0 ? modes.join(", ") : "(none)";
6
6
  }
7
+ function isUnattendedCreateConfigParent(parent) {
8
+ return parent.isUnattended;
9
+ }
10
+ function formatCreateConfigParentMode(parent) {
11
+ return parent.modeId ?? "<none>";
12
+ }
13
+ function formatCreateConfigParentSource(parent) {
14
+ return `caller (provider '${parent.provider}')`;
15
+ }
7
16
  export function resolveAndValidateCreateAgentMode(input) {
8
17
  const { requestedMode, targetProvider, parent, availableModes } = input;
9
18
  if (requestedMode !== undefined) {
@@ -13,15 +22,19 @@ export function resolveAndValidateCreateAgentMode(input) {
13
22
  return requestedMode;
14
23
  }
15
24
  if (!parent) {
25
+ if (input.unattended && input.targetUnattendedMode !== undefined) {
26
+ return input.targetUnattendedMode;
27
+ }
16
28
  return undefined;
17
29
  }
18
30
  if (parent.provider === targetProvider) {
19
31
  return parent.modeId ?? undefined;
20
32
  }
21
- if (parent.isUnattended && input.targetUnattendedMode !== undefined) {
33
+ if ((input.unattended || isUnattendedCreateConfigParent(parent)) &&
34
+ input.targetUnattendedMode !== undefined) {
22
35
  return input.targetUnattendedMode;
23
36
  }
24
- throw new Error(`cannot inherit mode '${parent.modeId ?? "<none>"}' from caller (provider '${parent.provider}') for new agent (provider '${targetProvider}'). Pass an explicit mode. Available modes for '${targetProvider}': ${listModes(availableModes)}`);
37
+ throw new Error(`cannot inherit mode '${formatCreateConfigParentMode(parent)}' from ${formatCreateConfigParentSource(parent)} for new agent (provider '${targetProvider}'). Pass an explicit mode. Available modes for '${targetProvider}': ${listModes(availableModes)}`);
25
38
  }
26
39
  export function resolveDefaultAgentCreateConfig(input) {
27
40
  const availableModeIds = input.availableModes?.map((mode) => mode.id);
@@ -30,6 +43,7 @@ export function resolveDefaultAgentCreateConfig(input) {
30
43
  requestedMode: input.requestedMode,
31
44
  targetProvider: input.provider,
32
45
  parent: input.parent,
46
+ unattended: input.unattended,
33
47
  availableModes: availableModeIds,
34
48
  targetUnattendedMode: input.availableModes?.find(isUnattendedMode)?.id,
35
49
  }),
@@ -37,7 +37,7 @@ export async function listImportableProviderSessions(input) {
37
37
  const providerFilter = request.providers ? new Set(request.providers) : undefined;
38
38
  const importedHandles = await collectImportedProviderSessionHandles(agentManager, agentStorage);
39
39
  const descriptors = await agentManager.listImportablePersistedAgents({
40
- limit: 200,
40
+ limit,
41
41
  providerFilter,
42
42
  cwd: request.cwd,
43
43
  });
@@ -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;
@@ -62,6 +62,9 @@ const MODELS = [
62
62
  function shouldEmitPlanApprovalPrompt(prompt) {
63
63
  return /emit\s+(?:a\s+)?synthetic\s+plan\s+approval/i.test(promptToText(prompt));
64
64
  }
65
+ function shouldEmitQuestionPrompt(prompt) {
66
+ return /emit\s+(?:a\s+)?synthetic\s+questions?/i.test(promptToText(prompt));
67
+ }
65
68
  function resolveModelProfile(modelId) {
66
69
  const model = MODELS.find((entry) => entry.id === modelId) ?? MODELS[0];
67
70
  const metadata = model.metadata ?? {};
@@ -389,6 +392,9 @@ export class MockLoadTestAgentSession {
389
392
  if (shouldEmitPlanApprovalPrompt(prompt)) {
390
393
  this.schedulePlanApprovalTurn(turn);
391
394
  }
395
+ else if (shouldEmitQuestionPrompt(prompt)) {
396
+ this.scheduleQuestionPromptTurn(turn);
397
+ }
392
398
  else if (largePayload) {
393
399
  this.scheduleLargePayloadTurn(turn, largePayload);
394
400
  }
@@ -432,9 +438,11 @@ export class MockLoadTestAgentSession {
432
438
  return Array.from(this.pendingPermissions.values());
433
439
  }
434
440
  async respondToPermission(requestId, response) {
435
- if (!this.pendingPermissions.delete(requestId)) {
441
+ const request = this.pendingPermissions.get(requestId);
442
+ if (!request) {
436
443
  return undefined;
437
444
  }
445
+ this.pendingPermissions.delete(requestId);
438
446
  const turn = this.activeTurn;
439
447
  this.emit({
440
448
  type: "permission_resolved",
@@ -444,7 +452,9 @@ export class MockLoadTestAgentSession {
444
452
  ...(turn ? { turnId: turn.turnId } : {}),
445
453
  });
446
454
  if (turn) {
447
- this.finishTurnWithText(turn, "Synthetic plan approval resolved");
455
+ this.finishTurnWithText(turn, request.kind === "question"
456
+ ? "Synthetic questions resolved"
457
+ : "Synthetic plan approval resolved");
448
458
  }
449
459
  return undefined;
450
460
  }
@@ -527,6 +537,12 @@ export class MockLoadTestAgentSession {
527
537
  }, 0);
528
538
  turn.timer.unref?.();
529
539
  }
540
+ scheduleQuestionPromptTurn(turn) {
541
+ turn.timer = setTimeout(() => {
542
+ this.emitQuestionPromptTurn(turn);
543
+ }, 0);
544
+ turn.timer.unref?.();
545
+ }
530
546
  emitPlanApprovalTurn(turn) {
531
547
  if (this.activeTurn !== turn) {
532
548
  return;
@@ -575,6 +591,57 @@ export class MockLoadTestAgentSession {
575
591
  turnId: turn.turnId,
576
592
  });
577
593
  }
594
+ emitQuestionPromptTurn(turn) {
595
+ if (this.activeTurn !== turn) {
596
+ return;
597
+ }
598
+ this.clearTurnTimer(turn);
599
+ this.emit({
600
+ type: "turn_started",
601
+ provider: this.provider,
602
+ turnId: turn.turnId,
603
+ });
604
+ const request = {
605
+ id: `mock-questions-${turn.turnId}`,
606
+ provider: this.provider,
607
+ name: "MockQuestions",
608
+ kind: "question",
609
+ title: "Questions",
610
+ input: {
611
+ questions: [
612
+ {
613
+ question: "Which surface should this apply to?",
614
+ header: "surface",
615
+ options: [{ label: "App" }, { label: "Desktop" }],
616
+ multiSelect: false,
617
+ },
618
+ {
619
+ question: "Which rollout should we use?",
620
+ header: "rollout",
621
+ options: [{ label: "Immediately" }, { label: "Behind feature flag" }],
622
+ multiSelect: false,
623
+ },
624
+ {
625
+ question: "What success criteria should we use?",
626
+ header: "success",
627
+ options: [],
628
+ multiSelect: false,
629
+ placeholder: "Describe success...",
630
+ },
631
+ ],
632
+ },
633
+ metadata: {
634
+ source: "mock_questions",
635
+ },
636
+ };
637
+ this.pendingPermissions.set(request.id, request);
638
+ this.emit({
639
+ type: "permission_requested",
640
+ provider: this.provider,
641
+ request,
642
+ turnId: turn.turnId,
643
+ });
644
+ }
578
645
  emitStressTurn(turn, stress) {
579
646
  if (this.activeTurn !== turn) {
580
647
  return;
@@ -1,5 +1,6 @@
1
1
  import { homedir } from "node:os";
2
2
  import { createPathEquivalenceMatcher } from "../../../utils/path.js";
3
+ import pLimit from "p-limit";
3
4
  import { z } from "zod";
4
5
  import { getAgentStreamEventTurnId, } from "../agent-sdk-types.js";
5
6
  import { isDefaultAgentCreateConfigUnattended, resolveDefaultAgentCreateConfig, } from "../create-agent-mode.js";
@@ -57,10 +58,17 @@ function withOpenCodeAutoAcceptFeature(featureValues, enabled) {
57
58
  }
58
59
  function resolveOpenCodeCreateConfig(input) {
59
60
  const legacyFullAccess = input.requestedMode === OPENCODE_LEGACY_FULL_ACCESS_MODE_ID;
60
- const inheritsUnattended = input.requestedMode === undefined && input.parent?.isUnattended === true;
61
- const requestedMode = legacyFullAccess ? OPENCODE_BUILD_MODE_ID : input.requestedMode;
61
+ const parent = input.parent;
62
+ const isUnattendedCreate = input.unattended || parent?.isUnattended === true;
63
+ const inheritsUnattended = input.requestedMode === undefined && isUnattendedCreate;
64
+ const inheritedOpenCodeMode = inheritsUnattended && parent?.provider === input.provider
65
+ ? (parent.modeId ?? undefined)
66
+ : undefined;
67
+ const requestedMode = legacyFullAccess
68
+ ? OPENCODE_BUILD_MODE_ID
69
+ : (input.requestedMode ?? inheritedOpenCodeMode);
62
70
  const featureValues = legacyFullAccess ||
63
- (inheritsUnattended && input.featureValues?.[OPENCODE_AUTO_ACCEPT_FEATURE_ID] === undefined)
71
+ (isUnattendedCreate && input.featureValues?.[OPENCODE_AUTO_ACCEPT_FEATURE_ID] === undefined)
64
72
  ? withOpenCodeAutoAcceptFeature(input.featureValues, true)
65
73
  : input.featureValues;
66
74
  if (inheritsUnattended && requestedMode === undefined) {
@@ -124,6 +132,8 @@ function resolveOpenCodePermissionReply(response) {
124
132
  }
125
133
  const MCP_ALREADY_PRESENT_ERROR_TOKENS = ["already", "exists", "connected"];
126
134
  const OPENCODE_PROVIDER_LIST_TIMEOUT_MS = 30000;
135
+ const OPENCODE_METADATA_CONCURRENCY = 4;
136
+ const openCodeMetadataLimit = pLimit(OPENCODE_METADATA_CONCURRENCY);
127
137
  const OPENCODE_HANDLED_BUILTIN_SLASH_COMMANDS = [
128
138
  { name: "compact", description: "Compact the current session", argumentHint: "" },
129
139
  { name: "summarize", description: "Compact the current session", argumentHint: "" },
@@ -918,7 +928,7 @@ export class OpenCodeAgentClient {
918
928
  try {
919
929
  // Background model discovery can be legitimately slow while OpenCode refreshes
920
930
  // provider state, so allow longer than turn execution paths.
921
- const response = await withTimeout(client.provider.list({ directory: options.cwd }), OPENCODE_PROVIDER_LIST_TIMEOUT_MS, `OpenCode provider.list timed out after ${OPENCODE_PROVIDER_LIST_TIMEOUT_MS / 1000}s - server may not be authenticated or connected to any providers`);
931
+ const response = await openCodeMetadataLimit(() => withTimeout(client.provider.list({ directory: options.cwd }), OPENCODE_PROVIDER_LIST_TIMEOUT_MS, `OpenCode provider.list timed out after ${OPENCODE_PROVIDER_LIST_TIMEOUT_MS / 1000}s - server may not be authenticated or connected to any providers`));
922
932
  if (response.error) {
923
933
  throw new Error(`Failed to fetch OpenCode providers: ${JSON.stringify(response.error)}`);
924
934
  }
@@ -964,7 +974,7 @@ export class OpenCodeAgentClient {
964
974
  const directory = options.cwd;
965
975
  const client = this.runtime.createClient({ baseUrl: url, directory });
966
976
  try {
967
- const response = await withTimeout(client.app.agents({ directory }), 10000, "OpenCode app.agents timed out after 10s");
977
+ const response = await openCodeMetadataLimit(() => withTimeout(client.app.agents({ directory }), 10000, "OpenCode app.agents timed out after 10s"));
968
978
  if (response.error || !response.data) {
969
979
  return DEFAULT_MODES;
970
980
  }
@@ -1102,7 +1112,7 @@ export class OpenCodeAgentClient {
1102
1112
  return normalizeOpenCodeConfig({ ...config, provider: "opencode" });
1103
1113
  }
1104
1114
  async populateModelContextWindowCache(client, cwd) {
1105
- const response = await client.provider.list({ directory: cwd });
1115
+ const response = await openCodeMetadataLimit(() => client.provider.list({ directory: cwd }));
1106
1116
  if (response.error || !response.data) {
1107
1117
  return;
1108
1118
  }
@@ -1794,6 +1804,7 @@ function appendOpenCodeQuestionAsked(event, state, events) {
1794
1804
  header: q.header,
1795
1805
  options,
1796
1806
  ...(q.multiple === true ? { multiSelect: true } : {}),
1807
+ allowOther: true,
1797
1808
  },
1798
1809
  ];
1799
1810
  });
@@ -2467,9 +2478,9 @@ class OpenCodeAgentSession {
2467
2478
  if (this.availableModesCache) {
2468
2479
  return this.availableModesCache;
2469
2480
  }
2470
- const response = await this.client.app.agents({
2481
+ const response = await openCodeMetadataLimit(() => this.client.app.agents({
2471
2482
  directory: this.config.cwd,
2472
- });
2483
+ }));
2473
2484
  const agents = response.error || !response.data ? [] : response.data;
2474
2485
  const discoveredModes = agents.filter(isSelectableOpenCodeAgent).map(mapOpenCodeAgentToMode);
2475
2486
  this.availableModesCache = mergeOpenCodeModes(discoveredModes);
@@ -270,7 +270,36 @@ export function selectProjectedTimelinePage(input) {
270
270
  const limit = input.limit === undefined ? 0 : Math.max(0, Math.floor(input.limit));
271
271
  const bounds = input.bounds ?? getTimelineBounds(input.rows);
272
272
  const projectedAll = projectTimelineRows({ rows: input.rows, mode: "projected" });
273
- if (projectedAll.length === 0 || !bounds) {
273
+ if (!bounds) {
274
+ return {
275
+ entries: [],
276
+ startSeq: null,
277
+ endSeq: null,
278
+ hasOlder: false,
279
+ hasNewer: false,
280
+ };
281
+ }
282
+ if (projectedAll.length === 0) {
283
+ if (input.direction === "after") {
284
+ const cursorSeq = input.cursorSeq ?? bounds.minSeq - 1;
285
+ return {
286
+ entries: [],
287
+ startSeq: null,
288
+ endSeq: null,
289
+ hasOlder: cursorSeq >= bounds.minSeq,
290
+ hasNewer: cursorSeq < bounds.maxSeq,
291
+ };
292
+ }
293
+ if (input.direction === "before") {
294
+ const cursorSeq = input.cursorSeq ?? bounds.maxSeq + 1;
295
+ return {
296
+ entries: [],
297
+ startSeq: null,
298
+ endSeq: null,
299
+ hasOlder: cursorSeq > bounds.minSeq,
300
+ hasNewer: cursorSeq <= bounds.maxSeq,
301
+ };
302
+ }
274
303
  return {
275
304
  entries: [],
276
305
  startSeq: null,
@@ -0,0 +1,3 @@
1
+ export declare function writeFileAtomic(filePath: string, data: string | NodeJS.ArrayBufferView): Promise<void>;
2
+ export declare function writeJsonFileAtomic(filePath: string, value: unknown): Promise<void>;
3
+ //# sourceMappingURL=atomic-file.d.ts.map