@cogenta/agents 0.2.1 → 0.5.0

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 (242) hide show
  1. package/dist/agents/builtins.d.ts +51 -0
  2. package/dist/agents/builtins.d.ts.map +1 -0
  3. package/dist/agents/builtins.js +190 -0
  4. package/dist/agents/builtins.js.map +1 -0
  5. package/dist/agents/orchestrator.d.ts +98 -0
  6. package/dist/agents/orchestrator.d.ts.map +1 -0
  7. package/dist/agents/orchestrator.js +286 -0
  8. package/dist/agents/orchestrator.js.map +1 -0
  9. package/dist/agents/store.d.ts +71 -0
  10. package/dist/agents/store.d.ts.map +1 -0
  11. package/dist/agents/store.js +211 -0
  12. package/dist/agents/store.js.map +1 -0
  13. package/dist/agents/types.d.ts +10 -0
  14. package/dist/agents/types.d.ts.map +1 -1
  15. package/dist/assist/agent-identity.d.ts +44 -0
  16. package/dist/assist/agent-identity.d.ts.map +1 -0
  17. package/dist/assist/agent-identity.js +136 -0
  18. package/dist/assist/agent-identity.js.map +1 -0
  19. package/dist/assist/chat.d.ts +2 -0
  20. package/dist/assist/chat.d.ts.map +1 -1
  21. package/dist/assist/chat.js +14 -5
  22. package/dist/assist/chat.js.map +1 -1
  23. package/dist/assist/classify.d.ts +3 -2
  24. package/dist/assist/classify.d.ts.map +1 -1
  25. package/dist/assist/classify.js +30 -11
  26. package/dist/assist/classify.js.map +1 -1
  27. package/dist/assist/faq.d.ts +3 -2
  28. package/dist/assist/faq.d.ts.map +1 -1
  29. package/dist/assist/faq.js +31 -13
  30. package/dist/assist/faq.js.map +1 -1
  31. package/dist/assist/runtime.d.ts +8 -2
  32. package/dist/assist/runtime.d.ts.map +1 -1
  33. package/dist/assist/runtime.js +2 -2
  34. package/dist/assist/runtime.js.map +1 -1
  35. package/dist/assist/toolset.d.ts +10 -0
  36. package/dist/assist/toolset.d.ts.map +1 -1
  37. package/dist/assist/toolset.js +36 -8
  38. package/dist/assist/toolset.js.map +1 -1
  39. package/dist/assist/usage.d.ts +58 -0
  40. package/dist/assist/usage.d.ts.map +1 -0
  41. package/dist/assist/usage.js +53 -0
  42. package/dist/assist/usage.js.map +1 -0
  43. package/dist/assist/writing.d.ts +10 -9
  44. package/dist/assist/writing.d.ts.map +1 -1
  45. package/dist/assist/writing.js +126 -58
  46. package/dist/assist/writing.js.map +1 -1
  47. package/dist/audit/with-audit.d.ts +13 -0
  48. package/dist/audit/with-audit.d.ts.map +1 -1
  49. package/dist/audit/with-audit.js +6 -0
  50. package/dist/audit/with-audit.js.map +1 -1
  51. package/dist/autonomy/levels.d.ts +37 -0
  52. package/dist/autonomy/levels.d.ts.map +1 -0
  53. package/dist/autonomy/levels.js +51 -0
  54. package/dist/autonomy/levels.js.map +1 -0
  55. package/dist/conversations/store.d.ts +62 -0
  56. package/dist/conversations/store.d.ts.map +1 -0
  57. package/dist/conversations/store.js +0 -0
  58. package/dist/conversations/store.js.map +1 -0
  59. package/dist/identity/context.d.ts +8 -0
  60. package/dist/identity/context.d.ts.map +1 -1
  61. package/dist/identity/context.js +19 -4
  62. package/dist/identity/context.js.map +1 -1
  63. package/dist/identity/markdown.d.ts +38 -0
  64. package/dist/identity/markdown.d.ts.map +1 -0
  65. package/dist/identity/markdown.js +85 -0
  66. package/dist/identity/markdown.js.map +1 -0
  67. package/dist/index.d.ts +62 -5
  68. package/dist/index.d.ts.map +1 -1
  69. package/dist/index.js +33 -3
  70. package/dist/index.js.map +1 -1
  71. package/dist/progress/job-store.d.ts +41 -0
  72. package/dist/progress/job-store.d.ts.map +1 -0
  73. package/dist/progress/job-store.js +44 -0
  74. package/dist/progress/job-store.js.map +1 -0
  75. package/dist/progress/types.d.ts +21 -0
  76. package/dist/progress/types.d.ts.map +1 -0
  77. package/dist/progress/types.js +3 -0
  78. package/dist/progress/types.js.map +1 -0
  79. package/dist/prompts/render.d.ts +23 -0
  80. package/dist/prompts/render.d.ts.map +1 -0
  81. package/dist/prompts/render.js +35 -0
  82. package/dist/prompts/render.js.map +1 -0
  83. package/dist/prompts/seeds.d.ts +34 -0
  84. package/dist/prompts/seeds.d.ts.map +1 -0
  85. package/dist/prompts/seeds.js +166 -0
  86. package/dist/prompts/seeds.js.map +1 -0
  87. package/dist/prompts/store.d.ts +8 -0
  88. package/dist/prompts/store.d.ts.map +1 -0
  89. package/dist/prompts/store.js +153 -0
  90. package/dist/prompts/store.js.map +1 -0
  91. package/dist/prompts/types.d.ts +51 -0
  92. package/dist/prompts/types.d.ts.map +1 -0
  93. package/dist/prompts/types.js +24 -0
  94. package/dist/prompts/types.js.map +1 -0
  95. package/dist/providers/anthropic.d.ts +26 -3
  96. package/dist/providers/anthropic.d.ts.map +1 -1
  97. package/dist/providers/anthropic.js +60 -12
  98. package/dist/providers/anthropic.js.map +1 -1
  99. package/dist/providers/catalog.d.ts +41 -0
  100. package/dist/providers/catalog.d.ts.map +1 -0
  101. package/dist/providers/catalog.js +85 -0
  102. package/dist/providers/catalog.js.map +1 -0
  103. package/dist/providers/content-parts.d.ts +12 -0
  104. package/dist/providers/content-parts.d.ts.map +1 -0
  105. package/dist/providers/content-parts.js +18 -0
  106. package/dist/providers/content-parts.js.map +1 -0
  107. package/dist/providers/defaults.d.ts +44 -0
  108. package/dist/providers/defaults.d.ts.map +1 -0
  109. package/dist/providers/defaults.js +72 -0
  110. package/dist/providers/defaults.js.map +1 -0
  111. package/dist/providers/google.d.ts +20 -3
  112. package/dist/providers/google.d.ts.map +1 -1
  113. package/dist/providers/google.js +53 -12
  114. package/dist/providers/google.js.map +1 -1
  115. package/dist/providers/openai.d.ts +36 -4
  116. package/dist/providers/openai.d.ts.map +1 -1
  117. package/dist/providers/openai.js +113 -21
  118. package/dist/providers/openai.js.map +1 -1
  119. package/dist/providers/registry.d.ts +38 -21
  120. package/dist/providers/registry.d.ts.map +1 -1
  121. package/dist/providers/registry.js +64 -8
  122. package/dist/providers/registry.js.map +1 -1
  123. package/dist/providers/request-signal.d.ts +18 -0
  124. package/dist/providers/request-signal.d.ts.map +1 -0
  125. package/dist/providers/request-signal.js +21 -0
  126. package/dist/providers/request-signal.js.map +1 -0
  127. package/dist/providers/resolve.d.ts +13 -0
  128. package/dist/providers/resolve.d.ts.map +1 -0
  129. package/dist/providers/resolve.js +30 -0
  130. package/dist/providers/resolve.js.map +1 -0
  131. package/dist/providers/store.d.ts +70 -0
  132. package/dist/providers/store.d.ts.map +1 -0
  133. package/dist/providers/store.js +308 -0
  134. package/dist/providers/store.js.map +1 -0
  135. package/dist/providers/tool-names.d.ts +9 -0
  136. package/dist/providers/tool-names.d.ts.map +1 -0
  137. package/dist/providers/tool-names.js +56 -0
  138. package/dist/providers/tool-names.js.map +1 -0
  139. package/dist/providers/types.d.ts +87 -3
  140. package/dist/providers/types.d.ts.map +1 -1
  141. package/dist/rag/reference-documents/ingest.d.ts +22 -0
  142. package/dist/rag/reference-documents/ingest.d.ts.map +1 -0
  143. package/dist/rag/reference-documents/ingest.js +69 -0
  144. package/dist/rag/reference-documents/ingest.js.map +1 -0
  145. package/dist/rag/reference-documents/store.d.ts +23 -0
  146. package/dist/rag/reference-documents/store.d.ts.map +1 -0
  147. package/dist/rag/reference-documents/store.js +140 -0
  148. package/dist/rag/reference-documents/store.js.map +1 -0
  149. package/dist/rag/reference-documents/types.d.ts +52 -0
  150. package/dist/rag/reference-documents/types.d.ts.map +1 -0
  151. package/dist/rag/reference-documents/types.js +32 -0
  152. package/dist/rag/reference-documents/types.js.map +1 -0
  153. package/dist/runtime/loop.d.ts +29 -7
  154. package/dist/runtime/loop.d.ts.map +1 -1
  155. package/dist/runtime/loop.js +209 -44
  156. package/dist/runtime/loop.js.map +1 -1
  157. package/dist/runtime/retry.d.ts +2 -0
  158. package/dist/runtime/retry.d.ts.map +1 -1
  159. package/dist/runtime/retry.js +1 -0
  160. package/dist/runtime/retry.js.map +1 -1
  161. package/dist/runtime/types.d.ts +14 -3
  162. package/dist/runtime/types.d.ts.map +1 -1
  163. package/dist/site-plan/analyse-brief.d.ts +8 -0
  164. package/dist/site-plan/analyse-brief.d.ts.map +1 -1
  165. package/dist/site-plan/analyse-brief.js +17 -8
  166. package/dist/site-plan/analyse-brief.js.map +1 -1
  167. package/dist/site-plan/approval.d.ts +2 -2
  168. package/dist/site-plan/approval.d.ts.map +1 -1
  169. package/dist/site-plan/approval.js +34 -4
  170. package/dist/site-plan/approval.js.map +1 -1
  171. package/dist/site-plan/content-model.d.ts +14 -0
  172. package/dist/site-plan/content-model.d.ts.map +1 -1
  173. package/dist/site-plan/content-model.js +51 -9
  174. package/dist/site-plan/content-model.js.map +1 -1
  175. package/dist/site-plan/demo-content.d.ts.map +1 -1
  176. package/dist/site-plan/demo-content.js +1 -4
  177. package/dist/site-plan/demo-content.js.map +1 -1
  178. package/dist/site-plan/propose-plan.d.ts +10 -0
  179. package/dist/site-plan/propose-plan.d.ts.map +1 -1
  180. package/dist/site-plan/propose-plan.js +25 -1
  181. package/dist/site-plan/propose-plan.js.map +1 -1
  182. package/dist/site-plan/site-context.d.ts +81 -0
  183. package/dist/site-plan/site-context.d.ts.map +1 -0
  184. package/dist/site-plan/site-context.js +76 -0
  185. package/dist/site-plan/site-context.js.map +1 -0
  186. package/dist/site-plan/skin-candidates.d.ts +13 -1
  187. package/dist/site-plan/skin-candidates.d.ts.map +1 -1
  188. package/dist/site-plan/skin-candidates.js +15 -0
  189. package/dist/site-plan/skin-candidates.js.map +1 -1
  190. package/dist/site-plan/structural-gaps.d.ts +32 -0
  191. package/dist/site-plan/structural-gaps.d.ts.map +1 -0
  192. package/dist/site-plan/structural-gaps.js +77 -0
  193. package/dist/site-plan/structural-gaps.js.map +1 -0
  194. package/dist/site-plan/types.d.ts +7 -0
  195. package/dist/site-plan/types.d.ts.map +1 -1
  196. package/dist/skills/frontmatter.d.ts +19 -1
  197. package/dist/skills/frontmatter.d.ts.map +1 -1
  198. package/dist/skills/frontmatter.js +29 -5
  199. package/dist/skills/frontmatter.js.map +1 -1
  200. package/dist/skills/library.d.ts +133 -0
  201. package/dist/skills/library.d.ts.map +1 -0
  202. package/dist/skills/library.js +332 -0
  203. package/dist/skills/library.js.map +1 -0
  204. package/dist/skills/types.d.ts +8 -1
  205. package/dist/skills/types.d.ts.map +1 -1
  206. package/dist/skin/generate.d.ts +22 -1
  207. package/dist/skin/generate.d.ts.map +1 -1
  208. package/dist/skin/generate.js +60 -5
  209. package/dist/skin/generate.js.map +1 -1
  210. package/dist/theme-creator/propose-theme.d.ts +83 -0
  211. package/dist/theme-creator/propose-theme.d.ts.map +1 -0
  212. package/dist/theme-creator/propose-theme.js +234 -0
  213. package/dist/theme-creator/propose-theme.js.map +1 -0
  214. package/dist/tools/core/agent-delegate.d.ts +8 -5
  215. package/dist/tools/core/agent-delegate.d.ts.map +1 -1
  216. package/dist/tools/core/agent-delegate.js +16 -6
  217. package/dist/tools/core/agent-delegate.js.map +1 -1
  218. package/dist/tools/core/content-browse.d.ts +74 -0
  219. package/dist/tools/core/content-browse.d.ts.map +1 -0
  220. package/dist/tools/core/content-browse.js +63 -0
  221. package/dist/tools/core/content-browse.js.map +1 -0
  222. package/dist/tools/core/content-schema.d.ts +86 -0
  223. package/dist/tools/core/content-schema.d.ts.map +1 -0
  224. package/dist/tools/core/content-schema.js +83 -0
  225. package/dist/tools/core/content-schema.js.map +1 -0
  226. package/dist/tools/core/deps-scan.d.ts +25 -0
  227. package/dist/tools/core/deps-scan.d.ts.map +1 -0
  228. package/dist/tools/core/deps-scan.js +85 -0
  229. package/dist/tools/core/deps-scan.js.map +1 -0
  230. package/dist/tools/core/media.d.ts +18 -2
  231. package/dist/tools/core/media.d.ts.map +1 -1
  232. package/dist/tools/core/media.js +8 -2
  233. package/dist/tools/core/media.js.map +1 -1
  234. package/dist/tools/core/not-found-log.d.ts +44 -0
  235. package/dist/tools/core/not-found-log.d.ts.map +1 -0
  236. package/dist/tools/core/not-found-log.js +32 -0
  237. package/dist/tools/core/not-found-log.js.map +1 -0
  238. package/dist/tools/core/redirects.d.ts +51 -0
  239. package/dist/tools/core/redirects.d.ts.map +1 -0
  240. package/dist/tools/core/redirects.js +50 -0
  241. package/dist/tools/core/redirects.js.map +1 -0
  242. package/package.json +6 -4
@@ -0,0 +1,23 @@
1
+ import { type DatabaseHandle } from '@cogenta/core';
2
+ import type { DocumentFormat } from '../../documents/extract-text.js';
3
+ import type { ReferenceDocumentRecord } from './types.js';
4
+ export interface CreateReferenceDocumentInput {
5
+ readonly siteId: string;
6
+ readonly filename: string;
7
+ readonly format: DocumentFormat;
8
+ readonly characters: number;
9
+ readonly warnings: readonly string[];
10
+ readonly uploadedBy: string | null;
11
+ }
12
+ export interface ReferenceDocumentStore {
13
+ ensureTable(): Promise<void>;
14
+ list(siteId: string): Promise<readonly ReferenceDocumentRecord[]>;
15
+ get(siteId: string, id: string): Promise<ReferenceDocumentRecord | null>;
16
+ /** Inserted with `status: 'pending'` — indexing (embedding + vector upsert) happens after, and reports back via `markIndexed`/`markError`. */
17
+ create(input: CreateReferenceDocumentInput): Promise<ReferenceDocumentRecord>;
18
+ markIndexed(siteId: string, id: string, chunkCount: number, at: string): Promise<void>;
19
+ markError(siteId: string, id: string, message: string): Promise<void>;
20
+ remove(siteId: string, id: string): Promise<void>;
21
+ }
22
+ export declare function createReferenceDocumentStore(db: DatabaseHandle, now?: () => number): ReferenceDocumentStore;
23
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../src/rag/reference-documents/store.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,cAAc,EAMpB,MAAM,eAAe,CAAA;AACtB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAA;AACrE,OAAO,KAAK,EAAE,uBAAuB,EAA2B,MAAM,YAAY,CAAA;AAqElF,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAA;IAC/B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAA;IACpC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CACnC;AAED,MAAM,WAAW,sBAAsB;IACrC,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5B,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,uBAAuB,EAAE,CAAC,CAAA;IACjE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,GAAG,IAAI,CAAC,CAAA;IACxE,8IAA8I;IAC9I,MAAM,CAAC,KAAK,EAAE,4BAA4B,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAAA;IAC7E,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACtF,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACrE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAClD;AAWD,wBAAgB,4BAA4B,CAC1C,EAAE,EAAE,cAAc,EAClB,GAAG,GAAE,MAAM,MAAiB,GAC3B,sBAAsB,CAwFxB"}
@@ -0,0 +1,140 @@
1
+ import { CogentaError, identifier, newId, sql, unsafeRaw, } from '@cogenta/core';
2
+ /**
3
+ * Metadata for every reference document a site has uploaded — L22 task 4's
4
+ * "état clair de ce qui est indexé/en cours/en erreur". The vector store
5
+ * (`VectorStore`) holds the embedded chunks; this table holds the one row
6
+ * per upload that the admin screen actually renders a list from, because a
7
+ * `VectorStore` has no notion of "a document", only of chunks that happen to
8
+ * share an `entryId`.
9
+ *
10
+ * Column helpers are written out by hand rather than imported from
11
+ * `@cogenta/schema`'s `store/columns.ts`: that module is not part of
12
+ * `@cogenta/schema`'s public `exports` map (only `.` is), and `@cogenta/agents`
13
+ * may not deep-import another package's `src/`. Three columns' worth of
14
+ * per-dialect typing is not worth widening that package's public surface for.
15
+ */
16
+ const REFERENCE_DOCUMENTS_TABLE = 'cogenta_reference_documents';
17
+ function textColumn(dialect, length) {
18
+ return unsafeRaw(dialect === 'sqlite' ? 'text' : `varchar(${length})`);
19
+ }
20
+ function idColumn(dialect) {
21
+ if (dialect === 'postgres')
22
+ return unsafeRaw('uuid');
23
+ if (dialect === 'mysql')
24
+ return unsafeRaw('char(36)');
25
+ return unsafeRaw('text');
26
+ }
27
+ function timestampColumn(dialect) {
28
+ return textColumn(dialect, 32);
29
+ }
30
+ function integerColumn() {
31
+ return unsafeRaw('integer');
32
+ }
33
+ function toRecord(row) {
34
+ return {
35
+ id: row.id,
36
+ siteId: row.site_id,
37
+ filename: row.filename,
38
+ format: row.format,
39
+ characters: Number(row.characters),
40
+ chunkCount: Number(row.chunk_count),
41
+ status: row.status,
42
+ errorMessage: row.error_message,
43
+ warnings: JSON.parse(row.warnings),
44
+ uploadedAt: row.uploaded_at,
45
+ uploadedBy: row.uploaded_by,
46
+ indexedAt: row.indexed_at,
47
+ };
48
+ }
49
+ function notFound(id) {
50
+ return new CogentaError({
51
+ code: 'ASSIST_DOCUMENT_NOT_FOUND',
52
+ message: `No reference document "${id}" exists on this site.`,
53
+ hint: 'Check the id against GET /api/assistant/documents — it may already have been removed.',
54
+ details: { id },
55
+ });
56
+ }
57
+ export function createReferenceDocumentStore(db, now = Date.now) {
58
+ const d = db.dialect;
59
+ const table = identifier(REFERENCE_DOCUMENTS_TABLE, d);
60
+ return {
61
+ async ensureTable() {
62
+ await db.query(sql `
63
+ create table if not exists ${table} (
64
+ id ${idColumn(d)} not null primary key,
65
+ site_id ${textColumn(d, 500)} not null,
66
+ filename ${textColumn(d, 300)} not null,
67
+ format ${textColumn(d, 16)} not null,
68
+ characters ${integerColumn()} not null,
69
+ chunk_count ${integerColumn()} not null,
70
+ status ${textColumn(d, 16)} not null,
71
+ error_message ${textColumn(d, 2000)},
72
+ warnings ${textColumn(d, 4000)} not null,
73
+ uploaded_at ${timestampColumn(d)} not null,
74
+ uploaded_by ${idColumn(d)},
75
+ indexed_at ${timestampColumn(d)}
76
+ )`);
77
+ },
78
+ async list(siteId) {
79
+ // `id` (UUIDv7) breaks a tie on `uploaded_at` in the same direction —
80
+ // two uploads landing in the same millisecond still sort newest first,
81
+ // deterministically, rather than by whatever order SQLite feels like.
82
+ const result = await db.query(sql `
83
+ select * from ${table} where site_id = ${siteId} order by uploaded_at desc, id desc`);
84
+ return result.rows.map(toRecord);
85
+ },
86
+ async get(siteId, id) {
87
+ const result = await db.query(sql `
88
+ select * from ${table} where site_id = ${siteId} and id = ${id}`);
89
+ const row = result.rows[0];
90
+ return row === undefined ? null : toRecord(row);
91
+ },
92
+ async create(input) {
93
+ const id = newId(now);
94
+ const uploadedAt = new Date(now()).toISOString();
95
+ await db.query(sql `
96
+ insert into ${table}
97
+ (id, site_id, filename, format, characters, chunk_count, status,
98
+ error_message, warnings, uploaded_at, uploaded_by, indexed_at)
99
+ values (
100
+ ${id}, ${input.siteId}, ${input.filename}, ${input.format}, ${input.characters}, 0,
101
+ ${'pending'}, ${null}, ${JSON.stringify([...input.warnings])}, ${uploadedAt},
102
+ ${input.uploadedBy}, ${null}
103
+ )`);
104
+ return {
105
+ id,
106
+ siteId: input.siteId,
107
+ filename: input.filename,
108
+ format: input.format,
109
+ characters: input.characters,
110
+ chunkCount: 0,
111
+ status: 'pending',
112
+ errorMessage: null,
113
+ warnings: input.warnings,
114
+ uploadedAt,
115
+ uploadedBy: input.uploadedBy,
116
+ indexedAt: null,
117
+ };
118
+ },
119
+ async markIndexed(siteId, id, chunkCount, at) {
120
+ await db.query(sql `
121
+ update ${table}
122
+ set status = ${'indexed'}, chunk_count = ${chunkCount}, error_message = ${null}, indexed_at = ${at}
123
+ where site_id = ${siteId} and id = ${id}`);
124
+ },
125
+ async markError(siteId, id, message) {
126
+ await db.query(sql `
127
+ update ${table}
128
+ set status = ${'error'}, error_message = ${message}
129
+ where site_id = ${siteId} and id = ${id}`);
130
+ },
131
+ async remove(siteId, id) {
132
+ const existing = await db.query(sql `
133
+ select id from ${table} where site_id = ${siteId} and id = ${id}`);
134
+ if (existing.rows.length === 0)
135
+ throw notFound(id);
136
+ await db.query(sql `delete from ${table} where site_id = ${siteId} and id = ${id}`);
137
+ },
138
+ };
139
+ }
140
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../../src/rag/reference-documents/store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ import type { DocumentFormat } from '../../documents/extract-text.js';
2
+ /**
3
+ * L22 task 4 — "un flux d'upload de documents additionnels", wired onto the
4
+ * existing extraction → chunking → vectorisation pipeline (L19/L18) rather
5
+ * than a second one.
6
+ *
7
+ * A reference document is not content: it has no collection, no locale
8
+ * variants, no draft/published lifecycle. It still needs a home in the
9
+ * vector index so `assist.chat` can retrieve it — `VectorRecord` requires a
10
+ * `collection`/`entryId`/`locale`/`status` (task 5's `VectorScope`), so this
11
+ * pseudo-collection name is the answer to "what collection is a reference
12
+ * document in?".
13
+ *
14
+ * `^_` can never collide with a real collection name:
15
+ * `defineCollection`'s own `COLLECTION_NAME_PATTERN` is
16
+ * `/^[a-z][a-z0-9_]*$/`, which requires a *letter* first. Starting with an
17
+ * underscore is therefore a name no `defineCollection` call can ever
18
+ * produce, on any site, without needing a registry or a reserved-word list
19
+ * kept in sync by hand.
20
+ */
21
+ export declare const REFERENCE_DOCUMENT_COLLECTION = "_reference_documents";
22
+ /** `locale` a reference document's chunks are stored under — it has no translations, so one sentinel value stands in for "not localised". */
23
+ export declare const REFERENCE_DOCUMENT_LOCALE = "und";
24
+ /**
25
+ * `status` a reference document's chunks are stored under. Reusing the
26
+ * content lifecycle's own `'published'` value (rather than inventing a new
27
+ * one) is what lets `assist.chat`'s hard-coded `status: 'published'` filter
28
+ * retrieve it with no change to that tool — a reference document is exactly
29
+ * as quotable as a published entry, the moment it finishes indexing.
30
+ */
31
+ export declare const REFERENCE_DOCUMENT_STATUS = "published";
32
+ export declare const REFERENCE_DOCUMENT_STATUSES: readonly ['pending', 'indexed', 'error'];
33
+ export type ReferenceDocumentStatus = (typeof REFERENCE_DOCUMENT_STATUSES)[number];
34
+ export interface ReferenceDocumentRecord {
35
+ readonly id: string;
36
+ readonly siteId: string;
37
+ readonly filename: string;
38
+ readonly format: DocumentFormat;
39
+ readonly characters: number;
40
+ /** Chunks actually embedded and stored. `0` until indexing finishes. */
41
+ readonly chunkCount: number;
42
+ readonly status: ReferenceDocumentStatus;
43
+ /** Set only when `status === 'error'`. */
44
+ readonly errorMessage: string | null;
45
+ /** `extractDocumentText`'s own warnings (e.g. "PDF page 4 has no text layer") — shown, never hidden. */
46
+ readonly warnings: readonly string[];
47
+ readonly uploadedAt: string;
48
+ readonly uploadedBy: string | null;
49
+ /** Set once indexing finishes successfully. */
50
+ readonly indexedAt: string | null;
51
+ }
52
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/rag/reference-documents/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAA;AAErE;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,6BAA6B,yBAAyB,CAAA;AAEnE,6IAA6I;AAC7I,eAAO,MAAM,yBAAyB,QAAQ,CAAA;AAE9C;;;;;;GAMG;AACH,eAAO,MAAM,yBAAyB,cAAc,CAAA;AAEpD,eAAO,MAAM,2BAA2B,YAAI,SAAS,EAAE,SAAS,EAAE,OAAO,CAAU,CAAA;AACnF,MAAM,MAAM,uBAAuB,GAAG,CAAC,OAAO,2BAA2B,CAAC,CAAC,MAAM,CAAC,CAAA;AAElF,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAA;IAC/B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,wEAAwE;IACxE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,MAAM,EAAE,uBAAuB,CAAA;IACxC,0CAA0C;IAC1C,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IACpC,wGAAwG;IACxG,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAA;IACpC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IAClC,+CAA+C;IAC/C,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CAClC"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * L22 task 4 — "un flux d'upload de documents additionnels", wired onto the
3
+ * existing extraction → chunking → vectorisation pipeline (L19/L18) rather
4
+ * than a second one.
5
+ *
6
+ * A reference document is not content: it has no collection, no locale
7
+ * variants, no draft/published lifecycle. It still needs a home in the
8
+ * vector index so `assist.chat` can retrieve it — `VectorRecord` requires a
9
+ * `collection`/`entryId`/`locale`/`status` (task 5's `VectorScope`), so this
10
+ * pseudo-collection name is the answer to "what collection is a reference
11
+ * document in?".
12
+ *
13
+ * `^_` can never collide with a real collection name:
14
+ * `defineCollection`'s own `COLLECTION_NAME_PATTERN` is
15
+ * `/^[a-z][a-z0-9_]*$/`, which requires a *letter* first. Starting with an
16
+ * underscore is therefore a name no `defineCollection` call can ever
17
+ * produce, on any site, without needing a registry or a reserved-word list
18
+ * kept in sync by hand.
19
+ */
20
+ export const REFERENCE_DOCUMENT_COLLECTION = '_reference_documents';
21
+ /** `locale` a reference document's chunks are stored under — it has no translations, so one sentinel value stands in for "not localised". */
22
+ export const REFERENCE_DOCUMENT_LOCALE = 'und';
23
+ /**
24
+ * `status` a reference document's chunks are stored under. Reusing the
25
+ * content lifecycle's own `'published'` value (rather than inventing a new
26
+ * one) is what lets `assist.chat`'s hard-coded `status: 'published'` filter
27
+ * retrieve it with no change to that tool — a reference document is exactly
28
+ * as quotable as a published entry, the moment it finishes indexing.
29
+ */
30
+ export const REFERENCE_DOCUMENT_STATUS = 'published';
31
+ export const REFERENCE_DOCUMENT_STATUSES = ['pending', 'indexed', 'error'];
32
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/rag/reference-documents/types.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,MAAM,6BAA6B,GAAG,sBAAsB,CAAA;AAEnE,6IAA6I;AAC7I,MAAM,CAAC,MAAM,yBAAyB,GAAG,KAAK,CAAA;AAE9C;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,WAAW,CAAA;AAEpD,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,SAAS,EAAE,SAAS,EAAE,OAAO,CAAU,CAAA"}
@@ -1,11 +1,33 @@
1
- import type { RunAgentLoopInput, RunResult } from './types.js';
1
+ import type { ProviderToolCall } from '../providers/types.js';
2
+ import type { ExecutableTool, RunAgentLoopInput, RunResult, ToolCallOutcome } from './types.js';
2
3
  /**
3
- * L4 task 2: one model call, tool dispatch, repeat with the three guards
4
- * the lot's own "pièges connus" call out by name: a step ceiling, repetition
5
- * detection, and per-call cost (usage) measured from the first line rather
6
- * than discovered later. Permission checking (task 4) and budgets (task 8)
7
- * wrap this from the outside; this loop only knows how to hold a
8
- * conversation with a model and the tools it was handed.
4
+ * L24 task 1's whole point, made concrete: this function knows nothing about
5
+ * autonomy, approval, or audit. It is handed whatever `ExecutableTool` its
6
+ * caller built, and calls `.execute()` on it nothing more. Contract C's R4
7
+ * ("un outil déclare ses permissions ; le runtime les vérifie — jamais un
8
+ * contrôle d'accès à l'intérieur d'un outil") extends unchanged to the graph
9
+ * era this way: the *tools* node below (the graph's own "inside a node" —
10
+ * see the task's own wording) is exactly this function wired to LangGraph,
11
+ * so it inherits the same "zero permission logic" property by construction.
12
+ * `packages/agents/test/runtime/loop.test.ts`'s "the graph node has no gate
13
+ * of its own" test calls this directly, once with a tool `withAutonomy`
14
+ * never touched (to show it *would* run unguarded) and once with the same
15
+ * tool wrapped (to show the run this package actually produces never can) —
16
+ * proving the gate lives in the tool object handed in, not in this function.
17
+ */
18
+ export declare function runTool(tool: ExecutableTool | undefined, call: ProviderToolCall, signal: AbortSignal): Promise<ToolCallOutcome>;
19
+ /**
20
+ * L4 task 2, migrated to LangGraph.js (L24 task 1): one model call, tool
21
+ * dispatch, repeat — with the same three guards as before (a step ceiling,
22
+ * repetition detection, per-call cost measured from the first line), now run
23
+ * as a two-node `StateGraph` (`buildAgentGraph` above) instead of a `for`
24
+ * loop. The public contract — `RunAgentLoopInput` in, `RunResult` out — is
25
+ * unchanged, so every existing caller (`agents/orchestrator.ts`,
26
+ * `subagents/run-subagent.ts`, `tools/core/agent-delegate.ts`,
27
+ * `assist/runtime.ts`, `eval/run-suite.ts`) needed no change at all.
28
+ * Permission checking (task 4, now `withAutonomy`) and budgets (task 8)
29
+ * still wrap this from the outside; this module still only knows how to
30
+ * hold a conversation with a model and the tools it was handed.
9
31
  */
10
32
  export declare function runAgentLoop(input: RunAgentLoopInput): Promise<RunResult>;
11
33
  //# sourceMappingURL=loop.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"loop.d.ts","sourceRoot":"","sources":["../../src/runtime/loop.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAEV,iBAAiB,EACjB,SAAS,EAGV,MAAM,YAAY,CAAA;AAkCnB;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,SAAS,CAAC,CAoG/E"}
1
+ {"version":3,"file":"loop.d.ts","sourceRoot":"","sources":["../../src/runtime/loop.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAe,gBAAgB,EAAc,MAAM,uBAAuB,CAAA;AAGtF,OAAO,KAAK,EACV,cAAc,EACd,iBAAiB,EACjB,SAAS,EAGT,eAAe,EAChB,MAAM,YAAY,CAAA;AAkBnB;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,OAAO,CAC3B,IAAI,EAAE,cAAc,GAAG,SAAS,EAChC,IAAI,EAAE,gBAAgB,EACtB,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,eAAe,CAAC,CAU1B;AAyND;;;;;;;;;;;;GAYG;AACH,wBAAsB,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,SAAS,CAAC,CAyC/E"}
@@ -1,10 +1,13 @@
1
+ import { CogentaError } from '@cogenta/core';
2
+ import { Annotation, END, GraphRecursionError, START, StateGraph } from '@langchain/langgraph';
1
3
  import { assertProviderAllowed } from '../privacy/assert-provider-allowed.js';
4
+ import { NOOP_PROGRESS } from '../progress/types.js';
2
5
  import { RepetitionGuard } from './repetition.js';
3
6
  import { retryModelCall, withTimeout } from './retry.js';
7
+ /** Tool-call step budget and repetition guard — orchestration policy, not a property of any one model, so these stay fixed defaults rather than admin-configurable per provider. */
4
8
  const DEFAULT_MAX_STEPS = 25;
5
- const DEFAULT_MAX_ATTEMPTS = 3;
6
- const DEFAULT_TIMEOUT_MS = 60_000;
7
9
  const DEFAULT_MAX_REPEATS = 2;
10
+ const ZERO_USAGE = { inputTokens: 0, outputTokens: 0 };
8
11
  function addUsage(a, b) {
9
12
  return {
10
13
  inputTokens: a.inputTokens + b.inputTokens,
@@ -14,7 +17,22 @@ function addUsage(a, b) {
14
17
  function toolByName(tools) {
15
18
  return new Map(tools.map((tool) => [tool.spec.name, tool]));
16
19
  }
17
- async function runTool(tool, call, signal) {
20
+ /**
21
+ * L24 task 1's whole point, made concrete: this function knows nothing about
22
+ * autonomy, approval, or audit. It is handed whatever `ExecutableTool` its
23
+ * caller built, and calls `.execute()` on it — nothing more. Contract C's R4
24
+ * ("un outil déclare ses permissions ; le runtime les vérifie — jamais un
25
+ * contrôle d'accès à l'intérieur d'un outil") extends unchanged to the graph
26
+ * era this way: the *tools* node below (the graph's own "inside a node" —
27
+ * see the task's own wording) is exactly this function wired to LangGraph,
28
+ * so it inherits the same "zero permission logic" property by construction.
29
+ * `packages/agents/test/runtime/loop.test.ts`'s "the graph node has no gate
30
+ * of its own" test calls this directly, once with a tool `withAutonomy`
31
+ * never touched (to show it *would* run unguarded) and once with the same
32
+ * tool wrapped (to show the run this package actually produces never can) —
33
+ * proving the gate lives in the tool object handed in, not in this function.
34
+ */
35
+ export async function runTool(tool, call, signal) {
18
36
  if (tool === undefined) {
19
37
  return { call, ok: false, error: `No tool named "${call.name}" is available in this run.` };
20
38
  }
@@ -27,90 +45,237 @@ async function runTool(tool, call, signal) {
27
45
  }
28
46
  }
29
47
  /**
30
- * L4 task 2: one model call, tool dispatch, repeat — with the three guards
31
- * the lot's own "pièges connus" call out by name: a step ceiling, repetition
32
- * detection, and per-call cost (usage) measured from the first line rather
33
- * than discovered later. Permission checking (task 4) and budgets (task 8)
34
- * wrap this from the outside; this loop only knows how to hold a
35
- * conversation with a model and the tools it was handed.
48
+ * The graph's state. Every field uses LangGraph's default `LastValue`
49
+ * channel (no reducer) a node's returned value simply replaces the
50
+ * previous one, the same "whole new value each time" style the pre-L24
51
+ * hand-written loop used for its local variables. `pending*` fields are the
52
+ * only thing carried from the `agent` node to the `tools` node they hold
53
+ * exactly what the old loop kept on the stack between "got a response with
54
+ * tool calls" and "finished executing them".
36
55
  */
37
- export async function runAgentLoop(input) {
56
+ const AgentGraphState = Annotation.Root({
57
+ messages: Annotation(),
58
+ steps: Annotation(),
59
+ usage: Annotation(),
60
+ finalText: Annotation(),
61
+ stopReason: Annotation(),
62
+ stepIndex: Annotation(),
63
+ pendingAssistant: Annotation(),
64
+ pendingToolCalls: Annotation(),
65
+ pendingUsage: Annotation(),
66
+ });
67
+ /**
68
+ * Builds (and compiles) a fresh two-node graph for one `runAgentLoop` call:
69
+ * `agent` (one model turn — every guard the old loop checked before calling
70
+ * the model, then the call itself) and `tools` (executes whatever tool calls
71
+ * the `agent` node decided to make, via `runTool` above). `agent` is the only
72
+ * node with an edge back to itself (through `tools`), so "one iteration" of
73
+ * the old `for` loop is exactly one `agent → tools → agent` cycle here — the
74
+ * per-iteration guard order (privacy, signal, kill switch, duration, budget,
75
+ * then the model call) is preserved byte-for-byte because it is still one
76
+ * function, just called by LangGraph instead of by a `for`.
77
+ *
78
+ * Nothing here decides whether a tool call is *allowed* — that decision was
79
+ * already made (or not) before this function ever sees `input.tools`, by
80
+ * whoever built that array (`withAutonomyForManifest` in
81
+ * `agents/orchestrator.ts` for every real caller). The `tools` node below
82
+ * has no branch for "is this tool allowed" — it cannot have one, because it
83
+ * never sees a raw tool to begin with.
84
+ */
85
+ function buildAgentGraph(input) {
38
86
  const maxSteps = input.maxSteps ?? DEFAULT_MAX_STEPS;
39
- const maxAttempts = input.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
40
- const timeoutMs = input.timeoutMs ?? DEFAULT_TIMEOUT_MS;
87
+ const maxAttempts = input.maxAttempts ?? input.client.maxCorrectionAttempts;
88
+ const timeoutMs = input.timeoutMs ?? input.client.requestTimeoutMs;
41
89
  const now = input.now ?? Date.now;
42
90
  const tools = input.tools ?? [];
43
91
  const toolIndex = toolByName(tools);
44
92
  const toolSpecs = tools.length === 0 ? undefined : tools.map((tool) => tool.spec);
45
93
  const repetitionGuard = new RepetitionGuard(input.maxRepeats ?? DEFAULT_MAX_REPEATS);
46
- const messages = [...input.messages];
47
- const steps = [];
48
- let totalUsage = { inputTokens: 0, outputTokens: 0 };
49
- let finalText = null;
50
94
  const runStartedAt = now();
51
- for (let step = 0; step < maxSteps; step++) {
95
+ const progress = input.onProgress ?? NOOP_PROGRESS;
96
+ async function agentNode(state) {
52
97
  if (input.privacyPolicy !== undefined) {
53
98
  assertProviderAllowed(input.client, input.privacyPolicy);
54
99
  }
55
- if (input.signal?.aborted === true) {
56
- return { messages, steps, finalText, stopReason: 'cancelled', usage: totalUsage };
57
- }
58
- if (input.killSwitch?.isActive() === true) {
59
- return { messages, steps, finalText, stopReason: 'killed', usage: totalUsage };
60
- }
100
+ if (input.signal?.aborted === true)
101
+ return { stopReason: 'cancelled' };
102
+ if (input.killSwitch?.isActive() === true)
103
+ return { stopReason: 'killed' };
61
104
  if (input.maxRunDurationMs !== undefined && now() - runStartedAt >= input.maxRunDurationMs) {
62
- return { messages, steps, finalText, stopReason: 'max_duration', usage: totalUsage };
105
+ return { stopReason: 'max_duration' };
63
106
  }
107
+ if (state.stepIndex >= maxSteps)
108
+ return { stopReason: 'max_steps' };
64
109
  if (input.budget !== undefined) {
65
110
  const check = input.budget.checkCall();
66
111
  if (!check.allowed) {
67
112
  if (check.reason !== undefined)
68
113
  input.onBudgetExceeded?.(check.reason);
69
- return { messages, steps, finalText, stopReason: 'budget_exceeded', usage: totalUsage };
114
+ return { stopReason: 'budget_exceeded' };
70
115
  }
71
116
  }
117
+ progress.report(state.stepIndex === 0 ? 'Thinking…' : `Thinking… (step ${state.stepIndex + 1})`);
72
118
  const response = await retryModelCall(() => withTimeout((signal) => input.client.chat({
73
119
  model: input.client.model,
74
120
  ...(input.system === undefined ? {} : { system: input.system }),
75
- messages: [...messages],
121
+ messages: [...state.messages],
76
122
  ...(toolSpecs === undefined ? {} : { tools: toolSpecs }),
77
- maxTokens: input.maxTokens,
123
+ ...(input.maxTokens === undefined ? {} : { maxTokens: input.maxTokens }),
78
124
  ...(input.temperature === undefined ? {} : { temperature: input.temperature }),
79
- }, { signal }), timeoutMs, input.signal), { maxAttempts });
80
- totalUsage = addUsage(totalUsage, response.usage);
125
+ }, { signal }), timeoutMs, input.signal), {
126
+ maxAttempts,
127
+ onRetry: (attempt, error) => progress.report(`Retrying after a provider error (attempt ${attempt}/${maxAttempts}): ${error instanceof Error ? error.message : String(error)}`),
128
+ });
129
+ const usage = addUsage(state.usage, response.usage);
81
130
  input.budget?.recordCall(response.usage);
82
131
  const assistantMessage = {
83
132
  role: 'assistant',
84
133
  ...(response.content === null ? {} : { content: response.content }),
85
134
  ...(response.toolCalls.length === 0 ? {} : { toolCalls: response.toolCalls }),
86
135
  };
87
- messages.push(assistantMessage);
88
- if (response.content !== null)
89
- finalText = response.content;
136
+ const messages = [...state.messages, assistantMessage];
137
+ const finalText = response.content !== null ? response.content : state.finalText;
138
+ const stepIndex = state.stepIndex + 1;
90
139
  if (response.toolCalls.length === 0) {
91
- steps.push({ response: assistantMessage, usage: response.usage, toolOutcomes: [] });
92
140
  const stopReason = response.stopReason === 'max_tokens' ? 'max_tokens' : 'end_turn';
93
- return { messages, steps, finalText, stopReason, usage: totalUsage };
141
+ return {
142
+ messages,
143
+ finalText,
144
+ usage,
145
+ stepIndex,
146
+ stopReason,
147
+ steps: [
148
+ ...state.steps,
149
+ { response: assistantMessage, usage: response.usage, toolOutcomes: [] },
150
+ ],
151
+ };
94
152
  }
95
153
  if (response.toolCalls.some((call) => repetitionGuard.wouldRepeat(call))) {
96
- steps.push({ response: assistantMessage, usage: response.usage, toolOutcomes: [] });
97
- return { messages, steps, finalText, stopReason: 'repetition_detected', usage: totalUsage };
154
+ return {
155
+ messages,
156
+ finalText,
157
+ usage,
158
+ stepIndex,
159
+ stopReason: 'repetition_detected',
160
+ steps: [
161
+ ...state.steps,
162
+ { response: assistantMessage, usage: response.usage, toolOutcomes: [] },
163
+ ],
164
+ };
98
165
  }
99
166
  for (const call of response.toolCalls)
100
167
  repetitionGuard.record(call);
168
+ return {
169
+ messages,
170
+ finalText,
171
+ usage,
172
+ stepIndex,
173
+ pendingAssistant: assistantMessage,
174
+ pendingToolCalls: response.toolCalls,
175
+ pendingUsage: response.usage,
176
+ };
177
+ }
178
+ async function toolsNode(state) {
101
179
  const toolOutcomes = [];
102
- for (const call of response.toolCalls) {
180
+ let messages = state.messages;
181
+ for (const call of state.pendingToolCalls) {
182
+ progress.report(`Calling tool "${call.name}"…`);
103
183
  const outcome = await runTool(toolIndex.get(call.name), call, input.signal ?? new AbortController().signal);
184
+ progress.report(outcome.ok
185
+ ? `Tool "${call.name}" finished.`
186
+ : `Tool "${call.name}" failed: ${outcome.error ?? 'unknown error'}`);
104
187
  toolOutcomes.push(outcome);
105
- messages.push({
106
- role: 'tool',
107
- toolCallId: call.id,
108
- toolName: call.name,
109
- content: outcome.ok ? (outcome.output ?? '') : `Error: ${outcome.error ?? 'unknown error'}`,
188
+ messages = [
189
+ ...messages,
190
+ {
191
+ role: 'tool',
192
+ toolCallId: call.id,
193
+ toolName: call.name,
194
+ content: outcome.ok
195
+ ? (outcome.output ?? '')
196
+ : `Error: ${outcome.error ?? 'unknown error'}`,
197
+ },
198
+ ];
199
+ }
200
+ return {
201
+ messages,
202
+ steps: [
203
+ ...state.steps,
204
+ {
205
+ // Set on every path that reaches `tools` (see `agentNode` above) —
206
+ // non-null by construction, not by luck.
207
+ response: state.pendingAssistant,
208
+ usage: state.pendingUsage,
209
+ toolOutcomes,
210
+ },
211
+ ],
212
+ pendingAssistant: null,
213
+ pendingToolCalls: [],
214
+ pendingUsage: null,
215
+ };
216
+ }
217
+ function routeAfterAgent(state) {
218
+ return state.stopReason !== null ? END : 'tools';
219
+ }
220
+ const graph = new StateGraph(AgentGraphState)
221
+ .addNode('agent', agentNode)
222
+ .addNode('tools', toolsNode)
223
+ .addEdge(START, 'agent')
224
+ .addConditionalEdges('agent', routeAfterAgent, { tools: 'tools', [END]: END })
225
+ .addEdge('tools', 'agent');
226
+ return { graph: graph.compile(), maxSteps };
227
+ }
228
+ /**
229
+ * L4 task 2, migrated to LangGraph.js (L24 task 1): one model call, tool
230
+ * dispatch, repeat — with the same three guards as before (a step ceiling,
231
+ * repetition detection, per-call cost measured from the first line), now run
232
+ * as a two-node `StateGraph` (`buildAgentGraph` above) instead of a `for`
233
+ * loop. The public contract — `RunAgentLoopInput` in, `RunResult` out — is
234
+ * unchanged, so every existing caller (`agents/orchestrator.ts`,
235
+ * `subagents/run-subagent.ts`, `tools/core/agent-delegate.ts`,
236
+ * `assist/runtime.ts`, `eval/run-suite.ts`) needed no change at all.
237
+ * Permission checking (task 4, now `withAutonomy`) and budgets (task 8)
238
+ * still wrap this from the outside; this module still only knows how to
239
+ * hold a conversation with a model and the tools it was handed.
240
+ */
241
+ export async function runAgentLoop(input) {
242
+ const { graph, maxSteps } = buildAgentGraph(input);
243
+ const initialState = {
244
+ messages: [...input.messages],
245
+ steps: [],
246
+ usage: ZERO_USAGE,
247
+ finalText: null,
248
+ stopReason: null,
249
+ stepIndex: 0,
250
+ pendingAssistant: null,
251
+ pendingToolCalls: [],
252
+ pendingUsage: null,
253
+ };
254
+ let finalState;
255
+ try {
256
+ // Generous relative to `maxSteps`: each loop iteration is at most two
257
+ // graph super-steps (`agent`, then `tools`), and `agentNode`'s own
258
+ // `stepIndex >= maxSteps` check always produces a `max_steps` stop
259
+ // before this ceiling could ever bind. It exists as a backstop against a
260
+ // future bug in that check, not as the mechanism `max_steps` relies on.
261
+ finalState = await graph.invoke(initialState, { recursionLimit: maxSteps * 2 + 10 });
262
+ }
263
+ catch (error) {
264
+ if (error instanceof GraphRecursionError) {
265
+ throw new CogentaError({
266
+ code: 'AGENT_LOOP_RECURSION_LIMIT',
267
+ message: 'The graph recursion ceiling was hit before its own max_steps check fired.',
268
+ hint: 'This indicates a bug in the agent graph, not a normal stop reason — please report it.',
110
269
  });
111
270
  }
112
- steps.push({ response: assistantMessage, usage: response.usage, toolOutcomes });
271
+ throw error;
113
272
  }
114
- return { messages, steps, finalText, stopReason: 'max_steps', usage: totalUsage };
273
+ return {
274
+ messages: finalState.messages,
275
+ steps: finalState.steps,
276
+ finalText: finalState.finalText,
277
+ stopReason: finalState.stopReason ?? 'max_steps',
278
+ usage: finalState.usage,
279
+ };
115
280
  }
116
281
  //# sourceMappingURL=loop.js.map