@getpaseo/server 0.1.99 → 0.1.101

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 (83) hide show
  1. package/dist/server/executable-resolution/windows.js +3 -0
  2. package/dist/server/server/agent/agent-manager.d.ts +10 -0
  3. package/dist/server/server/agent/agent-manager.js +65 -27
  4. package/dist/server/server/agent/agent-sdk-types.d.ts +8 -0
  5. package/dist/server/server/agent/mcp-server.d.ts +2 -45
  6. package/dist/server/server/agent/mcp-server.js +45 -1985
  7. package/dist/server/server/agent/prompt-attachments.js +6 -2
  8. package/dist/server/server/agent/provider-registry.js +1 -0
  9. package/dist/server/server/agent/provider-snapshot-manager.d.ts +4 -0
  10. package/dist/server/server/agent/provider-snapshot-manager.js +58 -13
  11. package/dist/server/server/agent/providers/acp-agent.d.ts +39 -2
  12. package/dist/server/server/agent/providers/acp-agent.js +281 -20
  13. package/dist/server/server/agent/providers/claude/agent.js +96 -62
  14. package/dist/server/server/agent/providers/codex-app-server-agent.js +6 -57
  15. package/dist/server/server/agent/providers/copilot-acp-agent.d.ts +2 -1
  16. package/dist/server/server/agent/providers/copilot-acp-agent.js +10 -0
  17. package/dist/server/server/agent/providers/diagnostic-utils.d.ts +1 -0
  18. package/dist/server/server/agent/providers/diagnostic-utils.js +1 -1
  19. package/dist/server/server/agent/providers/generic-acp-agent.d.ts +3 -0
  20. package/dist/server/server/agent/providers/generic-acp-agent.js +41 -23
  21. package/dist/server/server/agent/providers/mock-load-test-agent.js +4 -2
  22. package/dist/server/server/agent/providers/opencode/server-manager.d.ts +14 -11
  23. package/dist/server/server/agent/providers/opencode/server-manager.js +149 -91
  24. package/dist/server/server/agent/providers/opencode/test-server-manager.d.ts +6 -5
  25. package/dist/server/server/agent/providers/opencode/test-server-manager.js +13 -3
  26. package/dist/server/server/agent/providers/opencode/test-utils/{test-opencode-runtime.d.ts → test-opencode-harness.d.ts} +11 -11
  27. package/dist/server/server/agent/providers/opencode/test-utils/{test-opencode-runtime.js → test-opencode-harness.js} +23 -10
  28. package/dist/server/server/agent/providers/opencode-agent.d.ts +9 -3
  29. package/dist/server/server/agent/providers/opencode-agent.js +26 -38
  30. package/dist/server/server/agent/providers/pi/agent.d.ts +4 -2
  31. package/dist/server/server/agent/providers/pi/agent.js +8 -3
  32. package/dist/server/server/agent/providers/pi/cli-runtime.d.ts +3 -0
  33. package/dist/server/server/agent/providers/pi/cli-runtime.js +6 -3
  34. package/dist/server/server/agent/providers/pi/rpc-types.d.ts +2 -1
  35. package/dist/server/server/agent/providers/provider-image-output.d.ts +5 -0
  36. package/dist/server/server/agent/providers/provider-image-output.js +55 -0
  37. package/dist/server/server/agent/tools/paseo-tools.d.ts +48 -0
  38. package/dist/server/server/agent/tools/paseo-tools.js +2121 -0
  39. package/dist/server/server/agent/tools/types.d.ts +36 -0
  40. package/dist/server/server/agent/tools/types.js +2 -0
  41. package/dist/server/server/bootstrap.js +71 -62
  42. package/dist/server/server/persisted-config.d.ts +5 -0
  43. package/dist/server/server/persisted-config.js +10 -2
  44. package/dist/server/server/session/agent-updates/agent-updates-service.d.ts +59 -0
  45. package/dist/server/server/session/agent-updates/agent-updates-service.js +220 -0
  46. package/dist/server/server/session/checkout/checkout-session.d.ts +13 -15
  47. package/dist/server/server/session/checkout/checkout-session.js +18 -16
  48. package/dist/server/server/session/checkout/git-metadata-generator.d.ts +53 -0
  49. package/dist/server/server/session/checkout/git-metadata-generator.js +159 -0
  50. package/dist/server/server/session/daemon/daemon-session.d.ts +14 -0
  51. package/dist/server/server/session/daemon/daemon-session.js +38 -0
  52. package/dist/server/server/session/daemon/diagnostics.d.ts +41 -0
  53. package/dist/server/server/session/daemon/diagnostics.js +421 -0
  54. package/dist/server/server/session/git-mutation/git-mutation-service.d.ts +34 -0
  55. package/dist/server/server/session/git-mutation/git-mutation-service.js +71 -0
  56. package/dist/server/server/session/workspace-git-observer/workspace-git-observer-service.d.ts +36 -0
  57. package/dist/server/server/session/workspace-git-observer/workspace-git-observer-service.js +134 -0
  58. package/dist/server/server/session/workspace-provisioning/workspace-provisioning-service.d.ts +34 -0
  59. package/dist/server/server/session/workspace-provisioning/workspace-provisioning-service.js +190 -0
  60. package/dist/server/server/session/workspace-scripts/workspace-scripts-service.d.ts +41 -0
  61. package/dist/server/server/session/workspace-scripts/workspace-scripts-service.js +100 -0
  62. package/dist/server/server/session.d.ts +7 -51
  63. package/dist/server/server/session.js +113 -938
  64. package/dist/server/server/speech/providers/openai/config.d.ts +1 -2
  65. package/dist/server/server/speech/providers/openai/config.js +13 -9
  66. package/dist/server/server/speech/providers/openai/runtime.js +2 -16
  67. package/dist/server/server/speech/providers/openai/stt.d.ts +1 -0
  68. package/dist/server/server/speech/providers/openai/stt.js +4 -2
  69. package/dist/server/server/speech/providers/openai/tts.d.ts +1 -0
  70. package/dist/server/server/speech/providers/openai/tts.js +1 -0
  71. package/dist/server/server/websocket/runtime-metrics.d.ts +20 -0
  72. package/dist/server/server/websocket-server.d.ts +1 -2
  73. package/dist/server/server/websocket-server.js +26 -21
  74. package/dist/server/server/worktree-bootstrap.d.ts +1 -1
  75. package/dist/server/server/worktree-branch-name-generator.js +3 -1
  76. package/dist/server/utils/checkout-git.js +51 -26
  77. package/dist/src/executable-resolution/windows.js +3 -0
  78. package/dist/src/server/persisted-config.js +10 -2
  79. package/package.json +5 -5
  80. package/dist/server/server/agent/providers/opencode/runtime.d.ts +0 -28
  81. package/dist/server/server/agent/providers/opencode/runtime.js +0 -5
  82. package/dist/server/server/speech/providers/openai/realtime-transcription-session.d.ts +0 -42
  83. package/dist/server/server/speech/providers/openai/realtime-transcription-session.js +0 -168
@@ -1,5 +1,6 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import fs from "node:fs/promises";
3
+ import { homedir } from "node:os";
3
4
  import path from "node:path";
4
5
  import { Readable, Writable } from "node:stream";
5
6
  import { terminateWithTreeKill } from "../../../utils/tree-kill.js";
@@ -10,6 +11,8 @@ import { checkProviderLaunchAvailable, createProviderEnvSpec, resolveProviderLau
10
11
  import { renderPromptAttachmentAsText } from "../prompt-attachments.js";
11
12
  import { appendOrReplaceGrowingAssistantMessage, runProviderTurn } from "./provider-runner.js";
12
13
  import { platformShell, spawnProcess } from "../../../utils/spawn.js";
14
+ import { toDiagnosticErrorMessage, truncateForDiagnostic, } from "./diagnostic-utils.js";
15
+ import { withTimeout } from "../../../utils/promise-timeout.js";
13
16
  function assertChildWithPipes(child) {
14
17
  if (!child.stdin || !child.stdout || !child.stderr) {
15
18
  throw new Error("Child process did not expose stdio pipes");
@@ -70,6 +73,19 @@ function resolveTerminalCommand(command, args) {
70
73
  const shell = platformShell();
71
74
  return { command: shell.command, args: [...shell.flag, command] };
72
75
  }
76
+ function formatDurationMs(startedAt) {
77
+ return `${Math.max(0, Date.now() - startedAt)}ms`;
78
+ }
79
+ function pushACPStderrRow(rows, stderrChunks) {
80
+ const stderr = stderrChunks.join("").trim();
81
+ if (!stderr) {
82
+ return;
83
+ }
84
+ rows.push({
85
+ label: "ACP stderr",
86
+ value: truncateForDiagnostic(stderr),
87
+ });
88
+ }
73
89
  export const DEFAULT_ACP_CAPABILITIES = {
74
90
  supportsStreaming: true,
75
91
  supportsSessionPersistence: true,
@@ -96,6 +112,8 @@ const ACP_CLIENT_CAPABILITIES = {
96
112
  // sign-in URL in the browser) when probing an ACP agent for models/modes.
97
113
  // NO_BROWSER is honored by Gemini CLI; other ACP agents ignore it.
98
114
  const PROBE_ENV = { NO_BROWSER: "true" };
115
+ const ACP_CATALOG_TIMEOUT_MS = 60000;
116
+ const ACP_DIAGNOSTIC_PHASE_TIMEOUT_MS = 20000;
99
117
  function summarizeMalformedACPStdoutError(error) {
100
118
  return {
101
119
  type: error instanceof Error ? error.name : typeof error,
@@ -257,6 +275,26 @@ export function deriveModelDefinitionsFromACP(provider, models, configOptions) {
257
275
  metadata: option.metadata,
258
276
  }));
259
277
  }
278
+ export function deriveFeaturesFromACP(configOptions, featureOptions) {
279
+ return featureOptions.flatMap((featureOption) => {
280
+ const option = findSelectConfigFeatureOption(configOptions, featureOption);
281
+ if (!option) {
282
+ return [];
283
+ }
284
+ return [
285
+ {
286
+ type: "select",
287
+ id: featureOption.id,
288
+ label: featureOption.label,
289
+ description: featureOption.description,
290
+ tooltip: featureOption.tooltip,
291
+ icon: featureOption.icon,
292
+ value: option.currentValue ?? null,
293
+ options: deriveConfigFeatureSelectOptions(option, featureOption),
294
+ },
295
+ ];
296
+ });
297
+ }
260
298
  export class ACPAgentClient {
261
299
  constructor(options) {
262
300
  this.provider = options.provider;
@@ -272,6 +310,7 @@ export class ACPAgentClient {
272
310
  this.modelTransformer = options.modelTransformer;
273
311
  this.sessionResponseTransformer = options.sessionResponseTransformer;
274
312
  this.configOptionsTransformer = options.configOptionsTransformer;
313
+ this.configFeatureOptions = options.configFeatureOptions ?? [];
275
314
  this.modeIdTransformer = options.modeIdTransformer;
276
315
  this.toolSnapshotTransformer = options.toolSnapshotTransformer;
277
316
  this.providerModeWriter = options.providerModeWriter;
@@ -291,6 +330,7 @@ export class ACPAgentClient {
291
330
  modelTransformer: this.modelTransformer,
292
331
  sessionResponseTransformer: this.sessionResponseTransformer,
293
332
  configOptionsTransformer: this.configOptionsTransformer,
333
+ configFeatureOptions: this.configFeatureOptions,
294
334
  modeIdTransformer: this.modeIdTransformer,
295
335
  toolSnapshotTransformer: this.toolSnapshotTransformer,
296
336
  providerModeWriter: this.providerModeWriter,
@@ -329,6 +369,7 @@ export class ACPAgentClient {
329
369
  modelTransformer: this.modelTransformer,
330
370
  sessionResponseTransformer: this.sessionResponseTransformer,
331
371
  configOptionsTransformer: this.configOptionsTransformer,
372
+ configFeatureOptions: this.configFeatureOptions,
332
373
  modeIdTransformer: this.modeIdTransformer,
333
374
  toolSnapshotTransformer: this.toolSnapshotTransformer,
334
375
  providerModeWriter: this.providerModeWriter,
@@ -346,19 +387,50 @@ export class ACPAgentClient {
346
387
  }
347
388
  async fetchCatalog(options) {
348
389
  const { cwd } = options;
390
+ const timeoutMs = options.timeoutMs ?? ACP_CATALOG_TIMEOUT_MS;
391
+ let probe = null;
392
+ try {
393
+ const catalogProbe = (async () => {
394
+ const initializedProbe = await this.spawnProcess(PROBE_ENV, {
395
+ initializeTimeoutMs: timeoutMs,
396
+ onSpawned: (spawned) => {
397
+ probe = spawned;
398
+ },
399
+ });
400
+ probe = initializedProbe;
401
+ const response = await this.runACPRequest(() => initializedProbe.connection.newSession({
402
+ cwd,
403
+ mcpServers: [],
404
+ }));
405
+ const transformed = this.transformSessionResponse(response);
406
+ const models = deriveModelDefinitionsFromACP(this.provider, transformed.models, transformed.configOptions);
407
+ const modeInfo = deriveModesFromACP(this.defaultModes, transformed.modes, transformed.configOptions);
408
+ return {
409
+ models: this.modelTransformer ? this.modelTransformer(models) : models,
410
+ modes: modeInfo.modes,
411
+ };
412
+ })();
413
+ return await withTimeout(catalogProbe, timeoutMs, `ACP catalog probe timed out after ${timeoutMs}ms`);
414
+ }
415
+ finally {
416
+ if (probe) {
417
+ await this.closeProbe(probe);
418
+ }
419
+ }
420
+ }
421
+ async listFeatures(config) {
422
+ if (this.configFeatureOptions.length === 0) {
423
+ return [];
424
+ }
425
+ this.assertProvider(config);
349
426
  const probe = await this.spawnProcess(PROBE_ENV);
350
427
  try {
351
428
  const response = await this.runACPRequest(() => probe.connection.newSession({
352
- cwd,
429
+ cwd: config.cwd,
353
430
  mcpServers: [],
354
431
  }));
355
432
  const transformed = this.transformSessionResponse(response);
356
- const models = deriveModelDefinitionsFromACP(this.provider, transformed.models, transformed.configOptions);
357
- const modeInfo = deriveModesFromACP(this.defaultModes, transformed.modes, transformed.configOptions);
358
- return {
359
- models: this.modelTransformer ? this.modelTransformer(models) : models,
360
- modes: modeInfo.modes,
361
- };
433
+ return deriveFeaturesFromACP(transformed.configOptions, this.configFeatureOptions);
362
434
  }
363
435
  finally {
364
436
  await this.closeProbe(probe);
@@ -420,6 +492,28 @@ export class ACPAgentClient {
420
492
  }
421
493
  }
422
494
  async spawnProcess(launchEnv, options) {
495
+ const transport = await this.spawnTransport(launchEnv);
496
+ const probe = {
497
+ child: transport.child,
498
+ connection: transport.connection,
499
+ stderrChunks: transport.stderrChunks,
500
+ };
501
+ options?.onSpawned?.(probe);
502
+ try {
503
+ const initialize = await this.initializeTransport(transport, options?.initializeTimeoutMs);
504
+ const initializedProbe = {
505
+ ...probe,
506
+ initialize,
507
+ };
508
+ probe.initialize = initialize;
509
+ return initializedProbe;
510
+ }
511
+ catch (error) {
512
+ await terminateChildProcess(transport.child, 2000, this.terminateProcess);
513
+ throw error;
514
+ }
515
+ }
516
+ async spawnTransport(launchEnv) {
423
517
  const { command, args } = await this.resolveLaunchCommand();
424
518
  const child = spawnProcess(command, args, {
425
519
  cwd: process.cwd(),
@@ -440,38 +534,46 @@ export class ACPAgentClient {
440
534
  reject(new Error(stderr ? `${String(error)}\n${stderr}` : String(error)));
441
535
  });
442
536
  });
537
+ const spawnReadyPromise = new Promise((resolve) => {
538
+ child.once("spawn", () => {
539
+ resolve();
540
+ });
541
+ });
443
542
  const stream = createLoggedNdJsonStream(Writable.toWeb(child.stdin), Readable.toWeb(child.stdout), { logger: this.logger, provider: this.provider });
444
543
  const connection = new ClientSideConnection(() => this.buildProbeClient(), stream);
544
+ return {
545
+ child,
546
+ connection,
547
+ stderrChunks,
548
+ spawnReady: spawnReadyPromise,
549
+ spawnError: spawnErrorPromise,
550
+ };
551
+ }
552
+ async initializeTransport(transport, initializeTimeoutMs) {
445
553
  let timeout = null;
446
- const initializeTimeoutPromise = options?.initializeTimeoutMs
554
+ const initializeTimeoutPromise = initializeTimeoutMs
447
555
  ? new Promise((_, reject) => {
448
556
  timeout = setTimeout(() => {
449
- reject(new Error(`ACP initialize timed out after ${options.initializeTimeoutMs}ms`));
450
- }, options.initializeTimeoutMs);
557
+ reject(new Error(`ACP initialize timed out after ${initializeTimeoutMs}ms`));
558
+ }, initializeTimeoutMs);
451
559
  })
452
560
  : null;
453
- let initialize;
454
561
  try {
455
- initialize = await this.runACPRequest(() => Promise.race([
456
- connection.initialize({
562
+ return await this.runACPRequest(() => Promise.race([
563
+ transport.connection.initialize({
457
564
  protocolVersion: PROTOCOL_VERSION,
458
565
  clientCapabilities: ACP_CLIENT_CAPABILITIES,
459
566
  clientInfo: { name: "Paseo", version: "dev" },
460
567
  }),
461
- spawnErrorPromise,
568
+ transport.spawnError,
462
569
  ...(initializeTimeoutPromise ? [initializeTimeoutPromise] : []),
463
570
  ]));
464
571
  }
465
- catch (error) {
466
- await terminateChildProcess(child, 2000, this.terminateProcess);
467
- throw error;
468
- }
469
572
  finally {
470
573
  if (timeout) {
471
574
  clearTimeout(timeout);
472
575
  }
473
576
  }
474
- return { child, connection, initialize };
475
577
  }
476
578
  buildProbeClient() {
477
579
  return {
@@ -495,7 +597,7 @@ export class ACPAgentClient {
495
597
  }
496
598
  async closeProbe(probe) {
497
599
  try {
498
- if (probe.initialize.agentCapabilities?.sessionCapabilities?.close) {
600
+ if (probe.initialize?.agentCapabilities?.sessionCapabilities?.close) {
499
601
  // No active session to close here; ignore capability.
500
602
  }
501
603
  }
@@ -511,6 +613,89 @@ export class ACPAgentClient {
511
613
  throw toACPRequestError(error);
512
614
  }
513
615
  }
616
+ async buildACPProbeDiagnosticRows(options = {}) {
617
+ const rows = [];
618
+ const phaseTimeoutMs = options.phaseTimeoutMs ?? ACP_DIAGNOSTIC_PHASE_TIMEOUT_MS;
619
+ const cwd = options.cwd ?? homedir();
620
+ let transport = null;
621
+ try {
622
+ const spawnStartedAt = Date.now();
623
+ try {
624
+ transport = await this.spawnTransport(PROBE_ENV);
625
+ await withTimeout(Promise.race([transport.spawnReady, transport.spawnError]), phaseTimeoutMs, `ACP spawn timed out after ${phaseTimeoutMs}ms`);
626
+ rows.push({
627
+ label: "ACP spawn",
628
+ value: `ok (${formatDurationMs(spawnStartedAt)})`,
629
+ });
630
+ }
631
+ catch (error) {
632
+ rows.push({
633
+ label: "ACP spawn",
634
+ value: `error: ${toDiagnosticErrorMessage(error)}`,
635
+ });
636
+ return rows;
637
+ }
638
+ const activeTransport = transport;
639
+ const initializeStartedAt = Date.now();
640
+ try {
641
+ await this.initializeTransport(activeTransport, phaseTimeoutMs);
642
+ rows.push({
643
+ label: "ACP initialize",
644
+ value: `ok (${formatDurationMs(initializeStartedAt)})`,
645
+ });
646
+ }
647
+ catch (error) {
648
+ rows.push({
649
+ label: "ACP initialize",
650
+ value: `error: ${toDiagnosticErrorMessage(error)}`,
651
+ });
652
+ pushACPStderrRow(rows, activeTransport.stderrChunks);
653
+ return rows;
654
+ }
655
+ const sessionStartedAt = Date.now();
656
+ try {
657
+ const response = await withTimeout(this.runACPRequest(() => activeTransport.connection.newSession({
658
+ cwd,
659
+ mcpServers: [],
660
+ })), phaseTimeoutMs, `ACP session/new timed out after ${phaseTimeoutMs}ms`);
661
+ const transformed = this.transformSessionResponse(response);
662
+ const models = deriveModelDefinitionsFromACP(this.provider, transformed.models, transformed.configOptions);
663
+ const modeInfo = deriveModesFromACP(this.defaultModes, transformed.modes, transformed.configOptions);
664
+ rows.push({
665
+ label: "ACP session/new",
666
+ value: `ok (${formatDurationMs(sessionStartedAt)}; models=${models.length}; modes=${modeInfo.modes.length})`,
667
+ });
668
+ }
669
+ catch (error) {
670
+ rows.push({
671
+ label: "ACP session/new",
672
+ value: `error: ${toDiagnosticErrorMessage(error)}`,
673
+ });
674
+ pushACPStderrRow(rows, activeTransport.stderrChunks);
675
+ return rows;
676
+ }
677
+ pushACPStderrRow(rows, activeTransport.stderrChunks);
678
+ return rows;
679
+ }
680
+ finally {
681
+ if (transport) {
682
+ const cleanupStartedAt = Date.now();
683
+ try {
684
+ await terminateChildProcess(transport.child, 2000, this.terminateProcess);
685
+ rows.push({
686
+ label: "ACP cleanup",
687
+ value: `ok (${formatDurationMs(cleanupStartedAt)})`,
688
+ });
689
+ }
690
+ catch (error) {
691
+ rows.push({
692
+ label: "ACP cleanup",
693
+ value: `error: ${toDiagnosticErrorMessage(error)}`,
694
+ });
695
+ }
696
+ }
697
+ }
698
+ }
514
699
  async resolveLaunchCommand() {
515
700
  const prefix = await resolveProviderLaunch({
516
701
  commandConfig: this.runtimeSettings?.command,
@@ -582,6 +767,7 @@ export class ACPAgentSession {
582
767
  this.modelTransformer = options.modelTransformer;
583
768
  this.sessionResponseTransformer = options.sessionResponseTransformer;
584
769
  this.configOptionsTransformer = options.configOptionsTransformer;
770
+ this.configFeatureOptions = options.configFeatureOptions ?? [];
585
771
  this.modeIdTransformer = options.modeIdTransformer;
586
772
  this.toolSnapshotTransformer = options.toolSnapshotTransformer;
587
773
  this.providerModeWriter = options.providerModeWriter;
@@ -739,6 +925,9 @@ export class ACPAgentSession {
739
925
  async getCurrentMode() {
740
926
  return this.currentMode;
741
927
  }
928
+ get features() {
929
+ return deriveFeaturesFromACP(this.configOptions, this.configFeatureOptions);
930
+ }
742
931
  ensureCommandsReadyDeferred() {
743
932
  if (this.commandsReadyDeferred || this.commandsReadySettled || this.cachedCommands.length > 0) {
744
933
  return;
@@ -1015,6 +1204,37 @@ export class ACPAgentSession {
1015
1204
  thinkingOptionId: this.thinkingOptionId,
1016
1205
  });
1017
1206
  }
1207
+ async setFeature(featureId, value) {
1208
+ if (!this.connection || !this.sessionId) {
1209
+ throw new Error("ACP session not initialized");
1210
+ }
1211
+ const featureOption = this.configFeatureOptions.find((option) => option.id === featureId);
1212
+ if (!featureOption) {
1213
+ throw new Error(`Unknown ${this.provider} feature: ${featureId}`);
1214
+ }
1215
+ const option = findSelectConfigFeatureOption(this.configOptions, featureOption);
1216
+ if (!option) {
1217
+ throw new Error(`${this.provider} does not expose ACP feature '${featureId}'`);
1218
+ }
1219
+ const requestedValue = normalizeConfigFeatureValue(value);
1220
+ const choice = findSelectConfigChoice({ option, value: requestedValue });
1221
+ if (!choice) {
1222
+ throw new Error(`${this.provider} feature '${featureId}' does not include option '${requestedValue}'`);
1223
+ }
1224
+ const response = await this.connection.setSessionConfigOption({
1225
+ sessionId: this.sessionId,
1226
+ configId: option.id,
1227
+ value: requestedValue,
1228
+ });
1229
+ const currentValue = this.applyConfigOptionResponse({
1230
+ response,
1231
+ configId: option.id,
1232
+ category: featureOption.category,
1233
+ requestedValue,
1234
+ label: featureOption.label,
1235
+ });
1236
+ this.config.featureValues = { ...this.config.featureValues, [featureId]: currentValue };
1237
+ }
1018
1238
  applyConfigOptionResponse({ response, configId, category, requestedValue, label, }) {
1019
1239
  this.configOptions = this.transformConfigOptions(response.configOptions);
1020
1240
  const responseOption = findSelectConfigOption({
@@ -1386,6 +1606,13 @@ export class ACPAgentSession {
1386
1606
  if (this.config.thinkingOptionId && this.config.thinkingOptionId !== this.thinkingOptionId) {
1387
1607
  await this.setThinkingOption(this.config.thinkingOptionId);
1388
1608
  }
1609
+ const configuredFeatureValues = this.config.featureValues ?? {};
1610
+ for (const featureOption of this.configFeatureOptions) {
1611
+ if (!Object.prototype.hasOwnProperty.call(configuredFeatureValues, featureOption.id)) {
1612
+ continue;
1613
+ }
1614
+ await this.setFeature(featureOption.id, configuredFeatureValues[featureOption.id]);
1615
+ }
1389
1616
  }
1390
1617
  warnInvalidSelection(value, message) {
1391
1618
  this.logger.warn({ value }, message);
@@ -1671,6 +1898,12 @@ function findSelectConfigOption({ configOptions, category, id, }) {
1671
1898
  const option = configOptions?.find((entry) => entry.type === "select" && entry.category === category && (!id || entry.id === id));
1672
1899
  return option ?? null;
1673
1900
  }
1901
+ function findSelectConfigFeatureOption(configOptions, featureOption) {
1902
+ const option = configOptions?.find((entry) => entry.type === "select" &&
1903
+ entry.id === featureOption.configId &&
1904
+ entry.category === featureOption.category);
1905
+ return option ?? null;
1906
+ }
1674
1907
  function findSelectConfigChoice({ option, value, }) {
1675
1908
  if (!option) {
1676
1909
  return null;
@@ -1690,6 +1923,34 @@ function flattenSelectOptions(options) {
1690
1923
  }
1691
1924
  return flattened;
1692
1925
  }
1926
+ function deriveConfigFeatureSelectOptions(option, featureOption) {
1927
+ return flattenSelectOptions(option.options).map((choice) => ({
1928
+ id: choice.value,
1929
+ label: normalizeConfigFeatureOptionLabel(choice, featureOption),
1930
+ description: choice.description ?? undefined,
1931
+ isDefault: choice.value === option.currentValue,
1932
+ metadata: choice.group ? { group: choice.group } : undefined,
1933
+ }));
1934
+ }
1935
+ function normalizeConfigFeatureOptionLabel(choice, featureOption) {
1936
+ const name = choice.name.trim();
1937
+ if (name) {
1938
+ return name;
1939
+ }
1940
+ if (choice.value === "" && featureOption.emptyOptionLabel) {
1941
+ return featureOption.emptyOptionLabel;
1942
+ }
1943
+ return choice.value;
1944
+ }
1945
+ function normalizeConfigFeatureValue(value) {
1946
+ if (typeof value === "string") {
1947
+ return value;
1948
+ }
1949
+ if (value === null) {
1950
+ return "";
1951
+ }
1952
+ throw new Error(`ACP feature value must be a string`);
1953
+ }
1693
1954
  function deriveSelectorOptions(configOptions, category) {
1694
1955
  const option = findSelectConfigOption({ configOptions, category });
1695
1956
  if (!option) {