@promptbook/cli 0.113.0-5 → 0.113.0-8

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 (123) hide show
  1. package/apps/agents-server/src/app/admin/update/AdvancedOriginRepositoryPanel.tsx +76 -0
  2. package/apps/agents-server/src/app/admin/update/CurrentDeploymentCard.tsx +160 -0
  3. package/apps/agents-server/src/app/admin/update/PendingCommitsCard.tsx +86 -0
  4. package/apps/agents-server/src/app/admin/update/TargetEnvironmentCard.tsx +331 -0
  5. package/apps/agents-server/src/app/admin/update/UpdateClient.tsx +20 -873
  6. package/apps/agents-server/src/app/admin/update/UpdateJobCard.tsx +329 -0
  7. package/apps/agents-server/src/app/admin/update/UpdateOverview.ts +74 -0
  8. package/apps/agents-server/src/app/admin/update/buildDeploymentTimeBehindLabel.ts +29 -0
  9. package/apps/agents-server/src/app/admin/update/formatHumanReadableTimestamp.ts +18 -0
  10. package/apps/agents-server/src/app/admin/update/getUpdateJobFailureMessage.ts +13 -0
  11. package/apps/agents-server/src/app/admin/update/getUpdateJobSuccessMessage.ts +13 -0
  12. package/apps/agents-server/src/app/admin/update/useUpdateClientState.ts +441 -0
  13. package/apps/agents-server/src/app/agents/[agentName]/api/book/route.ts +13 -1
  14. package/apps/agents-server/src/app/agents/[agentName]/export-as-transpiled-code/AgentCodePageClient.tsx +14 -559
  15. package/apps/agents-server/src/app/agents/[agentName]/export-as-transpiled-code/AgentCodePageHeader.tsx +84 -0
  16. package/apps/agents-server/src/app/agents/[agentName]/export-as-transpiled-code/AgentCodePageSection.tsx +52 -0
  17. package/apps/agents-server/src/app/agents/[agentName]/export-as-transpiled-code/GeneratedCodePreview.tsx +138 -0
  18. package/apps/agents-server/src/app/agents/[agentName]/export-as-transpiled-code/GeneratedHarnessSection.tsx +98 -0
  19. package/apps/agents-server/src/app/agents/[agentName]/export-as-transpiled-code/SourceBookSection.tsx +66 -0
  20. package/apps/agents-server/src/app/agents/[agentName]/export-as-transpiled-code/TranspiledCodeErrorBanner.tsx +30 -0
  21. package/apps/agents-server/src/app/agents/[agentName]/export-as-transpiled-code/TranspiledCodeExportWarningBanner.tsx +62 -0
  22. package/apps/agents-server/src/app/agents/[agentName]/export-as-transpiled-code/Transpiler.ts +9 -0
  23. package/apps/agents-server/src/app/agents/[agentName]/export-as-transpiled-code/useAgentCodeExportState.ts +478 -0
  24. package/apps/agents-server/src/app/system/utilities/mocked-chats/cloneMockedChatPreset.ts +17 -0
  25. package/apps/agents-server/src/app/system/utilities/mocked-chats/confirmDeleteMockedChat.ts +16 -0
  26. package/apps/agents-server/src/app/system/utilities/mocked-chats/createDraftWithUpdatedMessage.ts +81 -0
  27. package/apps/agents-server/src/app/system/utilities/mocked-chats/createDraftWithUpdatedParticipant.ts +146 -0
  28. package/apps/agents-server/src/app/system/utilities/mocked-chats/createDraftWithUpdatedSettings.ts +19 -0
  29. package/apps/agents-server/src/app/system/utilities/mocked-chats/createDuplicatedDraft.ts +66 -0
  30. package/apps/agents-server/src/app/system/utilities/mocked-chats/findMockedChatById.ts +51 -0
  31. package/apps/agents-server/src/app/system/utilities/mocked-chats/persistMockedChats.ts +51 -0
  32. package/apps/agents-server/src/app/system/utilities/mocked-chats/useMockedChatsEditorState.ts +16 -426
  33. package/apps/agents-server/src/components/Homepage/buildFreeGraphBoxLayout.ts +446 -0
  34. package/apps/agents-server/src/components/Homepage/buildGraphLayoutNodes.ts +415 -157
  35. package/apps/agents-server/src/components/Homepage/useAgentsGraphCanvasState.ts +2 -1
  36. package/apps/agents-server/src/database/sqlite/$provideLocalSqliteSupabase.ts +3 -1541
  37. package/apps/agents-server/src/database/sqlite/localSqliteSupabase/LocalSqliteQueryBuilder.ts +623 -0
  38. package/apps/agents-server/src/database/sqlite/localSqliteSupabase/LocalSqliteSupabaseClient.ts +64 -0
  39. package/apps/agents-server/src/database/sqlite/localSqliteSupabase/ensureTable.ts +93 -0
  40. package/apps/agents-server/src/database/sqlite/localSqliteSupabase/localSqliteFilters.ts +202 -0
  41. package/apps/agents-server/src/database/sqlite/localSqliteSupabase/localSqliteMutationPayload.ts +131 -0
  42. package/apps/agents-server/src/database/sqlite/localSqliteSupabase/localSqliteRowOperations.ts +102 -0
  43. package/apps/agents-server/src/database/sqlite/localSqliteSupabase/localSqliteSql.ts +59 -0
  44. package/apps/agents-server/src/database/sqlite/localSqliteSupabase/localSqliteTableSchema.ts +329 -0
  45. package/apps/agents-server/src/database/sqlite/localSqliteSupabase/localSqliteTypes.ts +84 -0
  46. package/apps/agents-server/src/database/sqlite/localSqliteSupabase/localSqliteValueCodec.ts +63 -0
  47. package/apps/agents-server/src/database/sqlite/localSqliteSupabase/normalizeSqliteError.ts +22 -0
  48. package/apps/agents-server/src/search/createDefaultServerSearchProviders/loadLocalOrganizationSearchDataset.ts +12 -1
  49. package/apps/agents-server/src/tools/send_email.ts +1 -2
  50. package/apps/agents-server/src/utils/agentOrganization/loadAgentOrganizationState.ts +13 -1
  51. package/apps/agents-server/src/utils/backup/createServerBackupZipStream.ts +7 -1382
  52. package/apps/agents-server/src/utils/backup/serverBackup/appendAgentBackupEntriesToZip.ts +27 -0
  53. package/apps/agents-server/src/utils/backup/serverBackup/appendConversationBackupEntriesToZip.ts +140 -0
  54. package/apps/agents-server/src/utils/backup/serverBackup/appendFileBackupEntriesToZip.ts +147 -0
  55. package/apps/agents-server/src/utils/backup/serverBackup/appendMessageBackupEntriesToZip.ts +65 -0
  56. package/apps/agents-server/src/utils/backup/serverBackup/appendMetadataBackupEntriesToZip.ts +39 -0
  57. package/apps/agents-server/src/utils/backup/serverBackup/appendSectionEntriesToZip.ts +92 -0
  58. package/apps/agents-server/src/utils/backup/serverBackup/appendUserBackupEntriesToZip.ts +80 -0
  59. package/apps/agents-server/src/utils/backup/serverBackup/createRedactedWalletBackupRecord.ts +34 -0
  60. package/apps/agents-server/src/utils/backup/serverBackup/createServerBackupManifest.ts +82 -0
  61. package/apps/agents-server/src/utils/backup/serverBackup/downloadBackupBinaryContent.ts +39 -0
  62. package/apps/agents-server/src/utils/backup/serverBackup/resolveFeedbackThreadMessages.ts +53 -0
  63. package/apps/agents-server/src/utils/backup/serverBackup/serverBackupAttachments.ts +151 -0
  64. package/apps/agents-server/src/utils/backup/serverBackup/serverBackupContext.ts +167 -0
  65. package/apps/agents-server/src/utils/backup/serverBackup/serverBackupFilenames.ts +161 -0
  66. package/apps/agents-server/src/utils/backup/serverBackup/serverBackupPreviews.ts +97 -0
  67. package/apps/agents-server/src/utils/backup/serverBackup/serverBackupRowUtilities.ts +158 -0
  68. package/apps/agents-server/src/utils/backup/serverBackup/serverBackupTypes.ts +59 -0
  69. package/apps/agents-server/src/utils/createLocalAgentSourceImporter.ts +159 -0
  70. package/apps/agents-server/src/utils/createMissingImportedAgentFallback.ts +60 -0
  71. package/apps/agents-server/src/utils/customDomainRouting.ts +157 -12
  72. package/apps/agents-server/src/utils/externalChatRunner/processExternalUserChatJob.ts +1 -1
  73. package/apps/agents-server/src/utils/importAgentWithFallback.ts +1 -58
  74. package/apps/agents-server/src/utils/localAgentRouteReferences.ts +167 -0
  75. package/apps/agents-server/src/utils/localChatRunner/processLocalUserChatJob.ts +1 -1
  76. package/apps/agents-server/src/utils/managementApi/managementApiAgents.ts +17 -3
  77. package/apps/agents-server/src/utils/messages/sendMessage.ts +5 -3
  78. package/apps/agents-server/src/utils/resolveAgentStateFromSource.ts +7 -1
  79. package/apps/agents-server/src/utils/resolveInheritedAgentSource.ts +54 -5
  80. package/apps/agents-server/src/utils/resolveServerAgentContext.ts +12 -1
  81. package/apps/agents-server/src/utils/resolveStoredAgentState.ts +2 -1
  82. package/apps/agents-server/src/utils/speech-to-text/SpeechToTextFailoverRecognition/SpeechToTextFailoverRecognitionProviderRuntime.ts +0 -1
  83. package/apps/agents-server/src/utils/speech-to-text/SpeechToTextFailoverRecognition.ts +34 -2
  84. package/apps/agents-server/src/utils/userChat/userChatMessageLifecycle.ts +39 -9
  85. package/apps/agents-server/tests/e2e/support/ChatHistoryNavigationSupport.ts +5 -0
  86. package/esm/index.es.js +96 -57
  87. package/esm/index.es.js.map +1 -1
  88. package/esm/scripts/find-refactor-candidates/find-refactor-candidates.d.ts +7 -0
  89. package/esm/scripts/find-refactor-candidates/selectMostImportantRefactorCandidates.d.ts +12 -0
  90. package/esm/src/book-components/Chat/Chat/insertDictationChunk.d.ts +1 -1
  91. package/esm/src/book-components/Chat/Chat/insertDictationChunk.test.d.ts +1 -0
  92. package/esm/src/book-components/Chat/Chat/learnDictationDictionary.test.d.ts +1 -0
  93. package/esm/src/book-components/Chat/Chat/refineFinalDictationChunk.test.d.ts +1 -0
  94. package/esm/src/cli/cli-commands/common/createPositiveIntegerOptionParser.d.ts +10 -0
  95. package/esm/src/version.d.ts +1 -1
  96. package/package.json +1 -1
  97. package/src/avatars/avatarAnimationScheduler.ts +2 -1
  98. package/src/avatars/visuals/octopus3d3AvatarVisual.ts +6 -2
  99. package/src/avatars/visuals/octopus3d4AvatarVisual.ts +14 -18
  100. package/src/book-components/Chat/Chat/insertDictationChunk.ts +3 -3
  101. package/src/book-components/Chat/Chat/learnDictationDictionary.ts +1 -1
  102. package/src/book-components/Chat/Chat/useChatInputAreaDictation.ts +133 -26
  103. package/src/cli/cli-commands/agents-server/buildAgentsServer.ts +3 -1
  104. package/src/cli/cli-commands/coder/boilerplateTemplates.ts +14 -21
  105. package/src/cli/cli-commands/coder/find-refactor-candidates.ts +9 -2
  106. package/src/cli/cli-commands/coder/initializeCoderProjectConfiguration.ts +1 -4
  107. package/src/cli/cli-commands/coder/run.ts +3 -27
  108. package/src/cli/cli-commands/common/createPositiveIntegerOptionParser.ts +31 -0
  109. package/src/commitments/TEAM/TEAM.ts +5 -5
  110. package/src/other/templates/getTemplatesPipelineCollection.ts +727 -781
  111. package/src/utils/ascii-art/convertImageDataToAsciiArt.ts +1 -4
  112. package/src/version.ts +2 -2
  113. package/src/versions.txt +1 -0
  114. package/umd/index.umd.js +96 -57
  115. package/umd/index.umd.js.map +1 -1
  116. package/umd/scripts/find-refactor-candidates/find-refactor-candidates.d.ts +7 -0
  117. package/umd/scripts/find-refactor-candidates/selectMostImportantRefactorCandidates.d.ts +12 -0
  118. package/umd/src/book-components/Chat/Chat/insertDictationChunk.d.ts +1 -1
  119. package/umd/src/book-components/Chat/Chat/insertDictationChunk.test.d.ts +1 -0
  120. package/umd/src/book-components/Chat/Chat/learnDictationDictionary.test.d.ts +1 -0
  121. package/umd/src/book-components/Chat/Chat/refineFinalDictationChunk.test.d.ts +1 -0
  122. package/umd/src/cli/cli-commands/common/createPositiveIntegerOptionParser.d.ts +10 -0
  123. package/umd/src/version.d.ts +1 -1
@@ -15,4 +15,11 @@ export type FindRefactorCandidatesOptions = {
15
15
  * Aggressiveness level used to score candidate files.
16
16
  */
17
17
  readonly level?: RefactorCandidateLevel;
18
+ /**
19
+ * Maximum number of refactor prompts to create in this run.
20
+ *
21
+ * When more candidates are found than this limit, only the most important ones (by severity)
22
+ * are turned into prompts. When omitted, a prompt is created for every candidate.
23
+ */
24
+ readonly limit?: number;
18
25
  };
@@ -0,0 +1,12 @@
1
+ import type { RefactorCandidate } from './RefactorCandidate';
2
+ /**
3
+ * Keeps at most `limit` refactor candidates, preferring the most important ones.
4
+ *
5
+ * When there are more candidates than `limit`, only the highest-severity candidates are kept
6
+ * (ties broken by relative path for determinism). When `limit` is `undefined` or there are fewer
7
+ * candidates than the limit, every candidate is returned. The original input order is preserved
8
+ * among the kept candidates, so `limit` acts purely as a filter and never reorders prompts.
9
+ *
10
+ * @private function of findRefactorCandidates
11
+ */
12
+ export declare function selectMostImportantRefactorCandidates(candidates: ReadonlyArray<RefactorCandidate>, limit: number | undefined): ReadonlyArray<RefactorCandidate>;
@@ -8,7 +8,7 @@ export declare function insertDictationChunk(params: {
8
8
  readonly dictatedText: string;
9
9
  readonly selectionStart: number;
10
10
  readonly selectionEnd: number;
11
- readonly shouldReplaceSelection: boolean;
11
+ readonly isReplacingSelection: boolean;
12
12
  }): {
13
13
  nextValue: string;
14
14
  start: number;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Creates a Commander argument parser that accepts only positive integers.
3
+ *
4
+ * The returned parser is meant to be passed as the coercion callback of `command.option(...)`.
5
+ * It throws a branded `NotAllowed` error with a clear message referencing the given option name
6
+ * when the provided value is not a positive integer.
7
+ *
8
+ * @private internal utility of `promptbookCli`
9
+ */
10
+ export declare function createPositiveIntegerOptionParser(optionName: string): (value: string) => number;
@@ -15,7 +15,7 @@ export declare const BOOK_LANGUAGE_VERSION: string_semantic_version;
15
15
  export declare const PROMPTBOOK_ENGINE_VERSION: string_promptbook_version;
16
16
  /**
17
17
  * Represents the version string of the Promptbook engine.
18
- * It follows semantic versioning (e.g., `0.113.0-4`).
18
+ * It follows semantic versioning (e.g., `0.113.0-5`).
19
19
  *
20
20
  * @generated
21
21
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@promptbook/cli",
3
- "version": "0.113.0-5",
3
+ "version": "0.113.0-8",
4
4
  "description": "Promptbook: Create persistent AI agents that turn your company's scattered knowledge into action",
5
5
  "private": false,
6
6
  "sideEffects": false,
@@ -133,7 +133,8 @@ function ensureAvatarAnimationLoop(): void {
133
133
  // Run the most stale listeners first so each avatar keeps its target FPS even when
134
134
  // total work exceeds the per-frame budget.
135
135
  dueListenerEntries.sort(
136
- (firstListenerEntry, secondListenerEntry) => firstListenerEntry.lastRunAtMs - secondListenerEntry.lastRunAtMs,
136
+ (firstListenerEntry, secondListenerEntry) =>
137
+ firstListenerEntry.lastRunAtMs - secondListenerEntry.lastRunAtMs,
137
138
  );
138
139
 
139
140
  const listenersToRunCount = Math.min(dueListenerEntries.length, MAX_AVATAR_LISTENERS_PER_FRAME);
@@ -502,12 +502,16 @@ function resolveVisibleContinuousOctopusPatches(
502
502
  }
503
503
 
504
504
  for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
505
- const patchCenterLongitude = (longitudeBoundaries[longitudeIndex]! + longitudeBoundaries[longitudeIndex + 1]!) / 2;
505
+ const patchCenterLongitude =
506
+ (longitudeBoundaries[longitudeIndex]! + longitudeBoundaries[longitudeIndex + 1]!) / 2;
506
507
  cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex] = resolveContinuousTentacleInfluence(
507
508
  options,
508
509
  patchCenterLongitude,
509
510
  );
510
- cachedLobeWavesByPatchCenterLongitude[longitudeIndex] = resolveContinuousLobeWave(options, patchCenterLongitude);
511
+ cachedLobeWavesByPatchCenterLongitude[longitudeIndex] = resolveContinuousLobeWave(
512
+ options,
513
+ patchCenterLongitude,
514
+ );
511
515
  cachedCosByPatchCenterLongitude[longitudeIndex] = Math.max(0, Math.cos(patchCenterLongitude));
512
516
  }
513
517
 
@@ -193,8 +193,14 @@ export const octopus3d4AvatarVisual: AvatarVisualDefinition = {
193
193
  isAnimated: true,
194
194
  supportsPointerTracking: true,
195
195
  render({ context, size, palette, createRandom, timeMs, interaction }) {
196
- const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset, tentacleProfiles, skinSpots } =
197
- getOctopus3d4StableState(createRandom);
196
+ const {
197
+ morphologyProfile,
198
+ animationPhase,
199
+ leftEyePhaseOffset,
200
+ rightEyePhaseOffset,
201
+ tentacleProfiles,
202
+ skinSpots,
203
+ } = getOctopus3d4StableState(createRandom);
198
204
  const sceneCenterX = size * 0.5;
199
205
  const sceneCenterY = size * 0.535;
200
206
  const bob = Math.sin(timeMs / 980 + animationPhase) * size * 0.013;
@@ -568,7 +574,8 @@ function resolveVisibleBlobbyContinuousPatches(
568
574
  }
569
575
 
570
576
  for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
571
- const patchCenterLongitude = (longitudeBoundaries[longitudeIndex]! + longitudeBoundaries[longitudeIndex + 1]!) / 2;
577
+ const patchCenterLongitude =
578
+ (longitudeBoundaries[longitudeIndex]! + longitudeBoundaries[longitudeIndex + 1]!) / 2;
572
579
  cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex] = resolveBlobbyTentacleInfluence(
573
580
  options,
574
581
  patchCenterLongitude,
@@ -702,8 +709,7 @@ function sampleBlobbyContinuousSurfacePointWithLongitudeCache(
702
709
  const lowerBlend = smoothStep(0.34, 1, verticalProgress);
703
710
  const tipBlend = smoothStep(0.66, 1, verticalProgress);
704
711
  const centerPull = resolveSignedAngularDistance(longitude, tentacleInfluence.centerLongitude);
705
- const effectiveLongitude =
706
- longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.26 + tipBlend * 0.22);
712
+ const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.26 + tipBlend * 0.22);
707
713
  const mantleRipple =
708
714
  Math.sin(
709
715
  longitude * morphologyProfile.body.lobeCount +
@@ -742,10 +748,7 @@ function sampleBlobbyContinuousSurfacePointWithLongitudeCache(
742
748
  lowerBlend * tentacleInfluence.core * (0.12 + tentacleInfluence.depthScale * 0.07) -
743
749
  Math.max(0, -Math.cos(effectiveLongitude)) * 0.05;
744
750
  const tentacleTubeRadius =
745
- lowerBlend *
746
- tentacleInfluence.core *
747
- (0.12 + tipBlend * 0.07 + tentacleInfluence.widthScale * 0.028) *
748
- radiusX;
751
+ lowerBlend * tentacleInfluence.core * (0.12 + tipBlend * 0.07 + tentacleInfluence.widthScale * 0.028) * radiusX;
749
752
  const planarRadiusX = cosineLatitude * radiusX * horizontalScale + tentacleTubeRadius;
750
753
  const planarRadiusZ = cosineLatitude * radiusZ * depthScale + tentacleTubeRadius * 0.74;
751
754
  const lowerDrop =
@@ -759,10 +762,7 @@ function sampleBlobbyContinuousSurfacePointWithLongitudeCache(
759
762
  const combinedTentacleSway = (primaryTentacleWave + secondaryTentacleWave) * radiusX * (0.06 + tipBlend * 0.06);
760
763
 
761
764
  return {
762
- x:
763
- Math.sin(effectiveLongitude) * planarRadiusX +
764
- combinedTentacleSway +
765
- tentacleCurl * radiusX * 0.18,
765
+ x: Math.sin(effectiveLongitude) * planarRadiusX + combinedTentacleSway + tentacleCurl * radiusX * 0.18,
766
766
  y:
767
767
  Math.sin(latitude) * radiusY * (1 + upperBlend * 0.14) -
768
768
  upperBlend * radiusY * 0.12 +
@@ -911,11 +911,7 @@ function drawBlobbyContinuousSurfacePatch(
911
911
  drawProjectedQuad(context, surfacePatch.corners, surfacePatch.fillStyle);
912
912
 
913
913
  if (surfacePatch.lightIntensity > 0) {
914
- drawProjectedQuad(
915
- context,
916
- surfacePatch.corners,
917
- `rgba(255, 255, 255, ${0.2 * surfacePatch.lightIntensity})`,
918
- );
914
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.2 * surfacePatch.lightIntensity})`);
919
915
  } else if (surfacePatch.lightIntensity < 0) {
920
916
  drawProjectedQuad(
921
917
  context,
@@ -8,11 +8,11 @@ export function insertDictationChunk(params: {
8
8
  readonly dictatedText: string;
9
9
  readonly selectionStart: number;
10
10
  readonly selectionEnd: number;
11
- readonly shouldReplaceSelection: boolean;
11
+ readonly isReplacingSelection: boolean;
12
12
  }): { nextValue: string; start: number; caret: number } {
13
- const { currentValue, dictatedText, selectionStart, selectionEnd, shouldReplaceSelection } = params;
13
+ const { currentValue, dictatedText, selectionStart, selectionEnd, isReplacingSelection } = params;
14
14
  const replaceStart = selectionStart;
15
- const replaceEnd = shouldReplaceSelection ? selectionEnd : selectionStart;
15
+ const replaceEnd = isReplacingSelection ? selectionEnd : selectionStart;
16
16
  const prefix = currentValue.slice(0, replaceStart);
17
17
  const suffix = currentValue.slice(replaceEnd);
18
18
  const previousCharacter = prefix.slice(-1);
@@ -30,7 +30,7 @@ export function learnDictationDictionary(
30
30
  continue;
31
31
  }
32
32
 
33
- if (previousWord.toLowerCase() !== correctedWord.toLowerCase()) {
33
+ if (previousWord !== correctedWord) {
34
34
  nextDictionary[previousWord.toLowerCase()] = correctedWord;
35
35
  }
36
36
  }
@@ -38,6 +38,17 @@ type DictationChunk = {
38
38
  readonly start: number;
39
39
  };
40
40
 
41
+ /**
42
+ * Stable insertion range captured when the user starts dictation.
43
+ *
44
+ * @private function of `useChatInputAreaDictation`
45
+ */
46
+ type DictationInsertionSelection = {
47
+ readonly selectionStart: number;
48
+ readonly selectionEnd: number;
49
+ readonly isReplacingSelection: boolean;
50
+ };
51
+
41
52
  /**
42
53
  * Stable dictation error shape consumed by the correction panel UI.
43
54
  *
@@ -88,7 +99,7 @@ type UseChatInputAreaDictationFinalResultHandlerProps = {
88
99
  readonly applyMessageContent: (nextContent: string) => void;
89
100
  readonly dictationSettings: DictationRefinementSettings;
90
101
  readonly dictationDictionary: ReturnType<typeof useChatInputAreaDictationPersistence>['dictationDictionary'];
91
- readonly replaceSelectionOnNextFinalRef: MutableRefObject<boolean>;
102
+ readonly dictationInsertionSelectionRef: MutableRefObject<DictationInsertionSelection | null>;
92
103
  readonly focusTextareaSelection: (selectionStart: number, selectionEnd: number) => void;
93
104
  readonly state: UseChatInputAreaDictationState;
94
105
  };
@@ -126,7 +137,7 @@ type UseChatInputAreaDictationVoiceInputControlsProps = {
126
137
  readonly resolvedSpeechRecognitionLanguage: string;
127
138
  readonly whisperMode: boolean;
128
139
  readonly pendingStopFallbackRef: MutableRefObject<ReturnType<typeof setTimeout> | null>;
129
- readonly replaceSelectionOnNextFinalRef: MutableRefObject<boolean>;
140
+ readonly dictationInsertionSelectionRef: MutableRefObject<DictationInsertionSelection | null>;
130
141
  readonly clearPendingStopFallback: () => void;
131
142
  readonly state: Pick<
132
143
  UseChatInputAreaDictationState,
@@ -144,6 +155,7 @@ type UseChatInputAreaDictationCorrectionHandlersProps = {
144
155
  readonly dictationLastFinalChunk: string;
145
156
  readonly dictationEditableChunk: string;
146
157
  readonly dictationDictionary: ReturnType<typeof useChatInputAreaDictationPersistence>['dictationDictionary'];
158
+ readonly dictationInsertionSelectionRef: MutableRefObject<DictationInsertionSelection | null>;
147
159
  readonly messageContentRef: MutableRefObject<string>;
148
160
  readonly applyMessageContent: (nextContent: string) => void;
149
161
  readonly focusTextareaSelection: (selectionStart: number, selectionEnd: number) => void;
@@ -235,21 +247,96 @@ function clearPendingStopFallbackTimeout(
235
247
  }
236
248
 
237
249
  /**
238
- * Captures whether the next finalized chunk should replace the current selection.
250
+ * Captures where the next finalized chunk should be inserted.
239
251
  *
240
252
  * @private function of `useChatInputAreaDictation`
241
253
  */
242
- function captureSelectionReplacementIntent(
254
+ function captureDictationInsertionSelection(
243
255
  textareaRef: MutableRefObject<HTMLTextAreaElement | null>,
244
- replaceSelectionOnNextFinalRef: MutableRefObject<boolean>,
256
+ dictationInsertionSelectionRef: MutableRefObject<DictationInsertionSelection | null>,
245
257
  ): void {
246
258
  const textarea = textareaRef.current;
247
259
  if (!textarea) {
248
260
  return;
249
261
  }
250
262
 
251
- const { selectionStart, selectionEnd } = resolveTextareaSelection(textarea, 0);
252
- replaceSelectionOnNextFinalRef.current = selectionStart !== selectionEnd;
263
+ const { selectionStart, selectionEnd } = resolveTextareaSelection(textarea, textarea.value.length);
264
+ dictationInsertionSelectionRef.current = {
265
+ selectionStart,
266
+ selectionEnd,
267
+ isReplacingSelection: selectionStart !== selectionEnd,
268
+ };
269
+ }
270
+
271
+ /**
272
+ * Resolves the insertion range for one finalized dictation chunk.
273
+ *
274
+ * @private function of `useChatInputAreaDictation`
275
+ */
276
+ function resolveDictationInsertionSelection(
277
+ textareaElement: HTMLTextAreaElement,
278
+ messageContentLength: number,
279
+ dictationInsertionSelection: DictationInsertionSelection | null,
280
+ ): DictationInsertionSelection {
281
+ if (dictationInsertionSelection) {
282
+ return dictationInsertionSelection;
283
+ }
284
+
285
+ const { selectionStart, selectionEnd } = resolveTextareaSelection(textareaElement, messageContentLength);
286
+
287
+ return {
288
+ selectionStart,
289
+ selectionEnd,
290
+ isReplacingSelection: selectionStart !== selectionEnd,
291
+ };
292
+ }
293
+
294
+ /**
295
+ * Removes one tracked dictated chunk from the current composer value.
296
+ *
297
+ * @private function of `useChatInputAreaDictation`
298
+ */
299
+ function removeTrackedDictationChunk(currentValue: string, chunk: DictationChunk): string {
300
+ const trackedText = currentValue.slice(chunk.start, chunk.start + chunk.finalText.length);
301
+
302
+ if (trackedText === chunk.finalText) {
303
+ return `${currentValue.slice(0, chunk.start)}${currentValue.slice(chunk.start + chunk.finalText.length)}`;
304
+ }
305
+
306
+ return replaceLastOccurrence(currentValue, chunk.finalText, '');
307
+ }
308
+
309
+ /**
310
+ * Replaces one tracked dictated chunk inside the current composer value.
311
+ *
312
+ * @private function of `useChatInputAreaDictation`
313
+ */
314
+ function replaceTrackedDictationChunk(currentValue: string, chunk: DictationChunk, correctedChunk: string): string {
315
+ const trackedText = currentValue.slice(chunk.start, chunk.start + chunk.finalText.length);
316
+
317
+ if (trackedText === chunk.finalText) {
318
+ return `${currentValue.slice(0, chunk.start)}${correctedChunk}${currentValue.slice(
319
+ chunk.start + chunk.finalText.length,
320
+ )}`;
321
+ }
322
+
323
+ return replaceLastOccurrence(currentValue, chunk.finalText, correctedChunk);
324
+ }
325
+
326
+ /**
327
+ * Stores the next append position after a programmatic dictation edit.
328
+ *
329
+ * @private function of `useChatInputAreaDictation`
330
+ */
331
+ function updateDictationInsertionCaret(
332
+ dictationInsertionSelectionRef: MutableRefObject<DictationInsertionSelection | null>,
333
+ caret: number,
334
+ ): void {
335
+ dictationInsertionSelectionRef.current = {
336
+ selectionStart: caret,
337
+ selectionEnd: caret,
338
+ isReplacingSelection: false,
339
+ };
253
340
  }
254
341
 
255
342
  /**
@@ -376,7 +463,7 @@ function useChatInputAreaDictationFinalResultHandler({
376
463
  applyMessageContent,
377
464
  dictationSettings,
378
465
  dictationDictionary,
379
- replaceSelectionOnNextFinalRef,
466
+ dictationInsertionSelectionRef,
380
467
  focusTextareaSelection,
381
468
  state,
382
469
  }: UseChatInputAreaDictationFinalResultHandlerProps) {
@@ -403,16 +490,20 @@ function useChatInputAreaDictationFinalResultHandler({
403
490
  return;
404
491
  }
405
492
 
406
- const { selectionStart, selectionEnd } = resolveTextareaSelection(textarea, previousMessageContent.length);
493
+ const { selectionStart, selectionEnd, isReplacingSelection } = resolveDictationInsertionSelection(
494
+ textarea,
495
+ previousMessageContent.length,
496
+ dictationInsertionSelectionRef.current,
497
+ );
407
498
  const insertion = insertDictationChunk({
408
499
  currentValue: previousMessageContent,
409
500
  dictatedText: refinedText,
410
501
  selectionStart,
411
502
  selectionEnd,
412
- shouldReplaceSelection: replaceSelectionOnNextFinalRef.current,
503
+ isReplacingSelection,
413
504
  });
414
505
 
415
- replaceSelectionOnNextFinalRef.current = false;
506
+ updateDictationInsertionCaret(dictationInsertionSelectionRef, insertion.caret);
416
507
  setDictationInterimText('');
417
508
  setDictationError(null);
418
509
  setDictationUiState('listening');
@@ -433,10 +524,10 @@ function useChatInputAreaDictationFinalResultHandler({
433
524
  [
434
525
  applyMessageContent,
435
526
  dictationDictionary,
527
+ dictationInsertionSelectionRef,
436
528
  dictationSettings,
437
529
  focusTextareaSelection,
438
530
  messageContentRef,
439
- replaceSelectionOnNextFinalRef,
440
531
  setDictationChunks,
441
532
  setDictationEditableChunk,
442
533
  setDictationError,
@@ -491,7 +582,9 @@ function useChatInputAreaDictationSpeechRecognitionEventHandler({
491
582
  return;
492
583
  case 'STOP':
493
584
  clearPendingStopFallback();
494
- setDictationUiState((currentState) => (currentState === 'disabled' ? currentState : 'idle'));
585
+ setDictationUiState((currentState) =>
586
+ currentState === 'disabled' || currentState === 'error' ? currentState : 'idle',
587
+ );
495
588
  setDictationInterimText('');
496
589
  return;
497
590
  }
@@ -549,7 +642,7 @@ function useChatInputAreaDictationVoiceInputControls({
549
642
  resolvedSpeechRecognitionLanguage,
550
643
  whisperMode,
551
644
  pendingStopFallbackRef,
552
- replaceSelectionOnNextFinalRef,
645
+ dictationInsertionSelectionRef,
553
646
  clearPendingStopFallback,
554
647
  state,
555
648
  }: UseChatInputAreaDictationVoiceInputControlsProps) {
@@ -560,7 +653,7 @@ function useChatInputAreaDictationVoiceInputControls({
560
653
  return;
561
654
  }
562
655
 
563
- captureSelectionReplacementIntent(textareaRef, replaceSelectionOnNextFinalRef);
656
+ captureDictationInsertionSelection(textareaRef, dictationInsertionSelectionRef);
564
657
  setDictationError(null);
565
658
  setDictationInterimText('');
566
659
  setDictationUiState('listening');
@@ -570,7 +663,7 @@ function useChatInputAreaDictationVoiceInputControls({
570
663
  whisperMode,
571
664
  });
572
665
  }, [
573
- replaceSelectionOnNextFinalRef,
666
+ dictationInsertionSelectionRef,
574
667
  resolvedSpeechRecognitionLanguage,
575
668
  setDictationError,
576
669
  setDictationInterimText,
@@ -635,6 +728,7 @@ function useChatInputAreaDictationCorrectionHandlers({
635
728
  dictationLastFinalChunk,
636
729
  dictationEditableChunk,
637
730
  dictationDictionary,
731
+ dictationInsertionSelectionRef,
638
732
  messageContentRef,
639
733
  applyMessageContent,
640
734
  focusTextareaSelection,
@@ -651,17 +745,23 @@ function useChatInputAreaDictationCorrectionHandlers({
651
745
  return;
652
746
  }
653
747
 
748
+ const nextMessageContent = removeTrackedDictationChunk(messageContentRef.current, lastChunk);
749
+ const nextCaret = Math.min(lastChunk.start, nextMessageContent.length);
750
+
654
751
  setDictationChunks(previousChunks);
655
- applyMessageContent(lastChunk.beforeValue);
752
+ applyMessageContent(nextMessageContent);
656
753
 
657
754
  const previousFinalChunk = previousChunks[previousChunks.length - 1]?.finalText || '';
658
755
  setDictationLastFinalChunk(previousFinalChunk);
659
756
  setDictationEditableChunk(previousFinalChunk);
660
- focusTextareaSelection(lastChunk.start, lastChunk.start);
757
+ updateDictationInsertionCaret(dictationInsertionSelectionRef, nextCaret);
758
+ focusTextareaSelection(nextCaret, nextCaret);
661
759
  }, [
662
760
  applyMessageContent,
761
+ dictationInsertionSelectionRef,
663
762
  dictationChunks,
664
763
  focusTextareaSelection,
764
+ messageContentRef,
665
765
  setDictationChunks,
666
766
  setDictationEditableChunk,
667
767
  setDictationLastFinalChunk,
@@ -673,21 +773,27 @@ function useChatInputAreaDictationCorrectionHandlers({
673
773
  return;
674
774
  }
675
775
 
676
- const nextMessageContent = replaceLastOccurrence(
677
- messageContentRef.current,
678
- dictationLastFinalChunk,
679
- correctedChunk,
680
- );
776
+ const lastChunk = dictationChunks[dictationChunks.length - 1];
777
+ const nextMessageContent = lastChunk
778
+ ? replaceTrackedDictationChunk(messageContentRef.current, lastChunk, correctedChunk)
779
+ : replaceLastOccurrence(messageContentRef.current, dictationLastFinalChunk, correctedChunk);
780
+ const correctionStart = lastChunk?.start ?? nextMessageContent.length;
781
+ const correctionCaret = Math.min(correctionStart + correctedChunk.length, nextMessageContent.length);
681
782
 
682
783
  applyMessageContent(nextMessageContent);
683
784
  setDictationLastFinalChunk(correctedChunk);
684
785
  setDictationChunks((previousChunks) => replaceLastDictationChunk(previousChunks, correctedChunk));
685
786
  setDictationDictionary(learnDictationDictionary(dictationLastFinalChunk, correctedChunk, dictationDictionary));
787
+ updateDictationInsertionCaret(dictationInsertionSelectionRef, correctionCaret);
788
+ focusTextareaSelection(correctionCaret, correctionCaret);
686
789
  }, [
687
790
  applyMessageContent,
688
791
  dictationDictionary,
689
792
  dictationEditableChunk,
793
+ dictationChunks,
794
+ dictationInsertionSelectionRef,
690
795
  dictationLastFinalChunk,
796
+ focusTextareaSelection,
691
797
  messageContentRef,
692
798
  setDictationChunks,
693
799
  setDictationDictionary,
@@ -713,7 +819,7 @@ export function useChatInputAreaDictation({
713
819
  applyMessageContent,
714
820
  }: UseChatInputAreaDictationProps) {
715
821
  const pendingStopFallbackRef = useRef<ReturnType<typeof setTimeout> | null>(null);
716
- const replaceSelectionOnNextFinalRef = useRef(false);
822
+ const dictationInsertionSelectionRef = useRef<DictationInsertionSelection | null>(null);
717
823
  const [dictationUiState, setDictationUiState] = useState<DictationUiState>('idle');
718
824
  const [dictationInterimText, setDictationInterimText] = useState('');
719
825
  const [dictationError, setDictationError] = useState<DictationError | null>(null);
@@ -753,7 +859,7 @@ export function useChatInputAreaDictation({
753
859
  applyMessageContent,
754
860
  dictationSettings,
755
861
  dictationDictionary,
756
- replaceSelectionOnNextFinalRef,
862
+ dictationInsertionSelectionRef,
757
863
  focusTextareaSelection,
758
864
  state: dictationState,
759
865
  });
@@ -775,7 +881,7 @@ export function useChatInputAreaDictation({
775
881
  resolvedSpeechRecognitionLanguage,
776
882
  whisperMode: dictationSettings.whisperMode,
777
883
  pendingStopFallbackRef,
778
- replaceSelectionOnNextFinalRef,
884
+ dictationInsertionSelectionRef,
779
885
  clearPendingStopFallback,
780
886
  state: {
781
887
  dictationUiState,
@@ -789,6 +895,7 @@ export function useChatInputAreaDictation({
789
895
  dictationLastFinalChunk,
790
896
  dictationEditableChunk,
791
897
  dictationDictionary,
898
+ dictationInsertionSelectionRef,
792
899
  messageContentRef,
793
900
  applyMessageContent,
794
901
  focusTextareaSelection,
@@ -739,7 +739,9 @@ function isNextBuildTerminationRetryable(exitStatus: NextBuildExitStatus): boole
739
739
  * Creates one visible retry message for a killed Next.js build attempt.
740
740
  */
741
741
  function createNextBuildRetryMessage(exitStatus: NextBuildExitStatus, nextAttempt: number): string {
742
- return `\nAgents Server Next build was terminated by ${describeNextBuildExitStatus(exitStatus)}. Retrying attempt ${nextAttempt}/${AGENTS_SERVER_BUILD_MAX_ATTEMPTS}.\n`;
742
+ return `\nAgents Server Next build was terminated by ${describeNextBuildExitStatus(
743
+ exitStatus,
744
+ )}. Retrying attempt ${nextAttempt}/${AGENTS_SERVER_BUILD_MAX_ATTEMPTS}.\n`;
743
745
  }
744
746
 
745
747
  /**
@@ -120,26 +120,26 @@ const DEFAULT_CODER_PROMPT_TEMPLATE_DEFINITIONS = [
120
120
  id: 'common',
121
121
  relativeFilePath: join(PROMPTS_TEMPLATES_DIRECTORY_PATH, 'common.md'),
122
122
  slugPrefix: null,
123
- content: buildCoderPromptTemplateContent([
124
- '- @@@',
125
- "- Keep in mind the DRY _(don't repeat yourself)_ principle.",
126
- '- Do a proper analysis of the current functionality before you start implementing.',
127
- '- Add the changes into the [changelog](changelog/_current-preversion.md)',
128
- ]),
123
+ content: spaceTrim(`
124
+ - @@@
125
+ - Keep in mind the DRY _(don't repeat yourself)_ principle.
126
+ - Do a proper analysis of the current functionality before you start implementing.
127
+ - Add the changes into the [changelog](changelog/_current-preversion.md)
128
+ `),
129
129
  isDefaultProjectTemplate: true,
130
130
  },
131
131
  {
132
132
  id: 'agents-server',
133
133
  relativeFilePath: join(PROMPTS_TEMPLATES_DIRECTORY_PATH, 'agents-server.md'),
134
134
  slugPrefix: 'agents-server',
135
- content: buildCoderPromptTemplateContent([
136
- '- @@@',
137
- "- Keep in mind the DRY _(don't repeat yourself)_ principle.",
138
- '- Do a proper analysis of the current functionality before you start implementing.',
139
- '- You are working with the [Agents Server](apps/agents-server)',
140
- '- If you need to do the database migration, do it',
141
- '- Add the changes into the [changelog](changelog/_current-preversion.md)',
142
- ]),
135
+ content: spaceTrim(`
136
+ - @@@
137
+ - Keep in mind the DRY _(don't repeat yourself)_ principle.
138
+ - Do a proper analysis of the current functionality before you start implementing.
139
+ - You are working with the [Agents Server](apps/agents-server)
140
+ - If you need to do the database migration, do it
141
+ - Add the changes into the [changelog](changelog/_current-preversion.md)
142
+ `),
143
143
  isDefaultProjectTemplate: false,
144
144
  },
145
145
  ] as const satisfies ReadonlyArray<CoderPromptTemplateDefinition>;
@@ -288,13 +288,6 @@ function getDefaultCoderPromptTemplateDefinitionOrUndefined(
288
288
  return DEFAULT_CODER_PROMPT_TEMPLATE_DEFINITIONS.find((definition) => definition.id === template);
289
289
  }
290
290
 
291
- /**
292
- * Builds stable markdown content for one coder prompt template without indentation drift.
293
- */
294
- function buildCoderPromptTemplateContent(lines: ReadonlyArray<string>): string {
295
- return lines.join('\n');
296
- }
297
-
298
291
  /**
299
292
  * Creates a fully resolved template payload from one built-in definition.
300
293
  */
@@ -12,6 +12,7 @@ import {
12
12
  } from '../../../../scripts/find-refactor-candidates/RefactorCandidateLevel';
13
13
  import { assertsError } from '../../../errors/assertsError';
14
14
  import type { $side_effect } from '../../../utils/organization/$side_effect';
15
+ import { createPositiveIntegerOptionParser } from '../common/createPositiveIntegerOptionParser';
15
16
  import { handleActionErrors } from '../common/handleActionErrors';
16
17
 
17
18
  /**
@@ -44,11 +45,17 @@ export function $initializeCoderFindRefactorCandidatesCommand(program: Program):
44
45
  .choices([...REFACTOR_CANDIDATE_LEVEL_VALUES])
45
46
  .default(DEFAULT_REFACTOR_CANDIDATE_LEVEL),
46
47
  );
48
+ command.option(
49
+ '--limit <candidate-count>',
50
+ 'Create at most this many refactor prompts, keeping the most important candidates',
51
+ createPositiveIntegerOptionParser('--limit'),
52
+ );
47
53
 
48
54
  command.action(
49
55
  handleActionErrors(async (cliOptions) => {
50
- const { level = DEFAULT_REFACTOR_CANDIDATE_LEVEL } = cliOptions as {
56
+ const { level = DEFAULT_REFACTOR_CANDIDATE_LEVEL, limit } = cliOptions as {
51
57
  readonly level?: RefactorCandidateLevel;
58
+ readonly limit?: number;
52
59
  };
53
60
 
54
61
  // Note: Import the function dynamically to avoid loading heavy dependencies until needed
@@ -57,7 +64,7 @@ export function $initializeCoderFindRefactorCandidatesCommand(program: Program):
57
64
  );
58
65
 
59
66
  try {
60
- await findRefactorCandidates({ level });
67
+ await findRefactorCandidates({ level, limit });
61
68
  } catch (error) {
62
69
  assertsError(error);
63
70
  console.error(colors.bgRed(`${error.name}`));
@@ -8,10 +8,7 @@ import {
8
8
  PROMPTS_TEMPLATES_DIRECTORY_PATH,
9
9
  } from './boilerplateTemplates';
10
10
  import { ensureCoderEnvFile } from './ensureCoderEnvFile';
11
- import {
12
- CODER_AGENTS_DIRECTORY_PATH,
13
- ensureCoderDeveloperAgentFile,
14
- } from './ensureCoderDeveloperAgentFile';
11
+ import { CODER_AGENTS_DIRECTORY_PATH, ensureCoderDeveloperAgentFile } from './ensureCoderDeveloperAgentFile';
15
12
  import { ensureCoderGitignoreFile } from './ensureCoderGitignoreFile';
16
13
  import { ensureCoderMarkdownFile } from './ensureCoderMarkdownFile';
17
14
  import { ensureCoderPackageJsonFile } from './ensureCoderPackageJsonFile';