refresh
diff --git a/frontend/src/app/modules/agents/agent/agent-details/agent-details.component.ts b/frontend/src/app/modules/agents/agent/agent-details/agent-details.component.ts
index 4022f099..1b4645f4 100644
--- a/frontend/src/app/modules/agents/agent/agent-details/agent-details.component.ts
+++ b/frontend/src/app/modules/agents/agent/agent-details/agent-details.component.ts
@@ -62,6 +62,7 @@ export class AgentDetailsComponent implements OnInit {
feedbackForm: FormGroup;
hilForm: FormGroup;
errorForm: FormGroup;
+ userHilForm: FormGroup; // NEW: Form for hitl_user state
isSubmitting = signal(false);
isResumingError = signal(false);
@@ -109,6 +110,7 @@ export class AgentDetailsComponent implements OnInit {
this.feedbackForm = this.formBuilder.group({ feedback: ['', Validators.required] });
this.hilForm = this.formBuilder.group({ feedback: [''] });
this.errorForm = this.formBuilder.group({ errorDetails: ['', Validators.required] });
+ this.userHilForm = this.formBuilder.group({ note: [''] });
// Handle error side effects with RxJS
toObservable(this.functionsError)
@@ -192,6 +194,33 @@ export class AgentDetailsComponent implements OnInit {
});
}
+ onResumeUserHil(): void {
+ if (!this.userHilForm.valid) return;
+ this.isSubmitting.set(true);
+ const note = this.userHilForm.get('note')?.value;
+ const currentAgentDetails = this.agentDetails();
+ this.agentService
+ .resumeAgent(currentAgentDetails.agentId, currentAgentDetails.executionId, note)
+ .pipe(
+ takeUntilDestroyed(this.destroyRef),
+ catchError((error) => {
+ console.error('Error resuming agent from user HIL:', error);
+ this.snackBar.open('Error resuming agent', 'Close', { duration: 3000 });
+ return of(null);
+ }),
+ finalize(() => {
+ this.isSubmitting.set(false);
+ }),
+ )
+ .subscribe((response) => {
+ if (response) {
+ this.snackBar.open('Agent resumed successfully', 'Close', { duration: 3000 });
+ this.userHilForm.reset();
+ this.handleRefreshAgentDetails();
+ }
+ });
+ }
+
onResumeError(): void {
if (!this.errorForm.valid) return;
this.isResumingError.set(true);
diff --git a/package.json b/package.json
index 20a851f3..6c82d9a6 100644
--- a/package.json
+++ b/package.json
@@ -30,6 +30,7 @@
"repos": " node -r esbuild-register src/cli/repos.ts",
"scrape": " node -r esbuild-register src/cli/scrape.ts",
"slack": " node -r esbuild-register src/cli/slack.ts",
+ "slackHistory": "node -r esbuild-register --env-file=variables/local.env src/modules/slack/slackChatHistory.ts",
"summarize": "node -r esbuild-register src/cli/summarize.ts",
"swe": " node -r esbuild-register src/cli/swe.ts",
"swebench": " node -r esbuild-register src/cli/swebench.ts",
@@ -93,13 +94,13 @@
"@ghostery/adblocker-puppeteer": "2.11.3",
"@gitbeaker/core": "43.5.0",
"@gitbeaker/rest": "43.5.0",
- "@google-cloud/aiplatform": "^5.7.0",
+ "@google-cloud/aiplatform": "5.12.0",
"@google-cloud/bigquery": "^8.1.1",
- "@google-cloud/discoveryengine": "^2.4.0",
- "@google-cloud/firestore": "^7.11.3",
+ "@google-cloud/discoveryengine": "2.5.2",
+ "@google-cloud/firestore": "7.11.6",
"@google-cloud/opentelemetry-cloud-trace-exporter": "^2.4.1",
- "@google-cloud/secret-manager": "^6.1.0",
- "@google-cloud/storage": "^7.17.1",
+ "@google-cloud/secret-manager": "6.1.1",
+ "@google-cloud/storage": "7.17.2",
"@google-cloud/vertexai": "^1.10.0",
"@grpc/grpc-js": "^1.13.4",
"@microsoft/tiktokenizer": "^1.0.10",
@@ -129,6 +130,7 @@
"chromadb": "^1.9.2",
"clipboardy": "^4.0.0",
"cloud-pine": "^2.0.1",
+ "commander": "^14.0.2",
"crawlee": "^3.8.1",
"cross-fetch": "^3.1.8",
"date-fns": "^4.1.0",
@@ -173,7 +175,7 @@
"string-similarity-js": "^2.1.4",
"strip-ansi": "^7.1.0",
"table": "^6.9.0",
- "tree-sitter": "^0.21.0",
+ "tree-sitter": "^0.25.0",
"tree-sitter-typescript": "^0.23.2",
"ts-morph": "^21.0.1",
"ts-node": "^10.7.0",
@@ -220,6 +222,14 @@
"secretlint": "^8.2.4",
"sinon": "^17.0.0",
"sinon-chai": "^3.7.0",
+ "tree-sitter-c-sharp": "^0.23.1",
+ "tree-sitter-cpp": "^0.23.4",
+ "tree-sitter-go": "^0.25.0",
+ "tree-sitter-java": "^0.23.5",
+ "tree-sitter-javascript": "^0.25.0",
+ "tree-sitter-python": "^0.25.0",
+ "tree-sitter-rust": "^0.24.0",
+ "tree-sitter-scala": "^0.24.0",
"ts-node-dev": "^2.0.0",
"tsc-watch": "^4.0.0",
"typescript": "5.9.2"
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 12a26d6f..ce9922b2 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -84,26 +84,26 @@ importers:
specifier: 43.5.0
version: 43.5.0
'@google-cloud/aiplatform':
- specifier: ^5.7.0
- version: 5.7.0
+ specifier: 5.12.0
+ version: 5.12.0
'@google-cloud/bigquery':
specifier: ^8.1.1
version: 8.1.1
'@google-cloud/discoveryengine':
- specifier: ^2.4.0
- version: 2.4.0
+ specifier: 2.5.2
+ version: 2.5.2
'@google-cloud/firestore':
- specifier: ^7.11.3
- version: 7.11.3(encoding@0.1.13)
+ specifier: 7.11.6
+ version: 7.11.6(encoding@0.1.13)
'@google-cloud/opentelemetry-cloud-trace-exporter':
specifier: ^2.4.1
version: 2.4.1(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/resources@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)
'@google-cloud/secret-manager':
- specifier: ^6.1.0
- version: 6.1.0
+ specifier: 6.1.1
+ version: 6.1.1
'@google-cloud/storage':
- specifier: ^7.17.1
- version: 7.17.1(encoding@0.1.13)
+ specifier: 7.17.2
+ version: 7.17.2(encoding@0.1.13)
'@google-cloud/vertexai':
specifier: ^1.10.0
version: 1.10.0(encoding@0.1.13)
@@ -191,6 +191,9 @@ importers:
cloud-pine:
specifier: ^2.0.1
version: 2.0.1(encoding@0.1.13)
+ commander:
+ specifier: ^14.0.2
+ version: 14.0.2
crawlee:
specifier: ^3.8.1
version: 3.14.0(playwright@1.54.1)(puppeteer@22.15.0(typescript@5.9.2))
@@ -324,11 +327,11 @@ importers:
specifier: ^6.9.0
version: 6.9.0
tree-sitter:
- specifier: ^0.21.0
- version: 0.21.1
+ specifier: ^0.25.0
+ version: 0.25.0
tree-sitter-typescript:
specifier: ^0.23.2
- version: 0.23.2(tree-sitter@0.21.1)
+ version: 0.23.2(tree-sitter@0.25.0)
ts-morph:
specifier: ^21.0.1
version: 21.0.1
@@ -459,6 +462,30 @@ importers:
sinon-chai:
specifier: ^3.7.0
version: 3.7.0(chai@4.5.0)(sinon@17.0.2)
+ tree-sitter-c-sharp:
+ specifier: ^0.23.1
+ version: 0.23.1(tree-sitter@0.25.0)
+ tree-sitter-cpp:
+ specifier: ^0.23.4
+ version: 0.23.4(tree-sitter@0.25.0)
+ tree-sitter-go:
+ specifier: ^0.25.0
+ version: 0.25.0(tree-sitter@0.25.0)
+ tree-sitter-java:
+ specifier: ^0.23.5
+ version: 0.23.5(tree-sitter@0.25.0)
+ tree-sitter-javascript:
+ specifier: ^0.25.0
+ version: 0.25.0(tree-sitter@0.25.0)
+ tree-sitter-python:
+ specifier: ^0.25.0
+ version: 0.25.0(tree-sitter@0.25.0)
+ tree-sitter-rust:
+ specifier: ^0.24.0
+ version: 0.24.0(tree-sitter@0.25.0)
+ tree-sitter-scala:
+ specifier: ^0.24.0
+ version: 0.24.0(tree-sitter@0.25.0)
ts-node-dev:
specifier: ^2.0.0
version: 2.0.0(@swc/core@1.13.2(@swc/helpers@0.5.17))(@types/node@20.19.9)(typescript@5.9.2)
@@ -1605,8 +1632,8 @@ packages:
resolution: {integrity: sha512-HJgzKSBtdHrfpbH3vHj+1qSyH9RR0L/zMDCwvo4NE2Fg9P2nkWMfj2AzhytENHGTtL6gcfSPOS16HLLTQ4uVeg==}
engines: {node: '>=18.20.0'}
- '@google-cloud/aiplatform@5.7.0':
- resolution: {integrity: sha512-2dzlIMFwUbQQHNIHS/TBdJE7J4UBpbS57YE3RuE2eL3nXIGwGeK0fuhHpySlkqGHfHMdzwpS6F27mzU8EQFPWw==}
+ '@google-cloud/aiplatform@5.12.0':
+ resolution: {integrity: sha512-BN4DSumv3RP6oPSnlwnvErYK2sngG/ZsNv0SA6dp15UFEUM1tFm9TQQnnWw2HWdUI8aztMeJyxfOEfdgluxPuQ==}
engines: {node: '>=18'}
'@google-cloud/bigquery@8.1.1':
@@ -1625,12 +1652,12 @@ packages:
resolution: {integrity: sha512-IXh04DlkLMxWgYLIUYuHHKXKOUwPDzDgke1ykkkJPe48cGIS9kkL2U/o0pm4ankHLlvzLF/ma1eO86n/bkumIA==}
engines: {node: '>=18'}
- '@google-cloud/discoveryengine@2.4.0':
- resolution: {integrity: sha512-JNZ6C4GI3eXn8zDhoZBZ9CZmzXg9ma8BTRZYq8LMAfnG6daJQpDtO3OP5Nnnh19zuYPyQwuhe0TKcQfjcYJ4kw==}
+ '@google-cloud/discoveryengine@2.5.2':
+ resolution: {integrity: sha512-90pFlwUOrh+PCBdD8xZdZHBWWMPoPhxA60d7cJx2CFe8Jtu1HTYG6c5W0ZuvFzFeiqL5xOQxj1tTd+XnsD85ug==}
engines: {node: '>=18'}
- '@google-cloud/firestore@7.11.3':
- resolution: {integrity: sha512-qsM3/WHpawF07SRVvEJJVRwhYzM7o9qtuksyuqnrMig6fxIrwWnsezECWsG/D5TyYru51Fv5c/RTqNDQ2yU+4w==}
+ '@google-cloud/firestore@7.11.6':
+ resolution: {integrity: sha512-EW/O8ktzwLfyWBOsNuhRoMi8lrC3clHM5LVFhGvO1HCsLozCOOXRAlHrYBoE6HL42Sc8yYMuCb2XqcnJ4OOEpw==}
engines: {node: '>=14.0.0'}
'@google-cloud/logging@11.2.0':
@@ -1688,12 +1715,12 @@ packages:
resolution: {integrity: sha512-xWxJAlyUGd6OPp97u8maMcI3xVXuHjxfwh6Dr7P/P+6NK9o446slJobsbgsmK0xKY4nTK8m5uuJrhEKapfZSmQ==}
engines: {node: '>=14.0.0'}
- '@google-cloud/secret-manager@6.1.0':
- resolution: {integrity: sha512-IrXjT1z2yW98htydkopcxdhNVh4rpAzO3oTVzKymfdnzmzCKWVxhfwYWWvIor1bzgQN4sa21Wv0CMDokJyTt7A==}
+ '@google-cloud/secret-manager@6.1.1':
+ resolution: {integrity: sha512-dwSuxJ9RNmAW46FjK1StiNIeOiSHHQs/XIy4VArJ6bBMR+WsIvR+zhPh2pa40aFa9uTty67j38Rl268TVV62EA==}
engines: {node: '>=18'}
- '@google-cloud/storage@7.17.1':
- resolution: {integrity: sha512-2FMQbpU7qK+OtBPaegC6n+XevgZksobUGo6mGKnXNmeZpvLiAo1gTAE3oTKsrMGDV4VtL8Zzpono0YsK/Q7Iqg==}
+ '@google-cloud/storage@7.17.2':
+ resolution: {integrity: sha512-6xN0KNO8L/LIA5zu3CJwHkJiB6n65eykBLOb0E+RooiHYgX8CSao6lvQiKT9TBk2gL5g33LL3fmhDodZnt56rw==}
engines: {node: '>=14'}
'@google-cloud/vertexai@1.10.0':
@@ -3679,6 +3706,10 @@ packages:
resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==}
engines: {node: '>=18'}
+ commander@14.0.2:
+ resolution: {integrity: sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==}
+ engines: {node: '>=20'}
+
commander@2.20.3:
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
@@ -7826,6 +7857,46 @@ packages:
resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
hasBin: true
+ tree-sitter-c-sharp@0.23.1:
+ resolution: {integrity: sha512-9zZ4FlcTRWWfRf6f4PgGhG8saPls6qOOt75tDfX7un9vQZJmARjPrAC6yBNCX2T/VKcCjIDbgq0evFaB3iGhQw==}
+ peerDependencies:
+ tree-sitter: ^0.21.1
+ peerDependenciesMeta:
+ tree-sitter:
+ optional: true
+
+ tree-sitter-c@0.23.6:
+ resolution: {integrity: sha512-0dxXKznVyUA0s6PjNolJNs2yF87O5aL538A/eR6njA5oqX3C3vH4vnx3QdOKwuUdpKEcFdHuiDpRKLLCA/tjvQ==}
+ peerDependencies:
+ tree-sitter: ^0.22.1
+ peerDependenciesMeta:
+ tree-sitter:
+ optional: true
+
+ tree-sitter-cpp@0.23.4:
+ resolution: {integrity: sha512-qR5qUDyhZ5jJ6V8/umiBxokRbe89bCGmcq/dk94wI4kN86qfdV8k0GHIUEKaqWgcu42wKal5E97LKpLeVW8sKw==}
+ peerDependencies:
+ tree-sitter: ^0.21.1
+ peerDependenciesMeta:
+ tree-sitter:
+ optional: true
+
+ tree-sitter-go@0.25.0:
+ resolution: {integrity: sha512-APBc/Dq3xz/e35Xpkhb1blu5UgW+2E3RyGWawZSCNcbGwa7jhSQPS8KsUupuzBla8PCo8+lz9W/JDJjmfRa2tw==}
+ peerDependencies:
+ tree-sitter: ^0.25.0
+ peerDependenciesMeta:
+ tree-sitter:
+ optional: true
+
+ tree-sitter-java@0.23.5:
+ resolution: {integrity: sha512-Yju7oQ0Xx7GcUT01mUglPP+bYfvqjNCGdxqigTnew9nLGoII42PNVP3bHrYeMxswiCRM0yubWmN5qk+zsg0zMA==}
+ peerDependencies:
+ tree-sitter: ^0.21.1
+ peerDependenciesMeta:
+ tree-sitter:
+ optional: true
+
tree-sitter-javascript@0.23.1:
resolution: {integrity: sha512-/bnhbrTD9frUYHQTiYnPcxyHORIw157ERBa6dqzaKxvR/x3PC4Yzd+D1pZIMS6zNg2v3a8BZ0oK7jHqsQo9fWA==}
peerDependencies:
@@ -7834,6 +7905,38 @@ packages:
tree-sitter:
optional: true
+ tree-sitter-javascript@0.25.0:
+ resolution: {integrity: sha512-1fCbmzAskZkxcZzN41sFZ2br2iqTYP3tKls1b/HKGNPQUVOpsUxpmGxdN/wMqAk3jYZnYBR1dd/y/0avMeU7dw==}
+ peerDependencies:
+ tree-sitter: ^0.25.0
+ peerDependenciesMeta:
+ tree-sitter:
+ optional: true
+
+ tree-sitter-python@0.25.0:
+ resolution: {integrity: sha512-eCmJx6zQa35GxaCtQD+wXHOhYqBxEL+bp71W/s3fcDMu06MrtzkVXR437dRrCrbrDbyLuUDJpAgycs7ncngLXw==}
+ peerDependencies:
+ tree-sitter: ^0.25.0
+ peerDependenciesMeta:
+ tree-sitter:
+ optional: true
+
+ tree-sitter-rust@0.24.0:
+ resolution: {integrity: sha512-NWemUDf629Tfc90Y0Z55zuwPCAHkLxWnMf2RznYu4iBkkrQl2o/CHGB7Cr52TyN5F1DAx8FmUnDtCy9iUkXZEQ==}
+ peerDependencies:
+ tree-sitter: ^0.22.1
+ peerDependenciesMeta:
+ tree-sitter:
+ optional: true
+
+ tree-sitter-scala@0.24.0:
+ resolution: {integrity: sha512-vkMuAUrBZ1zZz2XcGDQk18Kz73JkpgaeXzbNVobPke0G35sd9jH32aUxG6OLRKM7et0TbsfqkWf4DeJoGk4K1g==}
+ peerDependencies:
+ tree-sitter: ^0.21.1
+ peerDependenciesMeta:
+ tree-sitter:
+ optional: true
+
tree-sitter-typescript@0.23.2:
resolution: {integrity: sha512-e04JUUKxTT53/x3Uq1zIL45DoYKVfHH4CZqwgZhPg5qYROl5nQjV+85ruFzFGZxu+QeFVbRTPDRnqL9UbU4VeA==}
peerDependencies:
@@ -7842,8 +7945,8 @@ packages:
tree-sitter:
optional: true
- tree-sitter@0.21.1:
- resolution: {integrity: sha512-7dxoA6kYvtgWw80265MyqJlkRl4yawIjO7S5MigytjELkX43fV2WsAXzsNfO7sBpPPCF5Gp0+XzHk0DwLCq3xQ==}
+ tree-sitter@0.25.0:
+ resolution: {integrity: sha512-PGZZzFW63eElZJDe/b/R/LbsjDDYJa5UEjLZJB59RQsMX+fo0j54fqBPn1MGKav/QNa0JR0zBiVaikYDWCj5KQ==}
triple-beam@1.4.1:
resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==}
@@ -9634,7 +9737,7 @@ snapshots:
'@gitbeaker/core': 43.5.0
'@gitbeaker/requester-utils': 43.5.0
- '@google-cloud/aiplatform@5.7.0':
+ '@google-cloud/aiplatform@5.12.0':
dependencies:
google-gax: 5.0.1
transitivePeerDependencies:
@@ -9693,13 +9796,13 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@google-cloud/discoveryengine@2.4.0':
+ '@google-cloud/discoveryengine@2.5.2':
dependencies:
google-gax: 5.0.1
transitivePeerDependencies:
- supports-color
- '@google-cloud/firestore@7.11.3(encoding@0.1.13)':
+ '@google-cloud/firestore@7.11.6(encoding@0.1.13)':
dependencies:
'@opentelemetry/api': 1.9.0
fast-deep-equal: 3.1.3
@@ -9796,13 +9899,13 @@ snapshots:
- encoding
- supports-color
- '@google-cloud/secret-manager@6.1.0':
+ '@google-cloud/secret-manager@6.1.1':
dependencies:
google-gax: 5.0.1
transitivePeerDependencies:
- supports-color
- '@google-cloud/storage@7.17.1(encoding@0.1.13)':
+ '@google-cloud/storage@7.17.2(encoding@0.1.13)':
dependencies:
'@google-cloud/paginator': 5.0.2
'@google-cloud/projectify': 4.0.0
@@ -12263,6 +12366,8 @@ snapshots:
commander@13.1.0: {}
+ commander@14.0.2: {}
+
commander@2.20.3: {}
commander@5.1.0: {}
@@ -13575,8 +13680,8 @@ snapshots:
node-forge: 1.3.1
uuid: 10.0.0
optionalDependencies:
- '@google-cloud/firestore': 7.11.3(encoding@0.1.13)
- '@google-cloud/storage': 7.17.1(encoding@0.1.13)
+ '@google-cloud/firestore': 7.11.6(encoding@0.1.13)
+ '@google-cloud/storage': 7.17.2(encoding@0.1.13)
transitivePeerDependencies:
- encoding
- supports-color
@@ -17231,22 +17336,86 @@ snapshots:
tree-kill@1.2.2: {}
- tree-sitter-javascript@0.23.1(tree-sitter@0.21.1):
+ tree-sitter-c-sharp@0.23.1(tree-sitter@0.25.0):
+ dependencies:
+ node-addon-api: 8.5.0
+ node-gyp-build: 4.8.4
+ optionalDependencies:
+ tree-sitter: 0.25.0
+
+ tree-sitter-c@0.23.6(tree-sitter@0.25.0):
+ dependencies:
+ node-addon-api: 8.5.0
+ node-gyp-build: 4.8.4
+ optionalDependencies:
+ tree-sitter: 0.25.0
+
+ tree-sitter-cpp@0.23.4(tree-sitter@0.25.0):
+ dependencies:
+ node-addon-api: 8.5.0
+ node-gyp-build: 4.8.4
+ tree-sitter-c: 0.23.6(tree-sitter@0.25.0)
+ optionalDependencies:
+ tree-sitter: 0.25.0
+
+ tree-sitter-go@0.25.0(tree-sitter@0.25.0):
+ dependencies:
+ node-addon-api: 8.5.0
+ node-gyp-build: 4.8.4
+ optionalDependencies:
+ tree-sitter: 0.25.0
+
+ tree-sitter-java@0.23.5(tree-sitter@0.25.0):
+ dependencies:
+ node-addon-api: 8.5.0
+ node-gyp-build: 4.8.4
+ optionalDependencies:
+ tree-sitter: 0.25.0
+
+ tree-sitter-javascript@0.23.1(tree-sitter@0.25.0):
+ dependencies:
+ node-addon-api: 8.5.0
+ node-gyp-build: 4.8.4
+ optionalDependencies:
+ tree-sitter: 0.25.0
+
+ tree-sitter-javascript@0.25.0(tree-sitter@0.25.0):
+ dependencies:
+ node-addon-api: 8.5.0
+ node-gyp-build: 4.8.4
+ optionalDependencies:
+ tree-sitter: 0.25.0
+
+ tree-sitter-python@0.25.0(tree-sitter@0.25.0):
+ dependencies:
+ node-addon-api: 8.5.0
+ node-gyp-build: 4.8.4
+ optionalDependencies:
+ tree-sitter: 0.25.0
+
+ tree-sitter-rust@0.24.0(tree-sitter@0.25.0):
+ dependencies:
+ node-addon-api: 8.5.0
+ node-gyp-build: 4.8.4
+ optionalDependencies:
+ tree-sitter: 0.25.0
+
+ tree-sitter-scala@0.24.0(tree-sitter@0.25.0):
dependencies:
node-addon-api: 8.5.0
node-gyp-build: 4.8.4
optionalDependencies:
- tree-sitter: 0.21.1
+ tree-sitter: 0.25.0
- tree-sitter-typescript@0.23.2(tree-sitter@0.21.1):
+ tree-sitter-typescript@0.23.2(tree-sitter@0.25.0):
dependencies:
node-addon-api: 8.5.0
node-gyp-build: 4.8.4
- tree-sitter-javascript: 0.23.1(tree-sitter@0.21.1)
+ tree-sitter-javascript: 0.23.1(tree-sitter@0.25.0)
optionalDependencies:
- tree-sitter: 0.21.1
+ tree-sitter: 0.25.0
- tree-sitter@0.21.1:
+ tree-sitter@0.25.0:
dependencies:
node-addon-api: 8.5.0
node-gyp-build: 4.8.4
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index ba827e83..8a1acad5 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -10,5 +10,13 @@ onlyBuiltDependencies:
- puppeteer
- re2
- tree-sitter
+ - tree-sitter-c
+ - tree-sitter-c-sharp
+ - tree-sitter-cpp
+ - tree-sitter-go
+ - tree-sitter-java
- tree-sitter-javascript
+ - tree-sitter-python
+ - tree-sitter-rust
+ - tree-sitter-scala
- tree-sitter-typescript
diff --git a/shared/agent/agent.model.ts b/shared/agent/agent.model.ts
index 02f3caaf..3145a457 100644
--- a/shared/agent/agent.model.ts
+++ b/shared/agent/agent.model.ts
@@ -116,7 +116,7 @@ export interface AgentContext {
/** The current state of the agent */
state: AgentRunningState;
/** Tracks what functions/spans we've called into */
- callStack: string[];
+ callStack: string[]; // Must match HasCallStack in agent.types.ts
/** Error message & stack */
error?: string;
output?: string;
diff --git a/shared/agent/agent.types.ts b/shared/agent/agent.types.ts
new file mode 100644
index 00000000..37565700
--- /dev/null
+++ b/shared/agent/agent.types.ts
@@ -0,0 +1,4 @@
+// We use this in trace.ts to minimise dependencies on agent model and its imports on startup
+export interface HasCallStack {
+ callStack: string[];
+}
diff --git a/src/agent/agentContext.test.ts b/src/agent/agentContext.test.ts
index 60e3d483..d2124b48 100644
--- a/src/agent/agentContext.test.ts
+++ b/src/agent/agentContext.test.ts
@@ -7,10 +7,12 @@ import { appContext } from '#app/applicationContext';
import { LlmTools } from '#functions/llmTools';
import { openaiGPT5 } from '#llm/services/openai';
import type { AgentContext } from '#shared/agent/agent.model';
+import { setupConditionalLoggerOutput } from '#test/testUtils';
import { functionRegistry } from '../functionRegistry';
import type { RunAgentConfig } from './autonomous/runAgentTypes';
describe('agentContext', () => {
+ setupConditionalLoggerOutput();
before(() => {
// Required for deserialisation of functions
functionRegistry();
diff --git a/src/agent/agentContextLocalStorage.ts b/src/agent/agentContextLocalStorage.ts
index 3b6f9b48..796a4935 100644
--- a/src/agent/agentContextLocalStorage.ts
+++ b/src/agent/agentContextLocalStorage.ts
@@ -2,13 +2,19 @@ import { AsyncLocalStorage } from 'node:async_hooks';
import { randomUUID } from 'node:crypto';
import { LlmFunctionsImpl } from '#agent/LlmFunctionsImpl';
import { ConsoleCompletedHandler } from '#agent/autonomous/agentCompletion';
-import { FileSystemService } from '#functions/storage/fileSystemService';
import { logger } from '#o11y/logger';
import type { AgentContext, AgentLLMs } from '#shared/agent/agent.model';
import type { IFileSystemService } from '#shared/files/fileSystemService';
import { currentUser } from '#user/userContext';
import type { RunAgentConfig, RunWorkflowConfig } from './autonomous/runAgentTypes';
+// Lazy load FileSystemService to avoid circular dependencies and minimize startup dependencies
+let _FileSystemService: typeof import('#functions/storage/fileSystemService').FileSystemService | undefined;
+function getFileSystemServiceClass(): typeof import('#functions/storage/fileSystemService').FileSystemService {
+ _FileSystemService ??= require('#functions/storage/fileSystemService').FileSystemService;
+ return _FileSystemService!;
+}
+
let _fileSystemOverride: IFileSystemService | null = null;
export function setFileSystemOverride(fs: IFileSystemService | null): void {
@@ -52,13 +58,17 @@ export function addNote(note: string): void {
*/
export function getFileSystem(): IFileSystemService {
if (_fileSystemOverride) return _fileSystemOverride;
- if (!agentContextStorage.getStore()) return new FileSystemService();
+ if (!agentContextStorage.getStore()) {
+ const FileSystemService = getFileSystemServiceClass();
+ return new FileSystemService();
+ }
const filesystem = agentContextStorage.getStore()?.fileSystem;
if (!filesystem) throw new Error('No file system available on the agent context');
return filesystem;
}
export function createContext(config: RunAgentConfig | RunWorkflowConfig): AgentContext {
+ const FileSystemService = getFileSystemServiceClass();
const fileSystem = new FileSystemService(config.fileSystemPath);
const hilBudget = config.humanInLoop?.budget ?? (process.env.HIL_BUDGET ? Number.parseFloat(process.env.HIL_BUDGET) : 2);
const hilCount = config.humanInLoop?.count ?? (process.env.HIL_COUNT ? Number.parseFloat(process.env.HIL_COUNT) : 5);
diff --git a/src/agent/autonomous/autonomousAgentRunner.ts b/src/agent/autonomous/autonomousAgentRunner.ts
index 97487937..a7cefd25 100644
--- a/src/agent/autonomous/autonomousAgentRunner.ts
+++ b/src/agent/autonomous/autonomousAgentRunner.ts
@@ -33,15 +33,15 @@ export async function startAgent(config: RunAgentConfig): Promise
');
agent.inputPrompt = config.initialPrompt;
agent.userPrompt = config.initialPrompt.slice(startIndex, endIndex);
- logger.info('Extracted ');
- logger.info(`agent.userPrompt: ${agent.userPrompt}`);
- logger.info(`agent.inputPrompt: ${agent.inputPrompt}`);
+ logger.debug('Extracted ');
+ logger.debug(`agent.userPrompt: ${agent.userPrompt}`);
+ logger.debug(`agent.inputPrompt: ${agent.inputPrompt}`);
} else {
agent.userPrompt = config.initialPrompt;
agent.inputPrompt = `${config.initialPrompt}`;
- logger.info('Wrapping initialPrompt in ');
- logger.info(`agent.userPrompt: ${agent.userPrompt}`);
- logger.info(`agent.inputPrompt: ${agent.inputPrompt}`);
+ logger.debug('Wrapping initialPrompt in ');
+ logger.debug(`agent.userPrompt: ${agent.userPrompt}`);
+ logger.debug(`agent.inputPrompt: ${agent.inputPrompt}`);
}
await appContext().agentStateService.save(agent);
logger.info(`Created agent ${agent.agentId}`);
diff --git a/src/agent/autonomous/codegen/caching-codegen-agent-system-prompt b/src/agent/autonomous/codegen/caching-codegen-agent-system-prompt
index 23abbcda..cd1aa110 100644
--- a/src/agent/autonomous/codegen/caching-codegen-agent-system-prompt
+++ b/src/agent/autonomous/codegen/caching-codegen-agent-system-prompt
@@ -262,13 +262,13 @@ After providing a planning response you will be asked to provide a coding respon
## Coding Response format
-
+
# Python code as per the instructions to progress the plan
-
+
## Coding Response examples
-
+
# Do not use Example_xxx functions in your code
# Check if the content is in memory from a previous step. Result: None found
tokenDesignPage: str = await Example_getPage("https://docs.github.com/docs/new-runner-token-design")
@@ -283,9 +283,9 @@ current_process_knowledge = f'''
await Agent_setMemory("current_process_knowledge", current_process_knowledge)
# The current process knowledge is minimal, request feedback for more
await AgentFeedback_requestFeedback("I have collated a report on the new registration process. My understanding of the current process is limited. Could you provide more details?")
-
+
-
+
# Check if the desired content is in memory from a previous step. Result: (None found/Found ...)
# Get the two lists asked for in the next step details
list1str: str = await Agent_getMemory("list1-json")
@@ -296,7 +296,7 @@ result2: List[str] = await FunctionClass2_returnStringList()
print("result2.length " + len(result2))
# Do not assume the structure/styles values, return the values for further analysis
return { list1: list1, list2: list2}
-
+
## Coding Response instructions
@@ -319,4 +319,4 @@ return { list1: list1, list2: list2}
- All maths must be done in Python code
- Do NOT assume anything about the structure of the results from functions. Return values that require further analysis
-You must respond in either the or format.
\ No newline at end of file
+You must respond in either the or format.
\ No newline at end of file
diff --git a/src/agent/autonomous/codegen/cachingCodeGenAgentRunner.ts b/src/agent/autonomous/codegen/cachingCodeGenAgentRunner.ts
index 842a684c..c4297adb 100644
--- a/src/agent/autonomous/codegen/cachingCodeGenAgentRunner.ts
+++ b/src/agent/autonomous/codegen/cachingCodeGenAgentRunner.ts
@@ -181,8 +181,8 @@ export async function runCachingCodegenAgent(agent: AgentContext): Promise' };
- const agentCodeResponse: string = `\n${await agentLLM.generateText(agent.messages, {
+ agent.messages[8] = { role: 'assistant', content: '' };
+ const agentCodeResponse: string = `\n${await agentLLM.generateText(agent.messages, {
id: 'dynamicAgentCode',
stopSequences,
temperature: 0.7,
diff --git a/src/agent/autonomous/codegen/codeGenAgentCodeReview.ts b/src/agent/autonomous/codegen/codeGenAgentCodeReview.ts
index 0f4d9524..1b97b9cd 100644
--- a/src/agent/autonomous/codegen/codeGenAgentCodeReview.ts
+++ b/src/agent/autonomous/codegen/codeGenAgentCodeReview.ts
@@ -30,7 +30,7 @@ Your task is to review the code provided to ensure it follows the following inst
${agentPlanResponse}
-First output through your review of the code in the tags against each of the review instructions.
+First output through your review of the code in the tags against each of the review instructions.
Then output the updated code to go in main() method wrapped in tags without any extra indentation.
If there are no changes to make then output the existing code as is in the result tags.
`;
diff --git a/src/agent/autonomous/codegen/codeGenAgentRunner.test.ts b/src/agent/autonomous/codegen/codeGenAgentRunner.test.ts
index c48c602a..02d63062 100644
--- a/src/agent/autonomous/codegen/codeGenAgentRunner.test.ts
+++ b/src/agent/autonomous/codegen/codeGenAgentRunner.test.ts
@@ -26,23 +26,23 @@ const PY_TEST_FUNC_SUM = (num1, num2) => `await ${TEST_FUNC_SUM}(${num1}, ${num2
const PY_TEST_FUNC_THROW_ERROR = `await ${TEST_FUNC_THROW_ERROR}()`;
const PY_SET_MEMORY = (key, content) => `await ${AGENT_MEMORY}("SAVE", "${key}", "${content}")`;
-const PYTHON_CODE_PLAN = (pythonCode: string) => `\nRun some code\n${pythonCode}\n`;
+const PYTHON_CODE_PLAN = (pythonCode: string) => `\nRun some code\n${pythonCode}\n`;
const REQUEST_FEEDBACK_FUNCTION_CALL_PLAN = (feedback) =>
- `\nRequesting feedback\n${PY_AGENT_REQUEST_FEEDBACK(feedback)}\n`;
+ `\nRequesting feedback\n${PY_AGENT_REQUEST_FEEDBACK(feedback)}\n`;
-const COMPLETE_FUNCTION_CALL_PLAN = `\nReady to complete\n${PY_AGENT_COMPLETED('done')}\n`;
+const COMPLETE_FUNCTION_CALL_PLAN = `\nReady to complete\n${PY_AGENT_COMPLETED('done')}\n`;
const ITERATION_SUMMARY_RESPONSE = '';
-const NOOP_FUNCTION_CALL_PLAN = `\nI'm going to call the noop function\n${PY_TEST_FUNC_NOOP}\n`;
+const NOOP_FUNCTION_CALL_PLAN = `\nI'm going to call the noop function\n${PY_TEST_FUNC_NOOP}\n`;
-const SKY_COLOUR_FUNCTION_CALL_PLAN = `\nGet the sky colour\n${PY_TEST_FUNC_SKY_COLOUR}\n`;
+const SKY_COLOUR_FUNCTION_CALL_PLAN = `\nGet the sky colour\n${PY_TEST_FUNC_SKY_COLOUR}\n`;
function result(contents: string): string {
return `${contents}`;
}
-xdescribe('codegenAgentRunner', () => {
+describe('codegenAgentRunner', () => {
setupConditionalLoggerOutput();
const ctx = initInMemoryApplicationContext();
@@ -93,7 +93,7 @@ xdescribe('codegenAgentRunner', () => {
beforeEach(() => {
initInMemoryApplicationContext();
// This is needed for the tests on the LlmCall.callStack property
- setTracer(null, agentContextStorage);
+ setTracer(null);
mockLLM = mockLLMs().easy as MockLLM;
mockLLM.reset();
functions = new LlmFunctionsImpl(AgentFeedback);
diff --git a/src/agent/autonomous/codegen/codegen-agent-system-prompt b/src/agent/autonomous/codegen/codegen-agent-system-prompt
index 69e32383..e96e046a 100644
--- a/src/agent/autonomous/codegen/codegen-agent-system-prompt
+++ b/src/agent/autonomous/codegen/codegen-agent-system-prompt
@@ -204,30 +204,37 @@ Example_PublicWeb_getPage is suitable as the URLs are on publicly available docu
We can retrieve the two pages, and then create a report by processing the combined contents.
-
+
# Do not use Example_xxx functions in your code
# Check if the content is in memory from a previous step. Result: None found
tokenDesignPage: str = await Example_getPage("https://docs.github.com/docs/new-runner-token-design")
runnerRegistrationPage: str = await Example_getPage("https://blog.github.com/new-runner-registration")
-webPages: str = f'${tokenDesignPage}${runnerRegistrationPage}'
+webPages: str = f'{{tokenDesignPage}}{{runnerRegistrationPage}}'
newProcessReport: str = await Example_processText(webPages, "Provide a detailed report of the new token registration process")
# Store the work we have done so far
-await Agent_setMemory("new_registration_process", newProcessReport)
+await Agent_memory("SAVE", "new_registration_process", newProcessReport)
current_process_knowledge = f'''
'''
await Agent_setMemory("current_process_knowledge", current_process_knowledge)
# The current process knowledge is minimal, request feedback for more
-await AgentFeedback_requestFeedback("I have collated a report on the new registration process. My understanding of the current process is limited. Could you provide more details?")
-
+await AgentFeedback_completed("I have collated a report on the new registration process. My understanding of the current process is limited. Could you provide more details?")
+
-
-
-
+line1
+line2
+line3
+
+
+
+# This is a revised code block, demonstrating the agent emitting multiple code blocks.
+# The last block will be executed.
+MorphEditor_editFile("content.txt", "Adding lines", FileXYZ_edit_diff)
+
@@ -238,12 +245,12 @@ The output or error from the previously executed Python script will be provided
- **Summaries:** For medium or large outputs/errors, only a summary will be shown directly in the tag. The `summary="true"` attribute will indicate this.
- **Full Content Access:**
- - The full result of the previous script is available in the Python variable
- `PREVIOUS_SCRIPT_RESULT` (or `PREVIOUS_SCRIPT_ERROR` if the previous run raised an error).
- - Note that PREVIOUS_SCRIPT_RESULT is a string value in JSON format. You will need to parse it to a Dict to lookup keys.
+ - The full output or error of the previous script is available in the Python variable
+ `PREVIOUS_SCRIPT_OUTPUT`.
+ - Note that `PREVIOUS_SCRIPT_OUTPUT` is a string value in JSON format. You will need to parse it to a Dict to lookup keys.
- The `` or `` tag in the prompt provides a summary
of the output/error and indicates the name of the Python variable holding the
- full content or handle via the `py_var_name` attribute.
+ full content via the `py_var_name` attribute.
# Response format
@@ -296,7 +303,7 @@ Select the function(s) to best complete the next step. You may call more than on
Otherwise return any values to analyse further.
-->
-
+
-
+
-
-
-
+
+
+
+VALUE
+
+
diff --git a/src/agent/autonomous/codegen/codegenAutonomousAgent.ts b/src/agent/autonomous/codegen/codegenAutonomousAgent.ts
index 4f483fc9..15e96860 100644
--- a/src/agent/autonomous/codegen/codegenAutonomousAgent.ts
+++ b/src/agent/autonomous/codegen/codegenAutonomousAgent.ts
@@ -20,6 +20,7 @@ import {
extractExpandedUserRequest,
extractNextStepDetails,
extractObservationsReasoning,
+ extractPythonGlobals,
} from '#agent/autonomous/codegen/codegenAutonomousAgentUtils';
import { AGENT_REQUEST_FEEDBACK, REQUEST_FEEDBACK_PARAM_NAME } from '#agent/autonomous/functions/agentFeedback';
import { AGENT_COMPLETED_NAME, AGENT_COMPLETED_PARAM_NAME, AGENT_SAVE_MEMORY_CONTENT_PARAM_NAME } from '#agent/autonomous/functions/agentFunctions';
@@ -57,8 +58,7 @@ const AGENT_TEMPERATURE = 0.6;
const LARGE_OUTPUT_THRESHOLD_BYTES = 50 * 1024; // 50KB
const MAX_PROMPT_TAG_CONTENT_BYTES = 1024; // Max length for summary in tag
-const PREVIOUS_SCRIPT_RESULT_VAR = 'PREVIOUS_SCRIPT_RESULT';
-const PREVIOUS_SCRIPT_ERROR_VAR = 'PREVIOUS_SCRIPT_ERROR';
+const PREVIOUS_SCRIPT_OUTPUT_VAR = 'PREVIOUS_SCRIPT_OUTPUT';
export const CODEGEN_AGENT_SPAN = 'CodeGen Agent';
@@ -139,9 +139,9 @@ async function runAgentExecution(agent: AgentContext, span: Span): Promise = {
- [PREVIOUS_SCRIPT_RESULT_VAR]: '',
- [PREVIOUS_SCRIPT_ERROR_VAR]: '',
+ [PREVIOUS_SCRIPT_OUTPUT_VAR]: '',
};
let shouldContinue = true;
@@ -258,19 +258,28 @@ async function runAgentExecution(agent: AgentContext, span: Span): Promise section that we pass to the next iteration, so the agent does not see the full script.
- // Replace whatever was inside … with the clean main-function code we just validated.
- const sanitised = agentPlanResponse.replace(/[\s\S]*?<\/python-code>/i, `\n${pythonMainFnCode}\n`);
+ // Only keep the main-function code in the section that we pass to the next iteration, so the agent does not see the full script and attempt to replicate that.
+ // Replace whatever was inside … with the clean main-function code we just validated.
+ if (!/ block');
+ }
+ const sanitised = replaceLastAgentPythonCodeBlock(agentPlanResponse, pythonMainFnCode);
// Save for next prompt
previousAgentPlanResponse = `\n${sanitised}\n`;
+ // Extract Python globals from the LLM response (by scanning for tags, with fallback to )
+ const pythonGlobalsFromXml: Record = extractPythonGlobals(agentPlanResponse);
+ if (Object.keys(pythonGlobalsFromXml).length) {
+ logger.debug({ pythonGlobalVars: Object.keys(pythonGlobalsFromXml) }, 'Extracted python globals from XML tags');
+ }
+
const currentIterationFunctionCalls: FunctionCallResult[] = [];
// Configure the objects for the Python global scope which proxy to the available @func class methods
const functionInstances: Record = agent.functions.getFunctionInstanceMap();
const functionSchemas: FunctionSchema[] = getAllFunctionSchemas(Object.values(functionInstances));
const functionProxies = setupPyodideFunctionProxies(functionSchemas, agent, agentPlanResponse, currentIterationFunctionCalls);
- const allGlobalsForPyodide = { ...functionProxies, ...pyGlobalsForNextScriptExecution }; // Combine proxies with previous script output/error global
+ const allGlobalsForPyodide = { ...pythonGlobalsFromXml, ...pyGlobalsForNextScriptExecution, ...functionProxies }; // XML globals + prev script vars + proxies (proxies win)
const pyodideGlobals = pyodide.toPy(allGlobalsForPyodide);
const wrapperCode = generatePythonWrapper(functionSchemas, pythonMainFnCode);
@@ -346,15 +355,14 @@ async function runAgentExecution(agent: AgentContext, span: Span): Promise LARGE_OUTPUT_THRESHOLD_BYTES) {
tagAttributes += ' summary="true"';
}
@@ -367,7 +375,8 @@ async function runAgentExecution(agent: AgentContext, span: Span): Promise${CDATA_START}\n${promptTagContent}\n${CDATA_END}${tagName}>`;
// Prepare Pyodide Global Variable Injection for the *next* iteration
- pyGlobalsForNextScriptExecution = { [pyGlobalVarName]: currentScriptOutputContent };
+ // The PREVIOUS_SCRIPT_OUTPUT_VAR will contain the full content of the last script's output or error.
+ pyGlobalsForNextScriptExecution = { [PREVIOUS_SCRIPT_OUTPUT_VAR]: currentScriptOutputContent };
// --- End of script output/error handling ---
currentFunctionHistorySize = agent.functionCallHistory.length;
@@ -498,6 +507,27 @@ function extractLineNumber(text: string): number | null {
return null;
}
+/**
+ * Replaces the content of the last block in the response string.
+ * This is used to update the agent's own response with the syntactically corrected code
+ * before passing it to the next iteration.
+ * @param response The full response string from the LLM.
+ * @param cleanedCode The syntactically corrected Python code to insert.
+ * @returns The response string with the last block updated.
+ */
+function replaceLastAgentPythonCodeBlock(response: string, cleanedCode: string): string {
+ const regex = /]*>([\s\S]*?)<\/agent:python_code>/gi;
+ let match: RegExpExecArray | null = null;
+ let lastMatch: RegExpExecArray | null = null;
+ // biome-ignore lint/suspicious/noAssignInExpressions: ok
+ while ((match = regex.exec(response)) !== null) lastMatch = match;
+ if (!lastMatch) return response; // No block found
+
+ const [fullMatch] = lastMatch;
+ const replacement = `\n${cleanedCode}\n`;
+ return response.slice(0, lastMatch.index) + replacement + response.slice(lastMatch.index + fullMatch.length);
+}
+
function setupPyodideFunctionProxies(
functionSchemas: FunctionSchema[],
agent: AgentContext,
diff --git a/src/agent/autonomous/codegen/codegenAutonomousAgentUtils.test.ts b/src/agent/autonomous/codegen/codegenAutonomousAgentUtils.test.ts
index cec32924..352ec4b7 100644
--- a/src/agent/autonomous/codegen/codegenAutonomousAgentUtils.test.ts
+++ b/src/agent/autonomous/codegen/codegenAutonomousAgentUtils.test.ts
@@ -1,6 +1,7 @@
import { expect } from 'chai';
// Adjust the import path as necessary
+import { extractPythonGlobals } from '#agent/autonomous/codegen/codegenAutonomousAgentUtils';
import { convertTypeScriptToPython } from '#agent/autonomous/codegen/pythonCodeGenUtils';
describe('codegenAgentUtils', () => {
@@ -138,4 +139,63 @@ describe('codegenAgentUtils', () => {
expect(convertTypeScriptToPython(' ')).to.equal('');
});
});
+
+ describe('Python Globals extraction', () => {
+ it('parses a single (preserves multiline content)', () => {
+ const llmResponse = `
+ line1
+line2
+line3
+`;
+ expect(extractPythonGlobals(llmResponse)).to.deep.equal({
+ FileXYZ_edit_diff: 'line1\nline2\nline3\n',
+ });
+ });
+
+ it('parses multiple entries', () => {
+ const llmResponse = `
+ value A
+ value B`;
+ expect(extractPythonGlobals(llmResponse)).to.deep.equal({
+ A: 'value A',
+ B: 'value B',
+ });
+ });
+
+ it('should merge multiple tags, with later tags overriding redefined variables', () => {
+ // Later tags only override the variables they explicitly redefine, so we merge them cumulatively.
+ const llmResponse = `
+ old A
+ value B
+...some content...
+ new A
+ value C`;
+ expect(extractPythonGlobals(llmResponse)).to.deep.equal({
+ A: 'new A',
+ B: 'value B',
+ C: 'value C',
+ });
+ });
+
+ it('falls back to the last block if no are present', () => {
+ const llmResponse = `
+
+ old A
+
+...some content...
+
+ new A
+ value C
+`;
+ expect(extractPythonGlobals(llmResponse)).to.deep.equal({
+ A: 'new A',
+ C: 'value C',
+ });
+ });
+
+ it('returns empty object when no global blocks present', () => {
+ const llmResponse = `print("hi")`;
+ expect(extractPythonGlobals(llmResponse)).to.deep.equal({});
+ });
+ });
});
diff --git a/src/agent/autonomous/codegen/codegenAutonomousAgentUtils.ts b/src/agent/autonomous/codegen/codegenAutonomousAgentUtils.ts
index 3914c053..3dfcfe3d 100644
--- a/src/agent/autonomous/codegen/codegenAutonomousAgentUtils.ts
+++ b/src/agent/autonomous/codegen/codegenAutonomousAgentUtils.ts
@@ -67,3 +67,61 @@ export function extractCodeReview(llmResponse: string): string {
export function extractAgentPlan(llmResponse: string): string {
return extractLastXmlTagContent(llmResponse, 'plan');
}
+
+/**
+ * Extracts python global variable definitions from the last block.
+ * @param llmResponse The full response string from the LLM.
+ * @returns A record where keys are variable names and values are their string content.
+ */
+/**
+ * Extracts Python global variable definitions from tags.
+ * Globals can be emitted as independent tags anywhere in the response.
+ * Later occurrences of the same variable name will override earlier ones.
+ * If no tags are found, it falls back to the legacy block.
+ * @param llmResponse The full response string from the LLM.
+ * @returns A record where keys are variable names and values are their string content.
+ */
+export function extractPythonGlobals(llmResponse: string): Record {
+ const globals: Record = {};
+ let newStyleTagFound = false;
+
+ // Scan for all individual tags
+ const re = /]*var="([^"]+)"[^>]*>([\s\S]*?)<\/agent:python_global>/gi;
+ let m: RegExpExecArray | null;
+ // biome-ignore lint/suspicious/noAssignInExpressions: ok
+ while ((m = re.exec(llmResponse)) !== null) {
+ newStyleTagFound = true;
+ const varName = m[1].trim();
+ const value = m[2].replace(/\r?$/, ''); // Remove trailing carriage return if present
+ if (varName) {
+ globals[varName] = value;
+ }
+ }
+
+ if (newStyleTagFound) {
+ return globals; // If any new-style tag found, return the merged content
+ }
+
+ // Fallback to legacy block if no new-style tags are present (last one wins)
+ const openTag = '';
+ const closeTag = '';
+ const startIndex = llmResponse.lastIndexOf(openTag);
+ if (startIndex === -1) return {}; // No legacy tag either
+ const endIndex = llmResponse.indexOf(closeTag, startIndex);
+ if (endIndex === -1) return {};
+
+ const globalsBlock = llmResponse.substring(startIndex + openTag.length, endIndex);
+
+ const legacyRe = /([\s\S]*?)<\/python:global>/g;
+ let legacyM: RegExpExecArray | null;
+ const legacyGlobals: Record = {}; // Use a separate object for legacy to avoid mixing
+ // biome-ignore lint/suspicious/noAssignInExpressions: ok
+ while ((legacyM = legacyRe.exec(globalsBlock)) !== null) {
+ const varName = legacyM[1].trim();
+ const value = legacyM[2].replace(/\r?$/, '');
+ if (varName) {
+ legacyGlobals[varName] = value;
+ }
+ }
+ return legacyGlobals;
+}
diff --git a/src/agent/autonomous/codegen/pyodideDeepConversion.test.ts b/src/agent/autonomous/codegen/pyodideDeepConversion.test.ts
index 4dacd9ab..a6325c66 100644
--- a/src/agent/autonomous/codegen/pyodideDeepConversion.test.ts
+++ b/src/agent/autonomous/codegen/pyodideDeepConversion.test.ts
@@ -1,10 +1,12 @@
import { expect } from 'chai';
import { type PyodideInterface, loadPyodide } from 'pyodide';
import type { FunctionSchema } from '#functionSchema/functions';
+import { setupConditionalLoggerOutput } from '#test/testUtils';
import { generatePythonWrapper } from './codegenAutonomousAgent';
import { mainFnCodeToFullScript } from './pythonCodeGenUtils';
describe('Pyodide deep conversion in wrappers', () => {
+ setupConditionalLoggerOutput();
let py: PyodideInterface;
before(async function () {
diff --git a/src/agent/autonomous/codegen/pythonCodeGenUtils.ts b/src/agent/autonomous/codegen/pythonCodeGenUtils.ts
index d05fd7f6..0cded081 100644
--- a/src/agent/autonomous/codegen/pythonCodeGenUtils.ts
+++ b/src/agent/autonomous/codegen/pythonCodeGenUtils.ts
@@ -179,15 +179,11 @@ fun ${def.name}(${def.parameters.map((p) => `${p.name}: ${p.optional ? `Optional
export function convertTypeScriptToPython(tsType: string): string {
// 0. Handle base cases and trim input
const originalTsType = tsType.trim();
- if (!originalTsType) {
- return '';
- }
+ if (!originalTsType) return '';
// 1. Strip Promise wrapper first
const processedType = originalTsType.replace(/^Promise<(.+)>$/, '$1').trim();
- if (!processedType) {
- return 'None'; // Handle Promise
- }
+ if (!processedType) return 'None'; // Handle Promise
// 2. Handle Unions by splitting and recursion
// Always try splitting by '|' and recurse on parts.
@@ -206,12 +202,8 @@ export function convertTypeScriptToPython(tsType: string): string {
// --- Process Single Type Part ---
// 3. Handle specific known types first (Binary, Object Literal, object keyword)
- if (processedType === 'Uint8Array' || processedType === 'Buffer' || processedType === 'ArrayBuffer') {
- return 'bytes';
- }
- if ((processedType.startsWith('{') && processedType.endsWith('}') && processedType.includes(':')) || processedType === 'object') {
- return 'Dict[str, Any]';
- }
+ if (processedType === 'Uint8Array' || processedType === 'Buffer' || processedType === 'ArrayBuffer') return 'bytes';
+ if ((processedType.startsWith('{') && processedType.endsWith('}') && processedType.includes(':')) || processedType === 'object') return 'Dict[str, Any]';
// 4. Handle base keywords
const keywordMappings: { [key: string]: string } = {
@@ -265,32 +257,30 @@ export function removePythonMarkdownWrapper(code: string): string {
}
/**
- * Extracts the text within tags
+ * Extracts the text within tags.
* @param llmResponse response from the LLM
*/
export function extractPythonCode(llmResponse: string): string {
- const index = llmResponse.lastIndexOf('');
- if (index < 0) {
+ const agentCodeMatches = [...llmResponse.matchAll(/]*>([\s\S]*?)<\/agent:python_code>/gi)];
+ if (agentCodeMatches.length === 0) {
logger.error(llmResponse);
- throw new Error('Could not find in response');
+ throw new Error('Could not find in response');
}
-
- const resultText = llmResponse.slice(index);
- const regexXml = /(.*)<\/python-code>/is;
- const matchXml = regexXml.exec(resultText);
-
- if (!matchXml) throw new Error(`Could not find in the response \n${resultText}`);
-
- const xmlContents = matchXml[1].trim();
- return removePythonMarkdownWrapper(xmlContents);
+ const lastMatch = agentCodeMatches.at(-1)!;
+ return removePythonMarkdownWrapper(lastMatch[1].trim());
}
/**
- * Extracts the text within tags.
+ * Extracts the text within the FIRST tags if there is more than one.
* @param llmResponse response from the LLM
*/
-export function extractDraftPythonCode(llmResponse: string): string {
- return extractLastXmlTagContent(llmResponse, 'draft-python-code');
+export function extractDraftPythonCode(llmResponse: string): string | undefined {
+ // Prioritize the new tag, taking the FIRST occurrence
+ const agentCodeMatch = llmResponse.match(/]*>([\s\S]*?)<\/agent:python_code>/i);
+ if (agentCodeMatch && agentCodeMatch.length > 1) {
+ return removePythonMarkdownWrapper(agentCodeMatch[0].trim());
+ }
+ return undefined;
}
/**
diff --git a/src/agent/autonomous/functions/agentFunctions.ts b/src/agent/autonomous/functions/agentFunctions.ts
index bcbc18c0..b1dfd704 100644
--- a/src/agent/autonomous/functions/agentFunctions.ts
+++ b/src/agent/autonomous/functions/agentFunctions.ts
@@ -21,7 +21,7 @@ export class Agent {
*/
@func()
async completed(note: string): Promise {
- await this.saveMemory('Agent_competed_note', note ?? '');
+ await this.saveMemory('Agent_completed_note', note ?? '', '');
logger.info(`Agent completed. Note: ${note}`);
}
@@ -31,7 +31,7 @@ export class Agent {
* @param {string} content The plain text contents to store in the working memory
*/
// @func()
- async saveMemory(key: string, content: string): Promise {
+ async saveMemory(key: string, content: string, description: string): Promise {
if (!key || !key.trim().length) throw new Error('Memory key must be provided');
if (!content || !content.trim().length) throw new Error('Memory content must be provided');
const memory = agentContext()!.memory;
@@ -69,12 +69,16 @@ export class Agent {
* @param operation 'SAVE', 'DELETE', or 'GET'
* @param key The memory key to save, delete, or get
* @param content The content to save to the memory (when operation is 'SAVE')
+ * @param description The description to save to the memory (when operation is 'SAVE')
* @returns void, or string when operation is 'GET'
*/
@func()
- async memory(operation: 'SAVE' | 'DELETE' | 'GET', key: string, content?: string): Promise {
+ async memory(operation: 'SAVE' | 'DELETE' | 'GET', key: string, content?: string, description?: string): Promise {
if (operation === 'SAVE') {
- await this.saveMemory(key, content as string);
+ if (!content) throw new Error('Content must be provided when saving memory');
+ if (!key) throw new Error('Key must be provided when saving memory');
+ // if (!description) throw new Error('Description must be provided when saving memory');
+ await this.saveMemory(key, content, description ?? '');
return undefined;
}
if (operation === 'DELETE') {
diff --git a/src/agent/autonomous/xml/xmlAutonomousAgent.test.ts b/src/agent/autonomous/xml/xmlAutonomousAgent.test.ts
index 551c2831..fbb72dbc 100644
--- a/src/agent/autonomous/xml/xmlAutonomousAgent.test.ts
+++ b/src/agent/autonomous/xml/xmlAutonomousAgent.test.ts
@@ -12,6 +12,7 @@ import { setTracer } from '#o11y/trace';
import type { AgentContext, AgentLLMs } from '#shared/agent/agent.model';
import { lastText } from '#shared/llm/llm.model';
import type { User } from '#shared/user/user.model';
+import { setupConditionalLoggerOutput } from '#test/testUtils';
import { sleep } from '#utils/async-utils';
import { agentContextStorage } from '../../agentContextLocalStorage';
import { type RunAgentConfig } from '../runAgentTypes';
@@ -23,6 +24,8 @@ const NOOP_FUNCTION_CALL = `I'm going to call the noop function\nGet the sky colour\n${TEST_FUNC_SKY_COLOUR}`;
describe('xmlAgentRunner', () => {
+ setupConditionalLoggerOutput();
+
const app = appContext();
let mockLLM = new MockLLM();
let llms: AgentLLMs = {
@@ -84,7 +87,7 @@ describe('xmlAgentRunner', () => {
beforeEach(() => {
initInMemoryApplicationContext();
// This is needed for the tests on the LlmCall.callStack property
- setTracer(null, agentContextStorage);
+ setTracer(null);
mockLLM = new MockLLM();
llms = {
easy: mockLLM,
diff --git a/src/app/applicationContext.ts b/src/app/applicationContext.ts
index e2b39cbc..0dd86e77 100644
--- a/src/app/applicationContext.ts
+++ b/src/app/applicationContext.ts
@@ -1,7 +1,9 @@
-import { agentContext } from '#agent/agentContextLocalStorage';
+import { agentContext, agentContextStorage } from '#agent/agentContextLocalStorage';
+import { checkForceStopped } from '#agent/forceStopAgent';
import { inMemoryApplicationContext } from '#modules/memory/inMemoryApplicationContext';
import { InMemoryUserService } from '#modules/memory/inMemoryUserService';
import { logger, setLogEnricher } from '#o11y/logger';
+import { setAgentContextStorageAndCheckForceStopped } from '#o11y/trace';
import type { ApplicationContext } from './applicationTypes';
export let applicationContext: ApplicationContext;
@@ -59,6 +61,7 @@ export async function initApplicationContext(): Promise {
if (agent.parentAgentId) logObj.parentAgentId = agent.parentAgentId;
}
});
+ setAgentContextStorageAndCheckForceStopped(agentContextStorage, checkForceStopped);
return applicationContext;
}
diff --git a/src/benchmarks/swebench/swe-bench-runner.ts b/src/benchmarks/swebench/swe-bench-runner.ts
index a2b697f1..0cdcaac1 100644
--- a/src/benchmarks/swebench/swe-bench-runner.ts
+++ b/src/benchmarks/swebench/swe-bench-runner.ts
@@ -2,7 +2,7 @@ import { promises as fs } from 'node:fs';
import path from 'node:path';
import { v4 as uuidv4 } from 'uuid';
import { logger } from '#o11y/logger';
-import { execCommand, failOnError } from '#utils/exec';
+import { CONTAINER_PATH, execCommand, failOnError } from '#utils/exec';
export interface SWEInstance {
instance_id: string;
@@ -40,8 +40,6 @@ export async function stopContainer(containerIdOrName: string, removeContainer =
}
}
-export const CONTAINER_PATH = '/testbed';
-
export async function startContainer(workspacePath: string, problemId: string): Promise<{ containerId: string; repoPathOnHost: string }> {
const containerName = `sweb.typedai.${problemId}_${uuidv4().slice(0, 8)}`;
const tempContainerName = `${containerName}.temp`;
diff --git a/src/cache/cacheRetry.ts b/src/cache/cacheRetry.ts
index 465f9f98..c09ce9ab 100644
--- a/src/cache/cacheRetry.ts
+++ b/src/cache/cacheRetry.ts
@@ -32,7 +32,7 @@ export class RetryableError extends Error {
* Decorator for adding caching and retries to a class method
* @param options
*/
-export function cacheRetry(options: Partial = DEFAULTS) {
+export function cacheRetry(options: Partial = {}) {
return function cacheRetryDecorator(originalMethod: any, context: ClassMethodDecoratorContext): (this: any, ...args: any[]) => Promise {
const methodName = String(context.name);
diff --git a/src/cli/codeAgent.ts b/src/cli/codeAgent.ts
index ba345941..40ba68e0 100644
--- a/src/cli/codeAgent.ts
+++ b/src/cli/codeAgent.ts
@@ -18,6 +18,7 @@ import { Slack } from '#modules/slack/slack';
import { logger } from '#o11y/logger';
import { CodeEditingAgent } from '#swe/codeEditingAgent';
import { CodeFunctions } from '#swe/codeFunctions';
+import { MorphEditor } from '#swe/morph/morphEditor';
import { registerErrorHandlers } from '../errorHandlers';
import { parseProcessArgs, saveAgentId } from './cli';
import { loadCliEnvironment } from './envLoader';
@@ -61,19 +62,20 @@ export async function main(): Promise {
}
const functions = [
- AgentFeedback,
- PublicWeb,
+ // AgentFeedback,
+ // PublicWeb,
CodeFunctions,
FileSystemList,
FileSystemTree,
LiveFiles,
Perplexity,
- CodeEditingAgent,
+ MorphEditor,
+ // CodeEditingAgent,
// Git,
// GitLab,
- DeepThink,
+ // DeepThink,
// Jira,
- Slack,
+ // Slack,
];
// Add any additional functions provided from CLI args
let additionalFunctions: Array any> = [];
diff --git a/src/cli/envLoader.ts b/src/cli/envLoader.ts
index 822a7577..d401de62 100644
--- a/src/cli/envLoader.ts
+++ b/src/cli/envLoader.ts
@@ -107,6 +107,7 @@ export function loadEnvFile(filePath: string): ParsedEnv {
* overwrite existing `process.env` values.
*/
export function applyEnvFile(filePath: string, options: ApplyEnvOptions = {}): void {
+ console.log(`loading env file ${filePath}`);
const envVars = loadEnvFile(filePath);
const override = options.override ?? false;
diff --git a/src/cli/functionAliases.ts b/src/cli/functionAliases.ts
index 10f89337..8270c1d7 100644
--- a/src/cli/functionAliases.ts
+++ b/src/cli/functionAliases.ts
@@ -10,6 +10,7 @@ import { LlmTools } from '#functions/llmTools';
import { FileSystemRead } from '#functions/storage/fileSystemRead';
import { FileSystemWrite } from '#functions/storage/fileSystemWrite';
import { LocalFileStore } from '#functions/storage/localFileStore';
+import { SupportKnowledgebase } from '#functions/supportKnowledgebase';
import { Perplexity } from '#functions/web/perplexity';
import { PublicWeb } from '#functions/web/web';
import { defaultLLMs } from '#llm/services/defaultLlms';
@@ -45,6 +46,7 @@ const functionAliases: Record = {
live: LiveFiles.name,
gcloud: GoogleCloud.name,
custom: CustomFunctions.name,
+ kb: SupportKnowledgebase.name,
};
interface FunctionMatch {
@@ -122,7 +124,7 @@ Which one of these class names most closely matches "${requested}"?
Consider similar words, abbreviations, and common variations.
Respond only with the exact matching class name from the list, or "NO_MATCH" if none are similar enough.`;
- const suggestedMatch = await llm.generateText(prompt);
+ const suggestedMatch = await llm.generateText(prompt, { id: 'Function aliases' });
// Validate LLM response is actually one of our class names
if (suggestedMatch && suggestedMatch !== 'NO_MATCH' && registryMap.has(suggestedMatch)) {
diff --git a/src/cli/gaia.ts b/src/cli/gaia.ts
index 4c2d94a5..2a1f99d2 100644
--- a/src/cli/gaia.ts
+++ b/src/cli/gaia.ts
@@ -103,9 +103,9 @@ async function answerGaiaQuestion(task: GaiaQuestion): Promise {
// Extract reasoning trace from LLM calls
const reasoningTrace: string[] = llmCalls
- .filter((call: LlmCall) => lastText(call.messages).includes(''))
+ .filter((call: LlmCall) => lastText(call.messages).includes(''))
.map((call) => {
- const match = lastText(call.messages).match(/(.*?)<\/python-code>/s);
+ const match = lastText(call.messages).match(/(.*?)<\/agent:python_code>/s);
return match ? match[1].trim() : '';
});
diff --git a/src/cli/scrape.ts b/src/cli/scrape.ts
index 3eaf11c3..c28afd1e 100644
--- a/src/cli/scrape.ts
+++ b/src/cli/scrape.ts
@@ -2,7 +2,6 @@ import '#fastify/trace-init/trace-init'; // leave an empty line next so this doe
import { writeFileSync } from 'node:fs';
import { agentContextStorage, createContext } from '#agent/agentContextLocalStorage';
-import { PublicWeb } from '#functions/web/web';
import { countTokens } from '#llm/tokens';
import { terminalLog } from './terminal';
@@ -19,7 +18,8 @@ async function url2markdown(url: string, outputFilename?: string) {
}),
);
- const markdown = await new PublicWeb().getWebPage(url);
+ const { default: functionModules } = await import('../functions/functionModules.cjs');
+ const markdown = await new functionModules.web.PublicWeb().getWebPage(url);
const file = outputFilename ?? 'scrape.md';
writeFileSync(file, markdown);
const tokens = await countTokens(markdown);
diff --git a/src/cli/startLocal.ts b/src/cli/startLocal.ts
index 743faa7a..f0ab79bb 100644
--- a/src/cli/startLocal.ts
+++ b/src/cli/startLocal.ts
@@ -52,10 +52,10 @@ async function main(): Promise {
process.env.NODE_ENV ??= 'development';
- // Determine if this is the "default" repository setup (e.g., the main repo)
- // or a contributor's setup (e.g., a fork). This affects port handling.
+ // Determine if this is the "default" repository setup (e.g., the main repo at $TYPEDAI_HOME)
+ // or a worktree or seperate clone. This affects port handling.
// In the default setup, we use fixed ports (3000/9229) and fail if they're taken.
- // In a contributor setup, we find the next available port to avoid conflicts.
+ // In a worktree/forked setup, we find the next available port to avoid conflicts.
const repoRoot = path.resolve(process.cwd());
const typedAiHome = process.env.TYPEDAI_HOME ? path.resolve(process.env.TYPEDAI_HOME) : null;
const isDefaultRepo = typedAiHome ? repoRoot === typedAiHome : false;
diff --git a/src/cli/terminal.ts b/src/cli/terminal.ts
index 5872f776..46f7f260 100644
--- a/src/cli/terminal.ts
+++ b/src/cli/terminal.ts
@@ -21,11 +21,17 @@ export function terminalLog(message: string): void {
flags: 'a', // 'a' for append mode is safest
});
+ // Handle errors on the stream to prevent unhandled error events
+ terminalStream.on('error', (error) => {
+ // Silently fall back to console.error if terminal is not available
+ console.error(message);
+ });
+
terminalStream.write(`${message}\n`);
terminalStream.end(); // Close the stream to release the file handle.
} catch (error) {
// If we can't write to the terminal (e.g., not in a TTY),
// we can fall back to stderr as a last resort for visibility.
- // console.error(`Fallback: Could not write directly to terminal. Message: ${message}`);
+ console.error(message);
}
}
diff --git a/src/fastify/trace-init/trace-init.ts b/src/fastify/trace-init/trace-init.ts
index 2ae56752..ad4b7b29 100644
--- a/src/fastify/trace-init/trace-init.ts
+++ b/src/fastify/trace-init/trace-init.ts
@@ -6,12 +6,9 @@ import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
import { Resource } from '@opentelemetry/resources';
import * as opentelemetry from '@opentelemetry/sdk-node';
import { TraceIdRatioBasedSampler } from '@opentelemetry/sdk-trace-base';
-import { PinoInstrumentation } from './instrumentation';
-
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
-import { agentContextStorage } from '#agent/agentContextLocalStorage';
-import { checkForceStopped } from '#agent/forceStopAgent';
import { setTracer } from '#o11y/trace';
+import { PinoInstrumentation } from './instrumentation';
let initialized = false;
let optelNodeSdk: opentelemetry.NodeSDK;
@@ -105,9 +102,9 @@ function initTrace(): void {
});
const tracer = trace.getTracer(traceServiceName);
- setTracer(tracer, agentContextStorage, checkForceStopped);
+ setTracer(tracer);
} else {
- setTracer(null, agentContextStorage, checkForceStopped);
+ setTracer(null);
}
}
diff --git a/src/functions/cloud/google/composerAirflow.ts b/src/functions/cloud/google/composerAirflow.ts
index c9eedae5..5c8c201a 100644
--- a/src/functions/cloud/google/composerAirflow.ts
+++ b/src/functions/cloud/google/composerAirflow.ts
@@ -79,6 +79,20 @@ export class ComposerAirflowClient {
this.httpClient = axios.create({ timeout: 90000 });
}
+ /**
+ * Fetches detailed metadata for a specific DAG.
+ * @param gcpProjectId The Google Cloud Project ID.
+ * @returns A promise that resolves to the DAG detail object.
+ */
+ @func()
+ public async fetchDags(gcpProjectId: string): Promise {
+ const airflowWebServerUrl = this.getWebServerUrl(gcpProjectId);
+ const token = await this.getAuthToken();
+ const url = `${airflowWebServerUrl}/api/v1/dags`;
+ const response = await this.makeRequest(url, 'GET', token);
+ return response.data.dags;
+ }
+
/**
* Helper function to determine the Composer Airflow Web Server URL based on Google Cloud project ID.
*/
diff --git a/src/functions/functionModules.cjs b/src/functions/functionModules.cjs
new file mode 100644
index 00000000..1271d7df
--- /dev/null
+++ b/src/functions/functionModules.cjs
@@ -0,0 +1,166 @@
+/**
+ * Lazy-loading module for all function classes.
+ * This avoids circular dependencies and reduces startup time by only loading
+ * function classes when they are actually called (using lazy getters).
+ *
+ * The key is that require() happens inside the getter, which is only invoked
+ * when the property is accessed - AFTER all module initialization is complete.
+ *
+ * Example usage:
+ * const { default: functionModules } = await import('../functions/functionModules.cjs');
+ * const markdown = await new functionModules.web.PublicWeb().getWebPage(url);
+ */
+
+module.exports = {
+ // Agent Functions
+ get agentFeedback() {
+ return require('../agent/autonomous/functions/agentFeedback.ts');
+ },
+ get fileSystemTree() {
+ return require('../agent/autonomous/functions/fileSystemTree.ts');
+ },
+ get liveFiles() {
+ return require('../agent/autonomous/functions/liveFiles.ts');
+ },
+
+ // Cloud Functions - Google
+ get bigQuery() {
+ return require('./cloud/google/bigquery.ts');
+ },
+ get composerAirflow() {
+ return require('./cloud/google/composerAirflow.ts');
+ },
+ get composerAirflowDagDebugAgent() {
+ return require('./cloud/google/composerAirflowDagDebugAgent.ts');
+ },
+ get composerDagDebugger() {
+ return require('./cloud/google/composerAirflowDebugger2.ts');
+ },
+ get googleCloud() {
+ return require('./cloud/google/google-cloud.ts');
+ },
+ get googleCloudSecurityCommandCenter() {
+ return require('./cloud/google/security-command-center.ts');
+ },
+
+ // Core Functions
+ get commandLine() {
+ return require('./commandLine.ts');
+ },
+ get confluence() {
+ return require('./confluence.ts');
+ },
+ get customFunctions() {
+ return require('./customFunctions.ts');
+ },
+ get deepThink() {
+ return require('./deepThink.ts');
+ },
+ get googleCalendar() {
+ return require('./googleCalendar.ts');
+ },
+ get image() {
+ return require('./image.ts');
+ },
+ get jira() {
+ return require('./jira.ts');
+ },
+ get llmTools() {
+ return require('./llmTools.ts');
+ },
+ get subProcess() {
+ return require('./subProcess.ts');
+ },
+ get supportKnowledgebase() {
+ return require('./supportKnowledgebase.ts');
+ },
+ get tempo() {
+ return require('./tempo.ts');
+ },
+ get testFunctions() {
+ return require('./testFunctions.ts');
+ },
+
+ // Email Functions
+ get gmail() {
+ return require('./email/gmail.ts');
+ },
+
+ // SCM Functions
+ get git() {
+ return require('./scm/git.ts');
+ },
+ get gitHub() {
+ return require('./scm/github.ts');
+ },
+ get gitLab() {
+ return require('./scm/gitlab.ts');
+ },
+ get gitLabCodeReview() {
+ return require('./scm/gitlabCodeReview.ts');
+ },
+
+ // Storage Functions
+ get fileSystemList() {
+ return require('./storage/fileSystemList.ts');
+ },
+ get fileSystemRead() {
+ return require('./storage/fileSystemRead.ts');
+ },
+ get fileSystemService() {
+ return require('./storage/fileSystemService.ts');
+ },
+ get fileSystemWrite() {
+ return require('./storage/fileSystemWrite.ts');
+ },
+ get localFileStore() {
+ return require('./storage/localFileStore.ts');
+ },
+
+ // Text Functions
+ get summarizer() {
+ return require('./text/summarizer.ts');
+ },
+
+ // Web Functions
+ get perplexity() {
+ return require('./web/perplexity.ts');
+ },
+ get web() {
+ return require('./web/web.ts');
+ },
+ get webResearch() {
+ return require('./web/webResearch.ts');
+ },
+
+ // SWE Functions
+ get codeEditingAgent() {
+ return require('../swe/codeEditingAgent.ts');
+ },
+ get codeFunctions() {
+ return require('../swe/codeFunctions.ts');
+ },
+ get morphCodeAgent() {
+ return require('../swe/morph/morphCoder.ts');
+ },
+ get morphEditor() {
+ return require('../swe/morph/morphEditor.ts');
+ },
+ get npmPackages() {
+ return require('../swe/lang/nodejs/npmPackages.ts');
+ },
+ get softwareDeveloperAgent() {
+ return require('../swe/softwareDeveloperAgent.ts');
+ },
+ get typescriptTools() {
+ return require('../swe/lang/nodejs/typescriptTools.ts');
+ },
+
+ // Slack/Chat Functions
+ get slack() {
+ return require('../modules/slack/slack.ts');
+ },
+ get slackAPI() {
+ return require('../modules/slack/slackApi.ts');
+ },
+};
diff --git a/src/functions/functionModules.d.ts b/src/functions/functionModules.d.ts
new file mode 100644
index 00000000..bef71633
--- /dev/null
+++ b/src/functions/functionModules.d.ts
@@ -0,0 +1,70 @@
+// /**
+// * Type definitions for functionModules.cjs
+// * Provides lazy-loading functions for all function classes.
+// */
+
+// declare module '*/functionModules.cjs' {
+// // Agent Functions
+// export function loadAgentFeedback(): Promise;
+// export function loadFileSystemTree(): Promise;
+// export function loadLiveFiles(): Promise;
+
+// // Cloud Functions - Google
+// export function loadBigQuery(): Promise;
+// export function loadComposerAirflow(): Promise;
+// export function loadComposerAirflowDagDebugAgent(): Promise;
+// export function loadComposerDagDebugger(): Promise;
+// export function loadGoogleCloud(): Promise;
+// export function loadGoogleCloudSecurityCommandCenter(): Promise;
+
+// // Core Functions
+// export function loadCommandLine(): Promise;
+// export function loadConfluence(): Promise;
+// export function loadCustomFunctions(): Promise;
+// export function loadDeepThink(): Promise;
+// export function loadGoogleCalendar(): Promise;
+// export function loadImage(): Promise;
+// export function loadJira(): Promise;
+// export function loadLlmTools(): Promise;
+// export function loadSubProcess(): Promise;
+// export function loadSupportKnowledgebase(): Promise;
+// export function loadTempo(): Promise;
+// export function loadTestFunctions(): Promise;
+
+// // Email Functions
+// export function loadGmail(): Promise;
+
+// // SCM Functions
+// export function loadGit(): Promise;
+// export function loadGitHub(): Promise;
+// export function loadGitLab(): Promise;
+// export function loadGitLabCodeReview(): Promise;
+
+// // Storage Functions
+// export function loadFileSystemList(): Promise;
+// export function loadFileSystemRead(): Promise;
+// export function loadFileSystemService(): Promise;
+// export function loadFileSystemWrite(): Promise;
+// export function loadLocalFileStore(): Promise;
+
+// // Text Functions
+// export function loadSummarizer(): Promise;
+
+// // Web Functions
+// export function loadPerplexity(): Promise;
+// export function loadWeb(): Promise;
+// export function loadWebResearch(): Promise;
+
+// // SWE Functions
+// export function loadCodeEditingAgent(): Promise;
+// export function loadCodeFunctions(): Promise;
+// export function loadMorphCodeAgent(): Promise;
+// export function loadMorphEditor(): Promise;
+// export function loadNpmPackages(): Promise;
+// export function loadSoftwareDeveloperAgent(): Promise;
+// export function loadTypescriptTools(): Promise;
+
+// // Slack/Chat Functions
+// export function loadSlack(): Promise;
+// export function loadSlackAPI(): Promise;
+// }
diff --git a/src/functions/llmTools.ts b/src/functions/llmTools.ts
index 9d6990ad..7b8484bf 100644
--- a/src/functions/llmTools.ts
+++ b/src/functions/llmTools.ts
@@ -3,10 +3,21 @@ import { join } from 'node:path';
import mime from 'mime-types';
import { getFileSystem, llms } from '#agent/agentContextLocalStorage';
import { func, funcClass } from '#functionSchema/functionDecorators';
+import { countTokens } from '#llm/tokens';
import { LlmMessage, messageText } from '#shared/llm/llm.model';
@funcClass(__filename)
export class LlmTools {
+ /**
+ * Counts the number of tokens in the provided text
+ * @param text the text to count tokens for
+ * @returns the number of tokens in the text
+ */
+ @func()
+ async countTextTokens(text: string): Promise {
+ return await countTokens(text);
+ }
+
/**
* Uses a large language model to transform the input content by applying the provided natural language instruction
* @param text the input text
@@ -20,10 +31,10 @@ export class LlmTools {
}
/**
- * Uses a large language model to analyse an image or document
- * @param filePath the path to the image or document
- * @param query a query to analyse the image or document
- * @returns the analysis of the image or document
+ * Uses a large language model to analyse an image/document
+ * @param filePath the path to the image/document
+ * @param query the query ask about the image/document
+ * @returns the analysis of the image/document
*/
@func()
async analyseFile(filePath: string, query: string): Promise {
diff --git a/src/functions/web/web.ts b/src/functions/web/web.ts
index 409bb1a8..f4fcf547 100644
--- a/src/functions/web/web.ts
+++ b/src/functions/web/web.ts
@@ -83,7 +83,7 @@ export class PublicWeb {
logger.info(`PublicWeb.getWebPage ${url}`);
// https://screenshotone.com/blog/how-to-hide-cookie-banners-when-taking-a-screenshot-with-puppeteer/
- const browser: Browser = await puppeteer.launch({ headless: false, args: ['--no-sandbox', '--disable-setuid-sandbox'] });
+ const browser: Browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox'] });
const page = await browser.newPage();
await page.setRequestInterception(true);
diff --git a/src/llm/llmFactory.ts b/src/llm/llmFactory.ts
index fab3234b..66f0b71c 100644
--- a/src/llm/llmFactory.ts
+++ b/src/llm/llmFactory.ts
@@ -24,36 +24,66 @@ import { logger } from '#o11y/logger';
import type { AgentLLMs } from '#shared/agent/agent.model';
import type { LLM } from '#shared/llm/llm.model';
-export const LLM_FACTORY: Record LLM> = {
- ...anthropicVertexLLMRegistry(),
- ...anthropicLLMRegistry(),
- ...fireworksLLMRegistry(),
- ...groqLLMRegistry(),
- ...openAiLLMRegistry(),
- ...togetherLLMRegistry(),
- ...vertexLLMRegistry(),
- ...geminiLLMRegistry(),
- ...deepseekLLMRegistry(),
- ...deepinfraLLMRegistry(),
- ...cerebrasLLMRegistry(),
- ...perplexityLLMRegistry(),
- // ...xaiLLMRegistry(),
- ...nebiusLLMRegistry(),
- ...sambanovaLLMRegistry(),
- ...ollamaLLMRegistry(),
- ...deepSeekFallbackRegistry(),
- ...MoA_reasoningLLMRegistry(),
- ...multiAgentLLMRegistry(),
- ...openrouterLLMRegistry(),
- ...mockLLMRegistry(),
-};
+/**
+ * Builds a Record LLM> from arrays of LLM factory functions.
+ * The key for each factory is obtained by calling factory().getId() on a temporary instance.
+ *
+ * @param registries - Arrays of LLM factory functions from each service/multi-agent registry
+ * @returns Record mapping LLM IDs to their factory functions
+ */
+function buildLlmFactory(...registries: Array<() => LLM>[]): Record LLM> {
+ const factory: Record LLM> = {};
+
+ for (const registry of registries) {
+ for (const llmFactory of registry) {
+ // Create a temporary instance to get the ID
+ const tempInstance = llmFactory();
+ const id = tempInstance.getId();
+ factory[id] = llmFactory;
+ }
+ }
+
+ return factory;
+}
+
+// Lazy initialization to avoid calling factory functions during module initialization
+let _LLM_FACTORY: Record LLM> | null = null;
+
+function ensureLLMFactory(): Record LLM> {
+ if (!_LLM_FACTORY) {
+ _LLM_FACTORY = buildLlmFactory(
+ anthropicVertexLLMRegistry(),
+ anthropicLLMRegistry(),
+ fireworksLLMRegistry(),
+ groqLLMRegistry(),
+ openAiLLMRegistry(),
+ togetherLLMRegistry(),
+ vertexLLMRegistry(),
+ geminiLLMRegistry(),
+ deepseekLLMRegistry(),
+ deepinfraLLMRegistry(),
+ cerebrasLLMRegistry(),
+ perplexityLLMRegistry(),
+ // xaiLLMRegistry(),
+ nebiusLLMRegistry(),
+ sambanovaLLMRegistry(),
+ ollamaLLMRegistry(),
+ deepSeekFallbackRegistry(),
+ MoA_reasoningLLMRegistry(),
+ multiAgentLLMRegistry(),
+ openrouterLLMRegistry(),
+ mockLLMRegistry(),
+ );
+ }
+ return _LLM_FACTORY;
+}
const modelMigrations: Record = {};
let _llmTypes: Array<{ id: string; name: string }> | null = null;
export function llmTypes(): Array<{ id: string; name: string }> {
- _llmTypes ??= Object.values(LLM_FACTORY)
+ _llmTypes ??= Object.values(ensureLLMFactory())
.map((factory) => factory())
.map((llm) => {
for (const model of llm.getOldModels()) {
@@ -67,7 +97,7 @@ export function llmTypes(): Array<{ id: string; name: string }> {
let _llmRegistryKeys: string[];
function llmRegistryKeys(): string[] {
- _llmRegistryKeys ??= Object.keys(LLM_FACTORY);
+ _llmRegistryKeys ??= Object.keys(ensureLLMFactory());
return _llmRegistryKeys;
}
@@ -75,14 +105,15 @@ function llmRegistryKeys(): string[] {
* @param llmId LLM identifier in the format service:model
*/
export function getLLM(llmId: string): LLM {
+ const factory = ensureLLMFactory();
// Check matching id first
- if (LLM_FACTORY[llmId]) {
- return LLM_FACTORY[llmId]();
+ if (factory[llmId]) {
+ return factory[llmId]();
}
// Check substring matching
- for (const key of Object.keys(LLM_FACTORY)) {
+ for (const key of Object.keys(factory)) {
if (llmId.startsWith(key)) {
- return LLM_FACTORY[key]!();
+ return factory[key]!();
}
}
if (llmId === 'multi:multi') {
diff --git a/src/llm/multi-agent/blueberry.ts b/src/llm/multi-agent/blueberry.ts
index a21aa6df..c8eb7873 100644
--- a/src/llm/multi-agent/blueberry.ts
+++ b/src/llm/multi-agent/blueberry.ts
@@ -9,10 +9,8 @@ import type { GenerateTextOptions, LLM } from '#shared/llm/llm.model';
// self-refine https://arxiv.org/pdf/2303.17651
// https://www.academia.edu/123745078/Mind_over_Data_Elevating_LLMs_from_Memorization_to_Cognition
-export function blueberryLLMRegistry(): Record LLM> {
- return {
- 'MoA:blueberry': () => new Blueberry(),
- };
+export function blueberryLLMRegistry(): Array<() => LLM> {
+ return [() => new Blueberry()];
}
const MIND_OVER_DATA_SYS_PROMPT = `When addressing a problem, employ "Comparative Problem Analysis and Direct Reasoning" as follows:
diff --git a/src/llm/multi-agent/deepSeek_Fallbacks.ts b/src/llm/multi-agent/deepSeek_Fallbacks.ts
index ee82f646..9e38800b 100644
--- a/src/llm/multi-agent/deepSeek_Fallbacks.ts
+++ b/src/llm/multi-agent/deepSeek_Fallbacks.ts
@@ -6,16 +6,6 @@ import type { GenerateTextOptions, LLM, LlmMessage } from '#shared/llm/llm.model
import { BaseLLM } from '../base-llm';
import { fireworksDeepSeekR1_Fast } from '../services/fireworks';
-export function deepSeekFallbackRegistry(): Record LLM> {
- return {
- DeepSeekFallback: DeepSeek_Together_Fireworks_Nebius_SambaNova,
- };
-}
-
-export function DeepSeek_Together_Fireworks_Nebius_SambaNova(): LLM {
- return new DeepSeek_Fallbacks();
-}
-
/**
* LLM implementation for DeepSeek which uses Together.ai and Fireworks.ai for more privacy.
* Tries Together.ai first as is slightly cheaper, then falls back to Fireworks
@@ -58,3 +48,11 @@ export class DeepSeek_Fallbacks extends BaseLLM {
throw new Error('All DeepSeek providers failed.');
}
}
+
+export function deepSeekFallbackRegistry(): Array<() => LLM> {
+ return [DeepSeek_Together_Fireworks_Nebius_SambaNova];
+}
+
+export function DeepSeek_Together_Fireworks_Nebius_SambaNova(): LLM {
+ return new DeepSeek_Fallbacks();
+}
diff --git a/src/llm/multi-agent/multiRegistry.ts b/src/llm/multi-agent/multiRegistry.ts
index ec717d08..faafbd06 100644
--- a/src/llm/multi-agent/multiRegistry.ts
+++ b/src/llm/multi-agent/multiRegistry.ts
@@ -2,9 +2,6 @@ import { FastEasyLLM } from '#llm/multi-agent/fastEasy';
import { FastMediumLLM } from '#llm/multi-agent/fastMedium';
import type { LLM } from '#shared/llm/llm.model';
-export function multiAgentLLMRegistry(): Record LLM> {
- const registry = {};
- registry['multi:fast-medium'] = () => new FastMediumLLM();
- registry['multi:fast-easy'] = () => new FastEasyLLM();
- return registry;
+export function multiAgentLLMRegistry(): Array<() => LLM> {
+ return [() => new FastMediumLLM(), () => new FastEasyLLM()];
}
diff --git a/src/llm/multi-agent/reasoning-debate.ts b/src/llm/multi-agent/reasoning-debate.ts
index 78b6f1cf..3c0beff3 100644
--- a/src/llm/multi-agent/reasoning-debate.ts
+++ b/src/llm/multi-agent/reasoning-debate.ts
@@ -48,96 +48,6 @@ function accumulateStats(accumulator: GenerationStats, newStats?: GenerationStat
// sparse multi-agent debate https://arxiv.org/abs/2406.11776
-export function MoA_reasoningLLMRegistry(): Record LLM> {
- return {
- 'MAD:Cost': MAD_Cost,
- 'MAD:Fast': MAD_Fast,
- 'MAD:SOTA': MAD_SOTA,
- 'MAD:Vertex': MAD_Vertex,
- 'MAD:Balanced': MAD_Balanced,
- 'MAD:Balanced4': MAD_Balanced4,
- };
-}
-
-export function MAD_Cost(): LLM {
- return new ReasonerDebateLLM(
- 'Cost',
- deepinfraDeepSeekR1,
- [deepinfraDeepSeekR1, deepinfraDeepSeekR1, deepinfraDeepSeekR1],
- 'MAD:Cost multi-agent debate (DeepSeek R1x3)',
- );
-}
-
-export function MAD_Fast(): LLM {
- const fastMedium = new FastMediumLLM();
- const fastMediumFactory = () => fastMedium;
- return new ReasonerDebateLLM(
- 'Fast',
- fastMediumFactory,
- [fastMediumFactory, fastMediumFactory, fastMediumFactory],
- 'MAD:Fast multi-agent debate (Cerebras Qwen3 32b, Flash 2.5 fallback)',
- );
-}
-
-export function MAD_Balanced(): LLM {
- return new ReasonerDebateLLM(
- 'Balanced',
- vertexGemini_2_5_Pro,
- [vertexGemini_2_5_Pro, xai_Grok4, openaiGPT5],
- 'MAD:Balanced multi-agent debate (Gemini 2.5 Pro, Grok 4, o3)',
- );
-}
-
-export function MAD_Balanced4(): LLM {
- return new ReasonerDebateLLM(
- 'Balanced4',
- vertexGemini_2_5_Pro,
- [vertexGemini_2_5_Pro, xai_Grok4, openaiGPT5, Claude4_5_Sonnet_Vertex],
- 'MAD:Balanced multi-agent debate (Gemini 2.5 Pro, Grok 4, o3, Sonnet 4)',
- );
-}
-
-export function MAD_Vertex(): LLM {
- return new ReasonerDebateLLM(
- 'Vertex',
- vertexGemini_2_5_Pro,
- [vertexGemini_2_5_Pro, vertexGemini_2_5_Pro, Claude4_5_Sonnet_Vertex],
- 'MAD:Vertex multi-agent debate (Gemini 2.5 Pro x2, Sonnet 4)',
- );
-}
-
-export function MAD_Anthropic(): LLM {
- return new ReasonerDebateLLM(
- 'Anthropic',
- anthropicClaude4_5_Sonnet,
- [anthropicClaude4_5_Sonnet, anthropicClaude4_5_Sonnet, anthropicClaude4_5_Sonnet],
- 'MAD:Anthropic multi-agent debate (Sonnet 4 x3)',
- );
-}
-
-export function MAD_OpenAI(): LLM {
- return new ReasonerDebateLLM('OpenAI', openaiGPT5, [openaiGPT5, openaiGPT5, openaiGPT5], 'MAD:OpenAI multi-agent debate (GPT5 x3)');
-}
-
-export function MAD_Grok(): LLM {
- return new ReasonerDebateLLM('Grok', xai_Grok4, [xai_Grok4, xai_Grok4, xai_Grok4], 'MAD:Grok multi-agent debate (Grok 4 x3)');
-}
-
-export function MAD_SOTA(): LLM {
- return new ReasonerDebateLLM(
- 'SOTA',
- openaiGPT5,
- [openaiGPT5, Claude4_1_Opus_Vertex, vertexGemini_2_5_Pro, xai_Grok4],
- 'MAD:SOTA multi-agent debate (Opus 4, GPT5, Gemini 2.5 Pro, Grok 4)',
- );
-}
-
-// export function MAD_Hard(): LLM {
-// const hardLLM = llms().hard; // cant have a dependancy to agentContextLocalStorage
-// const hardFactory = () => hardLLM;
-// return new ReasonerDebateLLM('Hard', hardFactory, [hardFactory, hardFactory, hardFactory], `MAD:Hard multi-agent debate (${hardLLM.getDisplayName} x3)`);
-// }
-
const INITIAL_TEMP = 0.7;
const DEBATE_TEMP = 0.5;
const FINAL_TEMP = 0.3;
@@ -297,3 +207,86 @@ Answer directly to the original user message and ensure any relevant response fo
return this.mediator.generateMessage(mergedMessages, { ...opts, id: `${opts.id} - final`, temperature: FINAL_TEMP });
}
}
+
+export function MoA_reasoningLLMRegistry(): Array<() => LLM> {
+ return [MAD_Cost, MAD_Fast, MAD_SOTA, MAD_Vertex, MAD_Balanced, MAD_Balanced4];
+}
+
+export function MAD_Cost(): LLM {
+ return new ReasonerDebateLLM(
+ 'Cost',
+ deepinfraDeepSeekR1,
+ [deepinfraDeepSeekR1, deepinfraDeepSeekR1, deepinfraDeepSeekR1],
+ 'MAD:Cost multi-agent debate (DeepSeek R1x3)',
+ );
+}
+
+export function MAD_Fast(): LLM {
+ const fastMedium = new FastMediumLLM();
+ const fastMediumFactory = () => fastMedium;
+ return new ReasonerDebateLLM(
+ 'Fast',
+ fastMediumFactory,
+ [fastMediumFactory, fastMediumFactory, fastMediumFactory],
+ 'MAD:Fast multi-agent debate (Cerebras Qwen3 32b, Flash 2.5 fallback)',
+ );
+}
+
+export function MAD_Balanced(): LLM {
+ return new ReasonerDebateLLM(
+ 'Balanced',
+ vertexGemini_2_5_Pro,
+ [vertexGemini_2_5_Pro, xai_Grok4, openaiGPT5],
+ 'MAD:Balanced multi-agent debate (Gemini 2.5 Pro, Grok 4, o3)',
+ );
+}
+
+export function MAD_Balanced4(): LLM {
+ return new ReasonerDebateLLM(
+ 'Balanced4',
+ vertexGemini_2_5_Pro,
+ [vertexGemini_2_5_Pro, xai_Grok4, openaiGPT5, Claude4_5_Sonnet_Vertex],
+ 'MAD:Balanced multi-agent debate (Gemini 2.5 Pro, Grok 4, o3, Sonnet 4)',
+ );
+}
+
+export function MAD_Vertex(): LLM {
+ return new ReasonerDebateLLM(
+ 'Vertex',
+ vertexGemini_2_5_Pro,
+ [vertexGemini_2_5_Pro, vertexGemini_2_5_Pro, Claude4_5_Sonnet_Vertex],
+ 'MAD:Vertex multi-agent debate (Gemini 2.5 Pro x2, Sonnet 4)',
+ );
+}
+
+export function MAD_Anthropic(): LLM {
+ return new ReasonerDebateLLM(
+ 'Anthropic',
+ anthropicClaude4_5_Sonnet,
+ [anthropicClaude4_5_Sonnet, anthropicClaude4_5_Sonnet, anthropicClaude4_5_Sonnet],
+ 'MAD:Anthropic multi-agent debate (Sonnet 4 x3)',
+ );
+}
+
+export function MAD_OpenAI(): LLM {
+ return new ReasonerDebateLLM('OpenAI', openaiGPT5, [openaiGPT5, openaiGPT5, openaiGPT5], 'MAD:OpenAI multi-agent debate (GPT5 x3)');
+}
+
+export function MAD_Grok(): LLM {
+ return new ReasonerDebateLLM('Grok', xai_Grok4, [xai_Grok4, xai_Grok4, xai_Grok4], 'MAD:Grok multi-agent debate (Grok 4 x3)');
+}
+
+export function MAD_SOTA(): LLM {
+ return new ReasonerDebateLLM(
+ 'SOTA',
+ openaiGPT5,
+ [openaiGPT5, Claude4_1_Opus_Vertex, vertexGemini_2_5_Pro, xai_Grok4],
+ 'MAD:SOTA multi-agent debate (Opus 4, GPT5, Gemini 2.5 Pro, Grok 4)',
+ );
+}
+
+// export function MAD_Hard(): LLM {
+// const hardLLM = llms().hard; // cant have a dependancy to agentContextLocalStorage
+// const hardFactory = () => hardLLM;
+// return new ReasonerDebateLLM('Hard', hardFactory, [hardFactory, hardFactory, hardFactory], `MAD:Hard multi-agent debate (${hardLLM.getDisplayName} x3)`);
+// }
diff --git a/src/llm/services/anthropic-vertex.ts b/src/llm/services/anthropic-vertex.ts
index 6e4c529d..d66cbd55 100644
--- a/src/llm/services/anthropic-vertex.ts
+++ b/src/llm/services/anthropic-vertex.ts
@@ -10,12 +10,8 @@ export const ANTHROPIC_VERTEX_SERVICE = 'anthropic-vertex';
// https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/use-claude#anthropic_claude_region_availability
-export function anthropicVertexLLMRegistry(): Record LLM> {
- return {
- [`${ANTHROPIC_VERTEX_SERVICE}:claude-haiku-4-5@20251001`]: Claude4_5_Haiku_Vertex,
- [`${ANTHROPIC_VERTEX_SERVICE}:claude-sonnet-4-5@20250929`]: Claude4_5_Sonnet_Vertex,
- [`${ANTHROPIC_VERTEX_SERVICE}:claude-opus-4-1@20250805`]: Claude4_1_Opus_Vertex,
- };
+export function anthropicVertexLLMRegistry(): Array<() => LLM> {
+ return [Claude4_5_Haiku_Vertex, Claude4_5_Sonnet_Vertex, Claude4_1_Opus_Vertex];
}
// Supported image types image/jpeg', 'image/png', 'image/gif' or 'image/webp'
diff --git a/src/llm/services/anthropic.ts b/src/llm/services/anthropic.ts
index 84305717..ce847f53 100644
--- a/src/llm/services/anthropic.ts
+++ b/src/llm/services/anthropic.ts
@@ -8,12 +8,8 @@ import { MultiLLM } from '../multi-llm';
export const ANTHROPIC_SERVICE = 'anthropic';
-export function anthropicLLMRegistry(): Record LLM> {
- return {
- [`${ANTHROPIC_SERVICE}:claude-haiku-4-5-20251001`]: anthropicClaude4_5_Haiku,
- [`${ANTHROPIC_SERVICE}:claude-sonnet-4-5-20250929`]: anthropicClaude4_5_Sonnet,
- [`${ANTHROPIC_SERVICE}:claude-opus-4-1-20250805`]: anthropicClaude4_1_Opus,
- };
+export function anthropicLLMRegistry(): Array<() => LLM> {
+ return [anthropicClaude4_5_Haiku, anthropicClaude4_5_Sonnet, anthropicClaude4_1_Opus];
}
export function anthropicClaude4_1_Opus(): LLM {
diff --git a/src/llm/services/cerebras-openrouter.ts b/src/llm/services/cerebras-openrouter.ts
index d03b6cc5..48ef01a4 100644
--- a/src/llm/services/cerebras-openrouter.ts
+++ b/src/llm/services/cerebras-openrouter.ts
@@ -7,11 +7,8 @@ import { AiLLM } from './ai-llm';
export const CEREBRAS_OPENROUTER_SERVICE = 'cerebras-openrouter';
-export function openrouterLLMRegistry(): Record LLM> {
- return {
- 'cerebras-openrouter:qwen/qwen3-235b-a22b-thinking-2507': () => openRouterQwen3_235b_Thinking(),
- 'cerebras-openrouter:qwen/qwen/qwen3-235b-a22b-2507': () => openRouterQwen3_235b_Instruct(),
- };
+export function openrouterLLMRegistry(): Array<() => LLM> {
+ return [openRouterQwen3_235b_Thinking, openRouterQwen3_235b_Instruct];
}
// https://openrouter.ai/models
diff --git a/src/llm/services/cerebras.ts b/src/llm/services/cerebras.ts
index aaac1ba7..fc887a3b 100644
--- a/src/llm/services/cerebras.ts
+++ b/src/llm/services/cerebras.ts
@@ -10,14 +10,8 @@ import { AiLLM } from './ai-llm';
export const CEREBRAS_SERVICE = 'cerebras';
-export function cerebrasLLMRegistry(): Record LLM> {
- return {
- 'cerebras:qwen-3-32b': () => cerebrasQwen3_32b(),
- 'cerebras:qwen-3-235b-a22b-instruct-2507': () => cerebrasQwen3_235b_Instruct(),
- 'cerebras:qwen-3-235b-a22b-thinking-2507': () => cerebrasQwen3_235b_Thinking(),
- 'cerebras:qwen-3-coder-480b': () => cerebrasQwen3_Coder(),
- 'cerebras:gpt-oss-120b': () => cerebrasGptOss_120b(),
- };
+export function cerebrasLLMRegistry(): Array<() => LLM> {
+ return [cerebrasQwen3_32b, cerebrasQwen3_235b_Instruct, cerebrasQwen3_235b_Thinking, cerebrasQwen3_Coder, cerebrasGptOss_120b];
}
// https://cloud.cerebras.ai/platform/org_/models PAYG rate limits
diff --git a/src/llm/services/deepinfra.ts b/src/llm/services/deepinfra.ts
index 661f2895..223c386f 100644
--- a/src/llm/services/deepinfra.ts
+++ b/src/llm/services/deepinfra.ts
@@ -28,12 +28,8 @@ export class Deepinfra extends AiLLM {
}
}
// https://deepinfra.com/models/text-generation
-export function deepinfraLLMRegistry(): Record LLM> {
- return {
- [`${DEEPINFRA_SERVICE}:Qwen/Qwen3-235B-A22B`]: deepinfraQwen3_235B_A22B,
- [`${DEEPINFRA_SERVICE}:deepseek-ai/DeepSeek-R1-0528`]: deepinfraDeepSeekR1,
- [`${DEEPINFRA_SERVICE}:moonshotai/Kimi-K2-Instruct`]: deepinfraKimiK2,
- };
+export function deepinfraLLMRegistry(): Array<() => LLM> {
+ return [deepinfraQwen3_235B_A22B, deepinfraDeepSeekR1, deepinfraKimiK2];
}
// https://deepinfra.com/Qwen/Qwen3-235B-A22B
diff --git a/src/llm/services/deepseek.ts b/src/llm/services/deepseek.ts
index c3b0411b..54589d90 100644
--- a/src/llm/services/deepseek.ts
+++ b/src/llm/services/deepseek.ts
@@ -6,11 +6,8 @@ import { AiLLM } from './ai-llm';
export const DEEPSEEK_SERVICE = 'deepseek';
-export function deepseekLLMRegistry(): Record LLM> {
- return {
- [`${DEEPSEEK_SERVICE}:deepseek-chat`]: deepSeekV3_1,
- [`${DEEPSEEK_SERVICE}:deepseek-reasoner`]: deepSeekV3_1_Reasoning,
- };
+export function deepseekLLMRegistry(): Array<() => LLM> {
+ return [deepSeekV3_1, deepSeekV3_1_Reasoning];
}
// https://api-docs.deepseek.com/quick_start/pricing
diff --git a/src/llm/services/defaultLlms.ts b/src/llm/services/defaultLlms.ts
index 49cd3f6f..d472307c 100644
--- a/src/llm/services/defaultLlms.ts
+++ b/src/llm/services/defaultLlms.ts
@@ -9,6 +9,7 @@ import type { AgentLLMs } from '#shared/agent/agent.model';
import type { LLM } from '#shared/llm/llm.model';
import { Claude4_5_Sonnet_Vertex } from './anthropic-vertex';
import { cerebrasQwen3_235b_Thinking } from './cerebras';
+import { fireworksGLM_4_6 } from './fireworks';
import { Gemini_2_5_Flash, Gemini_2_5_Pro } from './gemini';
import { groqLlama4_Scout } from './groq';
import { Ollama_LLMs } from './ollama';
@@ -47,7 +48,15 @@ export function defaultLLMs(): AgentLLMs {
const medium: LLM | undefined = mediumLLMs.find((llm) => llm.isConfigured());
if (!medium) throw new Error('No default medium LLM configured');
- const hardLLMs = [Claude4_5_Sonnet_Vertex(), openaiGPT5(), anthropicClaude4_5_Sonnet(), vertexGemini_2_5_Pro(), Gemini_2_5_Pro(), xai_Grok4()];
+ const hardLLMs = [
+ fireworksGLM_4_6(),
+ Claude4_5_Sonnet_Vertex(),
+ openaiGPT5(),
+ anthropicClaude4_5_Sonnet(),
+ vertexGemini_2_5_Pro(),
+ Gemini_2_5_Pro(),
+ xai_Grok4(),
+ ];
const hard: LLM | undefined = hardLLMs.find((llm) => llm.isConfigured());
if (!hard) throw new Error('No default hard LLM configured');
diff --git a/src/llm/services/fireworks.ts b/src/llm/services/fireworks.ts
index 013565b3..b57c6703 100644
--- a/src/llm/services/fireworks.ts
+++ b/src/llm/services/fireworks.ts
@@ -28,10 +28,8 @@ export class Fireworks extends AiLLM {
}
}
-export function fireworksLLMRegistry(): Record LLM> {
- return {
- [`${FIREWORKS_SERVICE}:accounts/fireworks/models/glm-4p6`]: fireworksGLM_4_6,
- };
+export function fireworksLLMRegistry(): Array<() => LLM> {
+ return [fireworksGLM_4_6];
}
export function fireworksGLM_4_6(): LLM {
diff --git a/src/llm/services/gemini.ts b/src/llm/services/gemini.ts
index 9d9f95d3..527565ee 100644
--- a/src/llm/services/gemini.ts
+++ b/src/llm/services/gemini.ts
@@ -7,12 +7,8 @@ import { currentUser } from '#user/userContext';
export const GEMINI_SERVICE = 'gemini';
-export function geminiLLMRegistry(): Record LLM> {
- return {
- [`${GEMINI_SERVICE}:gemini-2.5-flash-lite`]: Gemini_2_5_Flash_Lite,
- [`${GEMINI_SERVICE}:gemini-2.5-flash`]: Gemini_2_5_Flash,
- [`${GEMINI_SERVICE}:gemini-2.5-pro`]: Gemini_2_5_Pro,
- };
+export function geminiLLMRegistry(): Array<() => LLM> {
+ return [Gemini_2_5_Flash_Lite, Gemini_2_5_Flash, Gemini_2_5_Pro];
}
export function Gemini_2_5_Pro(): LLM {
diff --git a/src/llm/services/groq.ts b/src/llm/services/groq.ts
index b7bf121b..a099fd54 100644
--- a/src/llm/services/groq.ts
+++ b/src/llm/services/groq.ts
@@ -8,12 +8,8 @@ import { currentUser } from '#user/userContext';
export const GROQ_SERVICE = 'groq';
-export function groqLLMRegistry(): Record LLM> {
- return {
- 'groq:qwen/qwen3-32b': groqQwen3_32b,
- 'groq:moonshotai/kimi-k2-instruct': groqKimiK2,
- 'groq:meta-llama/llama-4-scout-17b-16e-instruct': groqLlama4_Scout,
- };
+export function groqLLMRegistry(): Array<() => LLM> {
+ return [groqQwen3_32b, groqKimiK2, groqLlama4_Scout];
}
// Pricing and model ids at
diff --git a/src/llm/services/key-rotation.ts b/src/llm/services/key-rotation.ts
index 3552c971..5c498256 100644
--- a/src/llm/services/key-rotation.ts
+++ b/src/llm/services/key-rotation.ts
@@ -13,7 +13,6 @@ export function createEnvKeyRotator(envBase: string, maxSuffix = 9): KeyRotator
else break;
}
let index = 0;
-
return {
current() {
return keys.length ? keys[index] : undefined;
diff --git a/src/llm/services/mock-llm.ts b/src/llm/services/mock-llm.ts
index 1dea74b6..56bed922 100644
--- a/src/llm/services/mock-llm.ts
+++ b/src/llm/services/mock-llm.ts
@@ -352,11 +352,11 @@ export class MockLLM extends BaseLLM {
export const mockLLM = new MockLLM();
-export function mockLLMRegistry(): Record LLM> {
- return {
+export function mockLLMRegistry(): Array<() => LLM> {
+ return [
// Tests need the same instance returned
- 'mock:mock': () => mockLLM,
- };
+ () => mockLLM,
+ ];
}
export function mockLLMs(): AgentLLMs {
diff --git a/src/llm/services/nebius.ts b/src/llm/services/nebius.ts
index 11fe48b0..fef218cd 100644
--- a/src/llm/services/nebius.ts
+++ b/src/llm/services/nebius.ts
@@ -6,10 +6,8 @@ import { currentUser } from '#user/userContext';
export const NEBIUS_SERVICE = 'nebius';
-export function nebiusLLMRegistry(): Record LLM> {
- return {
- 'nebius:deepseek-ai/DeepSeek-R1': nebiusDeepSeekR1,
- };
+export function nebiusLLMRegistry(): Array<() => LLM> {
+ return [nebiusDeepSeekR1];
}
export function nebiusDeepSeekR1(): LLM {
diff --git a/src/llm/services/ollama.ts b/src/llm/services/ollama.ts
index d6f20b0b..0ab594cc 100644
--- a/src/llm/services/ollama.ts
+++ b/src/llm/services/ollama.ts
@@ -131,11 +131,6 @@ export function Ollama_LLMs(): AgentLLMs {
};
}
-export function ollamaLLMRegistry(): Record LLM> {
- return {
- [`${OLLAMA_SERVICE}:qwen2:7b`]: Ollama_Qwen2_7b,
- [`${OLLAMA_SERVICE}:llama3:7b`]: Ollama_Llama3_7b,
- [`${OLLAMA_SERVICE}:codegemma:7b`]: Ollama_CodeGemma_7b,
- [`${OLLAMA_SERVICE}:phi3:latest`]: Ollama_Phi3,
- };
+export function ollamaLLMRegistry(): Array<() => LLM> {
+ return [Ollama_Qwen2_7b, Ollama_Llama3_7b, Ollama_CodeGemma_7b, Ollama_Phi3];
}
diff --git a/src/llm/services/openai.ts b/src/llm/services/openai.ts
index 4be461ce..ecfc975a 100644
--- a/src/llm/services/openai.ts
+++ b/src/llm/services/openai.ts
@@ -6,15 +6,8 @@ import { currentUser } from '#user/userContext';
export const OPENAI_SERVICE = 'openai';
-export function openAiLLMRegistry(): Record LLM> {
- return {
- 'openai:gpt-5': () => openaiGPT5(),
- 'openai:gpt-5-mini': () => openaiGPT5mini(),
- 'openai:gpt-5-nano': () => openaiGPT5nano(),
- 'openai:gpt-5-codex': () => openaiGPT5codex(),
- 'openai:gpt-5-pro': () => openaiGPT5pro(),
- // 'openai:gpt-5-chat': () => openaiGPT5chat(),
- };
+export function openAiLLMRegistry(): Array<() => LLM> {
+ return [openaiGPT5, openaiGPT5mini, openaiGPT5nano, openaiGPT5codex, openaiGPT5pro];
}
// https://sdk.vercel.ai/providers/ai-sdk-providers/openai#prompt-caching
diff --git a/src/llm/services/perplexity-llm.ts b/src/llm/services/perplexity-llm.ts
index e8a259e2..62d7a8cf 100644
--- a/src/llm/services/perplexity-llm.ts
+++ b/src/llm/services/perplexity-llm.ts
@@ -39,12 +39,8 @@ function perplexityCostFunction(inputMil: number, outputMil: number): LlmCostFun
};
}
-export function perplexityLLMRegistry(): Record LLM> {
- return {
- [`${PERPLEXITY_SERVICE}:sonar`]: perplexityLLM,
- [`${PERPLEXITY_SERVICE}:sonar-reasoning-pro`]: perplexityReasoningProLLM,
- [`${PERPLEXITY_SERVICE}:sonar-deep-research`]: perplexityDeepResearchLLM,
- };
+export function perplexityLLMRegistry(): Array<() => LLM> {
+ return [perplexityLLM, perplexityReasoningProLLM, perplexityDeepResearchLLM];
}
export function perplexityLLM(): LLM {
diff --git a/src/llm/services/sambanova.ts b/src/llm/services/sambanova.ts
index 52b8265e..fac7a1be 100644
--- a/src/llm/services/sambanova.ts
+++ b/src/llm/services/sambanova.ts
@@ -6,12 +6,8 @@ import { AiLLM } from './ai-llm';
export const SAMBANOVA_SERVICE = 'sambanova';
-export function sambanovaLLMRegistry(): Record LLM> {
- return {
- 'sambanova:Qwen3-32B': sambanovaQwen3_32b,
- 'sambanova:DeepSeek-R1': sambanovaDeepseekR1,
- 'sambanova:DeepSeek-V3-0324': sambanovaDeepseekV3,
- };
+export function sambanovaLLMRegistry(): Array<() => LLM> {
+ return [sambanovaQwen3_32b, sambanovaDeepseekR1, sambanovaDeepseekV3];
}
// https://docs.sambanova.ai/cloud/docs/get-started/supported-models
diff --git a/src/llm/services/together.ts b/src/llm/services/together.ts
index 167bdf87..c2ab646e 100644
--- a/src/llm/services/together.ts
+++ b/src/llm/services/together.ts
@@ -6,12 +6,8 @@ import { currentUser } from '#user/userContext';
export const TOGETHER_SERVICE = 'together';
-export function togetherLLMRegistry(): Record LLM> {
- return {
- [`${TOGETHER_SERVICE}:deepseek-ai/DeepSeek-R1`]: () => togetherDeepSeekR1(),
- [`${TOGETHER_SERVICE}:deepseek-ai/DeepSeek-R1-0528-tput`]: () => togetherDeepSeekR1_0528_tput(),
- [`${TOGETHER_SERVICE}:moonshotai/kimi-k2-instruct`]: () => togetherKimiK2(),
- };
+export function togetherLLMRegistry(): Array<() => LLM> {
+ return [togetherDeepSeekR1, togetherDeepSeekR1_0528_tput, togetherKimiK2];
}
// https://www.together.ai/models/deepseek-r1
diff --git a/src/llm/services/vertexai.ts b/src/llm/services/vertexai.ts
index 73174d6f..c06cd5c7 100644
--- a/src/llm/services/vertexai.ts
+++ b/src/llm/services/vertexai.ts
@@ -9,12 +9,8 @@ import { envVar } from '#utils/env-var';
export const VERTEX_SERVICE = 'vertex';
-export function vertexLLMRegistry(): Record LLM> {
- return {
- [`${VERTEX_SERVICE}:gemini-2.5-flash-lite`]: vertexGemini_2_5_Flash_Lite,
- [`${VERTEX_SERVICE}:gemini-2.5-pro`]: vertexGemini_2_5_Pro,
- [`${VERTEX_SERVICE}:gemini-2.5-flash`]: vertexGemini_2_5_Flash,
- };
+export function vertexLLMRegistry(): Array<() => LLM> {
+ return [vertexGemini_2_5_Flash_Lite, vertexGemini_2_5_Pro, vertexGemini_2_5_Flash];
}
// Prompts less than 200,000 tokens: $1.25/million tokens for input, $10/million for output
diff --git a/src/llm/services/xai.ts b/src/llm/services/xai.ts
index 8f599bbc..49c7fda8 100644
--- a/src/llm/services/xai.ts
+++ b/src/llm/services/xai.ts
@@ -25,12 +25,8 @@ export class XAI extends AiLLM {
}
}
-export function xaiLLMRegistry(): Record LLM> {
- return {
- [`${XAI_SERVICE}:grok-4`]: xai_Grok4,
- [`${XAI_SERVICE}:grok-4-fast-reasoning`]: xai_Grok4_Fast_Reasoning,
- [`${XAI_SERVICE}:grok-4-fast-instruct`]: xai_Grok4_Fast_Instruct,
- };
+export function xaiLLMRegistry(): Array<() => LLM> {
+ return [xai_Grok4, xai_Grok4_Fast_Reasoning, xai_Grok4_Fast_Instruct];
}
export function xai_Grok4(): LLM {
diff --git a/src/log-loader.js b/src/log-loader.js
new file mode 100644
index 00000000..3a7c3553
--- /dev/null
+++ b/src/log-loader.js
@@ -0,0 +1,32 @@
+// log-loader.js
+const Module = require('node:module');
+const path = require('node:path');
+const originalRequire = Module.prototype.require;
+
+const loadOrder = [];
+
+Module.prototype.require = function (id) {
+ // biome-ignore lint/style/noArguments: ok
+ const result = originalRequire.apply(this, arguments);
+
+ // Get the resolved path
+ try {
+ const resolvedPath = Module._resolveFilename(id, this);
+
+ // Filter out node_modules
+ if (!resolvedPath.includes('node_modules')) {
+ const relativePath = path.relative(process.cwd(), resolvedPath);
+ if (!loadOrder.includes(relativePath)) {
+ loadOrder.push(relativePath);
+ console.log(`[${loadOrder.length}] Loaded: ${relativePath}`);
+ }
+ }
+ } catch (e) {
+ // Built-in modules or modules that can't be resolved
+ }
+
+ return result;
+};
+
+// Export to use elsewhere if needed
+module.exports = { loadOrder };
diff --git a/src/modules/slack/slackChatHistory.ts b/src/modules/slack/slackChatHistory.ts
new file mode 100644
index 00000000..a363e5bf
--- /dev/null
+++ b/src/modules/slack/slackChatHistory.ts
@@ -0,0 +1,121 @@
+import { WebClient } from '@slack/web-api';
+import { slackConfig } from './slackConfig';
+
+type SlackConversation = {
+ id: string;
+ name?: string;
+ is_channel?: boolean;
+ is_group?: boolean;
+ is_im?: boolean;
+ is_mpim?: boolean;
+};
+
+// const config = slackConfig();
+
+const token = process.env.SLACK_USER_TOKEN;
+if (!token) {
+ throw new Error('SLACK_USER_TOKEN is not set');
+}
+
+const client = new WebClient(token);
+
+/**
+ * Get all conversations the authenticated user has participated in during the last 24 hours.
+ * This function:
+ * - Lists all conversations (channels, groups, im, mpim) the token can access
+ * - For each conversation, fetches recent messages and checks if any message is within last 24h
+ * - Returns a map of conversation IDs to their latest relevant message timestamps
+ *
+ * "User Token Scopes" section (NOT Bot Token Scopes)
+ * Click "Add an OAuth Scope" and add these scopes:
+ * channels:read
+ * channels:history
+ * groups:read
+ * groups:history
+ * im:read
+ * im:history
+ * mpim:read
+ * mpim:history
+ */
+async function getParticipatedConversationsLast24h(): Promise