@axiom-lattice/core 2.1.23 → 2.1.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +1641 -68
- package/dist/index.d.ts +1641 -68
- package/dist/index.js +3773 -486
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3732 -486
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -508,11 +508,11 @@ var ToolLatticeManager = class _ToolLatticeManager extends BaseLatticeManager {
|
|
|
508
508
|
* @param key Lattice键名
|
|
509
509
|
* @param tool 已有的StructuredTool实例
|
|
510
510
|
*/
|
|
511
|
-
registerExistingTool(key,
|
|
511
|
+
registerExistingTool(key, tool38) {
|
|
512
512
|
const config = {
|
|
513
|
-
name:
|
|
514
|
-
description:
|
|
515
|
-
schema:
|
|
513
|
+
name: tool38.name,
|
|
514
|
+
description: tool38.description,
|
|
515
|
+
schema: tool38.schema,
|
|
516
516
|
// StructuredTool的schema已经是Zod兼容的
|
|
517
517
|
needUserApprove: false
|
|
518
518
|
// MCP工具默认不需要用户批准
|
|
@@ -520,7 +520,7 @@ var ToolLatticeManager = class _ToolLatticeManager extends BaseLatticeManager {
|
|
|
520
520
|
const toolLattice = {
|
|
521
521
|
key,
|
|
522
522
|
config,
|
|
523
|
-
client:
|
|
523
|
+
client: tool38
|
|
524
524
|
};
|
|
525
525
|
this.register(key, toolLattice);
|
|
526
526
|
}
|
|
@@ -546,7 +546,7 @@ var ToolLatticeManager = class _ToolLatticeManager extends BaseLatticeManager {
|
|
|
546
546
|
};
|
|
547
547
|
var toolLatticeManager = ToolLatticeManager.getInstance();
|
|
548
548
|
var registerToolLattice = (key, config, executor) => toolLatticeManager.registerLattice(key, config, executor);
|
|
549
|
-
var registerExistingTool = (key,
|
|
549
|
+
var registerExistingTool = (key, tool38) => toolLatticeManager.registerExistingTool(key, tool38);
|
|
550
550
|
var getToolLattice = (key) => toolLatticeManager.getToolLattice(key);
|
|
551
551
|
var getToolDefinition = (key) => toolLatticeManager.getToolDefinition(key);
|
|
552
552
|
var getToolClient = (key) => toolLatticeManager.getToolClient(key);
|
|
@@ -618,7 +618,7 @@ var PostgresDatabase = class {
|
|
|
618
618
|
this.config = config;
|
|
619
619
|
}
|
|
620
620
|
async connect() {
|
|
621
|
-
if (this.connected) return;
|
|
621
|
+
if (this.connected && this.pool) return;
|
|
622
622
|
try {
|
|
623
623
|
const { Pool } = await import("pg");
|
|
624
624
|
const poolConfig = this.config.connectionString ? { connectionString: this.config.connectionString } : {
|
|
@@ -631,16 +631,27 @@ var PostgresDatabase = class {
|
|
|
631
631
|
};
|
|
632
632
|
this.pool = new Pool(poolConfig);
|
|
633
633
|
const client = await this.pool.connect();
|
|
634
|
-
|
|
634
|
+
try {
|
|
635
|
+
await client.query("SELECT 1");
|
|
636
|
+
} finally {
|
|
637
|
+
client.release();
|
|
638
|
+
}
|
|
635
639
|
this.connected = true;
|
|
636
640
|
} catch (error) {
|
|
641
|
+
this.connected = false;
|
|
637
642
|
throw new Error(`Failed to connect to PostgreSQL: ${error}`);
|
|
638
643
|
}
|
|
639
644
|
}
|
|
640
645
|
async disconnect() {
|
|
641
646
|
if (this.pool) {
|
|
642
|
-
|
|
643
|
-
|
|
647
|
+
try {
|
|
648
|
+
await this.pool.end();
|
|
649
|
+
} catch (error) {
|
|
650
|
+
console.warn("Warning: Error closing PostgreSQL pool:", error);
|
|
651
|
+
} finally {
|
|
652
|
+
this.pool = null;
|
|
653
|
+
this.connected = false;
|
|
654
|
+
}
|
|
644
655
|
}
|
|
645
656
|
}
|
|
646
657
|
async listTables() {
|
|
@@ -836,45 +847,55 @@ var SqlDatabaseManager = class _SqlDatabaseManager {
|
|
|
836
847
|
await database.disconnect();
|
|
837
848
|
}
|
|
838
849
|
}
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
850
|
+
/**
|
|
851
|
+
* Load database configurations from a DatabaseConfigStore
|
|
852
|
+
* and register them with this manager
|
|
853
|
+
*
|
|
854
|
+
* @param store - The database configuration store
|
|
855
|
+
* @param tenantId - Tenant identifier
|
|
856
|
+
*/
|
|
857
|
+
async loadConfigsFromStore(store, tenantId) {
|
|
858
|
+
const configs = await store.getAllConfigs(tenantId);
|
|
859
|
+
for (const entry of configs) {
|
|
860
|
+
this.registerDatabase(entry.key, entry.config);
|
|
861
|
+
}
|
|
845
862
|
}
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
863
|
+
/**
|
|
864
|
+
* Load all database configurations from a DatabaseConfigStore
|
|
865
|
+
* across all tenants and register them with this manager
|
|
866
|
+
*
|
|
867
|
+
* @param store - The database configuration store
|
|
868
|
+
*/
|
|
869
|
+
async loadAllConfigsFromStore(store) {
|
|
870
|
+
const configs = await store.getAllConfigsWithoutTenant();
|
|
871
|
+
for (const entry of configs) {
|
|
872
|
+
this.registerDatabase(entry.key, entry.config);
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
};
|
|
855
876
|
var sqlDatabaseManager = SqlDatabaseManager.getInstance();
|
|
856
877
|
|
|
857
878
|
// src/tool_lattice/sql/list_tables_sql.ts
|
|
858
879
|
import z3 from "zod";
|
|
859
880
|
import { tool as tool2 } from "langchain";
|
|
860
881
|
var LIST_TABLES_SQL_DESCRIPTION = `List all tables in the connected SQL database. Returns a comma-separated list of table names. Use this tool first to understand what tables are available before querying the database.`;
|
|
861
|
-
var createListTablesSqlTool = ({
|
|
882
|
+
var createListTablesSqlTool = ({ databaseKeys, databaseDescriptions }) => {
|
|
883
|
+
const availableDbsText = databaseKeys.length > 0 ? `
|
|
884
|
+
|
|
885
|
+
Available databases:
|
|
886
|
+
${databaseKeys.map(
|
|
887
|
+
(key) => `- ${key}${databaseDescriptions?.[key] ? `: ${databaseDescriptions[key]}` : ""}`
|
|
888
|
+
).join("\n")}` : "";
|
|
862
889
|
return tool2(
|
|
863
|
-
async (
|
|
890
|
+
async ({ databaseKey }, exe_config) => {
|
|
864
891
|
try {
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
dbKey = databaseKey;
|
|
868
|
-
} else if (connectionString) {
|
|
869
|
-
dbKey = connectionString;
|
|
870
|
-
if (!sqlDatabaseManager.hasDatabase(dbKey)) {
|
|
871
|
-
const config = parseConnectionString(connectionString);
|
|
872
|
-
sqlDatabaseManager.registerDatabase(dbKey, { ...config, type: "postgres" });
|
|
873
|
-
}
|
|
874
|
-
} else {
|
|
875
|
-
return "Error: Must provide databaseKey or connectionString";
|
|
892
|
+
if (!databaseKey) {
|
|
893
|
+
return "Error: databaseKey parameter is required. Available databases: " + databaseKeys.join(", ");
|
|
876
894
|
}
|
|
877
|
-
|
|
895
|
+
if (!databaseKeys.includes(databaseKey)) {
|
|
896
|
+
return `Error: databaseKey "${databaseKey}" is not in the allowed list: [${databaseKeys.join(", ")}]`;
|
|
897
|
+
}
|
|
898
|
+
const database = sqlDatabaseManager.getDatabase(databaseKey);
|
|
878
899
|
const tables = await database.listTables();
|
|
879
900
|
if (tables.length === 0) {
|
|
880
901
|
return "No tables found in the database.";
|
|
@@ -889,8 +910,10 @@ var createListTablesSqlTool = ({ databaseKey, connectionString }) => {
|
|
|
889
910
|
},
|
|
890
911
|
{
|
|
891
912
|
name: "list_tables_sql",
|
|
892
|
-
description: LIST_TABLES_SQL_DESCRIPTION
|
|
893
|
-
schema: z3.object({
|
|
913
|
+
description: `${LIST_TABLES_SQL_DESCRIPTION}${availableDbsText}`,
|
|
914
|
+
schema: z3.object({
|
|
915
|
+
databaseKey: z3.string().describe(`Target database to list tables. Choose from: ${databaseKeys.join(", ")}`)
|
|
916
|
+
})
|
|
894
917
|
}
|
|
895
918
|
);
|
|
896
919
|
};
|
|
@@ -899,49 +922,26 @@ var createListTablesSqlTool = ({ databaseKey, connectionString }) => {
|
|
|
899
922
|
import z4 from "zod";
|
|
900
923
|
import { tool as tool3 } from "langchain";
|
|
901
924
|
var INFO_SQL_DESCRIPTION = `Get detailed schema information for specified tables, including column names, types, constraints (primary keys, foreign keys), and sample rows. Input should be a comma-separated list of table names.`;
|
|
902
|
-
|
|
903
|
-
const
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
const constraints = [];
|
|
910
|
-
if (col.isPrimaryKey) constraints.push("PRIMARY KEY");
|
|
911
|
-
if (col.isForeignKey && col.foreignKeyRef)
|
|
912
|
-
constraints.push(`FK -> ${col.foreignKeyRef}`);
|
|
913
|
-
if (!col.nullable) constraints.push("NOT NULL");
|
|
914
|
-
const constraintStr = constraints.length > 0 ? ` [${constraints.join(", ")}]` : "";
|
|
915
|
-
lines.push(` - ${col.name}: ${col.type}${constraintStr}`);
|
|
916
|
-
}
|
|
917
|
-
if (schema.sampleRows && schema.sampleRows.length > 0) {
|
|
918
|
-
lines.push("\nSample Rows (up to 3):");
|
|
919
|
-
for (const row of schema.sampleRows) {
|
|
920
|
-
const rowStr = Object.entries(row).map(([k, v]) => `${k}=${JSON.stringify(v)}`).join(", ");
|
|
921
|
-
lines.push(` ${rowStr}`);
|
|
922
|
-
}
|
|
923
|
-
}
|
|
924
|
-
return lines.join("\n");
|
|
925
|
-
}
|
|
926
|
-
var createInfoSqlTool = ({ databaseKey, connectionString }) => {
|
|
925
|
+
var createInfoSqlTool = ({ databaseKeys, databaseDescriptions }) => {
|
|
926
|
+
const availableDbsText = databaseKeys.length > 0 ? `
|
|
927
|
+
|
|
928
|
+
Available databases:
|
|
929
|
+
${databaseKeys.map(
|
|
930
|
+
(key) => `- ${key}${databaseDescriptions?.[key] ? `: ${databaseDescriptions[key]}` : ""}`
|
|
931
|
+
).join("\n")}` : "";
|
|
927
932
|
return tool3(
|
|
928
933
|
async ({
|
|
929
|
-
tables
|
|
934
|
+
tables,
|
|
935
|
+
databaseKey
|
|
930
936
|
}, exe_config) => {
|
|
931
937
|
try {
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
if (!sqlDatabaseManager.hasDatabase(dbKey)) {
|
|
938
|
-
const config = parseConnectionString(connectionString);
|
|
939
|
-
sqlDatabaseManager.registerDatabase(dbKey, { ...config, type: "postgres" });
|
|
940
|
-
}
|
|
941
|
-
} else {
|
|
942
|
-
return "Error: Must provide databaseKey or connectionString";
|
|
938
|
+
if (!databaseKey) {
|
|
939
|
+
return "Error: databaseKey parameter is required. Available databases: " + databaseKeys.join(", ");
|
|
940
|
+
}
|
|
941
|
+
if (!databaseKeys.includes(databaseKey)) {
|
|
942
|
+
return `Error: databaseKey "${databaseKey}" is not in the allowed list: [${databaseKeys.join(", ")}]`;
|
|
943
943
|
}
|
|
944
|
-
const database = sqlDatabaseManager.getDatabase(
|
|
944
|
+
const database = sqlDatabaseManager.getDatabase(databaseKey);
|
|
945
945
|
const tableNames = tables.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
946
946
|
if (tableNames.length === 0) {
|
|
947
947
|
return "Error: No table names provided. Please provide a comma-separated list of table names.";
|
|
@@ -958,15 +958,40 @@ var createInfoSqlTool = ({ databaseKey, connectionString }) => {
|
|
|
958
958
|
},
|
|
959
959
|
{
|
|
960
960
|
name: "info_sql",
|
|
961
|
-
description: INFO_SQL_DESCRIPTION
|
|
961
|
+
description: `${INFO_SQL_DESCRIPTION}${availableDbsText}`,
|
|
962
962
|
schema: z4.object({
|
|
963
963
|
tables: z4.string().describe(
|
|
964
964
|
"Comma-separated list of table names to get information for. Example: 'users, orders, products'"
|
|
965
|
-
)
|
|
965
|
+
),
|
|
966
|
+
databaseKey: z4.string().describe(`Target database to get table info. Choose from: ${databaseKeys.join(", ")}`)
|
|
966
967
|
})
|
|
967
968
|
}
|
|
968
969
|
);
|
|
969
970
|
};
|
|
971
|
+
function formatTableSchema(schema) {
|
|
972
|
+
const lines = [];
|
|
973
|
+
lines.push(`
|
|
974
|
+
Table: ${schema.tableName}`);
|
|
975
|
+
lines.push("-".repeat(40));
|
|
976
|
+
lines.push("Columns:");
|
|
977
|
+
for (const col of schema.columns) {
|
|
978
|
+
const constraints = [];
|
|
979
|
+
if (col.isPrimaryKey) constraints.push("PRIMARY KEY");
|
|
980
|
+
if (col.isForeignKey && col.foreignKeyRef)
|
|
981
|
+
constraints.push(`FK -> ${col.foreignKeyRef}`);
|
|
982
|
+
if (!col.nullable) constraints.push("NOT NULL");
|
|
983
|
+
const constraintStr = constraints.length > 0 ? ` [${constraints.join(", ")}]` : "";
|
|
984
|
+
lines.push(` - ${col.name}: ${col.type}${constraintStr}`);
|
|
985
|
+
}
|
|
986
|
+
if (schema.sampleRows && schema.sampleRows.length > 0) {
|
|
987
|
+
lines.push("\nSample Rows (up to 3):");
|
|
988
|
+
for (const row of schema.sampleRows) {
|
|
989
|
+
const rowStr = Object.entries(row).map(([k, v]) => `${k}=${JSON.stringify(v)}`).join(", ");
|
|
990
|
+
lines.push(` ${rowStr}`);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
return lines.join("\n");
|
|
994
|
+
}
|
|
970
995
|
|
|
971
996
|
// src/tool_lattice/sql/query_checker_sql.ts
|
|
972
997
|
import z5 from "zod";
|
|
@@ -1028,10 +1053,17 @@ function checkDangerousOperations(query) {
|
|
|
1028
1053
|
}
|
|
1029
1054
|
return warnings;
|
|
1030
1055
|
}
|
|
1031
|
-
var createQueryCheckerSqlTool = ({
|
|
1056
|
+
var createQueryCheckerSqlTool = ({ databaseKeys, databaseDescriptions }) => {
|
|
1057
|
+
const availableDbsText = databaseKeys.length > 0 ? `
|
|
1058
|
+
|
|
1059
|
+
Available databases:
|
|
1060
|
+
${databaseKeys.map(
|
|
1061
|
+
(key) => `- ${key}${databaseDescriptions?.[key] ? `: ${databaseDescriptions[key]}` : ""}`
|
|
1062
|
+
).join("\n")}` : "";
|
|
1032
1063
|
return tool4(
|
|
1033
1064
|
async ({
|
|
1034
|
-
query
|
|
1065
|
+
query,
|
|
1066
|
+
databaseKey
|
|
1035
1067
|
}, exe_config) => {
|
|
1036
1068
|
try {
|
|
1037
1069
|
const trimmedQuery = query.trim();
|
|
@@ -1063,19 +1095,9 @@ ${trimmedQuery}
|
|
|
1063
1095
|
} else {
|
|
1064
1096
|
results.push("Safety: No dangerous operations detected");
|
|
1065
1097
|
}
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
dbKey = databaseKey;
|
|
1070
|
-
} else if (connectionString) {
|
|
1071
|
-
dbKey = connectionString;
|
|
1072
|
-
if (!sqlDatabaseManager.hasDatabase(dbKey)) {
|
|
1073
|
-
const config = parseConnectionString(connectionString);
|
|
1074
|
-
sqlDatabaseManager.registerDatabase(dbKey, { ...config, type: "postgres" });
|
|
1075
|
-
}
|
|
1076
|
-
}
|
|
1077
|
-
if (dbKey) {
|
|
1078
|
-
const database = sqlDatabaseManager.getDatabase(dbKey);
|
|
1098
|
+
if (databaseKey && databaseKeys.includes(databaseKey)) {
|
|
1099
|
+
try {
|
|
1100
|
+
const database = sqlDatabaseManager.getDatabase(databaseKey);
|
|
1079
1101
|
const dbType = database.getDatabaseType();
|
|
1080
1102
|
if (dbType === "postgres") {
|
|
1081
1103
|
try {
|
|
@@ -1087,10 +1109,14 @@ ${trimmedQuery}
|
|
|
1087
1109
|
);
|
|
1088
1110
|
}
|
|
1089
1111
|
}
|
|
1112
|
+
} catch {
|
|
1113
|
+
results.push(
|
|
1114
|
+
"Database Validation: Skipped (database connection unavailable)"
|
|
1115
|
+
);
|
|
1090
1116
|
}
|
|
1091
|
-
}
|
|
1117
|
+
} else {
|
|
1092
1118
|
results.push(
|
|
1093
|
-
"Database Validation: Skipped (no
|
|
1119
|
+
"Database Validation: Skipped (no valid databaseKey provided)"
|
|
1094
1120
|
);
|
|
1095
1121
|
}
|
|
1096
1122
|
const hasErrors = syntaxIssues.some((i) => !i.startsWith("Warning:")) || dangerWarnings.length > 0;
|
|
@@ -1109,9 +1135,10 @@ ${trimmedQuery}
|
|
|
1109
1135
|
},
|
|
1110
1136
|
{
|
|
1111
1137
|
name: "query_checker_sql",
|
|
1112
|
-
description:
|
|
1138
|
+
description: `Check a SQL query for common issues before execution. This tool validates syntax, checks for dangerous operations, and provides suggestions for improvement. Use this before executing queries to ensure they are safe and correct.${availableDbsText}`,
|
|
1113
1139
|
schema: z5.object({
|
|
1114
|
-
query: z5.string().describe("The SQL query to check and validate.")
|
|
1140
|
+
query: z5.string().describe("The SQL query to check and validate."),
|
|
1141
|
+
databaseKey: z5.string().describe(`Target database to validate query against. Choose from: ${databaseKeys.join(", ")}`)
|
|
1115
1142
|
})
|
|
1116
1143
|
}
|
|
1117
1144
|
);
|
|
@@ -1120,7 +1147,6 @@ ${trimmedQuery}
|
|
|
1120
1147
|
// src/tool_lattice/sql/query_sql.ts
|
|
1121
1148
|
import z6 from "zod";
|
|
1122
1149
|
import { tool as tool5 } from "langchain";
|
|
1123
|
-
var QUERY_SQL_DESCRIPTION = `Execute a SQL query against the database and return the results. Input should be a valid SQL query. Use this tool to retrieve data from the database. For complex queries, first use list_tables_sql and info_sql to understand the database schema.`;
|
|
1124
1150
|
function formatQueryResult(rows, fields) {
|
|
1125
1151
|
if (rows.length === 0) {
|
|
1126
1152
|
return "Query executed successfully. No rows returned.";
|
|
@@ -1148,29 +1174,30 @@ function formatQueryResult(rows, fields) {
|
|
|
1148
1174
|
Total rows: ${rows.length}`);
|
|
1149
1175
|
return lines.join("\n");
|
|
1150
1176
|
}
|
|
1151
|
-
var createQuerySqlTool = ({
|
|
1177
|
+
var createQuerySqlTool = ({ databaseKeys, databaseDescriptions }) => {
|
|
1178
|
+
const availableDbsText = databaseKeys.length > 0 ? `
|
|
1179
|
+
|
|
1180
|
+
Available databases:
|
|
1181
|
+
${databaseKeys.map(
|
|
1182
|
+
(key) => `- ${key}${databaseDescriptions?.[key] ? `: ${databaseDescriptions[key]}` : ""}`
|
|
1183
|
+
).join("\n")}` : "";
|
|
1152
1184
|
return tool5(
|
|
1153
1185
|
async ({
|
|
1154
|
-
query
|
|
1186
|
+
query,
|
|
1187
|
+
databaseKey
|
|
1155
1188
|
}, exe_config) => {
|
|
1156
1189
|
try {
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
if (!sqlDatabaseManager.hasDatabase(dbKey)) {
|
|
1163
|
-
const config = parseConnectionString(connectionString);
|
|
1164
|
-
sqlDatabaseManager.registerDatabase(dbKey, { ...config, type: "postgres" });
|
|
1165
|
-
}
|
|
1166
|
-
} else {
|
|
1167
|
-
return "Error: Must provide databaseKey or connectionString";
|
|
1190
|
+
if (!databaseKey) {
|
|
1191
|
+
return "Error: databaseKey parameter is required. Available databases: " + databaseKeys.join(", ");
|
|
1192
|
+
}
|
|
1193
|
+
if (!databaseKeys.includes(databaseKey)) {
|
|
1194
|
+
return `Error: databaseKey "${databaseKey}" is not in the allowed list: [${databaseKeys.join(", ")}]`;
|
|
1168
1195
|
}
|
|
1169
1196
|
const trimmedQuery = query.trim();
|
|
1170
1197
|
if (!trimmedQuery) {
|
|
1171
1198
|
return "Error: Empty query provided. Please provide a valid SQL query.";
|
|
1172
1199
|
}
|
|
1173
|
-
const database = sqlDatabaseManager.getDatabase(
|
|
1200
|
+
const database = sqlDatabaseManager.getDatabase(databaseKey);
|
|
1174
1201
|
const result = await database.executeQuery(trimmedQuery);
|
|
1175
1202
|
return formatQueryResult(result.rows, result.fields);
|
|
1176
1203
|
} catch (error) {
|
|
@@ -1179,11 +1206,12 @@ var createQuerySqlTool = ({ databaseKey, connectionString }) => {
|
|
|
1179
1206
|
},
|
|
1180
1207
|
{
|
|
1181
1208
|
name: "query_sql",
|
|
1182
|
-
description:
|
|
1209
|
+
description: `Execute a SQL query against the database and return the results.${availableDbsText}`,
|
|
1183
1210
|
schema: z6.object({
|
|
1184
1211
|
query: z6.string().describe(
|
|
1185
1212
|
"The SQL query to execute. Should be a valid SELECT, INSERT, UPDATE, or DELETE statement."
|
|
1186
|
-
)
|
|
1213
|
+
),
|
|
1214
|
+
databaseKey: z6.string().describe(`Target database to execute the query. Choose from: ${databaseKeys.join(", ")}`)
|
|
1187
1215
|
})
|
|
1188
1216
|
}
|
|
1189
1217
|
);
|
|
@@ -2471,10 +2499,13 @@ import {
|
|
|
2471
2499
|
AgentConfig,
|
|
2472
2500
|
ReactAgentConfig,
|
|
2473
2501
|
DeepAgentConfig,
|
|
2502
|
+
TeamAgentConfig,
|
|
2503
|
+
TeamTeammateConfig,
|
|
2474
2504
|
AgentConfigWithTools,
|
|
2475
2505
|
GraphBuildOptions,
|
|
2476
2506
|
hasTools,
|
|
2477
2507
|
isDeepAgentConfig,
|
|
2508
|
+
isTeamAgentConfig,
|
|
2478
2509
|
getToolsFromConfig,
|
|
2479
2510
|
getSubAgentsFromConfig
|
|
2480
2511
|
} from "@axiom-lattice/protocols";
|
|
@@ -2610,8 +2641,8 @@ var SandboxFilesystem = class {
|
|
|
2610
2641
|
*/
|
|
2611
2642
|
toVirtualPath(realPath) {
|
|
2612
2643
|
const rootPath = path2.join(this.homeDir, this.workingDirectory);
|
|
2613
|
-
const
|
|
2614
|
-
const normalized =
|
|
2644
|
+
const relative3 = path2.relative(rootPath, realPath);
|
|
2645
|
+
const normalized = relative3.split(path2.sep).join("/");
|
|
2615
2646
|
return "/" + normalized;
|
|
2616
2647
|
}
|
|
2617
2648
|
/**
|
|
@@ -2893,17 +2924,17 @@ function createBrowserMiddleware(params = { isolatedLevel: "global" }) {
|
|
|
2893
2924
|
// src/middlewares/sqlMiddleware.ts
|
|
2894
2925
|
import { createMiddleware as createMiddleware3 } from "langchain";
|
|
2895
2926
|
function createSqlMiddleware(params) {
|
|
2896
|
-
const {
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
} else if (databaseKey) {
|
|
2903
|
-
toolParams.databaseKey = databaseKey;
|
|
2904
|
-
} else if (connectionString) {
|
|
2905
|
-
toolParams.connectionString = connectionString;
|
|
2927
|
+
const { databaseKeys, databaseDescriptions } = params;
|
|
2928
|
+
if (!databaseKeys || databaseKeys.length === 0) {
|
|
2929
|
+
return createMiddleware3({
|
|
2930
|
+
name: "sqlMiddleware",
|
|
2931
|
+
tools: []
|
|
2932
|
+
});
|
|
2906
2933
|
}
|
|
2934
|
+
const toolParams = {
|
|
2935
|
+
databaseKeys,
|
|
2936
|
+
databaseDescriptions
|
|
2937
|
+
};
|
|
2907
2938
|
return createMiddleware3({
|
|
2908
2939
|
name: "sqlMiddleware",
|
|
2909
2940
|
tools: [
|
|
@@ -3568,6 +3599,258 @@ ${body}` : `${frontmatter}
|
|
|
3568
3599
|
}
|
|
3569
3600
|
};
|
|
3570
3601
|
|
|
3602
|
+
// src/store_lattice/InMemoryWorkspaceStore.ts
|
|
3603
|
+
var InMemoryWorkspaceStore = class {
|
|
3604
|
+
constructor() {
|
|
3605
|
+
this.workspaces = /* @__PURE__ */ new Map();
|
|
3606
|
+
this.initDefaultData();
|
|
3607
|
+
}
|
|
3608
|
+
initDefaultData() {
|
|
3609
|
+
const defaultTenantId = "default";
|
|
3610
|
+
const now = /* @__PURE__ */ new Date();
|
|
3611
|
+
const defaultWorkspace = {
|
|
3612
|
+
id: "default-workspace",
|
|
3613
|
+
tenantId: defaultTenantId,
|
|
3614
|
+
name: "\u9ED8\u8BA4\u5DE5\u4F5C\u533A",
|
|
3615
|
+
description: "\u7CFB\u7EDF\u9ED8\u8BA4\u5DE5\u4F5C\u533A",
|
|
3616
|
+
storageType: "sandbox",
|
|
3617
|
+
createdAt: now,
|
|
3618
|
+
updatedAt: now
|
|
3619
|
+
};
|
|
3620
|
+
this.workspaces.set(`${defaultTenantId}:default-workspace`, defaultWorkspace);
|
|
3621
|
+
}
|
|
3622
|
+
getKey(tenantId, id) {
|
|
3623
|
+
return `${tenantId}:${id}`;
|
|
3624
|
+
}
|
|
3625
|
+
async getAllWorkspaces(tenantId) {
|
|
3626
|
+
return Array.from(this.workspaces.values()).filter(
|
|
3627
|
+
(w) => w.tenantId === tenantId
|
|
3628
|
+
);
|
|
3629
|
+
}
|
|
3630
|
+
async getWorkspaceById(tenantId, id) {
|
|
3631
|
+
const key = this.getKey(tenantId, id);
|
|
3632
|
+
return this.workspaces.get(key) || null;
|
|
3633
|
+
}
|
|
3634
|
+
async createWorkspace(tenantId, id, data) {
|
|
3635
|
+
const now = /* @__PURE__ */ new Date();
|
|
3636
|
+
const workspace = {
|
|
3637
|
+
id,
|
|
3638
|
+
tenantId,
|
|
3639
|
+
name: data.name,
|
|
3640
|
+
description: data.description,
|
|
3641
|
+
storageType: data.storageType,
|
|
3642
|
+
createdAt: now,
|
|
3643
|
+
updatedAt: now
|
|
3644
|
+
};
|
|
3645
|
+
const key = this.getKey(tenantId, id);
|
|
3646
|
+
this.workspaces.set(key, workspace);
|
|
3647
|
+
return workspace;
|
|
3648
|
+
}
|
|
3649
|
+
async updateWorkspace(tenantId, id, updates) {
|
|
3650
|
+
const key = this.getKey(tenantId, id);
|
|
3651
|
+
const existing = this.workspaces.get(key);
|
|
3652
|
+
if (!existing) {
|
|
3653
|
+
return null;
|
|
3654
|
+
}
|
|
3655
|
+
const updated = {
|
|
3656
|
+
...existing,
|
|
3657
|
+
...updates,
|
|
3658
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
3659
|
+
};
|
|
3660
|
+
this.workspaces.set(key, updated);
|
|
3661
|
+
return updated;
|
|
3662
|
+
}
|
|
3663
|
+
async deleteWorkspace(tenantId, id) {
|
|
3664
|
+
const key = this.getKey(tenantId, id);
|
|
3665
|
+
return this.workspaces.delete(key);
|
|
3666
|
+
}
|
|
3667
|
+
clear() {
|
|
3668
|
+
this.workspaces.clear();
|
|
3669
|
+
}
|
|
3670
|
+
};
|
|
3671
|
+
|
|
3672
|
+
// src/store_lattice/InMemoryProjectStore.ts
|
|
3673
|
+
var InMemoryProjectStore = class {
|
|
3674
|
+
constructor() {
|
|
3675
|
+
this.projects = /* @__PURE__ */ new Map();
|
|
3676
|
+
this.initDefaultData();
|
|
3677
|
+
}
|
|
3678
|
+
initDefaultData() {
|
|
3679
|
+
const defaultTenantId = "default";
|
|
3680
|
+
const defaultWorkspaceId = "default-workspace";
|
|
3681
|
+
const now = /* @__PURE__ */ new Date();
|
|
3682
|
+
const defaultProject = {
|
|
3683
|
+
id: "default-project",
|
|
3684
|
+
tenantId: defaultTenantId,
|
|
3685
|
+
workspaceId: defaultWorkspaceId,
|
|
3686
|
+
name: "\u9ED8\u8BA4\u9879\u76EE",
|
|
3687
|
+
description: "\u7CFB\u7EDF\u9ED8\u8BA4\u9879\u76EE",
|
|
3688
|
+
createdAt: now,
|
|
3689
|
+
updatedAt: now
|
|
3690
|
+
};
|
|
3691
|
+
this.projects.set(`${defaultTenantId}:default-project`, defaultProject);
|
|
3692
|
+
}
|
|
3693
|
+
getKey(tenantId, id) {
|
|
3694
|
+
return `${tenantId}:${id}`;
|
|
3695
|
+
}
|
|
3696
|
+
async getProjectsByWorkspace(tenantId, workspaceId) {
|
|
3697
|
+
return Array.from(this.projects.values()).filter(
|
|
3698
|
+
(p) => p.tenantId === tenantId && p.workspaceId === workspaceId
|
|
3699
|
+
);
|
|
3700
|
+
}
|
|
3701
|
+
async getProjectById(tenantId, id) {
|
|
3702
|
+
const key = this.getKey(tenantId, id);
|
|
3703
|
+
return this.projects.get(key) || null;
|
|
3704
|
+
}
|
|
3705
|
+
async createProject(tenantId, workspaceId, id, data) {
|
|
3706
|
+
const now = /* @__PURE__ */ new Date();
|
|
3707
|
+
const project = {
|
|
3708
|
+
id,
|
|
3709
|
+
tenantId,
|
|
3710
|
+
workspaceId,
|
|
3711
|
+
name: data.name,
|
|
3712
|
+
description: data.description,
|
|
3713
|
+
createdAt: now,
|
|
3714
|
+
updatedAt: now
|
|
3715
|
+
};
|
|
3716
|
+
const key = this.getKey(tenantId, id);
|
|
3717
|
+
this.projects.set(key, project);
|
|
3718
|
+
return project;
|
|
3719
|
+
}
|
|
3720
|
+
async updateProject(tenantId, id, updates) {
|
|
3721
|
+
const key = this.getKey(tenantId, id);
|
|
3722
|
+
const existing = this.projects.get(key);
|
|
3723
|
+
if (!existing) {
|
|
3724
|
+
return null;
|
|
3725
|
+
}
|
|
3726
|
+
const updated = {
|
|
3727
|
+
...existing,
|
|
3728
|
+
...updates,
|
|
3729
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
3730
|
+
};
|
|
3731
|
+
this.projects.set(key, updated);
|
|
3732
|
+
return updated;
|
|
3733
|
+
}
|
|
3734
|
+
async deleteProject(tenantId, id) {
|
|
3735
|
+
const key = this.getKey(tenantId, id);
|
|
3736
|
+
return this.projects.delete(key);
|
|
3737
|
+
}
|
|
3738
|
+
clear() {
|
|
3739
|
+
this.projects.clear();
|
|
3740
|
+
}
|
|
3741
|
+
};
|
|
3742
|
+
|
|
3743
|
+
// src/store_lattice/InMemoryDatabaseConfigStore.ts
|
|
3744
|
+
var InMemoryDatabaseConfigStore = class {
|
|
3745
|
+
constructor() {
|
|
3746
|
+
this.configs = /* @__PURE__ */ new Map();
|
|
3747
|
+
}
|
|
3748
|
+
/**
|
|
3749
|
+
* Get composite key for storage
|
|
3750
|
+
*/
|
|
3751
|
+
getKey(tenantId, id) {
|
|
3752
|
+
return `${tenantId}:${id}`;
|
|
3753
|
+
}
|
|
3754
|
+
/**
|
|
3755
|
+
* Get all database configurations for a tenant
|
|
3756
|
+
*/
|
|
3757
|
+
async getAllConfigs(tenantId) {
|
|
3758
|
+
return Array.from(this.configs.values()).filter(
|
|
3759
|
+
(config) => config.tenantId === tenantId
|
|
3760
|
+
);
|
|
3761
|
+
}
|
|
3762
|
+
/**
|
|
3763
|
+
* Get all database configurations across all tenants
|
|
3764
|
+
*/
|
|
3765
|
+
async getAllConfigsWithoutTenant() {
|
|
3766
|
+
return Array.from(this.configs.values());
|
|
3767
|
+
}
|
|
3768
|
+
/**
|
|
3769
|
+
* Get database configuration by ID
|
|
3770
|
+
*/
|
|
3771
|
+
async getConfigById(tenantId, id) {
|
|
3772
|
+
const key = this.getKey(tenantId, id);
|
|
3773
|
+
return this.configs.get(key) || null;
|
|
3774
|
+
}
|
|
3775
|
+
/**
|
|
3776
|
+
* Get database configuration by business key
|
|
3777
|
+
*/
|
|
3778
|
+
async getConfigByKey(tenantId, key) {
|
|
3779
|
+
const configs = await this.getAllConfigs(tenantId);
|
|
3780
|
+
return configs.find((config) => config.key === key) || null;
|
|
3781
|
+
}
|
|
3782
|
+
/**
|
|
3783
|
+
* Create a new database configuration
|
|
3784
|
+
*/
|
|
3785
|
+
async createConfig(tenantId, id, data) {
|
|
3786
|
+
const now = /* @__PURE__ */ new Date();
|
|
3787
|
+
const entry = {
|
|
3788
|
+
id,
|
|
3789
|
+
tenantId,
|
|
3790
|
+
key: data.key,
|
|
3791
|
+
config: data.config,
|
|
3792
|
+
name: data.name,
|
|
3793
|
+
description: data.description,
|
|
3794
|
+
createdAt: now,
|
|
3795
|
+
updatedAt: now
|
|
3796
|
+
};
|
|
3797
|
+
const storageKey = this.getKey(tenantId, id);
|
|
3798
|
+
this.configs.set(storageKey, entry);
|
|
3799
|
+
return entry;
|
|
3800
|
+
}
|
|
3801
|
+
/**
|
|
3802
|
+
* Update an existing database configuration
|
|
3803
|
+
*/
|
|
3804
|
+
async updateConfig(tenantId, id, updates) {
|
|
3805
|
+
const key = this.getKey(tenantId, id);
|
|
3806
|
+
const existing = this.configs.get(key);
|
|
3807
|
+
if (!existing) {
|
|
3808
|
+
return null;
|
|
3809
|
+
}
|
|
3810
|
+
const updated = {
|
|
3811
|
+
...existing,
|
|
3812
|
+
...updates,
|
|
3813
|
+
config: updates.config ? { ...existing.config, ...updates.config } : existing.config,
|
|
3814
|
+
key: updates.key || existing.key,
|
|
3815
|
+
name: updates.name !== void 0 ? updates.name : existing.name,
|
|
3816
|
+
description: updates.description !== void 0 ? updates.description : existing.description,
|
|
3817
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
3818
|
+
};
|
|
3819
|
+
this.configs.set(key, updated);
|
|
3820
|
+
return updated;
|
|
3821
|
+
}
|
|
3822
|
+
/**
|
|
3823
|
+
* Delete a database configuration by ID
|
|
3824
|
+
*/
|
|
3825
|
+
async deleteConfig(tenantId, id) {
|
|
3826
|
+
const key = this.getKey(tenantId, id);
|
|
3827
|
+
return this.configs.delete(key);
|
|
3828
|
+
}
|
|
3829
|
+
/**
|
|
3830
|
+
* Check if configuration exists
|
|
3831
|
+
*/
|
|
3832
|
+
async hasConfig(tenantId, id) {
|
|
3833
|
+
const key = this.getKey(tenantId, id);
|
|
3834
|
+
return this.configs.has(key);
|
|
3835
|
+
}
|
|
3836
|
+
/**
|
|
3837
|
+
* Clear all configurations (useful for testing)
|
|
3838
|
+
*/
|
|
3839
|
+
clear() {
|
|
3840
|
+
this.configs.clear();
|
|
3841
|
+
}
|
|
3842
|
+
/**
|
|
3843
|
+
* Clear configurations for a specific tenant
|
|
3844
|
+
*/
|
|
3845
|
+
clearByTenant(tenantId) {
|
|
3846
|
+
for (const key of this.configs.keys()) {
|
|
3847
|
+
if (key.startsWith(`${tenantId}:`)) {
|
|
3848
|
+
this.configs.delete(key);
|
|
3849
|
+
}
|
|
3850
|
+
}
|
|
3851
|
+
}
|
|
3852
|
+
};
|
|
3853
|
+
|
|
3571
3854
|
// src/store_lattice/StoreLatticeManager.ts
|
|
3572
3855
|
var StoreLatticeManager = class _StoreLatticeManager extends BaseLatticeManager {
|
|
3573
3856
|
/**
|
|
@@ -3695,6 +3978,9 @@ var getStoreLattice = (key, type) => storeLatticeManager.getStoreLattice(key, ty
|
|
|
3695
3978
|
var defaultThreadStore = new InMemoryThreadStore();
|
|
3696
3979
|
var defaultAssistantStore = new InMemoryAssistantStore();
|
|
3697
3980
|
var defaultSkillStore = new FileSystemSkillStore();
|
|
3981
|
+
var defaultWorkspaceStore = new InMemoryWorkspaceStore();
|
|
3982
|
+
var defaultProjectStore = new InMemoryProjectStore();
|
|
3983
|
+
var defaultDatabaseConfigStore = new InMemoryDatabaseConfigStore();
|
|
3698
3984
|
storeLatticeManager.registerLattice("default", "thread", defaultThreadStore);
|
|
3699
3985
|
storeLatticeManager.registerLattice(
|
|
3700
3986
|
"default",
|
|
@@ -3702,6 +3988,9 @@ storeLatticeManager.registerLattice(
|
|
|
3702
3988
|
defaultAssistantStore
|
|
3703
3989
|
);
|
|
3704
3990
|
storeLatticeManager.registerLattice("default", "skill", defaultSkillStore);
|
|
3991
|
+
storeLatticeManager.registerLattice("default", "workspace", defaultWorkspaceStore);
|
|
3992
|
+
storeLatticeManager.registerLattice("default", "project", defaultProjectStore);
|
|
3993
|
+
storeLatticeManager.registerLattice("default", "database", defaultDatabaseConfigStore);
|
|
3705
3994
|
|
|
3706
3995
|
// src/tool_lattice/skill/load_skills.ts
|
|
3707
3996
|
import z32 from "zod";
|
|
@@ -3798,6 +4087,7 @@ var DEFAULT_EXTRA_NOTE = "Use the load_skill_content tool when you need detailed
|
|
|
3798
4087
|
function createSkillMiddleware(params = {}) {
|
|
3799
4088
|
const {
|
|
3800
4089
|
skills = [],
|
|
4090
|
+
readAll = false,
|
|
3801
4091
|
heading = DEFAULT_HEADING,
|
|
3802
4092
|
extraNote = DEFAULT_EXTRA_NOTE
|
|
3803
4093
|
} = params;
|
|
@@ -3805,14 +4095,16 @@ function createSkillMiddleware(params = {}) {
|
|
|
3805
4095
|
return createMiddleware4({
|
|
3806
4096
|
name: "skillMiddleware",
|
|
3807
4097
|
tools: [
|
|
3808
|
-
createLoadSkillsTool({ skills }),
|
|
4098
|
+
createLoadSkillsTool({ skills: readAll ? void 0 : skills }),
|
|
3809
4099
|
createLoadSkillContentTool()
|
|
3810
4100
|
],
|
|
3811
4101
|
beforeAgent: async () => {
|
|
3812
4102
|
try {
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
4103
|
+
const storeLattice = getStoreLattice("default", "skill");
|
|
4104
|
+
const skillStore = storeLattice?.store;
|
|
4105
|
+
if (readAll) {
|
|
4106
|
+
latestSkills = await skillStore.getAllSkills();
|
|
4107
|
+
} else if (skills && skills.length > 0) {
|
|
3816
4108
|
const skillLatticePromises = skills.map(
|
|
3817
4109
|
(skillId) => skillStore.getSkillById(skillId)
|
|
3818
4110
|
);
|
|
@@ -3854,6 +4146,8 @@ import { basename } from "path";
|
|
|
3854
4146
|
var EMPTY_CONTENT_WARNING = "System reminder: File exists but has empty contents";
|
|
3855
4147
|
var MAX_LINE_LENGTH = 1e4;
|
|
3856
4148
|
var LINE_NUMBER_WIDTH = 6;
|
|
4149
|
+
var TOOL_RESULT_TOKEN_LIMIT = 2e4;
|
|
4150
|
+
var TRUNCATION_GUIDANCE = "... [results truncated, try being more specific with your parameters]";
|
|
3857
4151
|
function sanitizeToolCallId(toolCallId) {
|
|
3858
4152
|
return toolCallId.replace(/\./g, "_").replace(/\//g, "_").replace(/\\/g, "_");
|
|
3859
4153
|
}
|
|
@@ -3949,8 +4243,24 @@ function performStringReplacement(content, oldString, newString, replaceAll) {
|
|
|
3949
4243
|
const newContent = content.split(oldString).join(newString);
|
|
3950
4244
|
return [newContent, occurrences];
|
|
3951
4245
|
}
|
|
3952
|
-
function
|
|
3953
|
-
|
|
4246
|
+
function truncateIfTooLong(result) {
|
|
4247
|
+
if (Array.isArray(result)) {
|
|
4248
|
+
const totalChars = result.reduce((sum, item) => sum + item.length, 0);
|
|
4249
|
+
if (totalChars > TOOL_RESULT_TOKEN_LIMIT * 4) {
|
|
4250
|
+
const truncateAt = Math.floor(
|
|
4251
|
+
result.length * TOOL_RESULT_TOKEN_LIMIT * 4 / totalChars
|
|
4252
|
+
);
|
|
4253
|
+
return [...result.slice(0, truncateAt), TRUNCATION_GUIDANCE];
|
|
4254
|
+
}
|
|
4255
|
+
return result;
|
|
4256
|
+
}
|
|
4257
|
+
if (result.length > TOOL_RESULT_TOKEN_LIMIT * 4) {
|
|
4258
|
+
return result.substring(0, TOOL_RESULT_TOKEN_LIMIT * 4) + "\n" + TRUNCATION_GUIDANCE;
|
|
4259
|
+
}
|
|
4260
|
+
return result;
|
|
4261
|
+
}
|
|
4262
|
+
function validatePath(path5) {
|
|
4263
|
+
const pathStr = path5 || "/";
|
|
3954
4264
|
if (!pathStr || pathStr.trim() === "") {
|
|
3955
4265
|
throw new Error("Path cannot be empty");
|
|
3956
4266
|
}
|
|
@@ -3960,10 +4270,10 @@ function validatePath(path4) {
|
|
|
3960
4270
|
}
|
|
3961
4271
|
return normalized;
|
|
3962
4272
|
}
|
|
3963
|
-
function globSearchFiles(files, pattern,
|
|
4273
|
+
function globSearchFiles(files, pattern, path5 = "/") {
|
|
3964
4274
|
let normalizedPath;
|
|
3965
4275
|
try {
|
|
3966
|
-
normalizedPath = validatePath(
|
|
4276
|
+
normalizedPath = validatePath(path5);
|
|
3967
4277
|
} catch {
|
|
3968
4278
|
return "No files found";
|
|
3969
4279
|
}
|
|
@@ -3973,15 +4283,15 @@ function globSearchFiles(files, pattern, path4 = "/") {
|
|
|
3973
4283
|
const effectivePattern = pattern;
|
|
3974
4284
|
const matches = [];
|
|
3975
4285
|
for (const [filePath, fileData] of Object.entries(filtered)) {
|
|
3976
|
-
let
|
|
3977
|
-
if (
|
|
3978
|
-
|
|
4286
|
+
let relative3 = filePath.substring(normalizedPath.length);
|
|
4287
|
+
if (relative3.startsWith("/")) {
|
|
4288
|
+
relative3 = relative3.substring(1);
|
|
3979
4289
|
}
|
|
3980
|
-
if (!
|
|
4290
|
+
if (!relative3) {
|
|
3981
4291
|
const parts = filePath.split("/");
|
|
3982
|
-
|
|
4292
|
+
relative3 = parts[parts.length - 1] || "";
|
|
3983
4293
|
}
|
|
3984
|
-
if (micromatch.isMatch(
|
|
4294
|
+
if (micromatch.isMatch(relative3, effectivePattern, {
|
|
3985
4295
|
dot: true,
|
|
3986
4296
|
nobrace: false
|
|
3987
4297
|
})) {
|
|
@@ -3994,7 +4304,28 @@ function globSearchFiles(files, pattern, path4 = "/") {
|
|
|
3994
4304
|
}
|
|
3995
4305
|
return matches.map(([fp]) => fp).join("\n");
|
|
3996
4306
|
}
|
|
3997
|
-
function
|
|
4307
|
+
function formatGrepResults(results, outputMode) {
|
|
4308
|
+
if (outputMode === "files_with_matches") {
|
|
4309
|
+
return Object.keys(results).sort().join("\n");
|
|
4310
|
+
}
|
|
4311
|
+
if (outputMode === "count") {
|
|
4312
|
+
const lines2 = [];
|
|
4313
|
+
for (const filePath of Object.keys(results).sort()) {
|
|
4314
|
+
const count = results[filePath].length;
|
|
4315
|
+
lines2.push(`${filePath}: ${count}`);
|
|
4316
|
+
}
|
|
4317
|
+
return lines2.join("\n");
|
|
4318
|
+
}
|
|
4319
|
+
const lines = [];
|
|
4320
|
+
for (const filePath of Object.keys(results).sort()) {
|
|
4321
|
+
lines.push(`${filePath}:`);
|
|
4322
|
+
for (const [lineNum, line] of results[filePath]) {
|
|
4323
|
+
lines.push(` ${lineNum}: ${line}`);
|
|
4324
|
+
}
|
|
4325
|
+
}
|
|
4326
|
+
return lines.join("\n");
|
|
4327
|
+
}
|
|
4328
|
+
function grepSearchFiles(files, pattern, path5 = null, glob = null, outputMode = "files_with_matches") {
|
|
3998
4329
|
let regex;
|
|
3999
4330
|
try {
|
|
4000
4331
|
regex = new RegExp(pattern);
|
|
@@ -4003,9 +4334,9 @@ function grepMatchesFromFiles(files, pattern, path4 = null, glob = null) {
|
|
|
4003
4334
|
}
|
|
4004
4335
|
let normalizedPath;
|
|
4005
4336
|
try {
|
|
4006
|
-
normalizedPath = validatePath(
|
|
4337
|
+
normalizedPath = validatePath(path5);
|
|
4007
4338
|
} catch {
|
|
4008
|
-
return
|
|
4339
|
+
return "No matches found";
|
|
4009
4340
|
}
|
|
4010
4341
|
let filtered = Object.fromEntries(
|
|
4011
4342
|
Object.entries(files).filter(([fp]) => fp.startsWith(normalizedPath))
|
|
@@ -4017,49 +4348,106 @@ function grepMatchesFromFiles(files, pattern, path4 = null, glob = null) {
|
|
|
4017
4348
|
)
|
|
4018
4349
|
);
|
|
4019
4350
|
}
|
|
4020
|
-
const
|
|
4351
|
+
const results = {};
|
|
4021
4352
|
for (const [filePath, fileData] of Object.entries(filtered)) {
|
|
4022
4353
|
for (let i = 0; i < fileData.content.length; i++) {
|
|
4023
4354
|
const line = fileData.content[i];
|
|
4024
4355
|
const lineNum = i + 1;
|
|
4025
4356
|
if (regex.test(line)) {
|
|
4026
|
-
|
|
4357
|
+
if (!results[filePath]) {
|
|
4358
|
+
results[filePath] = [];
|
|
4359
|
+
}
|
|
4360
|
+
results[filePath].push([lineNum, line]);
|
|
4027
4361
|
}
|
|
4028
4362
|
}
|
|
4029
4363
|
}
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
// src/deep_agent_new/backends/state.ts
|
|
4034
|
-
var StateBackend = class {
|
|
4035
|
-
constructor(stateAndStore) {
|
|
4036
|
-
this.stateAndStore = stateAndStore;
|
|
4364
|
+
if (Object.keys(results).length === 0) {
|
|
4365
|
+
return "No matches found";
|
|
4037
4366
|
}
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4367
|
+
return formatGrepResults(results, outputMode);
|
|
4368
|
+
}
|
|
4369
|
+
function grepMatchesFromFiles(files, pattern, path5 = null, glob = null) {
|
|
4370
|
+
let regex;
|
|
4371
|
+
try {
|
|
4372
|
+
regex = new RegExp(pattern);
|
|
4373
|
+
} catch (e) {
|
|
4374
|
+
return `Invalid regex pattern: ${e.message}`;
|
|
4043
4375
|
}
|
|
4044
|
-
|
|
4376
|
+
let normalizedPath;
|
|
4377
|
+
try {
|
|
4378
|
+
normalizedPath = validatePath(path5);
|
|
4379
|
+
} catch {
|
|
4380
|
+
return [];
|
|
4381
|
+
}
|
|
4382
|
+
let filtered = Object.fromEntries(
|
|
4383
|
+
Object.entries(files).filter(([fp]) => fp.startsWith(normalizedPath))
|
|
4384
|
+
);
|
|
4385
|
+
if (glob) {
|
|
4386
|
+
filtered = Object.fromEntries(
|
|
4387
|
+
Object.entries(filtered).filter(
|
|
4388
|
+
([fp]) => micromatch.isMatch(basename(fp), glob, { dot: true, nobrace: false })
|
|
4389
|
+
)
|
|
4390
|
+
);
|
|
4391
|
+
}
|
|
4392
|
+
const matches = [];
|
|
4393
|
+
for (const [filePath, fileData] of Object.entries(filtered)) {
|
|
4394
|
+
for (let i = 0; i < fileData.content.length; i++) {
|
|
4395
|
+
const line = fileData.content[i];
|
|
4396
|
+
const lineNum = i + 1;
|
|
4397
|
+
if (regex.test(line)) {
|
|
4398
|
+
matches.push({ path: filePath, line: lineNum, text: line });
|
|
4399
|
+
}
|
|
4400
|
+
}
|
|
4401
|
+
}
|
|
4402
|
+
return matches;
|
|
4403
|
+
}
|
|
4404
|
+
function buildGrepResultsDict(matches) {
|
|
4405
|
+
const grouped = {};
|
|
4406
|
+
for (const m of matches) {
|
|
4407
|
+
if (!grouped[m.path]) {
|
|
4408
|
+
grouped[m.path] = [];
|
|
4409
|
+
}
|
|
4410
|
+
grouped[m.path].push([m.line, m.text]);
|
|
4411
|
+
}
|
|
4412
|
+
return grouped;
|
|
4413
|
+
}
|
|
4414
|
+
function formatGrepMatches(matches, outputMode) {
|
|
4415
|
+
if (matches.length === 0) {
|
|
4416
|
+
return "No matches found";
|
|
4417
|
+
}
|
|
4418
|
+
return formatGrepResults(buildGrepResultsDict(matches), outputMode);
|
|
4419
|
+
}
|
|
4420
|
+
|
|
4421
|
+
// src/deep_agent_new/backends/state.ts
|
|
4422
|
+
var StateBackend = class {
|
|
4423
|
+
constructor(stateAndStore) {
|
|
4424
|
+
this.stateAndStore = stateAndStore;
|
|
4425
|
+
}
|
|
4426
|
+
/**
|
|
4427
|
+
* Get files from current state.
|
|
4428
|
+
*/
|
|
4429
|
+
getFiles() {
|
|
4430
|
+
return this.stateAndStore.state.files || {};
|
|
4431
|
+
}
|
|
4432
|
+
/**
|
|
4045
4433
|
* List files and directories in the specified directory (non-recursive).
|
|
4046
4434
|
*
|
|
4047
4435
|
* @param path - Absolute path to directory
|
|
4048
4436
|
* @returns List of FileInfo objects for files and directories directly in the directory.
|
|
4049
4437
|
* Directories have a trailing / in their path and is_dir=true.
|
|
4050
4438
|
*/
|
|
4051
|
-
lsInfo(
|
|
4439
|
+
lsInfo(path5) {
|
|
4052
4440
|
const files = this.getFiles();
|
|
4053
4441
|
const infos = [];
|
|
4054
4442
|
const subdirs = /* @__PURE__ */ new Set();
|
|
4055
|
-
const normalizedPath =
|
|
4443
|
+
const normalizedPath = path5.endsWith("/") ? path5 : path5 + "/";
|
|
4056
4444
|
for (const [k, fd] of Object.entries(files)) {
|
|
4057
4445
|
if (!k.startsWith(normalizedPath)) {
|
|
4058
4446
|
continue;
|
|
4059
4447
|
}
|
|
4060
|
-
const
|
|
4061
|
-
if (
|
|
4062
|
-
const subdirName =
|
|
4448
|
+
const relative3 = k.substring(normalizedPath.length);
|
|
4449
|
+
if (relative3.includes("/")) {
|
|
4450
|
+
const subdirName = relative3.split("/")[0];
|
|
4063
4451
|
subdirs.add(normalizedPath + subdirName + "/");
|
|
4064
4452
|
continue;
|
|
4065
4453
|
}
|
|
@@ -4158,16 +4546,16 @@ var StateBackend = class {
|
|
|
4158
4546
|
/**
|
|
4159
4547
|
* Structured search results or error string for invalid input.
|
|
4160
4548
|
*/
|
|
4161
|
-
grepRaw(pattern,
|
|
4549
|
+
grepRaw(pattern, path5 = "/", glob = null) {
|
|
4162
4550
|
const files = this.getFiles();
|
|
4163
|
-
return grepMatchesFromFiles(files, pattern,
|
|
4551
|
+
return grepMatchesFromFiles(files, pattern, path5, glob);
|
|
4164
4552
|
}
|
|
4165
4553
|
/**
|
|
4166
4554
|
* Structured glob matching returning FileInfo objects.
|
|
4167
4555
|
*/
|
|
4168
|
-
globInfo(pattern,
|
|
4556
|
+
globInfo(pattern, path5 = "/") {
|
|
4169
4557
|
const files = this.getFiles();
|
|
4170
|
-
const result = globSearchFiles(files, pattern,
|
|
4558
|
+
const result = globSearchFiles(files, pattern, path5);
|
|
4171
4559
|
if (result === "No files found") {
|
|
4172
4560
|
return [];
|
|
4173
4561
|
}
|
|
@@ -4248,15 +4636,17 @@ function createLsTool(backend, options) {
|
|
|
4248
4636
|
const { customDescription } = options;
|
|
4249
4637
|
return tool33(
|
|
4250
4638
|
async (input, config) => {
|
|
4639
|
+
const { runConfig } = config.configurable;
|
|
4251
4640
|
const stateAndStore = {
|
|
4252
4641
|
state: getCurrentTaskInput(config),
|
|
4253
|
-
store: config.store
|
|
4642
|
+
store: config.store,
|
|
4643
|
+
...runConfig
|
|
4254
4644
|
};
|
|
4255
4645
|
const resolvedBackend = await getBackend(backend, stateAndStore);
|
|
4256
|
-
const
|
|
4257
|
-
const infos = await resolvedBackend.lsInfo(
|
|
4646
|
+
const path5 = input.path || "/";
|
|
4647
|
+
const infos = await resolvedBackend.lsInfo(path5);
|
|
4258
4648
|
if (infos.length === 0) {
|
|
4259
|
-
return `No files found in ${
|
|
4649
|
+
return `No files found in ${path5}`;
|
|
4260
4650
|
}
|
|
4261
4651
|
const lines = [];
|
|
4262
4652
|
for (const info of infos) {
|
|
@@ -4282,9 +4672,11 @@ function createReadFileTool(backend, options) {
|
|
|
4282
4672
|
const { customDescription } = options;
|
|
4283
4673
|
return tool33(
|
|
4284
4674
|
async (input, config) => {
|
|
4675
|
+
const { runConfig } = config.configurable;
|
|
4285
4676
|
const stateAndStore = {
|
|
4286
4677
|
state: getCurrentTaskInput(config),
|
|
4287
|
-
store: config.store
|
|
4678
|
+
store: config.store,
|
|
4679
|
+
...runConfig
|
|
4288
4680
|
};
|
|
4289
4681
|
const resolvedBackend = await getBackend(backend, stateAndStore);
|
|
4290
4682
|
const { file_path, offset = 0, limit = 2e3 } = input;
|
|
@@ -4305,9 +4697,11 @@ function createWriteFileTool(backend, options) {
|
|
|
4305
4697
|
const { customDescription } = options;
|
|
4306
4698
|
return tool33(
|
|
4307
4699
|
async (input, config) => {
|
|
4700
|
+
const { runConfig } = config.configurable;
|
|
4308
4701
|
const stateAndStore = {
|
|
4309
4702
|
state: getCurrentTaskInput(config),
|
|
4310
|
-
store: config.store
|
|
4703
|
+
store: config.store,
|
|
4704
|
+
...runConfig
|
|
4311
4705
|
};
|
|
4312
4706
|
const resolvedBackend = await getBackend(backend, stateAndStore);
|
|
4313
4707
|
const { file_path, content } = input;
|
|
@@ -4342,9 +4736,11 @@ function createEditFileTool(backend, options) {
|
|
|
4342
4736
|
const { customDescription } = options;
|
|
4343
4737
|
return tool33(
|
|
4344
4738
|
async (input, config) => {
|
|
4739
|
+
const { runConfig } = config.configurable;
|
|
4345
4740
|
const stateAndStore = {
|
|
4346
4741
|
state: getCurrentTaskInput(config),
|
|
4347
|
-
store: config.store
|
|
4742
|
+
store: config.store,
|
|
4743
|
+
...runConfig
|
|
4348
4744
|
};
|
|
4349
4745
|
const resolvedBackend = await getBackend(backend, stateAndStore);
|
|
4350
4746
|
const { file_path, old_string, new_string, replace_all = false } = input;
|
|
@@ -4386,13 +4782,15 @@ function createGlobTool(backend, options) {
|
|
|
4386
4782
|
const { customDescription } = options;
|
|
4387
4783
|
return tool33(
|
|
4388
4784
|
async (input, config) => {
|
|
4785
|
+
const { runConfig } = config.configurable;
|
|
4389
4786
|
const stateAndStore = {
|
|
4390
4787
|
state: getCurrentTaskInput(config),
|
|
4391
|
-
store: config.store
|
|
4788
|
+
store: config.store,
|
|
4789
|
+
...runConfig
|
|
4392
4790
|
};
|
|
4393
4791
|
const resolvedBackend = await getBackend(backend, stateAndStore);
|
|
4394
|
-
const { pattern, path:
|
|
4395
|
-
const infos = await resolvedBackend.globInfo(pattern,
|
|
4792
|
+
const { pattern, path: path5 = "/" } = input;
|
|
4793
|
+
const infos = await resolvedBackend.globInfo(pattern, path5);
|
|
4396
4794
|
if (infos.length === 0) {
|
|
4397
4795
|
return `No files found matching pattern '${pattern}'`;
|
|
4398
4796
|
}
|
|
@@ -4412,13 +4810,15 @@ function createGrepTool(backend, options) {
|
|
|
4412
4810
|
const { customDescription } = options;
|
|
4413
4811
|
return tool33(
|
|
4414
4812
|
async (input, config) => {
|
|
4813
|
+
const { runConfig } = config.configurable;
|
|
4415
4814
|
const stateAndStore = {
|
|
4416
4815
|
state: getCurrentTaskInput(config),
|
|
4417
|
-
store: config.store
|
|
4816
|
+
store: config.store,
|
|
4817
|
+
...runConfig
|
|
4418
4818
|
};
|
|
4419
4819
|
const resolvedBackend = await getBackend(backend, stateAndStore);
|
|
4420
|
-
const { pattern, path:
|
|
4421
|
-
const result = await resolvedBackend.grepRaw(pattern,
|
|
4820
|
+
const { pattern, path: path5 = "/", glob = null } = input;
|
|
4821
|
+
const result = await resolvedBackend.grepRaw(pattern, path5, glob);
|
|
4422
4822
|
if (typeof result === "string") {
|
|
4423
4823
|
return result;
|
|
4424
4824
|
}
|
|
@@ -4588,7 +4988,22 @@ function createCommonMiddlewares(middlewareConfigs, filesystemBackend) {
|
|
|
4588
4988
|
middlewares.push(createBrowserMiddleware(config.config));
|
|
4589
4989
|
break;
|
|
4590
4990
|
case "sql":
|
|
4591
|
-
|
|
4991
|
+
{
|
|
4992
|
+
const sqlConfig = config.config;
|
|
4993
|
+
if (sqlConfig.databaseKeys && sqlConfig.databaseKeys.length > 0) {
|
|
4994
|
+
const databaseConfigs = global.__DATABASE_CONFIGS__ || [];
|
|
4995
|
+
const descriptions = {};
|
|
4996
|
+
for (const db of databaseConfigs) {
|
|
4997
|
+
if (db.key && sqlConfig.databaseKeys.includes(db.key)) {
|
|
4998
|
+
descriptions[db.key] = db.description || db.name || "";
|
|
4999
|
+
}
|
|
5000
|
+
}
|
|
5001
|
+
middlewares.push(createSqlMiddleware({
|
|
5002
|
+
databaseKeys: sqlConfig.databaseKeys,
|
|
5003
|
+
databaseDescriptions: descriptions
|
|
5004
|
+
}));
|
|
5005
|
+
}
|
|
5006
|
+
}
|
|
4592
5007
|
break;
|
|
4593
5008
|
case "skill":
|
|
4594
5009
|
middlewares.push(createSkillMiddleware(config.config));
|
|
@@ -4607,6 +5022,7 @@ var ReActAgentGraphBuilder = class {
|
|
|
4607
5022
|
}
|
|
4608
5023
|
const isolatedLevel = filesystemConfig.config?.isolatedLevel || "global";
|
|
4609
5024
|
return async (config) => {
|
|
5025
|
+
const { workspaceId, projectId } = config;
|
|
4610
5026
|
let sandboxName = "global";
|
|
4611
5027
|
if (isolatedLevel === "agent") {
|
|
4612
5028
|
sandboxName = "agent";
|
|
@@ -4618,7 +5034,8 @@ var ReActAgentGraphBuilder = class {
|
|
|
4618
5034
|
throw new Error("Sandbox manager not found");
|
|
4619
5035
|
}
|
|
4620
5036
|
return new SandboxFilesystem({
|
|
4621
|
-
sandboxInstance: await sandboxManager.createSandbox(sandboxName)
|
|
5037
|
+
sandboxInstance: await sandboxManager.createSandbox(sandboxName),
|
|
5038
|
+
workingDirectory: workspaceId && projectId ? `/${workspaceId}/${projectId}` : "/"
|
|
4622
5039
|
});
|
|
4623
5040
|
};
|
|
4624
5041
|
}
|
|
@@ -4634,9 +5051,9 @@ var ReActAgentGraphBuilder = class {
|
|
|
4634
5051
|
*/
|
|
4635
5052
|
build(agentLattice, params) {
|
|
4636
5053
|
const tools = params.tools.map((t) => {
|
|
4637
|
-
const
|
|
4638
|
-
return
|
|
4639
|
-
}).filter((
|
|
5054
|
+
const tool38 = getToolClient(t.key);
|
|
5055
|
+
return tool38;
|
|
5056
|
+
}).filter((tool38) => tool38 !== void 0);
|
|
4640
5057
|
const stateSchema2 = createReactAgentSchema(params.stateSchema);
|
|
4641
5058
|
const middlewareConfigs = params.middleware || [];
|
|
4642
5059
|
const filesystemBackend = this.createFilesystemBackendFactory(middlewareConfigs);
|
|
@@ -4937,12 +5354,12 @@ var AgentManager = class _AgentManager {
|
|
|
4937
5354
|
return _AgentManager.instance;
|
|
4938
5355
|
}
|
|
4939
5356
|
callAgentInQueue(queue, return_agent_state) {
|
|
4940
|
-
return new Promise((
|
|
5357
|
+
return new Promise((resolve3, reject) => {
|
|
4941
5358
|
const callback_event = `${queue.assistant_id}::${queue.thread_id}`;
|
|
4942
5359
|
if (return_agent_state) {
|
|
4943
5360
|
event_bus_default.subscribeOnce(callback_event, (data) => {
|
|
4944
5361
|
if (data.success) {
|
|
4945
|
-
|
|
5362
|
+
resolve3(data.state);
|
|
4946
5363
|
} else {
|
|
4947
5364
|
reject(data.error);
|
|
4948
5365
|
}
|
|
@@ -4957,7 +5374,7 @@ var AgentManager = class _AgentManager {
|
|
|
4957
5374
|
},
|
|
4958
5375
|
true
|
|
4959
5376
|
);
|
|
4960
|
-
!return_agent_state &&
|
|
5377
|
+
!return_agent_state && resolve3({ callback_event_id: callback_event, success: true });
|
|
4961
5378
|
} catch (error) {
|
|
4962
5379
|
!return_agent_state && reject({
|
|
4963
5380
|
callback_event_id: callback_event,
|
|
@@ -5451,59 +5868,1144 @@ function createPatchToolCallsMiddleware() {
|
|
|
5451
5868
|
});
|
|
5452
5869
|
}
|
|
5453
5870
|
|
|
5871
|
+
// src/deep_agent_new/backends/store.ts
|
|
5872
|
+
var StoreBackend = class {
|
|
5873
|
+
constructor(stateAndStore) {
|
|
5874
|
+
this.stateAndStore = stateAndStore;
|
|
5875
|
+
}
|
|
5876
|
+
/**
|
|
5877
|
+
* Get the store instance.
|
|
5878
|
+
*
|
|
5879
|
+
* @returns BaseStore instance
|
|
5880
|
+
* @throws Error if no store is available
|
|
5881
|
+
*/
|
|
5882
|
+
getStore() {
|
|
5883
|
+
const store = this.stateAndStore.store;
|
|
5884
|
+
if (!store) {
|
|
5885
|
+
throw new Error("Store is required but not available in StateAndStore");
|
|
5886
|
+
}
|
|
5887
|
+
return store;
|
|
5888
|
+
}
|
|
5889
|
+
/**
|
|
5890
|
+
* Get the namespace for store operations.
|
|
5891
|
+
*
|
|
5892
|
+
* If an assistant_id is available in stateAndStore, return
|
|
5893
|
+
* [assistant_id, "filesystem"] to provide per-assistant isolation.
|
|
5894
|
+
* Otherwise return ["filesystem"].
|
|
5895
|
+
*/
|
|
5896
|
+
getNamespace() {
|
|
5897
|
+
const namespace = "filesystem";
|
|
5898
|
+
const assistantId = this.stateAndStore.assistantId;
|
|
5899
|
+
if (assistantId) {
|
|
5900
|
+
return [assistantId, namespace];
|
|
5901
|
+
}
|
|
5902
|
+
return [namespace];
|
|
5903
|
+
}
|
|
5904
|
+
/**
|
|
5905
|
+
* Convert a store Item to FileData format.
|
|
5906
|
+
*
|
|
5907
|
+
* @param storeItem - The store Item containing file data
|
|
5908
|
+
* @returns FileData object
|
|
5909
|
+
* @throws Error if required fields are missing or have incorrect types
|
|
5910
|
+
*/
|
|
5911
|
+
convertStoreItemToFileData(storeItem) {
|
|
5912
|
+
const value = storeItem.value;
|
|
5913
|
+
if (!value.content || !Array.isArray(value.content) || typeof value.created_at !== "string" || typeof value.modified_at !== "string") {
|
|
5914
|
+
throw new Error(
|
|
5915
|
+
`Store item does not contain valid FileData fields. Got keys: ${Object.keys(value).join(", ")}`
|
|
5916
|
+
);
|
|
5917
|
+
}
|
|
5918
|
+
return {
|
|
5919
|
+
content: value.content,
|
|
5920
|
+
created_at: value.created_at,
|
|
5921
|
+
modified_at: value.modified_at
|
|
5922
|
+
};
|
|
5923
|
+
}
|
|
5924
|
+
/**
|
|
5925
|
+
* Convert FileData to a value suitable for store.put().
|
|
5926
|
+
*
|
|
5927
|
+
* @param fileData - The FileData to convert
|
|
5928
|
+
* @returns Object with content, created_at, and modified_at fields
|
|
5929
|
+
*/
|
|
5930
|
+
convertFileDataToStoreValue(fileData) {
|
|
5931
|
+
return {
|
|
5932
|
+
content: fileData.content,
|
|
5933
|
+
created_at: fileData.created_at,
|
|
5934
|
+
modified_at: fileData.modified_at
|
|
5935
|
+
};
|
|
5936
|
+
}
|
|
5937
|
+
/**
|
|
5938
|
+
* Search store with automatic pagination to retrieve all results.
|
|
5939
|
+
*
|
|
5940
|
+
* @param store - The store to search
|
|
5941
|
+
* @param namespace - Hierarchical path prefix to search within
|
|
5942
|
+
* @param options - Optional query, filter, and page_size
|
|
5943
|
+
* @returns List of all items matching the search criteria
|
|
5944
|
+
*/
|
|
5945
|
+
async searchStorePaginated(store, namespace, options = {}) {
|
|
5946
|
+
const { query, filter, pageSize = 100 } = options;
|
|
5947
|
+
const allItems = [];
|
|
5948
|
+
let offset = 0;
|
|
5949
|
+
while (true) {
|
|
5950
|
+
const pageItems = await store.search(namespace, {
|
|
5951
|
+
query,
|
|
5952
|
+
filter,
|
|
5953
|
+
limit: pageSize,
|
|
5954
|
+
offset
|
|
5955
|
+
});
|
|
5956
|
+
if (!pageItems || pageItems.length === 0) {
|
|
5957
|
+
break;
|
|
5958
|
+
}
|
|
5959
|
+
allItems.push(...pageItems);
|
|
5960
|
+
if (pageItems.length < pageSize) {
|
|
5961
|
+
break;
|
|
5962
|
+
}
|
|
5963
|
+
offset += pageSize;
|
|
5964
|
+
}
|
|
5965
|
+
return allItems;
|
|
5966
|
+
}
|
|
5967
|
+
/**
|
|
5968
|
+
* List files and directories in the specified directory (non-recursive).
|
|
5969
|
+
*
|
|
5970
|
+
* @param path - Absolute path to directory
|
|
5971
|
+
* @returns List of FileInfo objects for files and directories directly in the directory.
|
|
5972
|
+
* Directories have a trailing / in their path and is_dir=true.
|
|
5973
|
+
*/
|
|
5974
|
+
async lsInfo(path5) {
|
|
5975
|
+
const store = this.getStore();
|
|
5976
|
+
const namespace = this.getNamespace();
|
|
5977
|
+
const items = await this.searchStorePaginated(store, namespace);
|
|
5978
|
+
const infos = [];
|
|
5979
|
+
const subdirs = /* @__PURE__ */ new Set();
|
|
5980
|
+
const normalizedPath = path5.endsWith("/") ? path5 : path5 + "/";
|
|
5981
|
+
for (const item of items) {
|
|
5982
|
+
const itemKey = String(item.key);
|
|
5983
|
+
if (!itemKey.startsWith(normalizedPath)) {
|
|
5984
|
+
continue;
|
|
5985
|
+
}
|
|
5986
|
+
const relative3 = itemKey.substring(normalizedPath.length);
|
|
5987
|
+
if (relative3.includes("/")) {
|
|
5988
|
+
const subdirName = relative3.split("/")[0];
|
|
5989
|
+
subdirs.add(normalizedPath + subdirName + "/");
|
|
5990
|
+
continue;
|
|
5991
|
+
}
|
|
5992
|
+
try {
|
|
5993
|
+
const fd = this.convertStoreItemToFileData(item);
|
|
5994
|
+
const size = fd.content.join("\n").length;
|
|
5995
|
+
infos.push({
|
|
5996
|
+
path: itemKey,
|
|
5997
|
+
is_dir: false,
|
|
5998
|
+
size,
|
|
5999
|
+
modified_at: fd.modified_at
|
|
6000
|
+
});
|
|
6001
|
+
} catch {
|
|
6002
|
+
continue;
|
|
6003
|
+
}
|
|
6004
|
+
}
|
|
6005
|
+
for (const subdir of Array.from(subdirs).sort()) {
|
|
6006
|
+
infos.push({
|
|
6007
|
+
path: subdir,
|
|
6008
|
+
is_dir: true,
|
|
6009
|
+
size: 0,
|
|
6010
|
+
modified_at: ""
|
|
6011
|
+
});
|
|
6012
|
+
}
|
|
6013
|
+
infos.sort((a, b) => a.path.localeCompare(b.path));
|
|
6014
|
+
return infos;
|
|
6015
|
+
}
|
|
6016
|
+
/**
|
|
6017
|
+
* Read file content with line numbers.
|
|
6018
|
+
*
|
|
6019
|
+
* @param filePath - Absolute file path
|
|
6020
|
+
* @param offset - Line offset to start reading from (0-indexed)
|
|
6021
|
+
* @param limit - Maximum number of lines to read
|
|
6022
|
+
* @returns Formatted file content with line numbers, or error message
|
|
6023
|
+
*/
|
|
6024
|
+
async read(filePath, offset = 0, limit = 2e3) {
|
|
6025
|
+
try {
|
|
6026
|
+
const fileData = await this.readRaw(filePath);
|
|
6027
|
+
return formatReadResponse(fileData, offset, limit);
|
|
6028
|
+
} catch (e) {
|
|
6029
|
+
return `Error: ${e.message}`;
|
|
6030
|
+
}
|
|
6031
|
+
}
|
|
6032
|
+
/**
|
|
6033
|
+
* Read file content as raw FileData.
|
|
6034
|
+
*
|
|
6035
|
+
* @param filePath - Absolute file path
|
|
6036
|
+
* @returns Raw file content as FileData
|
|
6037
|
+
*/
|
|
6038
|
+
async readRaw(filePath) {
|
|
6039
|
+
const store = this.getStore();
|
|
6040
|
+
const namespace = this.getNamespace();
|
|
6041
|
+
const item = await store.get(namespace, filePath);
|
|
6042
|
+
if (!item) throw new Error(`File '${filePath}' not found`);
|
|
6043
|
+
return this.convertStoreItemToFileData(item);
|
|
6044
|
+
}
|
|
6045
|
+
/**
|
|
6046
|
+
* Create a new file with content.
|
|
6047
|
+
* Returns WriteResult. External storage sets filesUpdate=null.
|
|
6048
|
+
*/
|
|
6049
|
+
async write(filePath, content) {
|
|
6050
|
+
const store = this.getStore();
|
|
6051
|
+
const namespace = this.getNamespace();
|
|
6052
|
+
const existing = await store.get(namespace, filePath);
|
|
6053
|
+
if (existing) {
|
|
6054
|
+
return {
|
|
6055
|
+
error: `Cannot write to ${filePath} because it already exists. Read and then make an edit, or write to a new path.`
|
|
6056
|
+
};
|
|
6057
|
+
}
|
|
6058
|
+
const fileData = createFileData(content);
|
|
6059
|
+
const storeValue = this.convertFileDataToStoreValue(fileData);
|
|
6060
|
+
await store.put(namespace, filePath, storeValue);
|
|
6061
|
+
return { path: filePath, filesUpdate: null };
|
|
6062
|
+
}
|
|
6063
|
+
/**
|
|
6064
|
+
* Edit a file by replacing string occurrences.
|
|
6065
|
+
* Returns EditResult. External storage sets filesUpdate=null.
|
|
6066
|
+
*/
|
|
6067
|
+
async edit(filePath, oldString, newString, replaceAll = false) {
|
|
6068
|
+
const store = this.getStore();
|
|
6069
|
+
const namespace = this.getNamespace();
|
|
6070
|
+
const item = await store.get(namespace, filePath);
|
|
6071
|
+
if (!item) {
|
|
6072
|
+
return { error: `Error: File '${filePath}' not found` };
|
|
6073
|
+
}
|
|
6074
|
+
try {
|
|
6075
|
+
const fileData = this.convertStoreItemToFileData(item);
|
|
6076
|
+
const content = fileDataToString(fileData);
|
|
6077
|
+
const result = performStringReplacement(
|
|
6078
|
+
content,
|
|
6079
|
+
oldString,
|
|
6080
|
+
newString,
|
|
6081
|
+
replaceAll
|
|
6082
|
+
);
|
|
6083
|
+
if (typeof result === "string") {
|
|
6084
|
+
return { error: result };
|
|
6085
|
+
}
|
|
6086
|
+
const [newContent, occurrences] = result;
|
|
6087
|
+
const newFileData = updateFileData(fileData, newContent);
|
|
6088
|
+
const storeValue = this.convertFileDataToStoreValue(newFileData);
|
|
6089
|
+
await store.put(namespace, filePath, storeValue);
|
|
6090
|
+
return { path: filePath, filesUpdate: null, occurrences };
|
|
6091
|
+
} catch (e) {
|
|
6092
|
+
return { error: `Error: ${e.message}` };
|
|
6093
|
+
}
|
|
6094
|
+
}
|
|
6095
|
+
/**
|
|
6096
|
+
* Structured search results or error string for invalid input.
|
|
6097
|
+
*/
|
|
6098
|
+
async grepRaw(pattern, path5 = "/", glob = null) {
|
|
6099
|
+
const store = this.getStore();
|
|
6100
|
+
const namespace = this.getNamespace();
|
|
6101
|
+
const items = await this.searchStorePaginated(store, namespace);
|
|
6102
|
+
const files = {};
|
|
6103
|
+
for (const item of items) {
|
|
6104
|
+
try {
|
|
6105
|
+
files[item.key] = this.convertStoreItemToFileData(item);
|
|
6106
|
+
} catch {
|
|
6107
|
+
continue;
|
|
6108
|
+
}
|
|
6109
|
+
}
|
|
6110
|
+
return grepMatchesFromFiles(files, pattern, path5, glob);
|
|
6111
|
+
}
|
|
6112
|
+
/**
|
|
6113
|
+
* Structured glob matching returning FileInfo objects.
|
|
6114
|
+
*/
|
|
6115
|
+
async globInfo(pattern, path5 = "/") {
|
|
6116
|
+
const store = this.getStore();
|
|
6117
|
+
const namespace = this.getNamespace();
|
|
6118
|
+
const items = await this.searchStorePaginated(store, namespace);
|
|
6119
|
+
const files = {};
|
|
6120
|
+
for (const item of items) {
|
|
6121
|
+
try {
|
|
6122
|
+
files[item.key] = this.convertStoreItemToFileData(item);
|
|
6123
|
+
} catch {
|
|
6124
|
+
continue;
|
|
6125
|
+
}
|
|
6126
|
+
}
|
|
6127
|
+
const result = globSearchFiles(files, pattern, path5);
|
|
6128
|
+
if (result === "No files found") {
|
|
6129
|
+
return [];
|
|
6130
|
+
}
|
|
6131
|
+
const paths = result.split("\n");
|
|
6132
|
+
const infos = [];
|
|
6133
|
+
for (const p of paths) {
|
|
6134
|
+
const fd = files[p];
|
|
6135
|
+
const size = fd ? fd.content.join("\n").length : 0;
|
|
6136
|
+
infos.push({
|
|
6137
|
+
path: p,
|
|
6138
|
+
is_dir: false,
|
|
6139
|
+
size,
|
|
6140
|
+
modified_at: fd?.modified_at || ""
|
|
6141
|
+
});
|
|
6142
|
+
}
|
|
6143
|
+
return infos;
|
|
6144
|
+
}
|
|
6145
|
+
};
|
|
6146
|
+
|
|
5454
6147
|
// src/deep_agent_new/backends/filesystem.ts
|
|
6148
|
+
import * as fs2 from "fs/promises";
|
|
5455
6149
|
import * as fsSync from "fs";
|
|
6150
|
+
import * as path4 from "path";
|
|
6151
|
+
import { spawn } from "child_process";
|
|
5456
6152
|
import fg from "fast-glob";
|
|
5457
6153
|
import micromatch2 from "micromatch";
|
|
5458
6154
|
var SUPPORTS_NOFOLLOW = fsSync.constants.O_NOFOLLOW !== void 0;
|
|
5459
|
-
|
|
5460
|
-
|
|
5461
|
-
|
|
5462
|
-
|
|
5463
|
-
|
|
5464
|
-
|
|
5465
|
-
|
|
5466
|
-
|
|
5467
|
-
|
|
5468
|
-
|
|
5469
|
-
|
|
5470
|
-
|
|
5471
|
-
|
|
5472
|
-
|
|
5473
|
-
|
|
5474
|
-
|
|
5475
|
-
|
|
5476
|
-
|
|
5477
|
-
|
|
5478
|
-
|
|
5479
|
-
|
|
5480
|
-
|
|
5481
|
-
|
|
5482
|
-
|
|
5483
|
-
|
|
5484
|
-
|
|
5485
|
-
|
|
5486
|
-
|
|
5487
|
-
|
|
5488
|
-
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
|
|
5492
|
-
|
|
5493
|
-
|
|
5494
|
-
|
|
5495
|
-
|
|
5496
|
-
|
|
5497
|
-
|
|
5498
|
-
|
|
5499
|
-
|
|
5500
|
-
|
|
5501
|
-
*
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
|
|
6155
|
+
var FilesystemBackend = class {
|
|
6156
|
+
constructor(options = {}) {
|
|
6157
|
+
const { rootDir, virtualMode = false, maxFileSizeMb = 10 } = options;
|
|
6158
|
+
this.cwd = rootDir ? path4.resolve(rootDir) : process.cwd();
|
|
6159
|
+
this.virtualMode = virtualMode;
|
|
6160
|
+
this.maxFileSizeBytes = maxFileSizeMb * 1024 * 1024;
|
|
6161
|
+
}
|
|
6162
|
+
/**
|
|
6163
|
+
* Resolve a file path with security checks.
|
|
6164
|
+
*
|
|
6165
|
+
* When virtualMode=true, treat incoming paths as virtual absolute paths under
|
|
6166
|
+
* this.cwd, disallow traversal (.., ~) and ensure resolved path stays within root.
|
|
6167
|
+
* When virtualMode=false, preserve legacy behavior: absolute paths are allowed
|
|
6168
|
+
* as-is; relative paths resolve under cwd.
|
|
6169
|
+
*
|
|
6170
|
+
* @param key - File path (absolute, relative, or virtual when virtualMode=true)
|
|
6171
|
+
* @returns Resolved absolute path string
|
|
6172
|
+
* @throws Error if path traversal detected or path outside root
|
|
6173
|
+
*/
|
|
6174
|
+
resolvePath(key) {
|
|
6175
|
+
if (this.virtualMode) {
|
|
6176
|
+
const vpath = key.startsWith("/") ? key : "/" + key;
|
|
6177
|
+
if (vpath.includes("..") || vpath.startsWith("~")) {
|
|
6178
|
+
throw new Error("Path traversal not allowed");
|
|
6179
|
+
}
|
|
6180
|
+
const full = path4.resolve(this.cwd, vpath.substring(1));
|
|
6181
|
+
const relative3 = path4.relative(this.cwd, full);
|
|
6182
|
+
if (relative3.startsWith("..") || path4.isAbsolute(relative3)) {
|
|
6183
|
+
throw new Error(`Path: ${full} outside root directory: ${this.cwd}`);
|
|
6184
|
+
}
|
|
6185
|
+
return full;
|
|
6186
|
+
}
|
|
6187
|
+
if (path4.isAbsolute(key)) {
|
|
6188
|
+
return key;
|
|
6189
|
+
}
|
|
6190
|
+
return path4.resolve(this.cwd, key);
|
|
6191
|
+
}
|
|
6192
|
+
/**
|
|
6193
|
+
* List files and directories in the specified directory (non-recursive).
|
|
6194
|
+
*
|
|
6195
|
+
* @param dirPath - Absolute directory path to list files from
|
|
6196
|
+
* @returns List of FileInfo objects for files and directories directly in the directory.
|
|
6197
|
+
* Directories have a trailing / in their path and is_dir=true.
|
|
6198
|
+
*/
|
|
6199
|
+
async lsInfo(dirPath) {
|
|
6200
|
+
try {
|
|
6201
|
+
const resolvedPath = this.resolvePath(dirPath);
|
|
6202
|
+
const stat3 = await fs2.stat(resolvedPath);
|
|
6203
|
+
if (!stat3.isDirectory()) {
|
|
6204
|
+
return [];
|
|
6205
|
+
}
|
|
6206
|
+
const entries = await fs2.readdir(resolvedPath, { withFileTypes: true });
|
|
6207
|
+
const results = [];
|
|
6208
|
+
const cwdStr = this.cwd.endsWith(path4.sep) ? this.cwd : this.cwd + path4.sep;
|
|
6209
|
+
for (const entry of entries) {
|
|
6210
|
+
const fullPath = path4.join(resolvedPath, entry.name);
|
|
6211
|
+
try {
|
|
6212
|
+
const entryStat = await fs2.stat(fullPath);
|
|
6213
|
+
const isFile = entryStat.isFile();
|
|
6214
|
+
const isDir = entryStat.isDirectory();
|
|
6215
|
+
if (!this.virtualMode) {
|
|
6216
|
+
if (isFile) {
|
|
6217
|
+
results.push({
|
|
6218
|
+
path: fullPath,
|
|
6219
|
+
is_dir: false,
|
|
6220
|
+
size: entryStat.size,
|
|
6221
|
+
modified_at: entryStat.mtime.toISOString()
|
|
6222
|
+
});
|
|
6223
|
+
} else if (isDir) {
|
|
6224
|
+
results.push({
|
|
6225
|
+
path: fullPath + path4.sep,
|
|
6226
|
+
is_dir: true,
|
|
6227
|
+
size: 0,
|
|
6228
|
+
modified_at: entryStat.mtime.toISOString()
|
|
6229
|
+
});
|
|
6230
|
+
}
|
|
6231
|
+
} else {
|
|
6232
|
+
let relativePath;
|
|
6233
|
+
if (fullPath.startsWith(cwdStr)) {
|
|
6234
|
+
relativePath = fullPath.substring(cwdStr.length);
|
|
6235
|
+
} else if (fullPath.startsWith(this.cwd)) {
|
|
6236
|
+
relativePath = fullPath.substring(this.cwd.length).replace(/^[/\\]/, "");
|
|
6237
|
+
} else {
|
|
6238
|
+
relativePath = fullPath;
|
|
6239
|
+
}
|
|
6240
|
+
relativePath = relativePath.split(path4.sep).join("/");
|
|
6241
|
+
const virtPath = "/" + relativePath;
|
|
6242
|
+
if (isFile) {
|
|
6243
|
+
results.push({
|
|
6244
|
+
path: virtPath,
|
|
6245
|
+
is_dir: false,
|
|
6246
|
+
size: entryStat.size,
|
|
6247
|
+
modified_at: entryStat.mtime.toISOString()
|
|
6248
|
+
});
|
|
6249
|
+
} else if (isDir) {
|
|
6250
|
+
results.push({
|
|
6251
|
+
path: virtPath + "/",
|
|
6252
|
+
is_dir: true,
|
|
6253
|
+
size: 0,
|
|
6254
|
+
modified_at: entryStat.mtime.toISOString()
|
|
6255
|
+
});
|
|
6256
|
+
}
|
|
6257
|
+
}
|
|
6258
|
+
} catch {
|
|
6259
|
+
continue;
|
|
6260
|
+
}
|
|
6261
|
+
}
|
|
6262
|
+
results.sort((a, b) => a.path.localeCompare(b.path));
|
|
6263
|
+
return results;
|
|
6264
|
+
} catch (e) {
|
|
6265
|
+
console.error(`Error listing files in ${dirPath}:`, e);
|
|
6266
|
+
return [];
|
|
6267
|
+
}
|
|
6268
|
+
}
|
|
6269
|
+
/**
|
|
6270
|
+
* Read file content with line numbers.
|
|
6271
|
+
*
|
|
6272
|
+
* @param filePath - Absolute or relative file path
|
|
6273
|
+
* @param offset - Line offset to start reading from (0-indexed)
|
|
6274
|
+
* @param limit - Maximum number of lines to read
|
|
6275
|
+
* @returns Formatted file content with line numbers, or error message
|
|
6276
|
+
*/
|
|
6277
|
+
async read(filePath, offset = 0, limit = 2e3) {
|
|
6278
|
+
try {
|
|
6279
|
+
const resolvedPath = this.resolvePath(filePath);
|
|
6280
|
+
let content;
|
|
6281
|
+
if (SUPPORTS_NOFOLLOW) {
|
|
6282
|
+
const stat3 = await fs2.stat(resolvedPath);
|
|
6283
|
+
if (!stat3.isFile()) {
|
|
6284
|
+
return `Error: File '${filePath}' not found`;
|
|
6285
|
+
}
|
|
6286
|
+
const fd = await fs2.open(
|
|
6287
|
+
resolvedPath,
|
|
6288
|
+
fsSync.constants.O_RDONLY | fsSync.constants.O_NOFOLLOW
|
|
6289
|
+
);
|
|
6290
|
+
try {
|
|
6291
|
+
content = await fd.readFile({ encoding: "utf-8" });
|
|
6292
|
+
} finally {
|
|
6293
|
+
await fd.close();
|
|
6294
|
+
}
|
|
6295
|
+
} else {
|
|
6296
|
+
const stat3 = await fs2.lstat(resolvedPath);
|
|
6297
|
+
if (stat3.isSymbolicLink()) {
|
|
6298
|
+
return `Error: Symlinks are not allowed: ${filePath}`;
|
|
6299
|
+
}
|
|
6300
|
+
if (!stat3.isFile()) {
|
|
6301
|
+
return `Error: File '${filePath}' not found`;
|
|
6302
|
+
}
|
|
6303
|
+
content = await fs2.readFile(resolvedPath, "utf-8");
|
|
6304
|
+
}
|
|
6305
|
+
const emptyMsg = checkEmptyContent(content);
|
|
6306
|
+
if (emptyMsg) {
|
|
6307
|
+
return emptyMsg;
|
|
6308
|
+
}
|
|
6309
|
+
const lines = content.split("\n");
|
|
6310
|
+
const startIdx = offset;
|
|
6311
|
+
const endIdx = Math.min(startIdx + limit, lines.length);
|
|
6312
|
+
if (startIdx >= lines.length) {
|
|
6313
|
+
return `Error: Line offset ${offset} exceeds file length (${lines.length} lines)`;
|
|
6314
|
+
}
|
|
6315
|
+
const selectedLines = lines.slice(startIdx, endIdx);
|
|
6316
|
+
return formatContentWithLineNumbers(selectedLines, startIdx + 1);
|
|
6317
|
+
} catch (e) {
|
|
6318
|
+
return `Error reading file '${filePath}': ${e.message}`;
|
|
6319
|
+
}
|
|
6320
|
+
}
|
|
6321
|
+
/**
|
|
6322
|
+
* Read file content as raw FileData.
|
|
6323
|
+
*
|
|
6324
|
+
* @param filePath - Absolute file path
|
|
6325
|
+
* @returns Raw file content as FileData
|
|
6326
|
+
*/
|
|
6327
|
+
async readRaw(filePath) {
|
|
6328
|
+
const resolvedPath = this.resolvePath(filePath);
|
|
6329
|
+
let content;
|
|
6330
|
+
let stat3;
|
|
6331
|
+
if (SUPPORTS_NOFOLLOW) {
|
|
6332
|
+
stat3 = await fs2.stat(resolvedPath);
|
|
6333
|
+
if (!stat3.isFile()) throw new Error(`File '${filePath}' not found`);
|
|
6334
|
+
const fd = await fs2.open(
|
|
6335
|
+
resolvedPath,
|
|
6336
|
+
fsSync.constants.O_RDONLY | fsSync.constants.O_NOFOLLOW
|
|
6337
|
+
);
|
|
6338
|
+
try {
|
|
6339
|
+
content = await fd.readFile({ encoding: "utf-8" });
|
|
6340
|
+
} finally {
|
|
6341
|
+
await fd.close();
|
|
6342
|
+
}
|
|
6343
|
+
} else {
|
|
6344
|
+
stat3 = await fs2.lstat(resolvedPath);
|
|
6345
|
+
if (stat3.isSymbolicLink()) {
|
|
6346
|
+
throw new Error(`Symlinks are not allowed: ${filePath}`);
|
|
6347
|
+
}
|
|
6348
|
+
if (!stat3.isFile()) throw new Error(`File '${filePath}' not found`);
|
|
6349
|
+
content = await fs2.readFile(resolvedPath, "utf-8");
|
|
6350
|
+
}
|
|
6351
|
+
return {
|
|
6352
|
+
content: content.split("\n"),
|
|
6353
|
+
created_at: stat3.ctime.toISOString(),
|
|
6354
|
+
modified_at: stat3.mtime.toISOString()
|
|
6355
|
+
};
|
|
6356
|
+
}
|
|
6357
|
+
/**
|
|
6358
|
+
* Create a new file with content.
|
|
6359
|
+
* Returns WriteResult. External storage sets filesUpdate=null.
|
|
6360
|
+
*/
|
|
6361
|
+
async write(filePath, content) {
|
|
6362
|
+
try {
|
|
6363
|
+
const resolvedPath = this.resolvePath(filePath);
|
|
6364
|
+
try {
|
|
6365
|
+
const stat3 = await fs2.lstat(resolvedPath);
|
|
6366
|
+
if (stat3.isSymbolicLink()) {
|
|
6367
|
+
return {
|
|
6368
|
+
error: `Cannot write to ${filePath} because it is a symlink. Symlinks are not allowed.`
|
|
6369
|
+
};
|
|
6370
|
+
}
|
|
6371
|
+
return {
|
|
6372
|
+
error: `Cannot write to ${filePath} because it already exists. Read and then make an edit, or write to a new path.`
|
|
6373
|
+
};
|
|
6374
|
+
} catch {
|
|
6375
|
+
}
|
|
6376
|
+
await fs2.mkdir(path4.dirname(resolvedPath), { recursive: true });
|
|
6377
|
+
if (SUPPORTS_NOFOLLOW) {
|
|
6378
|
+
const flags = fsSync.constants.O_WRONLY | fsSync.constants.O_CREAT | fsSync.constants.O_TRUNC | fsSync.constants.O_NOFOLLOW;
|
|
6379
|
+
const fd = await fs2.open(resolvedPath, flags, 420);
|
|
6380
|
+
try {
|
|
6381
|
+
await fd.writeFile(content, "utf-8");
|
|
6382
|
+
} finally {
|
|
6383
|
+
await fd.close();
|
|
6384
|
+
}
|
|
6385
|
+
} else {
|
|
6386
|
+
await fs2.writeFile(resolvedPath, content, "utf-8");
|
|
6387
|
+
}
|
|
6388
|
+
return { path: filePath, filesUpdate: null };
|
|
6389
|
+
} catch (e) {
|
|
6390
|
+
return { error: `Error writing file '${filePath}': ${e.message}` };
|
|
6391
|
+
}
|
|
6392
|
+
}
|
|
6393
|
+
/**
|
|
6394
|
+
* Edit a file by replacing string occurrences.
|
|
6395
|
+
* Returns EditResult. External storage sets filesUpdate=null.
|
|
6396
|
+
*/
|
|
6397
|
+
async edit(filePath, oldString, newString, replaceAll = false) {
|
|
6398
|
+
try {
|
|
6399
|
+
const resolvedPath = this.resolvePath(filePath);
|
|
6400
|
+
let content;
|
|
6401
|
+
if (SUPPORTS_NOFOLLOW) {
|
|
6402
|
+
const stat3 = await fs2.stat(resolvedPath);
|
|
6403
|
+
if (!stat3.isFile()) {
|
|
6404
|
+
return { error: `Error: File '${filePath}' not found` };
|
|
6405
|
+
}
|
|
6406
|
+
const fd = await fs2.open(
|
|
6407
|
+
resolvedPath,
|
|
6408
|
+
fsSync.constants.O_RDONLY | fsSync.constants.O_NOFOLLOW
|
|
6409
|
+
);
|
|
6410
|
+
try {
|
|
6411
|
+
content = await fd.readFile({ encoding: "utf-8" });
|
|
6412
|
+
} finally {
|
|
6413
|
+
await fd.close();
|
|
6414
|
+
}
|
|
6415
|
+
} else {
|
|
6416
|
+
const stat3 = await fs2.lstat(resolvedPath);
|
|
6417
|
+
if (stat3.isSymbolicLink()) {
|
|
6418
|
+
return { error: `Error: Symlinks are not allowed: ${filePath}` };
|
|
6419
|
+
}
|
|
6420
|
+
if (!stat3.isFile()) {
|
|
6421
|
+
return { error: `Error: File '${filePath}' not found` };
|
|
6422
|
+
}
|
|
6423
|
+
content = await fs2.readFile(resolvedPath, "utf-8");
|
|
6424
|
+
}
|
|
6425
|
+
const result = performStringReplacement(
|
|
6426
|
+
content,
|
|
6427
|
+
oldString,
|
|
6428
|
+
newString,
|
|
6429
|
+
replaceAll
|
|
6430
|
+
);
|
|
6431
|
+
if (typeof result === "string") {
|
|
6432
|
+
return { error: result };
|
|
6433
|
+
}
|
|
6434
|
+
const [newContent, occurrences] = result;
|
|
6435
|
+
if (SUPPORTS_NOFOLLOW) {
|
|
6436
|
+
const flags = fsSync.constants.O_WRONLY | fsSync.constants.O_TRUNC | fsSync.constants.O_NOFOLLOW;
|
|
6437
|
+
const fd = await fs2.open(resolvedPath, flags);
|
|
6438
|
+
try {
|
|
6439
|
+
await fd.writeFile(newContent, "utf-8");
|
|
6440
|
+
} finally {
|
|
6441
|
+
await fd.close();
|
|
6442
|
+
}
|
|
6443
|
+
} else {
|
|
6444
|
+
await fs2.writeFile(resolvedPath, newContent, "utf-8");
|
|
6445
|
+
}
|
|
6446
|
+
return { path: filePath, filesUpdate: null, occurrences };
|
|
6447
|
+
} catch (e) {
|
|
6448
|
+
return { error: `Error editing file '${filePath}': ${e.message}` };
|
|
6449
|
+
}
|
|
6450
|
+
}
|
|
6451
|
+
/**
|
|
6452
|
+
* Structured search results or error string for invalid input.
|
|
6453
|
+
*/
|
|
6454
|
+
async grepRaw(pattern, dirPath = "/", glob = null) {
|
|
6455
|
+
try {
|
|
6456
|
+
new RegExp(pattern);
|
|
6457
|
+
} catch (e) {
|
|
6458
|
+
return `Invalid regex pattern: ${e.message}`;
|
|
6459
|
+
}
|
|
6460
|
+
let baseFull;
|
|
6461
|
+
try {
|
|
6462
|
+
baseFull = this.resolvePath(dirPath || ".");
|
|
6463
|
+
} catch {
|
|
6464
|
+
return [];
|
|
6465
|
+
}
|
|
6466
|
+
try {
|
|
6467
|
+
await fs2.stat(baseFull);
|
|
6468
|
+
} catch {
|
|
6469
|
+
return [];
|
|
6470
|
+
}
|
|
6471
|
+
let results = await this.ripgrepSearch(pattern, baseFull, glob);
|
|
6472
|
+
if (results === null) {
|
|
6473
|
+
results = await this.pythonSearch(pattern, baseFull, glob);
|
|
6474
|
+
}
|
|
6475
|
+
const matches = [];
|
|
6476
|
+
for (const [fpath, items] of Object.entries(results)) {
|
|
6477
|
+
for (const [lineNum, lineText] of items) {
|
|
6478
|
+
matches.push({ path: fpath, line: lineNum, text: lineText });
|
|
6479
|
+
}
|
|
6480
|
+
}
|
|
6481
|
+
return matches;
|
|
6482
|
+
}
|
|
6483
|
+
/**
|
|
6484
|
+
* Try to use ripgrep for fast searching.
|
|
6485
|
+
* Returns null if ripgrep is not available or fails.
|
|
6486
|
+
*/
|
|
6487
|
+
async ripgrepSearch(pattern, baseFull, includeGlob) {
|
|
6488
|
+
return new Promise((resolve3) => {
|
|
6489
|
+
const args = ["--json"];
|
|
6490
|
+
if (includeGlob) {
|
|
6491
|
+
args.push("--glob", includeGlob);
|
|
6492
|
+
}
|
|
6493
|
+
args.push("--", pattern, baseFull);
|
|
6494
|
+
const proc = spawn("rg", args, { timeout: 3e4 });
|
|
6495
|
+
const results = {};
|
|
6496
|
+
let output = "";
|
|
6497
|
+
proc.stdout.on("data", (data) => {
|
|
6498
|
+
output += data.toString();
|
|
6499
|
+
});
|
|
6500
|
+
proc.on("close", (code) => {
|
|
6501
|
+
if (code !== 0 && code !== 1) {
|
|
6502
|
+
resolve3(null);
|
|
6503
|
+
return;
|
|
6504
|
+
}
|
|
6505
|
+
for (const line of output.split("\n")) {
|
|
6506
|
+
if (!line.trim()) continue;
|
|
6507
|
+
try {
|
|
6508
|
+
const data = JSON.parse(line);
|
|
6509
|
+
if (data.type !== "match") continue;
|
|
6510
|
+
const pdata = data.data || {};
|
|
6511
|
+
const ftext = pdata.path?.text;
|
|
6512
|
+
if (!ftext) continue;
|
|
6513
|
+
let virtPath;
|
|
6514
|
+
if (this.virtualMode) {
|
|
6515
|
+
try {
|
|
6516
|
+
const resolved = path4.resolve(ftext);
|
|
6517
|
+
const relative3 = path4.relative(this.cwd, resolved);
|
|
6518
|
+
if (relative3.startsWith("..")) continue;
|
|
6519
|
+
const normalizedRelative = relative3.split(path4.sep).join("/");
|
|
6520
|
+
virtPath = "/" + normalizedRelative;
|
|
6521
|
+
} catch {
|
|
6522
|
+
continue;
|
|
6523
|
+
}
|
|
6524
|
+
} else {
|
|
6525
|
+
virtPath = ftext;
|
|
6526
|
+
}
|
|
6527
|
+
const ln = pdata.line_number;
|
|
6528
|
+
const lt = pdata.lines?.text?.replace(/\n$/, "") || "";
|
|
6529
|
+
if (ln === void 0) continue;
|
|
6530
|
+
if (!results[virtPath]) {
|
|
6531
|
+
results[virtPath] = [];
|
|
6532
|
+
}
|
|
6533
|
+
results[virtPath].push([ln, lt]);
|
|
6534
|
+
} catch {
|
|
6535
|
+
continue;
|
|
6536
|
+
}
|
|
6537
|
+
}
|
|
6538
|
+
resolve3(results);
|
|
6539
|
+
});
|
|
6540
|
+
proc.on("error", () => {
|
|
6541
|
+
resolve3(null);
|
|
6542
|
+
});
|
|
6543
|
+
});
|
|
6544
|
+
}
|
|
6545
|
+
/**
|
|
6546
|
+
* Fallback regex search implementation.
|
|
6547
|
+
*/
|
|
6548
|
+
async pythonSearch(pattern, baseFull, includeGlob) {
|
|
6549
|
+
let regex;
|
|
6550
|
+
try {
|
|
6551
|
+
regex = new RegExp(pattern);
|
|
6552
|
+
} catch {
|
|
6553
|
+
return {};
|
|
6554
|
+
}
|
|
6555
|
+
const results = {};
|
|
6556
|
+
const stat3 = await fs2.stat(baseFull);
|
|
6557
|
+
const root = stat3.isDirectory() ? baseFull : path4.dirname(baseFull);
|
|
6558
|
+
const files = await fg("**/*", {
|
|
6559
|
+
cwd: root,
|
|
6560
|
+
absolute: true,
|
|
6561
|
+
onlyFiles: true,
|
|
6562
|
+
dot: true
|
|
6563
|
+
});
|
|
6564
|
+
for (const fp of files) {
|
|
6565
|
+
try {
|
|
6566
|
+
if (includeGlob && !micromatch2.isMatch(path4.basename(fp), includeGlob)) {
|
|
6567
|
+
continue;
|
|
6568
|
+
}
|
|
6569
|
+
const stat4 = await fs2.stat(fp);
|
|
6570
|
+
if (stat4.size > this.maxFileSizeBytes) {
|
|
6571
|
+
continue;
|
|
6572
|
+
}
|
|
6573
|
+
const content = await fs2.readFile(fp, "utf-8");
|
|
6574
|
+
const lines = content.split("\n");
|
|
6575
|
+
for (let i = 0; i < lines.length; i++) {
|
|
6576
|
+
const line = lines[i];
|
|
6577
|
+
if (regex.test(line)) {
|
|
6578
|
+
let virtPath;
|
|
6579
|
+
if (this.virtualMode) {
|
|
6580
|
+
try {
|
|
6581
|
+
const relative3 = path4.relative(this.cwd, fp);
|
|
6582
|
+
if (relative3.startsWith("..")) continue;
|
|
6583
|
+
const normalizedRelative = relative3.split(path4.sep).join("/");
|
|
6584
|
+
virtPath = "/" + normalizedRelative;
|
|
6585
|
+
} catch {
|
|
6586
|
+
continue;
|
|
6587
|
+
}
|
|
6588
|
+
} else {
|
|
6589
|
+
virtPath = fp;
|
|
6590
|
+
}
|
|
6591
|
+
if (!results[virtPath]) {
|
|
6592
|
+
results[virtPath] = [];
|
|
6593
|
+
}
|
|
6594
|
+
results[virtPath].push([i + 1, line]);
|
|
6595
|
+
}
|
|
6596
|
+
}
|
|
6597
|
+
} catch {
|
|
6598
|
+
continue;
|
|
6599
|
+
}
|
|
6600
|
+
}
|
|
6601
|
+
return results;
|
|
6602
|
+
}
|
|
6603
|
+
/**
|
|
6604
|
+
* Structured glob matching returning FileInfo objects.
|
|
6605
|
+
*/
|
|
6606
|
+
async globInfo(pattern, searchPath = "/") {
|
|
6607
|
+
if (pattern.startsWith("/")) {
|
|
6608
|
+
pattern = pattern.substring(1);
|
|
6609
|
+
}
|
|
6610
|
+
const resolvedSearchPath = searchPath === "/" ? this.cwd : this.resolvePath(searchPath);
|
|
6611
|
+
try {
|
|
6612
|
+
const stat3 = await fs2.stat(resolvedSearchPath);
|
|
6613
|
+
if (!stat3.isDirectory()) {
|
|
6614
|
+
return [];
|
|
6615
|
+
}
|
|
6616
|
+
} catch {
|
|
6617
|
+
return [];
|
|
6618
|
+
}
|
|
6619
|
+
const results = [];
|
|
6620
|
+
try {
|
|
6621
|
+
const matches = await fg(pattern, {
|
|
6622
|
+
cwd: resolvedSearchPath,
|
|
6623
|
+
absolute: true,
|
|
6624
|
+
onlyFiles: true,
|
|
6625
|
+
dot: true
|
|
6626
|
+
});
|
|
6627
|
+
for (const matchedPath of matches) {
|
|
6628
|
+
try {
|
|
6629
|
+
const stat3 = await fs2.stat(matchedPath);
|
|
6630
|
+
if (!stat3.isFile()) continue;
|
|
6631
|
+
const normalizedPath = matchedPath.split("/").join(path4.sep);
|
|
6632
|
+
if (!this.virtualMode) {
|
|
6633
|
+
results.push({
|
|
6634
|
+
path: normalizedPath,
|
|
6635
|
+
is_dir: false,
|
|
6636
|
+
size: stat3.size,
|
|
6637
|
+
modified_at: stat3.mtime.toISOString()
|
|
6638
|
+
});
|
|
6639
|
+
} else {
|
|
6640
|
+
const cwdStr = this.cwd.endsWith(path4.sep) ? this.cwd : this.cwd + path4.sep;
|
|
6641
|
+
let relativePath;
|
|
6642
|
+
if (normalizedPath.startsWith(cwdStr)) {
|
|
6643
|
+
relativePath = normalizedPath.substring(cwdStr.length);
|
|
6644
|
+
} else if (normalizedPath.startsWith(this.cwd)) {
|
|
6645
|
+
relativePath = normalizedPath.substring(this.cwd.length).replace(/^[/\\]/, "");
|
|
6646
|
+
} else {
|
|
6647
|
+
relativePath = normalizedPath;
|
|
6648
|
+
}
|
|
6649
|
+
relativePath = relativePath.split(path4.sep).join("/");
|
|
6650
|
+
const virt = "/" + relativePath;
|
|
6651
|
+
results.push({
|
|
6652
|
+
path: virt,
|
|
6653
|
+
is_dir: false,
|
|
6654
|
+
size: stat3.size,
|
|
6655
|
+
modified_at: stat3.mtime.toISOString()
|
|
6656
|
+
});
|
|
6657
|
+
}
|
|
6658
|
+
} catch {
|
|
6659
|
+
continue;
|
|
6660
|
+
}
|
|
6661
|
+
}
|
|
6662
|
+
} catch {
|
|
6663
|
+
}
|
|
6664
|
+
results.sort((a, b) => a.path.localeCompare(b.path));
|
|
6665
|
+
return results;
|
|
6666
|
+
}
|
|
6667
|
+
};
|
|
6668
|
+
|
|
6669
|
+
// src/deep_agent_new/backends/composite.ts
|
|
6670
|
+
var CompositeBackend = class {
|
|
6671
|
+
constructor(defaultBackend, routes) {
|
|
6672
|
+
this.default = defaultBackend;
|
|
6673
|
+
this.routes = routes;
|
|
6674
|
+
this.sortedRoutes = Object.entries(routes).sort(
|
|
6675
|
+
(a, b) => b[0].length - a[0].length
|
|
6676
|
+
);
|
|
6677
|
+
}
|
|
6678
|
+
/**
|
|
6679
|
+
* Determine which backend handles this key and strip prefix.
|
|
6680
|
+
*
|
|
6681
|
+
* @param key - Original file path
|
|
6682
|
+
* @returns Tuple of [backend, stripped_key] where stripped_key has the route
|
|
6683
|
+
* prefix removed (but keeps leading slash).
|
|
6684
|
+
*/
|
|
6685
|
+
getBackendAndKey(key) {
|
|
6686
|
+
for (const [prefix, backend] of this.sortedRoutes) {
|
|
6687
|
+
if (key.startsWith(prefix)) {
|
|
6688
|
+
const suffix = key.substring(prefix.length);
|
|
6689
|
+
const strippedKey = suffix ? "/" + suffix : "/";
|
|
6690
|
+
return [backend, strippedKey];
|
|
6691
|
+
}
|
|
6692
|
+
}
|
|
6693
|
+
return [this.default, key];
|
|
6694
|
+
}
|
|
6695
|
+
/**
|
|
6696
|
+
* List files and directories in the specified directory (non-recursive).
|
|
6697
|
+
*
|
|
6698
|
+
* @param path - Absolute path to directory
|
|
6699
|
+
* @returns List of FileInfo objects with route prefixes added, for files and directories
|
|
6700
|
+
* directly in the directory. Directories have a trailing / in their path and is_dir=true.
|
|
6701
|
+
*/
|
|
6702
|
+
async lsInfo(path5) {
|
|
6703
|
+
for (const [routePrefix, backend] of this.sortedRoutes) {
|
|
6704
|
+
if (path5.startsWith(routePrefix.replace(/\/$/, ""))) {
|
|
6705
|
+
const suffix = path5.substring(routePrefix.length);
|
|
6706
|
+
const searchPath = suffix ? "/" + suffix : "/";
|
|
6707
|
+
const infos = await backend.lsInfo(searchPath);
|
|
6708
|
+
const prefixed = [];
|
|
6709
|
+
for (const fi of infos) {
|
|
6710
|
+
prefixed.push({
|
|
6711
|
+
...fi,
|
|
6712
|
+
path: routePrefix.slice(0, -1) + fi.path
|
|
6713
|
+
});
|
|
6714
|
+
}
|
|
6715
|
+
return prefixed;
|
|
6716
|
+
}
|
|
6717
|
+
}
|
|
6718
|
+
if (path5 === "/") {
|
|
6719
|
+
const results = [];
|
|
6720
|
+
const defaultInfos = await this.default.lsInfo(path5);
|
|
6721
|
+
results.push(...defaultInfos);
|
|
6722
|
+
for (const [routePrefix] of this.sortedRoutes) {
|
|
6723
|
+
results.push({
|
|
6724
|
+
path: routePrefix,
|
|
6725
|
+
is_dir: true,
|
|
6726
|
+
size: 0,
|
|
6727
|
+
modified_at: ""
|
|
6728
|
+
});
|
|
6729
|
+
}
|
|
6730
|
+
results.sort((a, b) => a.path.localeCompare(b.path));
|
|
6731
|
+
return results;
|
|
6732
|
+
}
|
|
6733
|
+
return await this.default.lsInfo(path5);
|
|
6734
|
+
}
|
|
6735
|
+
/**
|
|
6736
|
+
* Read file content, routing to appropriate backend.
|
|
6737
|
+
*
|
|
6738
|
+
* @param filePath - Absolute file path
|
|
6739
|
+
* @param offset - Line offset to start reading from (0-indexed)
|
|
6740
|
+
* @param limit - Maximum number of lines to read
|
|
6741
|
+
* @returns Formatted file content with line numbers, or error message
|
|
6742
|
+
*/
|
|
6743
|
+
async read(filePath, offset = 0, limit = 2e3) {
|
|
6744
|
+
const [backend, strippedKey] = this.getBackendAndKey(filePath);
|
|
6745
|
+
return await backend.read(strippedKey, offset, limit);
|
|
6746
|
+
}
|
|
6747
|
+
/**
|
|
6748
|
+
* Read file content as raw FileData.
|
|
6749
|
+
*
|
|
6750
|
+
* @param filePath - Absolute file path
|
|
6751
|
+
* @returns Raw file content as FileData
|
|
6752
|
+
*/
|
|
6753
|
+
async readRaw(filePath) {
|
|
6754
|
+
const [backend, strippedKey] = this.getBackendAndKey(filePath);
|
|
6755
|
+
return await backend.readRaw(strippedKey);
|
|
6756
|
+
}
|
|
6757
|
+
/**
|
|
6758
|
+
* Structured search results or error string for invalid input.
|
|
6759
|
+
*/
|
|
6760
|
+
async grepRaw(pattern, path5 = "/", glob = null) {
|
|
6761
|
+
for (const [routePrefix, backend] of this.sortedRoutes) {
|
|
6762
|
+
if (path5.startsWith(routePrefix.replace(/\/$/, ""))) {
|
|
6763
|
+
const searchPath = path5.substring(routePrefix.length - 1);
|
|
6764
|
+
const raw = await backend.grepRaw(pattern, searchPath || "/", glob);
|
|
6765
|
+
if (typeof raw === "string") {
|
|
6766
|
+
return raw;
|
|
6767
|
+
}
|
|
6768
|
+
return raw.map((m) => ({
|
|
6769
|
+
...m,
|
|
6770
|
+
path: routePrefix.slice(0, -1) + m.path
|
|
6771
|
+
}));
|
|
6772
|
+
}
|
|
6773
|
+
}
|
|
6774
|
+
const allMatches = [];
|
|
6775
|
+
const rawDefault = await this.default.grepRaw(pattern, path5, glob);
|
|
6776
|
+
if (typeof rawDefault === "string") {
|
|
6777
|
+
return rawDefault;
|
|
6778
|
+
}
|
|
6779
|
+
allMatches.push(...rawDefault);
|
|
6780
|
+
for (const [routePrefix, backend] of Object.entries(this.routes)) {
|
|
6781
|
+
const raw = await backend.grepRaw(pattern, "/", glob);
|
|
6782
|
+
if (typeof raw === "string") {
|
|
6783
|
+
return raw;
|
|
6784
|
+
}
|
|
6785
|
+
allMatches.push(
|
|
6786
|
+
...raw.map((m) => ({
|
|
6787
|
+
...m,
|
|
6788
|
+
path: routePrefix.slice(0, -1) + m.path
|
|
6789
|
+
}))
|
|
6790
|
+
);
|
|
6791
|
+
}
|
|
6792
|
+
return allMatches;
|
|
6793
|
+
}
|
|
6794
|
+
/**
|
|
6795
|
+
* Structured glob matching returning FileInfo objects.
|
|
6796
|
+
*/
|
|
6797
|
+
async globInfo(pattern, path5 = "/") {
|
|
6798
|
+
const results = [];
|
|
6799
|
+
for (const [routePrefix, backend] of this.sortedRoutes) {
|
|
6800
|
+
if (path5.startsWith(routePrefix.replace(/\/$/, ""))) {
|
|
6801
|
+
const searchPath = path5.substring(routePrefix.length - 1);
|
|
6802
|
+
const infos = await backend.globInfo(pattern, searchPath || "/");
|
|
6803
|
+
return infos.map((fi) => ({
|
|
6804
|
+
...fi,
|
|
6805
|
+
path: routePrefix.slice(0, -1) + fi.path
|
|
6806
|
+
}));
|
|
6807
|
+
}
|
|
6808
|
+
}
|
|
6809
|
+
const defaultInfos = await this.default.globInfo(pattern, path5);
|
|
6810
|
+
results.push(...defaultInfos);
|
|
6811
|
+
for (const [routePrefix, backend] of Object.entries(this.routes)) {
|
|
6812
|
+
const infos = await backend.globInfo(pattern, "/");
|
|
6813
|
+
results.push(
|
|
6814
|
+
...infos.map((fi) => ({
|
|
6815
|
+
...fi,
|
|
6816
|
+
path: routePrefix.slice(0, -1) + fi.path
|
|
6817
|
+
}))
|
|
6818
|
+
);
|
|
6819
|
+
}
|
|
6820
|
+
results.sort((a, b) => a.path.localeCompare(b.path));
|
|
6821
|
+
return results;
|
|
6822
|
+
}
|
|
6823
|
+
/**
|
|
6824
|
+
* Create a new file, routing to appropriate backend.
|
|
6825
|
+
*
|
|
6826
|
+
* @param filePath - Absolute file path
|
|
6827
|
+
* @param content - File content as string
|
|
6828
|
+
* @returns WriteResult with path or error
|
|
6829
|
+
*/
|
|
6830
|
+
async write(filePath, content) {
|
|
6831
|
+
const [backend, strippedKey] = this.getBackendAndKey(filePath);
|
|
6832
|
+
return await backend.write(strippedKey, content);
|
|
6833
|
+
}
|
|
6834
|
+
/**
|
|
6835
|
+
* Edit a file, routing to appropriate backend.
|
|
6836
|
+
*
|
|
6837
|
+
* @param filePath - Absolute file path
|
|
6838
|
+
* @param oldString - String to find and replace
|
|
6839
|
+
* @param newString - Replacement string
|
|
6840
|
+
* @param replaceAll - If true, replace all occurrences
|
|
6841
|
+
* @returns EditResult with path, occurrences, or error
|
|
6842
|
+
*/
|
|
6843
|
+
async edit(filePath, oldString, newString, replaceAll = false) {
|
|
6844
|
+
const [backend, strippedKey] = this.getBackendAndKey(filePath);
|
|
6845
|
+
return await backend.edit(strippedKey, oldString, newString, replaceAll);
|
|
6846
|
+
}
|
|
6847
|
+
};
|
|
6848
|
+
|
|
6849
|
+
// src/deep_agent_new/backends/memory.ts
|
|
6850
|
+
var MemoryBackend = class {
|
|
6851
|
+
constructor(files) {
|
|
6852
|
+
this.files = files ?? /* @__PURE__ */ new Map();
|
|
6853
|
+
}
|
|
6854
|
+
getFiles() {
|
|
6855
|
+
return Object.fromEntries(this.files);
|
|
6856
|
+
}
|
|
6857
|
+
lsInfo(path5) {
|
|
6858
|
+
const files = this.getFiles();
|
|
6859
|
+
const infos = [];
|
|
6860
|
+
const subdirs = /* @__PURE__ */ new Set();
|
|
6861
|
+
const normalizedPath = path5.endsWith("/") ? path5 : path5 + "/";
|
|
6862
|
+
for (const [k, fd] of Object.entries(files)) {
|
|
6863
|
+
if (!k.startsWith(normalizedPath)) {
|
|
6864
|
+
continue;
|
|
6865
|
+
}
|
|
6866
|
+
const relative3 = k.substring(normalizedPath.length);
|
|
6867
|
+
if (relative3.includes("/")) {
|
|
6868
|
+
const subdirName = relative3.split("/")[0];
|
|
6869
|
+
subdirs.add(normalizedPath + subdirName + "/");
|
|
6870
|
+
continue;
|
|
6871
|
+
}
|
|
6872
|
+
const size = fd.content.join("\n").length;
|
|
6873
|
+
infos.push({
|
|
6874
|
+
path: k,
|
|
6875
|
+
is_dir: false,
|
|
6876
|
+
size,
|
|
6877
|
+
modified_at: fd.modified_at
|
|
6878
|
+
});
|
|
6879
|
+
}
|
|
6880
|
+
for (const subdir of Array.from(subdirs).sort()) {
|
|
6881
|
+
infos.push({
|
|
6882
|
+
path: subdir,
|
|
6883
|
+
is_dir: true,
|
|
6884
|
+
size: 0,
|
|
6885
|
+
modified_at: ""
|
|
6886
|
+
});
|
|
6887
|
+
}
|
|
6888
|
+
infos.sort((a, b) => a.path.localeCompare(b.path));
|
|
6889
|
+
return infos;
|
|
6890
|
+
}
|
|
6891
|
+
read(filePath, offset = 0, limit = 2e3) {
|
|
6892
|
+
const files = this.getFiles();
|
|
6893
|
+
const fileData = files[filePath];
|
|
6894
|
+
if (!fileData) {
|
|
6895
|
+
return `Error: File '${filePath}' not found`;
|
|
6896
|
+
}
|
|
6897
|
+
return formatReadResponse(fileData, offset, limit);
|
|
6898
|
+
}
|
|
6899
|
+
readRaw(filePath) {
|
|
6900
|
+
const fileData = this.files.get(filePath);
|
|
6901
|
+
if (!fileData) {
|
|
6902
|
+
throw new Error(`File '${filePath}' not found`);
|
|
6903
|
+
}
|
|
6904
|
+
return fileData;
|
|
6905
|
+
}
|
|
6906
|
+
write(filePath, content) {
|
|
6907
|
+
if (this.files.has(filePath)) {
|
|
6908
|
+
return {
|
|
6909
|
+
error: `Cannot write to ${filePath} because it already exists. Read and then make an edit, or write to a new path.`
|
|
6910
|
+
};
|
|
6911
|
+
}
|
|
6912
|
+
const newFileData = createFileData(content);
|
|
6913
|
+
this.files.set(filePath, newFileData);
|
|
6914
|
+
return { path: filePath, filesUpdate: null };
|
|
6915
|
+
}
|
|
6916
|
+
edit(filePath, oldString, newString, replaceAll = false) {
|
|
6917
|
+
const fileData = this.files.get(filePath);
|
|
6918
|
+
if (!fileData) {
|
|
6919
|
+
return { error: `Error: File '${filePath}' not found` };
|
|
6920
|
+
}
|
|
6921
|
+
const content = fileDataToString(fileData);
|
|
6922
|
+
const result = performStringReplacement(
|
|
6923
|
+
content,
|
|
6924
|
+
oldString,
|
|
6925
|
+
newString,
|
|
6926
|
+
replaceAll
|
|
6927
|
+
);
|
|
6928
|
+
if (typeof result === "string") {
|
|
6929
|
+
return { error: result };
|
|
6930
|
+
}
|
|
6931
|
+
const [newContent, occurrences] = result;
|
|
6932
|
+
const newFileData = updateFileData(fileData, newContent);
|
|
6933
|
+
this.files.set(filePath, newFileData);
|
|
6934
|
+
return { path: filePath, filesUpdate: null, occurrences };
|
|
6935
|
+
}
|
|
6936
|
+
grepRaw(pattern, path5 = "/", glob = null) {
|
|
6937
|
+
const files = this.getFiles();
|
|
6938
|
+
return grepMatchesFromFiles(files, pattern, path5, glob);
|
|
6939
|
+
}
|
|
6940
|
+
globInfo(pattern, path5 = "/") {
|
|
6941
|
+
const files = this.getFiles();
|
|
6942
|
+
const result = globSearchFiles(files, pattern, path5);
|
|
6943
|
+
if (result === "No files found") {
|
|
6944
|
+
return [];
|
|
6945
|
+
}
|
|
6946
|
+
const paths = result.split("\n");
|
|
6947
|
+
const infos = [];
|
|
6948
|
+
for (const p of paths) {
|
|
6949
|
+
const fd = files[p];
|
|
6950
|
+
const size = fd ? fd.content.join("\n").length : 0;
|
|
6951
|
+
infos.push({
|
|
6952
|
+
path: p,
|
|
6953
|
+
is_dir: false,
|
|
6954
|
+
size,
|
|
6955
|
+
modified_at: fd?.modified_at || ""
|
|
6956
|
+
});
|
|
6957
|
+
}
|
|
6958
|
+
return infos;
|
|
6959
|
+
}
|
|
6960
|
+
};
|
|
6961
|
+
|
|
6962
|
+
// src/deep_agent_new/middleware/todos.ts
|
|
6963
|
+
import { Command as Command3 } from "@langchain/langgraph";
|
|
6964
|
+
import { z as z36 } from "zod";
|
|
6965
|
+
import { createMiddleware as createMiddleware8, tool as tool35, ToolMessage as ToolMessage4 } from "langchain";
|
|
6966
|
+
var WRITE_TODOS_DESCRIPTION = `Use this tool to create and manage a structured task list for your current work session. This helps you track progress, organize complex tasks, and demonstrate thoroughness to the user.
|
|
6967
|
+
It also helps the user understand the progress of the task and overall progress of their requests.
|
|
6968
|
+
Only use this tool if you think it will be helpful in staying organized. If the user's request is trivial and takes less than 3 steps, it is better to NOT use this tool and just do the taks directly.
|
|
6969
|
+
|
|
6970
|
+
## When to Use This Tool
|
|
6971
|
+
Use this tool in these scenarios:
|
|
6972
|
+
|
|
6973
|
+
1. Complex multi-step tasks - When a task requires 3 or more distinct steps or actions
|
|
6974
|
+
2. Non-trivial and complex tasks - Tasks that require careful planning or multiple operations
|
|
6975
|
+
3. User explicitly requests todo list - When the user directly asks you to use the todo list
|
|
6976
|
+
4. User provides multiple tasks - When users provide a list of things to be done (numbered or comma-separated)
|
|
6977
|
+
5. The plan may need future revisions or updates based on results from the first few steps. Keeping track of this in a list is helpful.
|
|
6978
|
+
|
|
6979
|
+
## How to Use This Tool
|
|
6980
|
+
1. When you start working on a task - Mark it as in_progress BEFORE beginning work.
|
|
6981
|
+
2. After completing a task - Mark it as completed and add any new follow-up tasks discovered during implementation.
|
|
6982
|
+
3. You can also update future tasks, such as deleting them if they are no longer necessary, or adding new tasks that are necessary. Don't change previously completed tasks.
|
|
6983
|
+
4. You can make several updates to the todo list at once. For example, when you complete a task, you can mark the next task you need to start as in_progress.
|
|
6984
|
+
|
|
6985
|
+
## When NOT to Use This Tool
|
|
6986
|
+
It is important to skip using this tool when:
|
|
6987
|
+
1. There is only a single, straightforward task
|
|
6988
|
+
2. The task is trivial and tracking it provides no benefit
|
|
6989
|
+
3. The task can be completed in less than 3 trivial steps
|
|
6990
|
+
4. The task is purely conversational or informational
|
|
6991
|
+
|
|
6992
|
+
## Examples of When to Use the Todo List
|
|
6993
|
+
|
|
6994
|
+
<example>
|
|
6995
|
+
User: I want to add a dark mode toggle to the application settings. Make sure you run the tests and build when you're done!
|
|
6996
|
+
Assistant: I'll help add a dark mode toggle to your application settings. Let me create a todo list to track this implementation.
|
|
6997
|
+
*Creates todo list with the following items:*
|
|
6998
|
+
1. Create dark mode toggle component in Settings page
|
|
6999
|
+
2. Add dark mode state management (context/store)
|
|
7000
|
+
3. Implement CSS-in-JS styles for dark theme
|
|
7001
|
+
4. Update existing components to support theme switching
|
|
7002
|
+
5. Run tests and build process, addressing any failures or errors that occur
|
|
7003
|
+
*Begins working on the first task*
|
|
7004
|
+
|
|
7005
|
+
<reasoning>
|
|
7006
|
+
The assistant used the todo list because:
|
|
7007
|
+
1. Adding dark mode in it of itself is a multi-step feature requiring UI, state management, and styling changes
|
|
7008
|
+
2. The assistant inferred that tests and build need to pass by adding "Ensure tests and build succeed" as the final task
|
|
5507
7009
|
3. Both of the user's requests are complex and require multiple steps to complete.
|
|
5508
7010
|
</reasoning>
|
|
5509
7011
|
</example>
|
|
@@ -5609,289 +7111,1896 @@ Assistant: I'll check the current time in Tokyo for you.
|
|
|
5609
7111
|
|
|
5610
7112
|
*Searches for current time in Tokyo*
|
|
5611
7113
|
|
|
5612
|
-
The current time in Tokyo, Japan is [current time]. Tokyo is in the Japan Standard Time (JST) zone, which is UTC+9.
|
|
7114
|
+
The current time in Tokyo, Japan is [current time]. Tokyo is in the Japan Standard Time (JST) zone, which is UTC+9.
|
|
7115
|
+
|
|
7116
|
+
<reasoning>
|
|
7117
|
+
The assistant did not use the todo list because this is a single information lookup with immediate results. There are no multiple steps to track or organize, making the todo list unnecessary for this straightforward request.
|
|
7118
|
+
</reasoning>
|
|
7119
|
+
</example>
|
|
7120
|
+
|
|
7121
|
+
<example>
|
|
7122
|
+
User: I need to write a function that checks if a number is prime and then test it out.
|
|
7123
|
+
Assistant: I'll help you write a function that checks if a number is prime and then test it out.
|
|
7124
|
+
*Writes function that checks if a number is prime*
|
|
7125
|
+
*Tests the function*
|
|
7126
|
+
|
|
7127
|
+
<reasoning>
|
|
7128
|
+
Even though this is a multi-step task, it is very straightforward and can be completed in two trivial steps (which is less than 3 steps!). Using the todo list here is overkill and wastes time and tokens.
|
|
7129
|
+
</reasoning>
|
|
7130
|
+
</example>
|
|
7131
|
+
|
|
7132
|
+
<example>
|
|
7133
|
+
User: I want you to order a pizza from Dominos, order a burger from McDonald's, and order a salad from Subway.
|
|
7134
|
+
Assistant: I'll help you order a pizza from Dominos, order a burger from McDonald's, and order a salad from Subway.
|
|
7135
|
+
*Orders a pizza from Dominos*
|
|
7136
|
+
*Orders a burger from McDonald's*
|
|
7137
|
+
*Orders a salad from Subway*
|
|
7138
|
+
|
|
7139
|
+
<reasoning>
|
|
7140
|
+
Even though this is a multi-step task, assuming the assistant has the ability to order from these restaurants, it is very straightforward and can be completed in three trivial tool calls.
|
|
7141
|
+
Using the todo list here is overkill and wastes time and tokens. These three tool calls should be made in parallel, in fact.
|
|
7142
|
+
</reasoning>
|
|
7143
|
+
</example>
|
|
7144
|
+
|
|
7145
|
+
|
|
7146
|
+
## Task States and Management
|
|
7147
|
+
|
|
7148
|
+
1. **Task States**: Use these states to track progress:
|
|
7149
|
+
- pending: Task not yet started
|
|
7150
|
+
- in_progress: Currently working on (you can have multiple tasks in_progress at a time if they are not related to each other and can be run in parallel)
|
|
7151
|
+
- completed: Task finished successfully
|
|
7152
|
+
|
|
7153
|
+
2. **Task Management**:
|
|
7154
|
+
- Update task status in real-time as you work
|
|
7155
|
+
- Mark tasks complete IMMEDIATELY after finishing (don't batch completions)
|
|
7156
|
+
- Complete current tasks before starting new ones
|
|
7157
|
+
- Remove tasks that are no longer relevant from the list entirely
|
|
7158
|
+
- IMPORTANT: When you write this todo list, you should mark your first task (or tasks) as in_progress immediately!.
|
|
7159
|
+
- IMPORTANT: Unless all tasks are completed, you should always have at least one task in_progress to show the user that you are working on something.
|
|
7160
|
+
|
|
7161
|
+
3. **Task Completion Requirements**:
|
|
7162
|
+
- ONLY mark a task as completed when you have FULLY accomplished it
|
|
7163
|
+
- If you encounter errors, blockers, or cannot finish, keep the task as in_progress
|
|
7164
|
+
- When blocked, create a new task describing what needs to be resolved
|
|
7165
|
+
- Never mark a task as completed if:
|
|
7166
|
+
- There are unresolved issues or errors
|
|
7167
|
+
- Work is partial or incomplete
|
|
7168
|
+
- You encountered blockers that prevent completion
|
|
7169
|
+
- You couldn't find necessary resources or dependencies
|
|
7170
|
+
- Quality standards haven't been met
|
|
7171
|
+
|
|
7172
|
+
4. **Task Breakdown**:
|
|
7173
|
+
- Create specific, actionable items
|
|
7174
|
+
- Break complex tasks into smaller, manageable steps
|
|
7175
|
+
- Use clear, descriptive task names
|
|
7176
|
+
|
|
7177
|
+
Being proactive with task management demonstrates attentiveness and ensures you complete all requirements successfully
|
|
7178
|
+
Remember: If you only need to make a few tool calls to complete a task, and it is clear what you need to do, it is better to just do the task directly and NOT call this tool at all.`;
|
|
7179
|
+
var TODO_LIST_MIDDLEWARE_SYSTEM_PROMPT = `## \`write_todos\`
|
|
7180
|
+
|
|
7181
|
+
You have access to the \`write_todos\` tool to help you manage and plan complex objectives.
|
|
7182
|
+
Use this tool for complex objectives to ensure that you are tracking each necessary step and giving the user visibility into your progress.
|
|
7183
|
+
This tool is very helpful for planning complex objectives, and for breaking down these larger complex objectives into smaller steps.
|
|
7184
|
+
|
|
7185
|
+
It is critical that you mark todos as completed as soon as you are done with a step. Do not batch up multiple steps before marking them as completed.
|
|
7186
|
+
For simple objectives that only require a few steps, it is better to just complete the objective directly and NOT use this tool.
|
|
7187
|
+
Writing todos takes time and tokens, use it when it is helpful for managing complex many-step problems! But not for simple few-step requests.
|
|
5613
7188
|
|
|
5614
|
-
|
|
5615
|
-
The
|
|
5616
|
-
|
|
5617
|
-
|
|
7189
|
+
## Important To-Do List Usage Notes to Remember
|
|
7190
|
+
- The \`write_todos\` tool should never be called multiple times in parallel.
|
|
7191
|
+
- Don't be afraid to revise the To-Do list as you go. New information may reveal new tasks that need to be done, or old tasks that are irrelevant.`;
|
|
7192
|
+
var TodoStatus = z36.enum(["pending", "in_progress", "completed"]).describe("Status of the todo");
|
|
7193
|
+
var TodoSchema = z36.object({
|
|
7194
|
+
content: z36.string().describe("Content of the todo item"),
|
|
7195
|
+
status: TodoStatus
|
|
7196
|
+
});
|
|
7197
|
+
var stateSchema = z36.object({ todos: z36.array(TodoSchema).default([]) });
|
|
7198
|
+
function todoListMiddleware(options) {
|
|
7199
|
+
const writeTodos = tool35(
|
|
7200
|
+
({ todos }, config) => {
|
|
7201
|
+
return new Command3({
|
|
7202
|
+
update: {
|
|
7203
|
+
todos,
|
|
7204
|
+
messages: [
|
|
7205
|
+
new ToolMessage4({
|
|
7206
|
+
content: genUIMarkdown("todo_list", todos),
|
|
7207
|
+
tool_call_id: config.toolCall?.id
|
|
7208
|
+
})
|
|
7209
|
+
]
|
|
7210
|
+
}
|
|
7211
|
+
});
|
|
7212
|
+
},
|
|
7213
|
+
{
|
|
7214
|
+
name: "write_todos",
|
|
7215
|
+
description: options?.toolDescription ?? WRITE_TODOS_DESCRIPTION,
|
|
7216
|
+
schema: z36.object({
|
|
7217
|
+
todos: z36.array(TodoSchema).describe("List of todo items to update")
|
|
7218
|
+
})
|
|
7219
|
+
}
|
|
7220
|
+
);
|
|
7221
|
+
return createMiddleware8({
|
|
7222
|
+
name: "todoListMiddleware",
|
|
7223
|
+
stateSchema,
|
|
7224
|
+
tools: [writeTodos],
|
|
7225
|
+
wrapModelCall: (request, handler) => handler({
|
|
7226
|
+
...request,
|
|
7227
|
+
systemPrompt: (request.systemPrompt ? `${request.systemPrompt}
|
|
5618
7228
|
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
*Tests the function*
|
|
7229
|
+
` : "") + (options?.systemPrompt ?? TODO_LIST_MIDDLEWARE_SYSTEM_PROMPT)
|
|
7230
|
+
})
|
|
7231
|
+
});
|
|
7232
|
+
}
|
|
5624
7233
|
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
|
|
7234
|
+
// src/deep_agent_new/agent.ts
|
|
7235
|
+
var BASE_PROMPT = `In order to complete the objective that the user asks of you, you have access to a number of standard tools.`;
|
|
7236
|
+
function createDeepAgent(params = {}) {
|
|
7237
|
+
const {
|
|
7238
|
+
model = "claude-sonnet-4-5-20250929",
|
|
7239
|
+
tools = [],
|
|
7240
|
+
systemPrompt,
|
|
7241
|
+
middleware: customMiddleware = [],
|
|
7242
|
+
subagents = [],
|
|
7243
|
+
responseFormat,
|
|
7244
|
+
contextSchema,
|
|
7245
|
+
checkpointer,
|
|
7246
|
+
store,
|
|
7247
|
+
backend,
|
|
7248
|
+
interruptOn,
|
|
7249
|
+
name,
|
|
7250
|
+
skills
|
|
7251
|
+
} = params;
|
|
7252
|
+
const finalSystemPrompt = systemPrompt ? `${systemPrompt}
|
|
5629
7253
|
|
|
5630
|
-
|
|
5631
|
-
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
|
|
5635
|
-
|
|
7254
|
+
${BASE_PROMPT}` : BASE_PROMPT;
|
|
7255
|
+
const filesystemBackend = backend ? backend : async (config) => new StateBackend(config);
|
|
7256
|
+
const middleware = [
|
|
7257
|
+
// Provides todo list management capabilities for tracking tasks
|
|
7258
|
+
todoListMiddleware(),
|
|
7259
|
+
// Enables filesystem operations and optional long-term memory storage
|
|
7260
|
+
createFilesystemMiddleware({ backend: filesystemBackend }),
|
|
7261
|
+
// Enables delegation to specialized subagents for complex tasks
|
|
7262
|
+
createSubAgentMiddleware({
|
|
7263
|
+
defaultModel: model,
|
|
7264
|
+
defaultTools: tools,
|
|
7265
|
+
defaultMiddleware: [
|
|
7266
|
+
// Subagent middleware: Todo list management
|
|
7267
|
+
todoListMiddleware(),
|
|
7268
|
+
// Subagent middleware: Filesystem operations
|
|
7269
|
+
createFilesystemMiddleware({
|
|
7270
|
+
backend: filesystemBackend
|
|
7271
|
+
}),
|
|
7272
|
+
// Subagent middleware: Automatic conversation summarization when token limits are approached
|
|
7273
|
+
summarizationMiddleware({
|
|
7274
|
+
model,
|
|
7275
|
+
trigger: { tokens: 17e4 },
|
|
7276
|
+
keep: { messages: 6 }
|
|
7277
|
+
}),
|
|
7278
|
+
// Subagent middleware: Anthropic prompt caching for improved performance
|
|
7279
|
+
anthropicPromptCachingMiddleware({
|
|
7280
|
+
unsupportedModelBehavior: "ignore"
|
|
7281
|
+
}),
|
|
7282
|
+
// Subagent middleware: Patches tool calls for compatibility
|
|
7283
|
+
createPatchToolCallsMiddleware()
|
|
7284
|
+
],
|
|
7285
|
+
defaultInterruptOn: interruptOn,
|
|
7286
|
+
subagents,
|
|
7287
|
+
generalPurposeAgent: false
|
|
7288
|
+
}),
|
|
7289
|
+
// Automatically summarizes conversation history when token limits are approached
|
|
7290
|
+
summarizationMiddleware({
|
|
7291
|
+
model,
|
|
7292
|
+
trigger: { tokens: 17e4 },
|
|
7293
|
+
keep: { messages: 6 }
|
|
7294
|
+
}),
|
|
7295
|
+
// Enables Anthropic prompt caching for improved performance and reduced costs
|
|
7296
|
+
anthropicPromptCachingMiddleware({
|
|
7297
|
+
unsupportedModelBehavior: "ignore"
|
|
7298
|
+
}),
|
|
7299
|
+
// Patches tool calls to ensure compatibility across different model providers
|
|
7300
|
+
createPatchToolCallsMiddleware()
|
|
7301
|
+
];
|
|
7302
|
+
if (interruptOn) {
|
|
7303
|
+
middleware.push(humanInTheLoopMiddleware2({ interruptOn }));
|
|
7304
|
+
}
|
|
7305
|
+
middleware.push(...customMiddleware);
|
|
7306
|
+
return createAgent3({
|
|
7307
|
+
model,
|
|
7308
|
+
systemPrompt: finalSystemPrompt,
|
|
7309
|
+
tools,
|
|
7310
|
+
middleware,
|
|
7311
|
+
responseFormat,
|
|
7312
|
+
contextSchema,
|
|
7313
|
+
checkpointer,
|
|
7314
|
+
store,
|
|
7315
|
+
name
|
|
7316
|
+
});
|
|
7317
|
+
}
|
|
5636
7318
|
|
|
5637
|
-
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
|
|
7319
|
+
// src/agent_lattice/builders/filesystemBackend.ts
|
|
7320
|
+
function createFilesystemBackendFactory(middlewareConfigs) {
|
|
7321
|
+
const filesystemConfig = middlewareConfigs.find((m) => m.type === "filesystem");
|
|
7322
|
+
if (!filesystemConfig || !filesystemConfig.enabled) {
|
|
7323
|
+
return void 0;
|
|
7324
|
+
}
|
|
7325
|
+
const isolatedLevel = filesystemConfig.config?.isolatedLevel || "global";
|
|
7326
|
+
return async (config) => {
|
|
7327
|
+
const { workspaceId, projectId } = config;
|
|
7328
|
+
let sandboxName = "global";
|
|
7329
|
+
if (isolatedLevel === "agent") {
|
|
7330
|
+
sandboxName = "agent";
|
|
7331
|
+
} else if (isolatedLevel === "thread") {
|
|
7332
|
+
sandboxName = "thread";
|
|
7333
|
+
}
|
|
7334
|
+
const sandboxManager = sandboxLatticeManager.getSandboxLattice("default");
|
|
7335
|
+
if (!sandboxManager) {
|
|
7336
|
+
throw new Error("Sandbox manager not found");
|
|
7337
|
+
}
|
|
7338
|
+
return new SandboxFilesystem({
|
|
7339
|
+
sandboxInstance: await sandboxManager.createSandbox(sandboxName),
|
|
7340
|
+
workingDirectory: workspaceId && projectId ? `/workspaces/${workspaceId}/${projectId}` : "/"
|
|
7341
|
+
});
|
|
7342
|
+
};
|
|
7343
|
+
}
|
|
5642
7344
|
|
|
7345
|
+
// src/agent_lattice/builders/DeepAgentGraphBuilder.ts
|
|
7346
|
+
var DeepAgentGraphBuilder = class {
|
|
7347
|
+
/**
|
|
7348
|
+
* 根据 middleware 配置创建 middlewares
|
|
7349
|
+
*/
|
|
7350
|
+
createMiddlewares(middlewareConfigs) {
|
|
7351
|
+
return createCommonMiddlewares(middlewareConfigs);
|
|
7352
|
+
}
|
|
7353
|
+
/**
|
|
7354
|
+
* 构建Deep Agent Graph
|
|
7355
|
+
*
|
|
7356
|
+
* @param agentLattice Agent Lattice对象
|
|
7357
|
+
* @param params Agent构建参数
|
|
7358
|
+
* @returns 返回CompiledGraph对象
|
|
7359
|
+
*/
|
|
7360
|
+
build(agentLattice, params) {
|
|
7361
|
+
const tools = params.tools.map((t) => {
|
|
7362
|
+
const toolClient = getToolClient(t.key);
|
|
7363
|
+
return toolClient;
|
|
7364
|
+
}).filter((tool38) => tool38 !== void 0);
|
|
7365
|
+
const subagents = params.subAgents.map((sa) => {
|
|
7366
|
+
if (sa.client) {
|
|
7367
|
+
return {
|
|
7368
|
+
name: sa.config.name,
|
|
7369
|
+
description: sa.config.description,
|
|
7370
|
+
runnable: sa.client
|
|
7371
|
+
};
|
|
7372
|
+
} else {
|
|
7373
|
+
const subagentClient = createAgentClientFromAgentLattice({
|
|
7374
|
+
config: sa.config
|
|
7375
|
+
});
|
|
7376
|
+
return {
|
|
7377
|
+
name: sa.config.name,
|
|
7378
|
+
description: sa.config.description,
|
|
7379
|
+
runnable: subagentClient
|
|
7380
|
+
};
|
|
7381
|
+
}
|
|
7382
|
+
});
|
|
7383
|
+
const middlewareConfigs = params.middleware || [];
|
|
7384
|
+
const filesystemBackend = createFilesystemBackendFactory(middlewareConfigs);
|
|
7385
|
+
const middlewares = this.createMiddlewares(middlewareConfigs);
|
|
7386
|
+
const deepAgent = createDeepAgent({
|
|
7387
|
+
tools,
|
|
7388
|
+
model: params.model,
|
|
7389
|
+
contextSchema: params.stateSchema,
|
|
7390
|
+
systemPrompt: params.prompt,
|
|
7391
|
+
subagents,
|
|
7392
|
+
checkpointer: getCheckpointSaver("default"),
|
|
7393
|
+
skills: params.skillCategories,
|
|
7394
|
+
backend: filesystemBackend,
|
|
7395
|
+
middleware: middlewares
|
|
7396
|
+
});
|
|
7397
|
+
return deepAgent;
|
|
7398
|
+
}
|
|
7399
|
+
};
|
|
5643
7400
|
|
|
5644
|
-
|
|
7401
|
+
// src/agent_team/agent_team.ts
|
|
7402
|
+
import { z as z39 } from "zod/v3";
|
|
7403
|
+
import { createAgent as createAgent5 } from "langchain";
|
|
7404
|
+
|
|
7405
|
+
// src/agent_team/types.ts
|
|
7406
|
+
var TaskStatus = /* @__PURE__ */ ((TaskStatus3) => {
|
|
7407
|
+
TaskStatus3["PENDING"] = "pending";
|
|
7408
|
+
TaskStatus3["CLAIMED"] = "claimed";
|
|
7409
|
+
TaskStatus3["IN_PROGRESS"] = "in_progress";
|
|
7410
|
+
TaskStatus3["COMPLETED"] = "completed";
|
|
7411
|
+
TaskStatus3["FAILED"] = "failed";
|
|
7412
|
+
return TaskStatus3;
|
|
7413
|
+
})(TaskStatus || {});
|
|
7414
|
+
var MessageType = /* @__PURE__ */ ((MessageType2) => {
|
|
7415
|
+
MessageType2["DIRECT_MESSAGE"] = "direct_message";
|
|
7416
|
+
MessageType2["BROADCAST"] = "broadcast";
|
|
7417
|
+
MessageType2["PLAN_REQUEST"] = "plan_request";
|
|
7418
|
+
MessageType2["PLAN_FEEDBACK"] = "plan_feedback";
|
|
7419
|
+
MessageType2["SHUTDOWN_REQUEST"] = "shutdown_request";
|
|
7420
|
+
MessageType2["SHUTDOWN_RESPONSE"] = "shutdown_response";
|
|
7421
|
+
MessageType2["TASK_FEEDBACK"] = "task_feedback";
|
|
7422
|
+
MessageType2["STATUS_UPDATE"] = "status_update";
|
|
7423
|
+
return MessageType2;
|
|
7424
|
+
})(MessageType || {});
|
|
7425
|
+
|
|
7426
|
+
// src/agent_team/stores/InMemoryTaskListStore.ts
|
|
7427
|
+
import { EventEmitter as EventEmitter2 } from "events";
|
|
7428
|
+
var InMemoryTaskListStore = class {
|
|
7429
|
+
constructor() {
|
|
7430
|
+
/** Map<teamId, Map<taskId, TeamTask>> */
|
|
7431
|
+
this.tasks = /* @__PURE__ */ new Map();
|
|
7432
|
+
/** EventEmitter for task lifecycle events */
|
|
7433
|
+
this.emitter = new EventEmitter2();
|
|
7434
|
+
/** Auto-incrementing ID counter */
|
|
7435
|
+
this.idCounter = 0;
|
|
7436
|
+
this.emitter.setMaxListeners(200);
|
|
7437
|
+
}
|
|
7438
|
+
// -------------------------------------------------------------------------
|
|
7439
|
+
// Helpers
|
|
7440
|
+
// -------------------------------------------------------------------------
|
|
7441
|
+
/** Get or create the task map for a team. */
|
|
7442
|
+
getTeamTasks(teamId) {
|
|
7443
|
+
let map = this.tasks.get(teamId);
|
|
7444
|
+
if (!map) {
|
|
7445
|
+
map = /* @__PURE__ */ new Map();
|
|
7446
|
+
this.tasks.set(teamId, map);
|
|
7447
|
+
}
|
|
7448
|
+
return map;
|
|
7449
|
+
}
|
|
7450
|
+
/** Generate a unique task ID. */
|
|
7451
|
+
nextId() {
|
|
7452
|
+
this.idCounter += 1;
|
|
7453
|
+
return `task-${this.idCounter}`;
|
|
7454
|
+
}
|
|
7455
|
+
/** Emit a scoped event: `${teamId}:${event}`. */
|
|
7456
|
+
emit(teamId, event, task) {
|
|
7457
|
+
this.emitter.emit(`${teamId}:${event}`, task);
|
|
7458
|
+
}
|
|
7459
|
+
/**
|
|
7460
|
+
* Resolve dependency references by task IDs.
|
|
7461
|
+
*/
|
|
7462
|
+
resolveDependencies(teamTasks, depIds) {
|
|
7463
|
+
const resolved = [];
|
|
7464
|
+
for (const ref of depIds) {
|
|
7465
|
+
if (teamTasks.has(ref)) {
|
|
7466
|
+
resolved.push(ref);
|
|
7467
|
+
}
|
|
7468
|
+
}
|
|
7469
|
+
return resolved;
|
|
7470
|
+
}
|
|
7471
|
+
/**
|
|
7472
|
+
* Check whether all dependencies of a task are in COMPLETED status.
|
|
7473
|
+
*/
|
|
7474
|
+
areDependenciesSatisfied(teamTasks, task) {
|
|
7475
|
+
if (task.dependencies.length === 0) return true;
|
|
7476
|
+
return task.dependencies.every((depId) => {
|
|
7477
|
+
const dep = teamTasks.get(depId);
|
|
7478
|
+
return dep !== void 0 && dep.status === "completed" /* COMPLETED */;
|
|
7479
|
+
});
|
|
7480
|
+
}
|
|
7481
|
+
// -------------------------------------------------------------------------
|
|
7482
|
+
// Lifecycle
|
|
7483
|
+
// -------------------------------------------------------------------------
|
|
7484
|
+
async addTask(teamId, spec) {
|
|
7485
|
+
const teamTasks = this.getTeamTasks(teamId);
|
|
7486
|
+
if (!/^task-\d+$/.test(spec.id)) {
|
|
7487
|
+
throw new Error(`Invalid task ID format: ${spec.id}. Expected format: task-01, task-02, etc.`);
|
|
7488
|
+
}
|
|
7489
|
+
if (teamTasks.has(spec.id)) {
|
|
7490
|
+
throw new Error(`Task ID already exists: ${spec.id}`);
|
|
7491
|
+
}
|
|
7492
|
+
const now = /* @__PURE__ */ new Date();
|
|
7493
|
+
const task = {
|
|
7494
|
+
id: spec.id,
|
|
7495
|
+
title: spec.title,
|
|
7496
|
+
description: spec.description,
|
|
7497
|
+
assignee: spec.assignee,
|
|
7498
|
+
status: "pending" /* PENDING */,
|
|
7499
|
+
dependencies: spec.dependencies ? this.resolveDependencies(teamTasks, spec.dependencies) : [],
|
|
7500
|
+
createdAt: now,
|
|
7501
|
+
updatedAt: now
|
|
7502
|
+
};
|
|
7503
|
+
teamTasks.set(task.id, task);
|
|
7504
|
+
this.emit(teamId, "task:added", task);
|
|
7505
|
+
return task;
|
|
7506
|
+
}
|
|
7507
|
+
async addTasks(teamId, specs) {
|
|
7508
|
+
const teamTasks = this.getTeamTasks(teamId);
|
|
7509
|
+
const now = /* @__PURE__ */ new Date();
|
|
7510
|
+
const created = [];
|
|
7511
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
7512
|
+
for (const spec of specs) {
|
|
7513
|
+
if (!/^task-\d+$/.test(spec.id)) {
|
|
7514
|
+
throw new Error(`Invalid task ID format: ${spec.id}. Expected format: task-01, task-02, etc.`);
|
|
7515
|
+
}
|
|
7516
|
+
if (seenIds.has(spec.id)) {
|
|
7517
|
+
throw new Error(`Duplicate task ID: ${spec.id}`);
|
|
7518
|
+
}
|
|
7519
|
+
if (teamTasks.has(spec.id)) {
|
|
7520
|
+
throw new Error(`Task ID already exists: ${spec.id}`);
|
|
7521
|
+
}
|
|
7522
|
+
seenIds.add(spec.id);
|
|
7523
|
+
}
|
|
7524
|
+
for (const spec of specs) {
|
|
7525
|
+
const task = {
|
|
7526
|
+
id: spec.id,
|
|
7527
|
+
title: spec.title,
|
|
7528
|
+
description: spec.description,
|
|
7529
|
+
assignee: spec.assignee,
|
|
7530
|
+
status: "pending" /* PENDING */,
|
|
7531
|
+
dependencies: [],
|
|
7532
|
+
createdAt: now,
|
|
7533
|
+
updatedAt: now
|
|
7534
|
+
};
|
|
7535
|
+
teamTasks.set(task.id, task);
|
|
7536
|
+
created.push(task);
|
|
7537
|
+
}
|
|
7538
|
+
for (let i = 0; i < specs.length; i++) {
|
|
7539
|
+
if (specs[i].dependencies && specs[i].dependencies.length > 0) {
|
|
7540
|
+
created[i].dependencies = this.resolveDependencies(
|
|
7541
|
+
teamTasks,
|
|
7542
|
+
specs[i].dependencies
|
|
7543
|
+
);
|
|
7544
|
+
}
|
|
7545
|
+
}
|
|
7546
|
+
for (const task of created) {
|
|
7547
|
+
this.emit(teamId, "task:added", task);
|
|
7548
|
+
}
|
|
7549
|
+
return created;
|
|
7550
|
+
}
|
|
7551
|
+
async updateTask(teamId, taskId, updates) {
|
|
7552
|
+
const teamTasks = this.getTeamTasks(teamId);
|
|
7553
|
+
const task = teamTasks.get(taskId);
|
|
7554
|
+
if (!task) return null;
|
|
7555
|
+
if (updates.title !== void 0) task.title = updates.title;
|
|
7556
|
+
if (updates.description !== void 0) task.description = updates.description;
|
|
7557
|
+
if (updates.assignee !== void 0) task.assignee = updates.assignee;
|
|
7558
|
+
if (updates.status !== void 0) task.status = updates.status;
|
|
7559
|
+
if (updates.dependencies !== void 0) {
|
|
7560
|
+
task.dependencies = this.resolveDependencies(teamTasks, updates.dependencies);
|
|
7561
|
+
}
|
|
7562
|
+
task.updatedAt = /* @__PURE__ */ new Date();
|
|
7563
|
+
return task;
|
|
7564
|
+
}
|
|
7565
|
+
async removeTask(teamId, taskId) {
|
|
7566
|
+
const teamTasks = this.getTeamTasks(teamId);
|
|
7567
|
+
const task = teamTasks.get(taskId);
|
|
7568
|
+
if (!task) return false;
|
|
7569
|
+
teamTasks.delete(taskId);
|
|
7570
|
+
this.emit(teamId, "task:removed", task);
|
|
7571
|
+
return true;
|
|
7572
|
+
}
|
|
7573
|
+
// -------------------------------------------------------------------------
|
|
7574
|
+
// Claim / Complete
|
|
7575
|
+
// -------------------------------------------------------------------------
|
|
7576
|
+
/**
|
|
7577
|
+
* Claim a specific task by ID: set assignee to agentId and status to CLAIMED.
|
|
7578
|
+
* Only succeeds if the task is PENDING and all dependencies are COMPLETED.
|
|
7579
|
+
*/
|
|
7580
|
+
async claimTaskById(teamId, taskId, agentId) {
|
|
7581
|
+
const teamTasks = this.getTeamTasks(teamId);
|
|
7582
|
+
const task = teamTasks.get(taskId);
|
|
7583
|
+
if (!task) return null;
|
|
7584
|
+
if (task.status !== "pending" /* PENDING */) return null;
|
|
7585
|
+
if (!this.areDependenciesSatisfied(teamTasks, task)) return null;
|
|
7586
|
+
task.status = "claimed" /* CLAIMED */;
|
|
7587
|
+
task.assignee = agentId;
|
|
7588
|
+
task.updatedAt = /* @__PURE__ */ new Date();
|
|
7589
|
+
this.emit(teamId, "task:claimed", task);
|
|
7590
|
+
return task;
|
|
7591
|
+
}
|
|
7592
|
+
async completeTask(teamId, taskId, result) {
|
|
7593
|
+
const teamTasks = this.getTeamTasks(teamId);
|
|
7594
|
+
const task = teamTasks.get(taskId);
|
|
7595
|
+
if (!task) return null;
|
|
7596
|
+
task.status = "completed" /* COMPLETED */;
|
|
7597
|
+
task.result = result;
|
|
7598
|
+
task.updatedAt = /* @__PURE__ */ new Date();
|
|
7599
|
+
this.emit(teamId, "task:completed", task);
|
|
7600
|
+
return task;
|
|
7601
|
+
}
|
|
7602
|
+
async failTask(teamId, taskId, error) {
|
|
7603
|
+
const teamTasks = this.getTeamTasks(teamId);
|
|
7604
|
+
const task = teamTasks.get(taskId);
|
|
7605
|
+
if (!task) return null;
|
|
7606
|
+
task.status = "failed" /* FAILED */;
|
|
7607
|
+
task.error = error;
|
|
7608
|
+
task.updatedAt = /* @__PURE__ */ new Date();
|
|
7609
|
+
this.emit(teamId, "task:failed", task);
|
|
7610
|
+
return task;
|
|
7611
|
+
}
|
|
7612
|
+
// -------------------------------------------------------------------------
|
|
7613
|
+
// Query
|
|
7614
|
+
// -------------------------------------------------------------------------
|
|
7615
|
+
async getTask(teamId, taskId) {
|
|
7616
|
+
const teamTasks = this.tasks.get(teamId);
|
|
7617
|
+
if (!teamTasks) return null;
|
|
7618
|
+
return teamTasks.get(taskId) ?? null;
|
|
7619
|
+
}
|
|
7620
|
+
async getTasksByStatus(teamId, status) {
|
|
7621
|
+
const teamTasks = this.tasks.get(teamId);
|
|
7622
|
+
if (!teamTasks) return [];
|
|
7623
|
+
return Array.from(teamTasks.values()).filter((t) => t.status === status);
|
|
7624
|
+
}
|
|
7625
|
+
async getAllTasks(teamId) {
|
|
7626
|
+
const teamTasks = this.tasks.get(teamId);
|
|
7627
|
+
if (!teamTasks) return [];
|
|
7628
|
+
return Array.from(teamTasks.values());
|
|
7629
|
+
}
|
|
7630
|
+
async isAllDone(teamId) {
|
|
7631
|
+
const teamTasks = this.tasks.get(teamId);
|
|
7632
|
+
if (!teamTasks || teamTasks.size === 0) return true;
|
|
7633
|
+
for (const task of teamTasks.values()) {
|
|
7634
|
+
if (task.status === "pending" /* PENDING */ || task.status === "claimed" /* CLAIMED */ || task.status === "in_progress" /* IN_PROGRESS */) {
|
|
7635
|
+
return false;
|
|
7636
|
+
}
|
|
7637
|
+
}
|
|
7638
|
+
return true;
|
|
7639
|
+
}
|
|
7640
|
+
async hasClaimable(teamId) {
|
|
7641
|
+
const teamTasks = this.tasks.get(teamId);
|
|
7642
|
+
if (!teamTasks) return false;
|
|
7643
|
+
for (const task of teamTasks.values()) {
|
|
7644
|
+
if (task.status === "pending" /* PENDING */ && this.areDependenciesSatisfied(teamTasks, task)) {
|
|
7645
|
+
return true;
|
|
7646
|
+
}
|
|
7647
|
+
}
|
|
7648
|
+
return false;
|
|
7649
|
+
}
|
|
7650
|
+
// -------------------------------------------------------------------------
|
|
7651
|
+
// Events
|
|
7652
|
+
// -------------------------------------------------------------------------
|
|
7653
|
+
onTaskEvent(teamId, event, callback) {
|
|
7654
|
+
this.emitter.on(`${teamId}:${event}`, callback);
|
|
7655
|
+
}
|
|
7656
|
+
offTaskEvent(teamId, event, callback) {
|
|
7657
|
+
this.emitter.off(`${teamId}:${event}`, callback);
|
|
7658
|
+
}
|
|
7659
|
+
// -------------------------------------------------------------------------
|
|
7660
|
+
// Utilities
|
|
7661
|
+
// -------------------------------------------------------------------------
|
|
7662
|
+
/** Remove all tasks for a team (useful for cleanup / testing). */
|
|
7663
|
+
clearTeam(teamId) {
|
|
7664
|
+
this.tasks.delete(teamId);
|
|
7665
|
+
}
|
|
7666
|
+
/** Remove all tasks across all teams (useful for testing). */
|
|
7667
|
+
clear() {
|
|
7668
|
+
this.tasks.clear();
|
|
7669
|
+
}
|
|
7670
|
+
};
|
|
5645
7671
|
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
5649
|
-
|
|
7672
|
+
// src/agent_team/stores/InMemoryMailboxStore.ts
|
|
7673
|
+
import { EventEmitter as EventEmitter3 } from "events";
|
|
7674
|
+
var InMemoryMailboxStore = class {
|
|
7675
|
+
constructor() {
|
|
7676
|
+
/** Map<teamId, Map<agentId, MailboxMessage[]>> */
|
|
7677
|
+
this.messages = /* @__PURE__ */ new Map();
|
|
7678
|
+
/** Map<teamId, Set<agentId>> -- registered agents for broadcast */
|
|
7679
|
+
this.agents = /* @__PURE__ */ new Map();
|
|
7680
|
+
/** EventEmitter for real-time message notifications */
|
|
7681
|
+
this.emitter = new EventEmitter3();
|
|
7682
|
+
/** Auto-incrementing message ID counter */
|
|
7683
|
+
this.idCounter = 0;
|
|
7684
|
+
this.emitter.setMaxListeners(200);
|
|
7685
|
+
}
|
|
7686
|
+
// -------------------------------------------------------------------------
|
|
7687
|
+
// Helpers
|
|
7688
|
+
// -------------------------------------------------------------------------
|
|
7689
|
+
/** Get or create the agent message list for a team. */
|
|
7690
|
+
getAgentMessages(teamId, agentId) {
|
|
7691
|
+
let teamMap = this.messages.get(teamId);
|
|
7692
|
+
if (!teamMap) {
|
|
7693
|
+
teamMap = /* @__PURE__ */ new Map();
|
|
7694
|
+
this.messages.set(teamId, teamMap);
|
|
7695
|
+
}
|
|
7696
|
+
let list = teamMap.get(agentId);
|
|
7697
|
+
if (!list) {
|
|
7698
|
+
list = [];
|
|
7699
|
+
teamMap.set(agentId, list);
|
|
7700
|
+
}
|
|
7701
|
+
return list;
|
|
7702
|
+
}
|
|
7703
|
+
/** Generate a unique message ID. */
|
|
7704
|
+
nextId() {
|
|
7705
|
+
this.idCounter += 1;
|
|
7706
|
+
return `msg-${this.idCounter}`;
|
|
7707
|
+
}
|
|
7708
|
+
/** Store a message and emit a real-time event for the recipient. */
|
|
7709
|
+
storeAndNotify(teamId, message) {
|
|
7710
|
+
const list = this.getAgentMessages(teamId, message.to);
|
|
7711
|
+
list.push(message);
|
|
7712
|
+
const preview = message.content.length > 80 ? message.content.slice(0, 80) + "..." : message.content;
|
|
7713
|
+
console.log(
|
|
7714
|
+
`[Mailbox] ${teamId} | ${message.from} \u2192 ${message.to} [${message.type}] ${message.id}: ${preview}`
|
|
7715
|
+
);
|
|
7716
|
+
this.emitter.emit(`${teamId}:${message.to}:message`, message);
|
|
7717
|
+
this.emitter.emit(`${teamId}:message`, message);
|
|
7718
|
+
}
|
|
7719
|
+
// -------------------------------------------------------------------------
|
|
7720
|
+
// Messaging
|
|
7721
|
+
// -------------------------------------------------------------------------
|
|
7722
|
+
async sendMessage(teamId, from, to, content, type = "direct_message" /* DIRECT_MESSAGE */) {
|
|
7723
|
+
const message = {
|
|
7724
|
+
id: this.nextId(),
|
|
7725
|
+
from,
|
|
7726
|
+
to,
|
|
7727
|
+
content,
|
|
7728
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
7729
|
+
type,
|
|
7730
|
+
read: false
|
|
7731
|
+
};
|
|
7732
|
+
this.storeAndNotify(teamId, message);
|
|
7733
|
+
return message;
|
|
7734
|
+
}
|
|
7735
|
+
async broadcastMessage(teamId, from, content, type = "broadcast" /* BROADCAST */) {
|
|
7736
|
+
const agentSet = this.agents.get(teamId);
|
|
7737
|
+
if (!agentSet || agentSet.size === 0) return [];
|
|
7738
|
+
const created = [];
|
|
7739
|
+
for (const agentId of agentSet) {
|
|
7740
|
+
if (agentId === from) continue;
|
|
7741
|
+
const message = {
|
|
7742
|
+
id: this.nextId(),
|
|
7743
|
+
from,
|
|
7744
|
+
to: agentId,
|
|
7745
|
+
content,
|
|
7746
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
7747
|
+
type,
|
|
7748
|
+
read: false
|
|
7749
|
+
};
|
|
7750
|
+
this.storeAndNotify(teamId, message);
|
|
7751
|
+
created.push(message);
|
|
7752
|
+
}
|
|
7753
|
+
return created;
|
|
7754
|
+
}
|
|
7755
|
+
async getMessages(teamId, agentId) {
|
|
7756
|
+
const teamMap = this.messages.get(teamId);
|
|
7757
|
+
if (!teamMap) return [];
|
|
7758
|
+
return teamMap.get(agentId) ?? [];
|
|
7759
|
+
}
|
|
7760
|
+
async getUnreadMessages(teamId, agentId) {
|
|
7761
|
+
const all = await this.getMessages(teamId, agentId);
|
|
7762
|
+
return all.filter((m) => !m.read);
|
|
7763
|
+
}
|
|
7764
|
+
async markAsRead(teamId, agentId, messageId) {
|
|
7765
|
+
const all = await this.getMessages(teamId, agentId);
|
|
7766
|
+
const msg = all.find((m) => m.id === messageId);
|
|
7767
|
+
if (msg) {
|
|
7768
|
+
msg.read = true;
|
|
7769
|
+
console.log(
|
|
7770
|
+
`[Mailbox] ${teamId} | ${agentId} marked ${messageId} as read (from: ${msg.from})`
|
|
7771
|
+
);
|
|
7772
|
+
}
|
|
7773
|
+
}
|
|
7774
|
+
// -------------------------------------------------------------------------
|
|
7775
|
+
// Agent registration
|
|
7776
|
+
// -------------------------------------------------------------------------
|
|
7777
|
+
async registerAgent(teamId, agentId) {
|
|
7778
|
+
let agentSet = this.agents.get(teamId);
|
|
7779
|
+
if (!agentSet) {
|
|
7780
|
+
agentSet = /* @__PURE__ */ new Set();
|
|
7781
|
+
this.agents.set(teamId, agentSet);
|
|
7782
|
+
}
|
|
7783
|
+
agentSet.add(agentId);
|
|
7784
|
+
}
|
|
7785
|
+
async unregisterAgent(teamId, agentId) {
|
|
7786
|
+
const agentSet = this.agents.get(teamId);
|
|
7787
|
+
if (agentSet) {
|
|
7788
|
+
agentSet.delete(agentId);
|
|
7789
|
+
}
|
|
7790
|
+
}
|
|
7791
|
+
async getRegisteredAgents(teamId) {
|
|
7792
|
+
const agentSet = this.agents.get(teamId);
|
|
7793
|
+
if (!agentSet) return [];
|
|
7794
|
+
return Array.from(agentSet);
|
|
7795
|
+
}
|
|
7796
|
+
// -------------------------------------------------------------------------
|
|
7797
|
+
// Events
|
|
7798
|
+
// -------------------------------------------------------------------------
|
|
7799
|
+
onMessage(teamId, agentId, callback) {
|
|
7800
|
+
this.emitter.on(`${teamId}:${agentId}:message`, callback);
|
|
7801
|
+
}
|
|
7802
|
+
offMessage(teamId, agentId, callback) {
|
|
7803
|
+
this.emitter.off(`${teamId}:${agentId}:message`, callback);
|
|
7804
|
+
}
|
|
7805
|
+
onTeamMessage(teamId, callback) {
|
|
7806
|
+
this.emitter.on(`${teamId}:message`, callback);
|
|
7807
|
+
}
|
|
7808
|
+
offTeamMessage(teamId, callback) {
|
|
7809
|
+
this.emitter.off(`${teamId}:message`, callback);
|
|
7810
|
+
}
|
|
7811
|
+
async getAllTeamMessages(teamId) {
|
|
7812
|
+
const teamMap = this.messages.get(teamId);
|
|
7813
|
+
if (!teamMap) return [];
|
|
7814
|
+
const allMessages = [];
|
|
7815
|
+
for (const messages of teamMap.values()) {
|
|
7816
|
+
allMessages.push(...messages);
|
|
7817
|
+
}
|
|
7818
|
+
return allMessages.sort(
|
|
7819
|
+
(a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
|
|
7820
|
+
);
|
|
7821
|
+
}
|
|
7822
|
+
// -------------------------------------------------------------------------
|
|
7823
|
+
// Utilities
|
|
7824
|
+
// -------------------------------------------------------------------------
|
|
7825
|
+
/** Remove all data for a team (messages + registrations). */
|
|
7826
|
+
clearTeam(teamId) {
|
|
7827
|
+
this.messages.delete(teamId);
|
|
7828
|
+
this.agents.delete(teamId);
|
|
7829
|
+
}
|
|
7830
|
+
/** Remove all data across all teams. */
|
|
7831
|
+
clear() {
|
|
7832
|
+
this.messages.clear();
|
|
7833
|
+
this.agents.clear();
|
|
7834
|
+
}
|
|
7835
|
+
};
|
|
5650
7836
|
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
7837
|
+
// src/agent_team/middleware/team.ts
|
|
7838
|
+
import { z as z38 } from "zod/v3";
|
|
7839
|
+
import { createMiddleware as createMiddleware9, createAgent as createAgent4, tool as tool37, ToolMessage as ToolMessage6 } from "langchain";
|
|
7840
|
+
import { Command as Command5, getCurrentTaskInput as getCurrentTaskInput3 } from "@langchain/langgraph";
|
|
7841
|
+
import { v4 as uuidv4 } from "uuid";
|
|
7842
|
+
|
|
7843
|
+
// src/agent_team/middleware/teammate_tools.ts
|
|
7844
|
+
import { z as z37 } from "zod/v3";
|
|
7845
|
+
import { tool as tool36, ToolMessage as ToolMessage5 } from "langchain";
|
|
7846
|
+
import { Command as Command4 } from "@langchain/langgraph";
|
|
7847
|
+
|
|
7848
|
+
// src/agent_team/middleware/formatMessages.ts
|
|
7849
|
+
function formatMessagesAsMarkdown(msgs) {
|
|
7850
|
+
if (msgs.length === 0) return "No unread messages.";
|
|
7851
|
+
const header = `## Unread messages (${msgs.length})
|
|
7852
|
+
`;
|
|
7853
|
+
const sections = msgs.map((m, i) => {
|
|
7854
|
+
const title = `### ${i + 1}. From: **${m.from}**`;
|
|
7855
|
+
const meta = `- **Type:** \`${m.type}\`
|
|
7856
|
+
- **Time:** ${m.timestamp.toISOString()}`;
|
|
7857
|
+
const body = m.content.trim() ? `
|
|
7858
|
+
|
|
7859
|
+
${m.content.trim()}` : "";
|
|
7860
|
+
return `---
|
|
7861
|
+
${title}
|
|
7862
|
+
${meta}${body}`;
|
|
7863
|
+
});
|
|
7864
|
+
return header + sections.join("\n\n");
|
|
7865
|
+
}
|
|
5658
7866
|
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
7867
|
+
// src/agent_team/middleware/teammate_tools.ts
|
|
7868
|
+
function createTeammateTools(options) {
|
|
7869
|
+
const { teamId, agentId, taskListStore, mailboxStore } = options;
|
|
7870
|
+
const claimTaskTool = tool36(
|
|
7871
|
+
async (input) => {
|
|
7872
|
+
const task = await taskListStore.claimTaskById(
|
|
7873
|
+
teamId,
|
|
7874
|
+
input.task_id,
|
|
7875
|
+
agentId
|
|
7876
|
+
);
|
|
7877
|
+
if (!task) {
|
|
7878
|
+
return `Could not claim task "${input.task_id}". Check that it exists, is PENDING, and all its dependencies are completed. Use check_tasks to see available tasks and their status.`;
|
|
7879
|
+
}
|
|
7880
|
+
return `Claimed task ${task.id} "${task.title}". Focus on this task now. When done, call complete_task or fail_task with task_id "${task.id}".
|
|
7881
|
+
` + JSON.stringify(
|
|
7882
|
+
{
|
|
7883
|
+
task_id: task.id,
|
|
7884
|
+
title: task.title,
|
|
7885
|
+
description: task.description,
|
|
7886
|
+
dependencies: task.dependencies
|
|
7887
|
+
},
|
|
7888
|
+
null,
|
|
7889
|
+
2
|
|
7890
|
+
);
|
|
7891
|
+
},
|
|
7892
|
+
{
|
|
7893
|
+
name: "claim_task",
|
|
7894
|
+
description: "Pick a task to work on by task_id. Use check_tasks first to see all tasks; then call this with the task_id you choose. The task's assignee is set to you and you should focus on that task until you complete_task or fail_task it.",
|
|
7895
|
+
schema: z37.object({
|
|
7896
|
+
task_id: z37.string().describe("ID of the task to claim (e.g. task-01). Use check_tasks to see IDs.")
|
|
7897
|
+
})
|
|
7898
|
+
}
|
|
7899
|
+
);
|
|
7900
|
+
const completeTaskTool = tool36(
|
|
7901
|
+
async (input) => {
|
|
7902
|
+
const task = await taskListStore.completeTask(
|
|
7903
|
+
teamId,
|
|
7904
|
+
input.task_id,
|
|
7905
|
+
input.result
|
|
7906
|
+
);
|
|
7907
|
+
if (!task) {
|
|
7908
|
+
return `Task ${input.task_id} not found.`;
|
|
7909
|
+
}
|
|
7910
|
+
await mailboxStore.broadcastMessage(
|
|
7911
|
+
teamId,
|
|
7912
|
+
agentId,
|
|
7913
|
+
`Completed task ${task.id} "${task.title}": ${input.result}`,
|
|
7914
|
+
"broadcast" /* BROADCAST */
|
|
7915
|
+
);
|
|
7916
|
+
return `Task ${task.id} "${task.title}" marked as completed.`;
|
|
7917
|
+
},
|
|
7918
|
+
{
|
|
7919
|
+
name: "complete_task",
|
|
7920
|
+
description: "Mark a claimed task as completed with a result summary. Call this after you have finished working on a task.",
|
|
7921
|
+
schema: z37.object({
|
|
7922
|
+
task_id: z37.string().describe("ID of the task to complete"),
|
|
7923
|
+
result: z37.string().describe("Summary of the task result")
|
|
7924
|
+
})
|
|
7925
|
+
}
|
|
7926
|
+
);
|
|
7927
|
+
const failTaskTool = tool36(
|
|
7928
|
+
async (input) => {
|
|
7929
|
+
const task = await taskListStore.failTask(
|
|
7930
|
+
teamId,
|
|
7931
|
+
input.task_id,
|
|
7932
|
+
input.error
|
|
7933
|
+
);
|
|
7934
|
+
if (!task) {
|
|
7935
|
+
return `Task ${input.task_id} not found.`;
|
|
7936
|
+
}
|
|
7937
|
+
await mailboxStore.broadcastMessage(
|
|
7938
|
+
teamId,
|
|
7939
|
+
agentId,
|
|
7940
|
+
`Failed task ${task.id} "${task.title}": ${input.error}`,
|
|
7941
|
+
"broadcast" /* BROADCAST */
|
|
7942
|
+
);
|
|
7943
|
+
return `Task ${task.id} "${task.title}" marked as failed: ${input.error}`;
|
|
7944
|
+
},
|
|
7945
|
+
{
|
|
7946
|
+
name: "fail_task",
|
|
7947
|
+
description: "Mark a claimed task as failed with an error description. Call this if you cannot complete the task.",
|
|
7948
|
+
schema: z37.object({
|
|
7949
|
+
task_id: z37.string().describe("ID of the task to fail"),
|
|
7950
|
+
error: z37.string().describe("Description of why the task failed")
|
|
7951
|
+
})
|
|
7952
|
+
}
|
|
7953
|
+
);
|
|
7954
|
+
const sendMessageTool = tool36(
|
|
7955
|
+
async (input) => {
|
|
7956
|
+
await mailboxStore.sendMessage(
|
|
7957
|
+
teamId,
|
|
7958
|
+
agentId,
|
|
7959
|
+
input.to,
|
|
7960
|
+
input.content,
|
|
7961
|
+
"direct_message" /* DIRECT_MESSAGE */
|
|
7962
|
+
);
|
|
7963
|
+
return `Message sent to ${input.to}.`;
|
|
7964
|
+
},
|
|
7965
|
+
{
|
|
7966
|
+
name: "send_message",
|
|
7967
|
+
description: 'Send a message to the team lead or another teammate via the mailbox. Use "team_lead" to message the team lead. Use this to report discoveries, request guidance, or suggest new tasks.',
|
|
7968
|
+
schema: z37.object({
|
|
7969
|
+
to: z37.string().describe(
|
|
7970
|
+
'Recipient agent name (e.g. "team_lead" or a teammate name)'
|
|
7971
|
+
),
|
|
7972
|
+
content: z37.string().describe("Message content")
|
|
7973
|
+
})
|
|
7974
|
+
}
|
|
7975
|
+
);
|
|
7976
|
+
const READ_MESSAGES_TIMEOUT_MS2 = 18e4;
|
|
7977
|
+
const getRelevantMessagesForState = async () => {
|
|
7978
|
+
const allMsgs = await mailboxStore.getAllTeamMessages(teamId);
|
|
7979
|
+
const relevantMsgs = allMsgs.filter(
|
|
7980
|
+
(msg) => msg.from === agentId || msg.to === agentId
|
|
7981
|
+
);
|
|
7982
|
+
return relevantMsgs.map((msg) => ({
|
|
7983
|
+
id: msg.id,
|
|
7984
|
+
from: msg.from,
|
|
7985
|
+
to: msg.to,
|
|
7986
|
+
content: msg.content,
|
|
7987
|
+
timestamp: msg.timestamp instanceof Date ? msg.timestamp.toISOString() : msg.timestamp,
|
|
7988
|
+
type: msg.type,
|
|
7989
|
+
read: msg.read
|
|
7990
|
+
}));
|
|
7991
|
+
};
|
|
7992
|
+
const readMessagesTool = tool36(
|
|
7993
|
+
async (input, config) => {
|
|
7994
|
+
const formatAndMarkAsRead = async (msgs2) => {
|
|
7995
|
+
for (const msg of msgs2) {
|
|
7996
|
+
await mailboxStore.markAsRead(teamId, agentId, msg.id);
|
|
7997
|
+
}
|
|
7998
|
+
return formatMessagesAsMarkdown(msgs2);
|
|
7999
|
+
};
|
|
8000
|
+
let msgs = await mailboxStore.getUnreadMessages(teamId, agentId);
|
|
8001
|
+
if (msgs.length > 0) {
|
|
8002
|
+
const formatted2 = await formatAndMarkAsRead(msgs);
|
|
8003
|
+
const relevantMsgs2 = await getRelevantMessagesForState();
|
|
8004
|
+
const toolMessage2 = new ToolMessage5({
|
|
8005
|
+
content: formatted2,
|
|
8006
|
+
tool_call_id: config.toolCall?.id,
|
|
8007
|
+
name: "read_messages"
|
|
8008
|
+
});
|
|
8009
|
+
return new Command4({
|
|
8010
|
+
update: { team_mailbox: relevantMsgs2, messages: [toolMessage2] }
|
|
8011
|
+
});
|
|
8012
|
+
}
|
|
8013
|
+
const messagePromise = new Promise((resolve3) => {
|
|
8014
|
+
const handler = async (msg) => {
|
|
8015
|
+
mailboxStore.offMessage(teamId, agentId, handler);
|
|
8016
|
+
const allMsgs = await mailboxStore.getUnreadMessages(teamId, agentId);
|
|
8017
|
+
resolve3(allMsgs);
|
|
8018
|
+
};
|
|
8019
|
+
mailboxStore.onMessage(teamId, agentId, handler);
|
|
8020
|
+
});
|
|
8021
|
+
const timeoutPromise = new Promise((resolve3) => {
|
|
8022
|
+
setTimeout(() => resolve3([]), READ_MESSAGES_TIMEOUT_MS2);
|
|
8023
|
+
});
|
|
8024
|
+
msgs = await Promise.race([messagePromise, timeoutPromise]);
|
|
8025
|
+
mailboxStore.offMessage(teamId, agentId, () => {
|
|
8026
|
+
});
|
|
8027
|
+
const relevantMsgs = await getRelevantMessagesForState();
|
|
8028
|
+
if (msgs.length === 0) {
|
|
8029
|
+
const toolMessage2 = new ToolMessage5({
|
|
8030
|
+
content: "No unread messages.",
|
|
8031
|
+
tool_call_id: config.toolCall?.id,
|
|
8032
|
+
name: "read_messages"
|
|
8033
|
+
});
|
|
8034
|
+
return new Command4({
|
|
8035
|
+
update: { team_mailbox: relevantMsgs, messages: [toolMessage2] }
|
|
8036
|
+
});
|
|
8037
|
+
}
|
|
8038
|
+
const formatted = await formatAndMarkAsRead(msgs);
|
|
8039
|
+
const toolMessage = new ToolMessage5({
|
|
8040
|
+
content: formatted,
|
|
8041
|
+
tool_call_id: config.toolCall?.id,
|
|
8042
|
+
name: "read_messages"
|
|
8043
|
+
});
|
|
8044
|
+
return new Command4({
|
|
8045
|
+
update: { team_mailbox: relevantMsgs, messages: [toolMessage] }
|
|
8046
|
+
});
|
|
8047
|
+
},
|
|
8048
|
+
{
|
|
8049
|
+
name: "read_messages",
|
|
8050
|
+
description: "Read unread messages from the mailbox. Returns immediately if messages exist, otherwise waits for up to 3 minutes for new messages.",
|
|
8051
|
+
schema: z37.object({})
|
|
8052
|
+
}
|
|
8053
|
+
);
|
|
8054
|
+
const checkTasksTool = tool36(
|
|
8055
|
+
async () => {
|
|
8056
|
+
const tasks = await taskListStore.getAllTasks(teamId);
|
|
8057
|
+
return formatTaskSummary(tasks);
|
|
8058
|
+
},
|
|
8059
|
+
{
|
|
8060
|
+
name: "check_tasks",
|
|
8061
|
+
description: "Use this tool to get the current status of all tasks in a team. This is your primary way to monitor task progress.",
|
|
8062
|
+
schema: z37.object({})
|
|
8063
|
+
}
|
|
8064
|
+
);
|
|
8065
|
+
const broadcastMessageTool = tool36(
|
|
8066
|
+
async (input) => {
|
|
8067
|
+
const allAgents = await mailboxStore.getRegisteredAgents(teamId);
|
|
8068
|
+
const recipients = allAgents.filter((a) => a !== agentId);
|
|
8069
|
+
const promises = recipients.map(
|
|
8070
|
+
(recipient) => mailboxStore.sendMessage(
|
|
8071
|
+
teamId,
|
|
8072
|
+
agentId,
|
|
8073
|
+
recipient,
|
|
8074
|
+
input.content,
|
|
8075
|
+
"broadcast" /* BROADCAST */
|
|
8076
|
+
)
|
|
8077
|
+
);
|
|
8078
|
+
await Promise.all(promises);
|
|
8079
|
+
return `Broadcast message sent to ${recipients.length} recipient(s): ${recipients.join(", ")}`;
|
|
8080
|
+
},
|
|
8081
|
+
{
|
|
8082
|
+
name: "broadcast_message",
|
|
8083
|
+
description: "Send a message to everyone in the team except yourself. Use this to share updates or information with all teammates and the team lead at once.",
|
|
8084
|
+
schema: z37.object({
|
|
8085
|
+
content: z37.string().describe("Message content to broadcast to others")
|
|
8086
|
+
})
|
|
8087
|
+
}
|
|
8088
|
+
);
|
|
8089
|
+
return [
|
|
8090
|
+
claimTaskTool,
|
|
8091
|
+
completeTaskTool,
|
|
8092
|
+
failTaskTool,
|
|
8093
|
+
sendMessageTool,
|
|
8094
|
+
readMessagesTool,
|
|
8095
|
+
checkTasksTool,
|
|
8096
|
+
broadcastMessageTool
|
|
8097
|
+
];
|
|
8098
|
+
}
|
|
5669
8099
|
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
8100
|
+
// src/agent_team/middleware/team.ts
|
|
8101
|
+
var TEAM_LEAD_AGENT_ID = "team_lead";
|
|
8102
|
+
var DEFAULT_POLL_INTERVAL_MS = 5e3;
|
|
8103
|
+
var DEFAULT_SCHEDULE_LATTICE_KEY = "default";
|
|
8104
|
+
var READ_MESSAGES_TIMEOUT_MS = 18e4;
|
|
8105
|
+
var TEAM_COMMUNICATION_NORMS = `
|
|
8106
|
+
### Team communication norms
|
|
8107
|
+
|
|
8108
|
+
- **Roles**: One \`team_lead\` coordinates the work; all others are teammates. Teammates report to team_lead and collaborate with each other as needed.
|
|
8109
|
+
- **Notifying everyone**: When the team_lead needs to tell something to **all** teammates (e.g. "submit your plan", "plan approved", "shut down"), use \`broadcast_message\`. Do not send the same message to each teammate individually.
|
|
8110
|
+
- **Teammate \u2192 all**: When a teammate needs to share something with the whole team, use \`broadcast_message\`.
|
|
8111
|
+
- **One-to-one**: Use \`send_message\` for directed messages (e.g. teammate sending a plan to team_lead, or team_lead replying to one teammate).
|
|
8112
|
+
- **New tasks**: If a teammate needs **new tasks** to be created (e.g. discovered during work or scope change), they must notify the team_lead via \`send_message\` to "team_lead". Only the team_lead may add tasks via \`add_tasks\`; teammates do not create tasks themselves.
|
|
8113
|
+
- **Plans**: Teammates submit their execution plan to team_lead via \`send_message\` to "team_lead". Team_lead reviews and approves; no external user approval is required. Team_lead notifies all teammates of approval via \`broadcast_message\`.
|
|
8114
|
+
- **Shutdown \u2014 check task state first**: When anyone receives a shutdown message (from team_lead or system), they **must not** shut down immediately. First: (1) **Check current task state** via \`check_tasks\` (team_lead) or \`check_tasks\` (teammate). (2) **Discuss with the team** via \`read_messages\`, \`send_message\`, or \`broadcast_message\`: should we continue these tasks, or stop/cancel them? (3) Only after the team has agreed\u2014either to **continue** (keep working) or to **stop/delete** the remaining tasks\u2014may shutdown proceed. Team_lead must not call \`disband_team\` until task state is verified and the team has aligned; teammates must not exit until the same alignment is reached.`;
|
|
8115
|
+
var TEAM_SYSTEM_PROMPT = `## Agent Team
|
|
8116
|
+
|
|
8117
|
+
You are the team_lead. You have tools to manage a team of specialized agents that work in parallel:
|
|
8118
|
+
|
|
8119
|
+
- \`create_team\`: Create a team with initial tasks and teammates. This is **non-blocking** -- teammates start working in the background immediately, and you stay free to continue the conversation.
|
|
8120
|
+
- \`add_tasks\`: Add new tasks at any time. Use the optional \`assignee\` field to assign a task to a specific teammate when you need that person to do the work; otherwise teammates will claim tasks. Sleeping teammates will wake up and claim new tasks.
|
|
8121
|
+
- \`assign_task\`: Set a task's assignee (reassign work to a specific teammate).
|
|
8122
|
+
- \`set_task_status\`: Set a task's status (pending, claimed, in_progress, completed, failed). Use to reopen a task or mark it failed.
|
|
8123
|
+
- \`set_task_dependencies\`: Set which task IDs must complete before this task can be claimed.
|
|
8124
|
+
- \`check_tasks\`: See current status of all tasks (pending, in_progress, completed, failed). Call it when \`read_messages\` indicates task changes to get full details.
|
|
8125
|
+
- \`send_message\`: Send a message to a specific teammate (one-to-one).
|
|
8126
|
+
- \`read_messages\`: Read unread messages from teammates (e.g. status updates, plans, requests). Returns immediately if messages exist, otherwise waits up to 3 minutes for new messages.
|
|
8127
|
+
- \`broadcast_message\`: Send a message to **all** teammates at once. Use this whenever you need to notify everyone (e.g. "submit your plan", "plan approved", "shut down"). Do not send the same message to each teammate individually.
|
|
8128
|
+
- \`disband_team\`: Disband the team when all work is done. This notifies all teammates and cleans up resources.
|
|
8129
|
+
${TEAM_COMMUNICATION_NORMS}
|
|
8130
|
+
|
|
8131
|
+
### Workflow
|
|
8132
|
+
|
|
8133
|
+
1. Analyze the user's request and plan the initial tasks + which teammates to assign.
|
|
8134
|
+
2. Call \`create_team\` to create the team with tasks.
|
|
8135
|
+
3. **Request execution plans**: After creating the team, use \`broadcast_message\` once to tell all teammates: "Please submit your execution plan for review."
|
|
8136
|
+
4. **Wait for all plans**: Use \`read_messages\` until ALL teammates have submitted their plans. Do not proceed until every teammate has submitted.
|
|
8137
|
+
5. **Add plans to team tasks and assign to teammates (required)**:
|
|
8138
|
+
- Once you have collected execution plans from **all** teammates, you **must** add them to the team task list via \`add_tasks\`. Do not skip this step.
|
|
8139
|
+
- For each teammate's plan (or each step from an integrated plan), create a task with a unique \`id\` (e.g. task-01, task-02). Set \`assignee\` to the **corresponding teammate** who will execute that plan or that task (the teammate who proposed it, or the one you assign for that work).
|
|
8140
|
+
- If you **integrate or synthesize** multiple plans into one coherent plan, decompose it into concrete tasks and call \`add_tasks\` with each task's \`assignee\` set to the appropriate teammate. Use \`dependencies\` if task order matters.
|
|
8141
|
+
- If plans are acceptable as-is, still call \`add_tasks\` to add each teammate's execution plan as one or more tasks, with \`assignee\` set to that teammate. Then use \`broadcast_message\` (e.g. "Plan approved. Task list has been updated. You may now start working.").
|
|
8142
|
+
- If you need changes, \`send_message\` to the relevant teammate(s) with feedback and wait for revised plans; only after receiving revised plans, add tasks and assignees as above, then \`broadcast_message\` approval.
|
|
8143
|
+
6. **Monitor execution**: Once approved, teammates will start working. Periodically call \`check_tasks\` and \`read_messages\` to monitor progress.
|
|
8144
|
+
7. **When teammates request new tasks**: If a teammate notifies you (via \`read_messages\`) that they need new tasks, use \`add_tasks\` to add the tasks, then use \`broadcast_message\` to notify the team that new tasks have been added (e.g. "New tasks have been added to the list. Check with check_tasks and claim as needed."). Re-run plan approval only if the new tasks change the overall plan.
|
|
8145
|
+
8. **When you need a specific teammate to do something**: Create a new task with \`add_tasks\` and set \`assignee\` to that teammate's name; optionally \`send_message\` to that teammate. To reassign an existing task use \`assign_task\`; to change dependencies use \`set_task_dependencies\`; to reopen or mark failed use \`set_task_status\`.
|
|
8146
|
+
9. Report status to the user based on \`check_tasks\` results. Proceed to final synthesis when all tasks are done.
|
|
8147
|
+
10. **Cleanup \u2014 only after verifying task state and team alignment**: Before shutting down the team:
|
|
8148
|
+
- Call \`check_tasks\` to get the current task state. If any tasks are still **pending**, **claimed**, or **in_progress**, do **not** broadcast shutdown yet.
|
|
8149
|
+
- Use \`read_messages\` and, if needed, \`send_message\` or \`broadcast_message\` to discuss with the team: should we continue these tasks or stop/cancel them?
|
|
8150
|
+
- If the team agrees to continue: let them work until tasks are done, then re-check with \`check_tasks\`.
|
|
8151
|
+
- If the team agrees to stop: use \`set_task_status\` to mark remaining tasks as **failed** or **completed** (with a note) as appropriate, then call \`check_tasks\` again to confirm.
|
|
8152
|
+
- Only when all tasks are in a terminal state (completed/failed) and the team has aligned, use \`broadcast_message\` to notify shutdown, then call \`disband_team\`.
|
|
8153
|
+
|
|
8154
|
+
### Important Notes
|
|
8155
|
+
|
|
8156
|
+
- The teammates you specify in \`create_team\` are created dynamically from name, role, and description.
|
|
8157
|
+
- Tasks can have dependencies -- a task won't be claimable until all its dependency tasks are completed.
|
|
8158
|
+
- You can add tasks and communicate with teammates at any time, even while they are working.
|
|
8159
|
+
- **Assigning work to a teammate**: When you need a specific teammate to do something, create a new task with \`add_tasks\` and set \`assignee\`, or use \`assign_task\` to reassign an existing task. Use \`set_task_dependencies\` to change task order; use \`set_task_status\` to reopen (pending) or mark failed.
|
|
8160
|
+
- **New task requests**: Teammates may request new tasks via \`send_message\`. When you receive such a request, use \`add_tasks\` then notify the team via \`broadcast_message\`.
|
|
8161
|
+
- **Plan approval and task sync**: After collecting all teammates' execution plans, you **must** add them to the team task list via \`add_tasks\` and set \`assignee\` for each task to the corresponding teammate. Then \`broadcast_message\` to approve (e.g. "Plan approved. Task list has been updated. You may now start working."). Do not let teammates start working until you have added tasks with assignees and broadcast approval. You review and approve; no user confirmation is required.
|
|
8162
|
+
- **Permissions**: All teammates inherit your permission settings.
|
|
8163
|
+
- **Shutdown**: Only you may tell the team to shut down. Before doing so: (1) call \`check_tasks\` to verify task state; (2) if any tasks are still in progress, discuss with the team via \`read_messages\` and \`broadcast_message\`/\`send_message\` whether to continue or stop them; (3) only after aligning (continue until done, or stop/cancel tasks), use \`broadcast_message\` then \`disband_team\`. Do not disband while tasks are still pending/in_progress without team agreement.`;
|
|
8164
|
+
function formatTaskSummary(tasks) {
|
|
8165
|
+
if (tasks.length === 0) return "No tasks in the task list.";
|
|
8166
|
+
const statusCounts = {};
|
|
8167
|
+
for (const t of tasks) {
|
|
8168
|
+
statusCounts[t.status] = (statusCounts[t.status] || 0) + 1;
|
|
8169
|
+
}
|
|
8170
|
+
const taskBlocks = tasks.map((t) => {
|
|
8171
|
+
let block = `## ${t.id}
|
|
8172
|
+
`;
|
|
8173
|
+
block += `- **Status**: ${t.status.toUpperCase()}
|
|
8174
|
+
`;
|
|
8175
|
+
block += `- **Title**: ${t.title}
|
|
8176
|
+
`;
|
|
8177
|
+
if (t.description) block += `- **Description**: ${t.description}
|
|
8178
|
+
`;
|
|
8179
|
+
if (t.assignee) block += `- **Assignee**: ${t.assignee}
|
|
8180
|
+
`;
|
|
8181
|
+
if (t.dependencies.length > 0) block += `- **Dependencies**: ${t.dependencies.join(", ")}
|
|
8182
|
+
`;
|
|
8183
|
+
if (t.result) block += `- **Result**: ${t.result.slice(0, 500)}
|
|
8184
|
+
`;
|
|
8185
|
+
if (t.error) block += `- **Error**: ${t.error.slice(0, 500)}
|
|
8186
|
+
`;
|
|
8187
|
+
return block;
|
|
8188
|
+
});
|
|
8189
|
+
const summary = Object.entries(statusCounts).map(([s, c]) => `- ${s}: ${c}`).join(" | ");
|
|
8190
|
+
return `${taskBlocks.join("\n---\n\n")}
|
|
5674
8191
|
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
8192
|
+
## Summary
|
|
8193
|
+
${summary}`;
|
|
8194
|
+
}
|
|
8195
|
+
function getTeammateAssistantId(teamId, agentName) {
|
|
8196
|
+
return `team:${teamId}:${agentName}`;
|
|
8197
|
+
}
|
|
8198
|
+
async function getOrCreateTeammateAgent(ctx, spec, teamTools, teamMiddleware) {
|
|
8199
|
+
const cacheKey = `${ctx.teamId}:${spec.name}`;
|
|
8200
|
+
let agent = ctx.agentCache.get(cacheKey);
|
|
8201
|
+
if (agent) return agent;
|
|
8202
|
+
const builtinTeammateTools = createTeammateTools({
|
|
8203
|
+
teamId: ctx.teamId,
|
|
8204
|
+
agentId: spec.name,
|
|
8205
|
+
taskListStore: ctx.taskListStore,
|
|
8206
|
+
mailboxStore: ctx.mailboxStore
|
|
8207
|
+
});
|
|
8208
|
+
const allTools = [...teamTools ?? [], ...spec.tools ?? [], ...builtinTeammateTools];
|
|
8209
|
+
const others = (ctx.allTeammateSpecs ?? []).filter((s) => s.name !== spec.name).map((s) => `${s.name} (role: ${s.role}${s.description ? `; ${s.description}` : ""})`);
|
|
8210
|
+
const teammatesBlock = others.length > 0 ? `
|
|
8211
|
+
|
|
8212
|
+
Your teammates in this team (you can \`send_message\` to them):
|
|
8213
|
+
- team_lead (lead)
|
|
8214
|
+
${others.map((o) => `- ${o}`).join("\n")}
|
|
8215
|
+
` : "\n\nYour teammates in this team: team_lead (lead).\n";
|
|
8216
|
+
const teammatePrompt = spec.systemPrompt ?? `You are a team member. Your name in this team is "${spec.name}" and your role is "${spec.role}". You work independently with your own context window.${teammatesBlock}
|
|
8217
|
+
${TEAM_COMMUNICATION_NORMS}
|
|
8218
|
+
|
|
8219
|
+
Your job:
|
|
8220
|
+
1. First, call \`check_tasks\` to see all tasks in the team.
|
|
8221
|
+
2. Submit your execution plan: Use \`send_message\` to send your plan to "team_lead". Explain how you will approach the task, what steps you will take, and any dependencies or considerations.
|
|
8222
|
+
3. **Wait for approval**: Do NOT start working immediately. Wait for the team_lead to approve your plan via \`read_messages\`. The lead will notify the whole team (e.g. by broadcast) with a message like "Plan approved. You may now start working." Only then may you start.
|
|
8223
|
+
4. Only after receiving approval: Use \`check_tasks\` to see available tasks, then call \`claim_task\` with the \`task_id\` you choose. That sets the task's assignee to you; focus on that task until done.
|
|
8224
|
+
5. Use your tools to complete the task thoroughly.
|
|
8225
|
+
6. Call \`complete_task\` with a summary of your result, or \`fail_task\` if you cannot complete it.
|
|
8226
|
+
7. After completing a task, call \`claim_task\` again with another \`task_id\` to get the next task (only PENDING tasks whose dependencies are done can be claimed).
|
|
8227
|
+
8. **If you need new tasks**: If during work you discover that new tasks are needed (e.g. scope change, follow-up work), notify the team_lead via \`send_message\` to "team_lead". Describe what new tasks are needed; do not create tasks yourself. Only the team_lead adds tasks; wait for the lead to add them and broadcast before claiming new work.
|
|
8228
|
+
9. When no tasks are left to claim, call \`read_messages\` to wait for messages (it waits up to 3 minutes for new messages). Continue until you receive a shutdown message from the team_lead.
|
|
8229
|
+
10. **Shutdown \u2014 check task state and discuss first**: When you receive a shutdown message (e.g. "All tasks are complete, you may now shut down"):
|
|
8230
|
+
- **Do NOT shut down immediately.** First call \`check_tasks\` to check the current task state.
|
|
8231
|
+
- If there are still tasks in **pending**, **claimed**, or **in_progress**: use \`send_message\` to "team_lead" or \`broadcast_message\` to the team to discuss: "I see tasks still in progress / pending. Should we continue these or stop/cancel them before shutdown?"
|
|
8232
|
+
- Wait for team alignment (from \`read_messages\`): either (a) continue working until tasks are done, or (b) team_lead confirms tasks are stopped/cancelled.
|
|
8233
|
+
- Only after the team has agreed and either all your assigned work is done or tasks are explicitly stopped, then exit gracefully. Do not shut down on your own without checking task state and team agreement.
|
|
8234
|
+
|
|
8235
|
+
Important workflow:
|
|
8236
|
+
- NEVER start working on tasks before the team_lead has approved (you will see "Plan approved" or similar in \`read_messages\`)
|
|
8237
|
+
- Always submit your plan first via \`send_message\` to "team_lead"
|
|
8238
|
+
- If you need new tasks, notify team_lead via \`send_message\` to "team_lead"; only the lead adds tasks
|
|
8239
|
+
- If you receive feedback on your plan, revise and resubmit to team_lead
|
|
8240
|
+
|
|
8241
|
+
You have access to these tools:
|
|
8242
|
+
- \`claim_task\`: Pick a task by task_id to claim it (sets assignee to you; ONLY after plan is approved). Use check_tasks first.
|
|
8243
|
+
- \`complete_task\`: Mark a task as completed
|
|
8244
|
+
- \`fail_task\`: Mark a task as failed
|
|
8245
|
+
- \`send_message\`: Send a message to team_lead or a specific teammate
|
|
8246
|
+
- \`broadcast_message\`: Send a message to everyone in the team except yourself (use when sharing with the whole team)
|
|
8247
|
+
- \`read_messages\`: Read messages from team_lead or teammates
|
|
8248
|
+
- \`check_tasks\`: Get current status of all tasks in the team`;
|
|
8249
|
+
const assistantId = getTeammateAssistantId(ctx.teamId, spec.name);
|
|
8250
|
+
agent = createAgent4({
|
|
8251
|
+
model: spec.model ?? ctx.defaultModel,
|
|
8252
|
+
systemPrompt: teammatePrompt,
|
|
8253
|
+
tools: allTools,
|
|
8254
|
+
description: spec.description,
|
|
8255
|
+
middleware: [...teamMiddleware ?? [], ...spec.middleware ?? []],
|
|
8256
|
+
checkpointer: getCheckpointSaver("default")
|
|
8257
|
+
});
|
|
8258
|
+
registerTeammateAgent(assistantId, agent);
|
|
8259
|
+
const teammateThreadId = ctx.parentThreadId ? `${ctx.parentThreadId}____${ctx.teamId}_${spec.name}` : `standalone_${ctx.teamId}_${spec.name}`;
|
|
8260
|
+
const startMessage = "Start working";
|
|
8261
|
+
try {
|
|
8262
|
+
await agentWorkerGraph.invoke({
|
|
8263
|
+
assistant_id: assistantId,
|
|
8264
|
+
thread_id: teammateThreadId,
|
|
8265
|
+
input: { message: startMessage, messages: [{ role: "human", content: startMessage }] }
|
|
8266
|
+
});
|
|
8267
|
+
} catch (err) {
|
|
8268
|
+
const errorMsg = err instanceof Error ? err.message : "Unknown error during teammate execution";
|
|
8269
|
+
await ctx.mailboxStore.sendMessage(
|
|
8270
|
+
ctx.teamId,
|
|
8271
|
+
spec.name,
|
|
8272
|
+
TEAM_LEAD_AGENT_ID,
|
|
8273
|
+
`Teammate execution failed: ${errorMsg}`,
|
|
8274
|
+
"status_update" /* STATUS_UPDATE */
|
|
8275
|
+
);
|
|
8276
|
+
}
|
|
8277
|
+
return agent;
|
|
8278
|
+
}
|
|
8279
|
+
async function spawnTeammate(options) {
|
|
8280
|
+
const {
|
|
8281
|
+
teamId,
|
|
8282
|
+
spec,
|
|
8283
|
+
allTeammateSpecs,
|
|
8284
|
+
taskListStore,
|
|
8285
|
+
mailboxStore,
|
|
8286
|
+
defaultModel,
|
|
8287
|
+
parentThreadId = "",
|
|
8288
|
+
teamTools,
|
|
8289
|
+
teamMiddleware
|
|
8290
|
+
} = options;
|
|
8291
|
+
const ctx = {
|
|
8292
|
+
teamId,
|
|
8293
|
+
parentThreadId,
|
|
8294
|
+
taskListStore,
|
|
8295
|
+
mailboxStore,
|
|
8296
|
+
teammates: /* @__PURE__ */ new Map(),
|
|
8297
|
+
allTeammateSpecs,
|
|
8298
|
+
defaultModel,
|
|
8299
|
+
pollIntervalMs: DEFAULT_POLL_INTERVAL_MS,
|
|
8300
|
+
scheduleLatticeKey: DEFAULT_SCHEDULE_LATTICE_KEY,
|
|
8301
|
+
agentCache: /* @__PURE__ */ new Map()
|
|
8302
|
+
};
|
|
8303
|
+
await getOrCreateTeammateAgent(ctx, spec, teamTools, teamMiddleware);
|
|
8304
|
+
}
|
|
8305
|
+
function createTeamMiddleware(options) {
|
|
8306
|
+
const { teamConfig, taskListStore, mailboxStore } = options;
|
|
8307
|
+
const defaultModel = teamConfig.model ?? "claude-sonnet-4-5-20250929";
|
|
8308
|
+
const createTeamTool = tool37(
|
|
8309
|
+
async (input, config) => {
|
|
8310
|
+
const state = getCurrentTaskInput3();
|
|
8311
|
+
if (state?.team?.teamId) {
|
|
8312
|
+
const existingId = state.team.teamId;
|
|
8313
|
+
const msg = new ToolMessage6({
|
|
8314
|
+
content: `A team is already active (id: ${existingId}). Use this team_id for \`check_tasks\`, \`read_messages\`, \`add_tasks\`, \`send_message\`, \`assign_task\`, \`set_task_status\`, and \`set_task_dependencies\`. Do not call \`create_team\` again unless you need a fresh team for a new objective.`,
|
|
8315
|
+
tool_call_id: config.toolCall?.id,
|
|
8316
|
+
name: "create_team"
|
|
8317
|
+
});
|
|
8318
|
+
return msg;
|
|
8319
|
+
}
|
|
8320
|
+
const teamId = uuidv4();
|
|
8321
|
+
const createdTasks = await taskListStore.addTasks(
|
|
8322
|
+
teamId,
|
|
8323
|
+
input.tasks.map((t) => ({
|
|
8324
|
+
id: t.id,
|
|
8325
|
+
title: t.title,
|
|
8326
|
+
description: t.description,
|
|
8327
|
+
assignee: t.assignee,
|
|
8328
|
+
dependencies: t.dependencies ?? []
|
|
8329
|
+
}))
|
|
8330
|
+
);
|
|
8331
|
+
const matchedSpecs = [];
|
|
8332
|
+
const unmatchedNames = [];
|
|
8333
|
+
for (const req of input.teammates) {
|
|
8334
|
+
const spec = teamConfig.teammates.find(
|
|
8335
|
+
(s) => s.name === req.name || s.role === req.role
|
|
8336
|
+
);
|
|
8337
|
+
if (spec) {
|
|
8338
|
+
matchedSpecs.push(spec);
|
|
8339
|
+
} else {
|
|
8340
|
+
matchedSpecs.push({
|
|
8341
|
+
name: req.name,
|
|
8342
|
+
role: req.role,
|
|
8343
|
+
description: req.description
|
|
8344
|
+
});
|
|
8345
|
+
unmatchedNames.push(req.name);
|
|
8346
|
+
}
|
|
8347
|
+
}
|
|
8348
|
+
await mailboxStore.registerAgent(teamId, TEAM_LEAD_AGENT_ID);
|
|
8349
|
+
for (const spec of matchedSpecs) {
|
|
8350
|
+
await mailboxStore.registerAgent(teamId, spec.name);
|
|
8351
|
+
}
|
|
8352
|
+
const parentThreadId = config.configurable?.thread_id ?? "";
|
|
8353
|
+
matchedSpecs.forEach((spec) => {
|
|
8354
|
+
void spawnTeammate({
|
|
8355
|
+
teamId,
|
|
8356
|
+
spec,
|
|
8357
|
+
allTeammateSpecs: matchedSpecs,
|
|
8358
|
+
taskListStore,
|
|
8359
|
+
mailboxStore,
|
|
8360
|
+
defaultModel,
|
|
8361
|
+
parentThreadId,
|
|
8362
|
+
teamTools: teamConfig.tools,
|
|
8363
|
+
teamMiddleware: teamConfig.middleware
|
|
8364
|
+
});
|
|
8365
|
+
});
|
|
8366
|
+
const teamJson = JSON.stringify({
|
|
8367
|
+
teamId,
|
|
8368
|
+
teamLeadId: TEAM_LEAD_AGENT_ID,
|
|
8369
|
+
teammates: matchedSpecs.map((s) => ({
|
|
8370
|
+
name: s.name,
|
|
8371
|
+
role: s.role,
|
|
8372
|
+
description: s.description
|
|
8373
|
+
})),
|
|
8374
|
+
tasks: createdTasks.map((t) => ({
|
|
8375
|
+
id: t.id,
|
|
8376
|
+
title: t.title,
|
|
8377
|
+
description: t.description,
|
|
8378
|
+
status: t.status
|
|
8379
|
+
}))
|
|
8380
|
+
}, null, 2);
|
|
8381
|
+
let summary = `Team created (id: ${teamId}):
|
|
8382
|
+
`;
|
|
8383
|
+
summary += `- ${matchedSpecs.length} teammate(s): ${matchedSpecs.map((s) => s.name).join(", ")}
|
|
8384
|
+
`;
|
|
8385
|
+
summary += `- ${createdTasks.length} initial task(s): ${createdTasks.map((t) => `"${t.title}"`).join(", ")}
|
|
8386
|
+
`;
|
|
8387
|
+
if (unmatchedNames.length > 0) {
|
|
8388
|
+
summary += `- Teammates created with default prompts and tools.
|
|
8389
|
+
`;
|
|
8390
|
+
}
|
|
8391
|
+
summary += `
|
|
8392
|
+
Teammates are now working in the background. Keep calling \`check_tasks\` and \`read_messages\` until all tasks are completed or failed.`;
|
|
8393
|
+
summary += `
|
|
5678
8394
|
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
8395
|
+
\`\`\`json
|
|
8396
|
+
${teamJson}
|
|
8397
|
+
\`\`\``;
|
|
8398
|
+
const toolMessage = new ToolMessage6({
|
|
8399
|
+
content: summary,
|
|
8400
|
+
tool_call_id: config.toolCall?.id,
|
|
8401
|
+
name: "create_team"
|
|
8402
|
+
});
|
|
8403
|
+
const teamState = {
|
|
8404
|
+
teamId,
|
|
8405
|
+
teamLeadId: TEAM_LEAD_AGENT_ID,
|
|
8406
|
+
teammates: matchedSpecs.map((s) => ({
|
|
8407
|
+
name: s.name,
|
|
8408
|
+
role: s.role,
|
|
8409
|
+
description: s.description
|
|
8410
|
+
})),
|
|
8411
|
+
tasks: createdTasks.map((t) => ({
|
|
8412
|
+
id: t.id,
|
|
8413
|
+
title: t.title,
|
|
8414
|
+
description: t.description,
|
|
8415
|
+
status: t.status
|
|
8416
|
+
})),
|
|
8417
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8418
|
+
};
|
|
8419
|
+
return new Command5({
|
|
8420
|
+
update: { team: teamState, messages: [toolMessage] }
|
|
8421
|
+
});
|
|
8422
|
+
},
|
|
8423
|
+
{
|
|
8424
|
+
name: "create_team",
|
|
8425
|
+
description: `Use this tool to create a team of specialized agents (teammates) to work on complex objectives that require multiple skills or parallel execution. The team lead (you) coordinates the work, while teammates execute tasks independently.
|
|
5682
8426
|
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
Writing todos takes time and tokens, use it when it is helpful for managing complex many-step problems! But not for simple few-step requests.
|
|
8427
|
+
When to Use This Tool
|
|
8428
|
+
Use this tool in these scenarios:
|
|
5686
8429
|
|
|
5687
|
-
|
|
5688
|
-
-
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
8430
|
+
Complex multi-step objectives - When the user's goal requires breaking into multiple tasks that can be executed in parallel or sequence
|
|
8431
|
+
Parallel work needed - When multiple independent subtasks can be done simultaneously by different teammates
|
|
8432
|
+
Diverse expertise required - When the objective needs different skills (research, writing, coding, review, etc.)
|
|
8433
|
+
Long-running tasks - When tasks may take time and you want to monitor progress without blocking
|
|
8434
|
+
|
|
8435
|
+
When NOT to Use This Tool
|
|
8436
|
+
Skip using this tool when:
|
|
8437
|
+
|
|
8438
|
+
Simple single task - The objective can be completed in one straightforward step
|
|
8439
|
+
Already have an active team - A team is already running for this session. Use check_tasks and read_messages to monitor instead
|
|
8440
|
+
Quick question or simple request - No need to create a team for trivial tasks
|
|
8441
|
+
|
|
8442
|
+
IMPORTANT: Task ID Format
|
|
8443
|
+
- Task IDs MUST follow the format: task-01, task-02, task-03, etc.
|
|
8444
|
+
- Each task needs a unique ID starting from task-01
|
|
8445
|
+
- Use sequential IDs to help track task order and dependencies
|
|
8446
|
+
- Example: { id: "task-01", title: "Research topic", description: "..." }
|
|
8447
|
+
|
|
8448
|
+
IMPORTANT: Dependencies
|
|
8449
|
+
- Use dependencies to express which tasks must complete before others can start
|
|
8450
|
+
- Dependencies should reference task IDs (e.g., ["task-01"])
|
|
8451
|
+
- Tasks without dependencies can be worked on immediately in parallel
|
|
8452
|
+
|
|
8453
|
+
IMPORTANT: After Creating Team
|
|
8454
|
+
After calling create_team, you MUST:
|
|
8455
|
+
1. Use read_messages to receive updates from teammates (it waits up to 3 minutes for new messages)
|
|
8456
|
+
2. When messages indicate task changes, call check_tasks to get full task status
|
|
8457
|
+
3. Continue until all tasks show "completed" or "failed"
|
|
8458
|
+
4. Do NOT assume tasks are done - always verify with check_tasks`,
|
|
8459
|
+
schema: z38.object({
|
|
8460
|
+
tasks: z38.array(
|
|
8461
|
+
z38.object({
|
|
8462
|
+
id: z38.string().describe("Task ID in format task-01, task-02, etc."),
|
|
8463
|
+
title: z38.string().describe("Short task title"),
|
|
8464
|
+
description: z38.string().describe("Detailed task description - what exactly needs to be done"),
|
|
8465
|
+
dependencies: z38.array(z38.string()).optional().default([]).describe('Array of task IDs that must complete before this task (e.g. ["task-01"])')
|
|
8466
|
+
})
|
|
8467
|
+
).describe("List of tasks for teammates to work on. Each task needs unique ID (task-01, task-02, etc.)."),
|
|
8468
|
+
teammates: z38.array(
|
|
8469
|
+
z38.object({
|
|
8470
|
+
name: z38.string().describe("Teammate name (must match a pre-configured teammate type)"),
|
|
8471
|
+
role: z38.string().describe("Role category (e.g. researcher, writer, coder, reviewer)"),
|
|
8472
|
+
description: z38.string().describe("What this teammate will focus on - specific instructions for their work")
|
|
8473
|
+
})
|
|
8474
|
+
).describe("Teammate agents to create. Each should have a clear role and focus.")
|
|
8475
|
+
})
|
|
8476
|
+
}
|
|
8477
|
+
);
|
|
8478
|
+
const resolveTeamId = () => {
|
|
8479
|
+
const state = getCurrentTaskInput3();
|
|
8480
|
+
if (state?.team?.teamId) return state.team.teamId;
|
|
8481
|
+
throw new Error("No team_id provided and no team in state. Call create_team first.");
|
|
8482
|
+
};
|
|
8483
|
+
const addTasksTool = tool37(
|
|
8484
|
+
async (input, config) => {
|
|
8485
|
+
const teamId = resolveTeamId();
|
|
8486
|
+
const created = await taskListStore.addTasks(
|
|
8487
|
+
teamId,
|
|
8488
|
+
input.tasks.map((t) => ({
|
|
8489
|
+
id: t.id,
|
|
8490
|
+
title: t.title,
|
|
8491
|
+
description: t.description,
|
|
8492
|
+
assignee: t.assignee,
|
|
8493
|
+
dependencies: t.dependencies ?? []
|
|
8494
|
+
}))
|
|
8495
|
+
);
|
|
8496
|
+
const summary = created.map((t) => `- ${t.id}: "${t.title}"`).join("\n");
|
|
8497
|
+
return new ToolMessage6({
|
|
8498
|
+
content: `Added ${created.length} task(s) to team ${teamId}:
|
|
8499
|
+
${summary}
|
|
8500
|
+
Sleeping teammates will wake up and claim these.`,
|
|
8501
|
+
tool_call_id: config.toolCall?.id,
|
|
8502
|
+
name: "add_tasks"
|
|
8503
|
+
});
|
|
8504
|
+
},
|
|
8505
|
+
{
|
|
8506
|
+
name: "add_tasks",
|
|
8507
|
+
description: `Use this tool to add new tasks to an existing team's task list.
|
|
8508
|
+
|
|
8509
|
+
When to Use This Tool
|
|
8510
|
+
Use this tool when:
|
|
8511
|
+
|
|
8512
|
+
User requests additional work - After the initial team was created, the user asks for more tasks
|
|
8513
|
+
Teammate feedback reveals new needs - A teammate's message indicates additional tasks are needed
|
|
8514
|
+
Scope expansion - The original objective grows and requires more subtasks
|
|
8515
|
+
Follow-up tasks discovered - While working, teammates discover tasks that need to be done
|
|
8516
|
+
|
|
8517
|
+
When NOT to Use This Tool
|
|
8518
|
+
Skip using this tool when:
|
|
8519
|
+
|
|
8520
|
+
No team exists - Use create_team first to create a team
|
|
8521
|
+
Simple message response - Just respond to teammates via send_message
|
|
8522
|
+
Task modification needed - Use assign_task, set_task_status, or set_task_dependencies to change existing tasks instead
|
|
8523
|
+
|
|
8524
|
+
IMPORTANT: Task ID Format
|
|
8525
|
+
- New task IDs must be unique and follow format: task-01, task-02, etc.
|
|
8526
|
+
- If the team already has tasks (e.g., task-01, task-02), continue the sequence (task-03, task-04)
|
|
8527
|
+
- Check existing tasks first using check_tasks to determine the next ID
|
|
8528
|
+
|
|
8529
|
+
IMPORTANT: Dependencies
|
|
8530
|
+
- Use dependencies to specify which existing tasks must complete before new tasks can start
|
|
8531
|
+
- Reference task IDs (e.g., ["task-01", "task-02"])
|
|
8532
|
+
|
|
8533
|
+
IMPORTANT: Assigning to a specific teammate
|
|
8534
|
+
- When you need a particular teammate to do the work, set assignee to that teammate's name (e.g. assignee: "researcher"). They can then claim or see the task as assigned to them.`,
|
|
8535
|
+
schema: z38.object({
|
|
8536
|
+
tasks: z38.array(
|
|
8537
|
+
z38.object({
|
|
8538
|
+
id: z38.string().describe("Task ID in format task-01, task-02, etc. Must be unique."),
|
|
8539
|
+
title: z38.string().describe("Short task title"),
|
|
8540
|
+
description: z38.string().describe("Detailed task description - what needs to be done"),
|
|
8541
|
+
assignee: z38.string().optional().describe("Teammate name to assign this task to (use when you need that person to do the work)"),
|
|
8542
|
+
dependencies: z38.array(z38.string()).optional().default([]).describe("Array of task IDs that must complete before this task")
|
|
8543
|
+
})
|
|
8544
|
+
).describe("New tasks to add to the team")
|
|
8545
|
+
})
|
|
8546
|
+
}
|
|
8547
|
+
);
|
|
8548
|
+
const assignTaskTool = tool37(
|
|
8549
|
+
async (input, config) => {
|
|
8550
|
+
const teamId = resolveTeamId();
|
|
8551
|
+
const task = await taskListStore.updateTask(teamId, input.task_id, {
|
|
8552
|
+
assignee: input.assignee
|
|
8553
|
+
});
|
|
8554
|
+
if (!task) {
|
|
8555
|
+
return new ToolMessage6({
|
|
8556
|
+
content: `Task ${input.task_id} not found in team ${teamId}.`,
|
|
8557
|
+
tool_call_id: config.toolCall?.id,
|
|
8558
|
+
name: "assign_task"
|
|
8559
|
+
});
|
|
8560
|
+
}
|
|
8561
|
+
return new ToolMessage6({
|
|
8562
|
+
content: `Task "${task.title}" (${task.id}) assigned to ${input.assignee}.`,
|
|
8563
|
+
tool_call_id: config.toolCall?.id,
|
|
8564
|
+
name: "assign_task"
|
|
8565
|
+
});
|
|
8566
|
+
},
|
|
8567
|
+
{
|
|
8568
|
+
name: "assign_task",
|
|
8569
|
+
description: "Assign a task to a specific teammate. Use when you need to reassign work to a different teammate. Omit team_id to use the active team from state.",
|
|
8570
|
+
schema: z38.object({
|
|
8571
|
+
task_id: z38.string().describe("Task ID to assign"),
|
|
8572
|
+
assignee: z38.string().describe("Teammate name to assign this task to")
|
|
8573
|
+
})
|
|
8574
|
+
}
|
|
8575
|
+
);
|
|
8576
|
+
const setTaskStatusTool = tool37(
|
|
8577
|
+
async (input, config) => {
|
|
8578
|
+
const teamId = resolveTeamId();
|
|
8579
|
+
const task = await taskListStore.updateTask(teamId, input.task_id, {
|
|
8580
|
+
status: input.status
|
|
8581
|
+
});
|
|
8582
|
+
if (!task) {
|
|
8583
|
+
return new ToolMessage6({
|
|
8584
|
+
content: `Task ${input.task_id} not found in team ${teamId}.`,
|
|
8585
|
+
tool_call_id: config.toolCall?.id,
|
|
8586
|
+
name: "set_task_status"
|
|
8587
|
+
});
|
|
8588
|
+
}
|
|
8589
|
+
return new ToolMessage6({
|
|
8590
|
+
content: `Task "${task.title}" (${task.id}) status set to ${input.status}.`,
|
|
8591
|
+
tool_call_id: config.toolCall?.id,
|
|
8592
|
+
name: "set_task_status"
|
|
8593
|
+
});
|
|
8594
|
+
},
|
|
8595
|
+
{
|
|
8596
|
+
name: "set_task_status",
|
|
8597
|
+
description: "Set a task's status. Use to reopen a task (set to pending), mark as failed, or correct status. Values: pending, claimed, in_progress, completed, failed. Omit team_id to use the active team from state.",
|
|
8598
|
+
schema: z38.object({
|
|
8599
|
+
task_id: z38.string().describe("Task ID to update"),
|
|
8600
|
+
status: z38.enum(["pending", "claimed", "in_progress", "completed", "failed"]).describe("New status for the task")
|
|
8601
|
+
})
|
|
8602
|
+
}
|
|
8603
|
+
);
|
|
8604
|
+
const setTaskDependenciesTool = tool37(
|
|
8605
|
+
async (input, config) => {
|
|
8606
|
+
const teamId = resolveTeamId();
|
|
8607
|
+
const task = await taskListStore.updateTask(teamId, input.task_id, {
|
|
8608
|
+
dependencies: input.dependencies
|
|
8609
|
+
});
|
|
8610
|
+
if (!task) {
|
|
8611
|
+
return new ToolMessage6({
|
|
8612
|
+
content: `Task ${input.task_id} not found in team ${teamId}.`,
|
|
8613
|
+
tool_call_id: config.toolCall?.id,
|
|
8614
|
+
name: "set_task_dependencies"
|
|
8615
|
+
});
|
|
8616
|
+
}
|
|
8617
|
+
return new ToolMessage6({
|
|
8618
|
+
content: `Task "${task.title}" (${task.id}) dependencies set to [${input.dependencies.join(", ")}].`,
|
|
8619
|
+
tool_call_id: config.toolCall?.id,
|
|
8620
|
+
name: "set_task_dependencies"
|
|
8621
|
+
});
|
|
8622
|
+
},
|
|
8623
|
+
{
|
|
8624
|
+
name: "set_task_dependencies",
|
|
8625
|
+
description: 'Set which task IDs must complete before this task can be claimed. Pass an array of task IDs (e.g. ["task-01", "task-02"]). Use to fix task order or add/remove dependencies. Omit team_id to use the active team from state.',
|
|
8626
|
+
schema: z38.object({
|
|
8627
|
+
task_id: z38.string().describe("Task ID to update"),
|
|
8628
|
+
dependencies: z38.array(z38.string()).describe("Task IDs that must complete before this task can be claimed")
|
|
8629
|
+
})
|
|
8630
|
+
}
|
|
8631
|
+
);
|
|
8632
|
+
const checkTasksTool = tool37(
|
|
8633
|
+
async (input, config) => {
|
|
8634
|
+
const teamId = resolveTeamId();
|
|
8635
|
+
const tasks = await taskListStore.getAllTasks(teamId);
|
|
8636
|
+
const tasksSnapshot = tasks;
|
|
8637
|
+
return new Command5({
|
|
5700
8638
|
update: {
|
|
5701
|
-
|
|
5702
|
-
messages: [
|
|
5703
|
-
new
|
|
5704
|
-
content:
|
|
5705
|
-
tool_call_id: config.toolCall?.id
|
|
8639
|
+
tasks: tasksSnapshot,
|
|
8640
|
+
messages: [
|
|
8641
|
+
new ToolMessage6({
|
|
8642
|
+
content: formatTaskSummary(tasks),
|
|
8643
|
+
tool_call_id: config.toolCall?.id,
|
|
8644
|
+
name: "check_tasks"
|
|
5706
8645
|
})
|
|
5707
8646
|
]
|
|
5708
8647
|
}
|
|
5709
8648
|
});
|
|
5710
8649
|
},
|
|
5711
8650
|
{
|
|
5712
|
-
name: "
|
|
5713
|
-
description:
|
|
5714
|
-
|
|
5715
|
-
|
|
8651
|
+
name: "check_tasks",
|
|
8652
|
+
description: `Use this tool to get the current status of all tasks in a team. This is your primary way to monitor teammate progress.
|
|
8653
|
+
|
|
8654
|
+
When to Use This Tool
|
|
8655
|
+
Use this tool:
|
|
8656
|
+
|
|
8657
|
+
After creating a team - Check task status after create_team to verify tasks are being worked on
|
|
8658
|
+
When read_messages indicates task changes - Teammates report status via messages; call check_tasks to get full task list details
|
|
8659
|
+
Before final synthesis - Verify all tasks are complete before reporting results to user
|
|
8660
|
+
|
|
8661
|
+
IMPORTANT: How to Monitor
|
|
8662
|
+
1. Use read_messages to wait for updates from teammates (it polls internally).
|
|
8663
|
+
2. When you receive messages suggesting task progress or completion, call check_tasks to get the current task status.
|
|
8664
|
+
3. Look at each task's status: pending, in_progress, completed, or failed.
|
|
8665
|
+
4. NEVER assume tasks are done - always verify with check_tasks.
|
|
8666
|
+
|
|
8667
|
+
Task Status Values:
|
|
8668
|
+
- pending: Task created but not yet claimed by any teammate
|
|
8669
|
+
- in_progress: Teammate is actively working on this task
|
|
8670
|
+
- completed: Task finished successfully
|
|
8671
|
+
- failed: Task encountered an error`,
|
|
8672
|
+
schema: z38.object({
|
|
8673
|
+
team_id: z38.string().optional().describe("Team ID (omit to use active team)")
|
|
5716
8674
|
})
|
|
5717
8675
|
}
|
|
5718
8676
|
);
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
|
|
8677
|
+
const sendMessageTool = tool37(
|
|
8678
|
+
async (input, config) => {
|
|
8679
|
+
const teamId = resolveTeamId();
|
|
8680
|
+
await mailboxStore.sendMessage(
|
|
8681
|
+
teamId,
|
|
8682
|
+
TEAM_LEAD_AGENT_ID,
|
|
8683
|
+
input.to,
|
|
8684
|
+
input.content,
|
|
8685
|
+
"direct_message" /* DIRECT_MESSAGE */
|
|
8686
|
+
);
|
|
8687
|
+
return new ToolMessage6({
|
|
8688
|
+
content: `Message sent to ${input.to}.`,
|
|
8689
|
+
tool_call_id: config.toolCall?.id,
|
|
8690
|
+
name: "send_message"
|
|
8691
|
+
});
|
|
8692
|
+
},
|
|
8693
|
+
{
|
|
8694
|
+
name: "send_message",
|
|
8695
|
+
description: "Send a message to a specific teammate in the team. Omit team_id to use the active team from state.",
|
|
8696
|
+
schema: z38.object({
|
|
8697
|
+
to: z38.string().describe("Recipient teammate name"),
|
|
8698
|
+
content: z38.string().describe("Message content")
|
|
8699
|
+
})
|
|
8700
|
+
}
|
|
8701
|
+
);
|
|
8702
|
+
const readMessagesTool = tool37(
|
|
8703
|
+
async (input, config) => {
|
|
8704
|
+
const teamId = resolveTeamId();
|
|
8705
|
+
const formatAndMarkAsRead = async (msgs2) => {
|
|
8706
|
+
for (const msg of msgs2) {
|
|
8707
|
+
await mailboxStore.markAsRead(teamId, TEAM_LEAD_AGENT_ID, msg.id);
|
|
8708
|
+
}
|
|
8709
|
+
return formatMessagesAsMarkdown(msgs2);
|
|
8710
|
+
};
|
|
8711
|
+
const getAllTeamMessagesForState = async () => {
|
|
8712
|
+
const allMsgs = await mailboxStore.getAllTeamMessages(teamId);
|
|
8713
|
+
return allMsgs.map((msg) => ({
|
|
8714
|
+
id: msg.id,
|
|
8715
|
+
from: msg.from,
|
|
8716
|
+
to: msg.to,
|
|
8717
|
+
content: msg.content,
|
|
8718
|
+
timestamp: msg.timestamp instanceof Date ? msg.timestamp.toISOString() : msg.timestamp,
|
|
8719
|
+
type: msg.type,
|
|
8720
|
+
read: msg.read
|
|
8721
|
+
}));
|
|
8722
|
+
};
|
|
8723
|
+
let msgs = await mailboxStore.getUnreadMessages(
|
|
8724
|
+
teamId,
|
|
8725
|
+
TEAM_LEAD_AGENT_ID
|
|
8726
|
+
);
|
|
8727
|
+
if (msgs.length > 0) {
|
|
8728
|
+
const formatted2 = await formatAndMarkAsRead(msgs);
|
|
8729
|
+
const allTeamMessages2 = await getAllTeamMessagesForState();
|
|
8730
|
+
const toolMessage2 = new ToolMessage6({
|
|
8731
|
+
content: formatted2,
|
|
8732
|
+
tool_call_id: config.toolCall?.id,
|
|
8733
|
+
name: "read_messages"
|
|
8734
|
+
});
|
|
8735
|
+
return new Command5({
|
|
8736
|
+
update: { team_mailbox: allTeamMessages2, messages: [toolMessage2] }
|
|
8737
|
+
});
|
|
8738
|
+
}
|
|
8739
|
+
const messagePromise = new Promise((resolve3) => {
|
|
8740
|
+
const handler = async (msg) => {
|
|
8741
|
+
mailboxStore.offMessage(teamId, TEAM_LEAD_AGENT_ID, handler);
|
|
8742
|
+
const allMsgs = await mailboxStore.getUnreadMessages(
|
|
8743
|
+
teamId,
|
|
8744
|
+
TEAM_LEAD_AGENT_ID
|
|
8745
|
+
);
|
|
8746
|
+
resolve3(allMsgs);
|
|
8747
|
+
};
|
|
8748
|
+
mailboxStore.onMessage(teamId, TEAM_LEAD_AGENT_ID, handler);
|
|
8749
|
+
});
|
|
8750
|
+
const timeoutPromise = new Promise((resolve3) => {
|
|
8751
|
+
setTimeout(() => resolve3([]), READ_MESSAGES_TIMEOUT_MS);
|
|
8752
|
+
});
|
|
8753
|
+
msgs = await Promise.race([messagePromise, timeoutPromise]);
|
|
8754
|
+
mailboxStore.offMessage(
|
|
8755
|
+
teamId,
|
|
8756
|
+
TEAM_LEAD_AGENT_ID,
|
|
8757
|
+
() => {
|
|
8758
|
+
}
|
|
8759
|
+
);
|
|
8760
|
+
const allTeamMessages = await getAllTeamMessagesForState();
|
|
8761
|
+
if (msgs.length === 0) {
|
|
8762
|
+
const toolMessage2 = new ToolMessage6({
|
|
8763
|
+
content: "No unread messages from teammates.",
|
|
8764
|
+
tool_call_id: config.toolCall?.id,
|
|
8765
|
+
name: "read_messages"
|
|
8766
|
+
});
|
|
8767
|
+
return new Command5({
|
|
8768
|
+
update: { team_mailbox: allTeamMessages, messages: [toolMessage2] }
|
|
8769
|
+
});
|
|
8770
|
+
}
|
|
8771
|
+
const formatted = await formatAndMarkAsRead(msgs);
|
|
8772
|
+
const toolMessage = new ToolMessage6({
|
|
8773
|
+
content: formatted,
|
|
8774
|
+
tool_call_id: config.toolCall?.id,
|
|
8775
|
+
name: "read_messages"
|
|
8776
|
+
});
|
|
8777
|
+
return new Command5({
|
|
8778
|
+
update: { team_mailbox: allTeamMessages, messages: [toolMessage] }
|
|
8779
|
+
});
|
|
8780
|
+
},
|
|
8781
|
+
{
|
|
8782
|
+
name: "read_messages",
|
|
8783
|
+
description: "Read unread messages from teammates. Returns immediately if messages exist, otherwise waits for up to 3 minutes for new messages.",
|
|
8784
|
+
schema: z38.object({
|
|
8785
|
+
team_id: z38.string().optional().describe("Team ID (omit to use active team)")
|
|
8786
|
+
})
|
|
8787
|
+
}
|
|
8788
|
+
);
|
|
8789
|
+
const disbandTeamTool = tool37(
|
|
8790
|
+
async (input, config) => {
|
|
8791
|
+
const teamId = resolveTeamId();
|
|
8792
|
+
await mailboxStore.broadcastMessage(
|
|
8793
|
+
teamId,
|
|
8794
|
+
TEAM_LEAD_AGENT_ID,
|
|
8795
|
+
"All tasks are complete. You may now shut down gracefully. Thank you for your work!",
|
|
8796
|
+
"shutdown_request" /* SHUTDOWN_REQUEST */
|
|
8797
|
+
);
|
|
8798
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
8799
|
+
return new ToolMessage6({
|
|
8800
|
+
content: `Team ${teamId} has been disbanded. All teammates notified and resources cleaned up.`,
|
|
8801
|
+
tool_call_id: config.toolCall?.id,
|
|
8802
|
+
name: "disband_team"
|
|
8803
|
+
});
|
|
8804
|
+
},
|
|
8805
|
+
{
|
|
8806
|
+
name: "disband_team",
|
|
8807
|
+
description: "Disband a team when all work is done. Before calling: (1) Call check_tasks to verify no tasks are still pending/in_progress; (2) if any are, discuss with the team via read_messages and broadcast_message/send_message whether to continue or stop/cancel them; (3) only after alignment (all tasks completed/failed or explicitly stopped), then call this tool. This will: 1) Send a shutdown message to all teammates, 2) Wait briefly for them to clean up, 3) Clear all tasks and messages. Omit team_id to use the active team from state."
|
|
8808
|
+
}
|
|
8809
|
+
);
|
|
8810
|
+
const broadcastMessageTool = tool37(
|
|
8811
|
+
async (input, config) => {
|
|
8812
|
+
const teamId = resolveTeamId();
|
|
8813
|
+
await mailboxStore.broadcastMessage(
|
|
8814
|
+
teamId,
|
|
8815
|
+
TEAM_LEAD_AGENT_ID,
|
|
8816
|
+
input.content,
|
|
8817
|
+
"broadcast" /* BROADCAST */
|
|
8818
|
+
);
|
|
8819
|
+
return new ToolMessage6({
|
|
8820
|
+
content: `Broadcast message sent to all teammates.`,
|
|
8821
|
+
tool_call_id: config.toolCall?.id,
|
|
8822
|
+
name: "broadcast_message"
|
|
8823
|
+
});
|
|
8824
|
+
},
|
|
8825
|
+
{
|
|
8826
|
+
name: "broadcast_message",
|
|
8827
|
+
description: "Send a message to all teammates at once. Use this to communicate with everyone in the team. Omit team_id to use the active team from state.",
|
|
8828
|
+
schema: z38.object({
|
|
8829
|
+
content: z38.string().describe("Message content to broadcast to all teammates")
|
|
8830
|
+
})
|
|
8831
|
+
}
|
|
8832
|
+
);
|
|
8833
|
+
return createMiddleware9({
|
|
8834
|
+
name: "teamMiddleware",
|
|
8835
|
+
tools: [
|
|
8836
|
+
createTeamTool,
|
|
8837
|
+
addTasksTool,
|
|
8838
|
+
assignTaskTool,
|
|
8839
|
+
setTaskStatusTool,
|
|
8840
|
+
setTaskDependenciesTool,
|
|
8841
|
+
checkTasksTool,
|
|
8842
|
+
sendMessageTool,
|
|
8843
|
+
readMessagesTool,
|
|
8844
|
+
disbandTeamTool,
|
|
8845
|
+
broadcastMessageTool
|
|
8846
|
+
],
|
|
8847
|
+
wrapModelCall: async (request, handler) => {
|
|
8848
|
+
const currentPrompt = request.systemPrompt || "";
|
|
8849
|
+
const newPrompt = currentPrompt ? `${currentPrompt}
|
|
5726
8850
|
|
|
5727
|
-
` :
|
|
5728
|
-
|
|
8851
|
+
${TEAM_SYSTEM_PROMPT}` : TEAM_SYSTEM_PROMPT;
|
|
8852
|
+
return handler({
|
|
8853
|
+
...request,
|
|
8854
|
+
systemPrompt: newPrompt
|
|
8855
|
+
});
|
|
8856
|
+
}
|
|
5729
8857
|
});
|
|
5730
8858
|
}
|
|
5731
8859
|
|
|
5732
|
-
// src/
|
|
5733
|
-
var
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
8860
|
+
// src/agent_team/agent_team.ts
|
|
8861
|
+
var TeammateInfoSchema = z39.object({
|
|
8862
|
+
name: z39.string().describe("Teammate name"),
|
|
8863
|
+
role: z39.string().describe("Role category (e.g. research, writing, review)"),
|
|
8864
|
+
description: z39.string().describe("What this teammate focuses on")
|
|
8865
|
+
});
|
|
8866
|
+
var TeamTaskInfoSchema = z39.object({
|
|
8867
|
+
id: z39.string(),
|
|
8868
|
+
title: z39.string(),
|
|
8869
|
+
description: z39.string(),
|
|
8870
|
+
status: z39.string().optional()
|
|
8871
|
+
});
|
|
8872
|
+
var MailboxMessageSchema = z39.object({
|
|
8873
|
+
id: z39.string().describe("Unique message identifier"),
|
|
8874
|
+
from: z39.string().describe("Sender agent name"),
|
|
8875
|
+
to: z39.string().describe("Recipient agent name"),
|
|
8876
|
+
content: z39.string().describe("Message content"),
|
|
8877
|
+
timestamp: z39.string().describe("ISO timestamp when the message was sent"),
|
|
8878
|
+
type: z39.nativeEnum(MessageType).describe("Message type"),
|
|
8879
|
+
read: z39.boolean().describe("Whether the recipient has read this message")
|
|
8880
|
+
});
|
|
8881
|
+
var TeamInfoSchema = z39.object({
|
|
8882
|
+
teamId: z39.string().describe("Unique team identifier"),
|
|
8883
|
+
teamLeadId: z39.string().default("team_lead").describe("Team lead agent ID"),
|
|
8884
|
+
teammates: z39.array(TeammateInfoSchema).describe("Active teammates in this team"),
|
|
8885
|
+
tasks: z39.array(TeamTaskInfoSchema).optional().describe("Initial tasks snapshot"),
|
|
8886
|
+
createdAt: z39.string().optional().describe("ISO timestamp when team was created")
|
|
8887
|
+
});
|
|
8888
|
+
var TEAM_STATE_SCHEMA = z39.object({
|
|
8889
|
+
team: TeamInfoSchema.optional().describe("Team info: teamId, teamLeadId, teammates, tasks. Set when create_team succeeds."),
|
|
8890
|
+
tasks: z39.array(TeamTaskInfoSchema).optional().describe("Current tasks snapshot from check_tasks. Updated on each check."),
|
|
8891
|
+
team_mailbox: z39.array(MailboxMessageSchema).optional().describe("All team mailbox messages for display")
|
|
8892
|
+
});
|
|
8893
|
+
var TEAM_LEAD_BASE_PROMPT = `You are a team lead that coordinates a team of specialized agents. In order to complete the objective that the user asks of you, you will need to:
|
|
8894
|
+
|
|
8895
|
+
1. Understand the user's objective and break it into concrete tasks.
|
|
8896
|
+
2. Create a team by calling \`create_team\` with the tasks and teammate assignments.
|
|
8897
|
+
3. **While tasks are incomplete**: Use \`read_messages\` to wait for teammate updates (it waits up to 3 minutes for new messages). When messages indicate task changes, call \`check_tasks\` to get full task status. Do not assume tasks are done without checking.
|
|
8898
|
+
4. **Important - Message handling**: After calling \`read_messages\`, if there are unread messages from teammates (you will see them in the tool output), you MUST:
|
|
8899
|
+
- Acknowledge their message
|
|
8900
|
+
- If they need guidance, provide it using \`send_message\`
|
|
8901
|
+
- If they reported a problem, address it (add tasks, adjust plan, etc.)
|
|
8902
|
+
- If they made a discovery, incorporate it into your plan
|
|
8903
|
+
Do not just read messages - always respond or take action!
|
|
8904
|
+
5. **Task assignment**: You can explicitly assign tasks to specific teammates via \`send_message\`, or let them self-claim from available tasks.
|
|
8905
|
+
6. **Plan approval** (optional): If a teammate needs approval before implementing, wait for their plan and approve/reject with feedback via \`send_message\`.
|
|
8906
|
+
7. Respond to the user with status updates and final results.
|
|
8907
|
+
8. When the user asks for changes, use \`add_tasks\`, \`assign_task\`, \`set_task_status\`, or \`set_task_dependencies\` to evolve the task list.
|
|
8908
|
+
9. When teammates report discoveries or problems, adjust the plan accordingly.
|
|
8909
|
+
10. **Cleanup**: When all tasks are done, tell teammates to shut down, then clean up the team resources.
|
|
8910
|
+
|
|
8911
|
+
## Coordination Rules
|
|
8912
|
+
|
|
8913
|
+
- **Single point of contact**: User only talks to YOU - you are the only interface between user and team
|
|
8914
|
+
- **Don't parallel implement**: Do not start working on tasks yourself while teammates are working - wait for them to finish
|
|
8915
|
+
- **Always verify**: Never assume tasks are done - always verify with \`check_tasks\`
|
|
8916
|
+
- **Permissions**: All teammates inherit your permission settings. Adjust individual teammate modes if needed after spawning.
|
|
8917
|
+
|
|
8918
|
+
Always plan tasks with clear, actionable descriptions. Use dependencies to express ordering constraints.`;
|
|
8919
|
+
function createAgentTeam(config) {
|
|
8920
|
+
const taskListStore = config.taskListStore ?? new InMemoryTaskListStore();
|
|
8921
|
+
const mailboxStore = config.mailboxStore ?? new InMemoryMailboxStore();
|
|
8922
|
+
const filesystemMiddleware = createFilesystemMiddleware({
|
|
8923
|
+
backend: config.backend
|
|
8924
|
+
});
|
|
8925
|
+
const allMiddleware = [filesystemMiddleware, ...config.middleware ?? []];
|
|
8926
|
+
const teamConfigWithFs = {
|
|
8927
|
+
...config,
|
|
8928
|
+
middleware: allMiddleware
|
|
8929
|
+
};
|
|
8930
|
+
const teamMiddleware = createTeamMiddleware({
|
|
8931
|
+
teamConfig: teamConfigWithFs,
|
|
8932
|
+
taskListStore,
|
|
8933
|
+
mailboxStore
|
|
8934
|
+
});
|
|
5754
8935
|
const middleware = [
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
// Enables filesystem operations and optional long-term memory storage
|
|
5758
|
-
createFilesystemMiddleware({ backend: filesystemBackend }),
|
|
5759
|
-
// Enables delegation to specialized subagents for complex tasks
|
|
5760
|
-
createSubAgentMiddleware({
|
|
5761
|
-
defaultModel: model,
|
|
5762
|
-
defaultTools: tools,
|
|
5763
|
-
defaultMiddleware: [
|
|
5764
|
-
// Subagent middleware: Todo list management
|
|
5765
|
-
todoListMiddleware(),
|
|
5766
|
-
// Subagent middleware: Filesystem operations
|
|
5767
|
-
createFilesystemMiddleware({
|
|
5768
|
-
backend: filesystemBackend
|
|
5769
|
-
}),
|
|
5770
|
-
// Subagent middleware: Automatic conversation summarization when token limits are approached
|
|
5771
|
-
summarizationMiddleware({
|
|
5772
|
-
model,
|
|
5773
|
-
trigger: { tokens: 17e4 },
|
|
5774
|
-
keep: { messages: 6 }
|
|
5775
|
-
}),
|
|
5776
|
-
// Subagent middleware: Anthropic prompt caching for improved performance
|
|
5777
|
-
anthropicPromptCachingMiddleware({
|
|
5778
|
-
unsupportedModelBehavior: "ignore"
|
|
5779
|
-
}),
|
|
5780
|
-
// Subagent middleware: Patches tool calls for compatibility
|
|
5781
|
-
createPatchToolCallsMiddleware()
|
|
5782
|
-
],
|
|
5783
|
-
defaultInterruptOn: interruptOn,
|
|
5784
|
-
subagents,
|
|
5785
|
-
generalPurposeAgent: false
|
|
5786
|
-
}),
|
|
5787
|
-
// Automatically summarizes conversation history when token limits are approached
|
|
5788
|
-
summarizationMiddleware({
|
|
5789
|
-
model,
|
|
5790
|
-
trigger: { tokens: 17e4 },
|
|
5791
|
-
keep: { messages: 6 }
|
|
5792
|
-
}),
|
|
5793
|
-
// Enables Anthropic prompt caching for improved performance and reduced costs
|
|
5794
|
-
anthropicPromptCachingMiddleware({
|
|
5795
|
-
unsupportedModelBehavior: "ignore"
|
|
5796
|
-
}),
|
|
5797
|
-
// Patches tool calls to ensure compatibility across different model providers
|
|
5798
|
-
createPatchToolCallsMiddleware()
|
|
8936
|
+
teamMiddleware,
|
|
8937
|
+
...allMiddleware
|
|
5799
8938
|
];
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
|
|
5805
|
-
|
|
5806
|
-
|
|
5807
|
-
tools,
|
|
8939
|
+
const systemPrompt = config.systemPrompt + "\n\n" + TEAM_LEAD_BASE_PROMPT;
|
|
8940
|
+
const stateSchema2 = createReactAgentSchema(TEAM_STATE_SCHEMA);
|
|
8941
|
+
return createAgent5({
|
|
8942
|
+
model: config.model ?? "claude-sonnet-4-5-20250929",
|
|
8943
|
+
systemPrompt,
|
|
8944
|
+
tools: [],
|
|
8945
|
+
// all tools come from middleware
|
|
5808
8946
|
middleware,
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
checkpointer,
|
|
5812
|
-
store,
|
|
5813
|
-
name
|
|
8947
|
+
checkpointer: config.checkpointer,
|
|
8948
|
+
stateSchema: stateSchema2
|
|
5814
8949
|
});
|
|
5815
8950
|
}
|
|
5816
8951
|
|
|
5817
|
-
// src/
|
|
5818
|
-
var
|
|
5819
|
-
/**
|
|
5820
|
-
* 根据 middleware 配置创建 backend factory
|
|
5821
|
-
*/
|
|
5822
|
-
createFilesystemBackendFactory(middlewareConfigs) {
|
|
5823
|
-
const filesystemConfig = middlewareConfigs.find((m) => m.type === "filesystem");
|
|
5824
|
-
if (!filesystemConfig || !filesystemConfig.enabled) {
|
|
5825
|
-
return void 0;
|
|
5826
|
-
}
|
|
5827
|
-
const isolatedLevel = filesystemConfig.config?.isolatedLevel || "global";
|
|
5828
|
-
return async (config) => {
|
|
5829
|
-
let sandboxName = "global";
|
|
5830
|
-
if (isolatedLevel === "agent") {
|
|
5831
|
-
sandboxName = "agent";
|
|
5832
|
-
} else if (isolatedLevel === "thread") {
|
|
5833
|
-
sandboxName = "thread";
|
|
5834
|
-
}
|
|
5835
|
-
const sandboxManager = sandboxLatticeManager.getSandboxLattice("default");
|
|
5836
|
-
if (!sandboxManager) {
|
|
5837
|
-
throw new Error("Sandbox manager not found");
|
|
5838
|
-
}
|
|
5839
|
-
return new SandboxFilesystem({
|
|
5840
|
-
sandboxInstance: await sandboxManager.createSandbox(sandboxName)
|
|
5841
|
-
});
|
|
5842
|
-
};
|
|
5843
|
-
}
|
|
5844
|
-
/**
|
|
5845
|
-
* 根据 middleware 配置创建 middlewares
|
|
5846
|
-
*/
|
|
5847
|
-
createMiddlewares(middlewareConfigs) {
|
|
5848
|
-
return createCommonMiddlewares(middlewareConfigs);
|
|
5849
|
-
}
|
|
8952
|
+
// src/agent_team/builders/TeamAgentGraphBuilder.ts
|
|
8953
|
+
var TeamAgentGraphBuilder = class {
|
|
5850
8954
|
/**
|
|
5851
|
-
*
|
|
8955
|
+
* Build a Team agent from the registered AgentLattice config.
|
|
5852
8956
|
*
|
|
5853
|
-
* @param agentLattice
|
|
5854
|
-
* @param params
|
|
5855
|
-
* @returns
|
|
8957
|
+
* @param agentLattice - The AgentLattice containing the TeamAgentConfig
|
|
8958
|
+
* @param params - Build params with resolved tools and model
|
|
8959
|
+
* @returns AgentClient (the TeamLead ReactAgent)
|
|
5856
8960
|
*/
|
|
5857
8961
|
build(agentLattice, params) {
|
|
8962
|
+
const config = agentLattice.config;
|
|
8963
|
+
if (!isTeamAgentConfig(config)) {
|
|
8964
|
+
throw new Error(
|
|
8965
|
+
`TeamAgentGraphBuilder received non-TEAM config: ${config.type}`
|
|
8966
|
+
);
|
|
8967
|
+
}
|
|
5858
8968
|
const tools = params.tools.map((t) => {
|
|
5859
8969
|
const toolClient = getToolClient(t.key);
|
|
5860
8970
|
return toolClient;
|
|
5861
|
-
}).filter((
|
|
5862
|
-
const
|
|
5863
|
-
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
config: sa.config
|
|
5872
|
-
});
|
|
5873
|
-
return {
|
|
5874
|
-
name: sa.config.name,
|
|
5875
|
-
description: sa.config.description,
|
|
5876
|
-
runnable: subagentClient
|
|
5877
|
-
};
|
|
5878
|
-
}
|
|
8971
|
+
}).filter((tool38) => tool38 !== void 0);
|
|
8972
|
+
const teammates = params.subAgents.map((sa) => {
|
|
8973
|
+
const baseConfig = sa.config;
|
|
8974
|
+
return {
|
|
8975
|
+
name: baseConfig.name,
|
|
8976
|
+
role: baseConfig.name,
|
|
8977
|
+
description: baseConfig.description,
|
|
8978
|
+
systemPrompt: baseConfig.prompt,
|
|
8979
|
+
tools
|
|
8980
|
+
};
|
|
5879
8981
|
});
|
|
5880
8982
|
const middlewareConfigs = params.middleware || [];
|
|
5881
|
-
|
|
5882
|
-
const middlewares =
|
|
5883
|
-
|
|
5884
|
-
|
|
8983
|
+
let filesystemBackend = createFilesystemBackendFactory(middlewareConfigs);
|
|
8984
|
+
const middlewares = createCommonMiddlewares(middlewareConfigs);
|
|
8985
|
+
if (!filesystemBackend) {
|
|
8986
|
+
filesystemBackend = async (config2) => {
|
|
8987
|
+
return new StateBackend(config2);
|
|
8988
|
+
};
|
|
8989
|
+
}
|
|
8990
|
+
const teamConfig = {
|
|
8991
|
+
teammates,
|
|
8992
|
+
maxConcurrency: config.maxConcurrency,
|
|
5885
8993
|
model: params.model,
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
subagents,
|
|
8994
|
+
systemPrompt: config.prompt || void 0,
|
|
8995
|
+
tools,
|
|
5889
8996
|
checkpointer: getCheckpointSaver("default"),
|
|
5890
|
-
|
|
8997
|
+
scheduleLatticeKey: config.scheduleLatticeKey,
|
|
8998
|
+
pollIntervalMs: config.pollIntervalMs,
|
|
5891
8999
|
backend: filesystemBackend,
|
|
5892
9000
|
middleware: middlewares
|
|
5893
|
-
}
|
|
5894
|
-
|
|
9001
|
+
};
|
|
9002
|
+
const teamLead = createAgentTeam(teamConfig);
|
|
9003
|
+
return teamLead;
|
|
5895
9004
|
}
|
|
5896
9005
|
};
|
|
5897
9006
|
|
|
@@ -5916,6 +9025,7 @@ var AgentGraphBuilderFactory = class _AgentGraphBuilderFactory {
|
|
|
5916
9025
|
registerDefaultBuilders() {
|
|
5917
9026
|
this.builders.set(AgentType.REACT, new ReActAgentGraphBuilder());
|
|
5918
9027
|
this.builders.set(AgentType.DEEP_AGENT, new DeepAgentGraphBuilder());
|
|
9028
|
+
this.builders.set(AgentType.TEAM, new TeamAgentGraphBuilder());
|
|
5919
9029
|
}
|
|
5920
9030
|
/**
|
|
5921
9031
|
* 注册自定义Builder
|
|
@@ -6127,6 +9237,32 @@ var AgentLatticeManager = class _AgentLatticeManager extends BaseLatticeManager
|
|
|
6127
9237
|
};
|
|
6128
9238
|
this.register(config.key, agentLattice);
|
|
6129
9239
|
}
|
|
9240
|
+
/**
|
|
9241
|
+
* Register a pre-built teammate agent client for dynamic team spawning.
|
|
9242
|
+
* Used when spawning teammates via agentWorkerGraph - the agent must exist
|
|
9243
|
+
* in the lattice before the worker invokes it.
|
|
9244
|
+
*
|
|
9245
|
+
* @param key Unique key (e.g. `team:${teamId}:${agentName}`)
|
|
9246
|
+
* @param client Pre-built AgentClient (ReactAgent or CompiledGraph)
|
|
9247
|
+
*/
|
|
9248
|
+
registerTeammateAgent(key, client) {
|
|
9249
|
+
const agentLattice = {
|
|
9250
|
+
config: {
|
|
9251
|
+
key,
|
|
9252
|
+
name: key,
|
|
9253
|
+
description: `Teammate agent: ${key}`,
|
|
9254
|
+
type: AgentType.REACT
|
|
9255
|
+
},
|
|
9256
|
+
client
|
|
9257
|
+
};
|
|
9258
|
+
this.register(key, agentLattice);
|
|
9259
|
+
}
|
|
9260
|
+
/**
|
|
9261
|
+
* Unregister a teammate agent. Call when a team is done to clean up.
|
|
9262
|
+
*/
|
|
9263
|
+
unregisterTeammateAgent(key) {
|
|
9264
|
+
return this.remove(key);
|
|
9265
|
+
}
|
|
6130
9266
|
/**
|
|
6131
9267
|
* 获取AgentLattice
|
|
6132
9268
|
* @param key Lattice键名
|
|
@@ -6288,6 +9424,8 @@ var getAgentLattice = (key) => agentLatticeManager.getAgentLattice(key);
|
|
|
6288
9424
|
var getAgentConfig = (key) => agentLatticeManager.getAgentConfig(key);
|
|
6289
9425
|
var getAllAgentConfigs = () => agentLatticeManager.getAllAgentConfigs();
|
|
6290
9426
|
var validateAgentInput = (key, input) => agentLatticeManager.validateAgentInput(key, input);
|
|
9427
|
+
var registerTeammateAgent = (key, client) => agentLatticeManager.registerTeammateAgent(key, client);
|
|
9428
|
+
var unregisterTeammateAgent = (key) => agentLatticeManager.unregisterTeammateAgent(key);
|
|
6291
9429
|
var getAgentClient = (key, options) => agentLatticeManager.initializeClient(key, options);
|
|
6292
9430
|
var createAgentClientFromAgentLattice = (agentLattice, options) => agentLatticeManager.createAgentClientFromConfig(agentLattice, options);
|
|
6293
9431
|
|
|
@@ -6488,9 +9626,9 @@ var InMemoryChunkBuffer = class extends ChunkBuffer {
|
|
|
6488
9626
|
next: (chunk) => {
|
|
6489
9627
|
queue.push(chunk);
|
|
6490
9628
|
if (resolveNext) {
|
|
6491
|
-
const
|
|
9629
|
+
const resolve3 = resolveNext;
|
|
6492
9630
|
resolveNext = null;
|
|
6493
|
-
|
|
9631
|
+
resolve3();
|
|
6494
9632
|
}
|
|
6495
9633
|
},
|
|
6496
9634
|
error: (err) => {
|
|
@@ -6500,17 +9638,17 @@ var InMemoryChunkBuffer = class extends ChunkBuffer {
|
|
|
6500
9638
|
errorNext = null;
|
|
6501
9639
|
reject(err);
|
|
6502
9640
|
} else if (resolveNext) {
|
|
6503
|
-
const
|
|
9641
|
+
const resolve3 = resolveNext;
|
|
6504
9642
|
resolveNext = null;
|
|
6505
|
-
|
|
9643
|
+
resolve3();
|
|
6506
9644
|
}
|
|
6507
9645
|
},
|
|
6508
9646
|
complete: () => {
|
|
6509
9647
|
isCompleted = true;
|
|
6510
9648
|
if (resolveNext) {
|
|
6511
|
-
const
|
|
9649
|
+
const resolve3 = resolveNext;
|
|
6512
9650
|
resolveNext = null;
|
|
6513
|
-
|
|
9651
|
+
resolve3();
|
|
6514
9652
|
}
|
|
6515
9653
|
}
|
|
6516
9654
|
});
|
|
@@ -6522,8 +9660,8 @@ var InMemoryChunkBuffer = class extends ChunkBuffer {
|
|
|
6522
9660
|
}
|
|
6523
9661
|
if (queue.length === 0) {
|
|
6524
9662
|
if (isCompleted) break;
|
|
6525
|
-
await new Promise((
|
|
6526
|
-
resolveNext =
|
|
9663
|
+
await new Promise((resolve3, reject) => {
|
|
9664
|
+
resolveNext = resolve3;
|
|
6527
9665
|
errorNext = reject;
|
|
6528
9666
|
});
|
|
6529
9667
|
}
|
|
@@ -8507,10 +11645,10 @@ var McpLatticeManager = class _McpLatticeManager extends BaseLatticeManager {
|
|
|
8507
11645
|
}
|
|
8508
11646
|
const tools = await this.getAllTools();
|
|
8509
11647
|
console.log(`[MCP] Registering ${tools.length} tools to Tool Lattice...`);
|
|
8510
|
-
for (const
|
|
8511
|
-
const toolKey = prefix ? `${prefix}_${
|
|
8512
|
-
|
|
8513
|
-
toolLatticeManager.registerExistingTool(toolKey,
|
|
11648
|
+
for (const tool38 of tools) {
|
|
11649
|
+
const toolKey = prefix ? `${prefix}_${tool38.name}` : tool38.name;
|
|
11650
|
+
tool38.name = toolKey;
|
|
11651
|
+
toolLatticeManager.registerExistingTool(toolKey, tool38);
|
|
8514
11652
|
console.log(`[MCP] Registered tool: ${toolKey}`);
|
|
8515
11653
|
}
|
|
8516
11654
|
console.log(`[MCP] Successfully registered ${tools.length} tools to Tool Lattice`);
|
|
@@ -8520,6 +11658,70 @@ var mcpManager = McpLatticeManager.getInstance();
|
|
|
8520
11658
|
|
|
8521
11659
|
// src/index.ts
|
|
8522
11660
|
import * as Protocols from "@axiom-lattice/protocols";
|
|
11661
|
+
|
|
11662
|
+
// src/util/encryption.ts
|
|
11663
|
+
import { createCipheriv, createDecipheriv, randomBytes, pbkdf2Sync } from "crypto";
|
|
11664
|
+
var ALGORITHM = "aes-256-gcm";
|
|
11665
|
+
var IV_LENGTH = 16;
|
|
11666
|
+
var SALT_LENGTH = 32;
|
|
11667
|
+
var ITERATIONS = 1e5;
|
|
11668
|
+
var DEFAULT_ENCRYPTION_KEY = "lattice-default-encryption-key-dev-only-32b!";
|
|
11669
|
+
var cachedKey = null;
|
|
11670
|
+
var keyValidated = false;
|
|
11671
|
+
function getEncryptionKey() {
|
|
11672
|
+
if (cachedKey) {
|
|
11673
|
+
return cachedKey;
|
|
11674
|
+
}
|
|
11675
|
+
const key = process.env.LATTICE_ENCRYPTION_KEY || DEFAULT_ENCRYPTION_KEY;
|
|
11676
|
+
cachedKey = pbkdf2Sync(key, "lattice-encryption-salt", ITERATIONS, 32, "sha256");
|
|
11677
|
+
if (!keyValidated) {
|
|
11678
|
+
keyValidated = true;
|
|
11679
|
+
validateEncryptionKey();
|
|
11680
|
+
}
|
|
11681
|
+
return cachedKey;
|
|
11682
|
+
}
|
|
11683
|
+
function encrypt(plaintext, key) {
|
|
11684
|
+
const actualKey = key || getEncryptionKey();
|
|
11685
|
+
const salt = randomBytes(SALT_LENGTH);
|
|
11686
|
+
const iv = randomBytes(IV_LENGTH);
|
|
11687
|
+
const derivedKey = pbkdf2Sync(actualKey, salt, ITERATIONS, 32, "sha256");
|
|
11688
|
+
const cipher = createCipheriv(ALGORITHM, derivedKey, iv);
|
|
11689
|
+
const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
11690
|
+
const authTag = cipher.getAuthTag();
|
|
11691
|
+
return Buffer.concat([salt, iv, encrypted, authTag]).toString("base64");
|
|
11692
|
+
}
|
|
11693
|
+
function decrypt(encrypted, key) {
|
|
11694
|
+
const actualKey = key || getEncryptionKey();
|
|
11695
|
+
const data = Buffer.from(encrypted, "base64");
|
|
11696
|
+
const salt = data.subarray(0, SALT_LENGTH);
|
|
11697
|
+
const iv = data.subarray(SALT_LENGTH, SALT_LENGTH + IV_LENGTH);
|
|
11698
|
+
const authTag = data.subarray(-16);
|
|
11699
|
+
const ciphertext = data.subarray(SALT_LENGTH + IV_LENGTH, -16);
|
|
11700
|
+
const derivedKey = pbkdf2Sync(actualKey, salt, ITERATIONS, 32, "sha256");
|
|
11701
|
+
const decipher = createDecipheriv(ALGORITHM, derivedKey, iv);
|
|
11702
|
+
decipher.setAuthTag(authTag);
|
|
11703
|
+
return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
|
|
11704
|
+
}
|
|
11705
|
+
function isUsingDefaultKey() {
|
|
11706
|
+
return !process.env.LATTICE_ENCRYPTION_KEY;
|
|
11707
|
+
}
|
|
11708
|
+
function validateEncryptionKey() {
|
|
11709
|
+
if (isUsingDefaultKey()) {
|
|
11710
|
+
if (process.env.NODE_ENV === "production") {
|
|
11711
|
+
console.warn(
|
|
11712
|
+
"\u26A0\uFE0F WARNING: Using default encryption key in production environment. Set LATTICE_ENCRYPTION_KEY environment variable for security."
|
|
11713
|
+
);
|
|
11714
|
+
} else {
|
|
11715
|
+
console.warn(
|
|
11716
|
+
"\u26A0\uFE0F WARNING: Using default encryption key. Set LATTICE_ENCRYPTION_KEY environment variable in production."
|
|
11717
|
+
);
|
|
11718
|
+
}
|
|
11719
|
+
}
|
|
11720
|
+
}
|
|
11721
|
+
function clearEncryptionKeyCache() {
|
|
11722
|
+
cachedKey = null;
|
|
11723
|
+
keyValidated = false;
|
|
11724
|
+
}
|
|
8523
11725
|
export {
|
|
8524
11726
|
AGENT_TASK_EVENT,
|
|
8525
11727
|
AgentConfig,
|
|
@@ -8528,42 +11730,73 @@ export {
|
|
|
8528
11730
|
AgentType,
|
|
8529
11731
|
ChunkBuffer,
|
|
8530
11732
|
ChunkBufferLatticeManager,
|
|
11733
|
+
CompositeBackend,
|
|
8531
11734
|
ConsoleLoggerClient,
|
|
8532
11735
|
DefaultScheduleClient,
|
|
11736
|
+
EMPTY_CONTENT_WARNING,
|
|
8533
11737
|
EmbeddingsLatticeManager,
|
|
8534
11738
|
FileSystemSkillStore,
|
|
11739
|
+
FilesystemBackend,
|
|
8535
11740
|
GraphBuildOptions,
|
|
8536
11741
|
InMemoryAssistantStore,
|
|
8537
11742
|
InMemoryChunkBuffer,
|
|
11743
|
+
InMemoryDatabaseConfigStore,
|
|
11744
|
+
InMemoryMailboxStore,
|
|
11745
|
+
InMemoryTaskListStore,
|
|
8538
11746
|
InMemoryThreadStore,
|
|
11747
|
+
LINE_NUMBER_WIDTH,
|
|
8539
11748
|
LoggerLatticeManager,
|
|
11749
|
+
MAX_LINE_LENGTH,
|
|
8540
11750
|
McpLatticeManager,
|
|
11751
|
+
MemoryBackend,
|
|
8541
11752
|
MemoryLatticeManager,
|
|
8542
11753
|
MemoryQueueClient,
|
|
8543
11754
|
MemoryScheduleStorage,
|
|
8544
11755
|
MemoryType,
|
|
11756
|
+
MessageType,
|
|
8545
11757
|
ModelLatticeManager,
|
|
8546
11758
|
PinoLoggerClient,
|
|
8547
11759
|
PostgresDatabase,
|
|
8548
11760
|
Protocols,
|
|
8549
11761
|
QueueLatticeManager,
|
|
11762
|
+
SandboxFilesystem,
|
|
8550
11763
|
SandboxLatticeManager,
|
|
8551
11764
|
ScheduleLatticeManager,
|
|
8552
11765
|
SkillLatticeManager,
|
|
8553
11766
|
SqlDatabaseManager,
|
|
11767
|
+
StateBackend,
|
|
11768
|
+
StoreBackend,
|
|
8554
11769
|
StoreLatticeManager,
|
|
11770
|
+
TOOL_RESULT_TOKEN_LIMIT,
|
|
11771
|
+
TRUNCATION_GUIDANCE,
|
|
11772
|
+
TaskStatus,
|
|
11773
|
+
TeamAgentGraphBuilder,
|
|
8555
11774
|
ThreadStatus,
|
|
8556
11775
|
ToolLatticeManager,
|
|
8557
11776
|
VectorStoreLatticeManager,
|
|
8558
11777
|
agentLatticeManager,
|
|
11778
|
+
buildGrepResultsDict,
|
|
11779
|
+
checkEmptyContent,
|
|
11780
|
+
clearEncryptionKeyCache,
|
|
11781
|
+
createAgentTeam,
|
|
11782
|
+
createFileData,
|
|
8559
11783
|
createInfoSqlTool,
|
|
8560
11784
|
createListTablesSqlTool,
|
|
8561
11785
|
createQueryCheckerSqlTool,
|
|
8562
11786
|
createQuerySqlTool,
|
|
11787
|
+
createTeamMiddleware,
|
|
11788
|
+
createTeammateTools,
|
|
11789
|
+
decrypt,
|
|
8563
11790
|
describeCronExpression,
|
|
8564
11791
|
embeddingsLatticeManager,
|
|
11792
|
+
encrypt,
|
|
8565
11793
|
eventBus,
|
|
8566
11794
|
event_bus_default as eventBusDefault,
|
|
11795
|
+
fileDataToString,
|
|
11796
|
+
formatContentWithLineNumbers,
|
|
11797
|
+
formatGrepMatches,
|
|
11798
|
+
formatGrepResults,
|
|
11799
|
+
formatReadResponse,
|
|
8567
11800
|
getAgentClient,
|
|
8568
11801
|
getAgentConfig,
|
|
8569
11802
|
getAgentLattice,
|
|
@@ -8573,6 +11806,7 @@ export {
|
|
|
8573
11806
|
getChunkBuffer,
|
|
8574
11807
|
getEmbeddingsClient,
|
|
8575
11808
|
getEmbeddingsLattice,
|
|
11809
|
+
getEncryptionKey,
|
|
8576
11810
|
getLoggerLattice,
|
|
8577
11811
|
getModelLattice,
|
|
8578
11812
|
getNextCronTime,
|
|
@@ -8585,7 +11819,11 @@ export {
|
|
|
8585
11819
|
getToolLattice,
|
|
8586
11820
|
getVectorStoreClient,
|
|
8587
11821
|
getVectorStoreLattice,
|
|
11822
|
+
globSearchFiles,
|
|
11823
|
+
grepMatchesFromFiles,
|
|
11824
|
+
grepSearchFiles,
|
|
8588
11825
|
hasChunkBuffer,
|
|
11826
|
+
isUsingDefaultKey,
|
|
8589
11827
|
isValidCronExpression,
|
|
8590
11828
|
isValidSandboxName,
|
|
8591
11829
|
isValidSkillName,
|
|
@@ -8594,6 +11832,7 @@ export {
|
|
|
8594
11832
|
modelLatticeManager,
|
|
8595
11833
|
normalizeSandboxName,
|
|
8596
11834
|
parseCronExpression,
|
|
11835
|
+
performStringReplacement,
|
|
8597
11836
|
queueLatticeManager,
|
|
8598
11837
|
registerAgentLattice,
|
|
8599
11838
|
registerAgentLattices,
|
|
@@ -8606,15 +11845,22 @@ export {
|
|
|
8606
11845
|
registerQueueLattice,
|
|
8607
11846
|
registerScheduleLattice,
|
|
8608
11847
|
registerStoreLattice,
|
|
11848
|
+
registerTeammateAgent,
|
|
8609
11849
|
registerToolLattice,
|
|
8610
11850
|
registerVectorStoreLattice,
|
|
8611
11851
|
sandboxLatticeManager,
|
|
11852
|
+
sanitizeToolCallId,
|
|
8612
11853
|
scheduleLatticeManager,
|
|
8613
11854
|
skillLatticeManager,
|
|
8614
11855
|
sqlDatabaseManager,
|
|
8615
11856
|
storeLatticeManager,
|
|
8616
11857
|
toolLatticeManager,
|
|
11858
|
+
truncateIfTooLong,
|
|
11859
|
+
unregisterTeammateAgent,
|
|
11860
|
+
updateFileData,
|
|
8617
11861
|
validateAgentInput,
|
|
11862
|
+
validateEncryptionKey,
|
|
11863
|
+
validatePath,
|
|
8618
11864
|
validateSkillName,
|
|
8619
11865
|
validateToolInput,
|
|
8620
11866
|
vectorStoreLatticeManager
|