@promptbook/cli 0.114.0-35 → 0.114.0-39

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 (86) hide show
  1. package/agents/default/.core/adam.book +33 -0
  2. package/apps/agents-server/package.json +1 -1
  3. package/apps/agents-server/scripts/build-agents-server.js +4 -0
  4. package/apps/agents-server/scripts/build-e2e.js +1 -1
  5. package/apps/agents-server/src/app/swagger/SwaggerPageClient.tsx +35 -19
  6. package/apps/agents-server/src/types/swagger-ui-dist.d.ts +5 -0
  7. package/apps/agents-server/src/utils/agentReferenceResolver/AgentReferenceResolutionIssue.ts +1 -69
  8. package/apps/agents-server/src/utils/agentReferenceResolver/extractAgentReferenceTokens.ts +1 -61
  9. package/apps/agents-server/src/utils/explicitFromCommitment.ts +1 -130
  10. package/apps/agents-server/src/utils/resolveInheritedAgentSource.ts +32 -817
  11. package/esm/index.es.js +1431 -128
  12. package/esm/index.es.js.map +1 -1
  13. package/esm/scripts/run-agent-messages/messages/createAgentRunnerSystemMessage.d.ts +2 -1
  14. package/esm/scripts/run-codex-prompts/common/resolveCoderAgent.d.ts +11 -0
  15. package/esm/scripts/run-codex-prompts/git/commitInitializedAgentBooks.d.ts +5 -0
  16. package/esm/scripts/run-codex-prompts/main/resolvePromptRunner.d.ts +5 -8
  17. package/esm/scripts/run-codex-prompts/main/runPromptRound.d.ts +2 -4
  18. package/esm/scripts/run-codex-prompts/prompts/buildPromptRunTraceContent.d.ts +5 -17
  19. package/esm/scripts/run-codex-prompts/prompts/buildPromptStatusDetails.d.ts +3 -15
  20. package/esm/scripts/run-codex-prompts/prompts/formatRunnerSignature.d.ts +14 -1
  21. package/esm/scripts/run-codex-prompts/prompts/markPromptFailed.d.ts +2 -9
  22. package/esm/src/book-2.0/agent-source/AgentReferenceResolutionIssue.d.ts +45 -0
  23. package/esm/src/book-2.0/agent-source/createAgentModelRequirements.deduplication.test.d.ts +1 -0
  24. package/esm/src/book-2.0/agent-source/deduplicateSystemMessage.d.ts +14 -0
  25. package/esm/src/book-2.0/agent-source/deduplicateSystemMessage.test.d.ts +1 -0
  26. package/esm/src/book-2.0/agent-source/explicitFromCommitment.d.ts +62 -0
  27. package/esm/src/book-2.0/agent-source/extractAgentReferenceTokens.d.ts +32 -0
  28. package/esm/src/book-2.0/agent-source/resolveInheritedAgentSource.d.ts +73 -0
  29. package/esm/src/cli/$initializePromptbookCliProgram.d.ts +8 -0
  30. package/esm/src/cli/cli-commands/coder/initializeCoderProjectConfiguration.d.ts +1 -0
  31. package/esm/src/cli/cli-commands/common/LocalAgentBookCollection.d.ts +46 -0
  32. package/esm/src/cli/cli-commands/common/createLocalAgentReferenceResolver.d.ts +8 -0
  33. package/esm/src/cli/cli-commands/common/ensureAdamAgentBook.d.ts +12 -0
  34. package/esm/src/cli/cli-commands/common/resolveBundledAgentBookPath.d.ts +6 -0
  35. package/esm/src/cli/cli-commands/common/resolveLocalAgentSource.d.ts +19 -0
  36. package/esm/src/version.d.ts +1 -1
  37. package/package.json +2 -3
  38. package/src/book-2.0/agent-source/AgentReferenceResolutionIssue.ts +75 -0
  39. package/src/book-2.0/agent-source/createAgentModelRequirementsWithCommitments.ts +3 -2
  40. package/src/book-2.0/agent-source/deduplicateSystemMessage.ts +327 -0
  41. package/src/book-2.0/agent-source/explicitFromCommitment.ts +132 -0
  42. package/src/book-2.0/agent-source/extractAgentReferenceTokens.ts +65 -0
  43. package/src/book-2.0/agent-source/resolveInheritedAgentSource.ts +841 -0
  44. package/src/cli/$initializePromptbookCliProgram.ts +87 -0
  45. package/src/cli/cli-commands/coder/ensureCoderDeveloperAgentFile.ts +2 -28
  46. package/src/cli/cli-commands/coder/init.ts +3 -1
  47. package/src/cli/cli-commands/coder/initializeCoderProjectConfiguration.ts +5 -0
  48. package/src/cli/cli-commands/coder/list.ts +5 -3
  49. package/src/cli/cli-commands/coder/printInitializationSummary.ts +6 -0
  50. package/src/cli/cli-commands/coder/run.ts +4 -1
  51. package/src/cli/cli-commands/coder/server.ts +2 -1
  52. package/src/cli/cli-commands/common/LocalAgentBookCollection.ts +220 -0
  53. package/src/cli/cli-commands/common/createLocalAgentReferenceResolver.ts +62 -0
  54. package/src/cli/cli-commands/common/ensureAdamAgentBook.ts +40 -0
  55. package/src/cli/cli-commands/common/resolveBundledAgentBookPath.ts +38 -0
  56. package/src/cli/cli-commands/common/resolveLocalAgentSource.ts +127 -0
  57. package/src/cli/promptbookCli.ts +2 -87
  58. package/src/other/templates/getTemplatesPipelineCollection.ts +612 -944
  59. package/src/version.ts +2 -2
  60. package/src/versions.txt +4 -0
  61. package/umd/index.umd.js +1431 -128
  62. package/umd/index.umd.js.map +1 -1
  63. package/umd/scripts/run-agent-messages/messages/createAgentRunnerSystemMessage.d.ts +2 -1
  64. package/umd/scripts/run-codex-prompts/common/resolveCoderAgent.d.ts +11 -0
  65. package/umd/scripts/run-codex-prompts/git/commitInitializedAgentBooks.d.ts +5 -0
  66. package/umd/scripts/run-codex-prompts/main/resolvePromptRunner.d.ts +5 -8
  67. package/umd/scripts/run-codex-prompts/main/runPromptRound.d.ts +2 -4
  68. package/umd/scripts/run-codex-prompts/prompts/buildPromptRunTraceContent.d.ts +5 -17
  69. package/umd/scripts/run-codex-prompts/prompts/buildPromptStatusDetails.d.ts +3 -15
  70. package/umd/scripts/run-codex-prompts/prompts/formatRunnerSignature.d.ts +14 -1
  71. package/umd/scripts/run-codex-prompts/prompts/markPromptFailed.d.ts +2 -9
  72. package/umd/src/book-2.0/agent-source/AgentReferenceResolutionIssue.d.ts +45 -0
  73. package/umd/src/book-2.0/agent-source/createAgentModelRequirements.deduplication.test.d.ts +1 -0
  74. package/umd/src/book-2.0/agent-source/deduplicateSystemMessage.d.ts +14 -0
  75. package/umd/src/book-2.0/agent-source/deduplicateSystemMessage.test.d.ts +1 -0
  76. package/umd/src/book-2.0/agent-source/explicitFromCommitment.d.ts +62 -0
  77. package/umd/src/book-2.0/agent-source/extractAgentReferenceTokens.d.ts +32 -0
  78. package/umd/src/book-2.0/agent-source/resolveInheritedAgentSource.d.ts +73 -0
  79. package/umd/src/cli/$initializePromptbookCliProgram.d.ts +8 -0
  80. package/umd/src/cli/cli-commands/coder/initializeCoderProjectConfiguration.d.ts +1 -0
  81. package/umd/src/cli/cli-commands/common/LocalAgentBookCollection.d.ts +46 -0
  82. package/umd/src/cli/cli-commands/common/createLocalAgentReferenceResolver.d.ts +8 -0
  83. package/umd/src/cli/cli-commands/common/ensureAdamAgentBook.d.ts +12 -0
  84. package/umd/src/cli/cli-commands/common/resolveBundledAgentBookPath.d.ts +6 -0
  85. package/umd/src/cli/cli-commands/common/resolveLocalAgentSource.d.ts +19 -0
  86. package/umd/src/version.d.ts +1 -1
@@ -0,0 +1,75 @@
1
+ import type { BookCommitment } from '../../commitments/_base/BookCommitment';
2
+ import type { AgentReferenceResolver } from './AgentReferenceResolver';
3
+
4
+ /**
5
+ * Structured issue captured when compact agent reference resolution fails.
6
+ *
7
+ * @private internal utility of agent reference resolution
8
+ */
9
+ export type AgentReferenceResolutionIssue = {
10
+ /**
11
+ * Commitment where the unresolved token appeared.
12
+ */
13
+ readonly commitmentType: BookCommitment;
14
+
15
+ /**
16
+ * Original token text (for example `{Unknown Agent}` or `@UnknownAgent`).
17
+ */
18
+ readonly token: string;
19
+
20
+ /**
21
+ * Normalized token payload used for lookup.
22
+ */
23
+ readonly reference: string;
24
+
25
+ /**
26
+ * Human-readable explanation of why the token could not be resolved.
27
+ */
28
+ readonly message: string;
29
+ };
30
+
31
+ /**
32
+ * Extension implemented by resolvers that can expose accumulated resolution issues.
33
+ *
34
+ * @private internal utility of agent reference resolution
35
+ */
36
+ export type IssueTrackingAgentReferenceResolver = AgentReferenceResolver & {
37
+ /**
38
+ * Returns tracked issues and clears the internal queue.
39
+ */
40
+ consumeResolutionIssues(): Array<AgentReferenceResolutionIssue>;
41
+ };
42
+
43
+ /**
44
+ * Type guard for issue-tracking resolver implementations.
45
+ *
46
+ * @param resolver - Resolver instance to check.
47
+ * @returns True when the resolver provides `consumeResolutionIssues`.
48
+ */
49
+ function isIssueTrackingAgentReferenceResolver(
50
+ resolver: AgentReferenceResolver,
51
+ ): resolver is IssueTrackingAgentReferenceResolver {
52
+ return typeof (resolver as Partial<IssueTrackingAgentReferenceResolver>).consumeResolutionIssues === 'function';
53
+ }
54
+
55
+ /**
56
+ * Drains unresolved compact-reference issues from a resolver when supported.
57
+ *
58
+ * @param resolver - Resolver instance that may implement issue tracking.
59
+ * @returns Collected issues or an empty list.
60
+ *
61
+ * @private internal utility of agent reference resolution
62
+ */
63
+ export function consumeAgentReferenceResolutionIssues(
64
+ resolver?: AgentReferenceResolver,
65
+ ): Array<AgentReferenceResolutionIssue> {
66
+ if (!resolver) {
67
+ return [];
68
+ }
69
+
70
+ if (!isIssueTrackingAgentReferenceResolver(resolver)) {
71
+ return [];
72
+ }
73
+
74
+ return resolver.consumeResolutionIssues();
75
+ }
@@ -6,6 +6,7 @@ import { applyCommitmentsToAgentModelRequirements } from './createAgentModelRequ
6
6
  import { augmentAgentModelRequirementsFromSource } from './createAgentModelRequirementsWithCommitments/augmentAgentModelRequirementsFromSource';
7
7
  import { filterCommitmentsForAgentModelRequirements } from './createAgentModelRequirementsWithCommitments/filterCommitmentsForAgentModelRequirements';
8
8
  import { materializeInlineKnowledgeSources } from './createAgentModelRequirementsWithCommitments/materializeInlineKnowledgeSources';
9
+ import { deduplicateSystemMessage } from './deduplicateSystemMessage';
9
10
  import { parseAgentSourceWithCommitments } from './parseAgentSourceWithCommitments';
10
11
  import { removeCommentsFromSystemMessage } from './removeCommentsFromSystemMessage';
11
12
  import type { string_book } from './string_book';
@@ -73,13 +74,13 @@ function createInitialAgentModelRequirements(
73
74
  * Performs the final system-message cleanup pass after all other augmentation steps are complete.
74
75
  *
75
76
  * @param requirements - Fully built requirements before final cleanup.
76
- * @returns Requirements with comment lines removed from the final system message.
77
+ * @returns Requirements with comment lines removed and repeated instructions deduplicated in the final system message.
77
78
  *
78
79
  * @private internal utility of `createAgentModelRequirementsWithCommitments`
79
80
  */
80
81
  function finalizeRequirements(requirements: AgentModelRequirements): AgentModelRequirements {
81
82
  return {
82
83
  ...requirements,
83
- systemMessage: removeCommentsFromSystemMessage(requirements.systemMessage),
84
+ systemMessage: deduplicateSystemMessage(removeCommentsFromSystemMessage(requirements.systemMessage)),
84
85
  };
85
86
  }
@@ -0,0 +1,327 @@
1
+ /**
2
+ * Pattern matching a markdown section heading (`## Title`) that separates system-message sections
3
+ *
4
+ * @private internal constant of `deduplicateSystemMessage`
5
+ */
6
+ const SYSTEM_MESSAGE_SECTION_HEADING_PATTERN = /^##(?!#)\s*(.+?)\s*$/;
7
+
8
+ /**
9
+ * Pattern matching a fenced code block delimiter which suspends section and block splitting
10
+ *
11
+ * @private internal constant of `deduplicateSystemMessage`
12
+ */
13
+ const CODE_FENCE_PATTERN = /^\s*(?:```|~~~)/;
14
+
15
+ /**
16
+ * Pattern matching a markdown list item which keeps merged bullet lists visually compact
17
+ *
18
+ * @private internal constant of `deduplicateSystemMessage`
19
+ */
20
+ const LIST_ITEM_PATTERN = /^\s*(?:[-*+]|\d+[.)])\s/;
21
+
22
+ /**
23
+ * One block of a system-message region, which is a group of lines surrounded by blank lines
24
+ *
25
+ * @private internal type of `deduplicateSystemMessage`
26
+ */
27
+ type SystemMessageBlock = {
28
+ /**
29
+ * Exact text of the block
30
+ */
31
+ readonly text: string;
32
+
33
+ /**
34
+ * Whether the block opens a section occurrence merged into an earlier section with the same heading
35
+ */
36
+ readonly isOpeningMergedSection: boolean;
37
+ };
38
+
39
+ /**
40
+ * One region of the system message, which is either the intro before the first heading or one `## Title` section
41
+ *
42
+ * @private internal type of `deduplicateSystemMessage`
43
+ */
44
+ type SystemMessageRegion = {
45
+ /**
46
+ * Case-insensitive key used to merge regions sharing one heading, `null` for the intro before the first heading
47
+ */
48
+ readonly titleKey: string | null;
49
+
50
+ /**
51
+ * Original heading line kept so the rendered heading preserves its exact original text
52
+ */
53
+ readonly headingLine: string | null;
54
+
55
+ /**
56
+ * Whether the original region separated its heading from the body by a blank line
57
+ */
58
+ readonly isBodySeparatedByBlankLine: boolean;
59
+
60
+ /**
61
+ * Body blocks of the region in their original order
62
+ */
63
+ readonly blocks: ReadonlyArray<SystemMessageBlock>;
64
+ };
65
+
66
+ /**
67
+ * Removes duplicated instructions from a generated system message
68
+ *
69
+ * Commitments are applied one by one, so a commitment used multiple times in one book repeats its whole
70
+ * section, including the shared guidance preamble. For example two `WRITING RULES` commitments emit the
71
+ * `## Writing rules` heading and its guidance paragraph twice. This function merges all sections sharing
72
+ * one heading into the position of their first occurrence and keeps every identical block only once.
73
+ *
74
+ * @param systemMessage The assembled system message which may contain repeated sections and blocks
75
+ * @returns The system message where each section heading and each identical block appears exactly once
76
+ *
77
+ * @private internal utility of `createAgentModelRequirementsWithCommitments`
78
+ */
79
+ export function deduplicateSystemMessage(systemMessage: string): string {
80
+ if (!systemMessage.trim()) {
81
+ return systemMessage;
82
+ }
83
+
84
+ return splitSystemMessageIntoRegions(systemMessage)
85
+ .reduce(mergeRegionSharingHeading, [])
86
+ .map(removeDuplicateBlocksFromRegion)
87
+ .map(renderRegion)
88
+ .filter((renderedRegion) => renderedRegion !== '')
89
+ .join('\n\n');
90
+ }
91
+
92
+ /**
93
+ * Splits the system message into the intro region and one region per `## Title` section
94
+ *
95
+ * @param systemMessage The system message to split
96
+ * @returns Regions in their original order, without empty ones
97
+ *
98
+ * @private internal utility of `deduplicateSystemMessage`
99
+ */
100
+ function splitSystemMessageIntoRegions(systemMessage: string): ReadonlyArray<SystemMessageRegion> {
101
+ const regions: SystemMessageRegion[] = [];
102
+ let headingLine: string | null = null;
103
+ let bodyLines: string[] = [];
104
+ let isInsideCodeFence = false;
105
+
106
+ for (const line of systemMessage.split(/\r?\n/)) {
107
+ if (CODE_FENCE_PATTERN.test(line)) {
108
+ isInsideCodeFence = !isInsideCodeFence;
109
+ }
110
+
111
+ if (isInsideCodeFence || !SYSTEM_MESSAGE_SECTION_HEADING_PATTERN.test(line)) {
112
+ bodyLines.push(line);
113
+ continue;
114
+ }
115
+
116
+ regions.push(createRegion(headingLine, bodyLines));
117
+ headingLine = line;
118
+ bodyLines = [];
119
+ }
120
+
121
+ regions.push(createRegion(headingLine, bodyLines));
122
+
123
+ return regions.filter((region) => region.headingLine !== null || region.blocks.length > 0);
124
+ }
125
+
126
+ /**
127
+ * Creates one region from its heading line and its raw body lines
128
+ *
129
+ * @param headingLine The original `## Title` line, or `null` for the intro before the first heading
130
+ * @param bodyLines The raw lines following the heading line
131
+ * @returns The region with its body already split into blocks
132
+ *
133
+ * @private internal utility of `splitSystemMessageIntoRegions`
134
+ */
135
+ function createRegion(headingLine: string | null, bodyLines: ReadonlyArray<string>): SystemMessageRegion {
136
+ return {
137
+ titleKey: headingLine === null ? null : normalizeSectionTitle(headingLine),
138
+ headingLine,
139
+ isBodySeparatedByBlankLine: bodyLines[0] !== undefined && bodyLines[0].trim() === '',
140
+ blocks: splitBodyIntoBlocks(bodyLines),
141
+ };
142
+ }
143
+
144
+ /**
145
+ * Normalizes a heading line into a key usable for matching sections which belong together
146
+ *
147
+ * @param headingLine The original `## Title` line
148
+ * @returns Lowercased title without the markdown heading prefix
149
+ *
150
+ * @private internal utility of `createRegion`
151
+ */
152
+ function normalizeSectionTitle(headingLine: string): string {
153
+ return (SYSTEM_MESSAGE_SECTION_HEADING_PATTERN.exec(headingLine)?.[1] ?? headingLine).toLowerCase();
154
+ }
155
+
156
+ /**
157
+ * Splits raw body lines into blocks separated by blank lines while keeping fenced code blocks intact
158
+ *
159
+ * @param bodyLines The raw lines of one region body
160
+ * @returns Non-empty blocks in their original order
161
+ *
162
+ * @private internal utility of `createRegion`
163
+ */
164
+ function splitBodyIntoBlocks(bodyLines: ReadonlyArray<string>): ReadonlyArray<SystemMessageBlock> {
165
+ const blockTexts: string[] = [];
166
+ let blockLines: string[] = [];
167
+ let isInsideCodeFence = false;
168
+
169
+ for (const line of bodyLines) {
170
+ if (CODE_FENCE_PATTERN.test(line)) {
171
+ isInsideCodeFence = !isInsideCodeFence;
172
+ }
173
+
174
+ if (!isInsideCodeFence && line.trim() === '') {
175
+ blockTexts.push(blockLines.join('\n'));
176
+ blockLines = [];
177
+ continue;
178
+ }
179
+
180
+ blockLines.push(line);
181
+ }
182
+
183
+ blockTexts.push(blockLines.join('\n'));
184
+
185
+ return blockTexts
186
+ .filter((blockText) => blockText.trim() !== '')
187
+ .map((blockText) => ({ text: blockText, isOpeningMergedSection: false }));
188
+ }
189
+
190
+ /**
191
+ * Appends one region to the already merged regions, merging it into an earlier region with the same heading
192
+ *
193
+ * @param mergedRegions Regions merged so far, used as the reducer accumulator
194
+ * @param region The next region in original order
195
+ * @returns The accumulator with the region either appended or merged into its earlier twin
196
+ *
197
+ * @private internal utility of `deduplicateSystemMessage`
198
+ */
199
+ function mergeRegionSharingHeading(
200
+ mergedRegions: ReadonlyArray<SystemMessageRegion>,
201
+ region: SystemMessageRegion,
202
+ ): ReadonlyArray<SystemMessageRegion> {
203
+ const existingRegionIndex = mergedRegions.findIndex(
204
+ (mergedRegion) => region.titleKey !== null && mergedRegion.titleKey === region.titleKey,
205
+ );
206
+
207
+ if (existingRegionIndex === -1) {
208
+ return [...mergedRegions, region];
209
+ }
210
+
211
+ return mergedRegions.map((mergedRegion, index) =>
212
+ index !== existingRegionIndex
213
+ ? mergedRegion
214
+ : {
215
+ ...mergedRegion,
216
+ blocks: [...mergedRegion.blocks, ...markFirstBlockAsOpeningMergedSection(region.blocks)],
217
+ },
218
+ );
219
+ }
220
+
221
+ /**
222
+ * Marks the first block of a merged section occurrence so the merge seam can be rendered as one list
223
+ *
224
+ * @param blocks The blocks of the section occurrence being merged into an earlier section
225
+ * @returns The same blocks with the first one marked as opening a merged section
226
+ *
227
+ * @private internal utility of `mergeRegionSharingHeading`
228
+ */
229
+ function markFirstBlockAsOpeningMergedSection(
230
+ blocks: ReadonlyArray<SystemMessageBlock>,
231
+ ): ReadonlyArray<SystemMessageBlock> {
232
+ return blocks.map((block, index) => (index === 0 ? { ...block, isOpeningMergedSection: true } : block));
233
+ }
234
+
235
+ /**
236
+ * Keeps only the first occurrence of every identical block inside one region
237
+ *
238
+ * @param region The region whose blocks may repeat
239
+ * @returns The region without repeated blocks
240
+ *
241
+ * @private internal utility of `deduplicateSystemMessage`
242
+ */
243
+ function removeDuplicateBlocksFromRegion(region: SystemMessageRegion): SystemMessageRegion {
244
+ const alreadyRenderedTexts = new Set<string>();
245
+ const uniqueBlocks: SystemMessageBlock[] = [];
246
+ let isMergeSeamPending = false;
247
+
248
+ for (const block of region.blocks) {
249
+ if (alreadyRenderedTexts.has(block.text)) {
250
+ // Note: A removed block must still hand its merge seam over to the next block which survives
251
+ isMergeSeamPending = isMergeSeamPending || block.isOpeningMergedSection;
252
+ continue;
253
+ }
254
+
255
+ alreadyRenderedTexts.add(block.text);
256
+ uniqueBlocks.push({
257
+ text: block.text,
258
+ isOpeningMergedSection: block.isOpeningMergedSection || isMergeSeamPending,
259
+ });
260
+ isMergeSeamPending = false;
261
+ }
262
+
263
+ return { ...region, blocks: uniqueBlocks };
264
+ }
265
+
266
+ /**
267
+ * Renders one region back into its markdown representation
268
+ *
269
+ * @param region The region to render
270
+ * @returns The heading line together with its joined blocks
271
+ *
272
+ * @private internal utility of `deduplicateSystemMessage`
273
+ */
274
+ function renderRegion(region: SystemMessageRegion): string {
275
+ const body = joinBlocks(region.blocks);
276
+
277
+ if (region.headingLine === null) {
278
+ return body;
279
+ }
280
+
281
+ if (body === '') {
282
+ return region.headingLine;
283
+ }
284
+
285
+ return `${region.headingLine}${region.isBodySeparatedByBlankLine ? '\n\n' : '\n'}${body}`;
286
+ }
287
+
288
+ /**
289
+ * Joins blocks of one region back together
290
+ *
291
+ * @param blocks The blocks to join in their original order
292
+ * @returns The joined body of one region
293
+ *
294
+ * @private internal utility of `renderRegion`
295
+ */
296
+ function joinBlocks(blocks: ReadonlyArray<SystemMessageBlock>): string {
297
+ return blocks
298
+ .map((block, index) =>
299
+ index === 0 ? block.text : `${createBlockSeparator(blocks[index - 1]!, block)}${block.text}`,
300
+ )
301
+ .join('');
302
+ }
303
+
304
+ /**
305
+ * Chooses the separator between two neighboring blocks
306
+ *
307
+ * Blocks are normally separated by a blank line, exactly as they were written. Only where two sections were
308
+ * merged together their lists are joined into one compact list instead of two separate ones.
309
+ *
310
+ * @param previousBlock The block rendered before the separator
311
+ * @param nextBlock The block rendered after the separator
312
+ * @returns Either a single or a double newline
313
+ *
314
+ * @private internal utility of `joinBlocks`
315
+ */
316
+ function createBlockSeparator(previousBlock: SystemMessageBlock, nextBlock: SystemMessageBlock): string {
317
+ if (!nextBlock.isOpeningMergedSection) {
318
+ return '\n\n';
319
+ }
320
+
321
+ const previousBlockLines = previousBlock.text.split('\n');
322
+ const isListContinuing =
323
+ LIST_ITEM_PATTERN.test(previousBlockLines[previousBlockLines.length - 1]!) &&
324
+ LIST_ITEM_PATTERN.test(nextBlock.text.split('\n')[0]!);
325
+
326
+ return isListContinuing ? '\n' : '\n\n';
327
+ }
@@ -0,0 +1,132 @@
1
+ import type { string_book } from './string_book';
2
+
3
+ /**
4
+ * Commitment keyword that declares the parent agent of one book.
5
+ *
6
+ * @private utility of Agents Server inheritance resolution
7
+ */
8
+ const FROM_COMMITMENT_TYPE = 'FROM';
9
+
10
+ /**
11
+ * Matches one standalone `FROM` commitment line and captures its inline content.
12
+ *
13
+ * The Book parser accepts horizontal whitespace between a commitment keyword and its value, so inheritance resolution
14
+ * must do the same. Keeping this pattern here makes every server-side consumer agree on which `FROM` lines are real.
15
+ *
16
+ * @private utility of Agents Server inheritance resolution
17
+ */
18
+ const FROM_COMMITMENT_LINE_PATTERN = new RegExp(`^${FROM_COMMITMENT_TYPE}(?:\\s+(.*))?$`);
19
+
20
+ /**
21
+ * One explicit `FROM` commitment written in a book.
22
+ *
23
+ * @private utility of Agents Server inheritance resolution
24
+ */
25
+ export type ExplicitFromCommitment = {
26
+ /**
27
+ * Zero-based index of the line the commitment was found on, within the scanned lines.
28
+ */
29
+ readonly lineIndex: number;
30
+
31
+ /**
32
+ * Trimmed commitment content, empty string for a blank `FROM`.
33
+ */
34
+ readonly content: string;
35
+ };
36
+
37
+ /**
38
+ * Collects every explicit single-line `FROM` commitment of one book.
39
+ *
40
+ * This lightweight parser is intentionally limited to the subset needed by inheritance
41
+ * resolution so it stays safe to bundle into the Next.js proxy path.
42
+ *
43
+ * @param sourceLines - Book source already split into lines.
44
+ * @returns Found `FROM` commitments in source order, empty when the book declares no parent.
45
+ *
46
+ * @private utility of Agents Server inheritance resolution
47
+ */
48
+ export function collectExplicitFromCommitments(
49
+ sourceLines: ReadonlyArray<string>,
50
+ ): ReadonlyArray<ExplicitFromCommitment> {
51
+ const explicitFromCommitments: Array<ExplicitFromCommitment> = [];
52
+ let hasSeenTitle = false;
53
+ let isInsideCodeBlock = false;
54
+
55
+ for (let lineIndex = 0; lineIndex < sourceLines.length; lineIndex++) {
56
+ const trimmedLine = (sourceLines[lineIndex] || '').trim();
57
+
58
+ if (!hasSeenTitle) {
59
+ if (!trimmedLine) {
60
+ continue;
61
+ }
62
+
63
+ hasSeenTitle = true;
64
+ continue;
65
+ }
66
+
67
+ if (trimmedLine.startsWith('```')) {
68
+ isInsideCodeBlock = !isInsideCodeBlock;
69
+ continue;
70
+ }
71
+
72
+ if (isInsideCodeBlock) {
73
+ continue;
74
+ }
75
+
76
+ const fromCommitmentMatch = FROM_COMMITMENT_LINE_PATTERN.exec(trimmedLine);
77
+ if (fromCommitmentMatch) {
78
+ explicitFromCommitments.push({
79
+ lineIndex,
80
+ content: (fromCommitmentMatch[1] || '').trim(),
81
+ });
82
+ }
83
+ }
84
+
85
+ return explicitFromCommitments;
86
+ }
87
+
88
+ /**
89
+ * Returns the effective explicit `FROM` commitment of one book.
90
+ *
91
+ * A book may repeat `FROM`, in which case the last one wins and overrides every earlier one.
92
+ *
93
+ * @param agentSource - Raw book source.
94
+ * @returns The last explicit `FROM` commitment, or `undefined` when the book declares no parent.
95
+ *
96
+ * @private utility of Agents Server inheritance resolution
97
+ */
98
+ export function getEffectiveExplicitFromCommitment(agentSource: string_book): ExplicitFromCommitment | undefined {
99
+ const explicitFromCommitments = collectExplicitFromCommitments(agentSource.split(/\r?\n/));
100
+ return explicitFromCommitments[explicitFromCommitments.length - 1];
101
+ }
102
+
103
+ /**
104
+ * Returns the effective explicit `FROM` commitment content of one book.
105
+ *
106
+ * A book may repeat `FROM`, in which case the last one wins and overrides every earlier one.
107
+ *
108
+ * @param agentSource - Raw book source.
109
+ * @returns Trimmed commitment content, empty string for a blank explicit `FROM`, or `undefined` when `FROM` is absent.
110
+ *
111
+ * @private utility of Agents Server inheritance resolution
112
+ */
113
+ export function getExplicitFromCommitmentContent(agentSource: string_book): string | undefined {
114
+ return getEffectiveExplicitFromCommitment(agentSource)?.content;
115
+ }
116
+
117
+ /**
118
+ * Returns true when one book declares no parent at all and therefore implicitly inherits from `@Adam`.
119
+ *
120
+ * Writing no `FROM` commitment is equivalent to writing `FROM @Adam`, so only an explicit
121
+ * `FROM @Null` / `FROM {Void}` turns the inheritance off.
122
+ *
123
+ * @param agentSource - Raw book source.
124
+ * @returns True when the implicit `@Adam` ancestor applies to this book.
125
+ *
126
+ * @private utility of Agents Server inheritance resolution
127
+ */
128
+ export function isImplicitAdamInheritance(agentSource: string_book): boolean {
129
+ return getExplicitFromCommitmentContent(agentSource) === undefined;
130
+ }
131
+
132
+ // Note: [💞] Ignore a discrepancy between file name and exported helper names.
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Matched compact agent reference token inside commitment content.
3
+ *
4
+ * @private internal utility of agent reference resolution
5
+ */
6
+ export type AgentReferenceTokenMatch = {
7
+ /**
8
+ * Original token text, for example `{Agent Name}` or `@AgentId`.
9
+ */
10
+ readonly token: string;
11
+
12
+ /**
13
+ * Token payload used for lookup.
14
+ */
15
+ readonly reference: string;
16
+
17
+ /**
18
+ * Zero-based character offset of `token` in the scanned content.
19
+ */
20
+ readonly index: number;
21
+
22
+ /**
23
+ * Length of `token` in characters.
24
+ */
25
+ readonly length: number;
26
+ };
27
+
28
+ /**
29
+ * Matches supported compact reference token syntaxes in commitment content.
30
+ */
31
+ const AGENT_REFERENCE_TOKEN_REGEX = /(\{([^}]+)\}|@([A-Za-z0-9_-]+))/g;
32
+
33
+ /**
34
+ * Extracts compact agent-reference tokens from commitment content.
35
+ *
36
+ * @param content - Commitment content to inspect.
37
+ * @returns Matched compact reference tokens.
38
+ *
39
+ * @private internal utility of agent reference resolution
40
+ */
41
+ export function extractAgentReferenceTokens(content: string): Array<AgentReferenceTokenMatch> {
42
+ const matches: Array<AgentReferenceTokenMatch> = [];
43
+ let match: RegExpExecArray | null;
44
+
45
+ AGENT_REFERENCE_TOKEN_REGEX.lastIndex = 0;
46
+
47
+ while ((match = AGENT_REFERENCE_TOKEN_REGEX.exec(content)) !== null) {
48
+ const token = match[1];
49
+ const reference = (match[2] ?? match[3] ?? '').trim();
50
+ const index = match.index;
51
+
52
+ if (!token || index === undefined) {
53
+ continue;
54
+ }
55
+
56
+ matches.push({
57
+ token,
58
+ reference,
59
+ index,
60
+ length: token.length,
61
+ });
62
+ }
63
+
64
+ return matches;
65
+ }