@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,841 @@
1
+ import type { string_agent_url } from '../../types/string_agent_url';
2
+ import type { string_book } from './string_book';
3
+ import type { AgentReferenceResolver } from './AgentReferenceResolver';
4
+ import { padBook } from './padBook';
5
+ import { isVoidPseudoAgentReference } from './pseudoAgentReferences';
6
+ import { validateBook } from './string_book';
7
+ import { ParseError } from '../../errors/ParseError';
8
+ import { spaceTrim } from '../../utils/organization/spaceTrim';
9
+ import { isValidAgentUrl } from '../../utils/validators/url/isValidAgentUrl';
10
+ import {
11
+ type AgentReferenceResolutionIssue,
12
+ consumeAgentReferenceResolutionIssues,
13
+ } from './AgentReferenceResolutionIssue';
14
+ import { collectExplicitFromCommitments, getExplicitFromCommitmentContent } from './explicitFromCommitment';
15
+
16
+ /**
17
+ * Gets the corpus of an agent source (removes title and trailing status)
18
+ *
19
+ * @param agentSource The agent source
20
+ * @returns The agent source corpus
21
+ */
22
+ function getAgentSourceCorpus(agentSource: string_book): string {
23
+ // Remove trailing OPEN or CLOSED if present
24
+ const agentSourceWithoutStatus = agentSource.replace(/\n?(OPEN|CLOSED)\s*$/i, '') as string_book;
25
+ // <- TODO: [🈲] Simple and encapsulated way to get book corpus
26
+
27
+ // Remove the first line (title) from agent source
28
+ const agentSourceCorpus = spaceTrim(agentSourceWithoutStatus.replace(/^.*$/m, ''));
29
+ // <- TODO: [🈲] Simple and encapsulated way to get book corpus
30
+
31
+ return agentSourceCorpus;
32
+ }
33
+
34
+ /**
35
+ * Formats a resolver issue into a visible NOTE line in resolved agent source.
36
+ *
37
+ * @param issue - Tracked missing-reference issue.
38
+ * @returns Single-line NOTE statement.
39
+ */
40
+ function formatResolutionIssueAsNote(issue: AgentReferenceResolutionIssue): string {
41
+ const commitmentType = issue.commitmentType === 'IMPORTS' ? 'IMPORT' : issue.commitmentType;
42
+
43
+ if (commitmentType === 'FROM') {
44
+ return `NOTE Referenced agent "${issue.reference}" in FROM commitment was not found. Inheritance skipped.`;
45
+ }
46
+
47
+ if (commitmentType === 'IMPORT') {
48
+ return `NOTE Referenced agent "${issue.reference}" in IMPORT commitment was not found. Import skipped.`;
49
+ }
50
+
51
+ if (commitmentType === 'TEAM') {
52
+ return `NOTE Referenced agent "${issue.reference}" in TEAM commitment was not found. Teammate disabled.`;
53
+ }
54
+
55
+ return `NOTE Referenced agent "${issue.reference}" in ${commitmentType} commitment was not found.`;
56
+ }
57
+
58
+ /**
59
+ * Appends NOTE lines for unresolved references while avoiding duplicates.
60
+ *
61
+ * @param targetChunks - Output chunks being assembled for the resolved book.
62
+ * @param issues - Missing-reference issues to materialize into NOTE lines.
63
+ */
64
+ function appendResolutionIssueNotes(
65
+ targetChunks: Array<string>,
66
+ issues: ReadonlyArray<AgentReferenceResolutionIssue>,
67
+ ): void {
68
+ const seenIssueKeys = new Set<string>();
69
+
70
+ for (const issue of issues) {
71
+ const key = `${issue.commitmentType}:${issue.reference}`.toLowerCase();
72
+ if (seenIssueKeys.has(key)) {
73
+ continue;
74
+ }
75
+
76
+ seenIssueKeys.add(key);
77
+ targetChunks.push(formatResolutionIssueAsNote(issue), '');
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Inserts NOTE lines right after the title line in a book.
83
+ *
84
+ * @param agentSource - Original agent source.
85
+ * @param notes - NOTE lines to insert.
86
+ * @returns Updated book with notes placed after the title.
87
+ */
88
+ function insertNotesAfterTitle(agentSource: string_book, notes: ReadonlyArray<string>): string_book {
89
+ if (notes.length === 0) {
90
+ return agentSource;
91
+ }
92
+
93
+ const sourceLines = spaceTrim(agentSource).split(/\r?\n/);
94
+ const titleLine = sourceLines[0] || '';
95
+ const restLines = sourceLines.slice(1);
96
+ const nextLines = [titleLine, '', ...notes, '', ...restLines];
97
+
98
+ return padBook(validateBook(nextLines.join('\n')));
99
+ }
100
+
101
+ /**
102
+ * Resolves the effective `FROM` parent URL using only lightweight commitment parsing.
103
+ *
104
+ * @param rawAgentSource - Original source used for diagnostics.
105
+ * @param agentReferenceResolver - Optional compact-reference resolver.
106
+ * @returns Valid parent URL, explicit `null` for `FROM VOID`/blank `FROM`, or `undefined` when `FROM` is absent.
107
+ */
108
+ async function resolveParentAgentUrlFromCommitments(
109
+ rawAgentSource: string_book,
110
+ agentReferenceResolver?: AgentReferenceResolver,
111
+ ): Promise<string_agent_url | null | undefined> {
112
+ const explicitFromContent = getExplicitFromCommitmentContent(rawAgentSource);
113
+
114
+ if (explicitFromContent === undefined) {
115
+ return undefined;
116
+ }
117
+
118
+ let resolvedParentReference = explicitFromContent.trim();
119
+
120
+ if (agentReferenceResolver && resolvedParentReference) {
121
+ resolvedParentReference = (
122
+ await agentReferenceResolver.resolveCommitmentContent('FROM', resolvedParentReference)
123
+ ).trim();
124
+ }
125
+
126
+ if (!resolvedParentReference || isVoidPseudoAgentReference(resolvedParentReference)) {
127
+ return null;
128
+ }
129
+
130
+ if (!isValidAgentUrl(resolvedParentReference)) {
131
+ throw new ParseError(
132
+ spaceTrim(
133
+ (block) => `
134
+ Invalid parent agent URL in FROM "${resolvedParentReference}" commitment:
135
+
136
+ \`\`\`book
137
+ ${block(rawAgentSource)}
138
+ \`\`\`
139
+
140
+ `,
141
+ ),
142
+ );
143
+ }
144
+
145
+ return resolvedParentReference as string_agent_url;
146
+ }
147
+
148
+ /**
149
+ * Shared options for resolving one agent source with inheritance and imports applied.
150
+ *
151
+ * @private internal utility of agent source resolution
152
+ */
153
+ export type ResolveInheritedAgentSourceOptions = {
154
+ /** Current recursion depth, forwarded to the source importer. */
155
+ readonly recursionLevel?: number;
156
+ /**
157
+ * The URL of the Adam agent to use as the default ancestor
158
+ *
159
+ * @default 'https://core.ptbk.io/agents/adam'
160
+ */
161
+ readonly adamAgentUrl?: string_agent_url;
162
+ /**
163
+ * Custom resolver used to expand compact agent references.
164
+ */
165
+ readonly agentReferenceResolver?: AgentReferenceResolver;
166
+ /**
167
+ * Canonical URL of the currently resolved agent.
168
+ */
169
+ readonly currentAgentUrl?: string_agent_url;
170
+ /**
171
+ * Additional equivalent URLs that should be treated as the current agent while detecting cycles.
172
+ */
173
+ readonly currentAgentAliases?: ReadonlyArray<string_agent_url>;
174
+ /**
175
+ * Already visited agent URLs in the current resolution stack.
176
+ */
177
+ readonly inheritancePath?: ReadonlyArray<string_agent_url>;
178
+ /**
179
+ * Source importer supplied by the host application.
180
+ */
181
+ readonly agentSourceImporter: AgentSourceImporter;
182
+ };
183
+
184
+ /**
185
+ * Context passed to a custom agent source importer.
186
+ *
187
+ * @private internal utility of agent source resolution
188
+ */
189
+ export type AgentSourceImporterContext = {
190
+ /**
191
+ * Commitment that requested the imported source.
192
+ */
193
+ readonly commitmentType: 'FROM' | 'IMPORT';
194
+
195
+ /**
196
+ * Import options propagated from the current resolution pass.
197
+ */
198
+ readonly importAgentOptions: {
199
+ readonly recursionLevel?: number;
200
+ readonly inheritancePath?: ReadonlyArray<string_agent_url>;
201
+ };
202
+ };
203
+
204
+ /**
205
+ * Loads and recursively resolves one referenced agent using the caller's transport.
206
+ *
207
+ * @private internal utility of agent source resolution
208
+ */
209
+ export type AgentSourceImporter = (
210
+ agentUrl: string_agent_url,
211
+ context: AgentSourceImporterContext,
212
+ ) => Promise<string_book>;
213
+
214
+ /**
215
+ * Parent inheritance state derived before rewriting the source body.
216
+ */
217
+ type ResolvedParentAgentContext = {
218
+ /**
219
+ * Effective parent URL after resolving explicit `FROM`, `FROM VOID`, or implicit Adam fallback.
220
+ */
221
+ readonly parentAgentUrl: string_agent_url | null;
222
+
223
+ /**
224
+ * Parent source body without title/status, ready to be embedded into the child source.
225
+ */
226
+ readonly parentAgentSourceCorpus: string | null;
227
+
228
+ /**
229
+ * Unresolved compact-reference issues captured while resolving `FROM`.
230
+ */
231
+ readonly fromResolutionIssues: Array<AgentReferenceResolutionIssue>;
232
+
233
+ /**
234
+ * Whether the effective parent closed the current lineage and must be materialized as `FROM @Null`.
235
+ */
236
+ readonly isCyclicFrom: boolean;
237
+ };
238
+
239
+ /**
240
+ * Shared import settings reused for parent and imported-agent loading.
241
+ */
242
+ type AgentImportContext = {
243
+ /**
244
+ * Adam agent URL used as the implicit default ancestor.
245
+ */
246
+ readonly adamAgentUrl: string_agent_url;
247
+
248
+ /**
249
+ * Resolver used to expand compact references in commitments.
250
+ */
251
+ readonly agentReferenceResolver?: AgentReferenceResolver;
252
+
253
+ /**
254
+ * Import options propagated to downstream agent loading.
255
+ */
256
+ readonly importAgentOptions: {
257
+ readonly recursionLevel?: number;
258
+ readonly inheritancePath?: ReadonlyArray<string_agent_url>;
259
+ };
260
+
261
+ /**
262
+ * Importer supplied by the host application.
263
+ */
264
+ readonly agentSourceImporter: AgentSourceImporter;
265
+
266
+ /**
267
+ * Original resolution options used for cycle detection.
268
+ */
269
+ readonly resolutionOptions: ResolveInheritedAgentSourceOptions;
270
+ };
271
+
272
+ /**
273
+ * Result of rewriting the source line by line before final validation.
274
+ */
275
+ type ResolvedAgentSourceBuild = {
276
+ /**
277
+ * Rewritten source lines.
278
+ */
279
+ readonly agentSourceChunks: Array<string>;
280
+
281
+ /**
282
+ * Whether one explicit `FROM ...` line was materialized during rewriting.
283
+ */
284
+ readonly isFromResolved: boolean;
285
+
286
+ /**
287
+ * Remaining unresolved `FROM` issues that still need to be turned into NOTE lines.
288
+ */
289
+ readonly fromResolutionIssues: Array<AgentReferenceResolutionIssue>;
290
+ };
291
+
292
+ /**
293
+ * Result of handling one explicit `FROM ...` line.
294
+ */
295
+ type ResolvedFromCommitmentLine = {
296
+ /**
297
+ * Output lines produced for the `FROM` commitment.
298
+ */
299
+ readonly agentSourceChunks: Array<string>;
300
+
301
+ /**
302
+ * Remaining `FROM` issues after the line was handled.
303
+ */
304
+ readonly fromResolutionIssues: Array<AgentReferenceResolutionIssue>;
305
+ };
306
+
307
+ /**
308
+ * One cycle found while following a source-resolution lineage.
309
+ *
310
+ * @private internal helper for Agents Server inherited/imported agent resolution
311
+ */
312
+ type ResolutionCycle = {
313
+ /**
314
+ * Normalized URL that closes the cycle.
315
+ */
316
+ readonly normalizedReferenceUrl: string_agent_url;
317
+
318
+ /**
319
+ * URLs visited before the repeated reference was encountered.
320
+ */
321
+ readonly lineage: ReadonlyArray<string_agent_url>;
322
+
323
+ /**
324
+ * Position in `lineage` where the cycle begins.
325
+ */
326
+ readonly cycleStartIndex: number;
327
+ };
328
+
329
+ /**
330
+ * Normalizes agent URLs used for cycle detection and lineage reporting.
331
+ *
332
+ * @param agentUrl - Raw agent URL.
333
+ * @returns URL without trailing slashes.
334
+ */
335
+ function normalizeAgentUrl(agentUrl: string_agent_url): string_agent_url {
336
+ return agentUrl.replace(/\/+$/g, '') as string_agent_url;
337
+ }
338
+
339
+ /**
340
+ * Builds the current resolution lineage ending with the agent being resolved now.
341
+ *
342
+ * @param options - Resolution options with current agent metadata.
343
+ * @returns Ordered lineage without empty values.
344
+ */
345
+ function createResolutionLineage(options?: ResolveInheritedAgentSourceOptions): Array<string_agent_url> {
346
+ const lineage = [...(options?.inheritancePath || [])];
347
+
348
+ if (options?.currentAgentUrl) {
349
+ lineage.push(options.currentAgentUrl);
350
+ }
351
+
352
+ for (const alias of options?.currentAgentAliases || []) {
353
+ lineage.push(alias);
354
+ }
355
+
356
+ return [...new Set(lineage.map(normalizeAgentUrl))];
357
+ }
358
+
359
+ /**
360
+ * Finds a cycle that would be closed by resolving one more agent reference.
361
+ *
362
+ * @param referenceUrl - Next agent reference to resolve.
363
+ * @param options - Current resolution options.
364
+ * @returns Cycle details, or `null` when the reference is not in the current lineage.
365
+ *
366
+ * @private internal helper for Agents Server inherited/imported agent resolution
367
+ */
368
+ function findResolutionCycle(
369
+ referenceUrl: string_agent_url,
370
+ options: ResolveInheritedAgentSourceOptions,
371
+ ): ResolutionCycle | null {
372
+ const normalizedReferenceUrl = normalizeAgentUrl(referenceUrl);
373
+ const lineage = createResolutionLineage(options);
374
+ const cycleStartIndex = lineage.findIndex((visitedUrl) => visitedUrl === normalizedReferenceUrl);
375
+
376
+ if (cycleStartIndex === -1) {
377
+ return null;
378
+ }
379
+
380
+ return { normalizedReferenceUrl, lineage, cycleStartIndex };
381
+ }
382
+
383
+ /**
384
+ * Creates the shared import context used throughout one resolution pass.
385
+ *
386
+ * @param options - Resolution options passed to `resolveInheritedAgentSource`.
387
+ * @returns Normalized shared import context.
388
+ */
389
+ function createAgentImportContext(options: ResolveInheritedAgentSourceOptions): AgentImportContext {
390
+ return {
391
+ adamAgentUrl: options?.adamAgentUrl || 'https://core.ptbk.io/agents/adam',
392
+ agentReferenceResolver: options?.agentReferenceResolver,
393
+ importAgentOptions: {
394
+ recursionLevel: options?.recursionLevel || 0,
395
+ inheritancePath: createResolutionLineage(options),
396
+ },
397
+ agentSourceImporter: options.agentSourceImporter,
398
+ resolutionOptions: options,
399
+ };
400
+ }
401
+
402
+ /**
403
+ * Computes the effective parent URL after applying explicit `FROM` and Adam fallback rules.
404
+ *
405
+ * @param resolvedParentAgentUrl - Parent URL derived from lightweight commitment parsing.
406
+ * @param hasExplicitFromCommitment - Whether the source explicitly declared `FROM`.
407
+ * @param adamAgentUrl - Default Adam ancestor URL.
408
+ * @param currentAgentUrl - Canonical URL of the source being resolved.
409
+ * @param currentAgentAliases - Additional URLs that identify the source being resolved.
410
+ * @param agentSource - Raw source used for unexpected diagnostics.
411
+ * @returns Effective parent URL or `null` when inheritance is disabled.
412
+ */
413
+ function determineParentAgentUrl(
414
+ resolvedParentAgentUrl: string_agent_url | null | undefined,
415
+ hasExplicitFromCommitment: boolean,
416
+ adamAgentUrl: string_agent_url,
417
+ currentAgentUrl?: string_agent_url,
418
+ currentAgentAliases?: ReadonlyArray<string_agent_url>,
419
+ agentSource?: string_book,
420
+ ): string_agent_url | null {
421
+ if (isValidAgentUrl(resolvedParentAgentUrl)) {
422
+ return resolvedParentAgentUrl as string_agent_url;
423
+ }
424
+
425
+ if (resolvedParentAgentUrl === null && hasExplicitFromCommitment) {
426
+ return null;
427
+ }
428
+
429
+ if (resolvedParentAgentUrl === undefined || resolvedParentAgentUrl === null) {
430
+ const currentAgentUrls = [currentAgentUrl, ...(currentAgentAliases || [])].filter(
431
+ (agentUrl): agentUrl is string_agent_url => Boolean(agentUrl),
432
+ );
433
+ const isCurrentAgentAdam = currentAgentUrls.some(
434
+ (agentUrl) => normalizeAgentUrl(agentUrl) === normalizeAgentUrl(adamAgentUrl),
435
+ );
436
+
437
+ return isCurrentAgentAdam ? null : adamAgentUrl;
438
+ }
439
+
440
+ throw new ParseError(
441
+ spaceTrim(
442
+ (block) => `
443
+ Invalid parent agent URL in FROM "${resolvedParentAgentUrl}" commitment:
444
+
445
+ \`\`\`book
446
+ ${block(agentSource || '')}
447
+ \`\`\`
448
+ `,
449
+ ),
450
+ );
451
+ }
452
+
453
+ /**
454
+ * Imports one referenced agent and returns only its body content ready to be embedded as a NOTE block.
455
+ *
456
+ * @param agentUrl - Canonical referenced agent URL.
457
+ * @param commitmentType - Commitment that introduced the reference.
458
+ * @param context - Shared import context for the current resolution pass.
459
+ * @returns Imported source body without title/status.
460
+ */
461
+ async function importAgentCorpus(
462
+ agentUrl: string_agent_url,
463
+ commitmentType: 'FROM' | 'IMPORT',
464
+ context: AgentImportContext,
465
+ ): Promise<string> {
466
+ assertNoResolutionCycle(agentUrl, commitmentType, context.resolutionOptions);
467
+
468
+ const importedAgentSource = await context.agentSourceImporter(agentUrl, {
469
+ commitmentType,
470
+ importAgentOptions: context.importAgentOptions,
471
+ });
472
+
473
+ return getAgentSourceCorpus(importedAgentSource as string_book);
474
+ }
475
+
476
+ /**
477
+ * Resolves the effective parent state before the source body is rewritten.
478
+ *
479
+ * @param agentSource - Raw child agent source.
480
+ * @param context - Shared import context for the current resolution pass.
481
+ * @returns Parent URL, imported parent body, and unresolved `FROM` issues.
482
+ */
483
+ async function resolveParentAgentContext(
484
+ agentSource: string_book,
485
+ context: AgentImportContext,
486
+ ): Promise<ResolvedParentAgentContext> {
487
+ const explicitFromContent = getExplicitFromCommitmentContent(agentSource);
488
+ const hasExplicitFromCommitment = explicitFromContent !== undefined;
489
+ const resolvedParentAgentUrl = await resolveParentAgentUrlFromCommitments(
490
+ agentSource,
491
+ context.agentReferenceResolver,
492
+ );
493
+ const fromResolutionIssues = consumeAgentReferenceResolutionIssues(context.agentReferenceResolver).filter(
494
+ (issue) => issue.commitmentType === 'FROM',
495
+ );
496
+ const parentAgentUrl = determineParentAgentUrl(
497
+ resolvedParentAgentUrl,
498
+ hasExplicitFromCommitment,
499
+ context.adamAgentUrl,
500
+ context.resolutionOptions?.currentAgentUrl,
501
+ context.resolutionOptions?.currentAgentAliases,
502
+ agentSource,
503
+ );
504
+ const isCyclicFrom = Boolean(parentAgentUrl && findResolutionCycle(parentAgentUrl, context.resolutionOptions));
505
+ const effectiveParentAgentUrl = isCyclicFrom ? null : parentAgentUrl;
506
+ const parentAgentSourceCorpus = effectiveParentAgentUrl
507
+ ? await importAgentCorpus(effectiveParentAgentUrl, 'FROM', context)
508
+ : null;
509
+
510
+ return {
511
+ // A parent that closes the current lineage has the same runtime effect as `FROM @Null`.
512
+ parentAgentUrl: effectiveParentAgentUrl,
513
+ parentAgentSourceCorpus,
514
+ fromResolutionIssues,
515
+ isCyclicFrom,
516
+ };
517
+ }
518
+
519
+ /**
520
+ * Formats one embedded agent body into the NOTE block used by inheritance/import resolution.
521
+ *
522
+ * @param noteLine - First NOTE line describing the embedded source.
523
+ * @param sourceCorpus - Imported source body to embed.
524
+ * @returns Multi-line NOTE block.
525
+ */
526
+ function createEmbeddedAgentSourceNote(noteLine: string, sourceCorpus: string): string {
527
+ return spaceTrim(
528
+ (block) => `
529
+
530
+ ${noteLine}
531
+ ${block(sourceCorpus)}
532
+
533
+ NOTE ===========
534
+ `,
535
+ );
536
+ }
537
+
538
+ /**
539
+ * Resolves one `IMPORT ...` line into embedded source or leaves non-agent imports untouched.
540
+ *
541
+ * @param line - Current source line.
542
+ * @param context - Shared import context for the current resolution pass.
543
+ * @returns Output lines replacing the original line.
544
+ */
545
+ async function resolveImportCommitmentLine(line: string, context: AgentImportContext): Promise<Array<string>> {
546
+ const rawImportedUrlOrPath = line
547
+ .trim()
548
+ .replace(/^IMPORTS?\s+/, '')
549
+ .trim();
550
+ let importedUrlOrPath = rawImportedUrlOrPath;
551
+ let importResolutionIssues: Array<AgentReferenceResolutionIssue> = [];
552
+
553
+ if (context.agentReferenceResolver && rawImportedUrlOrPath) {
554
+ try {
555
+ importedUrlOrPath = await context.agentReferenceResolver.resolveCommitmentContent(
556
+ 'IMPORT',
557
+ rawImportedUrlOrPath,
558
+ );
559
+ } catch (error) {
560
+ console.warn('[AgentReferenceResolver] Failed to resolve IMPORT commitment references:', error);
561
+ } finally {
562
+ importResolutionIssues = consumeAgentReferenceResolutionIssues(context.agentReferenceResolver).filter(
563
+ (issue) => issue.commitmentType === 'IMPORT' || issue.commitmentType === 'IMPORTS',
564
+ );
565
+ }
566
+ }
567
+
568
+ const resolvedChunks: Array<string> = [];
569
+ appendResolutionIssueNotes(resolvedChunks, importResolutionIssues);
570
+
571
+ if (!importedUrlOrPath) {
572
+ return resolvedChunks;
573
+ }
574
+
575
+ if (!isValidAgentUrl(importedUrlOrPath)) {
576
+ resolvedChunks.push(line);
577
+ return resolvedChunks;
578
+ }
579
+
580
+ const importedAgentUrl = importedUrlOrPath as string_agent_url;
581
+ const importedAgentSourceCorpus = await importAgentCorpus(importedAgentUrl, 'IMPORT', context);
582
+
583
+ resolvedChunks.push(
584
+ createEmbeddedAgentSourceNote(`NOTE Imported from ${importedAgentUrl}`, importedAgentSourceCorpus),
585
+ '', // <- Note: Add an extra newline for separation
586
+ );
587
+
588
+ return resolvedChunks;
589
+ }
590
+
591
+ /**
592
+ * Resolves the effective `FROM ...` line into inherited content or fallback NOTE lines.
593
+ *
594
+ * @param line - Current source line.
595
+ * @param parentContext - Effective parent state computed earlier in the resolution.
596
+ * @returns Output lines and remaining `FROM` issues.
597
+ */
598
+ function resolveFromCommitmentLine(
599
+ line: string,
600
+ parentContext: ResolvedParentAgentContext,
601
+ ): ResolvedFromCommitmentLine {
602
+ if (parentContext.parentAgentUrl === null) {
603
+ const agentSourceChunks = [parentContext.isCyclicFrom ? 'FROM @Null' : line];
604
+
605
+ if (parentContext.fromResolutionIssues.length > 0) {
606
+ appendResolutionIssueNotes(agentSourceChunks, parentContext.fromResolutionIssues);
607
+ }
608
+
609
+ return {
610
+ agentSourceChunks,
611
+ fromResolutionIssues: [],
612
+ };
613
+ }
614
+
615
+ if (parentContext.parentAgentSourceCorpus) {
616
+ return {
617
+ agentSourceChunks: [
618
+ createEmbeddedAgentSourceNote(
619
+ `NOTE Inherited FROM ${parentContext.parentAgentUrl}`,
620
+ parentContext.parentAgentSourceCorpus,
621
+ ),
622
+ '', // <- Note: Add an extra newline for separation
623
+ ],
624
+ fromResolutionIssues: parentContext.fromResolutionIssues,
625
+ };
626
+ }
627
+
628
+ return {
629
+ agentSourceChunks: [
630
+ `NOTE Parent agent "${parentContext.parentAgentUrl}" was not found or could not be loaded. Inheritance skipped.`,
631
+ '',
632
+ ],
633
+ fromResolutionIssues: parentContext.fromResolutionIssues,
634
+ };
635
+ }
636
+
637
+ /**
638
+ * Finds the `FROM` lines that a later `FROM` takes precedence over.
639
+ *
640
+ * A book is allowed to repeat `FROM`; the last one wins, so every earlier one is dropped instead of being resolved.
641
+ * The Book editor warns about this while the author is still writing the book.
642
+ *
643
+ * @param explicitFromCommitments - Explicit parent declarations found in source order.
644
+ * @returns Zero-based indexes of every overridden `FROM` line, empty when at most one `FROM` is present.
645
+ */
646
+ function collectOverriddenFromLineIndexes(
647
+ explicitFromCommitments: ReadonlyArray<{ readonly lineIndex: number }>,
648
+ ): ReadonlySet<number> {
649
+ return new Set(
650
+ explicitFromCommitments.slice(0, -1).map((explicitFromCommitment) => explicitFromCommitment.lineIndex),
651
+ );
652
+ }
653
+
654
+ /**
655
+ * Rewrites the source body line by line while delegating each branching step to a focused helper.
656
+ *
657
+ * @param agentSource - Raw child agent source.
658
+ * @param context - Shared import context for the current resolution pass.
659
+ * @param parentContext - Effective parent state computed earlier in the resolution.
660
+ * @returns Rewritten chunks plus final `FROM` bookkeeping.
661
+ */
662
+ async function resolveAgentSourceBuild(
663
+ agentSource: string_book,
664
+ context: AgentImportContext,
665
+ parentContext: ResolvedParentAgentContext,
666
+ ): Promise<ResolvedAgentSourceBuild> {
667
+ const agentSourceChunks = spaceTrim(agentSource).split(/\r?\n/);
668
+ const resolvedAgentSourceChunks: Array<string> = [];
669
+ const explicitFromCommitments = collectExplicitFromCommitments(agentSourceChunks);
670
+ const effectiveFromCommitment = explicitFromCommitments[explicitFromCommitments.length - 1];
671
+ const overriddenFromLineIndexes = collectOverriddenFromLineIndexes(explicitFromCommitments);
672
+ let isFromResolved = false;
673
+ let isInsideCodeBlock = false;
674
+ let fromResolutionIssues = parentContext.fromResolutionIssues;
675
+ // <- TODO: [🈲] Simple and encapsulated way to split book into commitments
676
+
677
+ for (let lineIndex = 0; lineIndex < agentSourceChunks.length; lineIndex++) {
678
+ const line = agentSourceChunks[lineIndex] || '';
679
+
680
+ if (line.trim().startsWith('```')) {
681
+ isInsideCodeBlock = !isInsideCodeBlock;
682
+ }
683
+
684
+ if (isInsideCodeBlock || line.trim().startsWith('```')) {
685
+ resolvedAgentSourceChunks.push(line);
686
+ continue;
687
+ }
688
+
689
+ // Note: A repeated `FROM` is overridden by the last one, so the outdated line is dropped from the resolved source.
690
+ if (overriddenFromLineIndexes.has(lineIndex)) {
691
+ continue;
692
+ }
693
+
694
+ if (lineIndex > 0 && /^IMPORTS?\s+/.test(line.trim())) {
695
+ resolvedAgentSourceChunks.push(...(await resolveImportCommitmentLine(line, context)));
696
+ continue;
697
+ }
698
+
699
+ if (effectiveFromCommitment?.lineIndex === lineIndex) {
700
+ const resolvedFromCommitment = resolveFromCommitmentLine(line, {
701
+ ...parentContext,
702
+ fromResolutionIssues,
703
+ });
704
+
705
+ resolvedAgentSourceChunks.push(...resolvedFromCommitment.agentSourceChunks);
706
+ fromResolutionIssues = resolvedFromCommitment.fromResolutionIssues;
707
+ isFromResolved = true;
708
+ continue;
709
+ }
710
+
711
+ resolvedAgentSourceChunks.push(line);
712
+ }
713
+ // <- TODO: [🈲] Simple and encapsulated way to split book into commitments
714
+
715
+ return {
716
+ agentSourceChunks: resolvedAgentSourceChunks,
717
+ isFromResolved,
718
+ fromResolutionIssues,
719
+ };
720
+ }
721
+
722
+ /**
723
+ * Materializes the implicit Adam inheritance block when no explicit `FROM ...` was present.
724
+ *
725
+ * @param build - Current rewritten source build.
726
+ * @param parentContext - Effective parent state computed earlier in the resolution.
727
+ * @param adamAgentUrl - Default Adam ancestor URL.
728
+ * @returns Updated source build with implicit Adam inheritance applied when needed.
729
+ */
730
+ function applyImplicitAdamInheritance(
731
+ build: ResolvedAgentSourceBuild,
732
+ parentContext: ResolvedParentAgentContext,
733
+ adamAgentUrl: string_agent_url,
734
+ ): ResolvedAgentSourceBuild {
735
+ if (build.isFromResolved || parentContext.parentAgentUrl !== adamAgentUrl) {
736
+ return build;
737
+ }
738
+
739
+ const titleLine = build.agentSourceChunks[0] || '';
740
+ const restLines = build.agentSourceChunks.slice(1);
741
+ const agentSourceChunks = [titleLine, ''];
742
+
743
+ if (parentContext.parentAgentSourceCorpus) {
744
+ agentSourceChunks.push(
745
+ createEmbeddedAgentSourceNote(
746
+ `NOTE Inherited Adam FROM ${parentContext.parentAgentUrl}`,
747
+ parentContext.parentAgentSourceCorpus,
748
+ ),
749
+ ...restLines,
750
+ );
751
+ } else {
752
+ agentSourceChunks.push(
753
+ `NOTE Default parent agent "${parentContext.parentAgentUrl}" was not found or could not be loaded. Inheritance skipped.`,
754
+ '',
755
+ ...restLines,
756
+ );
757
+ }
758
+
759
+ return {
760
+ ...build,
761
+ agentSourceChunks,
762
+ };
763
+ }
764
+
765
+ /**
766
+ * Validates and pads the resolved source, then inserts any deferred unresolved-`FROM` notes.
767
+ *
768
+ * @param build - Final rewritten source build.
769
+ * @returns Valid resolved book.
770
+ */
771
+ function finalizeResolvedAgentSource(build: ResolvedAgentSourceBuild): string_book {
772
+ const resolvedAgentSource = padBook(validateBook(build.agentSourceChunks.join('\n')));
773
+
774
+ if (build.fromResolutionIssues.length === 0) {
775
+ return resolvedAgentSource;
776
+ }
777
+
778
+ const unresolvedFromNotes = build.fromResolutionIssues.map(formatResolutionIssueAsNote);
779
+ return insertNotesAfterTitle(resolvedAgentSource, unresolvedFromNotes);
780
+ }
781
+
782
+ /**
783
+ * Throws when a `FROM`/`IMPORT` edge would create an inheritance cycle.
784
+ *
785
+ * @param referenceUrl - Next referenced agent URL.
786
+ * @param commitmentType - Commitment that introduced the reference.
787
+ * @param options - Current resolution options.
788
+ */
789
+ function assertNoResolutionCycle(
790
+ referenceUrl: string_agent_url,
791
+ commitmentType: 'FROM' | 'IMPORT',
792
+ options: ResolveInheritedAgentSourceOptions,
793
+ ): void {
794
+ const resolutionCycle = findResolutionCycle(referenceUrl, options);
795
+
796
+ if (!resolutionCycle) {
797
+ return;
798
+ }
799
+
800
+ const cycleChain = [
801
+ ...resolutionCycle.lineage.slice(resolutionCycle.cycleStartIndex),
802
+ resolutionCycle.normalizedReferenceUrl,
803
+ ]
804
+ .map((visitedUrl) => `- \`${visitedUrl}\``)
805
+ .join('\n');
806
+
807
+ throw new ParseError(
808
+ spaceTrim(
809
+ (block) => `
810
+ Cyclic \`${commitmentType}\` reference detected while resolving agent source.
811
+
812
+ Resolution chain:
813
+ ${block(cycleChain)}
814
+ `,
815
+ ),
816
+ );
817
+ }
818
+
819
+ /**
820
+ * Resolves agent source with inheritance (FROM commitment)
821
+ *
822
+ * It recursively fetches the parent agent source and merges it with the current source.
823
+ *
824
+ * @param agentSource The initial agent source
825
+ * @returns The resolved agent source with inheritance applied
826
+ *
827
+ * @private internal utility of agent source resolution
828
+ */
829
+ export async function resolveInheritedAgentSource(
830
+ agentSource: string_book,
831
+ options: ResolveInheritedAgentSourceOptions,
832
+ ): Promise<string_book> {
833
+ const context = createAgentImportContext(options);
834
+ const parentContext = await resolveParentAgentContext(agentSource, context);
835
+ const resolvedBuild = await resolveAgentSourceBuild(agentSource, context, parentContext);
836
+ const finalizedBuild = applyImplicitAdamInheritance(resolvedBuild, parentContext, context.adamAgentUrl);
837
+
838
+ return finalizeResolvedAgentSource(finalizedBuild);
839
+ }
840
+
841
+ // TODO: [🈲] Create a function that can manipulate books by modifying commitments, splitting the book up into commitments or syntactic tokens, and editing or deleting these via object methods.